/// \file sysguard.cpp
/// \author Ryan Patterson
/// \date Feb 8 2006
/// Attempt to disallow modification of files by hooking into system calls.
/// The code spawns `touch mytest`, and then hook onto the spawned process,
/// disallowing it from opening any files for writing.
/// \note System calls in Linux are handled through interrupt 80h, and the
/// call to be processed is the value of EAX. Parameters are in other
/// registers: EBX, ECX, EDX, ESI, and EDI.

#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <linux/user.h>
#include <iostream>
#include <sstream>

using namespace std;

/// Handler for the open system call. This is where the interesting things
/// happen.
void process_open(int pid, user_regs_struct& regs)
{
	stringstream filename;
	char c;
	
	// First get a copy of the filename (pointer stored in 1st parameter, EBX)
	int offset = regs.ebx;
	while(1)
	{
		// Copy 1 word at a time. This code is bad, because the docs say
		// to copy 1 word at a time on word-aligned boundaries but we are
		// copying 1 byte at a time on byte-aligned boundaries.
		c = ptrace(PTRACE_PEEKDATA, pid, offset++, NULL);
		if(!c)
			break;
		filename.put(c);
	}
	
	// Also get the open mode.
	int flags = regs.ecx;
	if(flags & O_WRONLY)
	{
		// Log the attempt, ask user's confirmation, blah, blah.
		cout << "Attempt to write to " << filename.str() << endl;
		// Bodge the attempt by setting the first two bytes of the filename to
		// NULL. We could, at our leisure, kill the process or something,
		// instead. In actuality this code would have to store the filename so
		// that it could be restored at the exit of the system call (see line
		// 80).
		ptrace(PTRACE_POKEDATA, pid, regs.ebx, 0);
	}
}

/// Handler for when a system call is received
void handle_syscall(int pid)
{
	// First get a copy of the register values from the child process.
	user_regs_struct regs;
	ptrace(PTRACE_GETREGS, pid, 0, &regs);

	switch(regs.orig_eax)
	{
		case SYS_open:
			process_open(pid, regs);
			break;
		default:
			break;
	}
}

int main(int argc, char* argv[])
{
	// Spawn a process to work with. We could also have attached to an
	// already-running process.
	pid_t child = fork();
	if(child == 0)
	{
		// Set up the current process for being traced.
		ptrace(PTRACE_TRACEME, 0, NULL, NULL);
		execl("/usr/bin/touch", "touch", "mytest", NULL);
	}
	
	int status;
	bool toggle = false;
	while(1)
	{
		// Wait for some message from the child
		wait(&status);
		
		if(WIFEXITED(status))
			break;
		
		// System call traces a fired twice for each call: once upon entry,
		// and once upon exit, giving us two chances to modify the data. This
		// application will only process entry messages.
		toggle = !toggle;
		if(toggle)
		{
			// Now is the time to handle a system call (on entry)
			handle_syscall(child);
		}
		
		// Tell the child to run again, but break when another system call is run.
		ptrace(PTRACE_SYSCALL, child, NULL, NULL);
		
	}
	
	return 0;
}

// The end
