Skip to the content.

I played justCTF 2022 with DiceGang. One challenge I solved, dumpme, was similar to readflag but with a twist.

dumpme - justctf 2022

Info

Writeup

The scenario presented is a low-privilege shell.

% nc dumpme.nc.jctf.pro 1337
ls -la /task/dumpme
---s--x--x. 1 1000 1000 12648 04-02 21:37 dumpme*

There is a binary which is marked as SUID + executable, but without read permissions. This scenario is similar to readflag but with a key difference - there is no source code provided. I began by writing a quick strace clone:

#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <sys/personality.h>
#include <sys/user.h>

int main(int argc, char *argv[])
{   pid_t traced_process;
    struct user_regs_struct regs = {};
    long ins;
    int pid = fork();
    if (pid == 0) {
        ptrace(PTRACE_TRACEME, 0, 0, 0);
        execve(argv[1], &argv[1], NULL);
        puts("exec failed");
        return -1;
    }
    wait(NULL);
    int status;
    while (1) {
        ptrace(PTRACE_GETREGS, pid, 0, &regs);
        printf("rip is %#llx\norig_rax is %#llx\nrax is %#llx\nrbx is %#llx\nrcx is %#llx\nrdx is %#llx\nrsi is %#llx\nrdi is %#llx\nrbp is %#llx\nrsp is %#llx\nr8 is %#llx\nr9 is %#llx\nr10 is %#llx\nr11 is %#llx\nr12 is %#llx\nr13 is %#llx\nr14 is %#llx\nr15 is %#llx, EFLAGS is %llx\n------------------\n",
                regs.rip, regs.orig_rax,regs.rax,regs.rbx,regs.rcx,regs.rdx,regs.rsi,regs.rdi,regs.rbp,regs.rsp,regs.r8,regs.r9,regs.r10,regs.r11,regs.r12,regs.r13,regs.r14,regs.r15, regs.eflags);
        ptrace(PTRACE_SINGLESTEP, pid, 0, 0);
        waitpid(pid, &status, 0);
        if (WIFEXITED(status)) {
            puts("Exited");
            exit(0);
        }
    }
    return 0;
}

From tracing through the very short program, I was able to determine that the program simply sets up the registers for exit(99) then executes a syscall. Due to how ptrace access checks work for SUID binaries, we’re not able to read process memory, but we can modify registers.

As the ptrace API allows breaking before and after a system call, we can actually hijack the program. By calling prctl(PR_SET_DUMPABLE, 1) the program’s memory space will become accessible to us via process_vm_readv, ptrace or other methods.

  1. By reading from the stack (given by the value of RSP) we can locate the auxiliary vectors
  2. One of the auxiliary vectors is a pointer to the ELF base, at 0x1337babe000
  3. By scanning through the section headers, we find a section at 0x00000deadf00d000
  4. Reading it out reveals the flag: justCTF{tr4cing_blind_a1nt_that_h4rd}