I'm trying to use ptrace.h
to track the creation of new processes in a binary. My intent is to only allow the creation of a single process (that in the future I will also track what it can write).
To monitor the program, I wrote the following code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <sys/syscall.h>
int main(int argc, char **argv)
{
if (argc != 2) {
return 1;
}
struct user_regs_struct regs;
int pid_parent = fork(), status_pai;
char buffer_pai[0x1000];
int pid_child = -1, status_filho;
char buffer_filho[0x1000];
int current_pid = - 1, new_pid, current_status, new_status;
if (pid_parent == 0) {
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
execl(argv[1], argv[1], NULL);
}
waitpid(pid_parent, &status_pai, 0);
// Track all kinds of clone
ptrace(PTRACE_SETOPTIONS, pid_parent, 0,
PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK |
PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXEC);
ptrace(PTRACE_SYSCALL, pid_parent, 0, 0);
int flag_fork = 1, pending_exits = 1;
while (pending_exits) {
current_pid = waitpid(-1, ¤t_status, __WALL);
if (WIFEXITED(current_status) || WIFSIGNALED(current_status)) {
pending_exits--;
continue;
}
if (current_status >> 8 == (SIGTRAP | (PTRACE_EVENT_FORK << 8)) ||
current_status >> 8 == (SIGTRAP | (PTRACE_EVENT_VFORK << 8)) ||
current_status >> 8 == (SIGTRAP | (PTRACE_EVENT_CLONE << 8))) {
ptrace(PTRACE_GETEVENTMSG, current_pid, 0, &new_pid);
printf("\nParent pid: %d - Child pid: %d\n", pid_parent, pid_child);
printf("Current pid: %d - New pid: %d\n", current_pid, new_pid);
if (current_pid == pid_parent) {
if ((new_pid != pid_parent) && (flag_fork)) {
pid_child = new_pid;
pending_exits++;
flag_fork = 0; // Parent can fork only once
puts("Parent forked for the first time.");
} else if ((new_pid != pid_parent) && (new_pid != pid_child) && (!flag_fork)) {
puts("Parent tried to fork again.");
exit(1);
} else {
puts("Weird situation with parent.");
exit(1);
}
} else if (current_pid == pid_child) {
if (new_pid != pid_child) {
puts("Child can not fork.");
exit(1);
} else {
puts("Weird situation with child.");
exit(1);
}
}
}
if (pending_exits > 2) {
puts("Fork overflow");
exit(1);
}
ptrace(PTRACE_SYSCALL, current_pid, 0, 0);
}
return 0;
}
The program that I used to test is the following:
#include <stdio.h>
#include <unistd.h>
int main() {
if (!fork()) {
fork();
}
return 0;
}
In this case, after the parent fork, the child only will fork too. When I run this program tracking with the first one, I got the following output:
[1337] j3r3mias@serenity > ./tracker example
Parent pid: 2605006 - Child pid: 0
Current pid: 2605006 - New pid: 2605007
Parent forked for the first time.
Parent pid: 2605006 - Child pid: 0
Current pid: 2605007 - New pid: 2605008
The problem with the tracker is that pid_child
even after being updated after the parent fork, shows the value 0
in the second interaction of the while
. Are the any differences in the state of the program when using ptrace
?
Some complementary infos about the environment I'm running theses tests:
- OS: Ubuntu 24.04
- Compiler: gcc 13.3.0
- GLIBC 2.39-0ubuntu8.4
- Kernel version: 5.15.167