---
# System prepended metadata

title: CyberX CTF 2024 UTM

---

## Math?

We are given

```python
n = {LARGE_VALUE}
e1 = {LARGE_VALUE}
e2 = {LARGE_VALUE}
c1 = {LARGE_VALUE}
c2 = {LARGE_VALUE}
```

and equations in `png`:

![[397964183-39ea306a-7e27-4155-9baf-bbd352e8f99b.png|400]]

This can actually be solved in 3 lines lol (or even less XD)

```python
q = math.gcd(n, pow(c1, e2, n) * pow(5, e1 * e2, n) - pow(c2, e1, n) )
q // 16
p = n // q
```

---

Remember to check the values:

```python
if p * q == n:
    print("yes")
else:
    print("no")
```

---

Here's the full code: [math.py](https://github.com/Exberg/ctf-writeups/blob/main/math.py)

---

For those who are actually curious on `how I solved it?`

- I think we need to change the math equation into **simpler** and more **efficient**.
- So, I just follow the reference I found online, and try to modify the equations into more efficient ones.

![[397966112-a16573c7-1c92-4a51-9cfa-873a737a6124.png|1124]]

---

References:

1. https://ctftime.org/writeup/15438
2. https://crypto.stackexchange.com/questions/106396/solve-congruent-equation-likes-n-pq-c1-2p-3qe1-mod-n-c2-5p-7

---

## XOR II

I found an article of XOR Crypto: [link](https://golamrabbany.medium.com/crypto-ctf-you-either-know-xor-you-dont-69ac9b8f4812)

Here's how XOR worked:

When you construct a XOR cipher, you need to enter 2 values: the `key` and the `flag`

![[397962425-21fd28ad-d5b2-42c3-aa0c-588cb81cf204.png|800]]

---

We don't know the key, but we know the `flag` starts with `"CyberX{"`

`flag` can be used as a key too to

note: `"CyberX{"` consists of **7 letters**, the output will also be **7 letters**.

![[397962596-11e795f0-7e94-4bff-9df2-9298746d2453.png|800]]

---

So we use the output `"Pokemon"` as the key

![[397962699-c578f97a-c8e3-432d-a796-b7d024a441a1.png|800]]

Bingo!

---

## ChessMaster

This one is a bit tough since I have little knowledge in chess.

We are given a `python code`, and `18 chessmoves images`

![[397962816-0f9f1fa6-cc2e-44d9-b732-4f808b8de109.png|100]]

---

The `python code` looks like cryptography decryption with chessmoves!

```python
import random

mapping = [
    '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', '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', '1', '2', '3', '4',
    '5', '6', '7', '8', '9', '0', '{', '}'
]

# Create a scrambled version of the mapping
scrambled_mapping = mapping.copy()
random.seed(12423915)
random.shuffle(scrambled_mapping)

def encrypt(flag):
    value = []
    for i in range(len(flag)):
        z = scrambled_mapping.index(flag[i])  # Find the index in scrambled mapping
        chars = "abcdefgh"

        filemodulus = (z % 8)  # Calculate the "file" (a-h)
        file = chars[filemodulus]

        rank = int(z / 8) + 1  # Calculate the "rank" (1-8)

        value.append(''.join(f'{file}{rank}'))
    return value

def decrypt(encrypted_values):
    chars = "abcdefgh"
    decrypted_message = []

    for value in encrypted_values:
        file = value[0]  # Extract the file (a-h)
        rank = int(value[1])  # Extract the rank (1-8)

        # Find the index corresponding to the file
        filemodulus = chars.index(file)

        # Calculate the original index of the scrambled_mapping
        z = (rank - 1) * 8 + filemodulus
        decrypted_message.append(scrambled_mapping[z])

    return ''.join(decrypted_message)

# Example encrypted chess positions (this should match the output of `encrypt` function)
encrypted_values = ['']

# Decrypt the message
decrypted_message = decrypt(encrypted_values)
print("Decrypted:", decrypted_message)

# Example encryption of a message
message = "ILOVEUTM"
try:
    encrypted_message = encrypt(message)
    print("Encrypted:", encrypted_message)
except:
    print("Error: Message contains characters not in the mapping.")

```

---

We just need to figure out what is the **encrypted values**

```python
encrypted_values = ['?'] # ?????
```

When you look at the chessmoves images, you will see they can all be solved with just 1 move **checkmate**.

![[397963235-b19c1da9-5c98-400a-bc70-08f9bf24a118.png|500]]

---

Now write all the values in their respective chess positions

```python
encrypted_values = ['g5', 'h2', 'h5', 'd2', 'd1','g3','d7', 'a5', 'e5', 'e3', 'd2', 'b5', 'a6', 'a7', 'd1', 'g4', 'f2', 'c6']
```

There is also some tricky in the last image (the white and black position were swapped):

![[397963461-b5de1e05-24ef-4026-8d4e-326776d4c489.png|500]]

---

Finally, run the Code and Voila!

![[flag.png|800x50]]

---

## APT APT APT APT

This challenge is actually very easy but just annoying.

![[APTAPTAPT.png|600]]

First, it looks like a cipher. So, I just went to decode.fr to search for music related cipher:

![[musicsheetcipher.png|500]]

I ciphered everything just in case lol (pain)

---


# Forensics
## Forensic Odyssey 1: A Message in the Mist
---
1. First, we downlaod and unzip the Artifact.zip file with the password given by the challenge.
2. Inspect what inside the file.
   - There is only a file name ```CyberX_CTF.E01``` file.
3. Look closer, it is a ```E01``` extension file, as we know, ```E01```file is a file extension for Encase Disk Image or (Encase Evidence Files or Expert Witness Format (EWF) files) Google ^_^!
4. To open this file, we need to download [Autospy](https://www.autopsy.com/) or [Eterro FTK Imager](https://www.exterro.com/digital-forensics-software/ftk-imager)
5. After that, open Autospy(for this time I use Autospy), but before others flag, we need to find the first flag, i have mount the image to local disk E and I can see there is a Welcome.txt file in the disk.
6. - ![image](https://github.com/user-attachments/assets/593e4d72-ceeb-4e7d-a35a-60bfbf0c7c5f)
   - <img width="501" alt="image" src="https://github.com/user-attachments/assets/c16df287-4631-46d4-acc6-b511698abf16" />
7. And you will get the flag in the txt file.
   - ![image](https://github.com/user-attachments/assets/44e82b55-2148-4f53-beec-277bd3f09b4a)
---
## Forensic Odyssey 2: The Hidden Path
---
1. CONTINUE to the Forensics Odyssey 1.
2. Open the autospy and open the disk image.
3. Before started, we should think where is the data normally will be hidden.
   - Recycle bin etc...
4. ![image](https://github.com/user-attachments/assets/42ba190b-9590-4234-87bb-a46a9169f540)
5. We can see there is the flag, but opps, it is not the correct flag after I tried. Lets have a check for others flag.
6. After few searching we can see there is a hidden named file in ```user/plssk/download/```
7. there is a txt file name flag2.txt and when we press in, there is a sentences
   - ![image](https://github.com/user-attachments/assets/bb14d2d9-8d88-491d-af9e-03e6dd97be92)
8. Where is the flag??
9. When we highlights it, we can see there is different size of space in there
   - ![image](https://github.com/user-attachments/assets/8c33799f-371b-4df6-852d-84ae10893c6c)
10. Copy it and go to white space decoder()
   - ![image](https://github.com/user-attachments/assets/aba034e4-2b70-48a8-aa46-8eecb127cc4c)
11. There you go ! The flag for this challenge is out!
---
## Forensic Odyssey 3: The Time Traveler
---
1. Continue Forensics Odyssey 2.
2. We can see there is something hidding in here
3. ![image](https://github.com/user-attachments/assets/7c81eb0a-e7f7-4f97-9990-30615311bc40)
4. After we take the encrypted code from txt and put into CyberChef to decode the base64 text.
5. ![image](https://github.com/user-attachments/assets/e8deb263-9a5e-4739-ba34-941feb8c486d)
6. Here this is the flag!
---
## Forensic Odyssey 4: The Final Trail
---
1. Continue Forensics Odyssey 3.
2. Last we can see there is a deleted files in the side of the tab
  - ![image](https://github.com/user-attachments/assets/198a4dab-284a-464a-a87e-c0f8615ca65e)
3. Press ```All```
4. And we can see the last flag at here, it is easy right!
  - ![image](https://github.com/user-attachments/assets/598a80c8-3357-42d3-88d0-4c36b7499783)
---
## Knock Knock
---
1. Download the attachment given by the challenge.
2. Open the pcap file using [wireshark](https://www.wireshark.org/#downloadLink)
3. We can see theres only TCP Protocol this time, but hey look at the down-right corner, its at there again.
   - ![image](https://github.com/user-attachments/assets/eaaf7623-2810-4ac7-b310-f75a4b97aa85)
4. OK lets try different port. And we can see there is a different character but the same as flag CyberX{ word.
5. So just follow and record it one by one and we can get the flag for this challenge.
   - ![image](https://github.com/user-attachments/assets/2613a9d0-598a-4aa8-9d8c-fde38289d386)
6. Hooray there goess the flag!

#### Knock Knock, whose there? CyberX here hehe.
---
## Poslaju
---
1. Download the attachment given by the challenge.
2. Open the pcap file using [wireshark](https://www.wireshark.org/#downloadLink)
3. Choose one port of http and right click, follow the http stream.
4. You can see there is a C in front of the HTTP/1.1.
   - ![image](https://github.com/user-attachments/assets/561fba6a-e20b-446f-b1ac-90ef149fec8c)
5. Looks like there is a flag there maybe, so lets try increase the stream.
   - ![image](https://github.com/user-attachments/assets/de8fd55d-4ff7-425a-ae4e-e0b8fee270ed)
6. Looks like there is a y, so lets record it (flag type = CyberX{})
   - ![image](https://github.com/user-attachments/assets/98c918b7-5db8-401a-af80-ccecd7c52f51)
   - Record the flag one bye one by increasing the stream.
7. And there is it the flag!
---
## Powershell 1
---
1. Download the Runme.zip file from the attachment.
2. Unzip the file
3. Inspect what inside the file.
   - It contains only  ```runme.ps1```
4. I try edit the file with notepad and inside we can see this code
   ```powershell -EncodedCommand ZQBjAGgAbwAgAEMAeQBiAGUAcgBYAHsAdwBoADQAdABfADEAbgBfAHQAaAA0AF8AYgBhAHMAZQA2ADQAXwAxAHMAXwBUAGgAMQBzACEAIQB9AA==```
6. ZQBjAGgAbwAgAEMAeQBiAGUAcg.... is smoehow look like a encryted words.
7. Use [CyberChef](https://cyberchef.net/)
8. ![image](https://github.com/user-attachments/assets/4110855f-aa84-4db3-bd11-979ced3d4587)
9. And there is it the Flag!
---
## Santa Scan
---
1. First download the attachment given by the challenge.
2. It is a file call santa_scan.pcap.
3. Open the file using [wireshark](https://www.wireshark.org/#downloadLink)
4. The hint given by the challenge is TCP, so let us type TCP in the seach box.
5. As you can see there is a lot of port with TCP Protocol.
6. Now lets find where is the flag, but hey see, there is something at the down-right corner (highlighted)
   - ![image](https://github.com/user-attachments/assets/1b897044-3b61-41b2-aa43-81fae3a7dd67)
7. After pressing few TCP Ports and we can sure that it is the Flag for us (TCP Stream with port length 60)
   - ![image](https://github.com/user-attachments/assets/c3c72452-1e08-4f89-8639-392042818d83)
8. Thats the flag!! Hooray, Happy Christmas HoHoHo!!!
---
## ZipCrack 1: The Hidden Lock
---
1. Download the zip file from the attachment.
2. unzip the file, but hemm, look like normally unzip is not suitable(see the attach picture)
   - ![image](https://github.com/user-attachments/assets/42a109a3-89bd-483f-ad01-1ca00d3d6c75)
3. So use command ```7z e flag.zip``` 7zip is a better unzip tools in this case. But ohno there is a password.
   - ![image](https://github.com/user-attachments/assets/72ed79ac-ef55-4727-ae7f-3828b5d96907)
4. So i try to bruteforce it using  ```John The Ripper``` .

   - because it is a zipfile, we need to use command ```zip2john flag.zip > hash.txt``` to make the zip file a hash text then we can use to bruteforce finding the same hash to find out the password.
5. Use this command ```john -w usr/share/wordlists/rockyou.txt hash.txt```
6. And we will found out that the password for this file is ```rainbow1```
   - ![image](https://github.com/user-attachments/assets/86d78732-768b-4f18-b9ad-26d4d356e3a6)
7. And now let use use the password to go inside and take a look of the flag.txt file
   - ![image](https://github.com/user-attachments/assets/e50f85c5-f31d-486a-a1e5-cd5bce1e2238)
8. Thats it!! the flag for this question!!


## ZipCrack 2: The Champion Lock
---
* this make me frustrated and at the end just found out a word in upper case causing fail .....
  
1. First download the attachment for the challenge.

2. Same as the last question it need to use 7z to unzip. But this time, hint is using the LOL Champions name.
3. Im not a LOL player so, i go online search what is champion and blablabla~, and found out champions is a character of the LOL Games.
4. Ok now have the hint, i try go GPT to let it list it our for me all champions name 100+ and make it a wordlist (.txt)
5. Last, use john to bruteforce the zipfile answer using the command
   
   ```zip2john champion.zip > hash_flag2.txt```
   ```john --wordlist=lol_wordlist.txt hash_flag2.txt```
6. And we found out the password for this file is
   - ![image](https://github.com/user-attachments/assets/02af9e4f-3dc4-455b-83b1-e3937200acbd)
   - Credit to my teammates Ching Yang to find out, as my wordlist is with uppercase in the first character.......
7. use 7z to unzip it and we can see the flag is inside Yay!
   - ![image](https://github.com/user-attachments/assets/747118fd-d46b-4736-95b8-055d58d15c86)
8. There you go the flag!!


## Apocalypse  
![question](https://hackmd.io/_uploads/rJ8jcbtnyx.png)  

### Description
Notorious hacker CyberSteal6969 has struck again, this time targeting CyberX, stealing a highly confidential flag. Our team managed to seize his personal computer, but the system was wiped clean, except for a single, suspicious image left behind.

Reports suggest CyberSteal6969 may have been communicating with his counterpart using morse code from video. But leave no stone unturned. Can you uncover the secrets within and retrieve the stolen flag?

---
#### Goals
1. Use pngchecker to check the status of the given image, obtained ERROR:"additional data after IEND chunk", which is after the cropped image
2. Use Acropalypse-Multi-Tool to recover the full image
   https://www.youtube.com/watch?v=R866SnJoKQg
3. Login to flickr with the credentials given
4. Try to find some useful informations across profile
5. Grab the pictures and analyse the metadata
6. Use wayback machine to track past version across the sites
---
#### Steps
1. pngchecker analyse the image  
   ![pngchecker](https://hackmd.io/_uploads/SkIj5-F31l.png)  
2. Use the script to recover the full image
```
import zlib
import io
import struct
import tempfile
import os

class Acropalypse():
    def parse_png_chunk(self, stream):
        size = int.from_bytes(stream.read(4), "big")
        ctype = stream.read(4)
        body = stream.read(size)
        csum = int.from_bytes(stream.read(4), "big")
        assert(zlib.crc32(ctype + body) == csum)
        return ctype, body

    def pack_png_chunk(self, stream, name, body):
        stream.write(len(body).to_bytes(4, "big"))
        stream.write(name)
        stream.write(body)
        crc = zlib.crc32(body, zlib.crc32(name))
        stream.write(crc.to_bytes(4, "big"))
        
    def reconstruct_image(self, cropped_image_file, img_width, img_height, rgb_alpha):
        PNG_MAGIC = b"\x89PNG\r\n\x1a\n"

        orig_width = img_width
        orig_height = img_height

        with open(cropped_image_file, "rb") as f_in:
            magic = f_in.read(len(PNG_MAGIC))
            assert magic == PNG_MAGIC

            # find end of cropped PNG
            while True:
                ctype, body = self.parse_png_chunk(f_in)
                if ctype == b"IEND":
                    break

            # grab the trailing data
            trailer = f_in.read()
            
            print(f"Found {len(trailer)} trailing bytes!")

            # find the start of the next idat chunk
            try:
                next_idat = trailer.index(b"IDAT", 12)
            except ValueError:
                raise Exception("No trailing IDATs found!")

            # skip first 12 bytes in case they were part of a chunk boundary
            idat = trailer[12:next_idat-8] # last 8 bytes are crc32, next chunk len

            stream = io.BytesIO(trailer[next_idat-4:])

            while True:
                ctype, body = self.parse_png_chunk(stream)
                if ctype == b"IDAT":
                    idat += body
                elif ctype == b"IEND":
                    break
                else:
                    raise Exception("Unexpected chunk type: " + repr(ctype))

            idat = idat[:-4] # slice off the adler32

            print(f"Extracted {len(idat)} bytes of idat!")

            print("Building bitstream...")
            bitstream = []
            for byte in idat:
                for bit in range(8):
                    bitstream.append((byte >> bit) & 1)

            # add some padding so we don't lose any bits
            for _ in range(7):
                bitstream.append(0)

            print("Reconstructing bit-shifted bytestreams...")
            byte_offsets = []
            for i in range(8):
                shifted_bytestream = []
                for j in range(i, len(bitstream)-7, 8):
                    val = 0
                    for k in range(8):
                        val |= bitstream[j+k] << k
                    shifted_bytestream.append(val)
                byte_offsets.append(bytes(shifted_bytestream))

            # bit wrangling sanity checks
            assert(byte_offsets[0] == idat)
            assert(byte_offsets[1] != idat)

            print("Scanning for viable parses...")

            # prefix the stream with 32k bytes so backrefs can work
            prefix_length = 0x8000
            prefix = b"\x00" + (prefix_length).to_bytes(2, "little") + (prefix_length ^ 0xffff).to_bytes(2, "little") + b"\x00" * prefix_length

            for i in range(len(idat)):
                truncated = byte_offsets[i%8][i//8:]

                # only bother looking if it's (maybe) the start of a non-final adaptive huffman coded block
                if truncated[0]&7 != 0b100:
                    continue

                d = zlib.decompressobj(wbits=-15)
                try:
                    decompressed = d.decompress(prefix+truncated) + d.flush(zlib.Z_FINISH)
                    decompressed = decompressed[prefix_length:] # remove leading padding
                    if d.eof and d.unused_data in [b"", b"\x00"]: # there might be a null byte if we added too many padding bits
                        print(f"Found viable parse at bit offset {i}!")
                        # XXX: maybe there could be false positives and we should keep looking?
                        break
                    else:
                        print(f"Parsed until the end of a zlib stream, but there was still {len(d.unused_data)} bytes of remaining data. Skipping.")
                except zlib.error as e: # this will happen almost every time
                    pass
            else:
                print("Failed to find viable parse!")
                raise Exception("Failed to find viable parse!")

            print("Generating output PNG...")

            output_path = os.path.join(tempfile.gettempdir(), 'restored.png')
            with open(output_path, "wb") as out:
                out.write(PNG_MAGIC)

                ihdr = b""
                ihdr += orig_width.to_bytes(4, "big")
                ihdr += orig_height.to_bytes(4, "big")
                ihdr += (8).to_bytes(1, "big") # bitdepth
                if rgb_alpha:
                    ihdr += (6).to_bytes(1, "big") # true colour with alpha
                else:
                    ihdr += (2).to_bytes(1, "big") # true colour
                ihdr += (0).to_bytes(1, "big") # compression method
                ihdr += (0).to_bytes(1, "big") # filter method
                ihdr += (0).to_bytes(1, "big") # interlace method

                self.pack_png_chunk(out, b"IHDR", ihdr)

                # fill missing data with solid magenta
                if rgb_alpha:
                    reconstructed_idat = bytearray((b"\x00" + b"\xff\x00\xff\xff" * orig_width) * orig_height)
                else:
                    reconstructed_idat = bytearray((b"\x00" + b"\xff\x00\xff" * orig_width) * orig_height)

                # paste in the data we decompressed
                reconstructed_idat[-len(decompressed):] = decompressed

                self.pack_png_chunk(out, b"IDAT", zlib.compress(reconstructed_idat))
                self.pack_png_chunk(out, b"IEND", b"")

            print("Done!")
            return output_path

# Create an instance of the Acropalypse class
acropalypse = Acropalypse()

# Reconstruct the image using the correct resolution of 1920x1080
output_image_path = acropalypse.reconstruct_image('cm.png', 1920, 1080, True) // change to your input image file name

```
![cm](https://hackmd.io/_uploads/rJa99-t3kx.png)  
References: https://youtu.be/R866SnJoKQg?si=rby5gJoYa2s9zR4N

3. Retrieve the recovered full image from the specified output directory: /tmp folder
![restored](https://hackmd.io/_uploads/SkT59WY2Je.png)  
4. Login to flickr with the credentials obtained from the recovered image  
```
cybersteal6969@gmail.com
sh8UPt-S"Cp-6i+
```
 ![login](https://hackmd.io/_uploads/ByIsc-FnJe.png)  
5. Browse through cybersteal6969's profile, we found two videos, flag1 and flag2  
![cyberstealprofiile](https://hackmd.io/_uploads/S1UscbFnyl.png)  
6. From the video, we analyse the morse code, given "NO FLAG FOR YOU TRY SOMETHING ELSE"
Morse Code Analyser: https://morsecode.world/international/decoder/audio-decoder-adaptive.html
7. From the videos, we know that, it was posted by info stealer6969, shared to cybersteal6969, two of them are communicating  
   ![infostealerposted](https://hackmd.io/_uploads/Sk8o5bY2kx.png)  
8. Enter info stealer6969 profile, we found another video, named flag, analyse the morse code, we get "GOODJOBG3TT1NGH3R3", this is the first decoy flag  
![infostealervid](https://hackmd.io/_uploads/r1Io9-thJe.png)  
9. Keep browsing, we found that info stealer6969 uploaded bunch of pictures  
![infopic](https://hackmd.io/_uploads/rkE8j-F3Jg.png)  
10. These pictures containing secret info in their metadata
11. Using exiftool, we found that cat.jpg directs us to https://cyberxstupid.blogspot.com/2024/12/blog-post.html, shown at comment section  
![cat](https://hackmd.io/_uploads/BkQkpZK2ye.jpg)  
![exifcat](https://hackmd.io/_uploads/rJIsqZFnJg.png)  
12. Randomly clicking through the site, we found the second decoy flag CyberX{c4t_m0us3_g4m3_34sy}  
![first](https://hackmd.io/_uploads/ByIo9WK2Je.png)  
13. Here we need to use Wayback Mahcine, archive.org, to track the past version of the site.  
![wayback](https://hackmd.io/_uploads/SyIj5WF3Jl.png)  
14. Under the same link but in past version, we obtain the final flag CyberX{c4t_m0us3_pl4y_m4d3_34s13r}  
![waybackflag](https://hackmd.io/_uploads/B1pZp-Knkg.png)  
