从汇编中逆向工程 C 源代码



我想知道是否有人可以帮助我解决我在学校参加的入门组装课的一张讲座幻灯片时遇到的问题。我遇到的问题是不理解程序集,而是 C 源代码如何根据程序集进行排序。我将发布我正在谈论的片段,也许我会更清楚我在说什么。

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是第 3 行,而在程序集中它甚至不在一行中, 它有点混杂在最后leal的声明中。 当我有源代码时,这对我来说是有意义的,但我应该能够重现源代码进行测试,我确实了解编译器优化了事情,但如果有人有一种方法可以思考可以帮助我的逆向工程,我将不胜感激如果他们能引导我完成他们的思考过程。

谢谢。

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

8(%ebp) = x12(%ebp) = y16(%ebp) = z

arith:

创建堆栈帧:

pushl %ebp
movl %esp,%ebp


x移入eaxy移入edx
movl 8(%ebp),%eax
movl 12(%ebp),%edx


t1 = x + y . leal(加载有效地址(将添加edxeaxt1ecx
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最初持有t1z16(%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作为偏移量来实现该结果。
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 = 显示源。您可以添加-M intel-mnemonic以使用英特尔 ASM 语法,如果您更喜欢该语法,而不是您的示例使用的 AT&T 语法。

输出:

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

您无法复制原始源,只能复制等效源。

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

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

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

在逆向工程时,你不关心一行一行的原始源代码,你关心它的作用。副作用是你看到的是代码做了什么,而不是程序员希望代码做什么。

反编译不是完全可以实现的:从源代码(注释和名称给你一个原始程序员意图的线索(到二进制机器代码(指令由处理器执行(时,会有一些知识损失。

最新更新