Gravitational plate of three masses and a slashed discABC0Static engraved plate. Three-dimensional view is unavailable or reduced motion is requested.

← back to fieldarticle

articleAug 16, 2020

HITCON 2017 Sakura writeup

HITCON 2017 Sakura: 400 bytes of input feed a huge sub_850 checker; angr finds satisfying paths by locating the shared false-store pattern and exploring every third true branch.

Vulnerability analysis

Running the binary

./0.png
./0.png

Waiting on input. πŸ˜‘

Binary info

./1.png
./1.png

main

./2.png
./2.png

Python>hex(end-fisrt)
0x18fL
Python>0x18f
399

A loop runs 0x14 times and reads into unk_212E0. That buffer is 400 bytes.

./3.png
./3.png

After the 400-byte read, main calls sub_850(unk_212E0) and prints the flag when the return value is non-zero. Between prints you also see sub_10FF6.

./4.png
./4.png

That helper SHA-256-hashes output. The interesting question is which conditions on the input make the hash path succeed.

sub_850

_start

./5.png
./5.png

_end

./6.png
./6.png

Start-to-end span is 67,493 bytes. Huge.

./7.png
./7.png

The prologue alone allocates 0x1E60 of stack and initializes a pile of locals.

./8.png
./8.png

A repeating pattern: loop, compare, and on mismatch mov [rbp+var_1E49], 0. On match, set mov [rbp+var_1E48], 0 and keep going toward ret.

./9.png
./9.png

The returned value follows the same pattern. We need a non-zero result driven by the input logic β€” specifically paths that can reach movzx eax, [rbp+var_1E49] successfully rather than the zeroing stores.

Solution

Approach

Use angr and treat sub_850 as a Boolean SAT problem.

  • True targets: the jz-taken sites that do mov eax, [...]
  • False / avoid sites: the untaken path that does mov [rbp+var_1E49], 0

The avoid bytes are thankfully identical everywhere: C6 85 B7 E1 FF FF 00.

./10.png
./10.png

True sites sit seven bytes after each False store. Do not accept every True address β€” skip the first two of each triplet and keep the third.

./11.png
./11.png

Solved code 1

import angr
from pwn import *
PATH='./sakura'
 
def main():
    global PATH
		# λ°”μ΄νŠΈλ‘œ μ½μ–΄μ˜€λ„λ‘ ν•œλ‹€.
    data = open("./sakura", "rb").read()
 
    find_list = []
    avoid_list = []
    idx = 0
    cnt = 0
 
    while True:
				# avoid ν•  νŒ¨ν„΄
        res = data.find(b"\xC6\x85\xB7\xE1\xFF\xFF\x00", idx)
        if res == -1:
            break
        # 베이슀 μ£Όμ†ŒλŠ” 0x100000을 ν•˜λ“  상관 μ—†λ‹€.
        avoid_list.append(0x400000 + res)
        # find 쑰건 
        if cnt % 3 == 2:
            find_list.append(0x400000 + res + 7)
            
        cnt += 1
        idx = res + 1 # aovid νŒ¨ν„΄μ˜ μ˜€ν”„μ…‹λ§ŒνΌ λ”ν•΄κ°€λ©΄μ„œ λ‹€μŒ μ˜€ν”„μ…‹μ„ μ°Ύμ•„λ‚Έλ‹€.
 
    p = angr.Project('./sakura')
    state = p.factory.entry_state()
 
    for find in find_list:
        sm=p.factory.simgr(state)
				# 순차적으둜 find쑰건을 만쑱 ν•˜λŠ”μ§€λ₯΄ ν™•μΈν•˜κΈ° μœ„ν•΄ for문을 λŒλ Έλ‹€.
				# 루프λ₯Ό λŒλ¦¬μ§€ μ•Šκ³  ν•œλ²ˆμ— λ°”λ‘œ 해도 λœλ‹€.
        sm.explore(find=find, avoid=avoid_list)
        state=sm.found[0]
				print(state)
        
    p=process(PATH) # ν”„λ‘œμ„ΈμŠ€ attach
    p.send(state.posix.dumps(0)) # 좜λ ₯ 값을 read ν•¨μˆ˜λ‘œ 보내버린닀.
    flag = p.recvline() # 응닡 λ°›κ³  좜λ ₯
    log.info(repr(flag))
 
if __name__ == "__main__":
    main()

./12.png
./12.png

related

  1. Aug 15, 2020/articleCodeGate 2017 angrybird writeup
  2. Aug 14, 2020/archiveangr binary analysis notes 2: the loader
  3. Sep 18, 2020/articleDEF CON 2016 feedme Vulnerability Analysis

graphfeed