REPNZ SCAS 装配指令说明

问题描述 投票:19回答:3

我想对一个二进制进行逆向工程,下面的指令让我很困惑,谁能说明一下这到底是干什么的?

=>0x804854e:    repnz scas al,BYTE PTR es:[edi]
  0x8048550:    not    ecx

在哪里。

EAX: 0x0
ECX: 0xffffffff
EDI: 0xbffff3dc ("aaaaaa\n")
ZF:  1

我看到它每次迭代都会将ECX递减1 EDI沿着字符串的长度递增。 我知道它计算的是字符串的长度,但至于它到底是如何发生的,以及为什么涉及到 "al",我不太清楚。

assembly x86 reverse-engineering
3个回答
34
投票

我试着把代码反转回C语言来解释一下。

英特尔的指令集参考(第2卷) 软件开发人员手册)对于这种逆向工程是非常宝贵的。

REPNE SCASB

REPNE和SCASB结合的逻辑。

while (ecx != 0) {
    temp = al - *(BYTE *)edi;
    SetStatusFlags(temp);
    if (DF == 0)   // DF = Direction Flag
        edi = edi + 1;
    else
        edi = edi - 1;
    ecx = ecx - 1;
    if (ZF == 1) break;
}

或者更简单地说:

while (ecx != 0) {
    ZF = (al == *(BYTE *)edi);
    if (DF == 0)
        edi++;
    else
        edi--;
    ecx--;
    if (ZF) break;
}

字符串长度

然而,上述内容不足以解释它是如何计算字符串的长度的。基于存在的 not ecx 在你的问题中,我假设这个片段属于这个成语(或类似的),用于使用 REPNE SCASB:

sub ecx, ecx
sub al, al
not ecx
cld
repne scasb
not ecx
dec ecx

翻译成C语言,并使用我们上一节的逻辑,我们得到。

ecx = (unsigned)-1;
al = 0;
DF = 0;
while (ecx != 0) {
    ZF = (al == *(BYTE *)edi);
    if (DF == 0)
        edi++;
    else
        edi--;
    ecx--;
    if (ZF) break;
}
ecx = ~ecx;
ecx--;

简化使用 al = 0DF = 0:

ecx = (unsigned)-1;
while (ecx != 0) {
    ZF = (0 == *(BYTE *)edi);
    edi++;
    ecx--;
    if (ZF) break;
}
ecx = ~ecx;
ecx--;

要注意的事情。

  • 在二补法中,翻转位子的是 ecx 相当于 -1 - ecx.
  • 循环中。ecx 是在循环中断前递减的,所以它以 length(edi) + 1 的总和。
  • ecx 在循环中永远不能为零,因为字符串必须占据整个地址空间。

所以在上面的循环之后。ecx 包含 -1 - (length(edi) + 1) 这和 -(length(edi) + 2),我们将其位数翻转,得到 length(edi) + 1,最后递减,得出 length(edi).

或重新排列循环,简化。

const char *s = edi;
size_t c = (size_t)-1;      // c == -1
while (*s++ != '\0') c--;   // c == -1 - length(s)
c = ~c;                     // c == length(s)

然后倒数:

size_t c = 0;
while (*s++ != '\0') c++;

这就是... strlen 涉及到C的函数。

size_t strlen(const char *s) {
    size_t c = 0;
    while (*s++ != '\0') c++;
    return c;
}

19
投票

AL 涉及,因为 scas 扫描内存中的 AL. AL 已被清零,因此该指令在字符串的末尾找到终止的零。scas 本身会递增(或递减,取决于方向标志)。EDI 自动。的 REPNZ 前缀(这在 REPNE 形式)重复了 scas 只要比较是假的 (REPNEqual)和 ECX > 0. 它还减少了 ECX 在每一次迭代中都会自动执行。ECX 已被初始化为尽可能长的字符串,这样它就不会提前结束循环。

由于 ECX 倒数 0xffffffff (也就是-1),所得长度为 -1-ECX 由于2补码算术的特殊性,它可以用一个 NOT 指令。


3
投票

它比较了字节在 es:[edi] 以至于 al 是,并重复此步骤,直到任何一个 ecx 变成零或在 es:[edi] 中的数值相匹配。al. 在每一步之后。edi 是递增的,所以它指向内存中的下一个字节。not 到之后的计数器(ecx),基于以下指令。

repnz 意思是 "重复执行,直到不设置零标志 cx不为零"。每一次迭代都会递减 ecx.scas 或者更准确地说 scasb 中的值进行比较。al 到内存操作数(总是 es:[edi]es:[di] 根据地址大小),然后相应地设置标志(如果两个值相等,则设置零标志),并递增(或递减,根据方向标志)。edi.

© www.soinside.com 2019 - 2024. All rights reserved.