x86反汇编,涉及模块化哈希



我已经评论了我认为代码在做什么。

我尝试过放置aaaaaaaaaAAAAAAAAA!!!!!!!!!000000000,它们都有效。但它不接受bbbbbbbbb111111111。密码似乎接受所有ascii字符对应于0x210x310x41。。。

最初,它调用<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循环,因此它需要类似的修正来实现/划分语义。

这就是为什么当不需要负余数,并且希望它能高效编译而不是编译到这个过于复杂的序列时,应该使用unsignedn &= 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是在它们相等的情况下设置的。

最新更新