在 C 中的数组中执行机器代码。这是在执行一个整数吗?
Executing machine code in an array in C. Is this executing an Integer?
我试图理解为什么 ret();
在以下 C 程序中工作:
#include<stdio.h>
#include<string.h>
unsigned char code[] = \
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69"
"\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80";
main()
{
printf("Shellcode Length: %d\n", strlen(code));
int (*ret)() = (int(*)())code;
ret();
}
为了让它工作,你必须在没有堆栈保护的情况下编译上面的代码,允许堆栈可执行。
我想知道的是 为什么 调用 ret();
,这似乎是一个分配给值 (int(*)())code;
的整数有效。
我猜这与函数指针确实是整数有关,但我无法在脑海中解开表达式 int (*ret)() = (int(*)())code;
的含义
感谢您的帮助
强制转换将数组 code
转换为指向函数的指针,并将其分配给函数指针 ret
。由于 ret
是指向函数的指针,因此当您调用指向该函数时,会执行数组 code
中的机器代码。
这就是理论。不确定存储到数组 code
中的实际机器码是什么。
所以 ret
是一个指向 return 是 int
的函数的指针。
函数指针不是int
而是函数指针。
函数指针定义和声明
对于诸如 int (*ret)()
的变量定义,您必须从变量名称开始解析定义,在本例中为 ret
。使函数指针变量定义更难破译的是括号用于定义表达式的解析顺序,并且括号还用作特殊符号来指示变量是函数指针。
标准函数声明看起来像 int retFunc ();
,它声明了一个 return 是 int
的函数。在这种旧式的函数声明中没有指定参数,所以我们不知道是否没有参数,或者是否有几个参数或它们的类型,如果有参数。顺便说一句,标准整数变量声明看起来像 int intVar;
.
要创建函数指针变量,您需要为函数指针指定与为函数声明所做的相同的信息,以及一条附加信息,表明这是函数指针的声明或定义,并且不是函数的声明。
下面是一些语法变体的代码,以显示函数声明和函数指针声明之间的区别。
main () {
extern int retFunc(); // declaration of a function, returns int
extern int (*ret)(); // declaration of a function pointer, function returns int
extern int *retFunc2(); // declaration of a function, returns int pointer
extern int *(*retVar)(); // declaration of a function pointer, function returns a pointer to an int
extern int (*((*ret2)()))(); // declaration of a function pointer, function returns a function pointer which points to a function that returns an int
}
这五个的区别在于在第二个、第四个和第五个带括号的声明中使用指针指示符来强制编译器解释声明的方式。由于运算符优先级规则导致编译器将更高的优先级放在指示函数的括号上,因此需要分组括号,因此我们使用分组括号覆盖优先级规则。
第五个特别有意思,extern int (*((*ret2)()))();
可以分两步解析出来。第一个是((*ret2)()))
块,表示符号ret2
是一个函数指针,第二阶段是判断return所指函数的类型,一个函数指针通过用任意符号 x 替换第一部分,return 成为 int
,如 int (*x)();
.
在创建函数指针声明时,我们必须了解 C 的运算符优先级规则以及这些规则如何影响编译器解释声明或定义的方式。我们需要在函数指针声明中的 *ret
周围添加额外的括号,以便编译器将其视为指向 return 是 int
的函数的指针,而不是 int
的函数 returns 指向 int
.
的指针
C 编译器使用的规则有时要求使用括号来强制执行表达式的翻译顺序,以使表达式具有所需的含义。这些规则有时会导致相同的字符或符号在不同的上下文中具有不同的含义。所以 int ret();
括号使符号 ret
成为一个函数, int (ret);
括号用于对符号进行分组,在这种情况下只使用一个符号和 int (*ret)();
括号对两个组符号和指示函数,在这种情况下 ret
是指向函数的指针。
在您的示例中,您不是将变量 ret
声明为函数指针,而是在语句 int (*ret)() = (int(*)())code;
中定义变量并为其赋值。解析定义的规则类似于解析声明的规则。
在您的示例中,code
被定义为 unsigned char
的数组,我假设是在数组初始化中指定的机器代码。
在 C 中,数组变量在很多方面都可以被视为常量指针变量。因此,您可以取消引用数组名称,这意味着 code[1]
与 *(code + 1)
相同,但是由于它是一个常量指针,您不能执行类似 code = code + 1;
的操作,尽管您可以执行类似 unsigned char *code1 = (code + 1);
与 unsigned char *code1 = &code[1];
.
相同
所以在语句 int (*ret)() = (int (*)())code;
中,您将指向 unsigned char
的常量指针 code
转换为指向 return 的函数的函数指针 [= =17=]。只要有某种方法可以从赋值运算符右侧的类型到赋值运算符左侧的类型,C 编译器很乐意满足您想要创建的任何幻想。
然而,仅仅因为编译器乐于从表达式生成机器代码并不意味着当程序实际上是 运行 时,底层操作系统和硬件会对结果感到满意。这些灰色区域,即未定义行为的区域,可能会导致程序有时 运行 有时而不是其他时间,或者可能 运行 在一个环境中而不在另一个环境中。
数组 code
的转换使得这有点难以理解,因为转换为 return 和 int
的函数指针的语法类似于用于声明或定义 return 为 int
的函数指针的语法,除了在转换 (int(*)())
中星号后没有变量。所以所有这些括号都会让它有点混乱。
在这种类型转换的情况下,我们使用圆括号对完整类型转换进行分组,(int(*)())
以及圆括号强制执行命令,(*)
,圆括号表示这是一个函数,()
。所以在这个类型转换中有很多括号。
当它变得更复杂时,就像 int *((*ret)()) = (int *((*)()))code;
是一个指向函数的函数指针,return 是指向 int
.
的指针
在这种情况下,我更喜欢明确地使用括号来指定解释顺序,而不是依赖于我对顺序运算符优先级的记忆。
What I'm wondering though is why calling ret()
, which appears to be an integer assigned to the value (int(*)())code
works
ret
不是整数,它是指向返回整数的函数的指针。 "inline" 语法,即 int (*ret)()
比等效的 typedef
,即
更难 "decipher"
typedef int (*func_returning_int)();
...
func_returning_int ret = (func_returning_int)code;
注意:不用说,无论您以何种方式转换指针,这都是未定义的行为。
我试图理解为什么 ret();
在以下 C 程序中工作:
#include<stdio.h>
#include<string.h>
unsigned char code[] = \
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69"
"\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80";
main()
{
printf("Shellcode Length: %d\n", strlen(code));
int (*ret)() = (int(*)())code;
ret();
}
为了让它工作,你必须在没有堆栈保护的情况下编译上面的代码,允许堆栈可执行。
我想知道的是 为什么 调用 ret();
,这似乎是一个分配给值 (int(*)())code;
的整数有效。
我猜这与函数指针确实是整数有关,但我无法在脑海中解开表达式 int (*ret)() = (int(*)())code;
感谢您的帮助
强制转换将数组 code
转换为指向函数的指针,并将其分配给函数指针 ret
。由于 ret
是指向函数的指针,因此当您调用指向该函数时,会执行数组 code
中的机器代码。
这就是理论。不确定存储到数组 code
中的实际机器码是什么。
所以 ret
是一个指向 return 是 int
的函数的指针。
函数指针不是int
而是函数指针。
函数指针定义和声明
对于诸如 int (*ret)()
的变量定义,您必须从变量名称开始解析定义,在本例中为 ret
。使函数指针变量定义更难破译的是括号用于定义表达式的解析顺序,并且括号还用作特殊符号来指示变量是函数指针。
标准函数声明看起来像 int retFunc ();
,它声明了一个 return 是 int
的函数。在这种旧式的函数声明中没有指定参数,所以我们不知道是否没有参数,或者是否有几个参数或它们的类型,如果有参数。顺便说一句,标准整数变量声明看起来像 int intVar;
.
要创建函数指针变量,您需要为函数指针指定与为函数声明所做的相同的信息,以及一条附加信息,表明这是函数指针的声明或定义,并且不是函数的声明。
下面是一些语法变体的代码,以显示函数声明和函数指针声明之间的区别。
main () {
extern int retFunc(); // declaration of a function, returns int
extern int (*ret)(); // declaration of a function pointer, function returns int
extern int *retFunc2(); // declaration of a function, returns int pointer
extern int *(*retVar)(); // declaration of a function pointer, function returns a pointer to an int
extern int (*((*ret2)()))(); // declaration of a function pointer, function returns a function pointer which points to a function that returns an int
}
这五个的区别在于在第二个、第四个和第五个带括号的声明中使用指针指示符来强制编译器解释声明的方式。由于运算符优先级规则导致编译器将更高的优先级放在指示函数的括号上,因此需要分组括号,因此我们使用分组括号覆盖优先级规则。
第五个特别有意思,extern int (*((*ret2)()))();
可以分两步解析出来。第一个是((*ret2)()))
块,表示符号ret2
是一个函数指针,第二阶段是判断return所指函数的类型,一个函数指针通过用任意符号 x 替换第一部分,return 成为 int
,如 int (*x)();
.
在创建函数指针声明时,我们必须了解 C 的运算符优先级规则以及这些规则如何影响编译器解释声明或定义的方式。我们需要在函数指针声明中的 *ret
周围添加额外的括号,以便编译器将其视为指向 return 是 int
的函数的指针,而不是 int
的函数 returns 指向 int
.
C 编译器使用的规则有时要求使用括号来强制执行表达式的翻译顺序,以使表达式具有所需的含义。这些规则有时会导致相同的字符或符号在不同的上下文中具有不同的含义。所以 int ret();
括号使符号 ret
成为一个函数, int (ret);
括号用于对符号进行分组,在这种情况下只使用一个符号和 int (*ret)();
括号对两个组符号和指示函数,在这种情况下 ret
是指向函数的指针。
在您的示例中,您不是将变量 ret
声明为函数指针,而是在语句 int (*ret)() = (int(*)())code;
中定义变量并为其赋值。解析定义的规则类似于解析声明的规则。
在您的示例中,code
被定义为 unsigned char
的数组,我假设是在数组初始化中指定的机器代码。
在 C 中,数组变量在很多方面都可以被视为常量指针变量。因此,您可以取消引用数组名称,这意味着 code[1]
与 *(code + 1)
相同,但是由于它是一个常量指针,您不能执行类似 code = code + 1;
的操作,尽管您可以执行类似 unsigned char *code1 = (code + 1);
与 unsigned char *code1 = &code[1];
.
所以在语句 int (*ret)() = (int (*)())code;
中,您将指向 unsigned char
的常量指针 code
转换为指向 return 的函数的函数指针 [= =17=]。只要有某种方法可以从赋值运算符右侧的类型到赋值运算符左侧的类型,C 编译器很乐意满足您想要创建的任何幻想。
然而,仅仅因为编译器乐于从表达式生成机器代码并不意味着当程序实际上是 运行 时,底层操作系统和硬件会对结果感到满意。这些灰色区域,即未定义行为的区域,可能会导致程序有时 运行 有时而不是其他时间,或者可能 运行 在一个环境中而不在另一个环境中。
数组 code
的转换使得这有点难以理解,因为转换为 return 和 int
的函数指针的语法类似于用于声明或定义 return 为 int
的函数指针的语法,除了在转换 (int(*)())
中星号后没有变量。所以所有这些括号都会让它有点混乱。
在这种类型转换的情况下,我们使用圆括号对完整类型转换进行分组,(int(*)())
以及圆括号强制执行命令,(*)
,圆括号表示这是一个函数,()
。所以在这个类型转换中有很多括号。
当它变得更复杂时,就像 int *((*ret)()) = (int *((*)()))code;
是一个指向函数的函数指针,return 是指向 int
.
在这种情况下,我更喜欢明确地使用括号来指定解释顺序,而不是依赖于我对顺序运算符优先级的记忆。
What I'm wondering though is why calling
ret()
, which appears to be an integer assigned to the value(int(*)())code
works
ret
不是整数,它是指向返回整数的函数的指针。 "inline" 语法,即 int (*ret)()
比等效的 typedef
,即
typedef int (*func_returning_int)();
...
func_returning_int ret = (func_returning_int)code;
注意:不用说,无论您以何种方式转换指针,这都是未定义的行为。