> For the complete documentation index, see [llms.txt](https://simon-6.gitbook.io/simoncyber/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://simon-6.gitbook.io/simoncyber/ctf-writeups/picoctf-2025/binary-exploitation/pie-time-easy.md).

# PIE TIME (easy)

<figure><img src="/files/N1LN7MOWhEbLSLFrAXNu" alt=""><figcaption></figcaption></figure>

This is my first Binary Exploitation (or pwn...) CTF! Hopefully it's not too hard...&#x20;

***

First thing we should try is to run the file.&#x20;

<figure><img src="/files/t23Y4Pe9DdDw0X6HFnfT" alt=""><figcaption></figcaption></figure>

I used gcc (a C compiler) and outputted it as a executable file. Seems like we need to input a address.

***

The challenge links the source code and binary. Let's take a closer look in the source code.&#x20;

```c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void segfault_handler() {
  printf("Segfault Occurred, incorrect address.\n");
  exit(0);
}

int win() {
  FILE *fptr;
  char c;

  printf("You won!\n");
  // Open file
  fptr = fopen("flag.txt", "r");
  if (fptr == NULL)
  {
      printf("Cannot open file.\n");
      exit(0);
  }

  // Read contents from file
  c = fgetc(fptr);
  while (c != EOF)
  {
      printf ("%c", c);
      c = fgetc(fptr);
  }

  printf("\n");
  fclose(fptr);
}

int main() {
  signal(SIGSEGV, segfault_handler);
  setvbuf(stdout, NULL, _IONBF, 0); // _IONBF = Unbuffered

  printf("Address of main: %p\n", &main);

  unsigned long val;
  printf("Enter the address to jump to, ex => 0x12345: ");
  scanf("%lx", &val);
  printf("Your input: %lx\n", val);

  void (*foo)(void) = (void (*)())val;
  foo();
}
```

If I'm being honest, I don't really know C too well, so we'll go through this step by step.

***

PIE (Position Independent Executable) is a security feature. Normally, when a program loads into memory, it expects to be at specific, predictable memory addresses. With PIE enabled, the program can be loaded at *random* memory addresses each time it runs. Hence, if an attacker knows exactly where code/data is in memory, they can exploit vulnerabilities more easily

{% hint style="info" %}
**ELF** stands for "Executable and Linkable Format" - it's the standard file format for executables, object code, and libraries on Linux systems (similar to how `.exe` files are used on Windows). When you compile a C program on Linux, you typically get an ELF file.
{% endhint %}

Additionally, let me explain memory addresses especially in C a bit. Your computer RAM is basically like an array of bytes. And every byte has their own unique memory address. For example some memory address of like 0x7ffd8b2a1c3c may store a value of 4. This means some line like "  FILE \*fptr;" declares a pointer.

* `FILE` is a data type (a struct) defined in C's standard library that represents a file
* `*` means this is a pointer (it will store a memory address)
* `fptr` is the variable name (short for "file pointer")

```c
  c = fgetc(fptr);
  while (c != EOF)
  {
      printf ("%c", c);
      c = fgetc(fptr);
  }

  printf("\n");
  fclose(fptr);
```

This part of the code I think is notable since there's many things I don't recognize.&#x20;

```
 c = fgetc(fptr); #fgetc is the file get character function that reads one character per file
```

"c" then stores that character. We then have a while loop that loops until the "END OF FILE". Basically just checks every line, making is to that we print the entire file regarding it's text.

```c
  signal(SIGSEGV, segfault_handler);
  setvbuf(stdout, NULL, _IONBF, 0); // _IONBF = Unbuffered

  printf("Address of main: %p\n", &main);

  unsigned long val;
  printf("Enter the address to jump to, ex => 0x12345: ");
  scanf("%lx", &val);
  printf("Your input: %lx\n", val);

  void (*foo)(void) = (void (*)())val;
  foo();
```

```
 signal(SIGSEGV, segfault_handler);
```

This registers a custom handler for segmentation faults (SIGSEGV) which is caused by access to restricted memory. It also calls the `segfault_handler()` which is just a custom function that is called when a system crashes.

```
setvbuf(stdout, NULL, _IONBF, 0); // _IONBF = Unbuffered
```

This makes the output of it buffered, essentially just printing it. After that is our key, it leaks the main address.&#x20;

```
  scanf("%lx", &val);
```

"In C, scanf() is a standard input function used to read formatted data from the standard input stream (stdin), which is usually the keyboard." %lx is the format specifier, and \&val is the variable that stores it. Printing \&val gives the memory address of where it was stored. Printing val just gives the value of where the memory address.&#x20;

```
  void (*foo)(void) = (void (*)())val;
```

{% hint style="info" %}
A function pointer type is a pointer that stores the address of a function, allowing you to call that function indirectly. It is defined by specifying the return type and the parameter types of the function it points to, using the syntax: `return_type`
{% endhint %}

This line stores foo as a function pointer type. Next, it cast your input of val into a memory address, essentially treating your input number as a memory address of a number.&#x20;

### Solving it.

I'll be using references and tools from the site:

{% embed url="<https://deepwiki.com/ctf-wiki/ctf-wiki/2-binary-exploitation-(pwn)>" %}

We'll need to use a debugger and compile the source code. As you may read from the "What is Reverse Engineering", I'll be using GDB. Our goal is essentially trying to find the win function (where it prints out the flag). The distance between the win function and the main function is consistent, so we are trying to find the offset. The reason PIE can work like this is because it initializes a random start, but the offset/distances after the initial is still the same.&#x20;

So after compliging the C code with gcc and using gdb.&#x20;

<figure><img src="/files/0DTIJx2fLswZhR8fRMm9" alt=""><figcaption></figcaption></figure>

We get the memory address 12aa and 134c. If we some hex calculator, we find the offset is 162.

<figure><img src="/files/R4Mt08mmpeGCFQTYJT3Z" alt=""><figcaption></figcaption></figure>

So when we connect again to get our main address, by ubtracting 162 (Or A2), we get our memory address.&#x20;

<figure><img src="/files/L6B9TTtoAyRRqa1HbLlo" alt=""><figcaption></figcaption></figure>
