18 minute read

Introduction


I’ve started a course on Modern Binary Exploitation and throughout this course there are challenges and labs with which to practice exploitation techniques. It starts with an introduction to reverse engineering and I’ve decided to write about how I solved the challenges and take notes of the things I learned.

This (as you can see) is the second post and it will focus on the bombs challenges from the “Extended Reverse Engineering” class. It came out too long, so this post is going to be about only the bomb executable. The cmubomb and crackme0x04_win.exe will be in separate posts.

If you plan to take the course I highly encourage you not to read any further and try to solve the challenges yourself. You’ll learn much more that way.

This time I’ll make use of gdb(pwndbg) and IDA. Also for some of the challenges you don’t need to look at every assembly instruction but just looking at the function calls and byte/string comparisons you can immediately understand what input is expected without reversing the whole thing.

bomb

After starting the bomb executable, we’re presented with four options - YELLOW, GREEN, BLUE and RED, corresponding to the wires that we need to cut to deactivate the bomb. When we choose an option we are asked about a password. Entering the right password cuts the wire and entering the wrong password blows the bomb. Our mission is to reverse the algorithms and find the passwords for each wire and deactivate the bomb.

Let’s start with Yellow.

Yellow

bomb_yellow_1

First I list the functions that are present in the executable. I assume that the function yellow handles the logic for the yellow option and disassembling yellow immediately shows something interesting.

$ gdb
pwndbg> file bomb
Reading symbols from bomb...done.

pwndbg> info functions 
All defined functions:

Non-debugging symbols:

...............
output omitted
...............

0x08048a4c  menu
0x08048fb4  kaboom
0x08048fc8  libc_start_main
0x080491cf  disarm_handler
0x080494ca  main
0x080496e8  yellow_preflight
0x08049719  yellow
0x080497a4  red_preflight
0x08049831  red
0x080498cc  postred
0x080498d4  green_preflight
0x08049904  green
0x080499c0  blue_preflight
0x080499f1  blue

...............
output omitted
...............

pwndbg> disass yellow
Dump of assembler code for function yellow:
   0x08049719 <+0>: push   ebp
   0x0804971a <+1>: mov    ebp,esp
   0x0804971c <+3>: sub    esp,0x8
   0x0804971f <+6>: call   0x80496e8 <yellow_preflight>
   0x08049724 <+11>:  movzx  eax,BYTE PTR ds:0x804c24c
   ; 0x38 = ascii 8
   0x0804972b <+18>:  cmp    al,0x38
   0x0804972d <+20>:  jne    0x804977c <yellow+99>
   0x0804972f <+22>:  movzx  eax,BYTE PTR ds:0x804c24d
   ; 0x34 = ascii 4
   0x08049736 <+29>:  cmp    al,0x34
   0x08049738 <+31>:  jne    0x804977c <yellow+99>
   0x0804973a <+33>:  movzx  eax,BYTE PTR ds:0x804c24e
   ; 0x33 = ascii 3
   0x08049741 <+40>:  cmp    al,0x33
   0x08049743 <+42>:  jne    0x804977c <yellow+99>
   0x08049745 <+44>:  movzx  eax,BYTE PTR ds:0x804c24f
   ; 0x37 = ascii 7
   0x0804974c <+51>:  cmp    al,0x37
   0x0804974e <+53>:  jne    0x804977c <yellow+99>
   0x08049750 <+55>:  movzx  eax,BYTE PTR ds:0x804c250
   ; 0x31= ascii 1
   0x08049757 <+62>:  cmp    al,0x31
   0x08049759 <+64>:  jne    0x804977c <yellow+99>
   0x0804975b <+66>:  movzx  eax,BYTE PTR ds:0x804c251
   ; 0x30 = ascii 0
   0x08049762 <+73>:  cmp    al,0x30
   0x08049764 <+75>:  jne    0x804977c <yellow+99>
   0x08049766 <+77>:  movzx  eax,BYTE PTR ds:0x804c252
   ; 0x36 = ascii 6
   0x0804976d <+84>:  cmp    al,0x36
   0x0804976f <+86>:  jne    0x804977c <yellow+99>
   0x08049771 <+88>:  movzx  eax,BYTE PTR ds:0x804c253
   ; 0x35 = ascii 5
   0x08049778 <+95>:  cmp    al,0x35
   0x0804977a <+97>:  je     0x804978b <yellow+114>
   0x0804977c <+99>:  mov    eax,ds:0x804c124
   0x08049781 <+104>: shl    eax,0xa
   0x08049784 <+107>: mov    ds:0x804c124,eax
   0x08049789 <+112>: jmp    0x80497a1 <yellow+136>
   0x0804978b <+114>: mov    DWORD PTR [esp],0x804a1f4
   0x08049792 <+121>: call   0x80487b4 <puts@plt>
   0x08049797 <+126>: mov    DWORD PTR ds:0x804c124,0x0
   0x080497a1 <+136>: leave  
   0x080497a2 <+137>: ret    

   ; password is 84371065

There is a comparison of each consecutive byte from ds:0x804c24c to ds:0x804c253 with the bytes 0x38, 0x34, 0x33, 0x37, 0x31, 0x30, 0x36, 0x35 respectively. The ascii representation of those bytes is 84371065.

I bet that our input is stored at ds:0x804c24c and is checked against 84371065.

Disassembling yellow_preflight also validates my suspicion that our input is stored at ds:0x804c24c. I guess all preflight functions just read our input.

And to try the password: bomb_yellow_2

It worked! We cut the yellow wire.

Green

Disassembling green it’s easy to notice where our input is stored and that there is a string comparison with a string at address 0x804a2c0.

pwndbg> disass green
Dump of assembler code for function green:
   0x08049904 <+0>: push   ebp
   0x08049905 <+1>: mov    ebp,esp
   0x08049907 <+3>: sub    esp,0x38
   0x0804990a <+6>: mov    eax,gs:0x14
   0x08049910 <+12>:  mov    DWORD PTR [ebp-0x4],eax
   0x08049913 <+15>:  xor    eax,eax
   0x08049915 <+17>:  mov    DWORD PTR [ebp-0x8],0x1
   ; [ebp-0x14] = user_input
   0x0804991c <+24>:  lea    eax,[ebp-0x14]
   0x0804991f <+27>:  mov    DWORD PTR [esp],eax
   0x08049922 <+30>:  call   0x80498d4 <green_preflight>
   0x08049927 <+35>:  mov    DWORD PTR [esp+0x8],0x8
   0x0804992f <+43>:  lea    eax,[ebp-0x14]
   0x08049932 <+46>:  mov    DWORD PTR [esp+0x4],eax
   0x08049936 <+50>:  mov    DWORD PTR [esp],0x804a2c0
   ; strncmp(0x804a2c0, user_input, 8)
   0x0804993d <+57>:  call   0x80487d4 <strncmp@plt>
   0x08049942 <+62>:  test   eax,eax
   ; to go to 'UNLOCK PASSWORD ACCEPTED' strings should be equal
   0x08049944 <+64>:  jne    0x804998e <green+138>
   0x08049946 <+66>:  mov    DWORD PTR [esp],0x804a2fc
   ; puts('UNLOCK PASSWORD ACCEPTED')
   0x0804994d <+73>:  call   0x80487b4 <puts@plt>
   0x08049952 <+78>:  mov    eax,DWORD PTR [ebp-0x8]
   0x08049955 <+81>:  and    eax,0x1
   0x08049958 <+84>:  test   eax,eax
   0x0804995a <+86>:  sete   al
   0x0804995d <+89>:  movzx  eax,al
   0x08049960 <+92>:  mov    DWORD PTR [ebp-0x8],eax
   0x08049963 <+95>:  mov    DWORD PTR [esp],0x7a120
   0x0804996a <+102>: call   0x8048724 <usleep@plt>
   0x0804996f <+107>: mov    DWORD PTR [esp],0x804a33c
   ; puts('ACTION OVERRIDEN....')
   0x08049976 <+114>: call   0x80487b4 <puts@plt>
   0x0804997b <+119>: mov    eax,DWORD PTR [ebp-0x8]
   0x0804997e <+122>: and    eax,0x1
   0x08049981 <+125>: test   eax,eax
   0x08049983 <+127>: sete   al
   0x08049986 <+130>: movzx  eax,al
   0x08049989 <+133>: mov    DWORD PTR [ebp-0x8],eax
   0x0804998c <+136>: jmp    0x804999a <green+150>
   0x0804998e <+138>: mov    eax,ds:0x804c12c
   0x08049993 <+143>: add    eax,eax
   0x08049995 <+145>: mov    ds:0x804c12c,eax
   0x0804999a <+150>: mov    eax,DWORD PTR [ebp-0x8]
   0x0804999d <+153>: test   eax,eax
   0x0804999f <+155>: jne    0x80499ad <green+169>
   0x080499a1 <+157>: mov    eax,ds:0x804c12c
   0x080499a6 <+162>: sar    eax,1
   0x080499a8 <+164>: mov    ds:0x804c12c,eax
   0x080499ad <+169>: mov    eax,DWORD PTR [ebp-0x4]
   0x080499b0 <+172>: xor    eax,DWORD PTR gs:0x14
   0x080499b7 <+179>: je     0x80499be <green+186>
   0x080499b9 <+181>: call   0x8048784 <__stack_chk_fail@plt>
   0x080499be <+186>: leave  
   0x080499bf <+187>: ret   

And as you can see the address 0x804a2c0 points to the string "dcaotdae".

pwndbg> x/s 0x804a2c0
0x804a2c0 <password>: "dcaotdae"

bomb_green_1

Blue

pwndbg> disass blue_preflight 
Dump of assembler code for function blue_preflight:
   0x080499c0 <+0>: push   ebp
   0x080499c1 <+1>: mov    ebp,esp
   0x080499c3 <+3>: sub    esp,0x18
   0x080499c6 <+6>: mov    DWORD PTR [esp],0x804a388
   0x080499cd <+13>:  call   0x8048744 <printf@plt>
   0x080499d2 <+18>:  mov    eax,ds:0x804c220
   0x080499d7 <+23>:  mov    DWORD PTR [esp+0x8],eax
   0x080499db <+27>:  mov    DWORD PTR [esp+0x4],0x10
   ; user_input is saved at 0x804c24c; max of 16 characters
   0x080499e3 <+35>:  mov    DWORD PTR [esp],0x804c24c
   0x080499ea <+42>:  call   0x8048704 <fgets@plt>
   0x080499ef <+47>:  leave  
   0x080499f0 <+48>:  ret  

pwndbg> disass blue
Dump of assembler code for function blue:
   0x080499f1 <+0>: push   ebp
   0x080499f2 <+1>: mov    ebp,esp
   0x080499f4 <+3>: sub    esp,0x18
   0x080499f7 <+6>: call   0x80499c0 <blue_preflight>
   ; [ebp-0x4] =: 0x804c160 
   0x080499fc <+11>:  mov    DWORD PTR [ebp-0x4],0x804c160
   ....

Hmm… Let’s see what’s at address 0x804c160

pwndbg> x/x 0x804c160
0x804c160 <graph>:	0x0804c19c

It’s a variable called graph and it appears to hold value that looks like an address. Let’s check if it poinsts to anywhere interesting.

pwndbg> x/x 0x0804c19c
0x804c19c <graph+60>:	0x0804c1cc

Points another address that belongs to the same variable and holds another address. I guess it’s going to be the same for this one.

pwndbg> x/x 0x0804c1cc
0x804c1cc <graph+108>:	0x0804c1f0

Yep… Ok, let’s continue analysing the disassembly.

pwndbg> disass blue
Dump of assembler code for function blue:
   0x080499f1 <+0>: push   ebp
   0x080499f2 <+1>: mov    ebp,esp
   0x080499f4 <+3>: sub    esp,0x18
   0x080499f7 <+6>: call   0x80499c0 <blue_preflight>
   ; [ebp-0x4] =: 0x804c160 
   ; 0x804c160 is address of the graph variable
   0x080499fc <+11>:  mov    DWORD PTR [ebp-0x4],0x804c160
   ; eax := 0x804c160
   0x08049a03 <+18>:  mov    eax,DWORD PTR [ebp-0x4]
   ; eax := the value at [0x804c160 + 0x4] 
   0x08049a06 <+21>:  mov    eax,DWORD PTR [eax+0x4]
   ; [ebp-0x8] is var1
   ; var1 := the value at [0x804c160 + 0x4] 
   0x08049a09 <+24>:  mov    DWORD PTR [ebp-0x8],eax

And now to see what’s at address 0x804c160 + 0x4 :

pwndbg> x/x 0x804c160 + 0x4
0x804c164 <graph+4>:	0x47bbfa96

We’re at the same variable, but at this address it doen’t hold a valid address as a value. I’m going to check several consecutive values.

0x804c160 <graph>:	0x0804c19c ;valid address
pwndbg> x/x 0x804c160 + 4
0x804c164 <graph+4>:	0x47bbfa96 ; some value
pwndbg> x/x 0x804c160 + 8
0x804c168 <graph+8>:	0x0804c178 ; valid address
pwndbg> x/x 0x804c160 + 12
0x804c16c <graph+12>:	0x0804c214 ; valid address
pwndbg> x/x 0x804c160 + 16 
0x804c170 <graph+16>:	0x50171a6e ; some value
pwndbg> x/x 0x804c160 + 20
0x804c174 <graph+20>:	0x0804c1b4 ; valid address
pwndbg> x/x 0x804c160 + 24
0x804c178 <graph+24>:	0x0804c1d8 ; valid address
pwndbg> x/x 0x804c160 + 28
0x804c17c <graph+28>:	0x23daf3f1 ; some value
pwndbg> x/x 0x804c160 + 32
0x804c180 <graph+32>:	0x0804c1a8 ; valid address
pwndbg> x/x 0x804c160 + 36
0x804c184 <graph+36>:	0x0804c19c ; valid address
pwndbg> x/x 0x804c160 + 40
0x804c188 <graph+40>:	0x634284d3 ; some value
pwndbg> x/x 0x804c160 + 44
0x804c18c <graph+44>:	0x0804c1c0 ; valid address

And a pattern emerges. For now I’ll continue analysing the disassembly.

pwndbg> disass blue
Dump of assembler code for function blue:
   0x080499f1 <+0>: push   ebp
   0x080499f2 <+1>: mov    ebp,esp
   0x080499f4 <+3>: sub    esp,0x18
   0x080499f7 <+6>: call   0x80499c0 <blue_preflight>
   ; [ebp-0x4] =: 0x804c160 
   ; 0x804c160 is address of the graph variable
   0x080499fc <+11>:  mov    DWORD PTR [ebp-0x4],0x804c160
   ; eax := 0x804c160
   0x08049a03 <+18>:  mov    eax,DWORD PTR [ebp-0x4]
   ; eax := the value at [0x804c160 + 0x4] 
   0x08049a06 <+21>:  mov    eax,DWORD PTR [eax+0x4]
   ; [ebp-0x8] is var1
   ; var1 := some the value at [0x804c160 + 0x4] that is not a valid address
   0x08049a09 <+24>:  mov    DWORD PTR [ebp-0x8],eax
   ; [ebp-0xc] is initialized to 0.
   ; Now look at address 0x08049a80 <+143>. 1 is added to it
   ; and if [ebp-0xc] is <= 14 execution returns to 0x8049a15
   ; I'm pretty sure  [ebp-0xc] is an iterator for a loop
   ; [ebp-0xc] is i := 0
   0x08049a0c <+27>:  mov    DWORD PTR [ebp-0xc],0x0
   ; jump to the check if i<= 14. If so return to the 
   ; next instruction at 0x08049a15
   0x08049a13 <+34>:  jmp    0x8049a84 <blue+147>
   ; [ebp-0x10] is initialized to zero
   ; Later in the disassembly you can see that it only gets
   ; assigned 1 or 0. No other operations with it.
   ; I think it's some kind of flag that tells if something happpened or not. 
   ; [ebp-0x10] is flag := 0
   0x08049a15 <+36>:  mov    DWORD PTR [ebp-0x10],0x0
   0x08049a1c <+43>:  mov    eax,DWORD PTR [ebp-0xc]
   ; if you remember 0x804c24c is where our input is stored.
   ; and eax holds the iterator
   ; eax := [i + &input] = user_input[i]
   0x08049a1f <+46>:  movzx  eax,BYTE PTR [eax+0x804c24c]
   0x08049a26 <+53>:  movsx  eax,al
   ; [ebp-0x14] := user_input[i]
   0x08049a29 <+56>:  mov    DWORD PTR [ebp-0x14],eax
   ; 0x4c = ascii L 
   ; Compare user_input[i] with 'L'
   0x08049a2c <+59>:  cmp    DWORD PTR [ebp-0x14],0x4c
   ; jump to SECTION L if user_input[i] == 'L'
   0x08049a30 <+63>:  je     0x8049a40 <blue+79>
   ; 0x52 = ascii R (right)
   ; Compare user_input[i] with 'R'
   0x08049a32 <+65>:  cmp    DWORD PTR [ebp-0x14],0x52
   ; jump to SECTION R if user_input[i] == 'R'
   0x08049a36 <+69>:  je     0x8049a4a <blue+89>
   ; 0xa = ascii newline
   ; Compare user_input[i] with '\n'
   0x08049a38 <+71>:  cmp    DWORD PTR [ebp-0x14],0xa
   ; jump to SECTION \n if user_input[i] == '\n'
   0x08049a3c <+75>:  je     0x8049a55 <blue+100>
   ; if none matched jump to SECTION NONE
   0x08049a3e <+77>:  jmp    0x8049a5e <blue+109>
   
;SECTION L -> GO HERE IF user_input[i] == 'L'
   ;
   ; [ebp-0x4] was holding the graph variable address 0x804c160
   ; eax := 0x804c160
   0x08049a40 <+79>:  mov    eax,DWORD PTR [ebp-0x4]
   ; eax := the value at [0x804c160] => 0x0804c19c
   0x08049a43 <+82>:  mov    eax,DWORD PTR [eax]
   ; [ebp-0x4] now holds the NEW (next) address 0x0804c19c
   0x08049a45 <+84>:  mov    DWORD PTR [ebp-0x4],eax
   ; jump to section CONT
   0x08049a48 <+87>:  jmp    0x8049a71 <blue+128>

;SECTION R -> GO HERE IF user_input[i] == 'R'
   ;
   ; [ebp-0x4] holds an address belonging to the graph variable
   ; eax := [ebp-0x4]
   0x08049a4a <+89>:  mov    eax,DWORD PTR [ebp-0x4]
   ; eax := value at [eax+0x8]
   ; based on my findings of graph variable this should
   ; also be a valid address (because has an offset of 8)
   0x08049a4d <+92>:  mov    eax,DWORD PTR [eax+0x8]
   ; [ebp-0x4] now holds NEW address but one found at an offset -0x8
   0x08049a50 <+95>:  mov    DWORD PTR [ebp-0x4],eax
   ; jump to section CONT
   0x08049a53 <+98>:  jmp    0x8049a71 <blue+128>

;SECTION \n -> GO HERE IF user_input[i] == '\n'
   ; flag gets set to 1
   ; flag := 1
   0x08049a55 <+100>: mov    DWORD PTR [ebp-0x10],0x1
   ; jump to section CONT
   0x08049a5c <+107>: jmp    0x8049a71 <blue+128>

;SECTION NONE
   ; flag is set to 1
   ; flag := 1
   0x08049a5e <+109>: mov    DWORD PTR [ebp-0x10],0x1
   0x08049a65 <+116>: mov    DWORD PTR [esp],0x804a3bb
   ; puts('boom')
   0x08049a6c <+123>: call   0x80487b4 <puts@plt>

;SECTION CONT1 -> GO HERE AFTER 'L', 'R', '\n' SECTIONS
   ; Compare flag with 0
   0x08049a71 <+128>: cmp    DWORD PTR [ebp-0x10],0x0
   ; jump to section CONT2 if flag != 0
   0x08049a75 <+132>: jne    0x8049a8a <blue+153>
   ; eax := [ebp-0x4] that holds the NEW address from the graph variable
   0x08049a77 <+134>: mov    eax,DWORD PTR [ebp-0x4]
   ; based on what I found on about the graph variable
   ; at offset 0x4 there is some value that is not a  valid
   ; address
   ;eax := [eax+0x4]  -> eax contains the value at an offset 0x4 from
   ; the SECOND address (i'll call it var2)
   0x08049a7a <+137>: mov    eax,DWORD PTR [eax+0x4]
   ; [ebp-0x8] was var1 which contained the value at offset of -0x4 to
   ; the FIRST address (the one we started with) of the graph variable
   ;
   ; eax now also contains some value (var2) with an offset of -0x4 but from the
   ; SECOND address
   ;
   ; var1 = var1 XOR var2 
   0x08049a7d <+140>: xor    DWORD PTR [ebp-0x8],eax
   ; i++
   0x08049a80 <+143>: add    DWORD PTR [ebp-0xc],0x1
   ; Compare i with 14
   0x08049a84 <+147>: cmp    DWORD PTR [ebp-0xc],0xe
   ; jump if i <= 14
   ; our input should be no more than 14 characters
   0x08049a88 <+151>: jle    0x8049a15 <blue+36>
   
;SECTION CONT2 -> GO HERE IF flag != 0
   ; this is the end of the function so I'm pretty sure the flag
   ; tells if we reached the end of our input (reached newline) or 
   ; that we typed an invalid character
   0x08049a8a <+153>: mov    DWORD PTR [esp],0x804a3c0
   0x08049a91 <+160>: call   0x8048744 <printf@plt>
   0x08049a96 <+165>: mov    eax,ds:0x804c240
   0x08049a9b <+170>: mov    DWORD PTR [esp],eax
   0x08049a9e <+173>: call   0x8048734 <fflush@plt>
   0x08049aa3 <+178>: mov    DWORD PTR [esp],0x1
   0x08049aaa <+185>: call   0x80487a4 <sleep@plt>
   0x08049aaf <+190>: mov    DWORD PTR [esp],0x804a3eb
   0x08049ab6 <+197>: call   0x80487b4 <puts@plt>
   0x08049abb <+202>: mov    DWORD PTR [esp],0x7a120
   0x08049ac2 <+209>: call   0x8048724 <usleep@plt>
   0x08049ac7 <+214>: mov    eax,ds:0x804a384
   ; Compare var1 with the value at address ds:0x804a384
   0x08049acc <+219>: cmp    DWORD PTR [ebp-0x8],eax
   0x08049acf <+222>: jne    0x8049aec <blue+251>
   0x08049ad1 <+224>: mov    DWORD PTR [esp],0x804a3fc
   0x08049ad8 <+231>: call   0x80487b4 <puts@plt>
   0x08049add <+236>: mov    eax,ds:0x804c140
   0x08049ae2 <+241>: sub    eax,0x1
   0x08049ae5 <+244>: mov    ds:0x804c140,eax
   0x08049aea <+249>: jmp    0x8049af9 <blue+264>
   0x08049aec <+251>: mov    eax,ds:0x804c140
   0x08049af1 <+256>: add    eax,0x1
   0x08049af4 <+259>: mov    ds:0x804c140,eax
   0x08049af9 <+264>: leave  
   0x08049afa <+265>: ret    

Well, it’s a mess looking at this disassembly, and my comments are for sure confusing, so I’ll show some screenshots from the IDA graph view. But first, lets summarize:

  • The only valid input characters are 'L', 'R' and '\n'
  • Input should be no more thatn 14 characters long
  • Also now I’m sure that our graph variable is kind of a linked list with structures of the form:
struct graph{    // example address 0x804c160
	int next_1;  // 0x804c160 + 0x0 -> 0x0804c19c
	int value;   // 0x804c160 + 0x4 -> 0x47bbfa96
	int next_2;  // 0x804c160 + 0x8 -> 0x0804c178
}

And the connected elements are forming a…. graph. And at each node you could go ‘L’eft and ‘R’ight on the graph.

  • At the end our var1 is compared to a value at address 0x804a384 which is equal to:
pwndbg> x/wx 0x804a384
0x804a384 <solution>:	0x40475194

That means after all iterations var1 should be equal to 0x40475194.

So we enter a path in the form 'LLRRLRLRLLR' and the values of each node with pass through and the expected result shuld be 0x40475194.

And below are screenshots from the IDA graph view (if it’s hard to see open them in a new tab):

bomb_blue_1

bomb_blue_2

bomb_blue_3

bomb_blue_4

I found all nodes and their values manually, and wrote the following python script that bruteforces the solution by trying all possible paths.

#!/usr/bin/env python3

class graph:
  def __init__(self, value):
    self.value = value
    self.left = None
    self.right = None

c160 = graph(0x47bbfa96)
c19c = graph(0x0c4079ef)
c1cc = graph(0x4b846cb6)
c1f0 = graph(0x16848c16)
c184 = graph(0x634284d3)


c1c0 = graph(0x237a3a88)
c1e4 = graph(0x3a4ad3ff)


c1a8 = graph(0x425ebd95)
c178 = graph(0x23daf3f1)
c1d8 = graph(0x1fba9a98)
c214 = graph(0x770ea82a)


c1fc = graph(0x499ee4ce)
c190 = graph(0x344c4eb1)

c1b4 = graph(0x07ace749)


c160.right = c178
c19c.right = c214
c1cc.right = c184
c1f0.right = c178
c184.right = c1c0
c1c0.right = c184
c1e4.right = c1c0
c1a8.right = c184
c178.right = c1a8
c1d8.right = c1c0
c214.right = c1fc
c1fc.right = c1b4
c190.right = c1fc
c1b4.right = c1a8


c160.left = c19c
c19c.left = c1cc
c1cc.left = c1f0
c1f0.left = c184
c184.left = c19c

c1c0.left = c1e4
c1e4.left = c19c

c1a8.left = c178
c178.left = c1d8
c1d8.left = c214
c214.left = c1cc

c1fc.left = c190
c190.left = c1f0

c1b4.left = c1cc

solution = 0x40475194

def find_key(directions):
  for i,d in enumerate(directions):
    if i == 0:
      k = c160
      xored = k.value
    if d == 'L':
      k = k.left
      v = k.value
      xored = xored ^ v
    if d == 'R':
      k = k.right
      v = k.value
      xored = xored ^ v

  if xored == solution:
    print("Sucess! Found Key!")
    print("Key: %s" % (directions))
    input()

for pos in range(2,15):
  print("Positions: %d" % (pos))
  print("Combinations to try: %d" % (2**pos))
  for i in range(2**pos): 
    # format binary with fixed length of 'pos' positions
    format_string = '{0:0'+str(pos)+'b}'
    binary = format_string.format(i)
    combination = binary.replace('0','L').replace('1','R')
    find_key(combination)
  print("==========================")
$ ./blue.py 
Positions: 2
Combinations to try: 4
==========================
Positions: 3
Combinations to try: 8
==========================
Positions: 4
Combinations to try: 16
Sucess! Found Key!
Key: LLRR

bomb_blue_5

Red

When you run the RED option three hexadecimal numbers are generated:

bomb_red_1

This time the preflight function is important.

pwndbg> disass red_preflight 
Dump of assembler code for function red_preflight:
   0x080497a4 <+0>: push   ebp
   0x080497a5 <+1>: mov    ebp,esp
   0x080497a7 <+3>: sub    esp,0x28
   0x080497aa <+6>: call   0x80487c4 <rand@plt>
   0x080497af <+11>:  and    eax,0x7fffffff
   ; 0x804c264 points to variable I'll call rnd1
   ; rnd1 := random && 0x7fffffff
   0x080497b4 <+16>:  mov    ds:0x804c264,eax
   0x080497b9 <+21>:  call   0x80487c4 <rand@plt>
   ; 0x804c268 is rnd2 := random
   0x080497be <+26>:  mov    ds:0x804c268,eax
   0x080497c3 <+31>:  call   0x80487c4 <rand@plt>
   ; 0x804c26c is rnd3 := random
   ; Thes three random numbers are consecutive and
   ; can be referenced as an array rnd[i] with first element rnd1
   0x080497c8 <+36>:  mov    ds:0x804c26c,eax
   ; [ebp-0x4] is an interatior
   ; At address 0x080497fc it is incremented and
   ; after that a compare and a jump
   ; i := 0
   0x080497cd <+41>:  mov    DWORD PTR [ebp-0x4],0x0
   0x080497d4 <+48>:  jmp    0x8049800 <red_preflight+92>
   0x080497d6 <+50>:  mov    eax,DWORD PTR [ebp-0x4]
   ; iterate through rnd[i]
   ; eax := i*4 + &rnd1 equivalent to rnd[i]
   0x080497d9 <+53>:  mov    eax,DWORD PTR [eax*4+0x804c264]
   0x080497e0 <+60>:  mov    DWORD PTR [esp+0x4],eax
   ; printf('CLOCK SYNC: rnd[i]')
   0x080497e4 <+64>:  mov    DWORD PTR [esp],0x804a234
   0x080497eb <+71>:  call   0x8048744 <printf@plt>
   0x080497f0 <+76>:  mov    DWORD PTR [esp],0x7a120
   0x080497f7 <+83>:  call   0x8048724 <usleep@plt>
   ; i++
   0x080497fc <+88>:  add    DWORD PTR [ebp-0x4],0x1
   ; compare i with 0x2
   0x08049800 <+92>:  cmp    DWORD PTR [ebp-0x4],0x2
   ; loop if i <= 0x2 (loop through the random numbers)
   0x08049804 <+96>:  jle    0x80497d6 <red_preflight+50>
   0x08049806 <+98>:  mov    DWORD PTR [esp],0x804a25c
   0x0804980d <+105>: call   0x8048744 <printf@plt>
   0x08049812 <+110>: mov    eax,ds:0x804c220
   0x08049817 <+115>: mov    DWORD PTR [esp+0x8],eax
   0x0804981b <+119>: mov    DWORD PTR [esp+0x4],0x15
   ; 0x804c24c = user_input
   0x08049823 <+127>: mov    DWORD PTR [esp],0x804c24c
   0x0804982a <+134>: call   0x8048704 <fgets@plt>
   0x0804982f <+139>: leave  
   0x08049830 <+140>: ret    
End of assembler dump.

pwndbg> disass red
Dump of assembler code for function red:
   0x08049831 <+0>: push   ebp
   0x08049832 <+1>: mov    ebp,esp
   0x08049834 <+3>: sub    esp,0x18
   0x08049837 <+6>: call   0x80497a4 <red_preflight>
   ; [ebp-0x4] := 0x804a29c 
   ; where 0x804a29c points to an array with alphanumeric characters
   ; [ebp-0x4] is characters := "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
   0x0804983c <+11>:  mov    DWORD PTR [ebp-0x4],0x804a29c
   ; [ebp-0x8] is an iterator i := 0 to 18
   0x08049843 <+18>:  mov    DWORD PTR [ebp-0x8],0x0
   0x0804984a <+25>:  jmp    0x80498ba <red+137>
   0x0804984c <+27>:  mov    eax,DWORD PTR [ebp-0x8]
   ; 0x804c24c points to the user_input
   ; edx := user_input[i]
   0x0804984f <+30>:  movzx  edx,BYTE PTR [eax+0x804c24c]
   0x08049856 <+37>:  mov    eax,ds:0x804c26c
   ; eax = rnd3 && 0x1f
   0x0804985b <+42>:  and    eax,0x1f
   ; eax := &characters[eax]
   0x0804985e <+45>:  add    eax,DWORD PTR [ebp-0x4]
   ; eax := characters[eax]
   0x08049861 <+48>:  movzx  eax,BYTE PTR [eax]
   0x08049864 <+51>:  cmp    dl,al
   ; jump if user_input[i] == characters[rnd3 && 0x1f]
   0x08049866 <+53>:  je     0x8049877 <red+70>
   0x08049868 <+55>:  mov    eax,ds:0x804c128
   0x0804986d <+60>:  add    eax,0x1
   0x08049870 <+63>:  mov    ds:0x804c128,eax
   ; if user_input[i] != characters -> exit
   0x08049875 <+68>:  jmp    0x80498ca <red+153>
   0x08049877 <+70>:  mov    eax,ds:0x804c26c
   0x0804987c <+75>:  mov    edx,eax
   ; edx := rnd3 >> 0x5
   0x0804987e <+77>:  shr    edx,0x5
   0x08049881 <+80>:  mov    eax,ds:0x804c268
   ; eax := rnd2 << 0x1b
   0x08049886 <+85>:  shl    eax,0x1b
   ; eax := (rnd2 << 0x1b) || (rnd3 >> 0x5)
   0x08049889 <+88>:  or     eax,edx
   ; rnd3 := (rnd2 << 0x1b) || (rnd3 >> 0x5)
   0x0804988b <+90>:  mov    ds:0x804c26c,eax
   0x08049890 <+95>:  mov    eax,ds:0x804c268
   0x08049895 <+100>: mov    edx,eax
   ; edx := rnd2 >> 0x5
   0x08049897 <+102>: shr    edx,0x5
   0x0804989a <+105>: mov    eax,ds:0x804c264
   ; eax := rnd1 << 0x1b
   0x0804989f <+110>: shl    eax,0x1b
   0x080498a2 <+113>: or     eax,edx
   ; rdn2 = (rnd1 << 0x1b) || (rnd2 >> 0x5)
   0x080498a4 <+115>: mov    ds:0x804c268,eax
   0x080498a9 <+120>: mov    eax,ds:0x804c264
   0x080498ae <+125>: shr    eax,0x5
   ; rnd1 := rnd1 >> 0x5
   0x080498b1 <+128>: mov    ds:0x804c264,eax
   ; i++
   0x080498b6 <+133>: add    DWORD PTR [ebp-0x8],0x1
   0x080498ba <+137>: cmp    DWORD PTR [ebp-0x8],0x12
   ; jump if i<= 18
   0x080498be <+141>: jle    0x804984c <red+27>
   0x080498c0 <+143>: mov    DWORD PTR ds:0x804c128,0x0
   0x080498ca <+153>: leave  
   0x080498cb <+154>: ret    
End of assembler dump.

And the IDA graph view:

preflight: bomb_red_2

red: bomb_red_3

It’s obvious what it does.

  • It generates 3 random numbers
  • Based on those random numbers it calculates what input characters to expect

It’s trivial to do it in a python script:

#!/usr/bin/env python3

characters = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'
length = 18

rnd1 = int(input("RND1: "), 16) 
rnd2 = int(input("RND2: "), 16) 
rnd3 = int(input("RND3: "), 16) 

key = []
for i in range(length + 1):
  key.append( characters[rnd3 & 0x1f] )
  rnd3 = (rnd2 << 0x1b) | (rnd3 >> 0x5)
  rnd2 = (rnd1 << 0x1b) | (rnd2 >> 0x5)
  rnd1 = rnd1 >> 0x5

print('Key: %s' % (''.join(key)))

Run the script and copy the random numbers that the executable generated.

$ ./red.py 
RND1: 6B8B4567
RND2: 327B23C6
RND3: 643C9869
Key: KDG3DU32D38EVVXJM64

Test the key:

bomb_red_4

That’s it for now. Part 0x02 will be a write up for the cmubomb executable.