跨平台二进制loader加载器自举寻址技术比对

admin 2026-03-12 22:44:15 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文档详述跨平台二进制加载器自举寻址技术。针对Windows阐述基于PEB遍历模块解析导出表的方法,针对Linux分析利用AuxiliaryVector与link_map获取符号地址的机制。文中对比标准与无库实现,提供地址修正逻辑与安全增强措施,并总结实战建议与Linux极简加载方案。 综合评分: 90 文章分类: 二进制安全,恶意软件,安全工具,逆向分析,实战经验


cover_image

跨平台二进制loader加载器自举寻址技术比对

原创

haidragon haidragon

安全狗的自我修养

2026年3月12日 12:33 湖南

官网:http://securitytech.cc

#

#

跨平台自举寻址技术说明文档

1. 核心背景

在开发高级 Loader、无文件攻击(Fileless)或安全研究工具时,程序往往无法预先知道系统库(如 Windows 的 kernel32.dll 或 Linux 的 libc.so)在内存中的确切位置。

技术挑战:

  • ASLR(Address Space Layout Randomization):现代操作系统在每次程序运行时随机化库文件的加载地址
  • 位置无关代码(PIC):shellcode 和反射式 DLL 需要在任意内存位置执行
  • 无文件场景:不依赖文件系统,直接从内存中解析 API 地址

解决方案:

  • Windows:依赖 PEB (Process Environment Block) 结构
  • Linux:依赖 Auxiliary Vector (auxv) 与 link_map 链表

2. Windows 寻址:基于 PEB 的链表爬行

2.1 PEB 结构概述

Windows 所有的模块信息都挂载在 PEB 下的 Ldr 成员中。开发者通过段寄存器(FS 或 GS)定位 PEB,随后遍历双向链表。

核心数据结构:

1. // PEB 结构(简化)
2. typedefstruct _PEB {
3. BYTE Reserved[0x18];
4. PVOID ImageBaseAddress;
5. PPEB_LDR_DATA Ldr;// 关键:加载器数据
6. // ...
7. } PEB,*PPEB;

9. // LDR 数据结构
10. typedefstruct _PEB_LDR_DATA {
11. LIST_ENTRY InLoadOrderModuleList;
12. LIST_ENTRY InMemoryOrderModuleList;// 常用
13. LIST_ENTRY InInitializationOrderModuleList;
14. } PEB_LDR_DATA,*PPEB_LDR_DATA;

16. // 模块表项
17. typedefstruct _LDR_DATA_TABLE_ENTRY {
18. LIST_ENTRY InMemoryOrderLinks;
19. PVOID DllBase;// 模块基址
20. PVOID EntryPoint;
21. SIZE_T SizeOfImage;
22. UNICODE_STRING FullDllName;// 完整路径
23. UNICODE_STRING BaseDllName;// 文件名
24. } LDR_DATA_TABLE_ENTRY,*PLDR_DATA_TABLE_ENTRY;

2.2 访问流程

步骤 1:定位 PEB

1. ; x64 架构
2. mov rax, gs:[0x60]; PEB 偏移量为0x60
3. ; x86 架构
4. mov eax, fs:[0x30]; PEB 偏移量为0x30

步骤 2:遍历模块链表

1. PPEB peb =(PPEB)__readgsqword(0x60);
2. PLDR_DATA_TABLE_ENTRY entry =
3. (PLDR_DATA_TABLE_ENTRY)peb->Ldr->InMemoryOrderModuleList.Flink;

5. do{
6. printf("Module: %wZ, Base: %p\n",
7. &entry->BaseDllName, entry->DllBase);
8. entry =(PLDR_DATA_TABLE_ENTRY)entry->InMemoryOrderLinks.Flink;
9. }while(entry != first_entry);

步骤 3:解析导出表获取 DllBase 后,按照 PE 格式解析导出目录:

1. PIMAGE_DOS_HEADER dos =(PIMAGE_DOS_HEADER)DllBase;
2. PIMAGE_NT_HEADERS nt =(PIMAGE_NT_HEADERS)((BYTE*)dos + dos->e_lfanew);
3. PIMAGE_EXPORT_DIRECTORY exp =(PIMAGE_EXPORT_DIRECTORY)(
4. (BYTE*)DllBase+ nt->OptionalHeader.DataDirectory[0].VirtualAddress);

6. // 遍历 ExportNameTable 查找函数
7. PDWORD names =(PDWORD)((BYTE*)DllBase+ exp->AddressOfNames);
8. for(DWORD i&nbsp;=0;&nbsp;i&nbsp;<&nbsp;exp->NumberOfNames;&nbsp;i++){
9. char*&nbsp;func_name&nbsp;=(char*)((BYTE*)DllBase+&nbsp;names[i]);
10. // 匹配目标函数名...
11. }

2.3 技术特点

| 特性 | 说明 | | — | — | | 可靠性 | PEB 是 Windows 内部结构,稳定性高 | | 隐蔽性 | 不触发 API 调用,避免 Hook 检测 | | 兼容性 | 适用于所有 Windows NT 系列系统 | | 检测风险 | EDR 可能监控 PEB 访问模式 |


Linux 提供两种主要方式获取模块信息:Auxiliary Vector 和 link_map 链表。

3.1 方法一:Auxiliary Vector(辅助向量)

auxv 结构:

1. typedefstruct{
2. Elf64_Word&nbsp;a_type;// 类型标识
3. union{
4. Elf64_Xword&nbsp;a_val;
5. void*a_ptr;
6. }&nbsp;a_un;
7. }Elf64_auxv_t;

9. // 关键类型
10. #define&nbsp;AT_PHDR &nbsp;&nbsp;3// Program Header 表地址
11. #define&nbsp;AT_PHNUM &nbsp;4// Program Header 数量
12. #define&nbsp;AT_BASE &nbsp;&nbsp;7// 解释器基址(动态链接器)
13. #define&nbsp;AT_ENTRY &nbsp;9// 程序入口点

访问流程(见 test2.c):

1. // 从栈上获取 auxv
2. size_t&nbsp;argc&nbsp;=*sp;
3. size_t*ptr&nbsp;=&nbsp;sp&nbsp;+1+&nbsp;argc&nbsp;+1;// 跳过 argv 和环境变量
4. while(*ptr&nbsp;!=0)&nbsp;ptr++;// 跳过环境变量
5. ptr++;// 现在指向 auxv

7. Elf64_auxv_t*auxv&nbsp;=(Elf64_auxv_t*)ptr;
8. for(;&nbsp;auxv->a_type&nbsp;!=&nbsp;AT_NULL;&nbsp;auxv++){
9. if(auxv->a_type&nbsp;==&nbsp;AT_PHDR)
10. phdr_v&nbsp;=&nbsp;auxv->a_un.a_val;
11. }

优点:

  • 不依赖任何库函数(nostdlib 友好)
  • 直接从内核获取信息
  • 适合 shellcode 和极简环境

核心结构:

1. struct&nbsp;link_map&nbsp;{
2. ElfW(Addr)&nbsp;l_addr;// 模块加载基址
3. char*l_name;// 模块名称
4. ElfW(Dyn)*l_ld;// 动态段指针
5. struct&nbsp;link_map&nbsp;*l_next;// 下一个模块
6. struct&nbsp;link_map&nbsp;*l_prev;// 上一个模块
7. // ...
8. };

10. struct&nbsp;r_debug&nbsp;{
11. int&nbsp;r_version;
12. struct&nbsp;link_map&nbsp;*r_map;// 模块链表头
13. ElfW(Addr)&nbsp;r_brk;
14. // ...
15. };

访问流程(见 test.c):

1. // 1. 从 _DYNAMIC 段找到 DT_DEBUG
2. ElfW(Dyn)*dyn&nbsp;=&nbsp;_DYNAMIC;
3. for(;&nbsp;dyn->d_tag&nbsp;!=&nbsp;DT_NULL;++dyn){
4. if(dyn->d_tag&nbsp;==&nbsp;DT_DEBUG){
5. debug_struct&nbsp;=(struct&nbsp;r_debug&nbsp;*)dyn->d_un.d_ptr;
6. break;
7. }
8. }

10. // 2. 遍历 link_map 链表
11. struct&nbsp;link_map&nbsp;*map=&nbsp;debug_struct->r_map;
12. while(map){
13. printf("MODULE: %s, BASE: 0x%lx\n",map->l_name,map->l_addr);
14. dump_module_symbols(map);
15. map=map->l_next;
16. }

3.3 符号表解析(核心修复逻辑)

动态段解析:

1. ElfW(Dyn)*dyn&nbsp;=map->l_ld;
2. for(;&nbsp;dyn&nbsp;&&&nbsp;dyn->d_tag&nbsp;!=&nbsp;DT_NULL;++dyn){
3. unsignedlong&nbsp;val&nbsp;=&nbsp;dyn->d_un.d_ptr;

5. // 关键:地址修正逻辑
6. // 如果 val 小于基址,说明是相对偏移,需要加上基址
7. // 对于主程序 (l_addr=0),特殊处理小地址
8. unsignedlong&nbsp;addr&nbsp;=&nbsp;val;
9. if(val&nbsp;<map->l_addr&nbsp;||(map->l_addr&nbsp;==0&&&nbsp;val&nbsp;<0x1000000)){
10. addr&nbsp;=map->l_addr&nbsp;+&nbsp;val;// 相对地址转绝对地址
11. }

13. if(dyn->d_tag&nbsp;==&nbsp;DT_SYMTAB)
14. symtab&nbsp;=(ElfW(Sym)*)addr;
15. if(dyn->d_tag&nbsp;==&nbsp;DT_STRTAB)
16. strtab&nbsp;=(char*)addr;
17. if(dyn->d_tag&nbsp;==&nbsp;DT_HASH)
18. hashtab&nbsp;=(ElfW(Word)*)addr;
19. }

符号过滤与地址计算:

1. for(size_t&nbsp;i&nbsp;=0;&nbsp;i&nbsp;<&nbsp;num_symbols;&nbsp;i++){
2. ElfW(Sym)*sym&nbsp;=&symtab[i];

4. // 只处理函数类型且有地址的符号
5. if(sym->st_value&nbsp;!=0&&&nbsp;ELF64_ST_TYPE(sym->st_info)==&nbsp;STT_FUNC){
6. constchar*name&nbsp;=&nbsp;safe_get_str(strtab,&nbsp;sym->st_name);
7. if(name){
8. // 计算绝对地址:基址 + 偏移
9. unsignedlong&nbsp;func_addr&nbsp;=map->l_addr&nbsp;+&nbsp;sym->st_value;
10. printf(" &nbsp; &nbsp;-> 0x%016lx | %s\n",&nbsp;func_addr,&nbsp;name);
11. }
12. }
13. }

3.4 安全增强措施

防止非法内存访问:

1. constchar*safe_get_str(constchar*strtab,ElfW(Word)&nbsp;index)
2. {
3. if(!strtab&nbsp;||&nbsp;index&nbsp;==0)
4. return&nbsp;NULL;
5. // 不要访问 0x10000 以下的地址(保护页)
6. if((size_t)(strtab&nbsp;+&nbsp;index)<0x10000)
7. return&nbsp;NULL;
8. return&nbsp;strtab&nbsp;+&nbsp;index;
9. }

防止无限循环:

1. size_t&nbsp;num_symbols&nbsp;=&nbsp;hashtab[1];
2. if(num_symbols&nbsp;>5000)
3. num_symbols&nbsp;=5000;// 强制上限

4. 实战对比:test.c vs test2.c

4.1 test.c – 标准实现

特点:

  • 使用标准 C 库(printf 等)
  • 依赖 _DYNAMIC 外部符号
  • 完善的错误处理和边界检查
  • 适合常规安全工具开发

编译命令:

1. gcc&nbsp;./test.c&nbsp;-o test

输出示例:

1. ======================================================================
2. MODULE:/lib/x86_64-linux-gnu/libc.so.6
3. BASE &nbsp;:0x7877ec400000
4. ======================================================================
5. [Symbolsfor/lib/x86_64-linux-gnu/libc.so.6]
6. ->0x00007877ec48ef70|&nbsp;fgetc
7. ->0x00007877ec49a3f0|&nbsp;pthread_attr_setscope
8. ...

4.2 test2.c – 无库实现(Shellcode 友好)

特点:

  • -nostdlib 编译,零依赖
  • 自定义 syscall 封装( my_syscall3
  • 自定义字符串函数( m_strcmpsys_print
  • 通过 auxv 定位主程序,再爬 link_map
  • 适合 shellcode 和反射式加载

编译命令:

1. gcc test2.c&nbsp;-o test&nbsp;-nostdlib&nbsp;-fPIC&nbsp;-fno-stack-protector&nbsp;-lc

关键差异:

1. // 自定义 syscall
2. staticinlinelong&nbsp;my_syscall3(long&nbsp;n,long&nbsp;a1,long&nbsp;a2,long&nbsp;a3)
3. {
4. long&nbsp;ret;
5. __asm__&nbsp;volatile("syscall"
6. :"=a"(ret)
7. :"a"(n),"D"(a1),"S"(a2),"d"(a3)
8. :"rcx","r11","memory");
9. return&nbsp;ret;
10. }

12. // 汇编入口点(绕过 CRT)
13. __asm__(
14. ".global _start\n"
15. "_start:\n"
16. "mov %rsp, %rdi\n"// 传递栈指针给 c_start
17. "and $-16, %rsp\n"// 栈对齐
18. "sub $8, %rsp\n"
19. "call c_start\n");

5. 跨平台抽象对比

| 特性 | Windows (PEB) | Linux (link_map) | | — | — | — | | 入口点 | GS:[0x60] (x64) | _DYNAMIC 或 auxv | | 核心结构 | PEB_LDR_DATA | structr_debug | | 模块链表 | InMemoryOrderModuleList | link_map->l_next | | 基址字段 | DllBase | l_addr | | 名称字段 | BaseDllName (UNICODE) | l_name (char*) | | 符号表 | PE Export Directory | ELF .dynsym | | 地址计算 | Base+RVA | l_addr+st_value |


6. 应用场景

6.1 高级 Loader 开发

1. // 伪代码示例
2. void*&nbsp;resolve_function(constchar*&nbsp;module_name,constchar*&nbsp;func_name){
3. #ifdef&nbsp;_WIN32
4. PPEB peb&nbsp;=&nbsp;get_peb();
5. void*&nbsp;module_base&nbsp;=&nbsp;find_module_by_name(peb,&nbsp;module_name);
6. return&nbsp;get_export_addr(module_base,&nbsp;func_name);
7. #else
8. struct&nbsp;link_map*map=&nbsp;get_link_map();
9. void*&nbsp;module_base&nbsp;=&nbsp;find_module_by_name(map,&nbsp;module_name);
10. return&nbsp;get_dynsym_addr(module_base,&nbsp;func_name);
11. #endif
12. }

6.2 无文件攻击模拟

  • 阶段 1:通过上述技术解析 GetProcAddress / dlsym
  • 阶段 2:动态获取所需 API 地址
  • 阶段 3:执行 payload,不落地文件

6.3 安全研究工具

  • API Hook 检测:对比 PEB/link_map 与实际调用地址
  • 内存扫描:遍历所有加载模块,搜索特征码
  • 反调试:检测调试器注入的异常模块

7. 注意事项与最佳实践

7.1 地址修正陷阱

问题: 动态段中的地址可能是相对偏移或绝对地址

解决(test.c/test2.c 通用逻辑):

1. unsignedlong&nbsp;addr&nbsp;=&nbsp;val;
2. if(val&nbsp;<map->l_addr&nbsp;||(map->l_addr&nbsp;==0&&&nbsp;val&nbsp;<0x1000000)){
3. addr&nbsp;=map->l_addr&nbsp;+&nbsp;val;// 相对地址转绝对
4. }

7.2 主程序特殊情况

  • 主可执行文件的 l_addr 通常为 0
  • 此时 small addresses (< 0x1000000) 应视为相对偏移

7.3 虚拟模块排除

1. // linux-vdso.so 是虚拟模块,没有实际文件映射
2. if(map->l_name&nbsp;&&&nbsp;strstr(map->l_name,"linux-vdso.so"))
3. return;// 跳过或特殊处理

7.4 符号表损坏防护

1. // 限制最大符号数
2. if(num_symbols&nbsp;>5000)
3. num_symbols&nbsp;=5000;

5. // 指针合法性检查
6. if((size_t)(strtab&nbsp;+&nbsp;index)<0x10000)
7. return&nbsp;NULL;

8. 总结

Windows PEB 方案:

  • ✅ 成熟稳定,文档丰富
  • ✅ 适用于所有 NT 系统
  • ⚠️ 容易被 EDR 监控

Linux link_map 方案:

  • ✅ 内核维护,信息准确
  • ✅ 支持 nostdlib 环境
  • ✅ 适合 shellcode 开发
  • ⚠️ 需注意地址修正逻辑

实战建议:

  1. 常规工具:使用 test.c 方式(标准 C 库)
  2. Shellcode:参考 test2.c(nostdlib + auxv)
  3. 跨平台:抽象统一的 API 解析接口
  4. 安全性:始终进行边界检查和指针验证

9. Linux 极简方案:syscall 自 loader(推荐)

9.1 为什么 Linux 不需要这么麻烦?

与 Windows 复杂的 PEB 结构不同,Linux 的 syscall 接口极其简洁。实际上,你根本不需要解析 auxv 或 link_map,直接使用 syscall() 加载内存并执行即可。

核心优势:

  • ✅ 无需符号解析:直接调用 syscall,不依赖库函数地址
  • ✅ 零依赖:不需要 libc,不需要动态链接器
  • ✅ 代码极简:几行汇编 + 几个 syscall 即可完成
  • ✅ 内核原生支持:syscall 是稳定接口,不会随发行版变化

9.2 syscall 自 loader 实现

最小化示例(纯汇编):

1. # shellcode_loader.s
2. .global&nbsp;_start

4. _start:
5. # 1. 直接从栈上获取参数(argc, argv, envp, auxv)
6. mov&nbsp;%rsp,%rdi

8. # 2. 调用内核 syscall(例如:mmap 分配内存)
9. mov $9,%rax &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# SYS_mmap
10. mov $0,%rdi &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# addr (0 = 让内核选择)
11. mov $4096,%rsi &nbsp; &nbsp; &nbsp;&nbsp;# length (4KB)
12. mov $3,%rdx &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# prot (PROT_READ | PROT_WRITE)
13. mov $0x22,%r10 &nbsp; &nbsp; &nbsp;&nbsp;# flags (MAP_PRIVATE | MAP_ANONYMOUS)
14. mov $-1,%r8 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# fd (-1 = 匿名映射)
15. mov $0,%r9 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# offset
16. syscall

18. # rax 现在包含分配的内存地址
19. # 可以直接写入 shellcode 并跳转执行
20. mov $payload,%rsi
21. mov&nbsp;%rsi,(%rax)# 复制 payload 到新内存

23. # 3. 修改内存权限为可执行
24. mov $10,%rax &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# SYS_mprotect
25. mov&nbsp;%rax,%rdi &nbsp; &nbsp; &nbsp; &nbsp;# addr
26. mov $4096,%rsi &nbsp; &nbsp; &nbsp;&nbsp;# length
27. mov $7,%rdx &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# prot (READ|WRITE|EXEC)
28. syscall

30. # 4. 跳转到 shellcode 执行
31. call&nbsp;*%rax

33. payload:
34. # 这里放置你的 shellcode
35. # 例如:execve("/bin/sh", ...)

编译与运行:

1. gcc&nbsp;-nostdlib&nbsp;-fPIC shellcode_loader.s&nbsp;-o loader
2. ./loader

9.3 C 语言封装版本(更实用)

1. // minimal_loader.c
2. #include<sys/syscall.h>
3. #include<unistd.h>

5. // 自定义 syscall 包装器(不依赖 libc)
6. staticinlinelong&nbsp;sys_mmap(void*addr,size_t&nbsp;len,int&nbsp;prot,int&nbsp;flags,int&nbsp;fd,off_t&nbsp;offset){
7. long&nbsp;ret;
8. __asm__&nbsp;volatile(
9. "mov $9, %%rax\n\t"
10. "syscall\n\t"
11. :"=a"(ret)
12. :"D"(addr),"S"(len),"d"(prot),"r"(flags),"r"(fd),"r"(offset)
13. :"rcx","r11","memory"
14. );
15. return&nbsp;ret;
16. }

18. staticinlinelong&nbsp;sys_mprotect(void*addr,size_t&nbsp;len,int&nbsp;prot){
19. long&nbsp;ret;
20. __asm__&nbsp;volatile(
21. "mov $10, %%rax\n\t"
22. "syscall\n\t"
23. :"=a"(ret)
24. :"D"(addr),"S"(len),"d"(prot)
25. :"rcx","r11","memory"
26. );
27. return&nbsp;ret;
28. }

30. staticinlinelong&nbsp;sys_write(int&nbsp;fd,constchar*buf,size_t&nbsp;count){
31. long&nbsp;ret;
32. __asm__&nbsp;volatile(
33. "mov $1, %%rax\n\t"
34. "syscall\n\t"
35. :"=a"(ret)
36. :"D"(fd),"S"(buf),"d"(count)
37. :"rcx","r11","memory"
38. );
39. return&nbsp;ret;
40. }

42. // Shellcode 示例:execve("/bin/sh")
43. unsignedchar&nbsp;shellcode[]=
44. "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57"
45. "\x54\x5f\x6a\x3b\x58\x99\x0f\x05";

47. void&nbsp;_start(){
48. // 1. 分配可执行内存
49. void*mem&nbsp;=(void*)sys_mmap(NULL,4096,&nbsp;PROT_READ&nbsp;|&nbsp;PROT_WRITE,
50. MAP_PRIVATE&nbsp;|&nbsp;MAP_ANONYMOUS,-1,0);

52. // 2. 复制 shellcode
53. for(int&nbsp;i&nbsp;=0;&nbsp;i&nbsp;<sizeof(shellcode)-1;&nbsp;i++){
54. ((char*)mem)[i]=&nbsp;shellcode[i];
55. }

57. // 3. 修改为可执行权限
58. sys_mprotect(mem,4096,&nbsp;PROT_READ&nbsp;|&nbsp;PROT_WRITE&nbsp;|&nbsp;PROT_EXEC);

60. // 4. 输出调试信息(可选)
61. sys_write(1,"[+] Executing shellcode...\n",27);

63. // 5. 跳转执行
64. ((void(*)())mem)();

66. // 6. 退出
67. __asm__&nbsp;volatile(
68. "mov $60, %rax\n\t"
69. "xor %rdi, %rdi\n\t"
70. "syscall\n\t"
71. );
72. }

74. // 汇编入口
75. __asm__(
76. ".global _start\n"
77. "_start:\n"
78. "and $-16, %rsp\n"// 栈对齐
79. "sub $8, %rsp\n"// 保留空间
80. "call _start\n"
81. );

编译命令:

1. gcc&nbsp;-nostdlib&nbsp;-fPIC&nbsp;-fno-stack-protector minimal_loader.c&nbsp;-o loader
2. strip loader &nbsp;# 去除符号表,减小体积
3. ./loader

9.4 对比:传统方式 vs syscall 自 loader

| 特性 | 传统方式(auxv/link_map) | syscall 自 loader | | — | — | — | | 复杂度 | 需要解析 ELF 结构、符号表 | 直接 syscall,无需解析 | | 代码量 | 100+ 行 | 30-50 行 | | 依赖 | 需要理解 ELF/DYNAMIC 段 | 只需知道 syscall 号 | | 灵活性 | 受限于符号表完整性 | 完全自由,可加载任意 shellcode | | 隐蔽性 | 可能被监控 link_map 访问 | 纯 syscall,更难检测 | | 兼容性 | 依赖 glibc 结构稳定性 | syscall 接口永久稳定 | | 适用场景 | 需要调用现有库函数 | 独立 payload、无文件攻击 |

9.5 实战示例:无文件执行 /bin/sh

完整代码(单文件,可直接编译):

1. // execve_shellcode.c
2. // gcc -nostdlib -fPIC execve_shellcode.c -o shell
3. #include<stdint.h>

5. // syscall 宏定义
6. #define&nbsp;SYS_mmap &nbsp; &nbsp;9
7. #define&nbsp;SYS_mprotect&nbsp;10
8. #define&nbsp;SYS_execve &nbsp;59
9. #define&nbsp;SYS_exit &nbsp; &nbsp;60

11. #define&nbsp;PROT_READ &nbsp;&nbsp;1
12. #define&nbsp;PROT_WRITE &nbsp;2
13. #define&nbsp;PROT_EXEC &nbsp;&nbsp;4
14. #define&nbsp;MAP_PRIVATE&nbsp;0x02
15. #define&nbsp;MAP_ANONYMOUS&nbsp;0x20

17. staticinlinelong&nbsp;syscall3(long&nbsp;num,long&nbsp;a1,long&nbsp;a2,long&nbsp;a3){
18. long&nbsp;ret;
19. __asm__&nbsp;volatile("syscall":"=a"(ret):"a"(num),"D"(a1),"S"(a2),"d"(a3):"rcx","r11","memory");
20. return&nbsp;ret;
21. }

23. void&nbsp;_start(){
24. // 分配内存
25. long&nbsp;mem&nbsp;=&nbsp;syscall3(SYS_mmap,0,4096,&nbsp;PROT_READ&nbsp;|&nbsp;PROT_WRITE);

27. // 构造 execve 参数(在栈上)
28. char*argv[2]={"/bin/sh",&nbsp;NULL};
29. char*envp[1]={NULL};

31. // 直接调用 execve(不需要 shellcode)
32. syscall3(SYS_execve,(long)argv[0],(long)argv,(long)envp);

34. // 失败则退出
35. syscall3(SYS_exit,1,0,0);
36. }

38. __asm__(".global _start\n_start: and $-16,%rsp; sub $8,%rsp; call _start\n");

编译运行:

1. gcc&nbsp;-nostdlib&nbsp;-fPIC execve_shellcode.c&nbsp;-o shell
2. ./shell &nbsp;# 直接获得 shell

9.6 进阶:反射式 ELF 加载器

如果需要加载完整的 ELF 文件到内存执行(类似反射式 DLL),可以这样:

1. // 伪代码框架
2. void&nbsp;load_elf_from_memory(uint8_t*elf_data){
3. Elf64_Ehdr*ehdr&nbsp;=(Elf64_Ehdr*)elf_data;
4. Elf64_Phdr*phdr&nbsp;=(Elf64_Phdr*)(elf_data&nbsp;+&nbsp;ehdr->e_phoff);

6. // 1. 遍历 Program Headers,加载 LOAD 段
7. for(int&nbsp;i&nbsp;=0;&nbsp;i&nbsp;<&nbsp;ehdr->e_phnum;&nbsp;i++){
8. if(phdr[i].p_type&nbsp;==&nbsp;PT_LOAD){
9. // mmap 分配内存
10. void*addr&nbsp;=&nbsp;mmap(phdr[i].p_vaddr,&nbsp;phdr[i].p_memsz,
11. PROT_READ&nbsp;|&nbsp;PROT_WRITE,
12. MAP_PRIVATE&nbsp;|&nbsp;MAP_ANONYMOUS,-1,0);
13. // 复制段数据
14. memcpy(addr,&nbsp;elf_data&nbsp;+&nbsp;phdr[i].p_offset,&nbsp;phdr[i].p_filesz);
15. }
16. }

18. // 2. 设置内存权限(根据 p_flags)
19. // 3. 跳转到入口点:ehdr->e_entry
20. ((void(*)())ehdr->e_entry)();
21. }

这种方式比解析 link_map 简单得多,因为:

  • 不需要查找符号表
  • 不需要处理重定位
  • 不需要关心动态链接器

9.7 总结:Linux 的优势

Windows 开发者羡慕的地方:

  1. syscall 数量少且稳定:Linux 只有 300+ 个 syscall,而 Windows API 成千上万
  2. 调用约定简单:参数通过寄存器传递(rdi, rsi, rdx, r10, r8, r9)
  3. 无需处理 PE 结构:ELF 更简洁,且内核直接支持
  4. 没有导入表限制:可以直接 syscall,不依赖 IAT
  5. ASLR 更容易绕过:内核分配的地址虽然随机,但 syscall 本身不受影响

最佳实践建议:

  • 如果是 独立 payload:直接用 syscall,不要解析任何结构
  • 如果需要 调用现有库函数:再使用 link_map/auxv 方案
  • 如果做 跨平台工具:Windows 用 PEB,Linux 用 syscall(而不是强行统一)

10. 最终对比与选型指南

10.1 技术路线对比总表

| 方案 | Windows PEB | Linux auxv/link_map | Linux syscall 自 loader | | — | — | — | — | | 复杂度 | 中等 | 高 | 极低 ⭐ | | 代码量 | ~80 行 | ~150 行 | ~30 行 ⭐ | | 稳定性 | 高(NT 内核稳定) | 中(依赖 glibc 结构) | 极高 (syscall 永久稳定)⭐ | | 隐蔽性 | 中(EDR 重点监控) | 中 | (纯 syscall)⭐ | | 灵活性 | 中(受 PE 结构限制) | 高 | 极高 (任意 shellcode)⭐ | | 学习曲线 | 陡峭(PE 结构复杂) | 陡峭(ELF+ 动态链接) | 平缓 (几个 syscall)⭐ | | 推荐指数 | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |

10.2 场景化选型建议

🎯 场景 1:无文件攻击/Shellcode 加载

  • Windows: PEB + GetProcAddress
  • Linuxsyscall 自 loader(强烈推荐)
1. // Linux 最佳实践
2. void*&nbsp;mem&nbsp;=&nbsp;mmap(NULL,4096,&nbsp;PROT_READ|PROT_WRITE,&nbsp;MAP_ANONYMOUS,-1,0);
3. memcpy(mem,&nbsp;shellcode,&nbsp;size);
4. mprotect(mem,4096,&nbsp;PROT_EXEC);
5. ((void(*)())mem)();

🎯 场景 2:调用系统库函数(如 printf、open)

  • Windows: PEB 解析 kernel32.dll 导出表
  • Linux: link_map 解析 libc.so 符号表
1. // Linux 次选方案(需要符号时使用)
2. struct&nbsp;link_map&nbsp;*map=&nbsp;get_link_map();
3. // 找到 libc,解析 printf 地址

🎯 场景 3:跨平台安全工具

1. #ifdef&nbsp;_WIN32
2. // Windows: PEB 方案
3. resolve_via_peb("kernel32.dll","CreateProcessA");
4. #else
5. // Linux: 优先 syscall,必要时 link_map
6. if(can_use_syscall()){
7. syscall_direct(...);
8. }else{
9. resolve_via_link_map("libc.so","execve");
10. }
11. #endif

🎯 场景 4:反射式加载器(Reflective Loader)

  • Windows: 手动 PE 加载 + 重定位处理
  • Linux: 手动 ELF 加载(比 link_map 简单)
1. // Linux 反射式 ELF 加载(简化版)
2. void&nbsp;load_elf(uint8_t*elf){
3. Elf64_Ehdr*ehdr&nbsp;=(Elf64_Ehdr*)elf;
4. // 遍历 PT_LOAD 段,mmap + memcpy
5. // 设置权限,跳转到 e_entry
6. ((void(*)())ehdr->e_entry)();
7. }

10.3 常见误区

❌ 误区 1: “Linux 必须解析 link_map 才能执行代码” ✅ 真相: syscall 可以直接分配内存并执行,完全不需要符号表

❌ 误区 2: “auxv 是获取模块信息的唯一方式” ✅ 真相: auxv 只在 nostdlib 时有用,常规场景 link_map 更简单

❌ 误区 3: “跨平台必须统一实现方式” ✅ 真相: Windows 用 PEB,Linux 用 syscall,各自最优才是真跨平台

❌ 误区 4: “syscall 不稳定,会随内核变化” ✅ 真相: Linux syscall ABI 向后兼容,比 glibc 稳定得多

10.4 性能对比

1. 测试:获取&nbsp;execve&nbsp;函数地址并调用

3. 方案&nbsp;A:&nbsp;link_map&nbsp;解析
4. -遍历&nbsp;_DYNAMIC&nbsp;段:~100次迭代
5. -解析&nbsp;DT_HASH/DT_SYMTAB:~50次计算
6. -字符串匹配:O(n)复杂度
7. -总耗时:~5000&nbsp;CPU&nbsp;周期

9. 方案&nbsp;B:&nbsp;syscall&nbsp;直接调用
10. -一条&nbsp;syscall&nbsp;指令
11. -总耗时:~100&nbsp;CPU&nbsp;周期

13. 结论:syscall&nbsp;方案快50倍+

10.5 检测与反检测

EDR/杀软检测手段:

| 检测点 | PEB 方案 | link_map 方案 | syscall 方案 | | — | — | — | — | | 内存扫描 | 可检测异常链表访问 | 可检测 link_map 读取 | 难以检测 | | API Hook | 可 Hook NtQueryInformationProcess | 可 Hook dlopen/dlsym | 无法 Hook(直接 syscall) | | 行为分析 | 可疑的 PEB 访问模式 | 异常的符号解析 | 正常 syscall 流 | | 静态特征 | PEB 偏移量特征码 | link_map 结构特征 | 无明显特征 |

反检测建议:

  1. Linux 优先 syscall:绕过所有用户态监控
  2. 混淆 syscall 号:不要硬编码 mov $9,%rax,用变量传递
  3. 减少特征码:避免使用常见的 shellcode 模板
  4. 动态计算:syscall 号通过计算得到(如 mov $15,%rbx;sub$6,%rbx 得到 9)

11. 实战代码仓库推荐

11.1 学习资源

  • test.c: 标准 link_map 实现(适合理解 ELF 动态链接)
  • test2.c: nostdlib + auxv 实现(适合 shellcode 环境)
  • minimal_loader.c: syscall 自 loader(推荐用于实际项目)

11.2 扩展阅读

  1. Linux syscall 表: https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_64.tbl
  2. ELF 规范: https://refspecs.linuxfoundation.org/elf/elf.pdf
  3. shellcode 编写指南: https://www.exploit-db.com/docs/linux
  4. 反调试技术: 使用 ptrace 自保护(syscall 303)

12. 总结陈词

💡 核心观点

Linux 开发者的福音:

“在 Windows 上你不得不解析复杂的 PEB 结构,但在 Linux 上,一条 syscall 就够了。”

技术本质:

  • Windows 的设计哲学是 “一切皆 API”,必须通过官方接口
  • Linux 的设计哲学是 “内核即服务”,syscall 就是契约

实战建议:

  1. 新手入门: 从 test.c 开始,理解 link_map 机制
  2. 进阶开发: 使用 syscall 自 loader,代码最简洁
  3. 跨平台项目: Windows 用 PEB,Linux 用 syscall,不要强行统一
  4. 安全研究: 理解所有三种方案,根据场景灵活选择

🚀 终极建议

如果你正在开发 Linux 平台的 loader 或安全工具:

1. 优先级排序:
2. 1️⃣&nbsp;syscall&nbsp;直接调用(最简单、最快、最隐蔽)
3. 2️⃣万不得已再用&nbsp;link_map(需要符号解析时)
4. 3️⃣&nbsp;auxv&nbsp;作为备选(nostdlib&nbsp;且&nbsp;syscall&nbsp;不可用时)

记住这句话:

“能用 syscall 解决的,就不要解析数据结构;能简单的,就不要复杂。”


测试代码1

// gcc ./test.c -o test// &nbsp;======================================================================// &nbsp;MODULE: [Main Image]// &nbsp;BASE &nbsp;: 0x5983655d2000// &nbsp;======================================================================// &nbsp; &nbsp;[!] 模块信息缺失,无法解析符号表// &nbsp;======================================================================// &nbsp;MODULE: linux-vdso.so.1// &nbsp;BASE &nbsp;: 0x7fff072cc000// &nbsp;======================================================================// &nbsp;======================================================================// &nbsp;MODULE: /lib/x86_64-linux-gnu/libc.so.6// &nbsp;BASE &nbsp;: 0x7877ec400000// &nbsp;======================================================================// &nbsp; &nbsp;[Symbols for /lib/x86_64-linux-gnu/libc.so.6]// &nbsp; &nbsp; &nbsp;-> 0x00007877ec48ef70 | fgetc// &nbsp; &nbsp; &nbsp;-> 0x00007877ec49a3f0 | pthread_attr_setscope// &nbsp; &nbsp; &nbsp;-> 0x00007877ec49a0a0 | pthread_attr_getstacksize// &nbsp; &nbsp; &nbsp;-> 0x00007877ec4b1480 | envz_strip// &nbsp; &nbsp; &nbsp;-> 0x00007877ec49a0a0 | pthread_attr_getstacksize// &nbsp; &nbsp; &nbsp;-> 0x00007877ec53dc40 | iruserok_af// &nbsp; &nbsp; &nbsp;-> 0x00007877ec56a010 | _nss_files_getpwent_r// &nbsp; &nbsp; &nbsp;-> 0x00007877ec4a38b0 | pthread_setcancelstate// &nbsp; &nbsp; &nbsp;-> 0x00007877ec44b3b0 | stdc_first_leading_one_ul// &nbsp; &nbsp; &nbsp;-> 0x00007877ec51d0b0 | cfmakeraw// &nbsp; &nbsp; &nbsp;-> 0x00007877ec544bb0 | ns_name_pack// &nbsp; &nbsp; &nbsp;-> 0x00007877ec544bb0 | ns_name_pack// &nbsp; &nbsp; &nbsp;-> 0x00007877ec496fb0 | _IO_iter_begin// &nbsp; &nbsp; &nbsp;-> 0x00007877ec4f98d0 | globfree// &nbsp; &nbsp; &nbsp;-> 0x00007877ec497390 | _IO_str_init_readonly// &nbsp; &nbsp; &nbsp;-> 0x00007877ec538410 | __vswprintf_chk// &nbsp; &nbsp; &nbsp;-> 0x00007877ec4a1c10 | pthread_mutexattr_getprotocol// &nbsp; &nbsp; &nbsp;-> 0x00007877ec4a1c10 | pthread_mutexattr_getprotocol// &nbsp; &nbsp; &nbsp;-> 0x00007877ec4d8fc0 | wmemmove// &nbsp; &nbsp; &nbsp;-> 0x00007877ec5685c0 | _nss_files_gethostbyname4_r// &nbsp; &nbsp; &nbsp;-> 0x00007877ec4a26d0 | __pthread_rwlock_rdlock// &nbsp; &nbsp; &nbsp;-> 0x00007877ec52c390 | __cmsg_nxthdr// &nbsp; &nbsp; &nbsp;-> 0x00007877ec55dbb0 | gethostbyname_r// &nbsp; &nbsp; &nbsp;-> 0x00007877ec579f00 | xdecrypt// &nbsp; &nbsp; &nbsp;-> 0x00007877ec568140 | _nss_files_gethostent_r// &nbsp; &nbsp; &nbsp;-> 0x00007877ec51bc40 | statvfs64// &nbsp; &nbsp; &nbsp;-> 0x00007877ec44b3f0 | stdc_first_leading_one_us// &nbsp; &nbsp; &nbsp;-> 0x00007877ec569b50 | _nss_files_setprotoent// &nbsp; &nbsp; &nbsp;-> 0x00007877ec561480 | getprotoent// &nbsp; &nbsp; &nbsp;-> 0x00007877ec56e370 | svcraw_create// &nbsp; &nbsp; &nbsp;-> 0x00007877ec4a8810 | mq_unlink// &nbsp; &nbsp; &nbsp;-> 0x00007877ec48b6a0 | _IO_free_wbackup_area// &nbsp; &nbsp; &nbsp;-> 0x00007877ec446210 | sigignore// &nbsp; &nbsp; &nbsp;-> 0x00007877ec4a2fe0 | __pthread_rwlock_trywrlock// &nbsp; &nbsp; &nbsp;-> 0x00007877ec4a8810 | mq_unlink// &nbsp; &nbsp; &nbsp;-> 0x00007877ec52aff0 | open_tree// &nbsp; &nbsp; &nbsp;-> 0x00007877ec574560 | clnt_spcreateerror// &nbsp; &nbsp; &nbsp;-> 0x00007877ec517450 | fstatvfs// &nbsp; &nbsp; &nbsp;-> 0x00007877ec577780 | pmap_getport// &nbsp; &nbsp; &nbsp;-> 0x00007877ec488a80 | vsscanf// &nbsp; &nbsp; &nbsp;-> 0x00007877ec4b1560 | memccpy// &nbsp; &nbsp; &nbsp;-> 0x00007877ec43a340 | nl_langinfo// &nbsp; &nbsp; &nbsp;-> 0x00007877ec527ba0 | syslog// &nbsp; &nbsp; &nbsp;-> 0x00007877ec4a7bb0 | lio_listio64// &nbsp; &nbsp; &nbsp;-> 0x00007877ec538780 | __wcstombs_chk// &nbsp;...#include&nbsp;<stdio.h>#include&nbsp;<elf.h>#include&nbsp;<link.h>#include&nbsp;<string.h>extern&nbsp;ElfW(Dyn) _DYNAMIC[];// 安全的字符串获取函数,防止非法内存访问const&nbsp;char&nbsp;*safe_get_str(const&nbsp;char&nbsp;*strtab, ElfW(Word) index){&nbsp; &nbsp;&nbsp;if&nbsp;(!strtab || index ==&nbsp;0)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;NULL;&nbsp; &nbsp;&nbsp;// 简单的指针合法性粗略判断:不要访问 0x10000 以下的地址&nbsp; &nbsp;&nbsp;if&nbsp;((size_t)(strtab + index) <&nbsp;0x10000)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;NULL;&nbsp; &nbsp;&nbsp;return&nbsp;strtab + index;}void&nbsp;dump_module_symbols(struct&nbsp;link_map *map){&nbsp; &nbsp;&nbsp;// 1. 绝对防御:排除虚拟模块和空指针&nbsp; &nbsp;&nbsp;if&nbsp;(!map || !map->l_ld)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return;&nbsp; &nbsp;&nbsp;if&nbsp;(map->l_name && strstr(map->l_name,&nbsp;"linux-vdso.so"))&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return;&nbsp; &nbsp; ElfW(Sym) *symtab =&nbsp;NULL;&nbsp; &nbsp;&nbsp;char&nbsp;*strtab =&nbsp;NULL;&nbsp; &nbsp; ElfW(Word) *hashtab =&nbsp;NULL;&nbsp; &nbsp;&nbsp;// 2. 解析动态段&nbsp; &nbsp; ElfW(Dyn) *dyn = map->l_ld;&nbsp; &nbsp;&nbsp;for&nbsp;(; dyn && dyn->d_tag != DT_NULL; ++dyn)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;unsigned&nbsp;long&nbsp;val = dyn->d_un.d_ptr;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(val ==&nbsp;0)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 核心修复逻辑:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 如果 val 大于基址,它可能是绝对地址。&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 如果 val 很小,它肯定是相对偏移,需要加上基址。&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 但对于主程序 (l_addr=0),我们要特别小心。&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;unsigned&nbsp;long&nbsp;addr = val;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(val < map->l_addr || (map->l_addr ==&nbsp;0&nbsp;&& val <&nbsp;0x1000000))&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 这是一个相对地址&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; addr = map->l_addr + val;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(dyn->d_tag == DT_SYMTAB)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; symtab = (ElfW(Sym) *)addr;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(dyn->d_tag == DT_STRTAB)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; strtab = (char&nbsp;*)addr;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(dyn->d_tag == DT_HASH)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; hashtab = (ElfW(Word) *)addr;&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;// 必须要表全了才敢开工&nbsp; &nbsp;&nbsp;if&nbsp;(!symtab || !strtab || !hashtab)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; printf(" &nbsp;[!] 模块信息缺失,无法解析符号表\n");&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return;&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;// SYSV Hash 表的第二项是符号总数&nbsp; &nbsp; size_t num_symbols = hashtab[1];&nbsp; &nbsp;&nbsp;if&nbsp;(num_symbols >&nbsp;5000)&nbsp; &nbsp; &nbsp; &nbsp; num_symbols =&nbsp;5000;&nbsp;// 强制上限,防止读取损坏的表导致无限循环&nbsp; &nbsp; printf(" &nbsp;[Symbols for %s]\n", map->l_name[0] ? map->l_name :&nbsp;"Main Program");&nbsp; &nbsp;&nbsp;for&nbsp;(size_t i =&nbsp;0; i < num_symbols; i++)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; ElfW(Sym) *sym = &symtab[i];&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 只有函数 (STT_FUNC) 且有地址 (st_value) 的才看&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(sym->st_value !=&nbsp;0&nbsp;&& ELF64_ST_TYPE(sym->st_info) == STT_FUNC)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;char&nbsp;*name = safe_get_str(strtab, sym->st_name);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(name)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 计算函数在内存中的绝对地址&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;unsigned&nbsp;long&nbsp;func_addr = map->l_addr + sym->st_value;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; printf(" &nbsp; &nbsp;-> 0x%016lx | %s\n", func_addr, name);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; printf("\n");}void&nbsp;scan_modules(){&nbsp; &nbsp;&nbsp;struct&nbsp;r_debug *debug_struct =&nbsp;NULL;&nbsp; &nbsp; ElfW(Dyn) *dyn = _DYNAMIC;&nbsp; &nbsp;&nbsp;for&nbsp;(; dyn->d_tag != DT_NULL; ++dyn)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(dyn->d_tag == DT_DEBUG)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; debug_struct = (struct&nbsp;r_debug *)dyn->d_un.d_ptr;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;if&nbsp;(!debug_struct || !debug_struct->r_map)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return;&nbsp; &nbsp;&nbsp;struct&nbsp;link_map *map = debug_struct->r_map;&nbsp; &nbsp;&nbsp;while&nbsp;(map)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; printf("======================================================================\n");&nbsp; &nbsp; &nbsp; &nbsp; printf("MODULE: %s\n", map->l_name[0] ? map->l_name :&nbsp;"[Main Image]");&nbsp; &nbsp; &nbsp; &nbsp; printf("BASE &nbsp;: 0x%lx\n", (unsigned&nbsp;long)map->l_addr);&nbsp; &nbsp; &nbsp; &nbsp; printf("======================================================================\n");&nbsp; &nbsp; &nbsp; &nbsp; dump_module_symbols(map);&nbsp; &nbsp; &nbsp; &nbsp; map = map->l_next;&nbsp; &nbsp; }}int&nbsp;main(){&nbsp; &nbsp; scan_modules();&nbsp; &nbsp;&nbsp;return&nbsp;0;}

测试代码2

// gcc test2.c -o test -nostdlib -fPIC -fno-stack-protector -lc; ./test// &nbsp;./test// [MODULE]: [Self]// BASE &nbsp; &nbsp;: 0x0000560ac4846000// [MODULE]: linux-vdso.so.1// BASE &nbsp; &nbsp;: 0x00007fff139ee000// &nbsp; &nbsp; -> 0x00007fff139eea70 | clock_gettime// &nbsp; &nbsp; -> 0x00007fff139ee7b0 | __vdso_gettimeofday// &nbsp; &nbsp; -> 0x00007fff139eedd0 | clock_getres// &nbsp; &nbsp; -> 0x00007fff139eedd0 | __vdso_clock_getres// &nbsp; &nbsp; -> 0x00007fff139ee7b0 | gettimeofday// &nbsp; &nbsp; -> 0x00007fff139eea40 | __vdso_time// &nbsp; &nbsp; -> 0x00007fff139eee70 | __vdso_sgx_enter_enclave// &nbsp; &nbsp; -> 0x00007fff139eea40 | time// &nbsp; &nbsp; -> 0x00007fff139eea70 | __vdso_clock_gettime// &nbsp; &nbsp; -> 0x00007fff139eee40 | __vdso_getcpu// &nbsp; &nbsp; -> 0x00007fff139eee40 | getcpu// [+] 扫描完成。退出程序。#include&nbsp;<elf.h>#include&nbsp;<stddef.h>#include&nbsp;<link.h>// --- 基础底层函数 ---static&nbsp;inline&nbsp;long&nbsp;my_syscall3(long&nbsp;n,&nbsp;long&nbsp;a1,&nbsp;long&nbsp;a2,&nbsp;long&nbsp;a3){&nbsp; &nbsp;&nbsp;long&nbsp;ret;&nbsp; &nbsp;&nbsp;__asm__&nbsp;volatile("syscall"&nbsp;:&nbsp;"=a"(ret) :&nbsp;"a"(n),&nbsp;"D"(a1),&nbsp;"S"(a2),&nbsp;"d"(a3) :&nbsp;"rcx",&nbsp;"r11",&nbsp;"memory");&nbsp; &nbsp;&nbsp;return&nbsp;ret;}void&nbsp;sys_print(const&nbsp;char&nbsp;*s){&nbsp; &nbsp;&nbsp;if&nbsp;(!s)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return;&nbsp; &nbsp;&nbsp;size_t&nbsp;len =&nbsp;0;&nbsp; &nbsp;&nbsp;while&nbsp;(s[len])&nbsp; &nbsp; &nbsp; &nbsp; len++;&nbsp; &nbsp;&nbsp;my_syscall3(1,&nbsp;1, (long)s, len);}void&nbsp;sys_print_hex(size_t&nbsp;val){&nbsp; &nbsp;&nbsp;char&nbsp;buf[19] =&nbsp;"0x0000000000000000";&nbsp; &nbsp;&nbsp;const&nbsp;char&nbsp;*hex =&nbsp;"0123456789abcdef";&nbsp; &nbsp;&nbsp;for&nbsp;(int&nbsp;i =&nbsp;17; i >=&nbsp;2; i--)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; buf[i] = hex[val &&nbsp;0xf];&nbsp; &nbsp; &nbsp; &nbsp; val >>=&nbsp;4;&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;sys_print(buf);}int&nbsp;m_strcmp(const&nbsp;char&nbsp;*s1,&nbsp;const&nbsp;char&nbsp;*s2){&nbsp; &nbsp;&nbsp;while&nbsp;(*s1 && (*s1 == *s2))&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; s1++;&nbsp; &nbsp; &nbsp; &nbsp; s2++;&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;return&nbsp;*(unsigned&nbsp;char&nbsp;*)s1 - *(unsigned&nbsp;char&nbsp;*)s2;}// --- 符号解析核心 ---// 遍历并打印模块的所有导出函数void&nbsp;dump_all_symbols(struct&nbsp;link_map *map){&nbsp; &nbsp;&nbsp;if&nbsp;(!map->l_ld)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return;&nbsp; &nbsp; Elf64_Sym *symtab =&nbsp;NULL;&nbsp; &nbsp;&nbsp;char&nbsp;*strtab =&nbsp;NULL;&nbsp; &nbsp;&nbsp;uint32_t&nbsp;*hashtab =&nbsp;NULL;&nbsp; &nbsp;&nbsp;// 解析 DYNAMIC 段&nbsp; &nbsp; Elf64_Dyn *dyn = map->l_ld;&nbsp; &nbsp;&nbsp;for&nbsp;(; dyn->d_tag != DT_NULL; dyn++)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 在 nostdlib 模式下,地址修正逻辑非常关键&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;unsigned&nbsp;long&nbsp;val = dyn->d_un.d_ptr;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 如果地址明显是一个偏移量,则加上基址&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;unsigned&nbsp;long&nbsp;addr = (val < map->l_addr || (map->l_addr ==&nbsp;0&nbsp;&& val <&nbsp;0x1000000)) ? (map->l_addr + val) : val;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(dyn->d_tag == DT_SYMTAB)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; symtab = (Elf64_Sym *)addr;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(dyn->d_tag == DT_STRTAB)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; strtab = (char&nbsp;*)addr;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(dyn->d_tag == DT_HASH)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; hashtab = (uint32_t&nbsp;*)addr;&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;if&nbsp;(!symtab || !strtab || !hashtab)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return;&nbsp; &nbsp;&nbsp;// 获取符号总数 (DT_HASH 第二项)&nbsp; &nbsp;&nbsp;uint32_t&nbsp;num_symbols = hashtab[1];&nbsp; &nbsp;&nbsp;// 限制一下,防止扫描过大模块时刷屏太猛&nbsp; &nbsp;&nbsp;if&nbsp;(num_symbols >&nbsp;2000)&nbsp; &nbsp; &nbsp; &nbsp; num_symbols =&nbsp;2000;&nbsp; &nbsp;&nbsp;for&nbsp;(uint32_t&nbsp;i =&nbsp;0; i < num_symbols; i++)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; Elf64_Sym *sym = &symtab[i];&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 过滤:必须是函数 (STT_FUNC),且有名称,且有地址&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(ELF64_ST_TYPE(sym->st_info) == STT_FUNC && sym->st_name !=&nbsp;0&nbsp;&& sym->st_value !=&nbsp;0)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;const&nbsp;char&nbsp;*name = strtab + sym->st_name;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;sys_print(" &nbsp; &nbsp;-> ");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;sys_print_hex(map->l_addr + sym->st_value);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;sys_print(" | ");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;sys_print(name);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;sys_print("\n");&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }}void&nbsp;c_start(size_t&nbsp;*sp){&nbsp; &nbsp;&nbsp;// 1. 获取 Auxv&nbsp; &nbsp;&nbsp;size_t&nbsp;argc = *sp;&nbsp; &nbsp;&nbsp;size_t&nbsp;*ptr = sp +&nbsp;1&nbsp;+ argc +&nbsp;1;&nbsp; &nbsp;&nbsp;while&nbsp;(*ptr !=&nbsp;0)&nbsp; &nbsp; &nbsp; &nbsp; ptr++;&nbsp; &nbsp; ptr++;&nbsp; &nbsp; Elf64_auxv_t *auxv = (Elf64_auxv_t *)ptr;&nbsp; &nbsp;&nbsp;size_t&nbsp;phdr_v =&nbsp;0, phnum =&nbsp;0;&nbsp; &nbsp;&nbsp;for&nbsp;(; auxv->a_type != AT_NULL; auxv++)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(auxv->a_type == AT_PHDR)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; phdr_v = auxv->a_un.a_val;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(auxv->a_type == AT_PHNUM)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; phnum = auxv->a_un.a_val;&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;// 2. 找到主程序的 DYNAMIC&nbsp; &nbsp; Elf64_Phdr *phdr = (Elf64_Phdr *)phdr_v;&nbsp; &nbsp;&nbsp;size_t&nbsp;load_bias =&nbsp;0;&nbsp; &nbsp; Elf64_Dyn *dyn_ptr =&nbsp;NULL;&nbsp; &nbsp;&nbsp;for&nbsp;(size_t&nbsp;i =&nbsp;0; i < phnum; i++)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(phdr[i].p_type == PT_PHDR)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; load_bias = (size_t)phdr - phdr[i].p_vaddr;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(phdr[i].p_type == PT_DYNAMIC)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dyn_ptr = (Elf64_Dyn *)(phdr[i].p_vaddr + load_bias);&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;// 3. 找到 DT_DEBUG 爬 link_map&nbsp; &nbsp;&nbsp;struct&nbsp;r_debug&nbsp;*r_dbg =&nbsp;NULL;&nbsp; &nbsp;&nbsp;for&nbsp;(; dyn_ptr && dyn_ptr->d_tag != DT_NULL; dyn_ptr++)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(dyn_ptr->d_tag == DT_DEBUG)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; r_dbg = (struct&nbsp;r_debug *)dyn_ptr->d_un.d_ptr;&nbsp; &nbsp;&nbsp;if&nbsp;(r_dbg && r_dbg->r_map)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;struct&nbsp;link_map&nbsp;*map = r_dbg->r_map;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;while&nbsp;(map)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;sys_print("\n[MODULE]: ");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;sys_print(map->l_name[0] ? map->l_name :&nbsp;"[Self]");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;sys_print("\nBASE &nbsp; &nbsp;: ");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;sys_print_hex(map->l_addr);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;sys_print("\n");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 遍历并打印符号&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;dump_all_symbols(map);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; map = map->l_next;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;sys_print("\n[+] 扫描完成。退出程序。\n");&nbsp; &nbsp;&nbsp;my_syscall3(60,&nbsp;0,&nbsp;0,&nbsp;0);&nbsp;// exit(0)}// 汇编入口__asm__(&nbsp; &nbsp;&nbsp;".global _start\n"&nbsp; &nbsp;&nbsp;"_start:\n"&nbsp; &nbsp;&nbsp;"mov %rsp, %rdi\n"&nbsp; &nbsp;&nbsp;"and $-16, %rsp\n"&nbsp; &nbsp;&nbsp;"sub $8, %rsp\n"&nbsp; &nbsp;&nbsp;"call c_start\n");
  • 公众号:安全狗的自我修养
  • vx:2207344074
  • http://gitee.com/haidragon
  • http://github.com/haidragon
  • bilibili:haidragonx

#


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:安全狗的自我修养 haidragon haidragon《跨平台二进制loader加载器自举寻址技术比对》

评论:0   参与:  0