我想知道是否有人可以帮助我解决我在学校参加的入门组装课的一张讲座幻灯片时遇到的问题。我遇到的问题是不理解程序集,而是 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)
= 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 + y
. leal
(加载有效地址(将添加edx
和eax
,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
最初持有t1
,z
在16(%ebp)
,在指令结束时,ecx
将持有t2
:
addl 16(%ebp),%ecx
int t5 = t3 + t4;
. t3
只是简单地x + 4
,而不是计算和存储t3
,t3
的表达式被内联放置。这个指令必不可少(x+4) + t4
,与t3
+t4
相同。它加上edx
(t4
(和eax
(x
(,并加上4作为偏移量来实现该结果。
leal 4(%edx,%eax),%eax
int rval = t2 * t5;
这个相当直截了当; ecx
代表t2
,eax
代表t5
。返回值通过 eax
传递回调用方。
imull %ecx,%eax
销毁堆栈帧并恢复
esp
和ebp
:
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));
在逆向工程时,你不关心一行一行的原始源代码,你关心它的作用。副作用是你看到的是代码做了什么,而不是程序员希望代码做什么。
反编译不是完全可以实现的:从源代码(注释和名称给你一个原始程序员意图的线索(到二进制机器代码(指令由处理器执行(时,会有一些知识损失。