我的问题主要是针对教授的,是关于以“奇怪”的方式使用 C++ 的。在 C++ 中,变量指针和函数指针之间并没有太大区别。我们可以像这样做一些无用的事情:
char* buff = new char[32];
void (*func)() = (void (*)())buff;
但我们几乎创建了一个从未存在过的函数,对吧?如果我们更进一步,用存储在文件中的 x86 命令填充 buff 会怎么样?操作系统永远不会知道函数已被创建。
#include <iostream>
using namespace std;
// no stack push'ing or pop'ing, nothing to return
void func(void){cout << "Hello?";}
int main()
{
char* x86_code = new char[6];
x86_code[0] = 0x9A; // call (far)
*((__int32*)(x86_code + 1)) = (__int32)func; // load 32-bit address
x86_code[5] = 0xC3; // ret
void (*x86_func)(void) = (void (*)(void))x86_code;
x86_func();
return 0;
}
调用 x86_func() 会产生运行时错误(违反读取位置 0xFFFFFFFF)。如果不是以这种方式,操作系统如何将其二进制文件或模块加载到 RAM 中?非常感谢。
事实上,您可以用 x86 机器代码填充数组并尝试执行它。它被称为外壳代码设法使应用程序或库在不打算执行此类代码时执行此类代码称为“漏洞利用”。
不幸的是,这并不容易,因为现代硬件和操作系统通常会阻止您从可写区域(例如非常量字符数组)执行代码。这称为 W^X(写入或执行权限,但不能两者兼而有之),但可以请求 POSIX 兼容操作系统禁用此类保护,方法是mprotect()
功能。这是一个有效的示例,因为它启用了对相关机器代码字节数组的读、写和执行权限:
#include <stdio.h>
#include <stdint.h>
#include <sys/mman.h>
typedef int(*FUNKY_POINTER)(void);
char shellcode[] = {
0xb8, 0x2a, 0x00, 0x00, 0x00, //mov $0x2a,%eax
0xc3 //retq
};
int main(void){
uintptr_t pageSize = 4096;
uintptr_t shellcodeAddr = (uintptr_t)shellcode;
uintptr_t pageAlignedAddr = shellcodeAddr & ~(pageSize-1);
FUNKY_POINTER shellcodeFn = (FUNKY_POINTER)shellcode;
/* Magic */
mprotect((void*)pageAlignedAddr,
(shellcodeAddr - pageAlignedAddr) + sizeof(shellcode),
PROT_EXEC|PROT_WRITE|PROT_READ);
printf("The answer to the ultimate question of life, "
"the universe and everything is %d\n",
shellcodeFn());
return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)