我遇到了这段代码(整个程序见this页面,请参阅名为“srop.c”的程序。)
我的问题是如何在func
方法中使用main
。我只保留了我认为可能相关的代码。
线*ret = (int)func +4;
让我感到困惑。
我对此有三个问题:
func(void)
是一个函数,如果不使用func()
调用(请注意括号)int
返回void
时将其投放到int
?(gdb) disassemble func
Dump of assembler code for function func:
0x000000000040069b <+0>: push %rbp
0x000000000040069c <+1>: mov %rsp,%rbp
0x000000000040069f <+4>: mov $0xf,%rax
0x00000000004006a6 <+11>: retq
0x00000000004006a7 <+12>: pop %rbp
0x00000000004006a8 <+13>: retq
End of assembler dump.
并添加四个函数来实现这两个跳过两线?。
warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
可能相关的是,编译时gcc告诉我以下内容:
void func(void)
{
asm("mov $0xf,%rax\n\t");
asm("retq\n\t");
}
int main(void)
{
unsigned long *ret;
/*...*/
/* overflowing */
ret = (unsigned long *)&ret + 2;
*ret = (int)func +4; //skip gadget's function prologue
/*...*/
return 0;
}
请参阅下面的代码。
calling func returns a pointer to the start of the function: 0x400530
casting this to an int is dangerous (in hex) 400530
casting this to an int in decimal 4195632
safe cast to unsigned long 4195632
size of void pointer: 8
size of int: 4
size of unsigned long: 8
[编辑]根据非常有用的建议,以下是一些进一步的信息:
prog
[编辑2:] @cmaster:请您指点一些关于如何将汇编程序功能放在单独的文件中并链接到它的更多信息?原始程序将无法编译,因为它不知道函数gcc -S
(当放入汇编程序文件时)是什么,所以它必须在编译之前或编译期间添加?
此外,仅在包含汇编命令的C文件上运行时func(void)
似乎添加了大量额外信息,以下汇编程序代码无法表示func:
mov $0xf,%rax
retq
?
{{1}}
答案 0 :(得分:1)
此代码假定的不仅仅是对它有益的东西。无论如何,您显示的片段仅尝试生成指向汇编程序函数体的指针,它不会尝试调用它。以下是它的作用,以及它的假设:
func
本身会生成一个指向该函数的指针。
假设1:
指针实际上指向func
的汇编程序代码的开头。这个假设不一定正确,有一些架构,其中函数指针实际上是指向一对指针的指针,其中一个指向代码,另一个指向数据段。
func + 4
将此指针递增以指向函数体的第一条指令。
假设2:
函数指针可以递增,其增量以字节为单位。我认为这不属于C标准,但我可能错了。
假设3: 编译器插入的序言长度恰好为四个字节。绝对没有任何东西可以规定编译器应该发出什么样的序言,允许多种变体,长度非常不同。您提供的代码试图通过不传递/返回任何参数来控制序言的长度,但仍然可以有编译器生成不同的序言。更糟糕的是,序言的大小可能取决于优化级别。
结果指针将转换为int
。
假设4:
sizeof(void (*)(void)) == sizeof(int)
。在大多数64位系统上这是错误的:在这些系统上int
通常仍然是4个字节,而指针占用8个字节。在这样的系统上,指针值将被截断。当int
被强制转换为函数指针并被调用时,这可能会使程序崩溃。
我的建议:
如果您真的想在汇编程序中编程,请使用gcc -S
编译一个只有空函数的文件。这将为您提供一个汇编源文件,其中包含汇编程序生成有效目标文件所需的所有内容,并显示您可以为自己的函数添加代码的位置。以您喜欢的任何方式修改该文件,然后像往常一样将其与一些调用C代码一起编译。这样你就可以避免所有这些危险的小假设。
答案 1 :(得分:0)
函数的名称是指向函数开头的指针。所以作者当时没有调用函数。只需保存对其开头的引用。
这不是空白。它是一个函数指针。更确切地说,在这种情况下,它的类型为:void(*)(void)。指针只是一个地址,因此可以转换为int(但如果为64位系统编译,则地址可能会被截断,因为在这种情况下,int是32位)。
该函数的第一条指令将fp推入堆栈。通过添加4,将跳过该指令。请注意,在您提供的代码片段中尚未调用该函数。它可能是您未包含的代码的一部分。