A series of well-written challenges which quickly ramp up in difficulty. For the most part, they only require knowledge of Assembly to solve. Python skills and a very basic understanding of memory corruption will be helpful. The binary is available here.
I used GDB to solve each challenge, and writing Python scripts can be helpful for the later challenges.
yellow()
and learn about ASCII
green()
when [ebp-0x08]
is even. To achieve this, learn about buffer overflows.
0x804c160
is a graph data structure storing nodes and pointers. What input characters are allowed, and how must you use them with this graph?
rand()
function isn't being used properly. Also, don't be afraid to write your own script to simulated the expected input!
Dissassembling main
reveals that it displays the menu, and then calls a function corresponding to the user's challenge choice: yellow()
, green()
, blue()
, or red()
. Within yellow()
, the first thing that happens is calling yellow_preflight()
. Let's see what this function does:
0x080496e8 <+0>: push ebp ; set up stack frame
0x080496e9 <+1>: mov ebp,esp
0x080496eb <+3>: sub esp,0x18
0x080496ee <+6>: mov DWORD PTR [esp],0x804a1c4; printf("ENTER UNLOCK PW1:")
0x080496f5 <+13>: call 0x8048744 <printf@plt>
0x080496fa <+18>: mov eax,ds:0x804c220 ; fgets(&buffer, 10, stdin)
0x080496ff <+23>: mov DWORD PTR [esp+0x8],eax
0x08049703 <+27>: mov DWORD PTR [esp+0x4],0xa
0x0804970b <+35>: mov DWORD PTR [esp],0x804c24c
0x08049712 <+42>: call 0x8048704 <fgets@plt>
0x08049717 <+47>: leave ; return
0x08049718 <+48>: ret
Okay, so yellow_preflight()
just stores our input into a buffer at address 0x804c24c
. Let's see what yellow()
does with this input string:
0x08049719 <+0>: push ebp ; set up stack frame
0x0804971a <+1>: mov ebp,esp
0x0804971c <+3>: sub esp,0x8
0x0804971f <+6>: call 0x80496e8 <yellow_preflight> ; get user input
0x08049724 <+11>: movzx eax,BYTE PTR ds:0x804c24c ; input[0] ?= '8'
0x0804972b <+18>: cmp al,0x38
0x0804972d <+20>: jne 0x804977c <yellow+99>
0x0804972f <+22>: movzx eax,BYTE PTR ds:0x804c24d
0x08049736 <+29>: cmp al,0x34 ; input[1] ?= '4'
0x08049738 <+31>: jne 0x804977c <yellow+99>
0x0804973a <+33>: movzx eax,BYTE PTR ds:0x804c24e
0x08049741 <+40>: cmp al,0x33 ; input[3] ?= '3'
0x08049743 <+42>: jne 0x804977c <yellow+99>
0x08049745 <+44>: movzx eax,BYTE PTR ds:0x804c24f
0x0804974c <+51>: cmp al,0x37 ; input[4] ?= '7'
0x0804974e <+53>: jne 0x804977c <yellow+99>
0x08049750 <+55>: movzx eax,BYTE PTR ds:0x804c250
0x08049757 <+62>: cmp al,0x31 ; input[5] ?= '1'
0x08049759 <+64>: jne 0x804977c <yellow+99>
0x0804975b <+66>: movzx eax,BYTE PTR ds:0x804c251
0x08049762 <+73>: cmp al,0x30 ; input[6] ?= '0'
0x08049764 <+75>: jne 0x804977c <yellow+99>
0x08049766 <+77>: movzx eax,BYTE PTR ds:0x804c252
0x0804976d <+84>: cmp al,0x36 ; input[7] ?= '6'
0x0804976f <+86>: jne 0x804977c <yellow+99>
0x08049771 <+88>: movzx eax,BYTE PTR ds:0x804c253
0x08049778 <+95>: cmp al,0x35 ; input[8] ?= '5'
0x0804977a <+97>: je 0x804978b <yellow+114>
0x0804977c <+99>: mov eax,ds:0x804c124 ; fail
0x08049781 <+104>: shl eax,0xa
0x08049784 <+107>: mov ds:0x804c124,eax
0x08049789 <+112>: jmp 0x80497a1 <yellow+136>
0x0804978b <+114>: mov DWORD PTR [esp],0x804a1f4 ; win
0x08049792 <+121>: call 0x80487b4 <puts@plt>
0x08049797 <+126>: mov DWORD PTR ds:0x804c124,0x0 ; return 0
0x080497a1 <+136>: leave
0x080497a2 <+137>: ret
yellow()
iterates over each inputted character, testing that it matches a pre-defined sequence. In order to pass all tests, our input string must be "84371065"
.
Let's take a look at the disassembled green()
function:
0x08049904 <+0>: push ebp ; set up stack frame
0x08049905 <+1>: mov ebp,esp
0x08049907 <+3>: sub esp,0x38
0x0804990a <+6>: mov eax,gs:0x14 ; var1 = secretval
0x08049910 <+12>: mov DWORD PTR [ebp-0x4],eax
0x08049913 <+15>: xor eax,eax ; erase evidence
0x08049915 <+17>: mov DWORD PTR [ebp-0x8],0x1 ; var2 = 1
0x0804991c <+24>: lea eax,[ebp-0x14] ; fgets(&var5, 20, stdin)
0x0804991f <+27>: mov DWORD PTR [esp],eax
0x08049922 <+30>: call 0x80498d4 <green_preflight>
0x08049927 <+35>: mov DWORD PTR [esp+0x8],0x8 ; strncmp(&"dcaotdae", &var5, 8)
0x0804992f <+43>: lea eax,[ebp-0x14]
0x08049932 <+46>: mov DWORD PTR [esp+0x4],eax
0x08049936 <+50>: mov DWORD PTR [esp],0x804a2c0
0x0804993d <+57>: call 0x80487d4 <strncmp@plt>
0x08049942 <+62>: test eax,eax ; if not equal, goto end
0x08049944 <+64>: jne 0x804998e <green+138>
0x08049946 <+66>: mov DWORD PTR [esp],0x804a2fc ; puts("PW2 ACCEPTED")
0x0804994d <+73>: call 0x80487b4 <puts@plt>
0x08049952 <+78>: mov eax,DWORD PTR [ebp-0x8] ; var2 = even(var2) = 0
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 ; usleep(0.5seconds)
0x0804996a <+102>: call 0x8048724 <usleep@plt>
0x0804996f <+107>: mov DWORD PTR [esp],0x804a33c ; puts("LOCK RE-ENGAGED")
0x08049976 <+114>: call 0x80487b4 <puts@plt>
0x0804997b <+119>: mov eax,DWORD PTR [ebp-0x8] ; var2 = even(var2) = 1
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> ; goto end2
0x0804998e <+138>: mov eax,ds:0x804c12c ;end: dsval = dsval + dsval
0x08049993 <+143>: add eax,eax
0x08049995 <+145>: mov ds:0x804c12c,eax
0x0804999a <+150>: mov eax,DWORD PTR [ebp-0x8] ;end2: var2 ?= 0
0x0804999d <+153>: test eax,eax ;
0x0804999f <+155>: jne 0x80499ad <green+169> ; if not, return
0x080499a1 <+157>: mov eax,ds:0x804c12c ; dsval >>= 1
0x080499a6 <+162>: sar eax,1
0x080499a8 <+164>: mov ds:0x804c12c,eax
0x080499ad <+169>: mov eax,DWORD PTR [ebp-0x4] ; var1 ?= secretval
0x080499b0 <+172>: xor eax,DWORD PTR gs:0x14 ; if not, we smashed the stack
0x080499b7 <+179>: je 0x80499be <green+186> ; return
0x080499b9 <+181>: call 0x8048784 <__stack_chk_fail@plt>
0x080499be <+186>: leave
0x080499bf <+187>: ret
This challenge is a big step up from the previous one, so let's examine one part at a time. Directly after setting up our new stack frame, in lines 5-7 a value is loaded from the data segment into register eax
and then into [ebp-0x04]
. Afterwards, register eax
is zeroed to erase evidence of these operations. Address [ebp-0x04]
is not touched again until line 61. At this point, we check that its value has not changed. If it has, then we call function __stack_chk_fail
, which immediately exits. This method is a common form of stack protection which ensures that we have not overwritten our base pointer and "smashed the stack".
Lines 15-22 perform the first validation check which we must pass. Using GDB, we can examine the string which the first 8 characters of our input are compared to, shown below. It is important to note that green_preflight
reads up to 20 characters from stdin
into a buffer at position ebp-0x14
, and only the first 8 are used during the comparison on line 19.
gef> x/s 0x804a2c0
0x804a2c0 <password>: "dcaotdae"
Matching this password alone is not sufficient to pass this challenge. If we try to solve the challenge with this string, we are simply alerted that Noizev has overridden this action:
ENTER UNLOCK PASSWORD 2: dcaotdae
UNLOCK PASSWORD 2 ACCEPTED, LOCK DISENGAGED
ACTION OVERRIDDEN FROM USER NOIZEV, LOCK RE-ENGAGED
Looking at lines 34-38, we can see that after a half second, this message will print unconditionally. Fortunately, we don't have to prevent the message from printing, we just need green()
to exit with a successful status. To do this, line 55 of green
must be reached with [ebp-0x08]
at an even value. Since [ebp-0x08]
is initialized to 1
on line 9 and its parity is changed twice (lines 27-32 and 40-45), the only way to do this is to overwrite ebp-0x08
through ebp-0x05
with our input string.
The input string is read into ebp-0x14
using fgets()
. Use man fgets
to learn more about this function:
char *fgets(char *s, int size, FILE *stream);
fgets() reads in at most one less than size characters from stream and
stores them into the buffer pointed to by s. Reading stops after an
EOF or a newline. If a newline is read, it is stored into the buffer.
A terminating null byte ('\0') is stored after the last character in
the buffer.
The stored string starting at ebp-0x14
should be 16 characters long in order to overwrite ebp-0x08
through ebp-0x05
but not ebp-0x04
. Because it will be terminated by both a newline (0x0a) and a null byte (0x00), we should supply 14 characters such that the first eight are "dcaotdae"
. When ebp-0x08
is read as a 4-byte integer, our 13th character will decide its parity because data is stored in little-endian format. Since we want [ebp-0x08]
to be even, our 13th character should have an even ASCII value, and one valid solution would be "dcaotdae123401"
.
We're halfway there! Similar to green_preflight()
, blue_preflight()
reads at most 16 characters using fgets()
from stdin
into buffer
, located at 0x804c24c
. Afterwards, blue()
validates our input:
0x080499f1 <+0>: push ebp ; setup stack frame
0x080499f2 <+1>: mov ebp,esp
0x080499f4 <+3>: sub esp,0x18
0x080499f7 <+6>: call 0x80499c0 <blue_preflight> ; fgets(&buffer, 16, stdin)
0x080499fc <+11>: mov DWORD PTR [ebp-0x4],0x804c160; var1 = graph
0x08049a03 <+18>: mov eax,DWORD PTR [ebp-0x4]
0x08049a06 <+21>: mov eax,DWORD PTR [eax+0x4]
0x08049a09 <+24>: mov DWORD PTR [ebp-0x8],eax ; var2 = graph[1]
0x08049a0c <+27>: mov DWORD PTR [ebp-0xc],0x0 ; var3 = 0
0x08049a13 <+34>: jmp 0x8049a84 <blue+147> ; goto testvar3
0x08049a15 <+36>: mov DWORD PTR [ebp-0x10],0x0 ;start: var4 = 0
0x08049a1c <+43>: mov eax,DWORD PTR [ebp-0xc] ; var5 = char(buffer[var3])
0x08049a1f <+46>: movzx eax,BYTE PTR [eax+0x804c24c]
0x08049a26 <+53>: movsx eax,al
0x08049a29 <+56>: mov DWORD PTR [ebp-0x14],eax
0x08049a2c <+59>: cmp DWORD PTR [ebp-0x14],0x4c ; var5 ?= 0x4c
0x08049a30 <+63>: je 0x8049a40 <blue+79> ; if so, goto +79
0x08049a32 <+65>: cmp DWORD PTR [ebp-0x14],0x52 ; var5 ?= 0x52
0x08049a36 <+69>: je 0x8049a4a <blue+89> ; if so, goto +89
0x08049a38 <+71>: cmp DWORD PTR [ebp-0x14],0xa ; var5 ?= 0x0a
0x08049a3c <+75>: je 0x8049a55 <blue+100> ; if so, goto +100
0x08049a3e <+77>: jmp 0x8049a5e <blue+109> ; goto fail
0x08049a40 <+79>: mov eax,DWORD PTR [ebp-0x4] ; var1 = *(var1+0)
0x08049a43 <+82>: mov eax,DWORD PTR [eax]
0x08049a45 <+84>: mov DWORD PTR [ebp-0x4],eax
0x08049a48 <+87>: jmp 0x8049a71 <blue+128> ; goto +128
0x08049a4a <+89>: mov eax,DWORD PTR [ebp-0x4] ; var1 = *(var1+8)
0x08049a4d <+92>: mov eax,DWORD PTR [eax+0x8]
0x08049a50 <+95>: mov DWORD PTR [ebp-0x4],eax
0x08049a53 <+98>: jmp 0x8049a71 <blue+128> ; goto +128
0x08049a55 <+100>: mov DWORD PTR [ebp-0x10],0x1 ; var4 = 1
0x08049a5c <+107>: jmp 0x8049a71 <blue+128> ; goto +128
0x08049a5e <+109>: mov DWORD PTR [ebp-0x10],0x1 ;fail: puts("boom")
0x08049a65 <+116>: mov DWORD PTR [esp],0x804a3bb
0x08049a6c <+123>: call 0x80487b4 <puts@plt>
0x08049a71 <+128>: cmp DWORD PTR [ebp-0x10],0x0 ; var4 ?= 0
0x08049a75 <+132>: jne 0x8049a8a <blue+153> ; if not, goto +153
0x08049a77 <+134>: mov eax,DWORD PTR [ebp-0x4] ; var2 ^= *(var1+4)
0x08049a7a <+137>: mov eax,DWORD PTR [eax+0x4]
0x08049a7d <+140>: xor DWORD PTR [ebp-0x8],eax
0x08049a80 <+143>: add DWORD PTR [ebp-0xc],0x1 ; var3++
0x08049a84 <+147>: cmp DWORD PTR [ebp-0xc],0xe ;testvar3: var3 ?<= 14
0x08049a88 <+151>: jle 0x8049a15 <blue+36> ; if so, goto start
0x08049a8a <+153>: mov DWORD PTR [esp],0x804a3c0 ; printf("PROGRAMMING GATE ARRAY")
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 ; sleep(1)
0x08049aaa <+185>: call 0x80487a4 <sleep@plt>
0x08049aaf <+190>: mov DWORD PTR [esp],0x804a3eb ; printf("SUCCEEDED")
0x08049ab6 <+197>: call 0x80487b4 <puts@plt>
0x08049abb <+202>: mov DWORD PTR [esp],0x7a120 ; sleep(0.5)
0x08049ac2 <+209>: call 0x8048724 <usleep@plt>
0x08049ac7 <+214>: mov eax,ds:0x804a384 ; var2 ?= 0x40475194
0x08049acc <+219>: cmp DWORD PTR [ebp-0x8],eax
0x08049acf <+222>: jne 0x8049aec <blue+251> ; if not, goto end
0x08049ad1 <+224>: mov DWORD PTR [esp],0x804a3fc ; printf("VOLTAGE REROUTED")
0x08049ad8 <+231>: call 0x80487b4 <puts@plt>
0x08049add <+236>: mov eax,ds:0x804c140 ; wire_blue--
0x08049ae2 <+241>: sub eax,0x1
0x08049ae5 <+244>: mov ds:0x804c140,eax
0x08049aea <+249>: jmp 0x8049af9 <blue+264> ; return
0x08049aec <+251>: mov eax,ds:0x804c140 ;end: wire_blue++
0x08049af1 <+256>: add eax,0x1
0x08049af4 <+259>: mov ds:0x804c140,eax
0x08049af9 <+264>: leave
0x08049afa <+265>: ret
Well, it looks like there's lots of jumping around! Which path of execution do we want to take, though? We will always return from blue()
, but what matters upon exiting is the value at memory location 0x804c140
. This value represents whether the blue wire is cut (and therefore if the challenge is solved!), and while disassembling menu()
proves this, it's simpler to get the variable name through GDB:
gef> x/xw 0x804c140
0x804c140 <wire_blue>: 0x00000001
In order to exit with wire_blue == 0
we must pass the check on line 71 that var2 == 0x40475194
. To reach line 71, either var3 > 15
or var4 != 0
. It turns out that both of these conditions are equivalent to reaching the end of our input string. If you look at lines 14-27, you can see that var5 = char(buffer[var3])
is the current character of our input string. If var5
is not 'L'
, 'R'
, or '\n'
, the bomb goes off. If either we reach the end of the input string (var3 > 15
) or a line feed (var5 == 0x0a
), we jump to line 71.
Now we must figure out which combination of 'L'
and 'R'
characters will result in var2 == 0x40475194
when we reach line 71. To start, let's examine the memory location loaded into var1
:
gef> x/48xw 0x804c160
0x804c160 <graph>: 0x0804c19c 0x47bbfa96 0x0804c178 0x0804c214
0x804c170 <graph+16>: 0x50171a6e 0x0804c1b4 0x0804c1d8 0x23daf3f1
0x804c180 <graph+32>: 0x0804c1a8 0x0804c19c 0x634284d3 0x0804c1c0
0x804c190 <graph+48>: 0x0804c1f0 0x344c4eb1 0x0804c1fc 0x0804c1cc
0x804c1a0 <graph+64>: 0x0c4079ef 0x0804c214 0x0804c178 0x425ebd95
0x804c1b0 <graph+80>: 0x0804c184 0x0804c1cc 0x07ace749 0x0804c1a8
0x804c1c0 <graph+96>: 0x0804c1e4 0x237a3a88 0x0804c184 0x0804c1f0
0x804c1d0 <graph+112>: 0x4b846cb6 0x0804c184 0x0804c214 0x1fba9a98
0x804c1e0 <graph+128>: 0x0804c1c0 0x0804c19c 0x3a4ad3ff 0x0804c1c0
0x804c1f0 <graph+144>: 0x0804c184 0x16848c16 0x0804c178 0x0804c190
0x804c200 <graph+160>: 0x499ee4ce 0x0804c1b4 0x0804c1c0 0x261af8fb
0x804c210 <graph+176>: 0x0804c184 0x0804c1cc 0x770ea82a 0x0804c1fc
Given the variable name graph
, the fact that our two valid characters are 'L'
and 'R'
, and that two out of every three dword
s in graph
is a valid pointer, we appear to be dealing with a graph composed of nodes, each with two pointers to neighboring nodes. Lines 29-37 show that based on each input character, either the left or right pointer is followed before var2
(starting at 0x477bbfa96
) is xor
ed with the node's value (lines 49-51). A quick brute-force search using paths of increasing length shows that 'LLRR'
is a valid solution.
For our last challenge, the red_preflight()
function cannot be ignored:
0x080497a4 <+0>: push ebp ; set up stack frame
0x080497a5 <+1>: mov ebp,esp
0x080497a7 <+3>: sub esp,0x28
0x080497aa <+6>: call 0x80487c4 <rand@plt> ; r = 0x804c264
0x080497af <+11>: and eax,0x7fffffff ; r[0] = 0x7fffffff & rand()
0x080497b4 <+16>: mov ds:0x804c264,eax
0x080497b9 <+21>: call 0x80487c4 <rand@plt> ; r[1] = rand()
0x080497be <+26>: mov ds:0x804c268,eax
0x080497c3 <+31>: call 0x80487c4 <rand@plt> ; r[2] = rand()
0x080497c8 <+36>: mov ds:0x804c26c,eax
0x080497cd <+41>: mov DWORD PTR [ebp-0x4],0x0 ; var1 = 0
0x080497d4 <+48>: jmp 0x8049800 <red_preflight+92> ; goto testvar1
0x080497d6 <+50>: mov eax,DWORD PTR [ebp-0x4] ;loop:
0x080497d9 <+53>: mov eax,DWORD PTR [eax*4+0x804c264] ; printf("CLOCK SYNC: %p", r[var1]);
0x080497e0 <+60>: mov DWORD PTR [esp+0x4],eax
0x080497e4 <+64>: mov DWORD PTR [esp],0x804a234
0x080497eb <+71>: call 0x8048744 <printf@plt>
0x080497f0 <+76>: mov DWORD PTR [esp],0x7a120 ; sleep(0.5)
0x080497f7 <+83>: call 0x8048724 <usleep@plt>
0x080497fc <+88>: add DWORD PTR [ebp-0x4],0x1 ; var1++
0x08049800 <+92>: cmp DWORD PTR [ebp-0x4],0x2 ;testvar1: var1 ?> 2
0x08049804 <+96>: jle 0x80497d6 <red_preflight+50> ; if not, goto loop
0x08049806 <+98>: mov DWORD PTR [esp],0x804a25c ; printf("ENTER RESYNC SEQUENCE:")
0x0804980d <+105>: call 0x8048744 <printf@plt>
0x08049812 <+110>: mov eax,ds:0x804c220 ; fgets(&buffer, 21, stdin)
0x08049817 <+115>: mov DWORD PTR [esp+0x8],eax
0x0804981b <+119>: mov DWORD PTR [esp+0x4],0x15
0x08049823 <+127>: mov DWORD PTR [esp],0x804c24c
0x0804982a <+134>: call 0x8048704 <fgets@plt>
0x0804982f <+139>: leave ; return
0x08049830 <+140>: ret
This time, before prompting us and loading our input string into buffer
(lines 26-32), we initialize array r
with three "random" numbers using rand()
(lines 4-10) and display each number to the user (lines 11-24):
CLOCK SYNC 6B8B4567
CLOCK SYNC 327B23C6
CLOCK SYNC 643C9869
ENTER CLOCK RESYNCHRONIZATION SEQUENCE:
The pseudorandom numbers which you see in your terminal should be different, but they won't be for one key reason: a time-based seed for rand()
was never set. Typically, rand()
is used with srand(time(0))
so that rand()
is seeded with a different number each time and the results of the pseudorandom number generation will be different. Since our rand()
is never seeded, it will generate the same random numbers in order every time: 0x6b8b4567
, 0x327b23c6
, and 0x643c9869
.
Now let's take a look at red()
:
0x08049831 <+0>: push ebp ; set up stack frame
0x08049832 <+1>: mov ebp,esp
0x08049834 <+3>: sub esp,0x18
0x08049837 <+6>: call 0x80497a4 <red_preflight>
0x0804983c <+11>: mov DWORD PTR [ebp-0x4],0x804a29c; var1 = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
0x08049843 <+18>: mov DWORD PTR [ebp-0x8],0x0 ; var2 = 0
0x0804984a <+25>: jmp 0x80498ba <red+137>
0x0804984c <+27>: mov eax,DWORD PTR [ebp-0x8] ;start: edx = char(buffer[var2])
0x0804984f <+30>: movzx edx,BYTE PTR [eax+0x804c24c]
0x08049856 <+37>: mov eax,ds:0x804c26c ; eax = (r[2] & 0x0000001f)
0x0804985b <+42>: and eax,0x1f
0x0804985e <+45>: add eax,DWORD PTR [ebp-0x4] ; eax = var1[r[2] % 32]
0x08049861 <+48>: movzx eax,BYTE PTR [eax]
0x08049864 <+51>: cmp dl,al ; buffer[var1] ?= var1[ r[2]%32 ]
0x08049866 <+53>: je 0x8049877 <red+70> ; if so, goto cont
0x08049868 <+55>: mov eax,ds:0x804c128 ; fail
0x0804986d <+60>: add eax,0x1
0x08049870 <+63>: mov ds:0x804c128,eax
0x08049875 <+68>: jmp 0x80498ca <red+153>
0x08049877 <+70>: mov eax,ds:0x804c26c ;cont:
0x0804987c <+75>: mov edx,eax
0x0804987e <+77>: shr edx,0x5 ; edx = r[2] // 32
0x08049881 <+80>: mov eax,ds:0x804c268 ; eax = r[1]
0x08049886 <+85>: shl eax,0x1b ; eax <<= 27
0x08049889 <+88>: or eax,edx ; eax |= edx
0x0804988b <+90>: mov ds:0x804c26c,eax ; r2 = eax
0x08049890 <+95>: mov eax,ds:0x804c268 ; remove 5 LSB (already compared)
0x08049895 <+100>: mov edx,eax ; and shift remaining bits right
0x08049897 <+102>: shr edx,0x5
0x0804989a <+105>: mov eax,ds:0x804c264 ; grab 5 LSB from left
0x0804989f <+110>: shl eax,0x1b ; insert these
0x080498a2 <+113>: or eax,edx
0x080498a4 <+115>: mov ds:0x804c268,eax
0x080498a9 <+120>: mov eax,ds:0x804c264 ; propagate
0x080498ae <+125>: shr eax,0x5
0x080498b1 <+128>: mov ds:0x804c264,eax
0x080498b6 <+133>: add DWORD PTR [ebp-0x8],0x1 ; var2++
0x080498ba <+137>: cmp DWORD PTR [ebp-0x8],0x12 ; var2 > 18?
0x080498be <+141>: jle 0x804984c <red+27> ; if not, goto start
0x080498c0 <+143>: mov DWORD PTR ds:0x804c128,0x0 ; return 0
0x080498ca <+153>: leave
0x080498cb <+154>: ret
On line 6, the string "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
is loaded into var1
, which is exactly 32 characters long. Afterwards, two things happen at the same time: var2
iterates over our input string, and some operations are performed on our "random" numbers. Let's look at those operations more closely. First (on line 12), r[2]
is and
ed with 0x0000001f
, which is equivalent to calculating r[2] % 32
. The character at corresponding index within var1
is then accessed and compared with the next character of our input string. To move onto the next character, var2
is incremented and all bits in r[0]
through r[2]
are shifted over by 5 bits. In order to determine the proper input string, let's use a Python script, since Python has built-in support for arbitrarily large numbers:
x = ( 0x643c9869 +
2**32 * 0x327b23c6 +
2**64 * 0x6b8b4567)
s = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
answer = ""
while x > 0:
answer += s[x % 32]
x >>= 5
print(answer)
After running this script, our answer is: "KDG3DU32D38EVVXJM64"
. Entering this in at the prompt, we have successfully defused Dr. Von Noizeman's bomb!