二元炸弹的解散。解码转储 - 第2阶段

时间:2016-04-24 01:29:58

标签: x86 reverse-engineering

ToIntBiFunction

这是我对二进制炸弹拆除实验室特定阶段的汇编转储。我必须输入六个数字来破解代码并继续下一阶段。我一直坚持解码,特别是在以下部分:

0x08048b94 <+0>:push   %esi
0x08048b95 <+1>:    push   %ebx
0x08048b96 <+2>:    sub    $0x34,%esp
0x08048b99 <+5>:    lea    0x18(%esp),%eax
0x08048b9d <+9>:    mov    %eax,0x4(%esp)
0x08048ba1 <+13>:   mov    0x40(%esp),%eax
0x08048ba5 <+17>:   mov    %eax,(%esp)
0x08048ba8 <+20>:   call   0x804920a <read_six_numbers>
0x08048bad <+25>:   cmpl   $0x0,0x18(%esp)
0x08048bb2 <+30>:   jne    0x8048bbb <phase_2+39>
0x08048bb4 <+32>:   cmpl   $0x1,0x1c(%esp)
0x08048bb9 <+37>:   je     0x8048bda <phase_2+70>
0x08048bbb <+39>:   call   0x80490d7 <explode_bomb>
0x08048bc0 <+44>:   jmp    0x8048bda <phase_2+70>
0x08048bc2 <+46>:   mov    -0x8(%ebx),%eax
0x08048bc5 <+49>:   add    -0x4(%ebx),%eax
0x08048bc8 <+52>:   cmp    %eax,(%ebx)
0x08048bca <+54>:   je     0x8048bd1 <phase_2+61>
0x08048bcc <+56>:   call   0x80490d7 <explode_bomb>
0x08048bd1 <+61>:   add    $0x4,%ebx
0x08048bd4 <+64>:   cmp    %esi,%ebx
0x08048bd6 <+66>:   jne    0x8048bc2 <phase_2+46>
---Type <return> to continue, or q <return> to quit---<return>
0x08048bd8 <+68>:   jmp    0x8048be4 <phase_2+80>
0x08048bda <+70>:   lea    0x20(%esp),%ebx
0x08048bde <+74>:   lea    0x30(%esp),%esi
0x08048be2 <+78>:   jmp    0x8048bc2 <phase_2+46>
0x08048be4 <+80>:   add    $0x34,%esp
0x08048be7 <+83>:   pop    %ebx
0x08048be8 <+84>:   pop    %esi
0x08048be9 <+85>:   ret 

如果有人可以帮助我执行这个汇编代码,那将会很棒。谢谢!

1 个答案:

答案 0 :(得分:2)

好的,这将是一个很长的答案,但是你走了:

让我先谈谈你关于+ 46 / + 49行的具体问题。他们做的是:

  • 将地址[ebx-8]的内存值写入eax
  • 将地址[ebx-4]的内存值添加到eax
  • 因此,实际上,它设置eax = [ebx-8] + [ebx-4],其中[]表示取消引用/内存访问。

您的代码使用AT&amp; T语法。我个人觉得它非常混乱,难以阅读。所以从现在开始,我将使用英特尔语法。对不起,如果我们和AT&amp; T呆在一起,我可能无法继续回答。

主要区别是:

  • AT&amp; T将来源放在最后,将目的地放在开头,而英特尔则反过来。例如,AT&amp; T中的mov %ebx,%eax在英特尔中为mov eax, ebx
  • 英特尔没有大多数前缀,例如%*等。
  • 对于内存访问(或使用lea进行有效地址计算),AT&amp; T使用offset(base),而英特尔使用[base+offset](我个人觉得它更容易阅读),所以{{在您的示例中,1}}在英特尔语法中变为mov -0x8(%ebx),%eax
  • 它使用诸如mov eax, [ebx-8]之类的语法(例如l变为dword ptr)来指定操作数大小,而不是cmpl之类的命令后缀。

好的,所以我试着整理一下整个代码。

首先,我将所有内容转换为Intel语法并创建了标签:

cmp dword ptr

然后我实际编译了代码然后将其加载到IDA(一个漂亮的反汇编程序)。我为变量设置了一些标签并创建了一个结构phase_2: push esi push ebx sub esp, 34 lea eax, [esp+18] mov [esp+4], eax mov eax, [esp+40] mov [esp], eax call read_six_numbers cmp dword ptr [esp+18], 0 jne phase_2_39 phase_2_32: cmp dword ptr [esp+1c], 1 je phase_2_70 phase_2_39: call explode_bomb jmp phase_2_70 phase_2_46: mov eax, [ebx-8] add eax, [ebx-4] cmp [ebx], eax je phase_2_61 phase_2_56: call explode_bomb phase_2_61: add ebx, 4 cmp ebx, esi jne phase_2_46 phase_2_68: jmp phase_2_80 phase_2_70: lea ebx, [esp+20] lea esi, [esp+30] jmp phase_2_46 phase_2_80: add esp, 34 pop ebx pop esi retn read_six_numbers: int3 explode_bomb: int3 ,它基本上是一个包含6个DWORD数字的数组。对我来说,SIX_NUMBERS_STRUCT函数看起来像一个参数,可能是你输入的数字的某种字符串。然后它调用phase_2并将上述输入指针与指向6 DWORD值结构的指针一起传递(保存在[esp + 3C-24] = [esp + 18],因此可以访问6数字使用[esp + 18],[esp + 1C],[esp + 20],[esp + 24],[esp + 28]和[esp + 2C])。然后,对这些数据进行了几次检查和计算。

对以下部分的一点解释:通常,在具有局部变量的函数中使用基指针(read_six_numbers)以具有指针,该指针始终引用堆栈上的相同点,即使在推送新值时也是如此。在这里,你不是 - 而是在开始时从ebp中减去0x34。另外,之前有两个esp es,这意味着堆栈指针现在比函数开始时的点低0x3C字节。这就是IDA计算相对于push的所有堆栈偏移的原因。

好的,首先,这里是可视化的汇编代码(您应该能够更容易地理解程序流): Flowchart

我还在其上运行了一个反编译器,以生成代码的C-like表示并稍加注释:

esp-3C

所以,我的见解是:

  • 首先,代码读取六个数字,将void __cdecl phase_2(void *argument_to_phase_2) { int *currentNumber; // ebx@6 SIX_NUMBERS_STRUCT numbers_struct; // [sp+18h] [bp-24h]@1 int dummy_var_after_numbers; // [sp+30h] [bp-Ch]@6 read_six_numbers(argument_to_phase_2, &numbers_struct); if ( numbers_struct.numbers[0] || numbers_struct.numbers[1] != 1 ) explode_bomb(); currentNumber = &numbers_struct.numbers[2]; do { if ( *currentNumber != *(currentNumber - 1) + *(currentNumber - 2) ) explode_bomb(); ++currentNumber; } while ( currentNumber != &dummy_var_after_numbers ); } ([esp + 40],保存在[esp + 0]中作为堆栈中参数列表的一部分)传递到argument_to_phase_2,使用指向6 * DWORD结构的指针来保存数字(结构从[esp + 18]开始,指针存储在[esp + 4]中作为堆栈参数列表的一部分)。
  • 然后,代码验证第一个数字(read_six_numbers = [esp + 18])是否为0,第二个数字(numbers[0] = [esp + 1C])是1。
  • 然后,代码循环通过其余数字(第三到第六),验证每个数字numbers[1]等于前两个numbers[n]的总和。当循环超过数字列表的末尾时,循环终止,这是通过将指向当前要检查的数字的指针与numbers[n - 1] + numbers[n - 2]([esp + 30])进行比较来完成的。数字列表后面的未使用的虚拟变量(&#34;第七个数字&#34;的种类。)。

如果你有任何数学背景,这个逻辑会立刻引发你的想法:斐波那契!没错,基本上你必须输入Fibonacci系列的前6个数字,包括零:dummy_var_after_numbers - 这是你的代码!

作为参考,这是堆栈上的变量列表及其使用方式:

0, 1, 1, 2, 3, 5

我希望这个答案能够清楚地说明代码的工作原理。再说一遍,我很抱歉,我强迫你在这里使用不同的语法(英特尔),但AT&amp; T语法只会让我感到畏缩,我无法使用它。