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!
|
|
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 theinput()
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.
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.
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!
Continuing through the levels gives the entire flag.
picoCTF{ur_4_w1z4rd_2a05d7a8}