x86 反汇编,涉及模块化散列

Posted

技术标签:

【中文标题】x86 反汇编,涉及模块化散列【英文标题】:x86 disassemble, involves modular hashing 【发布时间】:2020-07-22 08:40:48 【问题描述】:

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

我试过把aaaaaaaaaAAAAAAAAA!!!!!!!!!,和000000000,他们都工作。但它不会接受bbbbbbbbb111111111。似乎密码接受所有 ascii 字符对应于0x210x310x41,...

最初,它调用<getchar@plt>,然后进入一个接受10 个字符的for 循环。

之后,它开始一个新的循环,我不明白。你能解释一下这个循环吗?这是在做模块化划分吗?提前致谢!

 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```

【问题讨论】:

仅供参考,注释掉和评论是不同的。前者意味着将代码变成注释块使其不执行,后者意味着添加 cmets 来扩充代码。您“评论”了代码。 您遗漏了第一个循环的顶部; jle 804843b 向后跳转到 mov %al,-0x1b(%ebp,%ebx,1) 之前的几个字节。我想这就是你在谈论的 getchar 循环?呃,这是未优化的代码,它一直将所有内容都存储在内存中。 sar/shr 部分正在使用带符号整数类型执行n %= 16;,它必须为负n 产生负余数。这就是为什么你应该使用unsigned 或写n &amp;= 15; 当你不想 想要它,并希望它高效编译。它在屏蔽之前/之后为负 n 添加/减去 15。此外,这表明您的代码可能是由 gcc4.7 或更早版本编译的;较新的 gcc 足够聪明,可以使用 cltd 将 EAX 签名扩展为 EDX:EAX 而不是 mov/sar 31。godbolt.org/z/tRLEkq 显示与 return n%16` 相同的代码生成。 【参考方案1】:

我正要在Reverse engineering code that does SAR by 31, shr, then add and subtracts that around an AND 上发布有关此代码的 sar/shr/add/and/sub 部分的答案,但在我完成之前大约一分钟就被删除了。这部分代码可能经常弹出,值得回答:


AND 周围的代码正在为有符号的 32 位整数类型(例如 int)执行 n %= 16;,实现 C 有符号余数语义。 对于非负整数,它相当于n &amp;= 0xf;,即只保留低 4 位。

您可以让 GCC4.7 为该函数准确地发出这段代码 (Godbolt compiler explorer):

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 个编译时常量 with a multiplicative inverse,同样处理。)

回想一下 (n/16)*16 + n%16 = n 对所有 n 而言,并且 C 符号除法向 0 截断。(这适用于任何除数,而不仅仅是 2 的正幂)。例如-22/16 = -1-22 % 16 = -6-1 * 16 + -6 = -22。编译器必须生成适用于所有可能的n 的代码;它(通常)不知道您的 int 在您的代码运行时永远不会是负数。

算术右移转向 -Inf,因此它需要类似的修正来实现 / 除法语义。

这就是为什么当您不需要需要负余数时应该使用unsignedn &amp;= 15; 的原因,并且希望它能够高效地编译而不是编译成这个过于复杂的序列。 如果编译器可以证明该值是非负的,那么 and $0xf, %eax 周围的这些多余的东西可以优化掉,但这并不总是会发生(并且永远不会在调试版本中发生)。


这是几个小时前来自someone else's question 的代码的一部分,因此我们可以提供缺失的第一条从-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。

所以 EDX = 0 或 15,对于非负或负 n

 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) &amp; 15) 的优化方法,基于 -x = ~x + 1&amp; 结合的 2 的补码标识。

请注意,x &amp; 0xf 会产生 [0..15] 范围内的数字,因此负 15 将始终为负数或零。如果输入已经是 16 的倍数(即使是负数,因为这是 2 的补码的工作方式),则低位将从 0 开始。所以 (n + 15) & 15 = 15,并且 15-15 = 0,例如 -128 % 16 的正确结果。

对于最初为负的输入,这只是加/减 0,即使值保持不变的加法恒等式。所以整个事情相当于n &amp; 0xf 非负n

整个就相当于(eax &lt; 0) ? -((-eax) &amp; 15) : (eax &amp; 15)

GCC 将它用于n % 16 也表明它为每个可能的int 提供了正确的结果。 (对于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 是根据n - 3 设置的,因为 AT&T 语法是向后的。但是如果它们相等,则设置 ZF。

【讨论】:

以上是关于x86 反汇编,涉及模块化散列的主要内容,如果未能解决你的问题,请参考以下文章

反汇编基本原理与x86指令构造

如何反汇编原始 16 位 x86 机器代码?

第4章 x86反汇编速成班

基于ARM处理器的反汇编器软件简单设计及实现

优化系列汇编优化技术:x86架构内联汇编及demo

汇编器的NASM