将汇编 x64 代码转换为 C

逆向工程 拆卸 部件 反编译 x86-64
2021-07-08 16:29:41

我有以下代码:

0000000000400526 <main>:
  400526:   55                      push   rbp
  400527:   48 89 e5                mov    rbp,rsp
  40052a:   48 83 ec 20             sub    rsp,0x20
  40052e:   89 7d ec                mov    DWORD PTR [rbp-0x14],edi
  400531:   48 89 75 e0             mov    QWORD PTR [rbp-0x20],rsi
  400535:   c7 45 f4 4d 3c 2b 1a    mov    DWORD PTR [rbp-0xc],0x1a2b3c4d
  40053c:   48 8d 45 f4             lea    rax,[rbp-0xc]
  400540:   48 89 45 f8             mov    QWORD PTR [rbp-0x8],rax
  400544:   c7 45 f0 00 00 00 00    mov    DWORD PTR [rbp-0x10],0x0
  40054b:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  40054f:   0f b6 00                movzx  eax,BYTE PTR [rax]
  400552:   0f be c0                movsx  eax,al
  400555:   c1 e0 18                shl    eax,0x18
  400558:   89 c2                   mov    edx,eax
  40055a:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  40055e:   48 83 c0 01             add    rax,0x1
  400562:   0f b6 00                movzx  eax,BYTE PTR [rax]
  400565:   0f be c0                movsx  eax,al
  400568:   c1 e0 10                shl    eax,0x10
  40056b:   09 c2                   or     edx,eax
  40056d:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  400571:   48 83 c0 02             add    rax,0x2
  400575:   0f b6 00                movzx  eax,BYTE PTR [rax]
  400578:   0f be c0                movsx  eax,al
  40057b:   c1 e0 08                shl    eax,0x8
  40057e:   09 c2                   or     edx,eax
  400580:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  400584:   48 83 c0 03             add    rax,0x3
  400588:   0f b6 00                movzx  eax,BYTE PTR [rax]
  40058b:   0f be c0                movsx  eax,al
  40058e:   09 d0                   or     eax,edx
  400590:   89 45 f0                mov    DWORD PTR [rbp-0x10],eax
  400593:   8b 55 f0                mov    edx,DWORD PTR [rbp-0x10]
  400596:   8b 45 f4                mov    eax,DWORD PTR [rbp-0xc]
  400599:   89 c6                   mov    esi,eax
  40059b:   bf 44 06 40 00          mov    edi,0x400644 ; "a = %#x\nb = %#x\n"
  4005a0:   b8 00 00 00 00          mov    eax,0x0
  4005a5:   e8 56 fe ff ff          call   400400 <printf@plt>
  4005aa:   b8 00 00 00 00          mov    eax,0x0
  4005af:   c9                      leave
  4005b0:   c3                      ret
  4005b1:   66 2e 0f 1f 84 00 00    nop    WORD PTR cs:[rax+rax*1+0x0]
  4005b8:   00 00 00
  4005bb:   0f 1f 44 00 00          nop    DWORD PTR [rax+rax*1+0x0]

这是一段x64汇编代码,我想把这段代码改写成C。我看了一天的汇编书籍,还是有些困难。我只是想了解这段代码在做什么。从乱来,我认为它对 int (这就是为什么我认为 DWORD 在那里)和 long (这就是 QWORD 在那里的原因)执行一些操作。我认为这是真的,因为我用这些数据结构重新编译了 C 代码,并且这些词出现在等价的 Assembly 中,但我可能是错的。

任何帮助解码此代码表示赞赏。


对于 Amigag:第二段代码

0000000000400966 <my_tolower>:
  400966:   55                      push   rbp
  400967:   48 89 e5                mov    rbp,rsp
  40096a:   48 89 7d f8             mov    QWORD PTR [rbp-0x8],rdi
  40096e:   eb 2d                   jmp    40099d <my_tolower+0x37>
  400970:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  400974:   0f b6 00                movzx  eax,BYTE PTR [rax]
  400977:   3c 40                   cmp    al,0x40
  400979:   7e 1d                   jle    400998 <my_tolower+0x32>
  40097b:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  40097f:   0f b6 00                movzx  eax,BYTE PTR [rax]
  400982:   3c 5a                   cmp    al,0x5a
  400984:   7f 12                   jg     400998 <my_tolower+0x32>
  400986:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  40098a:   0f b6 00                movzx  eax,BYTE PTR [rax]
  40098d:   83 c0 20                add    eax,0x20
  400990:   89 c2                   mov    edx,eax
  400992:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  400996:   88 10                   mov    BYTE PTR [rax],dl
  400998:   48 83 45 f8 01          add    QWORD PTR [rbp-0x8],0x1
  40099d:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  4009a1:   0f b6 00                movzx  eax,BYTE PTR [rax]
  4009a4:   84 c0                   test   al,al
  4009a6:   75 c8                   jne    400970 <my_tolower+0xa>
  4009a8:   90                      nop
  4009a9:   5d                      pop    rbp
  4009aa:   c3                      ret    
  4009ab:   0f 1f 44 00 00          nop    DWORD PTR [rax+rax*1+0x0]
1个回答

我相信将程序集重写为 C 的最佳工具是 IDA Graph View,它与space.
它让您将功能视为基本块,由控制流指令连接。在此特定函数中,我无法发现任何跳转,因此您将看到一个长块。

您通常在函数中看到的第一件事是设置堆栈帧的函数序言。

  400526:   55                      push   rbp
  400527:   48 89 e5                mov    rbp,rsp
  40052a:   48 83 ec 20             sub    rsp,0x20

其次,正如 64 位调用约定所暗示的那样,第一个参数通过寄存器传递,其他参数通过堆栈传递。
使用的寄存器依赖于操作系统(参见Agner Fog 调用约定中的表 5 )。
您会看到该函数可能获得 2 个参数(edi 用于 32 位变量 forargc和 64 位变量argv

您可以看到一个“魔法”值 ( 0x1a2b3c4) 保存在一个局部变量中,并创建了一个指向它的指针。请注意,当它被保存时,它被存储为little-endian,这意味着字节顺序是相反的。

  400535:   c7 45 f4 4d 3c 2b 1a    mov    DWORD PTR [rbp-0xc],0x1a2b3c4d
  40053c:   48 8d 45 f4             lea    rax,[rbp-0xc]
  400540:   48 89 45 f8             mov    QWORD PTR [rbp-0x8],rax

并且他的第一个字节作为有符号字节被读取到 eax 并乘以2^0x18 (=2^24). 在这种情况下,它已签名的事实不会影响任何事情,因为符号位始终处于关闭状态(如0x1a0x2b0x3c0x4d都低于 128)

  40054f:   0f b6 00                movzx  eax,BYTE PTR [rax]
  400552:   0f be c0                movsx  eax,al
  400555:   c1 e0 18                shl    eax,0x18

并以类似的方式,使用下一个字节计算值,分别乘以 0x10(=16)、8 和 1(隐式)。结果存储在 edi 中,并or使用前一个值进行 ed。

我们可以得出结论,我们的函数是这样的:

void main(int argc, char *argv[])
{
     int calculated_value;  // represents the use of edi to store the result
     int magic_value = 0x1A2B3C4D;  // note that although i am using int, i mean uint32_t, a variable that has 4 bytes - a DWORD.
     char *magic_ptr = (char *) &magic_value;  // the values are read byte-by-byte, or char-by-char
     calculated_value = magic_ptr[0] << 24;
     calculated_value |= magic_ptr[1] << 16;
     calculated_value |= magic_ptr[2] << 8;
     calculated_value |= magic_ptr[3];  // note that at the last or, the result is saved at eax as edi will soon be used to pass the first parameter to printf

     printf("a = %#x\nb = %#x\n", magic_value, calculated_value);
}

所以,我们可以看到的是,魔法值被读回一个变量,同时保存了小字节序,这意味着如果magic_value.
因此,我们可以预期输出为:

a = 0x1a2b3c4d
b = 0x4d3c2b1a


此外,作为一般说明,此代码可以利用循环来执行读取。

void main(int argc, char *argv[])
{
     int i;
     int calculated_value = 0;  // represents the use of edi to store the result
     int magic_value = 0x1A2B3C4D;  // note that although i am using int, i mean uint32_t, a variable that has 4 bytes - a DWORD.
     char *magic_ptr = (char *) &magic_value;  // the values are read byte-by-byte, or char-by-char
     for(int i = 0; i < 4; i++)
     {
         calculated_value <<= 8;
         calculated_value = magic_ptr[i];
     }
     printf("a = %#x\nb = %#x\n", magic_value, calculated_value);
}

编辑:至于你的第二个代码。在这里我们可以看到跳转,因此我创建了代码的图形视图。它使阅读更容易。
图表上的一些信息:绿线表示如果满足条件就会发生跳转。红线表示如果条件为假则跳转发生。蓝线表示跳转是无条件的,它会一直跳转。
让我们通过它,看看会发生什么。

第二个代码的图形视图

在函数序言之后我们看到的第一件事是单个参数保存在rbp-0x8.
在块上0x40099d我们可以看到输入可能是char *ptr,它取消引用指针并读取它的值。
test al, al我们可以假设该值是一个字符串(不仅仅是二进制数据,它可能与用户输入有关),一旦我们读取了空终止符(\x00= 0),我们就停止

我们将检查的下一个块是0x400970它所做的只是检查所指向的字符rbp-0x8是否小于或等于0x40( 0x40is ascii for '@', 0x41is 'A')。如果是,则为continues(位于 的单行块0x400998)。

到目前为止,我们的功能是这样的:

void my_tolower(char *str)
{
    while(str[i] != '\x00')
    {
        if(str[i] < 'A')  // as opposed to <= '@'. I can't remember I saw a code ever caring about '@'.
        {
            str += 1;  // skipping the current character
            continue;
        }
        // unknown code for now
    }
}

查看0x40097b,我们可以看到类似的代码,但它检查字符是否小于0x5A(=ascii of Z)。所以我们可以在一个单一的条件下写这些条件:

if(str[i] < 'A' || str[i] > 'Z')

最后一个块 ( 0x400986)。我们现在知道 str[i] 包含一个大写字母。
代码获取字符并添加0x20到它。0x20(空格)和(a- A)的ascii 它将结果保存回字符串并继续。

  400992:   48 8b 45 f8             mov    rax,QWORD PTR [rbp-0x8]
  400996:   88 10                   mov    BYTE PTR [rax],dl

查看0x4009a8,没有结果传递给rax,这意味着该函数可能没有返回值。

所以,输出函数看起来像这样:

void my_tolower(char *str)
{
    while(str[i] != '\x00')
    {
        if(str[i] < 'A' || str[i] > 'Z')  // as opposed to <= '@'. I can't remember I saw a code ever caring about '@'.
        {
            continue;
        }
        str[i] = str[i] + 'a' - 'A';
        str += 1;
    }
}

我们还可以重写函数以显示增加指针的单个块,我相信这就是原始代码的样子。如果满足条件,而不是不满足条件,我们更改字符串在逻辑上更有意义。

void my_tolower(char *str)  // name was taken from 4009a6 and the first line of function
{
    while(str[i] != '\x00')
    {
        if(str[i] >= 'A' && str[i] <= 'Z')  // as opposed to <= '@'. I can't remember I saw a code ever caring about '@'.
        {
            str[i] = str[i] + 'a' - 'A';
        }
        str += 1;  // this is the block at 400998, that always happen
    }
}