angstromCTF 2019 High Quality Checks Writeup

I’ll keep this quick and short because this wasn’t a very hard challenge but I wanted to demonstrate how easy it is to solve simpler “crackmes” in CTFs by using a tool like angr. This one is called High Quality Checks from angstromCTF 2019:

High Quality Checks - Rev (110 points)

After two break-ins to his shell server, kmh got super paranoid about a third! He’s so paranoid that he abandoned the traditional password storage method and came up with this monstrosity! I reckon he used the flag as the password, can you find it?

Binary: High Quality Checks


I’ll first run some checks on this file:

1
2
$ file high_quality_checks
high_quality_checks: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=e7556b55e0c73b4de8b3f387571dd59c3535a0ee, not stripped
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ readelf -h high_quality_checks
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x400520
Start of program headers: 64 (bytes into file)
Start of section headers: 11016 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 29
Section header string table index: 28

Alright, everything looks normal. Running the binary a couple times to see what it does:

1
2
3
4
5
6
7
8
$ ./high_quality_checks
Enter your input:
skdjflf
Flag is too short.
$ ./high_quality_checks
Enter your input:
ajskldfjkdlsjflfjlsdkjfsdlfjdsljfld
That's not the flag.

Quick thing I noted: the program will tell me if my input is too short. This ends up not being useful at all later but I think it’s still good to notice these subtle differences.

On the surface, this seems like a simple crackme. Here is what it looks like in Ghidra analyzed with the default options:

High Quality Checks in Ghidra

A closer look at the decompiled main() function:

main() function in Ghidra

So the flag is at least 0x13 (or 19) characters long. Looks pretty clear to me that the check() function is the important part. Here is the decompilation of that function in Ghidra:

check() function in Ghidra

Err… this doesn’t look so fun. Without posting a screenshot for every individual one-letter function, I can tell that each individual function is very annoying to parse and calculate by hand. I decided to approach this another way: angr!

What is angr, you ask?

From http://angr.io/:

angr is a python framework for analyzing binaries. It combines both static and dynamic symbolic (“concolic”) analysis, making it applicable to a variety of tasks.

One of the simpler things that angr can do is run a binary and attempt to reach a desired branch or state in the program execution and tell you what the output at that point in the binary is, or (more importantly for what we’re doing here) what input allows you to reach that specific branch in the program.

I’ll post the source code for the angr script high_quality_checks_angr.py I used to get the flag and then describe more or less what is happening:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import angr

FIND_ADDR = 0x00400ad2
AVOID_ADDR = 0x00400ae0

def main():
proj = angr.Project('high_quality_checks', load_options={"auto_load_libs": False})

sm = proj.factory.simulation_manager()
sm.explore(find=FIND_ADDR, avoid=AVOID_ADDR)

return sm.found[0].posix.dumps(0)

if __name__ == '__main__':
print(repr(main()))

Here’s a bit of a breakdown:

  • Line 7 initializes the angr project with the path to the binary to analyze/run.
  • Line 9 initializes my “simulation manager” which allows me to control symbolic execution over states of the program. It allows me to apply some parameters to how I search for states in the program.
  • Line 10 is where I tell the simulation manager that I want to reach a certain instruction address (FIND_ADDR) in the execution while avoiding another address (AVOID_ADDR). More in a bit on how I chose these addresses…
  • Line 12 is where I return the stdin (which by default on Linux is file descriptor 0) of the first found state that satisfies the parameters given.

Choosing FIND_ADDR and AVOID_ADDR are simple:

1
2
3
4
5
6
7
8
9
10
11
12
$ objdump -d high_quality_checks
0000000000400a5b <main>:
...
400ac2: e8 83 fe ff ff callq 40094a <check>
400ac7: 85 c0 test %eax,%eax
400ac9: 74 0e je 400ad9 <main+0x7e>
400acb: 48 8d 3d dc 00 00 00 lea 0xdc(%rip),%rdi # 400bae <_IO_stdin_used+0x2e>
400ad2: e8 09 fa ff ff callq 4004e0 <puts@plt>
400ad7: eb 0c jmp 400ae5 <main+0x8a>
400ad9: 48 8d 3d e2 00 00 00 lea 0xe2(%rip),%rdi # 400bc2 <_IO_stdin_used+0x42>
400ae0: e8 fb f9 ff ff callq 4004e0 <puts@plt>
...

FIND_ADDR is the address of the puts() call where You found the flag! is printed to stdout, while AVOID_ADDR is the address of the puts() call where That's not the flag. is printed.

angr isn’t all too difficult to install but I much prefer to run it from within a docker container.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ docker run --name angr -it angr/angr
(angr) angr@da6c4b11289a:~$

(in another terminal window I copy over the binary and the angr script)
$ docker cp high_quality_checks_angr.py angr:/home/angr/high_quality_checks_angr.py
$ docker cp high_quality_checks angr:/home/angr/high_quality_checks

(angr) angr@da6c4b11289a:~$ python high_quality_checks_angr.py
WARNING | 2019-04-25 02:07:09,244 | angr.state_plugins.symbolic_memory | The program is accessing memory or registers with an unspecified value. This could indicate unwanted behavior.
WARNING | 2019-04-25 02:07:09,245 | angr.state_plugins.symbolic_memory | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by:
WARNING | 2019-04-25 02:07:09,245 | angr.state_plugins.symbolic_memory | 1) setting a value to the initial state
WARNING | 2019-04-25 02:07:09,245 | angr.state_plugins.symbolic_memory | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | 2019-04-25 02:07:09,245 | angr.state_plugins.symbolic_memory | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY_REGISTERS}, to suppress these messages.
WARNING | 2019-04-25 02:07:09,247 | angr.state_plugins.symbolic_memory | Filling register r15 with 8 unconstrained bytes referenced from 0x400b00 (__libc_csu_init+0x0 in high_quality_checks (0x400b00))
WARNING | 2019-04-25 02:07:09,250 | angr.state_plugins.symbolic_memory | Filling register r14 with 8 unconstrained bytes referenced from 0x400b02 (__libc_csu_init+0x2 in high_quality_checks (0x400b02))
WARNING | 2019-04-25 02:07:09,253 | angr.state_plugins.symbolic_memory | Filling register r13 with 8 unconstrained bytes referenced from 0x400b07 (__libc_csu_init+0x7 in high_quality_checks (0x400b07))
WARNING | 2019-04-25 02:07:09,256 | angr.state_plugins.symbolic_memory | Filling register r12 with 8 unconstrained bytes referenced from 0x400b09 (__libc_csu_init+0x9 in high_quality_checks (0x400b09))
WARNING | 2019-04-25 02:07:09,261 | angr.state_plugins.symbolic_memory | Filling register rbx with 8 unconstrained bytes referenced from 0x400b1a (__libc_csu_init+0x1a in high_quality_checks (0x400b1a))
WARNING | 2019-04-25 02:07:09,315 | angr.state_plugins.symbolic_memory | Filling register cc_ndep with 8 unconstrained bytes referenced from 0x4005b1 (register_tm_clones+0x21 in high_quality_checks (0x4005b1))
WARNING | 2019-04-25 02:07:09,589 | angr.state_plugins.symbolic_memory | Filling memory at 0x7ffffffffff0000 with 64 unconstrained bytes referenced from 0x1000010 (strlen+0x0 in extern-address space (0x10))
WARNING | 2019-04-25 02:07:09,591 | angr.state_plugins.symbolic_memory | Filling memory at 0x7fffffffffeff70 with 8 unconstrained bytes referenced from 0x1000010 (strlen+0x0 in extern-address space (0x10))
b'actf{fun_func710n5}'
(angr) angr@da6c4b11289a:~$

Easy as that! The flag is given by angr: actf{fun_func710n5}.