我偶然发现了这个内核页面:https://www.kernel.org/doc/html/v5.9/x86/x86_64/fsgs.html,并想try 一下LD_PRELOAD
,看看是否可以在加载时分配一个值,并在运行时读取段寄存器来加载值.
例如,我有两个简单的代码,可以轻松地编译和执行来演示(我必须修改内核站点中提供的示例代码的一小部分):
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;
}
Upon executing the comm和: > LD_PRELOAD=$PWD/load.so ./main
FSGSBASE enabled
From main: 4
Hello World!
因此,现在我可以在运行时成功加载值(sed
按照内核页面中的指示修改汇编代码以读取%gs
寄存器)
Next, I wanted to do something more interesting: store the base address of a list in the segment register %gs
和 access this address at runtime.
例如,我像这样修改了上面的load
个代码(我只是添加了两行,但为了完整起见):
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);
}
然而,将指向列表的指针地址加载到段寄存器与加载值不同(我的第一个示例).因此,我想知道我应该如何解决try 读取包含指向列表的指针的段寄存器的问题.
我try 通过使用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
寄存器加载时为空:
pwndbg> i r eax
rax 0x0 0
通过手动修改汇编代码,我还try 将%gs
寄存器中的值简单地mov
写入%eax
寄存器中(不取消引用
movl %gs:0, %eax
->;movl %gs, %eax
But to no avail, 和 now I'm wondering whether there might be some other assembly codes I may need to add 和 trying to get some clue on how to go from this.
我认为这可能很有趣,因为如果我可以在运行时获得列表的基地址,我很可能会使用指针算法来访问在加载时分配的列表的不同元素.
FWIW here is my OS information 和 architecture:
> 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
I appreciate any insights, 和 please let me know if I'm unclear in any parts of my question.
编辑:感谢@Peter Cordes;我想您可以改为添加以下汇编指令:
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
在gdb中如下所示:
*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
.