REPNZ SCAS汇编指令细节

Mic*_*ott 18 x86 assembly reverse-engineering

我正在尝试对二进制文件进行逆向工程,以下指令让我感到困惑,有人可以澄清这究竟是什么吗?

=>0x804854e:    repnz scas al,BYTE PTR es:[edi]
  0x8048550:    not    ecx
Run Code Online (Sandbox Code Playgroud)

哪里:

EAX: 0x0
ECX: 0xffffffff
EDI: 0xbffff3dc ("aaaaaa\n")
ZF:  1
Run Code Online (Sandbox Code Playgroud)

我看到它在某种程度上以每次迭代递减1次ECX,并且EDI沿着字符串的长度递增.我知道它会计算字符串的长度,但至于它是如何发生的,以及为什么"al"涉及到我不太确定.

Qua*_*key 28

我将尝试通过将代码反转回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;
}
Run Code Online (Sandbox Code Playgroud)

或者更简单:

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

字符串长度

但是,上述内容不足以解释它如何计算字符串的长度.根据not ecx您问题中的存在,我假设该片段属于此成语(或类似),用于计算字符串长度REPNE SCASB:

sub ecx, ecx
sub al, al
not ecx
cld
repne scasb
not ecx
dec ecx
Run Code Online (Sandbox Code Playgroud)

转换为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--;
Run Code Online (Sandbox Code Playgroud)

简化使用al = 0DF = 0:

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

注意事项:

  • 在二进制补码表示法中,翻转位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)
Run Code Online (Sandbox Code Playgroud)

并反转计数:

size_t c = 0;
while (*s++ != '\0') c++;
Run Code Online (Sandbox Code Playgroud)

这是strlenC 的功能:

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


Jes*_*ter 17

AL涉及,因为scas扫描内存的值AL.AL已被归零,以便指令在字符串的末尾找到终止零.scas自动递增(或递减,取决于方向标志)EDI.的REPNZ前缀(这是在更可读的REPNE形式)重复scas只要比较结果为假(REP吃而Ñ OT È QUAL)和ECX > 0.它也会ECX在每次迭代中自动递减.ECX已被初始化为最长的字符串,因此它不会提前终止循环.

由于ECX0xffffffff(也称为-1)向下计数,因此-1-ECX可以使用NOT指令计算由于2的补码运算的特性而导致的结果长度.