C 汇编程序函数转换
C assembler function casting
我遇到了这段代码(整个程序见 this 页,见名为 "srop.c" 的程序)。
我的问题是关于如何在 main
方法中使用 func
。我只保留了我认为可能相关的代码。
是*ret = (int)func +4;
这一行让我很困惑。
关于这个我有三个问题:
func(void)
是函数,不应该用func()
调用(注意括号)
- 承认这对我来说可能是一些未知的调用函数的方式,如何在应该 return
void
时将其转换为 int
?
- 我知道作者不想保存帧指针也不想更新它(序言),正如他的评论所表明的那样。通过将函数转换为
int
并添加四行,如何提前跳过两行?
。
(gdb) disassemble func
Dump of assembler code for function func:
0x000000000040069b <+0>: push %rbp
0x000000000040069c <+1>: mov %rsp,%rbp
0x000000000040069f <+4>: mov [=10=]xf,%rax
0x00000000004006a6 <+11>: retq
0x00000000004006a7 <+12>: pop %rbp
0x00000000004006a8 <+13>: retq
End of assembler dump.
可能相关的是,编译后的 gcc 告诉我以下内容:
warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
请看下面的代码。
void func(void)
{
asm("mov [=11=]xf,%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
[编辑 2:] @cmaster:能否请您指出更多有关如何将汇编函数放在单独文件中并 link 的信息?原程序不会编译,因为它不知道函数prog
(放入汇编文件时)是什么,所以必须在编译前或编译时添加?
此外,gcc -S
when 运行 在仅包含汇编命令的 C 文件上似乎添加了很多额外信息,不能 func(void)
由以下汇编程序表示代码?
func:
mov [=13=]xf,%rax
retq
函数名是指向函数开始的指针。所以作者当时并没有调用该函数。只是保存对它开头的引用。
这不是空白。它是一个函数指针。更准确地说,在这种情况下,它的类型是:void (*)(void)。指针只是一个地址,因此可以转换为 int(但如果为 64 位系统编译,地址可能会被截断,因为在这种情况下 int 是 32 位)。
函数的第一条指令将 fp 压入堆栈。通过加 4,该指令被跳过。请注意,在您提供的代码片段中,该函数尚未被调用。这可能是您没有包含的部分代码。
此代码的假设远不止于此。无论如何,您显示的代码段仅尝试生成指向汇编程序函数体的指针,而不会尝试调用它。这是它的作用和假设:
func
本身产生一个指向函数的指针。
假设 1:
该指针实际上指向 func
的汇编代码的开头。这个假设不一定正确,有些架构中函数指针实际上是指向一对指针的指针,其中一个指向代码,另一个指向数据段。
func + 4
递增此指针以指向函数体的第一条指令。
假设 2:
函数指针可以递增,它们的递增是以字节为单位的。我相信这不在 C 标准中,但我在那个方面可能是错误的。
假设 3:
编译器插入的序言正好是四个字节长。绝对没有什么可以规定编译器应该发出什么样的序言,允许有多种变体,长度非常不同。您给出的代码试图通过不 passing/returning 任何参数来控制序言的长度,但仍然可以有编译器生成不同的序言。更糟糕的是,序言的大小可能取决于优化级别。
结果指针被转换为 int
。
假设 4:
sizeof(void (*)(void)) == sizeof(int)
。这在大多数 64 位系统上是错误的:在这些系统上 int
通常仍然是四个字节,而指针占用八个字节。在这样的系统上,指针值将被截断。当 int
被转换回函数指针并被调用时,这可能会使程序崩溃。
我的建议:
如果你真的想用汇编程序编程,用 gcc -S
编译一个只有空函数的文件。这将为您提供一个汇编程序源文件,其中包含汇编程序生成有效目标文件所需的所有内容,并向您展示可以在何处为您自己的函数添加代码。以您喜欢的任何方式修改该文件,然后像往常一样将其与一些调用 C 代码一起编译。这样你就可以避免所有这些危险的小假设。
我遇到了这段代码(整个程序见 this 页,见名为 "srop.c" 的程序)。
我的问题是关于如何在 main
方法中使用 func
。我只保留了我认为可能相关的代码。
是*ret = (int)func +4;
这一行让我很困惑。
关于这个我有三个问题:
func(void)
是函数,不应该用func()
调用(注意括号)- 承认这对我来说可能是一些未知的调用函数的方式,如何在应该 return
void
时将其转换为int
? - 我知道作者不想保存帧指针也不想更新它(序言),正如他的评论所表明的那样。通过将函数转换为
int
并添加四行,如何提前跳过两行?
。
(gdb) disassemble func
Dump of assembler code for function func:
0x000000000040069b <+0>: push %rbp
0x000000000040069c <+1>: mov %rsp,%rbp
0x000000000040069f <+4>: mov [=10=]xf,%rax
0x00000000004006a6 <+11>: retq
0x00000000004006a7 <+12>: pop %rbp
0x00000000004006a8 <+13>: retq
End of assembler dump.
可能相关的是,编译后的 gcc 告诉我以下内容:
warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
请看下面的代码。
void func(void)
{
asm("mov [=11=]xf,%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
[编辑 2:] @cmaster:能否请您指出更多有关如何将汇编函数放在单独文件中并 link 的信息?原程序不会编译,因为它不知道函数prog
(放入汇编文件时)是什么,所以必须在编译前或编译时添加?
此外,gcc -S
when 运行 在仅包含汇编命令的 C 文件上似乎添加了很多额外信息,不能 func(void)
由以下汇编程序表示代码?
func:
mov [=13=]xf,%rax
retq
函数名是指向函数开始的指针。所以作者当时并没有调用该函数。只是保存对它开头的引用。
这不是空白。它是一个函数指针。更准确地说,在这种情况下,它的类型是:void (*)(void)。指针只是一个地址,因此可以转换为 int(但如果为 64 位系统编译,地址可能会被截断,因为在这种情况下 int 是 32 位)。
函数的第一条指令将 fp 压入堆栈。通过加 4,该指令被跳过。请注意,在您提供的代码片段中,该函数尚未被调用。这可能是您没有包含的部分代码。
此代码的假设远不止于此。无论如何,您显示的代码段仅尝试生成指向汇编程序函数体的指针,而不会尝试调用它。这是它的作用和假设:
func
本身产生一个指向函数的指针。假设 1:
该指针实际上指向func
的汇编代码的开头。这个假设不一定正确,有些架构中函数指针实际上是指向一对指针的指针,其中一个指向代码,另一个指向数据段。func + 4
递增此指针以指向函数体的第一条指令。假设 2:
函数指针可以递增,它们的递增是以字节为单位的。我相信这不在 C 标准中,但我在那个方面可能是错误的。假设 3: 编译器插入的序言正好是四个字节长。绝对没有什么可以规定编译器应该发出什么样的序言,允许有多种变体,长度非常不同。您给出的代码试图通过不 passing/returning 任何参数来控制序言的长度,但仍然可以有编译器生成不同的序言。更糟糕的是,序言的大小可能取决于优化级别。
结果指针被转换为
int
。假设 4:
sizeof(void (*)(void)) == sizeof(int)
。这在大多数 64 位系统上是错误的:在这些系统上int
通常仍然是四个字节,而指针占用八个字节。在这样的系统上,指针值将被截断。当int
被转换回函数指针并被调用时,这可能会使程序崩溃。
我的建议:
如果你真的想用汇编程序编程,用 gcc -S
编译一个只有空函数的文件。这将为您提供一个汇编程序源文件,其中包含汇编程序生成有效目标文件所需的所有内容,并向您展示可以在何处为您自己的函数添加代码。以您喜欢的任何方式修改该文件,然后像往常一样将其与一些调用 C 代码一起编译。这样你就可以避免所有这些危险的小假设。