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的逻辑相结合:
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 = 0和DF = 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已被初始化为最长的字符串,因此它不会提前终止循环.
由于ECX从0xffffffff(也称为-1)向下计数,因此-1-ECX可以使用NOT指令计算由于2的补码运算的特性而导致的结果长度.