32 minute read

In this post, I will be going over the challenges that I solved during picoCTF 2019. picoCTF is a capture the flag competition aimed at Middle School and High School students; it is created by students at Carnegie Mellon. It may be aimed for younger students but as I am still learning reverse engineering it was perfect for me. I participated with Auburn’s Ethical Hacking Club during the competition.

vault-door-training

Problem

Your mission is to enter Dr. Evil’s laboratory and retrieve the blueprints for his Doomsday Project. The laboratory is protected by a series of locked vault doors. Each door is controlled by a computer and requires a password to open. Unfortunately, our undercover agents have not been able to obtain the secret passwords for the vault doors, but one of our junior agents obtained the source code for each vault’s computer! You will need to read the source code for each level to figure out what the password is for that vault door. As a warmup, we have created a replica vault in our training facility. The source code for the training vault is here: VaultDoorTraining.java

Hint: The password is revealed in the program’s source code.

Solution

This challenge is meant to get your feet wet with reverse engineering. If you know Java the challenge is quite trivial.

String userInput = scanner.next();
String input = userInput.substring("picoCTF{".length(),userInput.length()-1);

We see it grabs the user’s input and parses out picoCTF{ and the last character; which should be } if we are following the flag format.

if (vaultDoor.checkPassword(input)) {
 System.out.println("Access granted.");
} else {
 System.out.println("Access denied!");
}

After that, it passes our input to a checkPassword function.

public boolean checkPassword(String password) {
    return password.equals("w4rm1ng_Up_w1tH_jAv4_ca5ae7fcc95");
}

Looking into checkPassword we see it compares the parameter against w4rm1ng_Up_w1tH_jAv4_ca5ae7fcc95 Therefore, if we send picoCTF{w4rm1ng_Up_w1tH_jAv4_ca5ae7fcc95} to the program we will be awarded the good boy message Access granted.

flag: picoCTF{w4rm1ng_Up_w1tH_jAv4_ca5ae7fcc95}

vault-door-1

Problem

This vault uses some complicated arrays! I hope you can make sense of it, special agent. The source code for this vault is here: VaultDoor1.java

Hint: Look up the charAt() method online.

Solution

Looking at the source code for this challenge it is very similar to the training challenge. However checkPassword is different.

public boolean checkPassword(String password) {
 return password.length() == 32 &&
 password.charAt(0) == 'd' &&
 password.charAt(29) == '3' &&
 password.charAt(4) == 'r' &&
 password.charAt(2) == '5' &&
 password.charAt(23) == 'r' &&
 password.charAt(3) == 'c' &&
 password.charAt(17) == '4' &&
 password.charAt(1) == '3' &&
 password.charAt(7) == 'b' &&
 password.charAt(10) == '_' &&
 password.charAt(5) == '4' &&
 password.charAt(9) == '3' &&
 password.charAt(11) == 't' &&
 password.charAt(15) == 'c' &&
 password.charAt(8) == 'l' &&
 password.charAt(12) == 'H' &&
 password.charAt(20) == 'c' &&
 password.charAt(14) == '_' &&
 password.charAt(6) == 'm' &&
 password.charAt(24) == '5' &&
 password.charAt(18) == 'r' &&
 password.charAt(13) == '3' &&
 password.charAt(19) == '4' &&
 password.charAt(21) == 'T' &&
 password.charAt(16) == 'H' &&
 password.charAt(27) == 'd' &&
 password.charAt(30) == '8' &&
 password.charAt(25) == '_' &&
 password.charAt(22) == '3' &&
 password.charAt(28) == '0' &&
 password.charAt(26) == '9' &&
 password.charAt(31) == 'f';
}

checkPassword still compares our input against a hardcoded string, however, this time the original plaintext version of the string has been obfuscated. charAt(int index) takes an integer that represents an index in the original string.

For example if you had hello and called hello.charAt(1) you would get e in return.

There are multiple ways to solve this and my solution is most definitely not the prettiest. If we copy all the lines that contain password.charAt... into a text file called parsed_output we can run this bash script to get the flag.

$ sort -V parsed_output | cut -c 25 | tr -d "\n"
d35cr4mbl3_tH3_cH4r4cT3r5_9d038f%

In this bash script we first sort the file using the version format. This works since in our input text we have strings that start the same and have a differing index value that can be treated as a “version-number”. After that, we cut all the text out except the characters in the flag, and then we replace all the newline characters with a blank character.

flag: picoCTF{d35cr4mbl3_tH3_cH4r4cT3r5_9d038f}

vault-door-3

Problem

This vault uses for-loops and byte arrays. The source code for this vault is here: VaultDoor3.java

Hint: Make a table that contains each value of the loop variables and the corresponding buffer index that it writes to.

Solution

public boolean checkPassword(String password) {
 if (password.length() != 32) {
 return false;
 }
 char[] buffer = new char[32];
 int i;
 for (i=0; i<8; i++) {
 buffer[i] = password.charAt(i);
 }
 for (; i<16; i++) {
 buffer[i] = password.charAt(23-i);
 }
 for (; i<32; i+=2) {
 buffer[i] = password.charAt(46-i);
 }
 for (i=31; i>=17; i-=2) {
 buffer[i] = password.charAt(i);
 }
 String s = new String(buffer);
 return s.equals("jU5t_a_sna_3lpm11ga4e_u_4_m9rf48");
}

As we have seen before the main program is the same and the difference is in the checkPassword function. This time the function rearranges our input which needs to end up equalling jU5t_a_sna_3lpm11ga4e_u_4_m9rf48.

for (i=0; i<8; i++) {
 buffer[i] = password.charAt(i);
}

We see for the first 8 characters there is no character shuffling.

Input Output Character
0 0 j
1 1 U
2 2 5
3 3 t
4 4 _
5 5 a
6 6 _
7 7 s
for (; i<16; i++) {
 buffer[i] = password.charAt(23-i);
}

However, characters 8 - 15 in the output array do NOT map to characters 8 - 15 in our original input array. We see that they do map to 23 - i; essentially writing the characters in reverse. With this, we can create a table for characters 8 - 15.

Input Output Character
8 15 1
9 14 m
10 13 p
11 12 l
12 11 3
13 10 _
14 9 a
15 8 n

For the next chunk of characters we do this again but skip every other character.

for (; i<32; i+=2) {
 buffer[i] = password.charAt(46-i);
}
Input Output Character
16 30 4
18 28 r
20 26 m
22 24 4
24 22 u
26 20 e
28 18 a
30 16 1

Now for the last transformation. For this we use the characters we skipped over in the last transformation.

for (i=31; i>=17; i-=2) {
 buffer[i] = password.charAt(i);
}
Input Output Character
31 31 8
29 29 f
27 27 9
25 25 _
23 23 _
21 21 _
19 19 4
17 17 g

Therefore, putting all the mappings together we get out input strings should be picoCTF{jU5t_a_s1mpl3_ang4r4m_4_u_e9af18}.

flag: picoCTF{jU5t_a_s1mpl3_an4gr4m_4_u_e9af18}

vault-door-4

Problem

This vault uses ASCII encoding for the password. The source code for this vault is here: VaultDoor4.java

Hint: Use a search engine to find an “ASCII table”.

Hint: You will also need to know the difference between octal, decimal, and hexadecimal numbers.

Solution

First, let’s look at the checkPassword function.

public boolean checkPassword(String password) {
    byte[] passBytes = password.getBytes();
    byte[] myBytes = {
        106 , 85 , 53 , 116 , 95 , 52 , 95 , 98 ,
        0x55, 0x6e, 0x43, 0x68, 0x5f, 0x30, 0x66, 0x5f,
        0142, 0131, 0164, 063 , 0163, 0137, 070 , 060 ,
        'f' , '8' , 'e' , '1' , 'e' , '0' , '4' , '7' ,
    };
    for (int i=0; i<32; i++) {
        if (passBytes[i] != myBytes[i]) {
            return false;
        }
    }
    return true;
}

We can see that there’s no array shuffling this time, thankfully. This time it uses different ASCII representations to obscure the flag; with each line being a different representation. The first line uses decimal values, the second line hexadecimal, the third octal, and the last plain ASCII.

Using Python 2 we can quickly convert this array. Python 3’s octal format is not 0NUMBER like Java, but Python 2’s is. Python 3 uses 0oNUMBER like 0o142.

enc = [
 106 , 85 , 53 , 116 , 95 , 52 , 95 , 98 ,
 0x55, 0x6e, 0x43, 0x68, 0x5f, 0x30, 0x66, 0x5f,
 0142, 0131, 0164, 063 , 0163, 0137, 070 , 060]
plain = ['f' , '8' , 'e' , '1' , 'e' , '0' , '4' , '7']
print ''.join(map(chr, enc) + plain)

Running this in Python 2 we can get our flag.

flag: picoCTF{jU5t_4_bUnCh_0f_bYt3s_80f8e1e047}.

vault-door-5

Problem

In the last challenge, you mastered octal (base 8), decimal (base 10), and hexadecimal (base 16) numbers, but this vault door uses a different change of base as well as URL encoding! The source code for this vault is here: VaultDoor5.java

Hint: You may find an encoder/decoder tool helpful, such as https://encoding.tools/

Hint: Read the wikipedia articles on URL encoding and base 64 encoding to understand how they work and what the results look like.

Solution

Looking at the source code this challenge is quite trivial.

public boolean checkPassword(String password) {
 String urlEncoded = urlEncode(password.getBytes());
 String base64Encoded = base64Encode(urlEncoded.getBytes());
 String expected = "JTYzJTMwJTZlJTc2JTMzJTcyJTc0JTMxJTZlJTY3JTVm"\
 + "JTY2JTcyJTMwJTZkJTVmJTYyJTYxJTM1JTY1JTVmJTM2"\
 + "JTM0JTVmJTY0JTYxJTM4JTM4JTMyJTY0JTMwJTMx";
 return base64Encoded.equals(expected);
}

urlEncode parses a string and converts it to the URL Encoded equivalent. After that, it simply uses a base 64 encoding scheme on the URL encoded output.

Now that we know how this challenge obscures the input it is relatively easy to solve. We can use a Python script to solve this.

import base64
print(''.join(map(chr, [int(x, 16) for x in base64.b64decode(expected).decode().strip('%').split('%')])))

In this script, we first decode the base 64 encryption. Following that we strip out the leading and ending percent signs, if we do not it will mess up the latter steps. After this, we will treat the percent sign as a delimiter and split the string at each instance of one. At this point, we now have a list of hexadecimal characters that need to be converted to their ASCII equivalents. We first convert the string representation of the hexadecimal numbers to integers and pass them to chr using map.

Running the Python script we get this in return; c0nv3rt1ng_fr0m_ba5e_64_da882d01.

flag: picoCTF{c0nv3rt1ng_fr0m_ba5e_64_da882d01}

vault-door-6

Problem

This vault uses an XOR encryption scheme. The source code for this vault is here: VaultDoor6.java

Hint: If X ^ Y = Z, then Z ^ Y = X. Write a program that decrypts the flag based on this fact.

Solution

Looking at the checkPassword function we see this challenge obscures the flag with a simple XOR encryption.

public boolean checkPassword(String password) {
 if (password.length() != 32) {
 return false;
 }
 byte[] passBytes = password.getBytes();
 byte[] myBytes = {
 0x3b, 0x65, 0x21, 0xa , 0x38, 0x0 , 0x36, 0x1d,
 0xa , 0x3d, 0x61, 0x27, 0x11, 0x66, 0x27, 0xa ,
 0x21, 0x1d, 0x61, 0x3b, 0xa , 0x2d, 0x65, 0x27,
 0xa , 0x63, 0x65, 0x64, 0x67, 0x37, 0x6d, 0x62,
 };
 for (int i=0; i<32; i++) {
 if (((passBytes[i] ^ 0x55) - myBytes[i]) != 0) {
 return false;
 }
 }
 return true;
}

The key used in the XOR encryption is 0x55. For XOR encryptions the key that is used to encrypt is also used to decrypt messages, and the algorithm used to decrypt is the same used to encrypt.

To solve this all we need to do is run a quick Python script.

enc = [
 0x3b, 0x65, 0x21, 0xa , 0x38, 0x0 , 0x36, 0x1d,
 0xa , 0x3d, 0x61, 0x27, 0x11, 0x66, 0x27, 0xa ,
 0x21, 0x1d, 0x61, 0x3b, 0xa , 0x2d, 0x65, 0x27,
 0xa , 0x63, 0x65, 0x64, 0x67, 0x37, 0x6d, 0x62,]
print(''.join(map(chr, [ x ^ 0x55 for x in enc])))

We use a simple list comprehension to quickly XOR all the bytes, following that we print out the ASCII representation. We get the following output: n0t_mUcH_h4rD3r_tH4n_x0r_6012b87.

flag: picoCTF{n0t_mUcH_h4rD3r_tH4n_x0r_6012b87}

vault-door-7

Problem

This vault uses bit shifts to convert a password string into an array of integers. Hurry, agent, we are running out of time to stop Dr. Evil’s nefarious plans! The source code for this vault is here: VaultDoor7.java

Hint: Use a decimal/hexadecimal converter such as this one: https://www.mathsisfun.com/binary-decimal-hexadecimal-converter.html

Hint: You will also need to consult an ASCII table such as this one: https://www.asciitable.com/

Solution

Looking at the source for this challenge we see it is a little different than the other ones we have faced. This time we need to understand bit shifting.

public boolean checkPassword(String password) {
 if (password.length() != 32) {
 return false;
 }
 int[] x = passwordToIntArray(password);
 return x[0] == 1096770097
 && x[1] == 1952395366
 && x[2] == 1600270708
 && x[3] == 1601398833
 && x[4] == 1716808014
 && x[5] == 1734304870
 && x[6] == 895891557
 && x[7] == 1681142832;
}

We see within the normal checkPassword function we pass our input password to a passwordToIntArray function. The values returned by this function are checked against hardcoded values. The first thing we should do is looking into how the output array is generated by passwordToIntArray.

public int[] passwordToIntArray(String hex) {
 int[] x = new int[8];
 byte[] hexBytes = hex.getBytes();
 for (int i=0; i<8; i++) {
 x[i] = hexBytes[i*4] << 24
 | hexBytes[i*4+1] << 16
 | hexBytes[i*4+2] << 8
 | hexBytes[i*4+3];
 }
 return x;
}

Inside passwordToIntArray we see it takes a string which it refers to as hex. It generates an empty int array of size 8 and then uses string’s getBytes function on hex. This will encode the string as a sequence of bytes and return the output as an array. Following the conversion of our input array, we enter a for loop that runs 8 times, with four characters of our input string being transformed each run.

The << refers to a bit shift, a signed bit shift to the left, in Java. So if we had 0001 and performed « 2 we would have 0100 as an output.

Therefore, if we perform a 24-bit shift to the left on a hexadecimal representation of an ASCII character its original 8 bits will be shifted 24 places to the left. In Java, an integer, which is the type these bits are being transformed into, is 4 bytes or 32 bits. If our first character was an A it would be 0x41 in hexadecimal and 0b1000001 in binary. To convert this to an integer we will need to zero extend to the 31st place. Therefore our A would look like 0b00000000000000000000000001000001 and performing a left bit shift of 24 would convert our output to 0b10000010000000000000000000000000. So, in short, we push our bits that encode an A to the most significant bit. This is done three more times, except we do a bit shift of 16, then 8, and then of 0. All we are doing is smashing four hexadecimal characters together. If we originally had ABCD they would be encoded as [0x41, 0x42, 0x43, 0x44] in our hexBytes but then in the for loop, they are smashed together to equal 0x41424344 or 1094861636.

To get our flag all we need to do is decode the hardcoded values and translate them into their original 1 byte values and then convert them back to ASCII.

dec = [1096770097, 1952395366,
 1600270708, 1601398833,
 1716808014, 1734304870,
 895891557, 1681142832]
for d in dec:
 value = hex(d)[2:]
 for i in range(0, len(value), 2):
 print(chr(int(value[i: i+2],16)),end ='')

Running the Python script we will get A_b1t_0f_b1t_sh1fTiNg_df5f8ed440 which if we input into the program we get the success message.

flag: picoCTF{A_b1t_0f_b1t_sh1fTiNg_df5f8ed440}

vault-door-8

Problem

Apparently Dr. Evil’s minions knew that our agency was making copies of their source code, because they intentionally sabotaged this source code in order to make it harder for our agents to analyze and crack into! The result is a quite mess, but I trust that my best special agent will find a way to solve it. The source code for this vault is here: VaultDoor8.java

Hint: Clean up the source code so that you can read it and understand what is going on.

Hint: Draw a diagram to illustrate which bits are being switched in the scramble() method, then figure out a sequence of bit switches to undo it. You should be able to reuse the switchBits() method as is.

Solution

Out of all the vault door challenge this took me the most amount of time. First things first, we need to clean up this obfuscated source code. I used tutorialspoint online Java formatter, but anything will do the trick.

// These pesky special agents keep reverse engineering our source code and then
// breaking into our secret vaults. THIS will teach those sneaky sneaks a
// lesson.
//
// -Minion #0891
import java.util.*;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
class VaultDoor8 {
 public static void main(String args[]) {
 Scanner b = new Scanner(System.in);
 System.out.print("Enter vault password: ");
 String c = b.next();
 String f = c.substring(8, c.length() - 1);
 VaultDoor8 a = new VaultDoor8();
 if (a.checkPassword(f)) {
 System.out.println("Access granted.");
 } else {
 System.out.println("Access denied!");
 }
 }
 public char[] scramble(String password) { /* Scramble a password by transposing pairs of bits. */
 char[] a = password.toCharArray();
 for (int b = 0; b < a.length; b++) {
 char c = a[b];
 c = switchBits(c, 1, 2);
 c = switchBits(c, 0, 3); /* c = switchBits(c,14,3); c = switchBits(c, 2, 0); */
 c = switchBits(c, 5, 6);
 c = switchBits(c, 4, 7);
 c = switchBits(c, 0, 1); /* d = switchBits(d, 4, 5); e = switchBits(e, 5, 6); */
 c = switchBits(c, 3, 4);
 c = switchBits(c, 2, 5);
 c = switchBits(c, 6, 7);
 a[b] = c;
 }
 return a;
 }
 public char switchBits(char c, int p1, int p2) {
 /* Move the bit in position p1 to position p2, and move the bit
 that was in position p2 to position p1. Precondition: p1 < p2 */
 char mask1 = (char)(1 << p1);
 char mask2 = (char)(1 << p2); /* char mask3 = (char)(1<<p1<<p2); mask1++; mask1--; */
 char bit1 = (char)(c & mask1);
 char bit2 = (char)(c & mask2);
 /*
 System.out.println("bit1 " + Integer.toBinaryString(bit1));
 System.out.println("bit2 " + Integer.toBinaryString(bit2));
 */
 char rest = (char)(c & ~(mask1 | mask2));
 char shift = (char)(p2 - p1);
 char result = (char)((bit1 << shift) | (bit2 >> shift) | rest);
 return result;
 }
 public boolean checkPassword(String password) {
 char[] scrambled = scramble(password);
 char[] expected = {
 0xF4,0xC0,0x97, 0xF0, 0x77, 0x97, 0xC0, 0xE4,
 0xF0, 0x77, 0xA4, 0xD0, 0xC5, 0x77, 0xF4, 0x86,
 0xD0, 0xA5, 0x45, 0x96, 0x27, 0xB5, 0x77, 0xC0,
 0xB4, 0xD1, 0xD2, 0x85, 0xA4, 0xA5, 0xC1,0x85};
 return Arrays.equals(scrambled, expected);
 }
}

Instead of trying to figure out how the bit swapping works I just changed the source code by reversing the calls in scramble. We will also need to change the input parameter to a character array since we will no longer be passing it as a string.

public char[] scramble(char [] a) {
 for (int b = 0; b < a.length; b++) {
 char c = a[b];
 c = switchBits(c, 6, 7);
 c = switchBits(c, 2, 5);
 c = switchBits(c, 3, 4);
 c = switchBits(c, 0, 1);
 c = switchBits(c, 4, 7);
 c = switchBits(c, 5, 6);
 c = switchBits(c, 0, 3);
 c = switchBits(c, 1, 2);
 a[b] = c;
 }
 return a;
}

If we then change checkPassword to pass expected to scramble we can de-scramble the obscured flag and print out the output we should receive the flag. An additional change to checkPassword is to remove the parameter that is passed to it. Also, we will need to change the main so it does not expect input from us anymore.

public void checkPassword() {

 char[] expected = {
 0xF4,0xC0,0x97, 0xF0, 0x77, 0x97, 0xC0, 0xE4,
 0xF0, 0x77, 0xA4, 0xD0, 0xC5, 0x77, 0xF4, 0x86,
 0xD0, 0xA5, 0x45, 0x96, 0x27, 0xB5, 0x77, 0xC0,
 0xB4, 0xD1, 0xD2, 0x85, 0xA4, 0xA5, 0xC1,0x85};
 char[] descrambled = scramble(expected);
 System.out.println(descrambled);
}

public static void main(String args[]) {
 VaultDoor8 a = new VaultDoor8();
 a.checkPassword();
}

Now running the program we will get s0m3_m0r3_b1t_sh1fTiNg_0c59dbf4d as the output. Running the original program and supplying that as the input will give us the good boy message. Therefore, our flag is picoCTF{s0m3_m0r3_b1t_sh1fTiNg_0c59dbf4d}.

asm1

Problem

What does asm1(0x1b4) 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/asm1_3_afba952b3219ced79409c353bf73fbd8.

Hint: assembly conditions

Solution

Looking at the assembly source code we see that there are multiple jump conditions we much pass.

Our first condition is below. We check the value pointed to by EBP+0x8 against 0x421. Since this program took one parameter, 0x1b4, we know that EBP+0x8 is the address pointing to the first passed parameter. With EBP+0x4 being the program’s runtime path. Since 0x1B4 < 0x421 we will not take the jump condition as it is JG or jump greater, therefore, we will move onto line 12.

<+3>: cmp DWORD PTR [ebp+0x8],0x421
<+10>: jg 0x512 <asm1+37>

In this check, we compare the passed parameter against 0x1B4. Since 0x1B4 == 0x1B4 we will not take this jump as it is jne or [jump not equal(https://www.aldeid.com/wiki/X86-assembly/Instructions/jg); on to the next line.

<+12>: cmp DWORD PTR [ebp+0x8],0x1b4
<+19>: jne 0x50a <asm1+29>

Here we move 0x421 into EAX. Following this, we perform 0x1B4 + 0x13; after this operation, EAX will contain 0x1C7 and jump to line 60.

<+21>: mov eax,DWORD PTR [ebp+0x8]
<+24>: add eax,0x13
<+27>: jmp 0x529 <asm1+60>

We now clean up our stack and exit this function. Since EAX is treated as the return value we return 0x1C7. Therefore, our flag is 0x1C7.

<+60>: pop ebp
<+61>: ret

asm2

Problem

What does asm2(0x6,0x24) 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/asm2_6_88bbaaae0b7723b33c39fce07d342e36.

Solution

The program starts with the stack looking like so:

+---------+
| 0x24 | <-- ebp + 0xc
+---------+
| 0x6 | <-- ebp + 0x8
+---------+
| ret | <-- ebp + 0x4
+---------+
| old ebp | <-- ebp
+---------+
<+0>: push ebp
<+1>: mov ebp,esp
<+3>: sub esp,0x10

After we set up the stack with the stack prologue it now looks like this:

+---------+
| 0x24 | <-- ebp + 0xc
+---------+
| 0x6 | <-- ebp + 0x8
+---------+
| ret | <-- ebp + 0x4
+---------+
| old ebp | <-- ebp
+---------+
| | <-- ebp - 0x4
+---------+
| | <-- ebp - 0x8
+---------+
| | <-- ebp - 0xc
+---------+
| | <-- ebp - 0x10
+---------+

This program starts by loading the second passed parameter, 0x24, into EAX. Following this, we load the contents of EAX into temporary storage at EBP-0x4, essentially EBP-0x4 points to the second parameter. We do the same for the first parameter, 0x6, but have it stored at EBP-0x8 and then jump to line 31.

<+6>: mov eax,DWORD PTR [ebp+0xc] ; eax = 0x24
<+9>: mov DWORD PTR [ebp-0x4],eax ; var1 = 0x24
<+12>: mov eax,DWORD PTR [ebp+0x8] ; eax = 0x6
<+15>: mov DWORD PTR [ebp-0x8],eax ; var2 = 0x6
<+18>: jmp 0x50c <asm2+31>

After this code block our stack will look like this:

+---------+
| 0x24 | <-- ebp + 0xc
+---------+
| 0x6 | <-- ebp + 0x8
+---------+
| ret | <-- ebp + 0x4
+---------+
| old ebp | <-- ebp
+---------+
| 0x24 | <-- ebp - 0x4
+---------+
| 0x6 | <-- ebp - 0x8
+---------+
| | <-- ebp - 0xc
+---------+
| | <-- ebp - 0x10
+---------+

Here we check and see if the value at EBP-0x8 less than or equal to 0x3C75. Currently EBP-0x8 contains 0x6 therefore we will make the jump to line 20.

<+31>: cmp DWORD PTR [ebp-0x8],0x3c75
<+38>: jle 0x501 <asm2+20>

At line 20 we add 0x1 to EBP-0x4 and add 0xF9 to EBP-0x8. Then we do the same compare and conditional jump. For the jump to fail EBP-0x8 needs to be greater than 0x501. Since we add 0xF9 each time to EBP-0x8 it will take 63 iterations to complete.

<+20>: add DWORD PTR [ebp-0x4],0x1
<+24>: add DWORD PTR [ebp-0x8],0xf9
<+31>: cmp DWORD PTR [ebp-0x8],0x3c75
<+38>: jle 0x501 <asm2+20>

After the first iteration of our loop our stack will look like:

+---------+
| 0x24 | <-- ebp + 0xc
+---------+
| 0x6 | <-- ebp + 0x8
+---------+
| ret | <-- ebp + 0x4
+---------+
| old ebp | <-- ebp
+---------+
| 0x25 | <-- ebp - 0x4
+---------+
| 0xff | <-- ebp - 0x8
+---------+
| | <-- ebp - 0xc
+---------+
| | <-- ebp - 0x10
+---------+

After not jumping we load EAX with EBP-0x4. Since we ran the loop 63 times EBP-0x4 will equal 0x63 (0x24 + 63). We then clean up the stack and leave the function.

<+40>: mov eax,DWORD PTR [ebp-0x4]
<+43>: leave
<+44>: ret

asm3

Problem

What does asm3(0xfe8cf7a4,0xf55018af,0xb8c70926) 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/asm3_6_22c78ed107ca0b7dd11f868d7203cf8c.

Solution

<+0>: push ebp
<+1>: mov ebp,esp
<+3>: xor eax,eax

After we setup the stack with the first two lines and clear EAX our stack will look like this:

+----------------+
| 0xb8c70926 | <-- ebp + 0x10
+----------------+
| 0xf55018af | <-- ebp + 0xc
+----------------+
| 0xfe8cf7a4 | <-- ebp + 0x8
+----------------+
| ret | <-- ebp + 0x4
+----------------+
| old ebp | <-- ebp
+----------------+
<+5>: mov ah,BYTE PTR [ebp+0x9]
<+8>: shl ax,0x10

We then load EBP+0x9 into AH which will be 0xF7. Following this, we perform a shift on AX which contains 0xF700. Performing a left shift of 16 will completely shift everything off of AX, therefore, AX after this operation will contain 0.

Index EBP+Index
0x8 0xA4
0x9 0xF7
0xA 0x8C
0xB 0xFE
0xC 0xAF
<+12>: sub al,BYTE PTR [ebp+0xd] ; AL = 0x0 - 0x18 = 0xE8

After the prior shift left we will subtract AL by the contents of EBP+0xD; which is 0x18. Performing this operation will set AL with 0xE8, which is a negative 0x18 in two’s complement.

<+15>: add ah,BYTE PTR [ebp+0xe] AH = 0x0 + 0x50 = 0x50

Now we add EBP+0xE to AH; which is 0x50. Since AH is still 0x0 from last time the resulting operation will have AH be 0x50.

<+18>: xor ax,WORD PTR [ebp+0x12]

Then we perform an XOR operation on AX with EBP+0x12; which is 0xE82F. So the value in AX after this operation will be 0x502F NOTE: This time the value we pull is larger. This is because we use the WORD keyword instead of BYTE as we have done in the past. This little difference tripped me up the first time attempting this challenge. Therefore, our flag is 0xE82F.

asm4

Problem

What will asm4(“picoCTF_c1373”) 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/asm4_5_ca12dca0134f6b54a52c905ffc1e5b35.

Solution

<+0>: push ebp
<+1>: mov ebp,esp
<+3>: push ebx
<+4>: sub esp,0x10

After the stack prologue our stack will look like this:

+----------------+
| picoCTF_c1373 | <-- ebp + 0x8
+----------------+
| ret | <-- ebp + 0x4
+----------------+
| old ebp | <-- ebp
+----------------+
| | <-- ebp - 0x4
+----------------+
| | <-- ebp - 0x8
+----------------+
| | <-- ebp - 0xc
+----------------+
| | <-- ebp - 0x10
+----------------+
<+7>: mov DWORD PTR [ebp-0x10],0x247 ; var2 = 0x247
<+14>: mov DWORD PTR [ebp-0xc],0x0 ; var1 = 0
<+21>: jmp 0x518 <asm4+27>

The first few lines will load some hardcoded values into local variables. After these lines the stack will look like this:

+----------------+
| picoCTF_c1373 | <-- ebp + 0x8
+----------------+
| ret | <-- ebp + 0x4
+----------------+
| old ebp | <-- ebp
+----------------+
| | <-- ebp - 0x4
+----------------+
| | <-- ebp - 0x8
+----------------+
| 0x0 | <-- ebp - 0xc
+----------------+
| 0x247 | <-- ebp - 0x10
+----------------+

1 and 1 = 1, ZF = 0

<+23>: add DWORD PTR [ebp-0xc],0x1 ; var1 += 1
<+27>: mov edx,DWORD PTR [ebp-0xc] ; edx = var1
<+30>: mov eax,DWORD PTR [ebp+0x8] ; eax = input
<+33>: add eax,edx ; eax = input[var1]
<+35>: movzx eax,BYTE PTR [eax] ;
<+38>: test al,al ; if al != 0
<+40>: jne 0x514 <asm4+23> ; jump 23

We now enter a loop block, line 23 is the loop counter incrementer but as this is the first iteration of the loop we skip over it. In the loop, we add the counter to the input character array, which starts as picoCTF_c1373. Then we do a test on the lower 8 bits of EAX. If the lower 8 bits are 0 then we will leave the loop otherwise we increment the loop counter at var1 and start again.

The only way for those bits to be zero is if we reach the null character in the character array; which denotes the end of the array. What this code block is doing is just getting the length of the input array; which is 13 characters long.

<+42>: mov DWORD PTR [ebp-0x8],0x1 ; var3 = 0
<+49>: jmp 0x587 <asm4+138>

Now we create a new local variable on the stack. So after this, our stack will look like this.

+----------------+
| picoCTF_c1373 | <-- ebp + 0x8
+----------------+
| ret | <-- ebp + 0x4
+----------------+
| old ebp | <-- ebp
+----------------+
| | <-- ebp - 0x4
+----------------+
| 0x1 | <-- ebp - 0x8
+----------------+
| 0xd | <-- ebp - 0xc
+----------------+
| 0x247 | <-- ebp - 0x10
+----------------+
<+138>: mov eax,DWORD PTR [ebp-0xc] ; eax = var1
<+141>: sub eax,0x1 ; eax -= 1
<+144>: cmp DWORD PTR [ebp-0x8],eax if var3 < eax
<+147>: jl 0x530 <asm4+51> jump 51

In this code block, we load var1 into EAX and decrement by 1 and compare it to var3 which is currently 1. If var3 is less than EAX we take a jump. Since during this first loop EAX contains 0xC and var3 contains 1 it is less so we do take the jump.

<+51>: mov edx,DWORD PTR [ebp-0x8] ; edx = var3
<+54>: mov eax,DWORD PTR [ebp+0x8] ; eax = arg1
<+57>: add eax,edx ; eax = eax[edx]
<+59>: movzx eax,BYTE PTR [eax]
<+62>: movsx edx,al ; edx = eax

Here we load EDX with var3, currently 1, and EAX with arg1, the character array. Then we get the character located at the index of var3. So for this first iteration, we grab the character at index 1, so i, and load it into EDX.

<+65>: mov eax,DWORD PTR [ebp-0x8] ; eax = var3
<+68>: lea ecx,[eax-0x1] ; ecx = eax - 1
<+71>: mov eax,DWORD PTR [ebp+0x8] ; eax = arg1
<+74>: add eax,ecx ; eax[ecx]
<+76>: movzx eax,BYTE PTR [eax]
<+79>: movsx eax,al
<+82>: sub edx,eax ; edx -= eax
<+84>: mov eax,edx ; eax = edx
<+86>: mov edx,eax ; edx = eax

Here we load EAX with var3 and then load ECX with EAX - 1. We then grab the index of the character array specified by ECX, which is currently 0.

So as of right now, EAX contains p and EDX contains i. After loading EAX with the sign-extended version of AL, we subtract EDX with EAX. So we subtract p with i and load it into EDX. In other words we calculate arg1[i] - arg1[i-1]. For this first iteration EDX contains 0xFFF9.

<+88>: mov eax,DWORD PTR [ebp-0x10] ; eax = var1
<+91>: lea ebx,[edx+eax*1] ; ebx = edx + eax * 1
<+94>: mov eax,DWORD PTR [ebp-0x8] ; eax = var2
<+97>: lea edx,[eax+0x1] ; edx = eax + 1
<+100>: mov eax,DWORD PTR [ebp+0x8] ; eax = arg1
<+103>: add eax,edx ; eax = eax[edx]
<+105>: movzx eax,BYTE PTR [eax]
<+108>: movsx edx,al ; edx = al

Now we load EAX with var1, 0x247, and calculate EDX + EAX * 1; 0xFFF9 + 0x247 * 1; so EBX will contain 0x240. Then we load EAX with var3, which is currently 1. Then we load EDX with EAX + 1, which during this first iteration is 0x1 + 1 = 0x2. Then we get the character at index EAX + 1 or index 2 for the first loop; so EDX will be c.

<+111>: mov ecx,DWORD PTR [ebp-0x8] ; ecx = var2
<+114>: mov eax,DWORD PTR [ebp+0x8] ; eax = arg1
<+117>: add eax,ecx ; eax = eax[ecx]
<+119>: movzx eax,BYTE PTR [eax]
<+122>: movsx eax,al
<+125>: sub edx,eax ; edx -= eax
<+127>: mov eax,edx ; eax = edx
<+129>: add eax,ebx ; eax += edx

In this block, we grab arg1[var2] which in the first iteration is i since var2 is 1. Then we once again subtract two characters from each other. This time we calculate arg1[i+1] - arg1[i] and load it into EDX. Then we add the result to EBX which contains 0x240 from earlier and set that into EAX.

<+131>: mov DWORD PTR [ebp-0x10],eax
<+134>: add DWORD PTR [ebp-0x8],0x1
<+138>: mov eax,DWORD PTR [ebp-0xc]
<+141>: sub eax,0x1
<+144>: cmp DWORD PTR [ebp-0x8],eax
<+147>: jl 0x530 <asm4+51>

We then load var1 with the contents of EAX. We also increment var3 by 1 and decrement var2 by 1 and check to see if var3 is less than var2. If it is still we loop again.

So after this first iteration of the loop our stack will look like this:

+----------------+
| picoCTF_c1373 | <-- ebp + 0x8
+----------------+
| ret | <-- ebp + 0x4
+----------------+
| old ebp | <-- ebp
+----------------+
| | <-- ebp - 0x4
+----------------+
| 0x2 | <-- ebp - 0x8
+----------------+
| 0xd | <-- ebp - 0xc
+----------------+
| 0x23a | <-- ebp - 0x10
+----------------+

We can port the algorithm that this assembly does to Python with the below code snippet. Running the code snippet will give us 0x1DE; which if we submit to the pico site we get a success.

arg1 = "picoCTF_c1373"
strlen = len(arg1)
seed = 0x247

x = 1
while x < strlen - 1:
 var1 = ord(arg1[x]) - ord(arg1[x-1])
 aux = var1 + seed * 1
 var2 = ord(arg1[x+1]) - ord(arg1[x])
 var2 += aux
 seed = var2
 x += 1
print(hex(seed))

reverse_cipher

Problem

We have recovered a binary and a text file. Can you reverse the flag. Its also found in /problems/reverse-cipher_1_2df63c1ee06e5ed37e35622b009f92ff on the shell server.

Hint: objdump and Gihdra are some tools that could assist with this

Solution

Loading rev into IDA we can start to reverse engineer this binary. IDA should automatically dump you into main for this binary, note it will not always be that way. For the first code blocks that IDA generates we see that the binary tries to load two files flag.txt and rev_this. If they cannot be opened error messages are printed to the screen.

Otherwise, we will use the file pointer to the flag.txt file to read 1 element of 24 bytes from the file. The total number of elements read is stored into EBP-0x24 and compared with 0. If zero bytes were read the program exits; otherwise the characters read are stored in EBP-0x50. We then set a loop counter up and enter a loop that checks to see if the counter is less than or equal to 7. Each iteration of the loop places a character from flag.txt into rev_this; since we run this 8 times this will place picoCTF{ into rev_this.

After the loop, we change the loop variable from 7 to 8 and move on.

In the next loop, we check and see if the loop variable is less than or equal to 0x16. Inside the loop, we load the current character of the array into a local variable. Then we check and see if the loop counter is even or odd. If it is odd we subtract 2 from the current character, otherwise, we add 5 to it. Then we place this modified character into rev_this. After we do this 15 times we leave the loop, place the last character of flag.txt into rev_this and cleanup.

To decode this we will need to perform the opposite of the operations that we performed on each value. Therefore, odd values will need to have 2 added to each character and evens 5 subtracted from each character.

for counter, x in enumerate("w1{1wq8b5.:/f.<"):
 if not counter % 2:
 print(chr(ord(x) - 5), end='')
 else:
 print(chr(ord(x) + 2), end='')

Running the above script we get r3v3rs3d0051a07. Therefore, our flag is picoCTF{r3v3rs3d0051a07}.

Need For Speed

Problem

The name of the game is speed. Are you quick enough to solve this problem and keep it above 50 mph? need-for-speed.

Solution

If we run this program straight out of the box it will tell us we are not fast enough.

Let’s open this binary open in IDA and see what is going on.

The main method is not too interesting as all the functionality has been refactored into functions that are called. Let’s first check out header.

In header we see that it prints off the message Keep this thing over 50 mph! plus a loop that prints off multiple =s. After the loop is done we print a newline character.

The function that is called after header is set_timer. In this function, we call __sysv_signal with a signal of 0xE meaning SIGALRM and a handler of alarm_handler. __sysv_signal handles signals triggered by the operating system. In this case, we are setting up a handling routing for SIGALRM. If the __sysv_signal function works properly we call alarm with 1 second for a timer.

When the 1-second timer goes off alarm triggers a SIGALRM which is handled by alarm_handler; which simply prints Not fast enough. BOOM! and exits the program.

After we exit this function we move on to get_key; which first prints that the key is being created and then move to calculate_key.

This function starts a loop an 0xD8C2071C and counts down to 0xEC61038E. Once at that value we return 0xEC61038E as the key. This is where the program is failing to pass, as when we ran the program initially it prints Creating key... but not Finished. Since our alarm only gives us 1 second to complete the program after it is set this loop is slowing us down enough to not allow the program to complete.

We have a few ways to solve this.

  1. Patch the original binary to change 0xD8C2071C to 0xEC61038F so it only takes one iteration of the loop block
  2. Change the alarms parameter to be greater than 1 second.
  3. Run the program in a wrapper that suppresses signals such as GDB.

The third solution is the easiest but not the most engaging, however, we have more challenges ahead of us so we will just do that.

Running the program with gdb we get this output; with our flag being picoCTF{Good job keeping bus #236cb1c9 speeding along!}

Time’s Up

Problem

Time waits for no one. Can you solve this before time runs out? times-up, located in the directory at /problems/time-s-up_5_44ffbb55dd7707c9e13da8551841f850.

Solution

If you run the program it will print out an equation that it wants you to solve. This binary is similar to the one before in the sense that they are both timed. Unfortunately, the amount of time required to solve the equation by hand is way more than the time allotted. To solve this we can use a script that uses pwntools a popular exploit development library.

from pwn import *

p = process('./times-up')
formula = p.recvline().split(':')[1].strip()
p.recvline()
print(formula)
exec('a=' + formula)
p.sendline(str(a))
p.interactive()

This solution will need to be run on the pico server as it reads flag.txt. Running this on the pico server we get picoCTF{Gotta go fast. Gotta go FAST. #3c4b5166}.