특정 프로세스가 주어진 실행 파일을 실행하도록 하려면 ptrace()를 어떻게 사용합니까?

특정 프로세스가 주어진 실행 파일을 실행하도록 하려면 ptrace()를 어떻게 사용합니까?

exec()나는 외부 파일 시스템을 시스템 플래시에 쓰기 전에 테스트할 수 있도록 임베디드 Linux 시스템의 초기화 프로세스를 자체 초기화 프로그램(systemd) 에 강제로 적용하려고 합니다 (그리고 장치가 벽돌이 될 위험이 있습니다). GDB를 사용하면 명령을 실행 gdb --pid=1한 다음 해당 셸 유형에서 실행할 수 있지만 call execl("/lib/systemd/systemd", "systemd", 0)(필요한 방식으로 정확히 작동합니다) 시스템의 플래시 메모리에 GDB를 넣을 공간이 충분하지 않습니다.

ptrace()GDB를 호출하는 명령이 정확히 무엇을 사용하는지 알고 싶습니다. call이를 내 자신의 간단한 C 프로그램에서 구현할 수 있습니다.

straceGDB가 사용하는 호출이 무엇인지 알아내려고 노력 중이지만 ptrace()결과 파일 길이는 172,031줄입니다. 소스 코드도 살펴봤지만 원하는 파일을 찾기에는 파일이 너무 많습니다.

장치는 Linux 커널 버전 3.10.0을 실행하고 있으며 구성은 여기에서 사용할 수 있습니다.https://pastebin.com/rk0Zux62

답변1

다음은 이를 수행하는 C 프로그램입니다. 다음과 같은 몇 가지 알려진 문제에 유의하세요.

  • 아마도 엄격한 별칭 위반 대신 memcpy를 사용해야 할 것입니다.
  • 이전 피추적자의 환경 변수 대신 자신의 환경 변수를 사용하세요.
  • 피추적자가 시스템 호출을 하지 않는다면 이는 결코 아무 일도 하지 않을 것입니다.
  • 피추적자가 언제 정지했는지 확인하지 않고 그것이 실제로 시스템 호출 정지인지 신호 정지 같은 것이 아닌지 확인하지 않음
  • IP는 syscall-enter-stop 대신 syscall-exit-stop에서 재설정되어야 합니다.
  • 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 커널과 wheezy userland를 사용하여 개발하고 테스트했습니다.https://people.debian.org/~aurel32/qemu/armel/

관련 정보