我想了解更多有关unix / linux的信息,这个问题突然出现在我的脑海中 - 让我说我制作了一个静态/动态库(.a或.so)并丢失了c / c ++源代码和标题文件。默认的nm输出给出了符号的名称,但我需要知道返回类型和参数计数/类型以生成标题。是否有可能以某种方式获取这些额外信息来反向设计给定库的标题?
答案 0 :(得分:3)
你标记了C和C ++,答案在两者之间略有不同。
对于C ++,类的方法名称具有嵌入在符号名称中的类型信息。你只需要弄清楚编译库的编译器是什么类型的名称。
对于C来说,没有真正干净的方法。您可以拆开组件并分析读取哪些寄存器和堆栈区域,而无需编写以确定函数需要多少参数。这需要了解编译库的任何编译器使用的调用约定。
同样,您可以查看在程序集中如何使用每个参数。如果你看到它在加载指令中使用,它很可能是某种指针,而如果你看到它在算术中使用,它可能是某种整数。
对于返回类型,您可以在返回指令之前检查返回寄存器中是否存在任何看似有意义的内容。同样,这需要了解您平台的调用约定。
以下是我将如何在ARM程序集中执行操作的示例。
我知道ARM中的参数在寄存器r0到r3中传递,返回值存储在寄存器r0中。考虑到这一点,我们可以开始逆向工程。让我们看看两个函数的程序集,并尝试找出函数原型是什么。
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中,所以我们推断该函数返回某种值。
如果这个函数被呈现给我,我猜测函数原型看起来像这样:
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中放置任何内容,因此该函数返回第一个参数或不返回值。
我猜原型将是以下之一
void func2(unsigned char *, int, int);
unsigned char *func2(unsigned char *, int, int);
原件:
void func2(char *, char, char);
答案 1 :(得分:0)
请记住,调用者/被调用者的约定因不同的处理器指令集而异,并且您在使用c和c ++库时已经知道名称损坏,您可以尝试以下方式:
gdb <executable>
....
disas <function name>
....
Here you can make a wild guess about the type of return value and parameters using the bit size of those values written on stack making use of assembly code.