【问题标题】:Understanding the decompilation of an object to source code了解对象到源代码的反编译
【发布时间】:2020-09-20 03:42:54
【问题描述】:

首先,我是一名学生,我对C、C++和汇编程序的知识还不是很全面,所以我非常努力地理解它。

我有一段来自 Intel x86-32 位处理器的汇编代码。

我的目标是将其转换为源代码。

0x80483dc <main>:    push       ebp        
0x80483dd <main+1>:  mov        ebp,esp     
0x80483df <main+3>:  sub        esp,0x10
0x80483e2 <main+6>:  mov        DWORD PTR [ebp-0x8],0x80484d0   
0x80483e9 <main+13>: lea        eax,[ebp-0x8]   
0x80483ec <main+16>: mov        DWORD PTR [ebp-0x4],eax     
0x80483ef <main+19>: mov        eax,DWORD PTR [ebp-0x4]     
0x80483f2 <main+22>: mov        edx,DWORD PTR [eax+0xc]
0x80483f5 <main+25>: mov        eax,DWORD PTR [ebp-0x4]         
0x80483f8 <main+28>: movzx      eax,WORD PTR [eax+0x10]
0x80483fc <main+32>: cwde
0x80483fd <main+33>: add        edx, eax
0x80483ff <main+35>: mov        eax,DWORD PTR [ebp-0x4]         
0x8048402 <main+38>: mov        DWORD PTR [eax+0xc],edx     
0x8048405 <main+41>: mov        eax,DWORD PTR [ebp-0x4]     
0x8048408 <main+44>: movzx      eax,BYTE PTR [eax]
0x804840b <main+47>: cmp        al,0x4f     
0x804840d <main+49>: jne        0x8048419 <main+61> 
0x804840f <main+51>: mov        eax,DWORD PTR [ebp-0x4] 
0x8048412 <main+54>: movzx      eax,BYTE PTR [eax] 
0x8048415 <main+57>: cmp        al,0x4b 
0x8048417 <main+59>: je         0x804842d <main+81> 
0x8048419 <main+61>: mov        eax,DWORD PTR [ebp-0x4] 
0x804841c <main+64>: mov        eax,DWORD PTR [eax+0xc]
0x804841f <main+67>: mov        edx, eax
0x8048421 <main+69>: and        edx,0xf0f0f0f
0x8048427 <main+75>: mov        eax,DWORD PTR [ebp-0x4] 
0x804842a <main+78>: mov        DWORD PTR [eax+0x4],edx
0x804842d <main+81>: mov        eax,0x0
0x8048432 <main+86>: leave
0x8048433 <main+87>: ret

这是我从代码中了解到的:

有4个变量:

a = [ebp-0x8] ebp
b = [ebp-0x4] eax
c = [eax + 0xc] edx
d = [eax + 0x10] eax

价值观:

0x4 = 4
0x8 = 8
0xc = 12
0x10 = 16
0x4b = 75
0x4f = 79

类型:

char (8 bits) = 1 BYTE
short (16 bits) = WORD
int (32 bit) = DWORD
long (32 bits) = DWORD
long long (32 bit) = DWORD

这是我能够创建的:

#include <stdio.h>
int main (void)
{
   int a = 0x80484d0;
   int b
   short c;
   int d;

   c + b?
if (79 <= al) {
instructions
} else {
instructions
}

   return 0
}

但我被困住了。我也无法理解“cmp al ..”这个句子与什么相比,“al”是什么?

这些说明如何工作?

编辑1:

也就是说,正如你评论的那样,大会似乎是错误的,或者正如某些 cmets 所说,这太疯狂了!

代码和练习来自以下书籍:“逆向,逆向工程”第 140 页(3.8 建议练习)。我永远不会想到这是错误的,如果是这样,这显然让我难以学习......

所以不能逆向获取源代码,因为它不是一个好的汇编?也许我没有被压迫?可以优化吗?

编辑2:

嗨!

我确实问过,最后她说这应该是 c 代码:

inf foo(void){
    char *string;//ebp-0x8
    unsigned int *pointerstring//[ebp-0x4]
    unsigned int *position;
    *position = *(pointerstring+0xc);
    unsigned char character;
    character=(unsigned char) string[*position];
    if ((character != 0x4)||(character != 0x4b))
    {
     *(position+0x4)=(unsigned int)(*position & 0x0f0f0f0f);
    }
    return(0);
}

这对你有任何意义吗?有人可以向我解释一下吗? 真的有人这样编程吗?

非常感谢!

【问题讨论】:

  • long long (32 bit) = DWORD 不正确。 C++ 标准要求 long long 为 64 位。 (嗯,不完全是,但它必须支持的最大值是它需要 64 位来存储它)
  • 如果您需要询问al 是什么,那么您需要对处理器进行更深入的研究,然后才能理解反汇编或汇编列表。 al 寄存器是eax 寄存器的最低有效 8 位,ax 是 l.s. eax 的 16 位。 blcldlbxcxdx 类似。 ah 也是 eax 的下 8 位,因此 ahal 一起构成 ax
  • Here's a link to the documentation。查找标题为“基本程序执行寄存器”的部分
  • @PeterCordes 我不认为你在放屁。我认为那个大会有一些非常奇怪的地方。特别是在0x80483f2 &lt;main+22&gt;: mov edx,DWORD PTR [eax+0xc] 之后,在我看来,返回地址最终会出现在edx 中,考虑到随后的代码如何处理它,这似乎是不可取的。
  • 除非章节是关于混淆代码的,否则我会写信给作者。代码中有一个微不足道的逻辑错误,而且这本书没有在 Google 上弹出(你给我们的名字是对的吗?)所以我倾向于相信这本书要么有一个关于控制流混淆的精彩章节,要么有是由不应该写的人写的。

标签: c assembly x86 reverse-engineering


【解决方案1】:

你的程序集完全疯了。这大致相当于C:

int main() {
    int i = 0x80484d0; // in ebp-8
    int *p = &i; // in ebp-4
    p[3] += (short)p[4]; // add argc to the return address(!)
    if((char)*p != 0x4f || (char)*p != 0x4b) // always true because of || instead of &&
        p[1] = p[3] & 0xf0f0f0f; // note that p[1] is p
    return 0;
}

很明显,这是非常糟糕的代码,几乎肯定不会达到程序员的预期。

【讨论】:

  • 0x80484d0 可能是 .rodata 部分中的地址,所以我在想char *arr[1] = { "something" };。但是,是的,int *p = (int*)arr; 和 UB 无法访问数组边界之外。我没看剩下的有多疯狂。
  • 我问了我的老师,他说是因为他没有优化……老实说,如果你知道的不明白,我怀疑我也能做到..
  • @conjim “未优化”意味着代码做了正确的事情,但比必要的慢。这段代码做错了。
  • 感谢您提供的信息,我想在这种情况下,没有什么可以做的了......我想也许我没有做对,但似乎每个人都同意这是错误的代码。
【解决方案2】:

x86 汇编语言有着悠久的传统,并且大部分都保持了兼容性。我们需要回到故事开始的 8086/8088 芯片。这些是 16 位处理器,这意味着它们的寄存器的字长为 16 位。通用寄存器被命名为 AX、BX、CX 和 DX。 8086 具有操作这些寄存器的高 8 位和低 8 位部分的指令,这些寄存器随后被命名为 AH、AL、BH、BL、CH、CL、DH 和 DL。 This Wikipedia page 描述了这个,请看一下。

这些寄存器的 32 位版本前面有一个E:EAX、EBX、ECX 等。

您提到的特定指令,例如cmp al,0x4f 是将 AX 寄存器的低字节与 0x4f 进行比较。比较实际上与减法相同,但不保存结果,仅设置标志。

对于 8086 指令集,there is a nice reference here。您的程序是 32 位代码,因此您至少需要 80386 指令参考。

【讨论】:

    【解决方案3】:

    您已经分析了变量,这是一个很好的起点。您应该尝试在开始时为它们添加类型注释,大小,当用作指针时(如b),指向什么类型/大小的指针。

    知道[ebp-4]b,我可能会更新您的变量图表如下:

    c = [b + 0xc]
    d = [b + 0x10]
    e = [b + 0], size = byte
    

    要分析的另一件事是控制流。对于大多数指令,控制流是顺序的,但某些指令会故意改变它。从广义上讲,当 pc 向前移动时,它会跳过一些代码,而当 pc 向后移动时,它会重复一些它已经运行过的代码。跳过代码用于构造 if-then、if-then-else 和跳出循环的语句。后退用于继续循环。

    某些指令称为条件分支,在某些动态条件为真时:向前(或向后)跳过,在为假时执行简单的顺序前进到下一条指令(有时称为条件分支失败)。

    这里的控制序列:

    ...
    0x8048405 <main+41>: mov        eax,DWORD PTR [ebp-0x4]    b
    0x8048408 <main+44>: movzx      eax,BYTE PTR [eax]         b->e
    
    0x804840b <main+47>: cmp        al,0x4f                    b->e <=> 'O'
    0x804840d <main+49>: jne        0x8048419 <main+61>        b->e != 'O'  skip to 61
    
    ** we know that the letter, a->e, must be 'O' here
    
    0x804840f <main+51>: mov        eax,DWORD PTR [ebp-0x4]    b      
    0x8048412 <main+54>: movzx      eax,BYTE PTR [eax]         b->e
    
    0x8048415 <main+57>: cmp        al,0x4b                    b->e <=> 'K'
    0x8048417 <main+59>: je         0x804842d <main+81>        b->e == 'K' skip to 81
    
    ** we know that the letter, a->e must not be 'K' here if we fall thru the above je 
    
    ** this line can be reached by taken branch jne or by fall thru je
    0x8048419 <main+61>: mov        eax,DWORD PTR [ebp-0x4]    ******
    ...
    

    控制流到达最后一行标记为我们知道字母不是“O”或者不是“K”。

    jne 指令用于跳过另一个测试的构造是短路|| 运算符。因此控制结构是:

    if ( a->e != 'O' || a->e != 'K' ) {
        then-part
    }
    

    由于这两个条件分支是函数中唯一的流控修改,所以if没有else部分,也没有循环或其他if。


    这段代码似乎有一点问题。

    如果值不是 'O',则 then 部分将从第一个测试中触发。但是,如果我们进行第二次测试,我们已经知道字母是 'O',所以测试 'K' 是愚蠢的,并且会是真的('O' 不是 'K')。

    因此,这个 if-then 将始终触发。

    要么效率非常低,要么存在一个错误,可能是(可能)字符串中的下一个字母应该针对“K”而不是完全相同的字母进行测试。

    【讨论】:

    • [ebp-4]b - b 不是指针变量,持有地址 ebp-8?因此,与[eax + 0xc] 的偏移是索引到函数自己的堆栈帧,mov edx, [eax + 0xc] 加载函数的返回地址。 (Joseph Sible 和我 discussed this in comments;这对我们来说看起来很疯狂。我想知道原始代码是否在像 char *arr[1] = { "hello" }; 这样的本地数组的范围之外进行索引?)
    • 我仍然很困惑......但非常感谢您提供的信息。有没有办法自动将目标代码转换为源代码,即使它远程相似......但不完全是?谢谢
    猜你喜欢
    • 2017-08-18
    • 2013-04-01
    • 2011-06-16
    • 1970-01-01
    • 2019-09-20
    • 2015-04-27
    • 1970-01-01
    • 1970-01-01
    • 2015-12-21
    相关资源
    最近更新 更多