articleAug 15, 2020
CodeGate 2017 angrybird writeup
CodeGate 2017 angrybird: patch early exits and canary-related checks, then use angr from 0x4007c2 to recover the 20-byte input that reaches the final printf.
Vulnerability analysis
Running Angry Bird

The binary prints nothing and exits immediately.
main

Stack smashing protection is on. The prologue pulls a canary from the TEB into rax and stores it on the stack. Then eax is cleared, compared to zero, and the path jumps to exit. That is why a normal run ends at once.
NOP sled past exit

Patch the _exit bytes to 90 (NOP) so the jump cannot terminate the process.


After that bypass, the binary reaches the string you should return 21 not 1 :( .
you should return 21 not 1 :(
The message says the return value must be 21. Dig into that path next.
sub_4006F6

Inside the block that printed the message, check dword_606060.

It holds 1. Patch it to 21.

The return value is then written with a mov into [rbp+n]. Operand 2 is eax (32-bit), so a mismatched width triggers a segfault.


NOP that store out. Do the same for sub_40070C.

sub_40072A

This function compares input against "hello" and only continues on a match. 0x606038 turned out to be a GOT slot for __libc_start_main; Ghidra helped clarify which string comparison was happening.

NOP that check as well.

Filled solid. ๐

After the patches, fgets finally accepts input.
The long condition chain

Many conditions sit between input and the final "you typed : %s\n" path.

Use angr to solve for the input.
Solution
Solved code
import angr
def solved():
p=angr.Project('./angrybird_3', auto_load_libs=False)
# state๋ฅผ ์์๋ก ์ ํจ์๋ฅผ ๋ฌด์ํ์ฑ ํด๋น ์ง์ญ๋ถํฐ ์์ํ๋๋ก ํ๋ค.
state = p.factory.blank_state(addr=0x4007C2)
sm = p.factory.simgr(state)
sm.explore(find=0x404fab) # ์ตํ๋จ True ์ฃผ์ ๊ฐ
flag = sm.found[0].posix.dumps(0)
print(flag[:20])Solved code 2
You can also skip the binary patches and let angr start mid-function.

Ignore sub_4006F6 / sub_40070C, set rbp as if the prologue had run at 0x4007c2, and seed the locals the later checks expect.

import angr
START_ADDR = 0x4007c2
FIND_ADDR = 0x404fab # This is right before the printf
def main():
proj = angr.Project('./angrybird')
# ํด๋น ๋ฐ์ด๋๋ฆฌ์์ ํจ์ ๋ถ๋ถ์ ์ฐํ ํ๋ค.
state = proj.factory.entry_state(addr=START_ADDR)
# ํจ์ ํ๋กค๋ก๊ทธ ๋ณ๊ฒฝ
state.regs.rbp = state.regs.rsp
# 0x40์ ๊ธธ์ด ๊ฐ
state.mem[state.regs.rbp - 0x74].int = 0x40
state.mem[state.regs.rbp - 0x70].long = 0x1000 # strncmp@got
state.mem[state.regs.rbp - 0x68].long = 0x1008 # puts@got
state.mem[state.regs.rbp - 0x60].long = 0x1010 # _stack_chk_fail@got
state.mem[state.regs.rbp - 0x58].long = 0x1018 # _lib_start_main
sm = proj.factory.simulation_manager(state) # ์๋ฎฌ๋ ์ด์
์์ฑ
sm.explore(find=FIND_ADDR)
found = sm.found[-1]
flag = found.posix.dumps(0)
print(flag[:20])
if __name__ == '__main__':
main()