You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否将任意程序用作库?ELF及Windows平台跳转执行方案问询

解决方案:在自定义代码中准备变量后跳转至目标程序指定位置

针对你需要先在自定义代码中准备变量,再跳转至编译好的ELF/Windows PE程序指定位置的需求,以下是两种平台下的可行方案,替代反编译重编译的低效方法:


ELF 平台(Linux/Unix)

方案1:动态加载目标ELF(适用于有导出符号的场景)

利用dlopen/dlsym系列函数加载目标ELF,直接获取目标变量和函数的地址,赋值后跳转执行。这种方法无需手动解析ELF结构,适合有符号导出的程序。

#include <dlfcn.h>
#include <stdio.h>

int main() {
    // 1. 在自定义代码中准备变量
    int prepared_val = 42;

    // 2. 加载目标ELF(支持PIE/非PIE)
    void* elf_handle = dlopen("./target_program.elf", RTLD_NOW);
    if (!elf_handle) {
        fprintf(stderr, "加载失败: %s\n", dlerror());
        return 1;
    }

    // 3. 获取目标程序的全局变量地址并赋值
    int* target_global_var = dlsym(elf_handle, "target_var");
    if (!target_global_var) {
        fprintf(stderr, "获取变量地址失败: %s\n", dlerror());
        dlclose(elf_handle);
        return 1;
    }
    *target_global_var = prepared_val;

    // 4. 获取目标函数/标签地址并跳转执行
    void (*target_entry)() = dlsym(elf_handle, "target_function");
    if (!target_entry) {
        fprintf(stderr, "获取入口地址失败: %s\n", dlerror());
        dlclose(elf_handle);
        return 1;
    }
    target_entry(); // 直接调用等价于跳转至目标位置

    dlclose(elf_handle);
    return 0;
}

注意:如果目标ELF是非PIE且未导出符号,此方法不适用,需用下面的手动加载方案。

方案2:手动解析并加载ELF(适用于无符号的场景)

直接解析ELF的头部、程序段,用mmap将代码/数据段加载到进程空间,手动定位变量和目标地址后跳转。适合非PIE或无符号导出的程序。

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

int main() {
    int fd = open("./non_pie_target.elf", O_RDONLY);
    if (fd == -1) { perror("打开文件失败"); return 1; }

    // 读取ELF头部
    Elf64_Ehdr elf_hdr;
    read(fd, &elf_hdr, sizeof(elf_hdr));

    Elf64_Phdr prog_hdr;
    void* code_segment = NULL;
    void* data_segment = NULL;

    // 遍历程序段,加载代码和数据段到对应虚拟地址
    for (int i = 0; i < elf_hdr.e_phnum; i++) {
        lseek(fd, elf_hdr.e_phoff + i * sizeof(prog_hdr), SEEK_SET);
        read(fd, &prog_hdr, sizeof(prog_hdr));

        if (prog_hdr.p_type == PT_LOAD) {
            if (prog_hdr.p_flags & PF_X) {
                // 加载代码段(非PIE使用固定虚拟地址)
                code_segment = mmap((void*)prog_hdr.p_vaddr, prog_hdr.p_memsz, 
                                    PROT_READ | PROT_EXEC | PROT_WRITE, 
                                    MAP_FIXED | MAP_PRIVATE, fd, prog_hdr.p_offset);
                if (code_segment == MAP_FAILED) { perror("映射代码段失败"); return 1; }
            } else if (prog_hdr.p_flags & PF_W) {
                // 加载数据段
                data_segment = mmap((void*)prog_hdr.p_vaddr, prog_hdr.p_memsz, 
                                    PROT_READ | PROT_WRITE, 
                                    MAP_FIXED | MAP_PRIVATE, fd, prog_hdr.p_offset);
                if (data_segment == MAP_FAILED) { perror("映射数据段失败"); return 1; }
            }
        }
    }
    close(fd);

    // 1. 准备变量:假设目标变量在数据段偏移0x1234处
    int* target_var = (int*)((char*)data_segment + 0x1234);
    *target_var = 42;

    // 2. 跳转至目标地址:假设目标标签在代码段偏移0x5678处
    void (*target_jump)() = (void*)((char*)code_segment + 0x5678);
    target_jump();

    return 0;
}

注意:MAP_FIXED会强制覆盖指定地址的内存,需确保该地址未被当前进程占用;PIE程序需计算基址偏移,调整加载地址。


Windows 平台

方案1:加载PE文件并调用导出符号(适用于有导出符号的场景)

使用LoadLibrary/GetProcAddress加载目标PE(EXE/DLL),获取变量和函数地址后操作,逻辑和ELF的动态加载类似。

#include <windows.h>
#include <stdio.h>

int main() {
    // 1. 准备变量
    int prepared_val = 42;

    // 2. 加载目标PE(EXE需显式导出符号,推荐用DLL)
    HMODULE pe_module = LoadLibraryA("./target_program.exe");
    if (!pe_module) {
        printf("加载失败: %lu\n", GetLastError());
        return 1;
    }

    // 3. 获取目标变量地址并赋值
    int* target_global_var = (int*)GetProcAddress(pe_module, "target_var");
    if (!target_global_var) {
        printf("获取变量地址失败: %lu\n", GetLastError());
        FreeLibrary(pe_module);
        return 1;
    }
    *target_global_var = prepared_val;

    // 4. 跳转至目标函数执行
    void (*target_entry)() = (void(*)())GetProcAddress(pe_module, "target_function");
    if (!target_entry) {
        printf("获取入口地址失败: %lu\n", GetLastError());
        FreeLibrary(pe_module);
        return 1;
    }
    target_entry();

    FreeLibrary(pe_module);
    return 0;
}

注意:Windows EXE默认不会导出符号,需在编译时用__declspec(dllexport)显式导出目标变量和函数,或改用DLL文件。

方案2:手动解析并加载PE(适用于无符号的场景)

解析PE的DOS头、NT头和节表,用VirtualAlloc分配内存,复制代码/数据段并修复重定位,最后跳转至指定地址。

#include <windows.h>
#include <stdio.h>

int main() {
    HANDLE file_handle = CreateFileA("./target_program.exe", GENERIC_READ, FILE_SHARE_READ, 
                                    NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (file_handle == INVALID_HANDLE_VALUE) {
        printf("打开文件失败: %lu\n", GetLastError());
        return 1;
    }

    DWORD file_size = GetFileSize(file_handle, NULL);
    LPBYTE file_buf = (LPBYTE)malloc(file_size);
    ReadFile(file_handle, file_buf, file_size, NULL, NULL);
    CloseHandle(file_handle);

    // 解析PE结构
    PIMAGE_DOS_HEADER dos_hdr = (PIMAGE_DOS_HEADER)file_buf;
    PIMAGE_NT_HEADERS nt_hdr = (PIMAGE_NT_HEADERS)(file_buf + dos_hdr->e_lfanew);
    PIMAGE_SECTION_HEADER sec_hdrs = IMAGE_FIRST_SECTION(nt_hdr);

    // 分配内存(优先尝试固定基址,失败则用系统分配的动态基址)
    LPVOID image_base = VirtualAlloc((LPVOID)nt_hdr->OptionalHeader.ImageBase, 
                                    nt_hdr->OptionalHeader.SizeOfImage, 
                                    MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (!image_base) {
        image_base = VirtualAlloc(NULL, nt_hdr->OptionalHeader.SizeOfImage, 
                                MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
        if (!image_base) {
            printf("分配内存失败: %lu\n", GetLastError());
            free(file_buf);
            return 1;
        }
    }

    // 复制PE头和节内容
    memcpy(image_base, file_buf, nt_hdr->OptionalHeader.SizeOfHeaders);
    for (int i = 0; i < nt_hdr->FileHeader.NumberOfSections; i++) {
        PIMAGE_SECTION_HEADER sec = &sec_hdrs[i];
        memcpy((LPBYTE)image_base + sec->VirtualAddress, 
               file_buf + sec->PointerToRawData, sec->SizeOfRawData);
    }

    // 修复重定位(动态基址时需要)
    if (nt_hdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size > 0) {
        PIMAGE_BASE_RELOCATION reloc = (PIMAGE_BASE_RELOCATION)((LPBYTE)image_base + 
                                    nt_hdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
        DWORD delta = (DWORD)((LPBYTE)image_base - nt_hdr->OptionalHeader.ImageBase);

        while (reloc->VirtualAddress != 0) {
            DWORD* relocs = (DWORD*)((LPBYTE)reloc + sizeof(IMAGE_BASE_RELOCATION));
            int count = (reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
            for (int j = 0; j < count; j++) {
                WORD rel_info = ((WORD*)relocs)[j];
                if ((rel_info >> 12) == IMAGE_REL_BASED_HIGHLOW) {
                    DWORD* addr = (DWORD*)((LPBYTE)image_base + reloc->VirtualAddress + (rel_info & 0xFFF));
                    *addr += delta;
                }
            }
            reloc = (PIMAGE_BASE_RELOCATION)((LPBYTE)reloc + reloc->SizeOfBlock);
        }
    }

    // 1. 准备变量:假设目标变量在数据节偏移0x1234处
    int* target_var = (int*)((LPBYTE)image_base + sec_hdrs[1].VirtualAddress + 0x1234);
    *target_var = 42;

    // 2. 跳转至目标地址:假设目标标签在代码节偏移0x5678处
    void (*target_jump)() = (void(*)())((LPBYTE)image_base + sec_hdrs[0].VirtualAddress + 0x5678);
    target_jump();

    VirtualFree(image_base, 0, MEM_RELEASE);
    free(file_buf);
    return 0;
}

注意:手动加载PE需处理导入表修复(加载依赖DLL并替换函数地址),上述代码为简化版,实际使用需完善该部分逻辑。


关于反编译重编译方案的说明

你之前尝试的反编译ELF为.s再重新编译的方法,存在天然缺陷:反编译工具无法100%还原原始指令(尤其是涉及绝对地址硬编码的非PIE程序),且工具本身的bug会导致编译后程序地址偏移,最终无法正常跳转。因此更推荐上述直接加载进程空间的方案。

内容的提问来源于stack exchange,提问作者Romain

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 13:01:17