assembly REPNZ SCAS汇编指令细节

aelbi1ox  于 2022-11-13  发布在  其他
关注(0)|答案(3)|浏览(171)

我正试图反向工程一个二进制和以下指令是困惑我,谁能澄清这到底是做什么?

=>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”,我不太清楚。

fjaof16o

fjaof16o1#

我将试着通过将代码反转回C来解释它。
英特尔的指令集参考(Software Developer's Manual的第2卷)对于此类逆向工程非常有用。

重复扫描

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
  • 在循环中,x1M6N1x在循环中断之前递减,因此它总共递减x1M7N1x。
  • 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++;

也就是C:中的strlen函数

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

tjvv9vkg2#

AL,因为scas会扫描内存以寻找AL的值。AL已被置零,因此指令会在字符串末尾找到终止零。scas本身会递增(或减量,取决于方向标志)EDIREPNZ前缀(在REPNE形式中更容易阅读)只要比较结果为false,就重复scas。(REPeat whileNotEqual)和ECX > 0。它还在每次迭代中自动递减ECXECX已被初始化为最长的字符串,因此它不会提前终止循环。
由于ECX0xffffffff(也称为-1)开始递减计数,因此得到的长度将是-1-ECX,由于2的补码运算的特殊性,可以使用NOT指令来计算该长度。

1cosmwyk

1cosmwyk3#

它将es:[edi]处的字节与al中的字节进行比较,并重复此步骤,直到ecx变为零或es:[edi]处的值与al中的值匹配。每执行一步后,edi递增,以便指向内存中的下一个字节。程序将not应用于计数器(ecx)之后,根据以下指令。
repnz表示“重复直到未设置零标志 * 且 * cx不为零”。每次迭代都会递减ecxscas或更准确地说,scasbal中的值与内存操作数进行比较(根据地址大小,始终为es:[edi]es:[di]),然后相应地设置标志(如果两个值相等,则将设置零标志),并基于方向标志递增(或递减)edi

相关问题