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

Waiting on input. π
Binary info

main

Python>hex(end-fisrt)
0x18fL
Python>0x18f
399A loop runs 0x14 times and reads into unk_212E0. That buffer is 400 bytes.

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.

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

_end

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

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

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.

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 domov 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.

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.

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()