您不是"运行括号",而是将环境变量的值转换为函数指针并将其作为机器代码执行. 在正常的C实现(如gcc)上,这意味着call它就像是本机机器代码一样.
(在语法上,()在这个上下文中是一个调用函数或函数指针的运算符.在其他上下文中,不是在变量名之后,它是表达式的分组运算符,如(1+2)*3.)
但通常env vars在堆栈上(内核在进程启动之前放置它们),并且堆栈通常不可执行.所以你只是段错误,除非你用它编译-zexecstack或以其他方式使堆栈内存可执行.
此外,stdio.h没有声明getenv,所以假设它返回int.因此,您在64位体系结构上出于这个原因进行了段错误,除非您构建32位代码,其中int可以保存指针而不截断它.(堆栈通常位于虚拟内存的用户空间部分的顶部,因此它位于低32位的地址空间之外).
但令人惊讶的是,现代gcc和clang实际上只用警告编译它,而不是错误,这与你使用C++模式不同.(当然,非常非常严重的警告.)
<source>: In function 'main':
<source>:4:11: warning: implicit declaration of function 'getenv'; did you mean 'getline'? [-Wimplicit-function-declaration]
ret = getenv("SOME_ENV_VAR");
^~~~~~
getline
<source>:4:9: warning: assignment to 'int (*)()' from 'int' makes pointer from integer without a cast [-Wint-conversion]
ret = getenv("SOME_ENV_VAR");
^
Compiler returned: 0
Run Code Online (Sandbox Code Playgroud)
x86 gcc 8.2的asm输出-xc -O3 -Wall -m32(来自Godbolt编译器浏览器:https://godbolt.org/z/9uPIbN )是:
.LC0:
.string "SOME_ENV_VAR"
main:
lea ecx, [esp+4]
and esp, -16
push DWORD PTR [ecx-4]
push ebp
mov ebp, esp
push ecx
sub esp, 16
push OFFSET FLAT:.LC0
call getenv
call eax # use getenv ret value as a function pointer
mov ecx, DWORD PTR [ebp-4]
add esp, 16
xor eax, eax
leave
lea esp, [ecx-4]
ret
Run Code Online (Sandbox Code Playgroud)
因此,如果你运行这个程序SOME_ENV_VAR=$'\xc3' ./a.out,它实际上会退出而不会崩溃,如果你编译它-zexecstack. 0xC3是x86ret的操作码,$''是用于处理C样式转义序列的bash语法,用于创建包含\n换行符或任何字节的字符串(除了0之外,因为bash内部使用C样式的隐式长度字符串).
peter@volta:/tmp$ gcc -Wall -m32 -O3 run-env.c -zexecstack
run-env.c: In function ‘main’:
run-env.c:4:11: warning: implicit declaration of function ‘getenv’; did you mean ‘getline’? [-Wimplicit-function-declaration]
ret = getenv("SOME_ENV_VAR");
^~~~~~
getline
run-env.c:4:9: warning: assignment makes pointer from integer without a cast [-Wint-conversion]
ret = getenv("SOME_ENV_VAR");
^
peter@volta:/tmp$ SOME_ENV_VAR=$'\xc3' ./a.out
peter@volta:/tmp$ ./a.out
Segmentation fault (core dumped)
Run Code Online (Sandbox Code Playgroud)
如果我遗漏了任何-m32或者-zexecstack,那将是段错误.退出env var,得到genenv返回NULL,或者任何非干净地返回的机器代码的值也将是段错误的.或者我可以使用(指令)使它成为SIGILL .0F 0Bud2
或者,如果我包含正确的标题,我不需要-m32,因为从char*函数指针隐式转换将"工作".就C标准而言,这显然是完全未定义的行为,但这种行为是我们从asm gcc发出的行为.(这应该是任何其他架构的情况,除非你需要输入返回指令的字节.通常是2或4个字节;大多数架构使用固定长度的指令字,与x86不同.这个答案是以x86为中心的因为这是我在桌面上轻松测试的内容.)
您可以而且应该通过GDB单步执行asm来了解正在发生的事情.
您可以将更长的机器代码序列放入env var中,甚至可以使用它来测试shellcode.printf除非您为libc禁用了ASLR,否则您无法通过此方式注入代码 轻松调用任何内容.即便如此,您还需要查看GDB以查看env var最终的地址.
有关指南,教程和x86 asm以及体系结构手册的更多链接,请参阅https://stackoverflow.com/tags/x86/info.