🚩
nabilmuafa's CTF Notes
  • Intro
  • 🖊️Write-ups
    • 2025 Writeups
      • Cyber Jawara National 2024 - General Category
        • Give Me File
        • U-AFF Arigatou STY :( - Upsolved
        • ASM Raw
        • pyrip
        • py50
    • 2024 Writeups
      • Cyber Jawara International 2024
        • Persona
      • Gemastik Keamanan Siber 2024 Quals
        • Baby Ulala (Upsolved)
      • ARA CTF 5.0 Quals
        • lemfao (Upsolved)
  • Random Writeups
    • Attacking 2-Round AES with 1 Known Plaintext
Powered by GitBook
On this page
  • Problem Description
  • Analysis
  • Solution Breakdown
  • Flag
  • Full Solver Script
  1. Write-ups
  2. 2025 Writeups
  3. Cyber Jawara National 2024 - General Category

pyrip

Breaking out of C invoked-Python interpreter jail.

Problem Description

Is this misc or pwn?

Author: farisv

Analysis

We're given a binary and a C code.

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

void trace(pid_t pid) {
  int status;
  struct user_regs_struct regs;

  waitpid(pid, &status, 0);
  ptrace(PTRACE_CONT, pid, 0, 0);

  while (1) {
    ptrace(PTRACE_CONT, pid, 0, 0);
    waitpid(pid, &status, 0);
    if (WSTOPSIG(status) != SIGCHLD) {
      break;
    }
  }

  if (WSTOPSIG(status) == SIGSEGV) {
    ptrace(PTRACE_GETREGS, pid, 0, &regs);
    if (regs.rip == 0xc0ffeedecaf) {
      system("cat flag.txt");
    }
  }

  exit(0);
}

void py_exec() {
  ptrace(PTRACE_TRACEME, 0, 0, 0);
  close(2);
  dup2(1, 2);
  setreuid(65534, 65534);
  setregid(65534, 65534);
  execl("/usr/bin/python3", "/usr/bin/python3", NULL);
}

int main() {
  pid_t pid;

  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stderr, 0, 2, 0);

  pid = fork();

  if (pid == -1) {
    exit(-1);
  }

  if (pid == 0) {
    py_exec();
  } 
  else {
    trace(pid);
  }

  return 0;
}

If we try to run the binary, the program will fork the process, and that forked process will run a Python interpreter, and that's what will appear in our terminal. The main process will trace that forked process. Looking at the source code, our objective is to cause a segmentation fault (SIGSEGV) on the fork process running the Python interpreter. When the SIGSEGV happens, the main process will check if the value in the RIP register before the SIGSEGV happened is 0xc0ffeedecaf. If it is, the main process will print a flag. This means that we need to find a way to run some code through Python that can cause a SIGSEGV and moves the value 0xc0ffeedecafto RIP.

Solution Breakdown

RIP is an instruction pointer register that stores the address of the current instruction. "Instruction" here refers to any line of assembly code, even the beginning of a function. That being said, if we want the value in RIP to be 0xc0ffeedecaf, we need to find a way to "call a function which instructions are stored in 0xc0ffeedecaf". With that, we're telling the program, like, "Hey, for the next instruction, you have to run the function in 0xc0ffeedecaf".

Note: I do not have a screenshot of getting the flag from the server. The server also has some technical issues, causing players unable to re-obtain the flag post-competition.

Flag

CJ{=*= Jump, pogo, pogo, pogo, pogo, pogo, pogo, pogo =*/=}

Full Solver Script

>>> import ctypes
>>> ctypes.CFUNCTYPE(None)(0xc0ffeedecaf)()

Last updated 4 months ago

Luckily, we can easily achieve this in Python using the ctypes module, specifically using CFUNCTYPE. Using CFUNCTYPE, we can call an arbitrary function in any arbitrary address. You can read more about how CFUNCTYPE works . We can call CFUNCTYPE with None as its first argument (denoting that it is a void), then set the function pointer to be at 0xc0ffeedecaf, then call the function itself. This will cause a SIGSEGV, because we are trying to access an invalid memory location. By calling the function, we've also set the value of the RIP to 0xc0ffeedecaf. Objective accomplished!

🖊️
here