我已经评论了我认为代码在做什么。
我尝试过放置aaaaaaaaa
、AAAAAAAAA
、!!!!!!!!!
和000000000
,它们都有效。但它不接受bbbbbbbbb
或111111111
。密码似乎接受所有ascii字符对应于0x21
、0x31
、0x41
。。。
最初,它调用<getchar@plt>
,然后进入一个for循环,该循环接受10个字符。
在那之后,它开始了一个新的循环,我不明白。你能解释一下这个循环吗?这是在做模块化分工吗?提前感谢!
8048443: 88 44 1d e5 mov %al,-0x1b(%ebp,%ebx,1)
8048447: 83 45 f4 01 addl $0x1,-0xc(%ebp)
804844b: 83 7d f4 09 cmpl $0x9,-0xc(%ebp) # x ($ebp - 0xc) counter 9
804844f: 7e ea jle 804843b <puts@plt+0xe7>
8048451: 8b 45 f4 mov -0xc(%ebp),%eax
8048454: c6 44 05 e5 00 movb $0x0,-0x1b(%ebp,%eax,1)
8048459: c7 45 f4 01 00 00 00 movl $0x1,-0xc(%ebp) # counter = 1
8048460: eb 15 jmp 8048477 <puts@plt+0x123> # start of the loop
8048462: 8b 45 f4 mov -0xc(%ebp),%eax
8048465: 83 e8 01 sub $0x1,%eax
8048468: 0f b6 44 05 e5 movzbl -0x1b(%ebp,%eax,1),%eax
804846d: 0f be c0 movsbl %al,%eax
8048470: 01 45 f0 add %eax,-0x10(%ebp)
8048473: 83 45 f4 01 addl $0x1,-0xc(%ebp)
8048477: 83 7d f4 0a cmpl $0xa,-0xc(%ebp) # 10
804847b: 7e e5 jle 8048462 <puts@plt+0x10e> # end loop
804847d: 8b 45 f0 mov -0x10(%ebp),%eax
8048480: 89 c2 mov %eax,%edx
8048482: c1 fa 1f sar $0x1f,%edx
8048485: c1 ea 1c shr $0x1c,%edx
8048488: 01 d0 add %edx,%eax
804848a: 83 e0 0f and $0xf,%eax # only look at the lowest four bits
804848d: 29 d0 sub %edx,%eax
804848f: 89 45 f0 mov %eax,-0x10(%ebp)
8048492: 83 7d f0 03 cmpl $0x3,-0x10(%ebp) # compare to 3
8048496: 75 16 jne 80484ae <puts@plt+0x15a> # go to wrong answer
8048498: b8 b4 85 04 08 mov $0x80485b4,%eax
804849d: 8d 55 e5 lea -0x1b(%ebp),%edx
80484a0: 89 54 24 04 mov %edx,0x4(%esp)
80484a4: 89 04 24 mov %eax,(%esp)
80484a7: e8 98 fe ff ff call 8048344 <printf@plt> # correct answer
80484ac: eb 0c jmp 80484ba <puts@plt+0x166>
80484ae: c7 04 24 e2 85 04 08 movl $0x80485e2,(%esp)
80484b5: e8 9a fe ff ff call 8048354 <puts@plt> # wrong answer```
我正要在逆向工程代码上发布一个关于该代码的sar/shr/add/和/子部分的答案,该代码通过31,shr进行sar,然后在and周围进行加减运算,但在我完成之前大约一分钟就被删除了。这部分代码可能经常弹出,值得回答:
AND周围的代码对有符号的32位整数类型(例如int
(执行n %= 16;
,实现C符号余数语义对于非负整数,它相当于n &= 0xf;
,即只保留低4位。
您可以让GCC4.7为该函数发出以下代码(Godbolt编译器资源管理器(:
int mod16(int n) {
return n % 16;
}
# gcc4.7.4 -O1 -m32 -mregparm=1 (first arg in EAX instead of on the stack)
mod16(int):
movl %eax, %edx
sarl $31, %edx
shrl $28, %edx
addl %edx, %eax
andl $15, %eax
subl %edx, %eax
ret # return with n%16 in EAX.
有趣的事实:gcc4.8和更新版本使用cdq
而不是mov/sar $31
将EAX符号扩展为EDX:EAX。即设置EDX的所有比特=EAX的符号比特。
代码中的存储/重载,并使用EBP作为帧指针,表明它是在没有(额外(优化的情况下编译的。GCC比大多数编译器更经常使用它在单个表达式中执行任务的有效方法。我在Godbolt上使用了-O1
,以避免编译器存储/重新加载到内存中。
在C中,%
必须为负n
产生负余数,但在AND指令周围模拟它以实现有符号整数除法语义仍然比使用idiv
进行2的幂更有效。(对于具有乘法逆的非2次方编译时间常数也是如此。(
回想一下所有n
的(n/16)*16 + n%16 = n
,以及C符号除法向0截断。(这适用于任何除数,而不仅仅是2的正幂(。例如CCD_ 22和CCD_-1 * 16 + -6 = -22
。编译器必须发出适用于所有可能的n
的代码;当代码运行时,它(通常(不知道int
从来都不是负数。
算术右移向-Inf循环,因此它需要类似的修正来实现/
划分语义。
这就是为什么当不需要负余数,并且希望它能高效编译而不是编译到这个过于复杂的序列时,应该使用unsigned
或n &= 15;
如果编译器能够证明and $0xf, %eax
的值是非负的,那么这个额外的cruft可以优化掉,但这不可能总是发生(在调试构建中也永远不会发生(。
这是几个小时前别人提问的代码的一部分,所以我们可以提供从-0x10(%ebp)
加载EAX的缺失的第一条指令
804847d: 8b 45 f0 mov -0x10(%ebp),%eax # EAX = n; you left out this insn
8048480: 89 c2 mov %eax,%edx
8048482: c1 fa 1f sar $0x1f,%edx # edx = 0 or -1 (sign bit of n)
8048485: c1 ea 1c shr $0x1c,%edx # edx = 0 or 15
逻辑右移28总是移到零,而不是符号位的副本,因此将-1
(0xffffffff(转换为15
。(32-28=寄存器底部剩余的4位(。当然,将0保留为0。
因此,对于非负或负n
,EDX=0或15。
8048488: 01 d0 add %edx,%eax
804848a: 83 e0 0f and $0xf,%eax
# only look at the lowest four bits
804848d: 29 d0 sub %edx,%eax
它在AND周围加/减15(或0(。
这是一种针对负输入执行-((-eax) & 15)
的优化方法,基于-x = ~x + 1
与&
组合的2的补码身份。
请注意,x & 0xf
会产生[0.15]范围内的数字,因此负15将始终为负或零。如果输入已经是16的倍数(即使是负数,因为2的补码就是这样工作的(,那么低位将从0开始。所以(n+15(&15=15,以及15-15=0,这是例如-128 % 16
的正确结果。
对于最初的负输入,这只是加/减0,即保持值不变的加法恒等式。因此,对于非负n
,整个过程等价于n & 0xf
。
整个东西相当于(eax < 0) ? -((-eax) & 15) : (eax & 15)
GCC将其用于CCD_ 43也表明它为每个可能的CCD_ 44给出了正确的结果。(对于n % 16
,不存在具有C未定义行为的n
值(。
804848f: 89 45 f0 mov %eax,-0x10(%ebp) # store back into where we loaded from
所以我们知道它是n %= 16;
,因为它将结果存储在最初加载的堆栈插槽的顶部。
8048492: 83 7d f0 03 cmpl $0x3,-0x10(%ebp)
# compare to 3
是的,没错。FLAGS是根据CCD_ 48设置的;T语法是向后的。但ZF是在它们相等的情况下设置的。