Intercepting system calls using ptrace
This article will demonstrate how to intercept system calls using the Linux OS as an example.
1. A little about ptrace
Here's what the prototype of the ptrace function looks like:
#include
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);- request is an action to be taken, for example PTRACE_CONT, PTRACE_PEEKTEXT
- pid - identifier of the traced process
- addr and data depend on request 'and
- PTRACE_SINGLESTEP - step-by-step program execution; control will be transferred after each instruction is executed; such tracing is slow enough
- PTRACE_SYSCALL - continue the program until the system call is entered or exited
- PTRACE_CONT - just continue program execution
2. View system calls
We will write a program to list the system calls used by the program (a simple analogue of the strace utility).
So, first you need to make a fork - the parent process will debug the child:
int main(int argc, char *argv[]) {
pid_t pid = fork();
if (pid)
parent(pid);
else
child();
return 0;
}In the child process, everything is simple - start the trace with PTRACE_TRACEME and run the desired program:
void child() {
ptrace(PTRACE_TRACEME, 0, 0, 0);
execl("/bin/echo", "/bin/echo", "Hello, world!", NULL);
perror("execl");
}When execl executes, the traced process will stop passing its new state to the parent. Therefore, the parent process must first wait for the program to start using waitpid (you can just wait , since there is only one child process):
int status;
waitpid(pid, &status, 0);In order to somehow distinguish between system calls and other stops (for example, SIGTRAP), a special parameter PTRACE_O_TRACESYSGOOD is provided - when a system call is stopped, the parent process will receive SIGTRAP | 0x80 :
ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACESYSGOOD);Now you can execute PTRACE_SYSCALL in a loop before exiting the program, and look at the value of the eax register to determine the system call number. To do this, use PTRACE_GETREGS. It should be noted that the eax register at the time of stopping is replaced, and therefore it is necessary to use the saved state.orig_eax :
while (!WIFEXITED(status)) {
struct user_regs_struct state;
ptrace(PTRACE_SYSCALL, pid, 0, 0);
waitpid(pid, &status, 0);
// at syscall
if (WIFSTOPPED(status) && WSTOPSIG(status) & 0x80) {
ptrace(PTRACE_GETREGS, pid, 0, &state);
printf("SYSCALL %d at %08lx\n", state.orig_eax, state.eip);
// skip after syscall
ptrace(PTRACE_SYSCALL, pid, 0, 0);
waitpid(pid, &status, 0);
}
}Running the program, we see something like this:
...
SYSCALL 6 at b783a430
SYSCALL 197 at b783a430
SYSCALL 192 at b783a430
SYSCALL 4 at b783a430
Hello, world!
SYSCALL 6 at b783a430
SYSCALL 91 at b783a430
SYSCALL 6 at b783a430
SYSCALL 252 at b783a430As you can see, after system call No. 4 (and this is sys_write ), our text is displayed.
3. Intercepting a system call
Now let's try to intercept the challenge and do something good. The write system call looks like this:
write(fd, buf, n);- ebx: fd - file descriptor (number)
- ecx: buf - a pointer to the text to display
- edx: n - number of bytes
// sys_write
if (state.orig_eax == 4) {
char * text = (char *)state.ecx;
ptrace(PTRACE_POKETEXT, pid, (void*)(text+7), 0x72626168); //habr
ptrace(PTRACE_POKETEXT, pid, (void*)(text+11), 0x00000a21); //!\n
}We launch, and ...
...
SYSCALL 6 at 00556416
SYSCALL 197 at 00556416
SYSCALL 192 at 00556416
SYSCALL 4 at 00556416
Hello, habr!
SYSCALL 6 at 00556416
SYSCALL 91 at 00556416
SYSCALL 6 at 00556416
SYSCALL 252 at 00556416Thus, we intercepted the sys_write system call in the / bin / echo program to output our text. This is just a simple example of using ptrace. It can also be used to easily make memory dumps (this, by the way, helps a lot when solving Linux crackmixes), set breakpoints (using PTRACE_SINGLESTEP or replacing the instruction with 0xCC), analyze registers / variables and much more. ptracevery useful, for example, when you can’t quickly get to the problematic code section - if you have to jump many times in the debugger, replace the data, and then the program dies and you have to do everything anew; if you write a program for debugging with ptrace, then all these actions need to be described only once, and they will be performed automatically. Of course, in some debuggers you can write scripts - but they are probably inferior in capabilities.
UPD: forgot to post the full source
4. What to read
man ptrace
man wait
Playing with ptrace, part I
Playing with ptrace, part II
syscalls table