我偶然发现了这个内核页面:https://www.kernel.org/doc/html/v5.9/x86/x86_64/fsgs.html,并想try 一下LD_PRELOAD,看看是否可以在加载时分配一个值,并在运行时读取段寄存器来加载值.

例如,我有两个简单的代码,可以轻松地编译和执行来演示(我必须修改内核站点中提供的示例代码的一小部分):

load.cgcc -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.cgcc -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.cgcc -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-&gt;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.

推荐答案

movl %gs, %eax读取16位 Select 器值(GDT的索引),从零扩展到32位.(https://felixcloutier.com/x86/mov).

您不是在实模式下,所以这与段基无关,64位内核将只为ds/es/fs/gs使用空 Select 符(0),只为CS和SS设置段描述符,x86-64仍然需要这些段描述符才能使机器满意(并告诉它它处于64位长模式).

You need 100 or a system call (since reading an MSR is a privileged operation, not available directly in user-space.)在64位模式下,为FS或GS设置64位段基础的机制是通过写入MSR,而不是创建GDT或LDT条目并执行mov %eax, %gs.(在64位模式下,其他段基准固定为0.)

我在How to access segment register without linking libc.so?上的回答显示了如何使用Linux arch_prctl系统调用编写FS,这是没有FSGSBASE扩展的CPU上的唯一选项,也是一个允许它供用户空间使用的内核.(后者比第一个支持wrfsbase/rdfsbase等的芯片要晚得多).具有不同参数的相同系统调用可以读取段基址.

C++相关问答推荐

C限制限定符是否可以通过指针传递?

如何将FileFilter添加到FileDialog GTK 4

为什么PLT表中没有push指令?

字符数组,字符指针,在一种情况下工作,但在另一种情况下不工作?

通过MQTT/蚊子发送大文件—限制在4MB

使用单个字节内的位字段

数据包未从DPDK端口传输到内核端口

在C++中头文件中声明外部 struct

以下声明和定义之间的区别

将uintptr_t添加到指针是否对称?

我怎么才能用GCC编译一个c库,让它包含另一个库呢?

如何在STM8项目中导入STM8S/A标准外设库(ST VisualDeveloper)?

如何使解释器存储变量

如何在GET_STRING输入后对少数几个特定字符串进行C判断?

int * 指向int的哪个字节?

为什么我的二叉树删除删除整个左部分的树?

有没有办法减少C语言中线程的堆大小?

为什么会出现此错误?二进制表达式的操作数无效

如何在C中处理流水线中的a、n命令?

函数的typedef是标准 C 语法吗?它与函数指针的typedef有何不同?