組み込みLinuxシステムのinitプロセスを強制的にexec()
自分のinitプログラム(systemd)にしようとしています。これにより、外部ファイルシステムをシステムのフラッシュに書き込む前にテストできます(デバイスをブリックするリスクがあります)。 。 GDBを使用すると、コマンド_gdb --pid=1
_を実行してから、そのシェルでcall execl("/lib/systemd/systemd", "systemd", 0)
(必要に応じて正確に機能します)と入力できますが、システムにGDBを配置するための十分なスペースがありません。閃光。
ptrace()
がGDBのcall
コマンドで何を呼び出すのかを正確に知り、それを自分の単純なCプログラムに実装できるようにしたいと思っていました。
strace
を使用して、GDBが使用するptrace()
呼び出しを把握しようとしましたが、結果のファイルの長さは172,031行でした。ソースコードも調べてみましたが、ファイルが多すぎて探しているものが見つかりませんでした。
デバイスはLinuxカーネルバージョン3.10.0を実行しています。構成はここから入手できます: https://Pastebin.com/rk0Zux62
これがそれを行うべきCプログラムです。いくつかの既知の問題に注意してください。
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, ®s)) {
perror("ptrace(PTRACE_GETREGS, ...)");
return 1;
}
/* End up back on the syscall instruction so we can use it again */
regs.ARM_pc -= (thumb_mode(®s) ? 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, ®s)) {
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, ®s)) {
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, ®s)) {
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, ®s)) {
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ユーザーランドを使用して開発およびテストしました https://people.debian.org/~aurel32/qemu/armel/