craftwa.re

A walk outside the sandbox

Home Blog Cheat Sheets MacOS Tips Area 51 About

[CTF] Difusing a (binary) bomb

|

Logo

Overview

This very good Intro to X86 Assembly course has a nice final exercise which is a task to defuse a binary bomb - an executable, without source code, with more phases, each one needing a password.

I understand that there are many variants of this bomb, so my answer won’t fit every bomb, but anyway, spoilers ahead, be aware! Each level introduces a programming construct (simple string manipulations, array, functions, cases, linked lists, trees, etc.) so it’s very interesting to actually reverse engineer them and understand them. The long version of the solutions is here. The short version below.

Phase 1

I’ve created a file with commands for easy debugging, and started the bomb like this:

$ gdb -q -x commands bomb
(gdb) run bomb-answers.txt

In the first phase there’s a simple string comparison in place. Very straight-forward. The first string being compared is our input:

(gdb) x /x $ebp+0x8
0xbffff440: 0x0804b680
(gdb) x /s 0x0804b680
0x804b680 <input_strings>: "test"

And second one is the password for phase_1:

(gdb) x/s 0x80497c0
0x80497c0:    "Public speaking is very easy."

Phase 2

Now 6 numbers are read from into an array, and an algorithm is applied. After reverse engineering, it looks as follows:

v[0] = 1
v[i] = (i+i) * v[i-1]

So we find the solution of phase 2: 1 2 6 24 120 720

Phase 3

This expects as input a number, a character and another number:

0x08048bb1 <+25>: push   0x80497de --> the format string for sscanf, "%d %c %d"
0x08048bb6 <+30>: push   edx
0x08048bb7 <+31>: call   0x8048860 <sscanf@plt>

The rest of the disassembled instructions are a case structure, which checks the letter and the second number based on the first value. For step by step guidance, check the phase3.txt file.

Phase 4

This phase is more interesting. It introduces a recursive function! The disassembled code looks like this:

Dump of assembler code for function func4:
   0x08048ca0 <+0>:	push   ebp
   0x08048ca1 <+1>:	mov    ebp,esp
   0x08048ca3 <+3>:	sub    esp,0x10
   0x08048ca6 <+6>:	push   esi
   0x08048ca7 <+7>:	push   ebx
   0x08048ca8 <+8>:	mov    ebx,DWORD PTR [ebp+0x8]
   0x08048cab <+11>:	cmp    ebx,0x1
   0x08048cae <+14>:	jle    0x8048cd0 <func4+48>
   0x08048cb0 <+16>:	add    esp,0xfffffff4
   0x08048cb3 <+19>:	lea    eax,[ebx-0x1]
   0x08048cb6 <+22>:	push   eax
   0x08048cb7 <+23>:	call   0x8048ca0 <func4>
   0x08048cbc <+28>:	mov    esi,eax
   0x08048cbe <+30>:	add    esp,0xfffffff4
   0x08048cc1 <+33>:	lea    eax,[ebx-0x2]
   0x08048cc4 <+36>:	push   eax
   0x08048cc5 <+37>:	call   0x8048ca0 <func4>
   0x08048cca <+42>:	add    eax,esi
   0x08048ccc <+44>:	jmp    0x8048cd5 <func4+53>
   0x08048cce <+46>:	mov    esi,esi
   0x08048cd0 <+48>:	mov    eax,0x1
   0x08048cd5 <+53>:	lea    esp,[ebp-0x18]
   0x08048cd8 <+56>:	pop    ebx
   0x08048cd9 <+57>:	pop    esi
   0x08048cda <+58>:	mov    esp,ebp
   0x08048cdc <+60>:	pop    ebp
   0x08048cdd <+61>:	ret    
End of assembler dump.

After a bit of playing around, the purpose of the code becomes obvious. It’s the Fibonacci function, implemented recursively:

func4(x):
	if x <= 1 :
		return 1
	else :
		y = func4(x-1)
		z = func4(x-2)
		return y + z

Phase 5

In this phase, the input password string is parsed character by character, and each parsed character gives an index into an input string. This way, the final password is constructed gradually, without the need to store it in plain.

We have to form the password giants from the source string isrveawhobpnutfg:

   [..]
   0x08048d4d <+33>:	xor    edx,edx
   0x08048d4f <+35>:	lea    ecx,[ebp-0x8]
   0x08048d52 <+38>:	mov    esi,0x804b220		--> "isrveawhobpnutfg\260\001"
   0x08048d57 <+43>:	mov    al,BYTE PTR [edx+ebx*1]
   0x08048d5a <+46>:	and    al,0xf
   0x08048d5c <+48>:	movsx  eax,al
   0x08048d5f <+51>:	mov    al,BYTE PTR [eax+esi*1]
   0x08048d62 <+54>:	mov    BYTE PTR [edx+ecx*1],al
   0x08048d65 <+57>:	inc    edx
   0x08048d66 <+58>:	cmp    edx,0x5
   0x08048d69 <+61>:	jle    0x8048d57 <phase_5+43>
   0x08048d6b <+63>:	mov    BYTE PTR [ebp-0x2],0x0
   0x08048d6f <+67>:	add    esp,0xfffffff8
   0x08048d72 <+70>:	push   0x804980b		-->  "giants"
   0x08048d77 <+75>:	lea    eax,[ebp-0x8]
   0x08048d7a <+78>:	push   eax
   0x08048d7b <+79>:	call   0x8049030 <strings_not_equal>
   [..]

After reversing, phase_5 function should be something like:

phase_5(s) {
 src = "isrveawhobpnutfg";
 dest = "12345";
  
 for (i=0; i&lt;=5; ++i) {
  idx = s[i] && 0xf; // cuts the most significant hex digit
  dest[i] = src[idx];
 }
}

So the first hex digit of the password represents the index. We need the following indexes: 15 (0xf), 0, 5, 11 (0xb), 13 (0xd) and 1. A possible password is be: o (0x6f) p (0x70) u (0x75) k (0x6b) m (0x6d) q (0x71).

Passphrase for next level: opukmq

Phase 6

This was the most difficult to figure out from the assembly code, as it’s split into more sub-stages, and involves linked lists structures. Complete details are in here.

First 6 numbers are read. There is a predefined linked list also. Another important variable used is an array containing addresses of list elements.

  • stage1: check that all 6 numbers read are between [1,..,6] and all different
  • stage2: builds and arranges a second array with pointers to list elements
  • stage3: fixes the links between elements from the input list to match the array constructed in stage2
  • stage4: checks that the elements of the linked list are in reverse sorted order.

The second stage is the most important: we have to arrange the values of the list elements, so that we can pass stage 4 check (should be in reverse order). Current order :

(gdb) printf "%08x %08x %08x %08x %08x %08x \n", *0x0804b26c, *0x0804b260, *0x0804b254, *0x0804b248, *0x0804b23c, *0x0804b230
000000fd 000002d5 0000012d 000003e5 000000d4 000001b0 

The pseudo-code for this step could be something like:

   // 2nd stage
   i = 0
   ecx = v[0]
   eax = v2
   y = v2
   
   while i<=5 : 
       elem = list_head
       elem = head
       j = 1
       edx = i
     
       if ( j < v[i] ):
           do {
               elem = elem.next
               j++
           } while (j < v[i])
       v2[i] = elem
       i++

This stage builds a list of pointers to elements, which is used in stage 3 and 4. Using the previously deduced agorithm, the input numbers (which mean how much we move an element, to have them in reverse order), should be:

  • pos 1: 3 (head->next->next->next which is the biggest num)
  • pos 2: 1 (head->next, which is the second biggest )
  • .. and so on.

Because of the advancing algorithm, we add 1 to the previous, and get the solution: 4 2 6 3 1 5

Secret Phase

In the phase_difused function, we have another function called secret_phase, activated only after first stages are difused:

0x08049533 <+7>:  cmp    DWORD PTR ds:0x804b480,0x6    
0x0804953a <+14>: jne    0x804959f <phase_defused+115> 

The passphrase from phase 4 is parsed again, now looking for a string. As we see below, we can get that string and advance:

0x08049544 <+24>: push   0x8049d03   --> "%d %s"   
0x08049549 <+29>: push   0x804b770   --> "9"  this is the input from phase_4    
0x0804954e <+34>: call   0x8048860 <sscanf@plt>    
0x08049553 <+39>: add    esp,0x10    
0x08049556 <+42>: cmp    eax,0x2    
0x08049559 <+45>: jne    0x8049592 <phase_defused+102>    
0x0804955b <+47>: add    esp,0xfffffff8    
0x0804955e <+50>: push   0x8049d09  --> "austinpowers"
0x08049563 <+55>: push   ebx    
0x08049564 <+56>: call   0x8049030 <strings_not_equal> 

We add the password austinpowers and get the following 2 messages printed:

Curses, you’ve found the secret phase!

But finding it and solving it are quite different…

The secret_phase function calls another function, fun7 (very fun:) with an address and our input as a second parameter. After digging into the disassebly, the last fun7 is something like this:

int fun7(int *adr, int x) {
	if(adr == NULL) {
		ret = -1; 	// 0xffffffff
		goto exit;
	}
	if (x >= *adr) {
		if (x == *adr) {
			ret = 0
		} else {
			ret = fun7(*(adr+8), x)
			ret *= 2;
			ret ++;
		}
	} else {
		ret = fun7(*(adr+4), x)
		ret *= 2		
	}

exit:	
	return ret;
}	

Initial address passed to the function is 0x804b320. At this address there is a tree with 4 levels, as below. We navigate to the left or right branch depending on the input value. If input x is equal to value in branch, we return 0.

0x24
0x8 0x32 
0x6 0x16 0x2d 0x6b 
................................. 0x3e9

We want fun7() to return 7. 7 = 23+1 = 2(2*1+1)+1. According to the tree and deduced algorothm, we have:

f(0x24) = 0 
f(0x32) = 2*f(0x24)+1 = 1 
f(0x6b) = 2*f(0x32)+1 = 3 
f(0x3e9) = 2*f(0x6b)+1 = 7 

0x3e9 is 1001 decimal, and is accepted by the first check (param-1 <= 1000).

–=End=–

~ ./bomb bomb-answers.txt 
Welcome to my fiendish little bomb. You have 6 phases with which to blow yourself up. Have a nice day! 
Phase 1 defused. How about the next one? 
That's number 2. Keep going! 
Halfway there! 
So you got that one. Try this one. 
Good work! On to the next... 
Curses, you've found the secret phase! 
But finding it and solving it are quite different... 
Wow! You've defused the secret stage! 
Congratulations! You've defused the bomb!_

Many thanks to the guys at Open Security Trainings for this and all the other interesting materials there!

If you’re interested in a reverse engineering ransomeware hands-on tutorial and learn to reverse and decrypt some ransomware along the way, check out this course! Very recommended for intermediate and advanced. Or, if you’re interested to cover the basics, see this one.