Mat*_*szL 5 c linux elf aslr position-independent-code
我需要在运行的进程中获取堆栈的基地址。这将使我能够打印将由addr2line理解的原始堆栈跟踪(已剥离运行二进制文件,但addr2line可以访问符号)。我通过检查以下内容的elf标头成功做到了这一点argv[0]:我读取了入口点并将其从中减去&_start:
#include <stdio.h>
#include <execinfo.h>
#include <unistd.h>
#include <elf.h>
#include <stdio.h>
#include <string.h>
void* entry_point = NULL;
void* base_addr = NULL;
extern char _start;
/// given argv[0] will populate global entry_pont
void read_elf_header(const char* elfFile) {
// switch to Elf32_Ehdr for x86 architecture.
Elf64_Ehdr header;
FILE* file = fopen(elfFile, "rb");
if(file) {
fread(&header, 1, sizeof(header), file);
if (memcmp(header.e_ident, ELFMAG, SELFMAG) == 0) {
printf("Entry point from file: %p\n", (void *) header.e_entry);
entry_point = (void*)header.e_entry;
base_addr = (void*) ((long)&_start - (long)entry_point);
}
fclose(file);
}
}
/// print stacktrace
void bt() {
static const int MAX_STACK = 30;
void *array[MAX_STACK];
auto size = backtrace(array, MAX_STACK);
for (int i = 0; i < size; ++i) {
printf("%p ", (long)array[i]-(long)base_addr );
}
printf("\n");
}
int main(int argc, char* argv[])
{
read_elf_header(argv[0]);
printf("&_start = %p\n",&_start);
printf("base address is: %p\n", base_addr);
bt();
// elf header is also in memory, but to find it I have to already have base address
Elf64_Ehdr * ehdr_addr = (Elf64_Ehdr *) base_addr;
printf("Entry from memory: %p\n", (void *) ehdr_addr->e_entry);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
样本输出:
Entry point from file: 0x10c0
&_start = 0x5648eeb150c0
base address is: 0x5648eeb14000
0x1321 0x13ee 0x29540f8ed09b 0x10ea
Entry from memory: 0x10c0
Run Code Online (Sandbox Code Playgroud)
然后我可以
$ addr2line -e a.out 0x1321 0x13ee 0x29540f8ed09b 0x10ea
/tmp/elf2.c:30
/tmp/elf2.c:45
??:0
??:?
Run Code Online (Sandbox Code Playgroud)
没有访问权限,如何获得基本地址argv?我可能需要先打印跟踪main()(全局变量的初始化)。不能选择打开ASLR或PIE。
如何在不访问argv的情况下获取基址?我可能需要在main()之前打印跟踪
有几种方法:
/proc已安装(几乎总是这样),则可以从中读取ELF标头/proc/self/exe。dladdr1(),如Antti Haapala的答案所示。_r_debug.r_map,它指向已加载的ELF图像的链接列表。该列表中的第一个条目对应于a.out,并且l_addr包含您要查找的重定位。该解决方案等效于dladdr1,但不需要链接libdl。您能否提供3的示例代码?
当然:
#include <link.h>
#include <stdio.h>
extern char _start;
int main()
{
uintptr_t relocation = _r_debug.r_map->l_addr;
printf("relocation: %p, &_start: %p, &_start - relocation: %p\n",
(void*)relocation, &_start, &_start - relocation);
return 0;
}
gcc -Wall -fPIE -pie t.c && ./a.out
relocation: 0x555d4995e000, &_start: 0x555d4995e5b0, &_start - relocation: 0x5b0
Run Code Online (Sandbox Code Playgroud)
2和3是否都可移植?
我认为它们同样具有可移植性:dladdr1是Solaris上也存在的GLIBC扩展。_r_debug早于Linux,也可以在Solaris上运行(我实际上没有检查过,但我相信可以)。它也可以在其他ELF平台上运行。
这段代码产生与base_addrLinux 上相同的值:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <link.h>
Dl_info info;
void *extra = NULL;
dladdr1(&_start, &info, &extra, RTLD_DL_LINKMAP);
struct link_map *map = extra;
printf("%#llx", (unsigned long long)map->l_addr);
Run Code Online (Sandbox Code Playgroud)
手册dladdr1页显示以下内容RTLD_DL_LINKMAP:
RTLD_DL_LINKMAP获取指向匹配文件的链接映射的指针。extra_info 参数指向一个
link_map结构体指针(即struct link_map **),定义如下:Run Code Online (Sandbox Code Playgroud)struct link_map { ElfW(Addr) l_addr; /* Difference between the address in the ELF file and the address in memory */ char *l_name; /* Absolute pathname where object was found */ ElfW(Dyn) *l_ld; /* Dynamic section of the shared object */ struct link_map *l_next, *l_prev; /* Chain of loaded objects */ /* Plus additional fields private to the implementation */ };
请注意,-ldl需要链接动态加载例程。