一般而言, 能否覆盖函数返回地址是栈溢出攻击成功与否的前提, 而获取函数返回地址原理很简单, 栈回溯即可, 下面是x86上相应获取代码(仅为示例不可用于生产):
#define Stack_Length (4 * 4 * 4)
static void *_esp_arr[Stack_Length];
void *GetRet(void *caller)
{
void *_esp;
__asm mov _esp, esp
for (int i = 0; i <= Stack_Length - 1; ++i) {
_esp_arr[i] = &_esp[i];
}
DWORD Protect = 0x00, CodeProtect = Protect | PAGE_EXECUTE_READ;
for (int k = 0; k <= Stack_Length - 1; k++) {
if (_esp[k] > caller) {
Protect = MemoryAllocator::Query(AppBase::GetCurrentProcess(), *_esp_arr[k])->Protect;
if (Protect == CodeProtect) {
_esp = _esp_arr[k];
break;
}
}
}
return *_esp;
}
函数返回地址受编译器优化影响, 比如内联、函数打包等, 在实际生产过程中, 获取函数返回地址应用范围似乎有限, 但是在模块调用合法性检查、自定义异常跳转(其实setjmp和longjmp就可以了)、异常信息打印等功能上确十分有用, 主流C/C++编译器都提供了内建拓展来获取函数返回地址, 比如
// gcc
__builtin_return_address
// msvc
// https://docs.microsoft.com/zh-cn/cpp/intrinsics/returnaddress
_ReturnAddress