Software Reverse Engineering: Diffusing Phase 2
Baby, I hate little secrets you keep from me x_x
The article continues the previous one where we diffused phase 1 of bomb binary. Let’s get started with phase 2 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_2
Looking at phase_2()
call site, we see the return value of read_line() being passed to phase_2()
as parameter. This is similar to how we called phase_1()
.
0:001> uf main
...
84 00007ff6`03771b0d call bomb!ILT+460(read_line)
84 00007ff6`03771b12 mov qword ptr [rbp+8],rax
85 00007ff6`03771b16 mov rcx,qword ptr [rbp+8]
85 00007ff6`03771b1a call bomb!ILT+970(phase_2) (00007ff6`037713cf)
...
0:001> .restart
0:001> .reload /f
0:001> bp bomb!phase_2
0:001> g
The program breaks at first instruction inside phase_2()
. Let’s have a complete overview of disassembly for phase_2 (uf phase_2
).
Here, call to read_six_numbers
hints us that this function takes 6 integers as argument. Let’s supply 6 random numbers as input to this phase — 1 2 3 4 5 6
.
The first useful instruction we encounter is call to read_six_numbers()
. Let’s step to this address using pa <address or symbol>
(that prints out every instruction it steps over on its way to address) and examine what parameters it takes. If you track a stack diagram carefully, the value in first parameter (RCX) points to the shadow store of caller stack frame, which is where our input string for phase_2 is stored and second parameter also points somewhere inside stack frame of phase_2()
which could possibly be the space allocated for 6 integers (i.e. 24 bytes).
0:000> da rcx
00007ff6`03780250 "1 2 3 4 5 6"
0:000> dd rdx L6
00000016`e3f0f638 cccccccc cccccccc cccccccc cccccccc
00000016`e3f0f648 cccccccc cccccccc0:000> p
0:000> dd 00000016`e3f0f638 L6
00000016`e3f0f638 00000001 00000002 00000003 00000004
00000016`e3f0f648 00000005 00000006
We see our input integers being stored on callee stack frame in the same order. Now, we just need to figure out how is this input validated by the program.
46 00007ff6`037720e7 mov eax,4
46 00007ff6`037720ec imul rax,rax,0
46 00007ff6`037720f0 cmp dword ptr [rbp+rax+28h],1
46 00007ff6`037720f5 je bomb!phase_2+0x6c (00007ff6`037720fc)
47 00007ff6`037720f7 call bomb!ILT+945(explode_bomb)
49 00007ff6`037720fc mov dword ptr [rbp+4],1
Did you notice how this compare instruction performs an array access using a r/mX form (as explained previously). Step over to this intruction and examine that the address points to the first element of where our supplied input is stored on stack. It compares first number entered by us with integer 1.
0:000> dd rbp+rax+28 L1
00000016`e3f0f638 00000001
Luckily, we choose 1 as the first random number to input taking the jump to (avoiding call to bomb!phase_2+0x6c (00007ff6`037720fc)
(avoiding call to explode_bomb()
). First validation passed.
Identifying Loop construct
A loop is comprised mainly of 3 parts apart from its body. Below is the example for loop
construct.
for (Initialization expr; Termination Condition; modification expr) {
// Loop body
}
Below we try to identify a loop construct from the below assembly.
----------------- Loop Initialization Expression ------------------
0007ff6`037720fc mov dword ptr [rbp+4],1
00007ff6`03772103 jmp bomb!phase_2+0x7d (00007ff6`0377210d)----------------- Loop Modification Expression ------------------
00007ff6`03772105 mov eax,dword ptr [rbp+4]
00007ff6`03772108 inc eax
00007ff6`0377210a mov dword ptr [rbp+4],eax------------------- Loop Termination Condition -------------------00007ff6`0377210d cmp dword ptr [rbp+4],6
00007ff6`03772111 jge bomb!phase_2+0xa2 (00007ff6`03772132) [br=0]------------------------ Loop Body ------------------------------
00007ff6`03772113 movsxd rax,dword ptr [rbp+4]
00007ff6`03772117 mov ecx,dword ptr [rbp+4]
00007ff6`0377211a dec ecx
00007ff6`0377211c movsxd rcx,ecx
00007ff6`0377211f mov ecx,dword ptr [rbp+rcx*4+28h]
00007ff6`03772123 shl ecx,1
00007ff6`03772125 cmp dword ptr [rbp+rax*4+28h],ecx
00007ff6`03772129 je bomb!phase_2+0xa0 (00007ff6`03772130)
00007ff6`0377212b call bomb!ILT+945(explode_bomb)
00007ff6`03772130 jmp bomb!phase_2+0x75 (00007ff6`03772105)00007ff6`03772132 ---> return to caller
Some points to note:
shl
instruction is generated by Microsoft VS compiler when performing multiplication of usually anunsigned integer
by a power of 2 (otherwise imul instruction is used). Hereshl ecx,1
essentially multiplies ecx by 2.[rbp+rcx*4+28h]
is performing array access using r/mX form[base +index*scale + displacement]
as explained earlier.
Pseudocode for this code construct may be —
void phase_2 (const char *ips) {
int nums[6]; // user input is stored here
int i; // 'i' is essentially [rbp+4] read_six_numbers (ips, a); for (i = 1; i < 6; ++i) {
if ((num[i-1] * 2) != num[i])
explode_bomb();
}
}
This loop construct ensures that user enter numbers in an ascending order such that every integer we enter is double of the preceeding integer while the sequence starts with integer 1. Therefore, the valid input for phase_2() becomes 1 2 4 8 16 32
.
Pseudocode for read_six_numbers (Optional)
Below is the disassembly for read_six_numbers()
.
We should now be able to reverse this simple disassembly. Below is the pseudocode for your reference.
Epilogue
While diffusing phase 2, we explored how Microsoft compiler makes use of r/mX form to performs memory access and how to identify a loop construct in the given assembly. Let’s continue right into the writeup for diffusing phase_3.
Cheers,
Abhinav Thakur
(a.k.a compilepeace)
Connect on Linkedin