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
如果有人可以帮助我执行这个汇编代码,那将会很棒。谢谢!
答案 0 :(得分:2)
好的,这将是一个很长的答案,但是你走了:
让我先谈谈你关于+ 46 / + 49行的具体问题。他们做的是:
eax = [ebx-8] + [ebx-4]
,其中[]
表示取消引用/内存访问。您的代码使用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
的所有堆栈偏移的原因。
好的,首先,这里是可视化的汇编代码(您应该能够更容易地理解程序流):
我还在其上运行了一个反编译器,以生成代码的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语法只会让我感到畏缩,我无法使用它。