// 读取动态链接符号表 std::string dynamicSymbolNames(reinterpret_cast<char*>(strTabAddr), strTabSize); Elf64_External_Sym* dynamicSymbols = reinterpret_cast<Elf64_External_Sym*>(symTabAddr); // 设置动态链接的函数地址 std::cout << std::hex << "read dynamic entires at: 0x" << jmpRelAddr << " size: 0x" << pltRelSize << std::dec << std::endl; if (jmpRelAddr == 0 || pltRelType != DT_RELA || pltRelSize % sizeof(Elf64_External_Rela) != 0) { throw std::runtime_error("invalid dynamic entry info, rel type should be rela"); } std::vector<std::shared_ptr<void>> libraryFuncs; for (std::uint64_t offset = 0; offset < pltRelSize; offset += sizeof(Elf64_External_Rela)) { Elf64_External_Rela* rela = (Elf64_External_Rela*)(jmpRelAddr + offset); std::uint64_t relaOffset = *reinterpret_cast<const std::uint64_t*>(rela->r_offset); std::uint64_t relaInfo = *reinterpret_cast<const std::uint64_t*>(rela->r_info); std::uint64_t relaSym = relaInfo >> 32; // ELF64_R_SYM std::uint64_t relaType = relaInfo & 0xffffffff; // ELF64_R_TYPE // 获取符号 Elf64_External_Sym* symbol = dynamicSymbols + relaSym; std::uint32_t symbolNameOffset = *reinterpret_cast<std::uint32_t*>(symbol->st_name); std::string symbolName(dynamicSymbolNames.data() + symbolNameOffset); std::cout << "relocate symbol: " << symbolName << std::endl; ** relaPtr = reinterpret_cast<void**>(relaOffset); std::shared_ptr<void> func = resolveLibraryFunc(symbolName); if (func == nullptr) { throw std::runtime_error("unsupport symbol name"); } libraryFuncs.emplace_back(func); *relaPtr = func.get(); }
上面的代码遍历了DT_JMPREL重定位记录, 并且在加载时设置了这些函数的地址,
其实应该通过延迟解决实现的, 但是这里为了简单就直接替换成最终的地址了.
上面获取函数实际地址的逻辑我写到了resolveLibraryFunc中,这个函数的实现在另外一个文件, 如下
namespace HelloElfLoader { namespace { * originalReturnAddress = nullptr; void* getOriginalReturnAddress() { return originalReturnAddress; } void setOriginalReturnAddress(void* address) { originalReturnAddress = address; } // 模拟libc调用main的函数,目前不支持传入argc和argv void __libc_start_main(int(*main)()) { std::cout << "call main: " << main << std::endl; int ret = main(); std::cout << "result: " << ret << std::endl; std::exit(0); } * fmt, ...) { int ret; va_list myargs; va_start(myargs, fmt); ret = ::vprintf(fmt, myargs); va_end(myargs); return ret; } generic_func_loader[]{ , , , , , , , , , , , , , , , }; const int generic_func_loader_set_addr_offset = 18; const int generic_func_loader_target_offset = 44; const int generic_func_loader_get_addr_offset = 61; } // 获取动态链接函数的调用地址 std::shared_ptr<void> resolveLibraryFunc(const std::string& name) { void* funcPtr = nullptr; if (name == "__libc_start_main") { funcPtr = __libc_start_main; } else if (name == "printf") { funcPtr = printf; } else { return nullptr; } void* addr = ::VirtualAlloc(nullptr, sizeof(generic_func_loader), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (addr == nullptr) { throw std::runtime_error("allocate memory for _libc_start_main_loader failed"); } std::shared_ptr<void> result(addr, [](void* ptr) { ::VirtualFree(ptr, 0, MEM_RELEASE); }); std::memcpy(addr, generic_func_loader, sizeof(generic_func_loader)); char* addr_c = reinterpret_cast<char*>(addr); *reinterpret_cast<void**>(addr_c + generic_func_loader_set_addr_offset) = setOriginalReturnAddress; *reinterpret_cast<void**>(addr_c + generic_func_loader_target_offset) = funcPtr; *reinterpret_cast<void**>(addr_c + generic_func_loader_get_addr_offset) = getOriginalReturnAddress; return result; } }
理解这段代码需要先了解什么是x86 calling conventions, 在汇编中传递函数参数的办法由很多种, 像cdecl是把所有参数都放在栈中从低到高排列, 而fastcall是把第一个参数放ecx, 第二个参数放edx, 其余参数放栈中.