void* inject_syscall(
pid_t pid,
int syscall_n,
void* arg0,
void* arg1,
void* arg2,
void* arg3,
void* arg4,
void* arg5
){
void* ret = (void*)-1;
int status;
struct user_regs_struct old_regs, regs;
void* injection_addr = (void*)-1;
//This buffer is our payload, which will run a syscall properly on x86/x64
unsigned char injection_buf[] =
{
# if defined(ARCH_86) //32 bits syscall
0xcd, 0x80, //int80 (syscall)
# elif defined(ARCH_64) //64 bits syscall
0x0f, 0x05, //syscall
# endif
/* these nops are here because
* we're going to write memory using
* ptrace, and it always writes the size
* of a word, which means we have to make
* sure the buffer is long enough
*/
0x90, //nop
0x90, //nop
0x90, //nop
0x90, //nop
0x90, //nop
0x90 //nop
};
//As ptrace will always write a uintptr_t, let's make sure we're using proper buffers
uintptr_t old_data;
uintptr_t injection_buffer;
memcpy(&injection_buffer, injection_buf, sizeof(injection_buffer));
//Attach to process using 'PTRACE_ATTACH'
ptrace(PTRACE_ATTACH, pid, NULL, NULL);
wait(&status);
/* Get the current registers using 'PTRACE_GETREGS' so that
* we can restore the execution later
* and also modify the bytes of EIP/RIP
*/
ptrace(PTRACE_GETREGS, pid, NULL, &old_regs);
regs = old_regs;
//Now, let's set up the registers that will be injected into the tracee
# if defined(ARCH_86)
regs.eax = (uintptr_t)syscall_n;
regs.ebx = (uintptr_t)arg0;
regs.ecx = (uintptr_t)arg1;
regs.edx = (uintptr_t)arg2;
regs.esi = (uintptr_t)arg3;
regs.edi = (uintptr_t)arg4;
regs.ebp = (uintptr_t)arg5;
injection_addr = (void*)regs.eip;
# elif defined(ARCH_64)
regs.rax = (uintptr_t)syscall_n;
regs.rdi = (uintptr_t)arg0;
regs.rsi = (uintptr_t)arg1;
regs.rdx = (uintptr_t)arg2;
regs.r10 = (uintptr_t)arg3;
regs.r8 = (uintptr_t)arg4;
regs.r9 = (uintptr_t)arg5;
injection_addr = (void*)regs.rip;
# endif
//Let's store the buffer at EIP/RIP that we're going to modify into 'old_data' using 'PTRACE_PEEKDATA'
old_data = (uintptr_t)ptrace(PTRACE_PEEKDATA, pid, injection_addr, NULL);
//Let's write our payload into the EIP/RIP of the target process using 'PTRACE_POKEDATA'
ptrace(PTRACE_POKEDATA, pid, injection_addr, injection_buffer);
//Let's inject our modified registers into the target process using 'PTRACE_SETREGS'
ptrace(PTRACE_SETREGS, pid, NULL, ®s);
//Let's run a single step in the target process (execute one assembly instruction)
ptrace(PTRACE_SINGLESTEP, pid, NULL, NULL);
waitpid(pid, &status, WSTOPPED); //Wait for the instruction to run
//Let's get the registers after the syscall to store the return value
ptrace(PTRACE_GETREGS, pid, NULL, ®s);
# if defined(ARCH_86)
ret = (void*)regs.eax;
# elif defined(ARCH_64)
ret = (void*)regs.rax;
# endif
//Let's write the old data at EIP/RIP
ptrace(PTRACE_POKEDATA, pid, (void*)injection_addr, old_data);
//Let's restore the old registers to continue the normal execution
ptrace(PTRACE_SETREGS, pid, NULL, &old_regs);
ptrace(PTRACE_DETACH, pid, NULL, NULL); //Detach and continue the execution
return ret;
}