/ REV, CTF, CODE

Breaking CMU's Bomblab with Angr for Fun and Profit - Part 4

Welcome back to Part 4 of cracking CMU’s Bomblab using Angr! If you just stumbled upon this, I would recommend starting with part 1 here.

Phase 4

Let’s disassemble Phase 4:

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
gef  disas phase_4
Dump of assembler code for function phase_4:
   0x000000000040100c <+0>:	sub    rsp,0x18
   0x0000000000401010 <+4>:	lea    rcx,[rsp+0xc]
   0x0000000000401015 <+9>:	lea    rdx,[rsp+0x8]
   0x000000000040101a <+14>:	mov    esi,0x4025cf
   0x000000000040101f <+19>:	mov    eax,0x0
   0x0000000000401024 <+24>:	call   0x400bf0 <__isoc99_sscanf@plt>
   0x0000000000401029 <+29>:	cmp    eax,0x2
   0x000000000040102c <+32>:	jne    0x401035 <phase_4+41>
   0x000000000040102e <+34>:	cmp    DWORD PTR [rsp+0x8],0xe
   0x0000000000401033 <+39>:	jbe    0x40103a <phase_4+46>
   0x0000000000401035 <+41>:	call   0x40143a <explode_bomb>
   0x000000000040103a <+46>:	mov    edx,0xe
   0x000000000040103f <+51>:	mov    esi,0x0
   0x0000000000401044 <+56>:	mov    edi,DWORD PTR [rsp+0x8]
   0x0000000000401048 <+60>:	call   0x400fce <func4>
   0x000000000040104d <+65>:	test   eax,eax
   0x000000000040104f <+67>:	jne    0x401058 <phase_4+76>
   0x0000000000401051 <+69>:	cmp    DWORD PTR [rsp+0xc],0x0
   0x0000000000401056 <+74>:	je     0x40105d <phase_4+81>
   0x0000000000401058 <+76>:	call   0x40143a <explode_bomb>
   0x000000000040105d <+81>:	add    rsp,0x18
   0x0000000000401061 <+85>:	ret    
End of assembler dump.

The sscanf and the call to func4 is interesting. Let’s check out func4 first:

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
gef  disas func4
Dump of assembler code for function func4:
   0x0000000000400fce <+0>:	sub    rsp,0x8
   0x0000000000400fd2 <+4>:	mov    eax,edx
   0x0000000000400fd4 <+6>:	sub    eax,esi
   0x0000000000400fd6 <+8>:	mov    ecx,eax
   0x0000000000400fd8 <+10>:	shr    ecx,0x1f
   0x0000000000400fdb <+13>:	add    eax,ecx
   0x0000000000400fdd <+15>:	sar    eax,1
   0x0000000000400fdf <+17>:	lea    ecx,[rax+rsi*1]
   0x0000000000400fe2 <+20>:	cmp    ecx,edi
   0x0000000000400fe4 <+22>:	jle    0x400ff2 <func4+36>
   0x0000000000400fe6 <+24>:	lea    edx,[rcx-0x1]
   0x0000000000400fe9 <+27>:	call   0x400fce <func4>
   0x0000000000400fee <+32>:	add    eax,eax
   0x0000000000400ff0 <+34>:	jmp    0x401007 <func4+57>
   0x0000000000400ff2 <+36>:	mov    eax,0x0
   0x0000000000400ff7 <+41>:	cmp    ecx,edi
   0x0000000000400ff9 <+43>:	jge    0x401007 <func4+57>
   0x0000000000400ffb <+45>:	lea    esi,[rcx+0x1]
   0x0000000000400ffe <+48>:	call   0x400fce <func4>
   0x0000000000401003 <+53>:	lea    eax,[rax+rax*1+0x1]
   0x0000000000401007 <+57>:	add    rsp,0x8
   0x000000000040100b <+61>:	ret    
End of assembler dump.

I was so happy when I saw this. There is no input expected at all, and everything is self contained, so we could basically ignore this whole function.

Let’s see the format string for sscanf again to see what’s expected of us:

1
2
gef  x/s 0x4025cf
0x4025cf:	"%d %d"

Wow, another 2 integers? They are surely making our life very easy. In fact, even the stack offsets of the arguments that sscanf extracts to are the same as in Phase 3, of being 0x8 and 0xc from the stack base.

We can literally re-use the same exploit script as Phase 3, except we need to change the start address and the find address accordingly.

Full Solution 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import angr
import claripy
import sys

def phase_4(argv):
    path_to_binary = argv[1]
    project = angr.Project(path_to_binary)

    # Tell Angr where to start executing 
    start_addr = 0x00401029
    initial_state = project.factory.blank_state(addr=start_addr)

    num_12 = claripy.BVS('num_12', 64)

    initial_state.stack_push(num_12)

    padding_length_in_bytes = 8
    initial_state.regs.rsp -= padding_length_in_bytes

    
    # Create a simulation manager initialized with the starting state
    simulation = project.factory.simgr(initial_state)

    success_addr = 0x00401061 # right before ret
    explode_addr = 0x0040143a # explode_bomb

    simulation.explore(find=success_addr, avoid=explode_addr)

    # Check that we have found a solution
    if simulation.found:
        solution_state = simulation.found[0]

        num_12_sol = solution_state.se.eval(num_12, cast_to=int)

        def unpack_ints(n):
            lower_32_mask = (1 << 32) - 1
            return (n & lower_32_mask, (n >> 32) & lower_32_mask)

        num_1_sol, num_2_sol = unpack_ints(num_12_sol)

        print(f"{num_1_sol} {num_2_sol}")
    else:
        raise Exception('Could not find the solution')

if __name__ == '__main__':
    phase_4(sys.argv)

If we run it, we get the following:

1
2
3
4
5
6
7
8
9
$ python solve.py bomb
WARNING | 2020-08-02 19:55:49,080 | angr.state_plugins.symbolic_memory | The program is accessing memory or registers with an unspecified value. This could indicate unwanted behavior.
WARNING | 2020-08-02 19:55:49,081 | angr.state_plugins.symbolic_memory | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by:
WARNING | 2020-08-02 19:55:49,081 | angr.state_plugins.symbolic_memory | 1) setting a value to the initial state
WARNING | 2020-08-02 19:55:49,081 | angr.state_plugins.symbolic_memory | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | 2020-08-02 19:55:49,081 | angr.state_plugins.symbolic_memory | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY_REGISTERS}, to suppress these messages.
WARNING | 2020-08-02 19:55:49,081 | angr.state_plugins.symbolic_memory | Filling register rax with 8 unconstrained bytes referenced from 0x401029 (phase_4+0x1d in bomb (0x401029))
CRITICAL | 2020-08-02 19:55:49,906 | angr.sim_state | The name state.se is deprecated; please use state.solver.
7 0

Trying it on the bomb itself:

1
2
3
4
5
6
7
8
9
10
11
$ ./bomb
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Border relations with Canada have never been better.
Phase 1 defused. How about the next one?
1 2 4 8 16 32
That's number 2.  Keep going!
1 311
Halfway there!
7 0
So you got that one.  Try this one.

Easy peasy lemon squeezy! This took basically no effort at all. You can continue on to Part 5 here.