如何读取存储列表指针的x86_64 %gs段寄存器并访问列表?
问题:通过LD_PRELOAD结合FSGSBASE存储并访问数组指针
背景与成功验证案例
我研究x86_64的FSGSBASE特性,希望通过LD_PRELOAD在程序加载阶段给%gs段寄存器赋值,运行时从该寄存器读取数据。
先编写了两段代码验证基础思路:
- load.c 编译命令:
gcc -Wall -fPIC -shared -o load.so load.c -ldl -mfsgsbase
#include <sys/auxv.h> #include <elf.h> #include <immintrin.h> #include <stdio.h> #include <sys/mman.h> #include <unistd.h> /* Will be eventually in asm/hwcap.h */ #ifndef HWCAP2_FSGSBASE #define HWCAP2_FSGSBASE (1 << 1) #endif #define _GNU_SOURCE void __attribute__((constructor)) load() { int a = 4; unsigned val = getauxval(AT_HWCAP2); if (val & HWCAP2_FSGSBASE) printf("FSGSBASE enabled\n"); int *addr_a = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (addr_a == MAP_FAILED) { fprintf(stderr, "mmap() failed\n"); exit(EXIT_FAILURE); } *addr_a = a; _writegsbase_u64(addr_a); }
- main.c 编译命令:
gcc -S main.c -o main.s && sed -i 's/-4(%rbp)/%gs:0x0/g' main.s && gcc main.s -o main
#include <stdio.h> int main() { int a; printf("From main: %d\n", a); printf("Hello World!\n"); return 0; }
执行命令:LD_PRELOAD=$PWD/load.so ./main,输出如下:
FSGSBASE enabled From main: 4 Hello World!
此时已成功通过运行时读取%gs寄存器获取加载时分配的值(sed用于修改汇编代码,按要求读取%gs)。
遇到的问题:存储数组指针后无法正确访问
接下来尝试将数组的基地址存储到%gs段寄存器,运行时访问该数组。
修改后的load_updated.c,编译命令:gcc -Wall -fPIC -shared -o load_updated.so load_updated.c -ldl -mfsgsbase
void __attribute__((constructor)) load() { int a = 4; unsigned val = getauxval(AT_HWCAP2); if (val & HWCAP2_FSGSBASE) printf("FSGSBASE enabled\n"); int *addr_a = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (addr_a == MAP_FAILED) { fprintf(stderr, "mmap() failed\n"); exit(EXIT_FAILURE); } *addr_a = a; int *table[] = {addr_a}; printf("Table base address: %p\n", &table); _writegsbase_u64(&table); }
但运行时无法正确读取该指针并访问数组。通过gdb调试:
gdb ./main pwndbg> set environment LD_PRELOAD ./load_updated.so pwndbg> b main Breakpoint 1 at 0x1171 pwndbg> start FSGSBASE enabled Table base address: 0x7fffffffd880 0x7ffff7fb5000 pwndbg> i r $gs_base gs_base 0x7fffffffd880 140737488345216
可以确认%gs寄存器已加载正确的数组基地址,但执行到如下汇编指令时:
0x555555555171 <main+8> sub rsp, 0x10 ► 0x555555555175 <main+12> mov eax, dword ptr gs:[0] 0x55555555517d <main+20> mov esi, eax
尝试将%gs指向的地址解引用到%eax寄存器时,%eax的值为0:
pwndbg> i r eax rax 0x0 0
尝试直接将%gs寄存器的值移动到%eax(不解引用),修改汇编代码将movl %gs:0, %eax改为movl %gs, %eax,仍未解决问题。
进一步尝试后的问题
按照提示添加汇编指令:
rdgsbase %rax movq 0x0(%rax), %rax
虽然能通过这种方式读取%gs寄存器,但寄存器中存储的地址并未包含预期的数组,而是垃圾值。例如在gdb中:
pwndbg> set environment LD_PRELOAD ./load.so pwndbg> b main Breakpoint 1 at 0x1171 pwndbg> start Temporary breakpoint 2 at 0x1171 Table addr: (base address) 0x7fffffffd870 (*table[0]) 0x7ffff7ffa000
汇编执行时:
*RAX 0x7fffffffd870 ◂— 0x86d11c8e53b3e43 ──────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────── 0x555555555171 <main+8> sub rsp, 0x10 0x555555555175 <main+12> rdgsbase rax ► 0x55555555517a <main+17> mov rax, qword ptr [rax + 0x40]
可见0x7fffffffd870并未包含预期的0x7ffff7ffa000,而是垃圾值0x86d11c8e53b3e43。
我的操作系统及架构信息:
> uname -a Linux pop-os 6.2.6-76060206-generic #202303130630~1685473338~22.04~995127e SMP PREEMPT_DYNAMIC Tue M x86_64 x86_64 x86_64 GNU/Linux
现寻求技术指导,解决如何正确读取存储数组指针的%gs段寄存器并访问数组的问题。
内容的提问来源于stack exchange,提问作者Jay
相关产品推荐
相关产品推荐

