如何将程序集代码翻译回C



我看到了以下汇编代码(注意,我自己添加了注释(:

00000000004005f0 <check_password>:
4005f0:   31 c0                   xor    %eax,%eax          # i=0
4005f2:   66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)   # ignore
4005f8:   0f b6 90 c0 1b 40 00    movzbl 0x401bc0(%rax),%edx# edx=foo[i]
4005ff:   83 f2 5f                xor    $0x5f,%edx
400602:   38 14 07                cmp    %dl,(%rdi,%rax,1)  # cmp prev result with s[i]
400605:   75 14                   jne    40061b <check_password+0x2b> # failed return 0
400607:   48 83 c0 01             add    $0x1,%rax          # i++
40060b:   48 83 f8 0a             cmp    $0xa,%rax          # if (i==10)
40060f:   75 e7                   jne    4005f8 <check_password+0x8> # run again
400611:   31 c0                   xor    %eax,%eax
400613:   80 7f 0a 00             cmpb   $0x0,0xa(%rdi) 
400617:   0f 94 c0                sete   %al
40061a:   c3                      retq   
40061b:   31 c0                   xor    %eax,%eax
40061d:   c3                      retq    

并且想要写等价的C代码来填充这个:

第1版:

int check_password (char *s)
{
for (int i=0; i!=10 ; i++)
{
if (foo[i] ^ 0x5f != s[i])
return 0;
}
return 0==s[10]; // Isn't this strange? input is of size 10...
}

编辑2:

int check_password (char *s)
{
for (int i=0; i!=10 ; i++)
{
if (foo[i] ^ 0x5f != s[i])
return 0==s[10]; // Isn't this strange? input is of size 10...
}
return 1;
}

我觉得离它太近了,但有点卡住了,版本1是对的还是2,或者没有。另外,这里的正确翻译应该是什么?

需要了解的一些重要事项:

我有下面一个名为foo的字符数组,它位于内存中的地址401bc0

char foo[10] = { 0x33, 0x3d, ...... }; // Not all values are shown

您是否查看了实际的编译器输出,看看GCC是否用您的任何一个版本重建了相同的asm?https://godbolt.org/z/ed86jG9rf显示了几乎正确和错误的版本。(我在这里故意不说哪一个是正确的,所以你必须自己去寻找,尽管在我看来,cmp/sete位于失败路径或不匹配路径之间的区别非常明显。此外,你看到任何无条件返回1的asm吗?0如何?(

请注意,在修复运算符优先级错误后,其中一个会用GCC5.4编译回您的asm:foo[i] ^ 0x5f != s[i]表示foo[i] ^ (0x5f != s[i]),即与布尔进行XOR,因此根据XOR结果的低位,if将为true/false。您实际上(pass[i] ^ 0xf5) != s[i]来比较XOR结果。

我只是从asm中注意到这一点很奇怪(循环中的cmp/setne(,所以我打开了-Wall,GCC告诉我发生了什么。

<source>:19:28: warning: suggest parentheses around comparison in operand of '^' [-Wparentheses]
if (pass[i] ^ 0x5f != s[i])
^

我们有非常好的编译C的工具;无论出于何种原因,在编写C时都要利用它们,包括asm的反向工程。特别是对于您知道是编译器生成的代码,尤其是如果您可以猜测编译器的话。带有System V调用约定(RDI中的arg(的x86-64通常是GCC或clang。在这种情况下,如果将-fno-unroll-loops与clang一起使用,它们都会生成非常相似的asm。


return 0==s[10]; // Isn't this strange? input is of size 10...

对我来说似乎很正常(但效率低下(:显然静态const char pass[]没有以0 ^ 0x5f终止符结束。否则,他们可以让循环再运行一次迭代,以检查输入的隐式长度C字符串函数arg在其10个匹配字节之后是否有一个0终止符。

这个最后的检查将拒绝在前10个字节之后有尾随垃圾的密码,但在这一点上匹配。

这意味着它期望strlen(s) == 10,即char [11]保持10个字节加上终止的0。

根据过去做这类事情的经验,需要一个不断完善的过程。所以首先像fortran一样编写:

check_password(char *rdi) {
int  eax = 0;
char *str = (char *)0x401bc0;
loop:
char dl = str[eax];
dl ^= 0x5f;
if (dl == rdi[eax]) {
eax++;
if (eax == 10) {
return rdi[10] == 0;
}
goto loop;
}
return 0;
}

在推进到更C-ish之前:

char *str = (char *)0x401bc0;
int check_password(char *rdi) {
int i;
for (i = 0; i < 10 && rdi[i] == str[i]^0x5f; i++) {
}
return i == 10 && rdi[i] == 0;
}

这并不重要,但随着asm的分支越来越激烈,C-goto是你的朋友;一旦减少了它,检测结构化模式通常是简单的。在一个例子中,我将一个900行的汇编块提炼成了80行C。然后,它在6个体系结构上传播,每个体系结构都有10-30%的速度提升,所有这些都是在内核最长的中断关闭时间内完成的。

最新更新