从汇编逆向工程 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 + 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 作为 offset 以实现该结果。
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
= 显示源。如果您更喜欢示例使用的 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 源代码的主要内容,如果未能解决你的问题,请参考以下文章