Break It Even Harder

Breaking “mktemp -d” lvl up

Last time we saw that we could play with mktemp a little just to test its limits.

I want to see how many attempts mktemp -d

How to break mktemp -d programmatically

Idealy we would use some kind of gdb-like thingy that allows me to break on syscalls and read the data of the mktemp process to prevent the directory to be created by creating this directory just befor mktemp.

What did I chose ? A little mix of fork(2) and ptrace(2) ! Using fork(2) I will launch mktemp in the child process and request to be traced.

pid_t child = fork();
if (child == 0)
{
  ptrace(PTRACE_TRACEME, 0, NULL, NULL);
  char *args[] = {"mktemp", "-d", NULL};
  return execvp(args[0], args + 1);
}

On the father side, I will the code will extensively rely on calls to ptrace(2) with the request parameter equal to:

  • PTRACE_GETREGS
    • to get the child’s register values
  • PTRACE_PEEKDATA
    • to get the child’s memory at the address passed as long addr parameter
  • PTRACE_SYSCALL
    • to stop the execution of the child at the next syscall event which will be either entering or leaving a syscall

Intuitively one would think that mktemp -d would rely on calls to mkdir(2) and checking the return value to know if it worked or failed but it first uses a call to openat(2) to check if the desired directory exits so we will focus on openat(2) calls and build our code from here.

struct user_regs_struct regs;
int insyscall = 0;
while (waitpid(child, &status, 0) && !WiFEXITED(status))
{
  //get registers of the child
  ptrace(PTRACE_GETREGS, child, NULL, &regs);
  if (regs.orig_rax == SYS_openat)
  {
    if (syscall == 0)
    {
      //enter syscall
    }
    else
    {
      //exit syscall
    }
  }
}

When we have this main loop we can start hacking our way to break mktemp. For this I will need some functions/mechanisms. The first one is a function that will peek in the child’s memomry to get the string passed to openat(2)

void getdata(pid_t child, long addr, char **str);

one other function that given a string will return a boolean wether it’s a path that starts with /tmp/tmp.

int match_tmp(char *str);

Now that we have those function we just need to use them to build the logic of the code. To sum up very fast we want to:

  • catch the entering of openat(2) syscall
  • get the string from child’s memory (getdata)
  • check that it is a path from /tmp…
  • call mkdir(2) with this path
  • catch the exiting of openat(2) syscall
  • remove the direcory we created
  • keep track of the number of calls to openat(2)

I will not show code for this part as it is self exlpanatory in the code that can be found here.

What did it show ?

Well this shows exactly what I expected, mktemp -d has a limited number of tries before it breaks (understand exits here)

The limit I foud was 2^13 iterations before it exits.

Written on May 13, 2019