将代码注入旧的 ELF 二进制文件时,ptrace 失败 (2005)

问题描述

我有一个非常旧的游戏服务器文件,用于几年前已经过时的视频游戏(从那时起已经过去了十多年)。这个引擎有很多克隆和逆向工程复制品,但没有什么会像真实的东西,或者至少没有大量的工作(它是一个 MMORPG 服务器)。它太老了,它使用 LinuxThreads 而不是 NPTL,并将其用于每个连接的线程架构(多达 1100 个同时连接与巨大的 2D 瓷砖游戏世界交互)。

我试图从头开始重写它,因为它有古老的 GCC 调试信息可用,但不得不放弃它,因为要做的工作量太疯狂了。现在我正在尝试向其中注入新功能,但无法进一步推进,因为我真的不明白我应该如何注入它。

我尝试使用 ptrace 编写一个非常简单的注入器,但服务器由于分段错误 (SIGSEGV) 而死机,并且由于我无法附加 GDB,我真的不知道崩溃发生在哪里。

>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>

#define EMPTY_ADDR 0x80EF000UL

const char *SHELLCODE = "\x90\x90\x90\x90\x90\x90\x90\x90";

pid_t GamePID;

void GetGamePID() {
    FILE *fp = fopen("/home/bob/lock.pid","rb");
    if (!fp) {
        printf(":: [Error] Game not running.\n");
        return;
    }

    fscanf(fp,"%d",&GamePID);
}

int main() {
    GetGamePID();
    if (!GamePID)
        return 0;

    if (ptrace(PTRACE_ATTACH,GamePID,NULL,NULL)) {
        printf(":: [Error] Cannot attach.\n");
        printf(":: # Fail %d: %s.\n",errno,strerror(errno));
        return 0;
    }

    siginfo_t info;
    waitid(P_PID,&info,WSTOPPED);

    printf(":: [Info] Attach on PID %d.\n",GamePID);

    struct user_regs_struct oldregs,regs;
    if (ptrace(PTRACE_GETREGS,&oldregs)) {
        printf(":: [Error] Cannot save registers (Fail %d).\n",errno);
        ptrace(PTRACE_DETACH,NULL);
        return 1;
    }
    
    unsigned long save[2];
    save[0] = ptrace(PTRACE_PEEKTEXT,(void *)(EMPTY_ADDR),NULL);
    save[1] = ptrace(PTRACE_PEEKTEXT,(void *)(EMPTY_ADDR + 8UL),NULL);

    size_t payload_size = strlen(SHELLCODE);
    uint64_t *payload = (uint64_t *)SHELLCODE;

    printf("[*] Injecting payload at address 0x%lx.\n",EMPTY_ADDR);
    for (size_t i = 0; i < payload_size; i += 8,payload++) {
        if (ptrace(PTRACE_POKETEXT,EMPTY_ADDR + i,*payload) < 0) {
            printf(":: [Error] Failed to PTRACE_POKETEXT: %s\n",strerror(errno));
            return 1;
        }
    }

    regs = oldregs;
    regs.rip = EMPTY_ADDR;
    if (ptrace(PTRACE_SETREGS,&regs)) {
        printf(":: [Error] Cannot modify registers.\n");
        ptrace(PTRACE_DETACH,NULL);
        return 1;
    }

    if (ptrace(PTRACE_CONT,NULL)) {
        printf(":: [Error] Cannot run shellcode.\n");
        ptrace(PTRACE_DETACH,NULL);
        return 1;
    }

    waitid(P_PID,WSTOPPED);

    // I used blank r-xp space but just in case...
    if (ptrace(PTRACE_POKETEXT,(void *)save[0]) ||
        ptrace(PTRACE_POKETEXT,(void *)save[1])) {
        printf(":: [Error] Cannot restore origcode.\n");
        ptrace(PTRACE_DETACH,NULL);
        return 1;
    }

    if (ptrace(PTRACE_SETREGS,&oldregs)) {
        printf(":: [Error] Cannot restore registers.\n");
        ptrace(PTRACE_DETACH,NULL);
        return 1;
    }

    if (ptrace(PTRACE_DETACH,NULL)) {
        printf(":: [Error] Cannot detach from process.\n");
        return 1;
    }

    puts("[+] Done.");
}

这就是我的注射器的样子。字符串和诸如此类的东西是硬编码的,因为这是虚拟代码。这是 /proc/pid/maps 输出

08047000-08048000 rwxp 00000000 08:01 549662                             /home/bob/bin/game.orig
08048000-08125000 r-xp 00001000 08:01 549662                             /home/bob/bin/game.orig
08125000-08129000 rwxp 000dd000 08:01 549662                             /home/bob/bin/game.orig
08129000-0eb45000 rwxp 00000000 00:00 0
0f3e2000-14f10000 rwxp 00000000 00:00 0                                  [heap]
c573f000-f7b69000 rwxp 00000000 00:00 0
f7b6a000-f7b71000 rwxs 00000000 00:01 32821                              /SYSV0000271b (deleted)
f7b71000-f7b72000 rwxp 00000000 00:00 0
f7bb5000-f7beb000 rwxp 00000000 00:00 0
f7bec000-f7d3e000 rwxp 00000000 00:00 0
f7d3e000-f7e57000 r-xp 00000000 08:01 1041510                            /home/bob/lib/libc.so.6
f7e57000-f7e59000 r-xp 00119000 08:01 1041510                            /home/bob/lib/libc.so.6
f7e59000-f7e5b000 rwxp 0011b000 08:01 1041510                            /home/bob/lib/libc.so.6
f7e5b000-f7e5d000 rwxp 00000000 00:00 0
f7e5d000-f7e66000 r-xp 00000000 08:01 1041511                            /home/bob/lib/libgcc_s.so.1
f7e66000-f7e67000 rwxp 00009000 08:01 1041511                            /home/bob/lib/libgcc_s.so.1
f7e67000-f7e68000 rwxp 00000000 00:00 0
f7e68000-f7e8c000 r-xp 00000000 08:01 1041512                            /home/bob/lib/libm.so.6
f7e8c000-f7e8d000 r-xp 00023000 08:01 1041512                            /home/bob/lib/libm.so.6
f7e8d000-f7e8e000 rwxp 00024000 08:01 1041512                            /home/bob/lib/libm.so.6
f7e8e000-f7f3d000 r-xp 00000000 08:01 1041514                            /home/bob/lib/libstdc++.so.5
f7f3d000-f7f42000 rwxp 000ae000 08:01 1041514                            /home/bob/lib/libstdc++.so.5
f7f42000-f7f47000 rwxp 00000000 00:00 0
f7f47000-f7f56000 r-xp 00000000 08:01 1041513                            /home/bob/lib/libpthread.so.0
f7f56000-f7f57000 r-xp 0000e000 08:01 1041513                            /home/bob/lib/libpthread.so.0
f7f57000-f7f58000 rwxp 0000f000 08:01 1041513                            /home/bob/lib/libpthread.so.0
f7f58000-f7f9b000 rwxp 00000000 00:00 0
f7f9b000-f7f9e000 r--p 00000000 00:00 0                                  [vvar]
f7f9e000-f7f9f000 r-xp 00000000 00:00 0                                  [vdso]
f7f9f000-f7fb8000 r-xp 00000000 08:01 1041509                            /home/bob/lib/ld-linux.so.2
f7fb8000-f7fb9000 r-xp 00018000 08:01 1041509                            /home/bob/lib/ld-linux.so.2
f7fb9000-f7fba000 rwxp 00019000 08:01 1041509                            /home/bob/lib/ld-linux.so.2
ff000000-ff001000 ---p 00000000 00:00 0
ff001000-ff200000 rwxp 00000000 00:00 0
ff200000-ff201000 ---p 00000000 00:00 0
ff201000-ff400000 rwxp 00000000 00:00 0
ff400000-ff401000 ---p 00000000 00:00 0
ff401000-ff600000 rwxp 00000000 00:00 0
ff600000-ff601000 ---p 00000000 00:00 0
ff601000-ff800000 rwxp 00000000 00:00 0
fff4b000-fffa2000 rwxp 00000000 00:00 0                                  [stack]

我的服务器将每个线程作为一个独立的进程生成,正如您所看到的,我不得不使用从与线程模型兼容的古老 RedHat 版本(getpid() 返回)复制的库(/home/bob/lib)每个线程都有不同的值)。

我真的很想听听一些关于如何注入新代码的建议。它和直接写入 /proc/pid/mem 一样笨拙吗?我将如何使用 /proc/pid/mem 覆盖游戏功能(例如 HandlePlayerMove)?是否可以使用 LD_PRELOAD 注入共享对象,因为它是针对较新版本的 libc、libm 等编译的?有没有办法知道是什么触发了 SIGSEGV 而不会导致 ptrace 失败?

编辑:虚拟代码错别字

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)