从汇编逆向工程 C 源代码

Posted

技术标签:

【中文标题】从汇编逆向工程 C 源代码【英文标题】:Reverse engineering C-source code from assembly 【发布时间】:2011-12-28 03:36:41 【问题描述】:

我想知道是否有人可以帮助我解决我在学习我在学校上的介绍性装配课上的一张演讲幻灯片时遇到的问题。我遇到的问题是不理解程序集,而是如何根据程序集对 C 源代码进行排序。我将发布我正在谈论的 sn-p,也许它会更清楚我在说什么。

C 源给出:

int arith(int x, int y, int z)
 
   int t1 = x+y;
   int t2 = z+t1;
   int t3 = x+4;
   int t4 = y * 48; 
   int t5 = t3 + t4;
   int rval = t2 * t5;
   return rval;

大会给出:

arith:
pushl %ebp
movl %esp,%ebp

movl 8(%ebp),%eax
movl 12(%ebp),%edx
leal (%edx,%eax),%ecx
leal (%edx,%edx,2),%edx
sall $4,%edx
addl 16(%ebp),%ecx
leal 4(%edx,%eax),%eax
imull %ecx,%eax

movl %ebp,%esp
popl %ebp
ret

我只是很困惑我应该如何辨别例如z + t1 (z + x + y) 的添加列在第二行(在源代码中),而在它之后汇编代码中的y * 48 或例如x + 4 是第三行,而在汇编中它甚至不在一行中,它与最后一个leal 语句混在一起。当我拥有源代码时这对我来说很有意义,但我应该能够重现源代码以进行测试,并且我确实理解编译器会优化事物,但是如果有人有一种思考逆向工程的方法可以帮助我如果他们能引导我完成他们的思考过程,我将不胜感激。

谢谢。

【问题讨论】:

您在编译时使用的是什么优化级别?如果您想要逐行转换,请使用 -O0,否则,您需要考虑优化。 ...将优化后的代码反转回原来的 C 语句顺序是不可能的。 你确定这是一个幼稚的编译吗?看起来它有些优化。写下函数计算的代数表达式,看看你是否能发现。 @KerrekSB 在我看来非常直接。它使用 LEA 和移位进行乘法运算,但我希望即使优化设置较低,因为它对可调试性没有负面影响。 【参考方案1】:

我已经分解了反汇编,以展示如何从 C 源代码生成程序集。

8(%ebp) = x, 12(%ebp) = y, 16(%ebp) = z

arith:

创建堆栈帧:

pushl %ebp
movl %esp,%ebp


x 移动到eax,将y 移动到edx
movl 8(%ebp),%eax
movl 12(%ebp),%edx


t1 = x + yleal(加载有效地址)会添加edxeax,而t1会在ecx中:
leal (%edx,%eax),%ecx


int t4 = y * 48; 在下面的两个步骤中,乘以 3,然后乘以 16。t4 最终将在 edx 中:

edx 乘以2,然后将edx 添加到结果中,即。 edx = edx * 3:

leal (%edx,%edx,2),%edx

左移 4 位,即。乘以 16:

sall $4,%edx


int t2 = z+t1;ecx 最初持有t1z位于16(%ebp),在指令结束时ecx将持有t2
addl 16(%ebp),%ecx


int t5 = t3 + t4;t3 就是简单的x + 4,而不是计算和存储t3t3 的表达式被放置在行内。这条指令必不可少的是(x+4) + t4,与t3 + t4 相同。它添加 edx (t4) 和 eax (x),并添加 4 作为 offset 以实现该结果。
leal 4(%edx,%eax),%eax

int rval = t2 * t5; 这个比较直截了当; ecx 代表 t2eax 代表 t5。返回值通过eax传回给调用者。

imull %ecx,%eax


销毁栈帧并恢复espebp
movl %ebp,%esp
popl %ebp


从例程返回:
ret


从这个例子你可以看到结果是一样的,但是结构有点不同。很可能这段代码是用某种优化编译的,或者有人自己写了这样的代码来证明一个观点。

正如其他人所说,您无法从反汇编中完全回到源头。这取决于阅读程序集的人的解释来提出等效的 C 代码。


为了帮助学习汇编和理解 C 程序的反汇编,您可以在 Linux 上执行以下操作:

使用调试信息 (-g) 编译,这将嵌入源代码:

gcc -c -g arith.c

如果您使用的是 64 位机器,您可以告诉编译器使用 -m32 标志创建一个 32 位二进制文​​件(我在下面的示例中这样做了)。

使用 objdump 转储目标文件及其源交错:

objdump -d -S arith.o

-d = 反汇编,-S = 显示源。如果您更喜欢示例使用的 AT&T 语法,则可以添加 -M intel-mnemonic 以使用 Intel ASM 语法。

输出:

arith.o:     file format elf32-i386


Disassembly of section .text:

00000000 <arith>:
int arith(int x, int y, int z)
 
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   83 ec 20                sub    $0x20,%esp
   int t1 = x+y;
   6:   8b 45 0c                mov    0xc(%ebp),%eax
   9:   8b 55 08                mov    0x8(%ebp),%edx
   c:   01 d0                   add    %edx,%eax
   e:   89 45 fc                mov    %eax,-0x4(%ebp)
   int t2 = z+t1;
  11:   8b 45 fc                mov    -0x4(%ebp),%eax
  14:   8b 55 10                mov    0x10(%ebp),%edx
  17:   01 d0                   add    %edx,%eax
  19:   89 45 f8                mov    %eax,-0x8(%ebp)
   int t3 = x+4;
  1c:   8b 45 08                mov    0x8(%ebp),%eax
  1f:   83 c0 04                add    $0x4,%eax
  22:   89 45 f4                mov    %eax,-0xc(%ebp)
   int t4 = y * 48; 
  25:   8b 55 0c                mov    0xc(%ebp),%edx
  28:   89 d0                   mov    %edx,%eax
  2a:   01 c0                   add    %eax,%eax
  2c:   01 d0                   add    %edx,%eax
  2e:   c1 e0 04                shl    $0x4,%eax
  31:   89 45 f0                mov    %eax,-0x10(%ebp)
   int t5 = t3 + t4;
  34:   8b 45 f0                mov    -0x10(%ebp),%eax
  37:   8b 55 f4                mov    -0xc(%ebp),%edx
  3a:   01 d0                   add    %edx,%eax
  3c:   89 45 ec                mov    %eax,-0x14(%ebp)
   int rval = t2 * t5;
  3f:   8b 45 f8                mov    -0x8(%ebp),%eax
  42:   0f af 45 ec             imul   -0x14(%ebp),%eax
  46:   89 45 e8                mov    %eax,-0x18(%ebp)
   return rval;
  49:   8b 45 e8                mov    -0x18(%ebp),%eax

  4c:   c9                      leave  
  4d:   c3                      ret

如您所见,如果不进行优化,编译器会生成比您的示例更大的二进制文件。您可以在编译时使用它并添加编译器优化标志(即-O1-O2-O3)。优化级别越高,反汇编看起来越抽象。

例如,仅使用 1 级优化 (gcc -c -g -O1 -m32 arith.c1),生成的汇编代码要短得多:

00000000 <arith>:
int arith(int x, int y, int z)
 
   0:   8b 4c 24 04             mov    0x4(%esp),%ecx
   4:   8b 54 24 08             mov    0x8(%esp),%edx
   int t1 = x+y;
   8:   8d 04 11                lea    (%ecx,%edx,1),%eax
   int t2 = z+t1;
   b:   03 44 24 0c             add    0xc(%esp),%eax
   int t3 = x+4;
   int t4 = y * 48; 
   f:   8d 14 52                lea    (%edx,%edx,2),%edx
  12:   c1 e2 04                shl    $0x4,%edx
   int t5 = t3 + t4;
  15:   8d 54 11 04             lea    0x4(%ecx,%edx,1),%edx
   int rval = t2 * t5;
  19:   0f af c2                imul   %edx,%eax
   return rval;

  1c:   c3                      ret

【讨论】:

【参考方案2】:

您不能复制原始来源,您只能复制等效来源。

在您的情况下,t2 的计算可以出现在t1 之后和retval 之前的任何位置。

来源甚至可能是一个表达式:

return (x+y+z) * ((x+4) + (y * 48));

【讨论】:

【参考方案3】:

进行逆向工程时,您不关心逐行原始源代码,您关心的是它的作用。一个副作用是你看到代码做了什么,而不是程序员想要代码做什么。

【讨论】:

【参考方案4】:

Decompilation 不是完全可以实现的:从源代码(cmets 和名称为您提供原始程序员意图的线索)到二进制机器代码(指令将由处理器)。

【讨论】:

以上是关于从汇编逆向工程 C 源代码的主要内容,如果未能解决你的问题,请参考以下文章

将汇编代码逆向工程为 c 代码

用C语言编写了一个程序,源代码和工程文件都弄丢了,只剩下.exe的二进制程序文件。怎么反编译出.c

用汇编语言解释这一行?

keil怎样把C语言转换成单片机汇编语言

自制反汇编逆向分析工具 迭代第三版本

逆向工程部分