Software Reverse Engineering: Diffusing Phase 4
Baby, I hate little secrets you keep from me x_x
The article continues the previous one where we diffused phase 3 of bomb binary. Let’s get started with phase 4 on Windows platform using Windbg.
This writeup is a part of the article series —
- Software Reverse Engineering: Ripping Apart Bomb Binary
(Windbg: Windows)
- Software Reverse Engineering: Diffusing Phase 1
(GNU GDB: Linux)
- Software Reverse Engineering: Diffusing Phase 2
(Windbg: Windows)
- Software Reverse Engineering: Diffusing Phase 3
(Windbg: Windows)
- Software Reverse Engineering: Diffusing Phase 4
(Windbg: Windows)
- Software Reverse Engineering: Diffusing Phase 5
(Windbg: Windows)
- Software Reverse Engineering: Diffusing Phase 6
(GNU GDB: Linux)
Rescue Mission: Phase_4
Looking at the disassembly for phase_4()
(uf bomb!phase_4
). We see sscanf() call exactly the same way as in phase_3()
, i.e. expecting 2 integers. Therefore, we’ll be following an initial approach similar to previous phase.
0:001> da 00007ff6`0377c220
00007ff6`0377c220 "%d %d"
Validating user input
Let’s statically analyze how it validates user input.
0:001> uf phase_4
...
135 00007ff6`037723c1 cmp dword ptr [rbp+4],0
135 00007ff6`037723c5 jl bomb!phase_4+0x7d (00007ff6`037723cd)
135 00007ff6`037723c7 cmp dword ptr [rbp+4],0Eh
135 00007ff6`037723cb jle bomb!phase_4+0x82 (00007ff6`037723d2)
136 00007ff6`037723cd call bomb!ILT+945(explode_bomb)
...
This simply says that user input should be — 0 < [rbp+4] < 0xe (14)
(where [rbp+4]
is first integer of user input) for bomb to not explode.
Next, we see a call to func4()
whose return value decides whether bomb explodes or not.
0x00007ff6`037723e9
to0x00007ff6`037723f2
: return value (in eax) is checked against[rbp+64]
(first integer input), if it isn’t equal explode_bomb() is called.00007ff6`037723f4
to00007ff6`037723fa
: explodes bomb if[rbp+24]
(second integer input) is not equal to[rbp+64]
. We have to peek intorpb+64
to find out this value.
0:000> dd rbp+64 L1
00000038`b74ff974 0000000a
Therefore, we are sure that the correct value for second integer in input string is 10. The question is how can we make func4()
return a value of 0xa
?
Analyzing func4
Let’s analyze what parameters it takes by stepping to call instruction for func4()
.
0:001> pa 00007ff6`037723e4
0:000> r rcx, rdx, r8
rcx=0000000000000002 rdx=0000000000000000 r8=000000000000000e
which makes function prototype for func4 to be —
int func4 (my_int, a, b); // a = 0, b = 0xe
This function references its parameters via RBP, a stack diagram therefore would give us more clarity over what it refers.
Therefore, [rbp+100]
is first parameter, whereas [rbp+108]
and [rbp+110]
are second and third parameter. First part of the disassembly does some math which should make sense after understanding the stack diagram —
eax = (((signed qword typecast)(b - a)) - a)/2;
*(rbp+4) = ecx = a + eax
[rbp+4]
is then compared to first parameter of func4() (i.e. first integer in our input string). Accordingly, the corresponding block of code is executed.
121 00007ff6`03771f75 cmp dword ptr [rbp+4],eax
121 00007ff6`03771f78 jle bomb!func4+0x8a (00007ff6`03771f9a)
...
123 00007ff6`03771f9a mov eax,dword ptr [rbp+100h]
123 00007ff6`03771fa0 cmp dword ptr [rbp+4],eax
123 00007ff6`03771fa3 jge bomb!func4+0xb5 (00007ff6`03771fc5)
else
124 00007ff6`03771fa5 mov eax,dword ptr [rbp+4]
124 00007ff6`03771fa8 inc eax
124 00007ff6`03771faa mov r8d,dword ptr [rbp+110h]
124 00007ff6`03771fb1 mov edx,eax
124 00007ff6`03771fb3 mov ecx,dword ptr [rbp+100h]
124 00007ff6`03771fb9 call bomb!ILT+725(func4) (00007ff6`037712da)
124 00007ff6`03771fbe add eax,dword ptr [rbp+4]
124 00007ff6`03771fc1 jmp bomb!func4+0xb8 (00007ff6`03771fc8)...
126 00007ff6`03771fc5 mov eax,dword ptr [rbp+4] // BASE CASE
127 00007ff6`03771fc8 lea rsp,[rbp+0E8h]
127 00007ff6`03771fcf pop rdi
127 00007ff6`03771fd0 pop rbp
127 00007ff6`03771fd1 ret
Disassembly code for else
case is as follows —
122 00007ff6`03771f7a mov eax,dword ptr [rbp+4]
122 00007ff6`03771f7d dec eax
122 00007ff6`03771f7f mov r8d,eax
122 00007ff6`03771f82 mov edx,dword ptr [rbp+108h]
122 00007ff6`03771f88 mov ecx,dword ptr [rbp+100h]
122 00007ff6`03771f8e call bomb!ILT+725(func4) (00007ff6`037712da)
122 00007ff6`03771f93 add eax,dword ptr [rbp+4]
122 00007ff6`03771f96 jmp bomb!func4+0xb8 (00007ff6`03771fc8)
...
127 00007ff6`03771fc8 lea rsp,[rbp+0E8h]
127 00007ff6`03771fcf pop rdi
127 00007ff6`03771fd0 pop rbp
127 00007ff6`03771fd1 ret
Therefore, final pseudocode for func4 becomes —
NOTE: Disassembly of func4()
for Linux version of bomb binary differs a bit from this one (generated by Microsoft’s VS compiler). You can find the pseudocode crafted for Linux version of bomb here.
Understanding Reccursive Calls
Below are a few attributes I observed while analyzing this function —
- There is a reccursive call to
func4()
with distinct 2nd and 3rd parameter (a
andb
) but constant 1st parameter (first integer from our input string). - Base case for reccursion here is:
*(rbp+4) == my_int
. - Mathematically the expression —
((b-a)-a)/2 + a
is evaluated (and stored in*(rbp+4)
)and compared withmy_int
. Depending on the value ofmy_int
, the control flow is transfered for nextfunc4()
call. - Value inside
a
&b
can be controlled via value inmy_int
.
Initially, the expression evaluates to a value of 7
. My initial approach was to create a call tree to find a pattern , but as I created the first level …
func4 (x, 0, 14) [expr = 7 (i.e. (14-0-0)/2 + 0)]
|
|
|-> if(x > 7): call func4(x, 7+1, 14): [expr = 7 (i.e. (14-16)/2+8)]
|
|-> if(x < 7): call func4(x, 0, 7-1): [expr = 3 (i.e. (6-0)/2+0)]
The second level (when x < 7) returns us a value of 3, if we provide an integer of 3 as input it will fall into the base case (i.e. x == expr
). The return value of 3
when added to initial evaluation of expression, i.e. 7
returns a value of 10
(i.e. eax += *(rbp+4); return eax;
in the end of our func4() pseudocode) as needed by phase_4(). Let’s try a value of 3
as our input.
Epilogue
Phase 4 was interesting as it involved reverse engineering simple mathematical expressions, if-else code constructs, reccursion and drawing stack diagram as done for func4()'s
stack frame. Let’s continue right into the writeup for diffusing phase_5.
Cheers,
Abhinav Thakur
(a.k.a compilepeace)
Connect on Linkedin