我想了解有关 unix/linux 的更多信息,这个问题突然出现在我的脑海中 - 假设我制作了一个静态/动态库(.a 或 .so)并丢失了 c/c++ 源代码和头文件。默认 nm 输出为我提供了符号的名称,但我需要知道返回类型和参数计数/类型才能制作标题。是否有可能以某种方式获得这些额外信息以对给定库的 header 进行逆向工程?
最佳答案
您标记了 C 和 C++,两者的答案略有不同。
对于 C++,类的方法名称在符号名称中嵌入了类型信息。您只需要弄清楚编译库的编译器是如何处理什么样的名称。
对于 C,没有真正干净的方法来做到这一点。您可以拆开程序集并分析读取了哪些寄存器和堆栈区域,而无需编写以确定函数需要多少参数。这需要了解编译库的任何编译器所使用的调用约定。
同样,您可以查看每个参数在程序集中的使用方式。如果您看到它在加载指令中使用,它很可能是某种指针,而如果您看到它在算术中使用,它可能是某种整数。
对于返回类型,您可以在返回指令之前检查返回寄存器中是否放置了看似有意义的内容。同样,这需要了解您平台的调用约定。
这是我如何在 ARM 汇编中执行操作的示例。
我知道ARM中的参数是通过寄存器r0到r3传递的,返回值保存在寄存器r0中。考虑到这一点,我们可以开始逆向工程。让我们看一下两个函数的汇编,并尝试找出函数原型(prototype)是什么。
00000000 <func1>:
0: e3510000 cmp r1, #0
4: 0a000007 beq 28 <func1+0x28>
8: e0801001 add r1, r0, r1
c: e1a03000 mov r3, r0
10: e3a00000 mov r0, #0
14: e4d32001 ldrb r2, [r3], #1
18: e1530001 cmp r3, r1
1c: e0800002 add r0, r0, r2
20: 1afffffb bne 14 <func1+0x14>
24: e12fff1e bx lr
28: e1a00001 mov r0, r1
2c: e12fff1e bx lr
如果我们看这里,r0 和 r1 都在写入任何内容之前被读取。我们还可以看到 r2 和 r3 在读取之前被写入。因此我们可以推断 func1
最多有两个参数。
我们还意识到 r0 被移动到 r3,然后用作 ldrb
的地址,这是一条从内存加载字节的指令。因此,我们推断第一个参数是一个指针。由于该指令仅加载一个字节,我们还可以判断它可能是指向某种单字节数据类型的指针。
r1 中的第二个参数除了在比较和添加指令中似乎从未使用过,所以它可能是一个整数。
在每个 bx lr
(返回调用者指令)之前,在 r0 中放置了一些东西,因此我们推断该函数返回某种值。
如果向我展示这个函数,我猜函数原型(prototype)应该是这样的:
int func1(unsigned char *, int);
原文:
unsigned int func1(void *, unsigned int);
这是另一个函数
00000030 <func2>:
30: e0822001 add r2, r2, r1
34: e5c02000 strb r2, [r0]
38: e12fff1e bx lr
这个很简单。
我们看到 r0、r1 和 r2 在被写入之前都是被读取的,因此我们可以猜测该函数需要三个参数。 r0 用作 strb
指令(存储字节)的地址,因此它可能是一个指针。同样,它只存储一个字节,因此它可能是一个指向字节大小数据类型的指针。
另外两个仅用于加法指令,因此可能是整数。
最后似乎没有任何内容放入 r0,因此该函数要么返回第一个参数,要么不返回值。
我猜原型(prototype)会是以下之一
void func2(unsigned char *, int, int);
unsigned char *func2(unsigned char *, int, int);
原文:
void func2(char *, char, char);
关于c++ - 假设 - 关于为*现有*静态/动态库制作 header ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17387401/