Linux

如何使用 ptrace() 使特定程序執行給定的執行檔?

  • September 3, 2018

我正在嘗試將嵌入式 Linux 系統的 init 程序強制執行到exec()我自己的 init 程序(systemd),以便我可以在將外部文件系統寫入系統的快閃記憶體之前對其進行測試(並冒著使設備變磚的風險)。使用 GDB,我可以執行 command gdb --pid=1,然後在那個 shell 類型中執行call execl("/lib/systemd/systemd", "systemd", 0)(它完全按照我的需要工作),但是我沒有足夠的空間將 GDB 放在系統的快閃記憶體上。

我想知道ptrace()GDB 的call命令到底使用了什麼呼叫,以便我可以在自己的簡單 C 程序中實現它。

我試圖strace找出ptrace()GDB 使用了什麼呼叫,但生成的文件有 172,031 行長。我也嘗試過查看它的原始碼,但是文件太多,無法找到我要查找的內容。

該設備執行 Linux 核心版本 3.10.0,配置可在此處獲得:https ://pastebin.com/rk0Zux62

這是一個應該執行此操作的 C 程序。注意幾個已知問題:

  • 應該可能使用 memcpy 而不是嚴格的別名違規
  • 使用自己的環境變數而不是舊的tracee的環境變數
  • 如果被跟踪者沒有進行任何系統呼叫,這將永遠無法做任何事情
  • 不檢查跟踪對象何時停止以確保它確實是系統呼叫停止而不是信號停止或其他東西
  • 應該在 syscall-exit-stop 而不是 syscall-enter-stop 中設置 IP
  • 不對 execve 參數進行任何完整性檢查(這樣做是 execveat 的好機會)
  • 完全不可移植(CONFIG_ARM_THUMB許多其他東西中的硬程式碼)
  • 如果任何系統呼叫無法正常工作,則使程序處於可能崩潰的狀態

編譯它-fno-strict-aliasing,然後執行它./a.out 1 /lib/systemd/systemd systemd

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <linux/ptrace.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <sys/syscall.h>
#include <sys/mman.h>

#define CONFIG_ARM_THUMB

#ifdef CONFIG_ARM_THUMB
#define thumb_mode(regs) \
       (((regs)->ARM_cpsr & PSR_T_BIT))
#else
#define thumb_mode(regs) (0)
#endif

extern char **environ;

static pid_t pid;

/* The length of a string, plus the null terminator, rounded up to the nearest sizeof(long). */
size_t str_size(char *str) {
       size_t len = strlen(str);
       return len + sizeof(long) - len % sizeof(long);
}

void must_poke(long addr, long data) {
       if(ptrace(PTRACE_POKEDATA, pid, (void*)addr, (void*)data)) {
               perror("ptrace(PTRACE_POKEDATA, ...)");
               exit(1);
       }
}

void must_poke_multi(long addr, long* data, size_t len) {
       size_t i;
       for(i = 0; i < len; ++i) {
               must_poke(addr + i * sizeof(long), data[i]);
       }
}

long must_poke_string(long addr, char* str) {
       size_t len = str_size(str);
       size_t longs_len = len / sizeof(long);
       char *more_nulls_str = malloc(len);
       memset(more_nulls_str + len - sizeof(long), 0, sizeof(long)); /* initialize the bit we might not copy over */
       strcpy(more_nulls_str, str);
       must_poke_multi(addr, (long*)more_nulls_str, longs_len);
       free(more_nulls_str);
       return addr + len;
}

int main(int argc, char** argv) {
       struct user_regs regs;
       int i, envc;
       unsigned long mmap_base;
       size_t mmap_string_offset, mmap_argv_offset, mmap_envp_offset;
       size_t mmap_len = 2 * sizeof(char*); /* for the NULLs at the end of argv and envp */

       if(argc < 3) {
               fprintf(stderr, "Usage: %s <pid> <executable image> <args...>\n", argv[0]);
               return 1;
       }

       pid = strtol(argv[1], NULL, 10);

       /* for the image name */
       mmap_len += str_size(argv[2]);

       for(i = 3; i < argc; ++i) {
               /* for the pointer in argv plus the string itself */
               mmap_len += sizeof(char*) + str_size(argv[i]);
       }

       for(i = 0; environ[i]; ++i) {
               /* for the pointer in envp plus the string itself */
               mmap_len += sizeof(char*) + str_size(environ[i]);
       }
       envc = i;

       if(ptrace(PTRACE_ATTACH, pid, 0, 0)) {
               perror("ptrace(PTRACE_ATTACH, ...)");
               return 1;
       }
       if(waitid(P_PID, pid, NULL, WSTOPPED)) {
               perror("waitid");
               return 1;
       }

       /* Stop at whatever syscall happens to be next */
       if(ptrace(PTRACE_SYSCALL, pid, 0, 0)) {
               perror("ptrace(PTRACE_SYSCALL, ...)");
               return 1;
       }
       printf("Waiting for the target process to make a syscall...\n");
       if(waitid(P_PID, pid, NULL, WSTOPPED)) {
               perror("waitid");
               return 1;
       }
       printf("Target made a syscall. Proceeding with injection.\n");

       if(ptrace(PTRACE_GETREGS, pid, 0, &regs)) {
               perror("ptrace(PTRACE_GETREGS, ...)");
               return 1;
       }

       /* End up back on the syscall instruction so we can use it again */
       regs.ARM_pc -= (thumb_mode(&regs) ? 2 : 4);

       /* mmap some space for the exec parameters */
       regs.ARM_r0 = (long)0;
       regs.ARM_r1 = (long)mmap_len;
       regs.ARM_r2 = (long)(PROT_READ|PROT_WRITE);
       regs.ARM_r3 = (long)(MAP_PRIVATE|MAP_ANONYMOUS);
       regs.ARM_r4 = (long)-1;
       regs.ARM_r5 = (long)0;
       if(ptrace(PTRACE_SETREGS, pid, 0, &regs)) {
               perror("ptrace(PTRACE_SETREGS, ...)");
               return 1;
       }
       if(ptrace(PTRACE_SET_SYSCALL, pid, 0, SYS_mmap2)) {
               perror("ptrace(PTRACE_SET_SYSCALL, ...)");
               return 1;
       }

       /* jump to the end of the syscall */
       if(ptrace(PTRACE_SYSCALL, pid, 0, 0)) {
               perror("ptrace(PTRACE_SYSCALL, ...)");
               return 1;
       }
       if(waitid(P_PID, pid, NULL, WSTOPPED)) {
               perror("waitid");
               return 1;
       }

       /* make sure it worked and get the memory address */
       if(ptrace(PTRACE_GETREGS, pid, 0, &regs)) {
               perror("ptrace(PTRACE_GETREGS, ...)");
               return 1;
       }

       if(regs.ARM_r0 > -4096UL) {
               errno = -regs.ARM_r0;
               perror("traced process: mmap");
               return 1;
       }
       mmap_base = regs.ARM_r0;

       /* set up the execve args in memory */
       mmap_argv_offset = must_poke_string(mmap_base, argv[2]);

       mmap_string_offset = mmap_argv_offset + (argc - 2) * sizeof(char*); /* don't forget the null pointer */
       for(i = 0; i < argc - 3; ++i) {
               must_poke(mmap_argv_offset + i * sizeof(char*), mmap_string_offset);
               mmap_string_offset = must_poke_string(mmap_string_offset, argv[i + 3]);
       }
       must_poke(mmap_argv_offset + (argc - 3) * sizeof(char*), 0);
       mmap_envp_offset = mmap_string_offset;
       mmap_string_offset = mmap_envp_offset + (envc + 1) * sizeof(char*); /* don't forget the null pointer */
       for(i = 0; i < envc; ++i) {
               must_poke(mmap_envp_offset + i * sizeof(char*), mmap_string_offset);
               mmap_string_offset = must_poke_string(mmap_string_offset, environ[i]);
       }
       must_poke(mmap_envp_offset + envc * sizeof(char*), 0);

       /* jump to the start of the next syscall (same PC since we reset it) */
       if(ptrace(PTRACE_SYSCALL, pid, 0, 0)) {
               perror("ptrace(PTRACE_SYSCALL, ...)");
               return 1;
       }
       if(waitid(P_PID, pid, NULL, WSTOPPED)) {
               perror("waitid");
               return 1;
       }

       if(ptrace(PTRACE_GETREGS, pid, 0, &regs)) {
               perror("ptrace(PTRACE_GETREGS, ...)");
               return 1;
       }

       /* call execve */
       regs.ARM_r0 = (long)mmap_base;
       regs.ARM_r1 = (long)mmap_argv_offset;
       regs.ARM_r2 = (long)mmap_envp_offset;
       if(ptrace(PTRACE_SETREGS, pid, 0, &regs)) {
               perror("ptrace(PTRACE_SETREGS, ...)");
               return 1;
       }
       if(ptrace(PTRACE_SET_SYSCALL, pid, 0, SYS_execve)) {
               perror("ptrace(PTRACE_SET_SYSCALL, ...)");
               return 1;
       }

       /* and done. */
       if(ptrace(PTRACE_DETACH, pid, 0, 0)) {
               perror("ptrace(PTRACE_DETACH, ...)");
               return 1;
       }
       return 0;
}

我通過 qemu-system-arm 使用 3.2.0-4 核心和來自https://people.debian.org/~aurel32/qemu/armel/的 wheezy 使用者區開發和測試了它

引用自:https://unix.stackexchange.com/questions/464539