返回

如何在 ELF 文件中计算 PLT 的虚拟地址并检测篡改

Linux

利用 sh_addr 计算 PLT 虚拟地址

计算 PLT 虚拟地址

当 ELF 文件加载到内存时,它的可执行代码段(即 PLT)会被分配一个虚拟地址。要计算 PLT 的虚拟地址,我们需要将 PLT 的 sh_addr 与动态链接器用于加载 ELF 文件的加载基址相加。

确定加载基址

加载基址存储在 ELF 文件头中的 e_phoff 字段中。它通常是一个随机地址,位于 0x004000000x04000000 之间。

读取程序头表

程序头表包含有关 ELF 文件各部分的信息。其中,PT_LOAD 程序头定义了可执行代码段的加载地址。

查找 PT_LOAD 程序头

通过遍历程序头表,我们可以找到类型为 PT_LOAD 的程序头,它包含 PLT 的加载地址。

计算 PLT 虚拟地址

最后,我们将 PLT 的 sh_addr 与加载基址相加,得到 PLT 的虚拟地址。

PLT 虚拟地址 = PLT 的 sh_addr + 加载基址

示例代码

#include <stdio.h>
#include <stdlib.h>
#include <elf.h>

int main(int argc, char **argv) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <ELF file>\n", argv[0]);
        return EXIT_FAILURE;
    }

    // 读取 ELF 文件头
    FILE *fp = fopen(argv[1], "rb");
    if (fp == NULL) {
        perror("fopen");
        return EXIT_FAILURE;
    }

    Elf64_Ehdr elf_hdr;
    if (fread(&elf_hdr, sizeof(elf_hdr), 1, fp) != 1) {
        perror("fread");
        fclose(fp);
        return EXIT_FAILURE;
    }

    // 校验 ELF 文件类型
    if (elf_hdr.e_ident[EI_MAG0] != ELFMAG0 ||
        elf_hdr.e_ident[EI_MAG1] != ELFMAG1 ||
        elf_hdr.e_ident[EI_MAG2] != ELFMAG2 ||
        elf_hdr.e_ident[EI_MAG3] != ELFMAG3) {
        fprintf(stderr, "Invalid ELF file\n");
        fclose(fp);
        return EXIT_FAILURE;
    }

    // 获取程序头表偏移量
    off_t ph_offset = elf_hdr.e_phoff;

    // 读取程序头表
    Elf64_Phdr *phdr_table = malloc(elf_hdr.e_phnum * sizeof(Elf64_Phdr));
    if (phdr_table == NULL) {
        perror("malloc");
        fclose(fp);
        return EXIT_FAILURE;
    }

    if (fseek(fp, ph_offset, SEEK_SET) != 0) {
        perror("fseek");
        fclose(fp);
        free(phdr_table);
        return EXIT_FAILURE;
    }

    if (fread(phdr_table, sizeof(Elf64_Phdr), elf_hdr.e_phnum, fp) != elf_hdr.e_phnum) {
        perror("fread");
        fclose(fp);
        free(phdr_table);
        return EXIT_FAILURE;
    }

    // 查找 PT_LOAD 程序头
    Elf64_Phdr *pt_load_phdr = NULL;
    for (int i = 0; i < elf_hdr.e_phnum; i++) {
        if (phdr_table[i].p_type == PT_LOAD) {
            pt_load_phdr = &phdr_table[i];
            break;
        }
    }

    // 未找到 PT_LOAD 程序头
    if (pt_load_phdr == NULL) {
        fprintf(stderr, "PT_LOAD program header not found\n");
        fclose(fp);
        free(phdr_table);
        return EXIT_FAILURE;
    }

    // 计算 PLT 的虚拟地址
    uint64_t plt_virtual_address = pt_load_phdr->p_vaddr + elf_hdr.e_shoff;

    // 打印 PLT 的虚拟地址
    printf("PLT virtual address: 0x%lx\n", plt_virtual_address);

    // 释放内存
    fclose(fp);
    free(phdr_table);

    return EXIT_SUCCESS;
}

检测 PLT 或 GOT 的篡改

共享库攻击

攻击者可以篡改共享库中的 PLT 或 GOT(执行挂钩),以劫持代码执行。要检测这种类型的攻击,我们可以比较内存中的 PLT 或 GOT 与 ELF 文件中存储的预期值。

检测方法

  1. 解析进程内存中的 ELF 文件。
  2. 获取 PLT 或 GOT 节的 sh_addr
  3. 计算 PLT 或 GOT 的虚拟地址。
  4. 访问内存中的 PLT 或 GOT。
  5. 将内存中的 PLT 或 GOT 与 ELF 文件中存储的 PLT 或 GOT 进行比较。

示例代码

#include <stdio.h>
#include <stdlib.h>
#include <elf.h>
#include <sys/mman.h>

int main(int argc, char **argv) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <ELF file>\n", argv[0]);
        return EXIT_FAILURE;
    }

    // 打开 ELF 文件
    FILE *elf_fp = fopen(argv[1], "rb");
    if (elf_fp == NULL) {
        perror("fopen");
        return EXIT_FAILURE;
    }

    // 获取 ELF 文件大小
    fseek(elf_fp, 0, SEEK_END);
    size_t elf_size = ftell(elf_fp);
    rewind(elf_fp);

    // 将 ELF 文件映射到内存
    void *elf_mem = mmap(NULL, elf_size, PROT_READ, MAP_PRIVATE, fileno(elf_fp), 0);
    if (elf_mem == MAP_FAILED) {
        perror("mmap");
        fclose(elf_fp);
        return EXIT_FAILURE;
    }

    // 解析 ELF 文件头
    Elf64_Ehdr *elf_hdr = (Elf64_Ehdr *)elf_mem;

    // 校验 ELF 文件类型
    if (elf_hdr->e_ident[EI_MAG0] != ELFMAG0 ||
        elf_hdr->e_ident[EI_MAG1] != ELFMAG1 ||
        elf_hdr->e_ident[EI_MAG2] != ELFMAG2 ||
        elf_hdr->e_ident[EI_MAG3] != ELFMAG3) {
        fprintf(stderr, "Invalid ELF file\n");
        fclose(elf_fp);
        munmap(elf_mem, elf_size);
        return EXIT_FAILURE;
    }

    // 获取程序头表偏移量
    off_t ph_offset = elf_hdr->e_phoff;

    // 读取程序头表
    Elf64_Phdr *phdr_table = (Elf64_Phdr *)(elf_mem + ph_offset);

    // 查找 PT_LOAD 程序头
    Elf64_Phdr *pt_load_phdr = NULL;
    for (int i = 0; i < elf_hdr->e_phnum; i++) {
        if (phdr_table[i].p_type == PT_LOAD) {
            pt_load_phdr = &phdr_table[i];
            break;
        }
    }

    // 未找到 PT_LOAD 程序头
    if (pt_load_phdr == NULL) {
        fprintf(stderr, "PT_LOAD program header not found\n");
        fclose(elf_fp);
        munmap(elf_mem, elf_size);
        return EXIT_FAILURE;
    }

    // 计算 PLT 的虚拟地址
    uint64_t plt_virtual_address = pt_load_phdr->p_vaddr + elf_hdr->e_shoff;

    // 获取 PLT 节的 sh_addr
    Elf64_Shdr *plt_shdr = (Elf64_Shdr *)(elf_mem + elf_hdr->e_shstrndx);
    uint64_t plt_sh_addr = plt_shdr->sh_addr;

    // 访问内存中的 PLT
    uint64_t *plt_mem = (uint64_