How can I make a specific process exec a given executable with ptrace()?

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP











up vote
2
down vote

favorite
2












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










share|improve this question























  • 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















up vote
2
down vote

favorite
2












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










share|improve this question























  • 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













up vote
2
down vote

favorite
2









up vote
2
down vote

favorite
2






2





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










share|improve this question















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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








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 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

















  • 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
















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











1 Answer
1






active

oldest

votes

















up vote
2
down vote



accepted
+100










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, &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;



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/






share|improve this answer






















  • 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










Your Answer







StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "106"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);













 

draft saved


draft discarded


















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






























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
2
down vote



accepted
+100










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, &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;



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/






share|improve this answer






















  • 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














up vote
2
down vote



accepted
+100










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, &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;



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/






share|improve this answer






















  • 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












up vote
2
down vote



accepted
+100







up vote
2
down vote



accepted
+100




+100




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, &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;



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/






share|improve this answer














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, &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;



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/







share|improve this answer














share|improve this answer



share|improve this answer








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
















  • 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

















 

draft saved


draft discarded















































 


draft saved


draft discarded














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













































































Popular posts from this blog

How to check contact read email or not when send email to Individual?

Bahrain

Postfix configuration issue with fips on centos 7; mailgun relay