【问题标题】:REPNZ SCAS Assembly Instruction SpecificsREPNZ SCAS 组装说明细节
【发布时间】:2015-01-03 05:09:33
【问题描述】:

我正在尝试对二进制文件进行逆向工程,但下面的说明让我感到困惑,谁能澄清这到底是做什么的?

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


    【解决方案1】:

    我将尝试通过将代码反转回 C 来解释它。

    英特尔的指令集参考(Software Developer's Manual 第 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 的存在,我假设 sn-p 属于此成语(或类似),用于使用 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++;
    

    这是 C 中的 strlen 函数:

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

    【讨论】:

    • 感谢您转换为 HOL - 非常有助于了解正在发生的事情。
    • 什么是*(BYTE *)?
    • @user12140617 (BYTE *)edi 的意思类似于“将 edi 转换为 BYTE 的类型,然后取消引用”。希望这会有所帮助。
    【解决方案2】:

    AL 参与其中,因为scas 会在内存中扫描AL 的值。 AL 已被归零,以便指令在字符串末尾找到终止零。 scas 本身会自动增加(或减少,取决于方向标志)EDIREPNZ 前缀(在REPNE 形式中更易读)重复scas,只要比较为假(REPeat while Not Equal) 和ECX > 0。它还在每次迭代中自动递减ECXECX 已被初始化为可能的最长字符串,因此它不会提前终止循环。

    由于ECX0xffffffff(也称为-1)开始倒数,因此得到的长度将为-1-ECX,由于2 的补码算法的特殊性,可以使用NOT 指令来计算。

    【讨论】:

    • 太好了,现在说得通了。谢谢!
    • 也许是我比较懒,但是当我在谷歌上搜索“repne scas”时,这就是我要找的答案,所以我不必下载指令集手册并阅读我自己。所以感谢你让我的懒惰。 ;)
    【解决方案3】:

    它将es:[edi] 处的字节与al 中的任何内容进行比较,并重复此步骤直到ecx 变为零或es:[edi] 处的值与al 中的值匹配。在每一步之后,edi 都会递增,因此它指向内存中的下一个字节。 程序随后根据以下指令将not 应用于计数器(ecx)。

    repnz 表示“重复直到没有设置零标志并且 cx 不为零”。每次迭代递减ecxscas 或更准确地说是scasbal 中的值与内存操作数(总是es:[edi]es:[di] 取决于地址大小)进行比较,然后相应地设置标志(如果这两个值将设置为零标志相等)和递增(或递减,基于方向标志)edi

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-28
      • 2011-07-01
      • 1970-01-01
      相关资源
      最近更新 更多