PicoCTF 2019 Rev Writeups


Preface

PicoCTF was my first introduction to the world of CTF when I played PicoCTF 2021. PicoCTF 2019 is the only CTF available on the PicoGym that I did not participate in. As such, I decided to go back and solve the challenges and write up my solutions. Below are the solutions to all the reverse engineering challenges from PicoCTF 2019. Each writeup is a brief (informal) explanation of the solution written while solving the challenge, sorted by difficulty.

Challenge Writeups

vault-door-training (50 points)

For this challenge, we’re given the source code through the file VaultDoorTraining.java

import java.util.*;

class VaultDoorTraining {
    public static void main(String args[]) {
        VaultDoorTraining vaultDoor = new VaultDoorTraining();
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter vault password: ");
        String userInput = scanner.next();
        String input = userInput.substring("picoCTF{".length(),userInput.length()-1);
        if (vaultDoor.checkPassword(input)) {
            System.out.println("Access granted.");
        } else {
            System.out.println("Access denied!");
        }
   }

    // The password is below. Is it safe to put the password in the source code?
    // What if somebody stole our source code? Then they would know what our
    // password is. Hmm... I will think of some ways to improve the security
    // on the other doors.
    //
    // -Minion #9567
    public boolean checkPassword(String password) {
        return password.equals("w4rm1ng_Up_w1tH_jAv4_3808d338b46");
    }
}

If we look at the last return statement, the password is obvious. Wrapping that with our picoCTF{} flag wrapper, we get the flag. picoCTF{w4rm1ng_Up_w1tH_jAv4_3808d338b46}

vault-door-1 (100 points)

Similar to the last challenge, we’re given the java source code for a file called VaultDoor1.java.

public boolean checkPassword(String password) {
    return password.length() == 32 &&
            password.charAt(0)  == 'd' &&
            password.charAt(29) == 'a' &&
            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) == '6' &&
            password.charAt(30) == 'f' &&
            password.charAt(25) == '_' &&
            password.charAt(22) == '3' &&
            password.charAt(28) == 'd' &&
            password.charAt(26) == 'f' &&
            password.charAt(31) == '4';
}

For this challenge, we see that the password verification is a little bit different. Essentially what the new password check function is doing is individually checking each character of the password. For example, the line password.charAt(0) == 'd' && checks to make sure that the character at the zeroth index of the password is d.

Putting them all together, we get our flag. picoCTF{d35cr4mbl3_tH3_cH4r4cT3r5_f6daf4}

vault-door-3 (200 points)

We’re given another java file for this vault door. Password verification is once again different. Let’s see how!

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_3lpm12g94c_u_4_m7ra41");
}
char[] buffer = new char[32];
int i;
for (i=0; i<8; i++) {
    buffer[i] = password.charAt(i);
}

This initializes an array of characters called buffer, and then puts the corresponding first 8 characters (indeces 0 to 7, inclusive) into the same posistions as they were.

for (; i<16; i++) {
    buffer[i] = password.charAt(23-i);
}

For the next 8 characters in buffer (indeces 8 to 15, inclusive), this loop takes from string positions (23-8) to (23-15), or in other words goes from (15 to 8 inclusive). Essentially all this does is reverse the string from positions 8 to 15.

for (; i<32; i+=2) {
    buffer[i] = password.charAt(46-i);
}

For this one, in our iterator, we see that it’s going from indeces 16 to 31. However, it’s iterating by adding 2 to i each time. Note that the previous loop increments i to 16, but i is never run because it does not satisfy the conditional i < 16. Combined with the increment of two, that means we’re actually starting at index 16, and going 16,18,20 … 30. Furthermore, 46 - 16 = 30, so it’s moving backwards from 30 all the way to 46 - 30 = 16.

for (i=31; i>=17; i-=2) {
    buffer[i] = password.charAt(i);
}

This one does a similar take, but moves from indices 31 to 17 (inclusive), and decrements by two each time, so it takes every other character in the string and puts it in the same place as the buffer.

String s = new String(buffer);
return s.equals("jU5t_a_sna_3lpm12g94c_u_4_m7ra41");

The last two lines convert the char array into a string, and checks to make sure that the string we get at the end of all those functions should equal that string.

Let’s write a python script to reverse the operations done to get our original string!

original_string = "jU5t_a_sna_3lpm12g94c_u_4_m7ra41"
password_string = [""] * 32

# take first 8 characters and keep them the same
for i in range(8):
    password_string[i] = original_string[i]

# for the next 8 characters, reverse them
for i in range(8, 16):
    password_string[i] = original_string[23 - i]

# for the next 8 characters, take every other character
for i in range(16, 32, 2):
    password_string[46-i] = original_string[i]

# for the next 8 characters, go backwards from the end
for i in range(31, 15, -2):
    password_string[i] = original_string[i]

# convert to string and print
print(''.join(password_string))

Running this gets us our password, which we can then use to get our flag. picoCTF{jU5t_a_s1mpl3_an4gr4m_4_u_c79a21}

vault-door-4 (250 points)

Another java file, another different password check 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, 0146, 064 ,
        'a' , '8' , 'c' , 'd' , '8' , 'f' , '7' , 'e' ,
    };
    for (int i=0; i<32; i++) {
        if (passBytes[i] != myBytes[i]) {
            return false;
        }
    }
    return true;
}

Looking at this, we see that the comparison loop just checks to make sure that each character is equivalent to a certain number, or another character. In Java (and other languages), characters can also be equivalent to numbers. In this case, we’re given decimal numbers, hex numbers (prefix 0x), octal numbers (prefix 0), and just plain chars (represented with the single quotes ').

Simple python script to convert these with the chr() function. Note that in python octal numbers are represented with 0o, so we have to change our script to accomadate that.

password_bytes = [
    106,
    85,
    53,
    116,
    95,
    52,
    95,
    98,
    0x55,
    0x6E,
    0x43,
    0x68,
    0x5F,
    0x30,
    0x66,
    0x5F,
    0o142,
    0o131,
    0o164,
    0o63,
    0o163,
    0o137,
    0o146,
    0o64,
    "a",
    "8",
    "c",
    "d",
    "8",
    "f",
    "7",
    "e",
]

password_string = ""

for char in password_bytes:
    # if number convert to char
    if isinstance(char, int):
        password_string += chr(char)
    # if string just add it
    else:
        password_string += char

print(password_string)

Running this gets us our password/flag. picoCTF{jU5t_4_bUnCh_0f_bYt3s_f4a8cd8f7e}

vault-door-5 (300 points)

Same java file, checkPassword function changed and this time we have a couple of helper functions.

public String base64Encode(byte[] input) {
    return Base64.getEncoder().encodeToString(input);
}

public String urlEncode(byte[] input) {
    StringBuffer buf = new StringBuffer();
    for (int i=0; i<input.length; i++) {
        buf.append(String.format("%%%2x", input[i]));
    }
    return buf.toString();
}

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

We don’t really need to understand what these do too much, it’s a pretty self explanatory base64encode and urlencode, so we just run the respective decodes. Here is a python script that does that.

# imports that decode stuff for us
import base64
import urllib.parse

encoded = "JTYzJTMwJTZlJTc2JTMzJTcyJTc0JTMxJTZlJTY3JTVmJTY2JTcyJTMwJTZkJTVmJTYyJTYxJTM1JTY1JTVmJTM2JTM0JTVmJTM4JTM0JTY2JTY0JTM1JTMwJTM5JTM1"
base64_decoded = base64.b64decode(encoded)
url_decoded = urllib.parse.unquote(base64_decoded)

print(url_decoded)

Running the script gets us our flag! picoCTF{c0nv3rt1ng_fr0m_ba5e_64_84fd5095}

vault-door-6 (350 points)

Basically same java file, new checkPassword function. This one is different because it uses XOR.

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 , 0x66, 0x36, 0x30, 0x67, 0x6c, 0x64, 0x6c,
    };
    for (int i=0; i<32; i++) {
        if (((passBytes[i] ^ 0x55) - myBytes[i]) != 0) {
            return false;
        }
    }
    return true;
}

For those who don’t yet know how XOR works, check out the Wikipedia. What’s important for us however, is that if you take (A XOR B) XOR B, you’ll get A back again. In other words, the inverse function to XOR is just XOR itself. Check out this stackoverflow post for more information.

We see that we’re given an array of numbers, and that our password XOR 0x55 must be equal to that array. So to find the password, we simply reverse each character in that array by 0x55. Here’s a python script implementing it.

password_pre_xor_bytes = [
    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, 0x66,
    0x36, 0x30, 0x67, 0x6c, 0x64, 0x6c
]

# xor every byte with 0x55 and convert to ascii
password = ''.join([chr(b ^ 0x55) for b in password_pre_xor_bytes])

print(password)

Running that script gets us our flag! picoCTF{n0t_mUcH_h4rD3r_tH4n_x0r_3ce2919}

vault-door-7 (400 points)

Another java file! This time we have a new checkPassword function, and a new helper function. Seems like this one is a bit more complicated and uses bit-shifts.

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;
}

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] == 1734293296
        && x[6] == 842413104
        && x[7] == 1684157793;
}

Let’s first try to figure out what the passwordToIntArray function is doing. The first and last lines of the function tell us that it retuns an integer array x of length 8. The hex is also converted to bytes, and then somehow converted into the int array. Let’s examine how it’s being converted into an int.

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];
}

Essentially, we loop over every set of four bytes (8 loops for 32 total). Each character takes up one byte, or 8 bits. We also see that the fourth hex byte in each group is shifted to the left by 24 bits, three bytes, or 3 characters. The second hex byte is shifted by two characters, and the third by one, and the fourth by zero. They are then paired together using the | OR operator, which essentially serves to concatenate them in this case. I’d reccomend researching more about how this works if you don’t understand. What this means however, is that we can write a python script that converts those integers back to groups of four characters.

x = [1096770097, 1952395366, 1600270708, 1601398833, 1716808014, 1734293296, 842413104, 1684157793]

# convert to integers to hex, then bytes then decode to ascii
reversed = ''.join([bytes.fromhex(hex(i)[2:]).decode('utf-8') for i in x])

print(reversed)

Running the script, we get our flag! picoCTF{A_b1t_0f_b1t_sh1fTiNg_702640db5a}

vault-door-8 (450 points)

We open our java file again, but this time it seems like the file has been scrambled around a bit.

// 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, 0xD2, 0xD0, 0xB4, 0xE1, 0xC1, 0xE0, 0xD0, 0xD0, 0xE0 }; return Arrays.equals(scrambled, expected); } }

Definitely quite hard to read, so we run it through a java beautifier, and then extract the important functions.

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,
        0xD2,
        0xD0,
        0xB4,
        0xE1,
        0xC1,
        0xE0,
        0xD0,
        0xD0,
        0xE0
    };
    return Arrays.equals(scrambled, expected);
}

Let’s just take the switchBits function for what it is, and assume it swaps bits. For the scrambling function, all we need to do is write an unscramble function that swaps the bits around in the opposite order, and that takes in the expected array. Let’s just hop into implementing this in python.

scrambled = [
    0xF4,
    0xC0,
    0x97,
    0xF0,
    0x77,
    0x97,
    0xC0,
    0xE4,
    0xF0,
    0x77,
    0xA4,
    0xD0,
    0xC5,
    0x77,
    0xF4,
    0x86,
    0xD0,
    0xA5,
    0x45,
    0x96,
    0x27,
    0xB5,
    0x77,
    0xD2,
    0xD0,
    0xB4,
    0xE1,
    0xC1,
    0xE0,
    0xD0,
    0xD0,
    0xE0,
]


def switch_bits(c, p1, p2):
    mask1 = 1 << p1
    mask2 = 1 << p2
    bit1 = c & mask1
    bit2 = c & mask2
    rest = c & ~(mask1 | mask2)
    shift = p2 - p1
    result = (bit1 << shift) | (bit2 >> shift) | rest
    return result


def unscramble(scrambled):
    unscrambled = []
    for i in scrambled:
        new_char = switch_bits(i, 6, 7)
        new_char = switch_bits(new_char, 2, 5)
        new_char = switch_bits(new_char, 3, 4)
        new_char = switch_bits(new_char, 0, 1)
        new_char = switch_bits(new_char, 4, 7)
        new_char = switch_bits(new_char, 5, 6)
        new_char = switch_bits(new_char, 0, 3)
        new_char = switch_bits(new_char, 1, 2)
        unscrambled.append(new_char)
    return unscrambled

# convert unscrambled to ascii
unscrambled_ascii_string = "".join([chr(i) for i in unscramble(scrambled)])

print(unscrambled_ascii_string)

Running this, we get our flag! picoCTF{s0m3_m0r3_b1t_sh1fTiNg_91c642112}

asm1 (200 points)

Well here’s our first assembly challenge! We’re supposed to figure out what asm1(0x8be) returns, and submit that as our flag.

asm1:
    <+0>:   push   ebp
    <+1>:   mov    ebp,esp
    <+3>:   cmp    DWORD PTR [ebp+0x8],0x71c
    <+10>:  jg     0x512 <asm1+37>
    <+12>:  cmp    DWORD PTR [ebp+0x8],0x6cf
    <+19>:  jne    0x50a <asm1+29>
    <+21>:  mov    eax,DWORD PTR [ebp+0x8]
    <+24>:  add    eax,0x3
    <+27>:  jmp    0x529 <asm1+60>
    <+29>:  mov    eax,DWORD PTR [ebp+0x8]
    <+32>:  sub    eax,0x3
    <+35>:  jmp    0x529 <asm1+60>
    <+37>:  cmp    DWORD PTR [ebp+0x8],0x8be
    <+44>:  jne    0x523 <asm1+54>
    <+46>:  mov    eax,DWORD PTR [ebp+0x8]
    <+49>:  sub    eax,0x3
    <+52>:  jmp    0x529 <asm1+60>
    <+54>:  mov    eax,DWORD PTR [ebp+0x8]
    <+57>:  add    eax,0x3
    <+60>:  pop    ebp
    <+61>:  ret

Note that at the start, the stack looks something like this:

|  0x8b3           <- ebp + 8
|  return address  <- ebp + 4
|  old esp         <- ebp points here

Here is a commented and slightly edited version to trace through the assembly.

asm1:
    <+0>:   push   ebp                          ; pushes 0x8be from stack to ebp
    <+1>:   mov    ebp,esp                      ; moves 0x8be in ebp to esp
    <+3>:   cmp    DWORD PTR [ebp+0x8],0x71c    ; sets up comparison between 0x8be and 0x71c
    <+10>:  jg     0x512 <asm1+37>              ; jump if greater, 0x8be > 0x71c, so jump to <asm1+37>
    <+37>:  cmp    DWORD PTR [ebp+0x8],0x8be    ; place jumped to, setup 0x8be comparison with 0x8be
    <+44>:  jne    0x523 <asm1+54>              ; jump if not equal, is equal, so no jump
    <+46>:  mov    eax,DWORD PTR [ebp+0x8]      ; moves 0x8be to eax
    <+49>:  sub    eax,0x3                      ; subtracts 0x3 from eax, eax now 0x8bb
    <+52>:  jmp    0x529 <asm1+60>              ; jump to 60
    <+60>:  pop    ebp                          ; pops the stack
    <+61>:  ret                                 ; returns whatever value is at stack (0x8bb)

So the final hex we get, which is our flag, is: 0xbb

asm2 (250 points)

We are once again given an asm file. We’re told to give the output for asm2(0xb, 0x2e).

asm2:
    <+0>:   push   ebp
    <+1>:   mov    ebp,esp
    <+3>:   sub    esp,0x10
    <+6>:   mov    eax,DWORD PTR [ebp+0xc]
    <+9>:   mov    DWORD PTR [ebp-0x4],eax
    <+12>:  mov    eax,DWORD PTR [ebp+0x8]
    <+15>:  mov    DWORD PTR [ebp-0x8],eax
    <+18>:  jmp    0x509 <asm2+28>
    <+20>:  add    DWORD PTR [ebp-0x4],0x1
    <+24>:  sub    DWORD PTR [ebp-0x8],0xffffff80
    <+28>:  cmp    DWORD PTR [ebp-0x8],0x63f3
    <+35>:  jle    0x501 <asm2+20>
    <+37>:  mov    eax,DWORD PTR [ebp-0x4]
    <+40>:  leave
    <+41>:  ret

Note that at the start of the program, the stack looks something like this (after <+3>):

|  0x2e            <- ebp + 12
|  0xb             <- ebp + 8
|  return address  <- ebp + 4
|  old esp         <- ebp points here
|  esp now         <- ebp - 16???
asm2:
    <+0>:   push   ebp                              ; Saves old value of ebp
    <+1>:   mov    ebp,esp                          ; Saves old value of esp
    <+3>:   sub    esp,0x10                         ; Allocate 0x10 in stack
    <+6>:   mov    eax,DWORD PTR [ebp+0xc]          ; moves whatever's at 0xc (0x2e) to eax
    <+9>:   mov    DWORD PTR [ebp-0x4],eax          ; moves eax (0x2e) to four below the base pointer, aka 12.
    <+12>:  mov    eax,DWORD PTR [ebp+0x8]          ; moves (0xb) at ebp+0x8 to eax
    <+15>:  mov    DWORD PTR [ebp-0x8],eax          ; moves this nothing at eax to ebp-0x8
    <+18>:  jmp    0x509 <asm2+28>                  ; jumps to line 28
    <+20>:  add    DWORD PTR [ebp-0x4],0x1          ; adds 0x1 to ebp-0x4 a couple times.
    <+24>:  sub    DWORD PTR [ebp-0x8],0xffffff80   ; keeps on subtracting by 0xffffff80, basically add 0x80
    <+28>:  cmp    DWORD PTR [ebp-0x8],0x63f3       ; (0x63f3 - 0xb)/ 0x80 = basically 200.
    <+35>:  jle    0x501 <asm2+20>                  ; jumps back to line 20 if 0x63f3 less than or equal. Essentially while loop
    <+37>:  mov    eax,DWORD PTR [ebp-0x4]          ; moves arg in 0x4 back to ebp. arg4 is now 200 + 0x2e
    <+40>:  leave                                   ; this and below essentially resets and returns whats in eax
    <+41>:  ret                                     ; returns 0xF6

At the end, this should return our flag, which is: 0xF6

asm3 (300 points)

Given an ASM file, asked what asm3(0xba6c5a02,0xd101e3dd,0xbb86a173) returns.

asm3:
    <+0>:   push   ebp
    <+1>:   mov    ebp,esp
    <+3>:   xor    eax,eax
    <+5>:   mov    ah,BYTE PTR [ebp+0xb]
    <+8>:   shl    ax,0x10
    <+12>:  sub    al,BYTE PTR [ebp+0xd]
    <+15>:  add    ah,BYTE PTR [ebp+0xc]
    <+18>:  xor    ax,WORD PTR [ebp+0x12]
    <+22>:  nop
    <+23>:  pop    ebp
    <+24>:  ret

The stack looks like this.

|  0xbb86a173      <- ebp + 16
|  0xd101e3dd      <- ebp + 12
|  0xba6c5a02      <- ebp + 8
|  return address  <- ebp + 4
|  old esp         <- ebp points here

Lets annotate the code and see how it goes.

asm3:
    <+0>:   push   ebp
    <+1>:   mov    ebp,esp                  ; func prologue
    <+3>:   xor    eax,eax                  ; xors eax with itself, so all zeros
    <+5>:   mov    ah,BYTE PTR [ebp+0xb]    ; moves byte (0xba) at 0xb to ah
    <+8>:   shl    ax,0x10                  ; bit shifts ax left by 16 bits, eax now zero
    <+12>:  sub    al,BYTE PTR [ebp+0xd]    ; subtracts the byte (0xdd), so 0x00 - 0xe3 = 0x1d
    <+15>:  add    ah,BYTE PTR [ebp+0xc]    ; add to ah what's at 0xc (0xdd), ah = 0xdd
    <+18>:  xor    ax,WORD PTR [ebp+0x12]   ; xor ax (0xdd1d) with what's at ebp+0x12 (0xbb86)
    <+22>:  nop                             ; nothing happens here
    <+23>:  pop    ebp                      ; returns whats on eax which is just = 0x669b
    <+24>:  ret

As such, we find the output, and our flag to be: 0x669b

asm4 (400 points)

The final ASM challenge! We need to find what asm4(“picoCTF_f97bb”) returns. This one is the scariest by far.

asm4:
    <+0>:    push   ebp
    <+1>:    mov    ebp,esp
    <+3>:    push   ebx
    <+4>:    sub    esp,0x10
    <+7>:    mov    DWORD PTR [ebp-0x10],0x27a
    <+14>:   mov    DWORD PTR [ebp-0xc],0x0
    <+21>:   jmp    0x518 <asm4+27>
    <+23>:   add    DWORD PTR [ebp-0xc],0x1
    <+27>:   mov    edx,DWORD PTR [ebp-0xc]
    <+30>:   mov    eax,DWORD PTR [ebp+0x8]
    <+33>:   add    eax,edx
    <+35>:   movzx  eax,BYTE PTR [eax]
    <+38>:   test   al,al
    <+40>:   jne    0x514 <asm4+23>
    <+42>:   mov    DWORD PTR [ebp-0x8],0x1
    <+49>:   jmp    0x587 <asm4+138>
    <+51>:   mov    edx,DWORD PTR [ebp-0x8]
    <+54>:   mov    eax,DWORD PTR [ebp+0x8]
    <+57>:   add    eax,edx
    <+59>:   movzx  eax,BYTE PTR [eax]
    <+62>:   movsx  edx,al
    <+65>:   mov    eax,DWORD PTR [ebp-0x8]
    <+68>:   lea    ecx,[eax-0x1]
    <+71>:   mov    eax,DWORD PTR [ebp+0x8]
    <+74>:   add    eax,ecx
    <+76>:   movzx  eax,BYTE PTR [eax]
    <+79>:   movsx  eax,al
    <+82>:   sub    edx,eax
    <+84>:   mov    eax,edx
    <+86>:   mov    edx,eax
    <+88>:   mov    eax,DWORD PTR [ebp-0x10]
    <+91>:   lea    ebx,[edx+eax*1]
    <+94>:   mov    eax,DWORD PTR [ebp-0x8]
    <+97>:   lea    edx,[eax+0x1]
    <+100>:  mov    eax,DWORD PTR [ebp+0x8]
    <+103>:  add    eax,edx
    <+105>:  movzx  eax,BYTE PTR [eax]
    <+108>:  movsx  edx,al
    <+111>:  mov    ecx,DWORD PTR [ebp-0x8]
    <+114>:  mov    eax,DWORD PTR [ebp+0x8]
    <+117>:  add    eax,ecx
    <+119>:  movzx  eax,BYTE PTR [eax]
    <+122>:  movsx  eax,al
    <+125>:  sub    edx,eax
    <+127>:  mov    eax,edx
    <+129>:  add    eax,ebx
    <+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>
    <+149>:  mov    eax,DWORD PTR [ebp-0x10]
    <+152>:  add    esp,0x10
    <+155>:  pop    ebx
    <+156>:  pop    ebp
    <+157>:  ret

Not going to do the usual comment as this one needs to be broken down a lot more.

Let’s start with the prologue.

    <+0>:    push   ebp         ; pushes ebp onto stack
    <+1>:    mov    ebp,esp     ; puts esp to ebp
    <+3>:    push   ebx         ; pushes ebx ... array onto stack
    <+4>:    sub    esp,0x10    ; decrements the stack pointer by 16 bits

Currently, the stack looks like this.

# stack
|  0x400000        <- ebp + 8
   ^ address points to the contents of the string
|  return address  <- ebp + 4
|  old esp         <- ebp points here
|  stuff underneath

# somewhere in memory lets say addr 0x400000
|  "picoCTF_f97bb"

Let’s look at the next section now.

    <+7>:    mov    DWORD PTR [ebp-0x10],0x27a  ; Add an 0x27a to 0x10.
    <+14>:   mov    DWORD PTR [ebp-0xc],0x0     ; Set 0xc to 0x0
    <+21>:   jmp    0x518 <asm4+27>             ; Jump straight to line 27
    <+23>:   add    DWORD PTR [ebp-0xc],0x1     ; add one to ebp-0xc
    <+27>:   mov    edx,DWORD PTR [ebp-0xc]     ; set edx equal to whatevers in ebp-0xc
    <+30>:   mov    eax,DWORD PTR [ebp+0x8]     ; set address of string to eax
    <+33>:   add    eax,edx                     ; add value of edx to eax.
    <+35>:   movzx  eax,BYTE PTR [eax]          ; copies the value of the pointer to eax, and extends to 32 bits w/ zeros
    <+38>:   test   al,al                       ; does bitwise & between al and al.
    <+40>:   jne    0x514 <asm4+23>             ; if al != 0, then jump back up! basically keep on going until eax == 0

Essentially what this code is doing is going all the way until it finds the null, which is at the end of the string. The memory looks like this now.

# stack
|  0x400000              <- ebp + 8
   ^ address points to the contents of the string
|  return address        <- ebp + 4
|  old esp               <- ebp points here
|  len("picoCTF_f97bb")  <- ebp - 12
|  0x27a                 <- ebp - 16

# not stack
|  0                     <- eax, null byte at end of string

# somewhere in memory lets say addr 0x400000
|  "picoCTF_f97bb"

Let’s look at the next (warning: HUGE) chunk.

    <+42>:   mov    DWORD PTR [ebp-0x8],0x1     ; set ebp-0x8 to 0x1
    <+49>:   jmp    0x587 <asm4+138>            ; jump to line 138
    <+51>:   mov    edx,DWORD PTR [ebp-0x8]     ; set edx to ebp-0x8 (0x1 on first run)
    <+54>:   mov    eax,DWORD PTR [ebp+0x8]     ; set eax to address of string
    <+57>:   add    eax,edx                     ; add iterator count (think as i = 0, i++ etc.) to eax
    <+59>:   movzx  eax,BYTE PTR [eax]          ; copy the current character to eax, do a zero extend too
    <+62>:   movsx  edx,al                      ; copy the current character to edx (from eax)
    <+65>:   mov    eax,DWORD PTR [ebp-0x8]     ; set eax to ebp-0x8 (0x1 on first run)
    <+68>:   lea    ecx,[eax-0x1]               ; set ecx to value one less than whats at the address eax
    <+71>:   mov    eax,DWORD PTR [ebp+0x8]     ; set eax to address of string
    <+74>:   add    eax,ecx                     ; add ecx to eax. Basically one less than our iterator. (i-1)
    <+76>:   movzx  eax,BYTE PTR [eax]          ; copy value of pointer to eax, and extend to 32 bits w/ zeros
    <+79>:   movsx  eax,al                      ; copy value of al to eax, and extend to 32 bits w/ sign
    <+82>:   sub    edx,eax                     ; subtract that character in eax from edx
    <+84>:   mov    eax,edx                     ; set eax to edx
    <+86>:   mov    edx,eax                     ; set edx to eax
    <+88>:   mov    eax,DWORD PTR [ebp-0x10]    ; set eax to ebp-0x10 (0x27a on first run)
    <+91>:   lea    ebx,[edx+eax*1]             ; set ebx to edx + eax * 1
    <+94>:   mov    eax,DWORD PTR [ebp-0x8]     ; set eax to ebp-0x8 (0x1 on first run)
    <+97>:   lea    edx,[eax+0x1]               ; set edx to eax + 0x1
    <+100>:  mov    eax,DWORD PTR [ebp+0x8]     ; set eax to address of string
    <+103>:  add    eax,edx                     ; add edx to eax
    <+105>:  movzx  eax,BYTE PTR [eax]          ; copy value of pointer to eax, and extend to 32 bits w/ zeros
    <+108>:  movsx  edx,al                      ; copy value of al to edx, and extend to 32 bits w/ sign
    <+111>:  mov    ecx,DWORD PTR [ebp-0x8]     ; set ecx to ebp-0x8 (0x1 on first run)
    <+114>:  mov    eax,DWORD PTR [ebp+0x8]     ; set eax to address of string
    <+117>:  add    eax,ecx                     ; add ecx to eax
    <+119>:  movzx  eax,BYTE PTR [eax]          ; copy value of pointer to eax, and extend to 32 bits w/ zeros
    <+122>:  movsx  eax,al                      ; copy value of al to eax, and extend to 32 bits w/ sign
    <+125>:  sub    edx,eax                     ; subtract eax from edx
    <+127>:  mov    eax,edx                     ; set eax to edx
    <+129>:  add    eax,ebx                     ; add ebx to eax
    <+131>:  mov    DWORD PTR [ebp-0x10],eax    ; set ebp-0x10 to eax
    <+134>:  add    DWORD PTR [ebp-0x8],0x1     ; increment ebp-0x8
    <+138>:  mov    eax,DWORD PTR [ebp-0xc]     ; set eax to [ebp-0xc], len("picoCTF_f97bb")
    <+141>:  sub    eax,0x1                     ; subtract 1 from eax
    <+144>:  cmp    DWORD PTR [ebp-0x8],eax     ; compare [ebp-0x8] to eax
    <+147>:  jl     0x530 <asm4+51>             ; if [ebp-0x8] < eax, jump back up to line 51. Basically iterate over the string.

Well that was nice. Essentially this just goes through the string starting from the 1st index all the way to one before length of the string. It then adds the value of each character to some value that is stored, subtracts the value of the character before it, and then adds the value of the next character subtracted by the value of the current character. Note that the current character is added and subtracted, so it cancels out. So basically it iterates over each character in the string, and each time adds the value of the next character, but subtracts the last character.

These are the final lines

    <+149>:  mov    eax,DWORD PTR [ebp-0x10]    ; move back ebp-0x10 to eax
    <+152>:  add    esp,0x10                    ; return stuff to stack probably
    <+155>:  pop    ebx                         ; pop ebx
    <+156>:  pop    ebp                         ; pop ebp
    <+157>:  ret                                ; return eax stuffs

The equivalent python code is below. I used kusano_k’s writeup as a reference.

s = list(map(ord, "picoCTF_f97bb"))
a = 0x27a
for i in range(1, len(s)-1):
    a += s[i+1] - s[i-1]
print(hex(a))

Running that, we get our hex value, which is also our flag. 0x265

reverse_cipher (300 points)

We’re given a binary and a text file. The text file contains a slightly encrypted flag. Let’s assume the binary is the program that encrypted the flag. So we’ll need to reverse the binary to figure out how it did that, and then we can undo the encryption.

Putting it through binary ninja’s decompiler, we get this as our main function.

int32_t main(int32_t argc, char ** argv, char ** envp) {
  FILE * rax = fopen("flag.txt", & data_2008);
  FILE * rax_1 = fopen("rev_this", & data_2013);
  if (rax == 0) {
    puts("No flag found, please make sure …");
  }
  if (rax_1 == 0) {
    puts("please run this on the server");
  }
  void var_58;
  if (fread( & var_58, 0x18, 1, rax) <= 0) {
    exit(0);
    /* no return */
  }
  for (int32_t var_10 = 0; var_10 <= 7; var_10 = (var_10 + 1)) {
    fputc( * ( & var_58 + var_10), rax_1);
  }
  for (int32_t var_14 = 8; var_14 <= 0x16; var_14 = (var_14 + 1)) {
    char rax_9 = * ( & var_58 + var_14);
    char var_9_3;
    if ((var_14 & 1) != 0) {
      var_9_3 = (rax_9 - 2);
    } else {
      var_9_3 = (rax_9 + 5);
    }
    fputc(var_9_3, rax_1);
  }
  char var_41;
  fputc(var_41, rax_1);
  fclose(rax_1);
  return fclose(rax);
}

Running this through my local LLM, I get it to rename variables and functions to produce a more readable output.

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

int main(int argc, char **argv, char **envp) {
    FILE *flagFile = fopen("flag.txt", "r");
    FILE *revFile = fopen("rev_this", "w");

    if (flagFile == NULL) {
        puts("No flag found. Please make sure the file 'flag.txt' exists.");
    }

    if (revFile == NULL) {
        puts("Please run this on the server.");
    }

    char flagData[24];

    if (fread(flagData, sizeof(char), sizeof(flagData), flagFile) <= 0) {
        exit(0);
    }

    for (int i = 0; i <= 7; i++) {
        fputc(flagData[i], revFile);
    }

    for (int i = 8; i <= 22; i++) {
        char currentChar = flagData[i];
        char modifiedChar;

        if ((i & 1) != 0) {
            modifiedChar = currentChar - 2;
        } else {
            modifiedChar = currentChar + 5;
        }

        fputc(modifiedChar, revFile);
    }

    char lastChar;
    fputc(lastChar, revFile);

    fclose(revFile);
    fclose(flagFile);

    return 0;
}

Well it should be pretty trivial to write a python script to reverse this now that we know what the c binary is doing. Let’s try that.

encrypted_text = "picoCTF{w1{1wq8/7376j.:}"

decrypted_text = ""

# keep chars 1 through 7 inclusive the same
for i in range(8):
    decrypted_text += encrypted_text[i]

# for chars 8 through 22 inclusive
for i in range(8, 23):
    # if i & 1 != 0, i is odd
    if i & 1 != 0:
        decrypted_text += chr(ord(encrypted_text[i]) + 2)
    # else i is even
    else:
        decrypted_text += chr(ord(encrypted_text[i]) - 5)

# put the last char in the decrypted text
decrypted_text += encrypted_text[23]

print(decrypted_text)

Running that, we get our flag. picoCTF{r3v3rs312528e05}

Need For Speed

Let’s just toss this into binary ninja again. We get a main function and several helper functions

uint64_t decrypt_flag(int32_t arg1) {
  int32_t var_1c = arg1;
  int32_t var_c = 0;
  uint64_t rax_19;
  while (true) {
    rax_19 = var_c;
    if (rax_19 > 0x36) {
      break;
    }
    int32_t temp0_1;
    int32_t temp1_1;
    temp0_1 = HIGHD(var_c);
    temp1_1 = LOWD(var_c);
    uint32_t rdx_3 = (temp0_1 >> 0x1f);
    *(var_c + & flag) = ( * (var_c + & flag) ^ * ((((temp1_1 + rdx_3) & 1) - rdx_3) + & var_1c));
    int32_t temp2_1;
    int32_t temp3_1;
    temp2_1 = HIGHD((var_c * 0x55555556));
    temp3_1 = LOWD((var_c * 0x55555556));
    int32_t rdx_6 = (temp2_1 - (var_c >> 0x1f));
    if ((var_c - ((rdx_6 + rdx_6) + rdx_6)) == 2) {
      var_1c = (var_1c + 1);
    }
    var_c = (var_c + 1);
  }
  return rax_19;
}

uint64_t calculate_key() {
  int32_t var_c = 0xce61f8de;
  do {
    var_c = (var_c - 1);
  } while (var_c != 0xe730fc6f);
  return var_c;
}

int64_t alarm_handler() __noreturn {
  int32_t rdi;
  int32_t var_c = rdi;
  puts("Not fast enough. BOOM!");
  exit(0);
  /* no return */
}

int64_t set_timer() {
  int32_t var_14 = 1;
  if (__sysv_signal(0xe, alarm_handler) != -1) {
    return alarm(1);
  }
  puts("\n\nSomething bad happened here.…");
  exit(0);
  /* no return */
}

int64_t get_key() {
  puts("Creating key...");
  key = calculate_key();
  return puts("Finished");
}

int64_t print_flag() {
  puts("Printing flag:");
  decrypt_flag(key);
  return puts( & flag);
}

int64_t header() {
  puts("Keep this thing over 50 mph!");
  for (int32_t var_c = 0; var_c <= 0x1b; var_c = (var_c + 1)) {
    putchar(0x3d);
  }
  return puts( & data_a6d);
}

int32_t main(int32_t argc, char ** argv, char ** envp) {
  int32_t var_c = argc;
  char ** var_18 = argv;
  header();
  set_timer();
  get_key();
  print_flag();
  return 0;
}

Let’s break down what the code does. When we run the prgoram, we get the following output.

Keep this thing over 50 mph!
============================

Creating key...
Not fast enough. BOOM!

We see that the "Not fast enough. BOOM!" is called in alarm_handler(), which is called inside set_timer(). This prevents us from running the program and actually getting to the print_flag() function.

I tried opening this in GDB, and stepping through and directly running the print_flag() function, but that didn’t work. The reason why this didn’t work was that I tried running it at the beginning of the program, before get_key() is run. It was probably possible to use gdb of some sort to get this challenge working, but I ended up using a different method.

This different method is of patching the binary. Binary Ninja has a really simple tool for binary patching. I’m not sure if other decompilers like ghidra have this feature, but it’s really easy to use with binary ninja. We simply patch out the call to set_timer() and we can run the program without the alarm going off, and we get our flag.

PICOCTF{Good job keeping bus #190ca38b speeding along!}

Forky

Let’s open this binary in Binary Ninja. We get the following decompilation.

{
    void* const var_4 = __return_addr;
    int32_t* var_10 = &argc;
    int32_t* eax = mmap(nullptr, 4, 3, 0x21, 0xffffffff, 0);
    *(int32_t*)eax = 0x3b9aca00;
    fork();
    fork();
    fork();
    fork();
    *(int32_t*)eax = (*(int32_t*)eax + 0x499602d2);
    doNothing(*(int32_t*)eax);
    return 0;
}

My initial plan was to step through this with gdb. However, it didn’t run through properly on my computer.

Running it in the picowebshell, and setting a breakpoint on doNothing (b doNothing), and then printing the value of eax (p $eax) gives us the flag.

This felt too easy. Looking at other writeups, it seemed like another solution would be to realize that fork calls 16 times, and then run 16 of the eax adds, or to just recompile this in C again with some extra print statements. Not sure why this was a 500 point challenge.

picoCTF{-721750240}