c0pydump

Description

Welcome to our printing service c0pydump! It is a highly secure application. Can you acquire the flag?

nc challenges.crysys.hu 5006

  • Author: tcs
  • Difficulty: easy
  • Attachment: c0pydump

Solution

0x1 basic information

Running checksec on the file, it gives us the following information:

checksec --file=c0pydump
{
   "c0pydump":{
      "relro":"partial",
      "canary":"yes",
      "nx":"yes",
      "pie":"yes",
      "rpath":"yes",
      "runpath":"no",
      "symbols":"yes",
      "fortify_source":"no",
      "fortified":"0",
      "fortify-able":"3"
    }
}

Connecting to the address given above with nc we can observe the application: It basically gets some input from STDIN and prints it out for us.

After this, I loaded the binary into Ghidra, and it didn’t take too long until I found this gem right here: a format string exploit

void print(char *param_1)

{
  puts("Printing document...");
  printf(param_1);
  puts("\nDocument printed!");
  return;
}

0x2 exploitation

Okay, we know its a format string exploit, so lets test some stuff out:

Scanning...
%6$p
Document scanned!
Printing document...
0x55cb55f8d3b0
Document printed!

Just as expected, we get some random address back from the stack.

Now here comes the part I struggled to understand. When debugging the program locally with gdb, I quickly found the address the flag was loaded to, just had to break between the fopen and fgets calls:

  0x555555555344 <main+154>       lea    rdi, [rip+0x2d55]        # 0x5555555580a0 <flag>
  0x55555555534b <main+161>       call   0x555555555080 <fgets@plt>
→ 0x555555555350 <main+166>       mov    rax, QWORD PTR [rbp-0x38]

From here, all we have to do is write this address to the stack, and read the flag from there using %s format string…. Is what you could do, if the binary was loaded to the same address every time, which of course it wasn’t.

However, if you could calculate the offset between a known address, i.e. something on the stack, and the flag, you could read the address from the stack, increment the address with the offset, and then write that address to the stack, and read from it. This is what I ended up doing, and probably the right thing to do, but let’s understand it (somewhat).

The addresses are randomized, how can you get an offset then, if they always change? Well, here comes the learning part. Apparently, gdb loads the binary to the same address, even with address randomization. Why? Long story short, this is how gdb operates. So if you were to use the format string exploit when running gdb, it would leak the same global address every time, and you could calculate the offset.

After this, all you have to do is:

  • calculate the offset locally
  • read the address, you calculated the offset to
  • increment the address returned
  • write it on the stack using the program, and jump to it

This is easily achieved with pwntools: (since this would count as a ‘one-click’ solution, so I’m hiding it until the end of the semester)

from pwn import *

try:
	conn = remote('challenges.crysys.hu', 5006)
	...

The acquired flag is:

cd21{HIDDEN}

← Back to SecChallenge21

all tags