How can I make a specific process exec a given executable with ptrace()?
Clash Royale CLAN TAG#URR8PPP
up vote
2
down vote
favorite
I am trying to force the init process of an embedded Linux system to exec()
my own init program (systemd) so that I can test an external filesystem before writing it to the system's flash (and risk bricking the device). With GDB, I can run the command gdb --pid=1
, then in that shell type call execl("/lib/systemd/systemd", "systemd", 0)
(which works exactly as I need it to), but I do not have enough room to put GDB on the system's flash.
I was wondering exactly what ptrace()
calls GDB uses with its call
command so that I can implement that in my own simple C program.
I tried using strace
to figure out what ptrace()
calls GDB used, but the resulting file was 172,031 lines long. I also tried looking through its source code, but there were too many files to find what I was looking for.
The device is running Linux kernel version 3.10.0, the configuration is available here: https://pastebin.com/rk0Zux62
linux system-calls exec ptrace
 |Â
show 12 more comments
up vote
2
down vote
favorite
I am trying to force the init process of an embedded Linux system to exec()
my own init program (systemd) so that I can test an external filesystem before writing it to the system's flash (and risk bricking the device). With GDB, I can run the command gdb --pid=1
, then in that shell type call execl("/lib/systemd/systemd", "systemd", 0)
(which works exactly as I need it to), but I do not have enough room to put GDB on the system's flash.
I was wondering exactly what ptrace()
calls GDB uses with its call
command so that I can implement that in my own simple C program.
I tried using strace
to figure out what ptrace()
calls GDB used, but the resulting file was 172,031 lines long. I also tried looking through its source code, but there were too many files to find what I was looking for.
The device is running Linux kernel version 3.10.0, the configuration is available here: https://pastebin.com/rk0Zux62
linux system-calls exec ptrace
You want to run some command against any arbitrary process and force it to invoke another process viaexec
so that this new executable assumes the PID of the original process, right?
â slmâ¦
Aug 24 at 1:52
@slm Yes, exactly.
â Billy
Aug 24 at 1:52
What's the kernel version? You could do this - stackoverflow.com/questions/18122592/â¦.
â slmâ¦
Aug 24 at 1:54
@slm 3.10.0, but CONFIG_CHECKPOINT_RESTORE is disabled in the kernel configuration.
â Billy
Aug 24 at 1:55
1
This could be a good start : Intercepting and Emulating Linux System Calls with Ptrace. You might have to search for additional architecture-dependent informations
â A.B
Aug 26 at 2:24
 |Â
show 12 more comments
up vote
2
down vote
favorite
up vote
2
down vote
favorite
I am trying to force the init process of an embedded Linux system to exec()
my own init program (systemd) so that I can test an external filesystem before writing it to the system's flash (and risk bricking the device). With GDB, I can run the command gdb --pid=1
, then in that shell type call execl("/lib/systemd/systemd", "systemd", 0)
(which works exactly as I need it to), but I do not have enough room to put GDB on the system's flash.
I was wondering exactly what ptrace()
calls GDB uses with its call
command so that I can implement that in my own simple C program.
I tried using strace
to figure out what ptrace()
calls GDB used, but the resulting file was 172,031 lines long. I also tried looking through its source code, but there were too many files to find what I was looking for.
The device is running Linux kernel version 3.10.0, the configuration is available here: https://pastebin.com/rk0Zux62
linux system-calls exec ptrace
I am trying to force the init process of an embedded Linux system to exec()
my own init program (systemd) so that I can test an external filesystem before writing it to the system's flash (and risk bricking the device). With GDB, I can run the command gdb --pid=1
, then in that shell type call execl("/lib/systemd/systemd", "systemd", 0)
(which works exactly as I need it to), but I do not have enough room to put GDB on the system's flash.
I was wondering exactly what ptrace()
calls GDB uses with its call
command so that I can implement that in my own simple C program.
I tried using strace
to figure out what ptrace()
calls GDB used, but the resulting file was 172,031 lines long. I also tried looking through its source code, but there were too many files to find what I was looking for.
The device is running Linux kernel version 3.10.0, the configuration is available here: https://pastebin.com/rk0Zux62
linux system-calls exec ptrace
linux system-calls exec ptrace
edited Sep 3 at 7:40
Joseph Sible
886213
886213
asked Aug 24 at 1:42
Billy
72116
72116
You want to run some command against any arbitrary process and force it to invoke another process viaexec
so that this new executable assumes the PID of the original process, right?
â slmâ¦
Aug 24 at 1:52
@slm Yes, exactly.
â Billy
Aug 24 at 1:52
What's the kernel version? You could do this - stackoverflow.com/questions/18122592/â¦.
â slmâ¦
Aug 24 at 1:54
@slm 3.10.0, but CONFIG_CHECKPOINT_RESTORE is disabled in the kernel configuration.
â Billy
Aug 24 at 1:55
1
This could be a good start : Intercepting and Emulating Linux System Calls with Ptrace. You might have to search for additional architecture-dependent informations
â A.B
Aug 26 at 2:24
 |Â
show 12 more comments
You want to run some command against any arbitrary process and force it to invoke another process viaexec
so that this new executable assumes the PID of the original process, right?
â slmâ¦
Aug 24 at 1:52
@slm Yes, exactly.
â Billy
Aug 24 at 1:52
What's the kernel version? You could do this - stackoverflow.com/questions/18122592/â¦.
â slmâ¦
Aug 24 at 1:54
@slm 3.10.0, but CONFIG_CHECKPOINT_RESTORE is disabled in the kernel configuration.
â Billy
Aug 24 at 1:55
1
This could be a good start : Intercepting and Emulating Linux System Calls with Ptrace. You might have to search for additional architecture-dependent informations
â A.B
Aug 26 at 2:24
You want to run some command against any arbitrary process and force it to invoke another process via
exec
so that this new executable assumes the PID of the original process, right?â slmâ¦
Aug 24 at 1:52
You want to run some command against any arbitrary process and force it to invoke another process via
exec
so that this new executable assumes the PID of the original process, right?â slmâ¦
Aug 24 at 1:52
@slm Yes, exactly.
â Billy
Aug 24 at 1:52
@slm Yes, exactly.
â Billy
Aug 24 at 1:52
What's the kernel version? You could do this - stackoverflow.com/questions/18122592/â¦.
â slmâ¦
Aug 24 at 1:54
What's the kernel version? You could do this - stackoverflow.com/questions/18122592/â¦.
â slmâ¦
Aug 24 at 1:54
@slm 3.10.0, but CONFIG_CHECKPOINT_RESTORE is disabled in the kernel configuration.
â Billy
Aug 24 at 1:55
@slm 3.10.0, but CONFIG_CHECKPOINT_RESTORE is disabled in the kernel configuration.
â Billy
Aug 24 at 1:55
1
1
This could be a good start : Intercepting and Emulating Linux System Calls with Ptrace. You might have to search for additional architecture-dependent informations
â A.B
Aug 26 at 2:24
This could be a good start : Intercepting and Emulating Linux System Calls with Ptrace. You might have to search for additional architecture-dependent informations
â A.B
Aug 26 at 2:24
 |Â
show 12 more comments
1 Answer
1
active
oldest
votes
up vote
2
down vote
accepted
Here's a C program that should do it. Note a few known issues:
- Should probably use memcpy instead of strict aliasing violations
- Uses its own environment variables instead of the old tracee's environment variables
- If the tracee doesn't make any syscalls, this will never manage to do anything
- Doesn't check when the tracee gets stopped to make sure it's really a syscall stop and not a signal stop or something
- Should set IP back in syscall-exit-stop instead of syscall-enter-stop
- Doesn't do any sanity checking of the execve arguments (doing this would be a good opportunity for execveat)
- Completely unportable (hardcodes
CONFIG_ARM_THUMB
among lots of other things) - Leaves the process in a state where it'll probably crash if any of the syscalls don't work right
Compile it with -fno-strict-aliasing
, then run it as ./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) 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;
I developed and tested this via qemu-system-arm with the 3.2.0-4 kernel and wheezy userland from https://people.debian.org/~aurel32/qemu/armel/
Maybe you should add "it captures and changes the next syscall the ptraced application makes" to explain some of the shortcomings.
â dirkt
Sep 3 at 6:29
add a comment |Â
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
2
down vote
accepted
Here's a C program that should do it. Note a few known issues:
- Should probably use memcpy instead of strict aliasing violations
- Uses its own environment variables instead of the old tracee's environment variables
- If the tracee doesn't make any syscalls, this will never manage to do anything
- Doesn't check when the tracee gets stopped to make sure it's really a syscall stop and not a signal stop or something
- Should set IP back in syscall-exit-stop instead of syscall-enter-stop
- Doesn't do any sanity checking of the execve arguments (doing this would be a good opportunity for execveat)
- Completely unportable (hardcodes
CONFIG_ARM_THUMB
among lots of other things) - Leaves the process in a state where it'll probably crash if any of the syscalls don't work right
Compile it with -fno-strict-aliasing
, then run it as ./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) 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;
I developed and tested this via qemu-system-arm with the 3.2.0-4 kernel and wheezy userland from https://people.debian.org/~aurel32/qemu/armel/
Maybe you should add "it captures and changes the next syscall the ptraced application makes" to explain some of the shortcomings.
â dirkt
Sep 3 at 6:29
add a comment |Â
up vote
2
down vote
accepted
Here's a C program that should do it. Note a few known issues:
- Should probably use memcpy instead of strict aliasing violations
- Uses its own environment variables instead of the old tracee's environment variables
- If the tracee doesn't make any syscalls, this will never manage to do anything
- Doesn't check when the tracee gets stopped to make sure it's really a syscall stop and not a signal stop or something
- Should set IP back in syscall-exit-stop instead of syscall-enter-stop
- Doesn't do any sanity checking of the execve arguments (doing this would be a good opportunity for execveat)
- Completely unportable (hardcodes
CONFIG_ARM_THUMB
among lots of other things) - Leaves the process in a state where it'll probably crash if any of the syscalls don't work right
Compile it with -fno-strict-aliasing
, then run it as ./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) 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;
I developed and tested this via qemu-system-arm with the 3.2.0-4 kernel and wheezy userland from https://people.debian.org/~aurel32/qemu/armel/
Maybe you should add "it captures and changes the next syscall the ptraced application makes" to explain some of the shortcomings.
â dirkt
Sep 3 at 6:29
add a comment |Â
up vote
2
down vote
accepted
up vote
2
down vote
accepted
Here's a C program that should do it. Note a few known issues:
- Should probably use memcpy instead of strict aliasing violations
- Uses its own environment variables instead of the old tracee's environment variables
- If the tracee doesn't make any syscalls, this will never manage to do anything
- Doesn't check when the tracee gets stopped to make sure it's really a syscall stop and not a signal stop or something
- Should set IP back in syscall-exit-stop instead of syscall-enter-stop
- Doesn't do any sanity checking of the execve arguments (doing this would be a good opportunity for execveat)
- Completely unportable (hardcodes
CONFIG_ARM_THUMB
among lots of other things) - Leaves the process in a state where it'll probably crash if any of the syscalls don't work right
Compile it with -fno-strict-aliasing
, then run it as ./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) 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;
I developed and tested this via qemu-system-arm with the 3.2.0-4 kernel and wheezy userland from https://people.debian.org/~aurel32/qemu/armel/
Here's a C program that should do it. Note a few known issues:
- Should probably use memcpy instead of strict aliasing violations
- Uses its own environment variables instead of the old tracee's environment variables
- If the tracee doesn't make any syscalls, this will never manage to do anything
- Doesn't check when the tracee gets stopped to make sure it's really a syscall stop and not a signal stop or something
- Should set IP back in syscall-exit-stop instead of syscall-enter-stop
- Doesn't do any sanity checking of the execve arguments (doing this would be a good opportunity for execveat)
- Completely unportable (hardcodes
CONFIG_ARM_THUMB
among lots of other things) - Leaves the process in a state where it'll probably crash if any of the syscalls don't work right
Compile it with -fno-strict-aliasing
, then run it as ./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) 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;
I developed and tested this via qemu-system-arm with the 3.2.0-4 kernel and wheezy userland from https://people.debian.org/~aurel32/qemu/armel/
edited Sep 3 at 19:12
answered Sep 3 at 5:29
Joseph Sible
886213
886213
Maybe you should add "it captures and changes the next syscall the ptraced application makes" to explain some of the shortcomings.
â dirkt
Sep 3 at 6:29
add a comment |Â
Maybe you should add "it captures and changes the next syscall the ptraced application makes" to explain some of the shortcomings.
â dirkt
Sep 3 at 6:29
Maybe you should add "it captures and changes the next syscall the ptraced application makes" to explain some of the shortcomings.
â dirkt
Sep 3 at 6:29
Maybe you should add "it captures and changes the next syscall the ptraced application makes" to explain some of the shortcomings.
â dirkt
Sep 3 at 6:29
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f464539%2fhow-can-i-make-a-specific-process-exec-a-given-executable-with-ptrace%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
You want to run some command against any arbitrary process and force it to invoke another process via
exec
so that this new executable assumes the PID of the original process, right?â slmâ¦
Aug 24 at 1:52
@slm Yes, exactly.
â Billy
Aug 24 at 1:52
What's the kernel version? You could do this - stackoverflow.com/questions/18122592/â¦.
â slmâ¦
Aug 24 at 1:54
@slm 3.10.0, but CONFIG_CHECKPOINT_RESTORE is disabled in the kernel configuration.
â Billy
Aug 24 at 1:55
1
This could be a good start : Intercepting and Emulating Linux System Calls with Ptrace. You might have to search for additional architecture-dependent informations
â A.B
Aug 26 at 2:24