file-run1

A program has been provided to you, what happens if you try to run it on the command line?

If we wish to execute this file, then we need to add the executable bit to it. This can be done using chmod +x run. Then running the file gives us the flag.

┌──(dcm㉿cocoahacks)-[~/wargames/picoctf/2022/re/file-run1]
└─$ ./run 
The flag is: picoCTF{U51N6_Y0Ur_F1r57_F113_5578e314}

file-run2

Another program, but this time, it seems to want some input. What happens if you try to run it on the command line with input “Hello!”? (100 points)

Do as the instructions say.

┌──(dcm㉿cocoahacks)-[~/wargames/picoctf/2022/re/file-run2]
└─$ ./run Hello!
The flag is: picoCTF{F1r57_4rgum3n7_981abfb5}

GDB Test Drive

Can you get the flag? Download this binary. Here’s the test drive instructions: (100 points)

$ chmod +x gdbme
$ gdb gdbme
(gdb) layout asm
(gdb) break *(main+99)
(gdb) run
(gdb) jump *(main+104)

Running the instructions as given provides us the flag. The instructions don’t do much more than show you how to set a breakpoint on a very long sleep call and then jump over it to continue execution of the program.

0x1325 <main+94>        mov    edi,0x186a0
0x132a <main+99>        call   0x1110 <sleep@plt>    # Breakpoint here
0x132f <main+104>       lea    rax,[rbp-0x30]        # Jump to here
pwndbg> jump *(main+104)
Continuing at 0x55555555532f.
picoCTF{d3bugg3r_dr1v3_93b87433}
[Inferior 1 (process 448292) exited normally]

patchme.py

Can you get the flag? Run this Python program in the same directory as this encrypted flag. (100 points)

To solve this easily, we can change the user_pw to != the string concatenation that occurs.

Solution

def level_1_pw_check():

    user_pw = input("Please enter correct password for flag: ")

    # Changed from == to !=
    if( user_pw != "ak98" + \
                   "-=90" + \
                   "adfjhgj321" + \
                   "sleuth9000"):
        print("Welcome back... your flag, user:")
        decryption = str_xor(flag_enc.decode(), "utilitarian")
        print(decryption)
        return
    print("That password is incorrect")

picoCTF{p47ch1ng_l1f3_h4ck_4d5af99c}


Safe Opener

Can you open this safe? I forgot the key to my safe but this program is supposed to help me with retrieving the lost key. Can you help me unlock my safe? Put the password you recover into the picoCTF flag format like: picoCTF{password} (100 points)

This challenge provides us with a Java class that asks for a password, base64 encodes the input, and checks against an encoded key. To get the password, we can simply base64 decode the hardcoded key and get the flag.

public static boolean openSafe(String password) {
    String encodedkey = "cGwzYXMzX2wzdF9tM18xbnQwX3RoM19zYWYz";
    
    if (password.equals(encodedkey)) {
        System.out.println("Sesame open");
        return true;
    }
    else {
        System.out.println("Password is incorrect\n");
        return false;
    }
}
┌──(dcm㉿cocoahacks)-[~/wargames/picoctf/2022/re/SafeOpener]
└─$ echo cGwzYXMzX2wzdF9tM18xbnQwX3RoM19zYWYz | base64 -d
pl3as3_l3t_m3_1nt0_th3_saf3

unpackme.py

Can you get the flag? Reverse engineer this Python program. (100 points)

import base64
from cryptography.fernet import Fernet


payload = b"gAAAAABiMD1Dt87s50caSunQlHoZqPOwtGNaQXexN-gjKwZeuLEN_-v6UcFJHVXOT4B6DcD1SB7cnd6yTcHg9e9LZCAeJY2cJ0r0sfyGPRiH60F-WbkyUjlAdDywI8RPdTpDYLuBmpZ_Kun-kHyTzMjeKR6R26Z4JITUS8n7Dj9X--9eNLajH6UuYD4GkjRACpsidl_8z33DlB86u_xDCMMm7HFK2oJTs8HG1fBex6enQsu0frUAJbx56DxhRvWawAysDMtLE50vaohrzkVV7Yaz4ClilwgfjQ=="

key_str = "correctstaplecorrectstaplecorrec"
key_base64 = base64.b64encode(key_str.encode())
f = Fernet(key_base64)
plain = f.decrypt(payload)
exec(plain.decode())

This script performs some Fernet decryption. To determine what is being executed, we can change the exec() to print() and grab the flag.

pw = input('What\'s the password? ')

if pw == 'batteryhorse':
  print('picoCTF{175_chr157m45_8aef58d2}')
else:
  print('That password is incorrect.')

bloat.py

Can you get the flag? Run this Python program in the same directory as this encrypted flag. (200 points)

This script is a mess!

 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
import sys
a = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ"+ \
            "[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ "
def arg133(arg432):
  if arg432 == a[71]+a[64]+a[79]+a[79]+a[88]+a[66]+a[71]+a[64]+a[77]+a[66]+a[68]:
    return True
  else:
    print(a[51]+a[71]+a[64]+a[83]+a[94]+a[79]+a[64]+a[82]+a[82]+a[86]+a[78]+\
a[81]+a[67]+a[94]+a[72]+a[82]+a[94]+a[72]+a[77]+a[66]+a[78]+a[81]+\
a[81]+a[68]+a[66]+a[83])
    sys.exit(0)
    return False
def arg111(arg444):
  return arg122(arg444.decode(), a[81]+a[64]+a[79]+a[82]+a[66]+a[64]+a[75]+\
a[75]+a[72]+a[78]+a[77])
def arg232():
  return input(a[47]+a[75]+a[68]+a[64]+a[82]+a[68]+a[94]+a[68]+a[77]+a[83]+\
a[68]+a[81]+a[94]+a[66]+a[78]+a[81]+a[81]+a[68]+a[66]+a[83]+\
a[94]+a[79]+a[64]+a[82]+a[82]+a[86]+a[78]+a[81]+a[67]+a[94]+\
a[69]+a[78]+a[81]+a[94]+a[69]+a[75]+a[64]+a[70]+a[25]+a[94])
def arg132():
  return open('flag.txt.enc', 'rb').read()
def arg112():
  print(a[54]+a[68]+a[75]+a[66]+a[78]+a[76]+a[68]+a[94]+a[65]+a[64]+a[66]+\
a[74]+a[13]+a[13]+a[13]+a[94]+a[88]+a[78]+a[84]+a[81]+a[94]+a[69]+\
a[75]+a[64]+a[70]+a[11]+a[94]+a[84]+a[82]+a[68]+a[81]+a[25])
def arg122(arg432, arg423):
    arg433 = arg423
    i = 0
    while len(arg433) < len(arg432):
        arg433 = arg433 + arg423[i]
        i = (i + 1) % len(arg423)        
    return "".join([chr(ord(arg422) ^ ord(arg442)) for (arg422,arg442) in zip(arg432,arg433)])
arg444 = arg132()
arg432 = arg232()
arg133(arg432)
arg112()
arg423 = arg111(arg444)
print(arg423)
sys.exit(0)

Running the script asks us to enter the correct password. Taking this one step at a time we notice a few things:

  • arg232() is the call to the input()
  • arg132() reads the encrypted flag
  • There is an additional check to arg133() which compares our input to something

Removing the call to arg133() allows us to bypass the check and print out the flag.

┌──(dcm㉿cocoahacks)-[~/wargames/picoctf/2022/re/bloat]
└─$ /bin/python3.10 -u "/home/dcm/wargames/picoctf/2022/re/bloat/bloat.flag.py"
Please enter correct password for flag: lol
Welcome back... your flag, user:
picoCTF{d30bfu5c4710n_f7w_c47f9e9c}

Fresh Java

Can you get the flag? Reverse engineer this Java program. (200 points)

We are provided with a compiled Java class called KeygenMe.class. There are several tools people prefer for decompiling Java and a very popular one is jd-gui. Opening the file with jd-gui results in the source code which checks each letter of the password entered. We can read the flag starting from the bottom of the source code and working up.

import java.util.Scanner;

public class KeygenMe {
  public static void main(String[] paramArrayOfString) {
    Scanner scanner = new Scanner(System.in);
    System.out.println("Enter key:");
    String str = scanner.nextLine();
    if (str.length() != 34) {
      System.out.println("Invalid key");
      return;
    } 
    if (str.charAt(33) != '}') {
      System.out.println("Invalid key");
      return;
    } 
    if (str.charAt(32) != '0') {
      System.out.println("Invalid key");
      return;
    } 

    ...
   
    if (str.charAt(3) != 'o') {
      System.out.println("Invalid key");
      return;
    } 
    if (str.charAt(2) != 'c') {
      System.out.println("Invalid key");
      return;
    } 
    if (str.charAt(1) != 'i') {
      System.out.println("Invalid key");
      return;
    } 
    if (str.charAt(0) != 'p') {
      System.out.println("Invalid key");
      return;
    } 
    System.out.println("Valid key");
  }
}

picoCTF{700l1ng_r3qu1r3d_126c59f0}


Bbbloat

Can you get the flag? Reverse engineer this binary. (300 points)

This binary wants us to guess a number.

┌──(dcm㉿cocoahacks)-[~/wargames/picoctf/2022/re/bbbbloat]
└─$ ./bbbbloat 
What's my favorite number? 1
Sorry, that's not it!

We can open the binary is any disassembler to determine how the logic works. I prefer Binary Ninja for its modern look and learning curve. If we check the main function, we can see that out input is compared to a number to determine if we got it right.

Bbbbloat disassembly

0x86187 is equal to 549255 and we can enter that into the program to get the flag.

┌──(dcm㉿cocoahacks)-[~/wargames/picoctf/2022/re/bbbbloat]
└─$ ./bbbbloat 
What's my favorite number? 549255
picoCTF{cu7_7h3_bl047_2d7aeca1}

unpackme

Can you get the flag? Reverse engineer this binary. (300 points)

The actual name of the file provided is unpackme-upx so we can assume the binary is packed with UPX. Therefore, we can use UPX to unpack it.

┌──(dcm㉿cocoahacks)-[~/wargames/picoctf/2022/re/unpackme]
└─$ upx -d unpackme-upx 
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2020
UPX 3.96        Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 23rd 2020

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
   1002408 <-    379116   37.82%   linux/amd64   unpackme-upx

Unpacked 1 file

The challenge asks for a number, and following the steps from the previous challenge using Binary Ninja, we can determine the number to match this time is 0xb83cb, which is 754635.

┌──(dcm㉿cocoahacks)-[~/wargames/picoctf/2022/re/unpackme]
└─$ ./unpackme-upx 
What's my favorite number? 754635
picoCTF{up><_m3_f7w_ed7b0850}

Keygenme

Can you get the flag? Reverse engineer this binary. (400 points)

This challenge asks us to enter a license key.

┌──(dcm㉿cocoahacks)-[~/wargames/picoctf/2022/re/keygenme]
└─$ ./keygenme 
Enter your license key: lol
That key is invalid.

Using Binary Ninja, we can locate the function that is responsible for processing our input. As soon as we get into the function, we can see some string being placed into a local variable.

00001209  int64_t sub_1209(char* arg1)

00001209  {
0000121f      void* fsbase;
0000121f      int64_t rax = *(int64_t*)((char*)fsbase + 0x28);
00001242      int64_t var_98 = 0x7b4654436f636970;
00001249      int64_t var_90 = 0x30795f676e317262;
0000125a      int64_t var_88 = 0x6b5f6e77305f7275;
0000125e      int32_t var_80 = 0x5f7933;
00001265      int16_t var_ba = 0x7d;

In Binary Ninja, you can convert hex into ASCII by pressing the “r” key. Doing that on variables that are being assigned hex reveals part of the flag. However, we are still missing the unique tag at the end of the flag.

00001209  int64_t sub_1209(char* arg1)

00001209  {
0000121f      void* fsbase;
0000121f      int64_t rax = *(int64_t*)((char*)fsbase + 0x28);
00001242      int64_t var_98 = 'picoCTF{';
00001249      int64_t var_90 = 'br1ng_y0';
0000125a      int64_t var_88 = 'ur_0wn_k';
0000125e      int32_t var_80 = '3y_';
00001265      int16_t var_ba = '}';

picoCTF{br1ng_y0ur_0wn_k3y_}

So what happened here? Well, it turns out the rest of the key is calculated through some additional functionality, mostly notably through MD5 hashing.

00001278      uint64_t rax_1 = strlen(&var_98)
00001294      void var_b8
00001294      MD5(&var_98, rax_1, &var_b8, rax_1)
000012a3      uint64_t rax_2 = strlen(&var_ba)
000012bf      void var_a8
000012bf      MD5(&var_ba, rax_2, &var_a8, rax_2)

...

0000141d      int64_t rax_28
0000141d      if (strlen(arg1) != 0x24)
0000141f          rax_28 = 0
00001426      else
00001426          int32_t var_c0_1 = 0
0000146e          while (true)
0000146e              if (var_c0_1 s> 0x23)
00001470                  rax_28 = 1
00001470                  break
00001457              if (arg1[sx.q(var_c0_1)] != *(&var_38 + sx.q(var_c0_1)))
00001459                  rax_28 = 0
0000145e                  break
00001460              var_c0_1 = var_c0_1 + 1
00001482      if ((rax ^ *(fsbase + 0x28)) == 0)
0000148a          return rax_28

The final part of the key is calculated here and some actions are performed on our input to check if they match. If we run the program, attach a debugger, and breakpoint at the strlen() call, we can view the stack to find the flag.

00007ffc:1b07e850|picoCTF{|
00007ffc:1b07e858|br1ng_y0|
00007ffc:1b07e860|ur_0wn_k|
00007ffc:1b07e868|3y_.....|
00007ffc:1b07e870|438218d5|
00007ffc:1b07e878|72e90162|
00007ffc:1b07e880|d0981cbb|
00007ffc:1b07e888|c7d43882|
00007ffc:1b07e890|cbb184dd|
00007ffc:1b07e898|8e05c970|
00007ffc:1b07e8a0|9e5dcaed|
00007ffc:1b07e8a8|aa0495cf|
00007ffc:1b07e8b0|picoCTF{|
00007ffc:1b07e8b8|br1ng_y0|
00007ffc:1b07e8c0|ur_0wn_k|
00007ffc:1b07e8c8|3y_247d8|
00007ffc:1b07e8d0|a57}....|

picoCTF{br1ng_y0ur_0wn_k3y_247d8a57}


Wizardlike

Do you seek your destiny in these deplorable dungeons? If so, you may want to look elsewhere. Many have gone before you and honestly, they’ve cleared out the place of all monsters, ne’erdowells, bandits and every other sort of evil foe. The dungeons themselves have seen better days too. There’s a lot of missing floors and key passages blocked off. You’d have to be a real wizard to make any progress in this sorry excuse for a dungeon! Download the game. ‘w’, ‘a’, ’s', ’d' moves your character and ‘Q’ quits. You’ll need to improvise some wizardly abilities to find the flag in this dungeon crawl. ‘.’ is floor, ‘#’ are walls, ‘<’ are stairs up to previous level, and ‘>’ are stairs down to next level. (500 points)

It’s a game! We can try playing it but at some point we come across deadends with no obvious path forward. Like this:

...             ..  .....
.<.          #
..@          ...#
...          ...#
             ..>#
             #

If we go back to the first level, we can see that there’s some unreachable part of the map to the right of the screen:

#########
#.......#  ......#   ..........
#.......#  ........
#.....@..  .#
#.......#  .#
#.......#  .#
#.......#  .#
#.......#  .#
#.......#  ..
#.......#
#.......#
#.......#
#.......#
#.......#
#......>#
########

The blank space prevents us from moving across the gap. Since we can move across dot characters, we can either try to replace the blank spaces in the maps with dots instead or change the logic that determines that we can’t move across a dot. The latter might be easier, since it means that each map wouldn’t have to be modified.

First we have to locate where input gets processed, which is simple to find in the main function.

00001f07          if (rax_87 == 'Q')
00001f09              var_32 = 0
00001f13          else if (rax_87 == 'w')
00001f1a              sub_166b()
00001f25          else if (rax_87 == 's')
00001f2c              sub_16e7()
00001f37          else if (rax_87 == 'a')
00001f3e              sub_1763()
00001f49          else if (rax_87 == 'd')
00001f50              sub_17df()

Each of the calls also calls another function sub_15ac() and processes some variables which are set in the main function.

Input Process

If we check the 15ac function, we find the logic that checks to see if the space we’re trying to occupy is a # or a . It returns a 0. If it’s not equal to one of those, it returns a 1. So let’s make it return a 1 in either case so we can pass through walls!

0000161a          if (*(int8_t*)(&data_1fea0 + ((rax_4 * 0x14) + ((int64_t)arg1))) != '#')
00001618          {
0000162f              rax_13 = (((int64_t)arg2) * 5);
00001654              if (*(int8_t*)(&data_1fea0 + ((rax_13 * 0x14) + ((int64_t)arg1))) != ' ')
00001652              {
0000165d                  rax_18 = 1;
0000165d              }
00001618          }
00001654          if ((*(int8_t*)(&data_1fea0 + ((rax_4 * 0x14) + ((int64_t)arg1))) == '#' || (*(int8_t*)(&data_1fea0 + ((rax_4 * 0x14) + ((int64_t)arg1))) != '#' && *(int8_t*)(&data_1fea0 + ((rax_13 * 0x14) + ((int64_t)arg1))) == ' ')))
00001652          {
00001656              rax_18 = 0;
00001656          }

We can change rax_18 = 0 to rax_18 = 1 to complete the bypass. Now we can walk through walls!

Wizardlike_Change_RAX

Wizrdlike_Map1 Wizrdlike_Map2

Continuing through the levels gives the entire flag.

picoCTF{ur_4_w1z4rd_2a05d7a8}