问题描述
我在这里有这个Assembly函数,并多次运行它以试图了解它的功能和模式。在理解它的模式方面,我碰到了一堵砖墙。在此感谢任何形式的指导。
0x0000555555555cbe <+0>: push %rbx
0x0000555555555cbf <+1>: mov %edx,%eax
0x0000555555555cc1 <+3>: sub %esi,%eax
0x0000555555555cc3 <+5>: mov %eax,%ebx
0x0000555555555cc5 <+7>: shr $0x1f,%ebx
0x0000555555555cc8 <+10>: add %eax,%ebx
0x0000555555555cca <+12>: sar %ebx
0x0000555555555ccc <+14>: add %esi,%ebx
0x0000555555555cce <+16>: cmp %edi,%ebx
0x0000555555555cd0 <+18>: jg 0x555555555cda <func4+28>
0x0000555555555cd2 <+20>: cmp %edi,%ebx
0x0000555555555cd4 <+22>: jl 0x555555555ce6 <func4+40>
0x0000555555555cd6 <+24>: mov %ebx,%eax
0x0000555555555cd8 <+26>: pop %rbx
0x0000555555555cd9 <+27>: retq
0x0000555555555cda <+28>: lea -0x1(%rbx),%edx
0x0000555555555cdd <+31>: callq 0x555555555cbe <func4>
0x0000555555555ce2 <+36>: add %eax,%ebx
0x0000555555555ce4 <+38>: jmp 0x555555555cd6 <func4+24>
0x0000555555555ce6 <+40>: lea 0x1(%rbx),%esi
0x0000555555555ce9 <+43>: callq 0x555555555cbe <func4>
0x0000555555555cee <+48>: add %eax,%ebx
0x0000555555555cf0 <+50>: jmp 0x555555555cd6 <func4+24>
如果我的输入为“ 6 1”,则寄存器如下:
rax 0x2 2
rbx 0x7fffffffe0c8 140737488347336
rcx 0x0 0
rdx 0xe 14
rsi 0x0 0
rdi 0x6 6
rbp 0x555555557170 0x555555557170 <__libc_csu_init>
rsp 0x7fffffffdfb8 0x7fffffffdfb8
r8 0x0 0
r9 0x0 0
r10 0x7ffff7b82f20 140737349431072
r11 0x55555555763a 93824992245306
r12 0x5555555558f0 93824992237808
r13 0x7fffffffe0c0 140737488347328
r14 0x0 0
r15 0x0 0
rip 0x555555555cbe 0x555555555cbe <func4>
eflags 0x293 [ CF AF SF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
k0 0x0 0
k1 0x0 0
k2 0x0 0
k3 0x0 0
k4 0x0 0
k5 0x0 0
k6 0x0 0
k7 0x0 0
我已经尝试了很多代码,并一步步遵循代码,似乎无法理解此函数遵循的模式。
RDX似乎总是为14,无论输入如何。从我的理解中可以看出,存在一个循环,可以查看移位和减法的某种组合是否大于或小于原始输入。
这些是我尝试过的输入以及它们的以下输出:
14,1 = 45
13,1 = 31
5,1 = 15
6,1 = 21
7,1 =返回7?
8,1 = 35
解决方法
我总是喜欢为程序集添加一个调用图,所以我在Binary Ninja中进行了组装:
就个人而言,我更喜欢intel语法,因此从现在开始我将继续使用它。我们可以看到3个寄存器,它们是在写入之前被读取的,分别是edi,esi和edx。这些也恰好是标准x86调用约定中的前3个参数寄存器,因此我们可以推断此函数具有3个参数。这是带注释的(要理解为什么将0x1f移位会产生符号位,您必须首先了解two's complement):
以下是等效的C代码:
int func4(int a,int b,int c) {
int num = (b + c + (c < b)) / 2;
if (num > a) {
return func4(a,b,num - 1) + num;
} else if (num < a) {
return func4(a,num + 1,c) + num;
} else {
return num;
}
}
现在,我们有了清晰易读的C代码,我们可以开始思考该函数的实际作用。 (b + c) / 2
是平均值,c < b
始终为0或1,这意味着num
变量必须非常接近平均值。实际上,如果b
和c
均为偶数或均为奇数,则num
就是平均值,因为可能的加法1被整数除法取整并且不会改变价值。但是,如果b
和c
中只有一个是偶数,则可能的加法实际上很重要。我们来看两种情况:
-
b
小于c
,在这种情况下c < b
的取值为0,因此num
只是平均值,由于整数除法而四舍五入。由于b
小于c
,因此四舍五入是朝b
舍入。 -
b
大于c
,在这种情况下,c < b
的取值为1,因此num
是平均值加一半,将其推高到下一个值。这意味着num
是四舍五入的平均值,并且由于b
大于c
,因此将其舍入到b
。
因此,b
本质上是b
和c
的平均值,但四舍五入为b
的值。现在我们来看一下实际的递归情况。要注意的一件事是a
永远不会改变。实际上,该函数永远不会终止,直到b
和c
的平均值达到a
为止。如果平均值大于a
,则将c
减小为平均值减去1,以使平均值下降;如果平均值小于a
,则b
增加到平均值加一以提高平均值。现在,假设b < c
。为什么?由于将b > c
设置为平均值实际上会减少 b
,因此b
及其所有逻辑中断并进入无限递归时,此函数确实令人讨厌而不是增加b
,永远不要让平均值达到a
。因此,最后,您进行了一些奇怪的二进制搜索,其中平均值开始于期望值之上或之下,然后到达另一侧,然后缓慢接近期望值,并沿途返回所有平均值的总和。这也是func4(7,1,14)
返回7:7是1
和14
的平均值的原因,因此它立即返回。