247CTF - Angr-y Binary

Why waste time creating multiple functions, when you can just use one? Can you find the path to the flag in this angr-y binary?

Challenge Overview

The challenge provides a 32-bit ELF executable. The goal is to find the correct input that leads to the flag.

1
2
❯ file angr-y_binary
angr-y_binary: ELF 32-bit LSB executable, Intel i386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=611e939f262f927b8515162283d36476df2d3244, not stripped

Analysis

Using radare2 to inspect the disassembly, we identify three key functions: print_flag, no_flag, and maybe_flag.

Disassembly

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
     ;-- print_flag:
0x08048596 55 push ebp
0x08048597 89e5 mov ebp, esp
...
0x080485db e830feffff call sym.imp.fgets ;[3]
0x080485e0 83c410 add esp, 0x10
0x080485e3 83ec0c sub esp, 0xc
0x080485e6 8d45b4 lea eax, [ebp - 0x4c]
0x080485e9 50 push eax
0x080485ea e841feffff call sym.imp.puts ;[4]
...

;-- no_flag:
0x08048609 55 push ebp
0x0804860a 89e5 mov ebp, esp
0x0804860c e8d0005e00 call sym.__x86.get_pc_thunk.ax ;[6]
0x08048611 05ef195e00 add eax, 0x5e19ef
0x08048616 c780300000.. mov dword [eax + 0x30], 0
0x08048620 90 nop
0x08048621 5d pop ebp
0x08048622 c3 ret

;-- maybe_flag:
0x08048623 55 push ebp
0x08048624 89e5 mov ebp, esp
0x08048626 53 push ebx
0x08048627 83ec04 sub esp, 4
0x0804862a e8b2005e00 call sym.__x86.get_pc_thunk.ax ;[6]
0x0804862f 05d1195e00 add eax, 0x5e19d1
0x08048634 8b9030000000 mov edx, dword [eax + 0x30]
0x0804863a 85d2 test edx, edx
┌─< 0x0804863c 7407 je 0x8048645
│ 0x0804863e e853ffffff call sym.print_flag ;[7]
┌──< 0x08048643 eb14 jmp 0x8048659
│└─> 0x08048645 83ec0c sub esp, 0xc
...

The logic suggests we need to reach print_flag while avoiding the paths that lead to no_flag.

Solution

Since the binary’s path to the flag is complex, we use angr to perform symbolic execution and find the correct input.

Solver Script

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
import angr
import sys

def solve(bin_path):
project = angr.Project(bin_path)

# Initialize the state at entry
initial_state = project.factory.entry_state(
add_options = {
angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS
}
)

simulation = project.factory.simgr(initial_state)

# Explore searching for the print_flag function and avoiding no_flag
# print_flag: 0x08048596
# no_flag: 0x8048609
simulation.explore(find=0x08048596, avoid=0x8048609)

if simulation.found:
solution_state = simulation.found[0]
print(f"Found solution: {solution_state.posix.dumps(sys.stdin.fileno()).decode()}")
else:
raise Exception('Could not find the solution')

if __name__ == "__main__":
solve('./angr-y_binary')

Execution

Running the script gives us the password:

1
2
❯ python solve.py
wgIdWOS6Df9sCzAfiK

Connecting to the server with the found password:

1
2
3
❯ nc 0c4c28058a1f7a2f.247ctf.com 50230
Enter a valid password:
wgIdWOS6Df9sCzAfiK
247CTF{a3bbb9d2e648841d99e1cf4535a92945}

References

  • angr_ctf learn - jakespringer