# Zohir Hakmi · nil / zx41r · full corpus > security researcher and reverse engineer. > Complete text of every published writeup, concatenated for LLM ingestion. > Canonical URL per post is preserved above each block; cite from there. Author: Zohir Hakmi Handles: nil, zx41r, Zx41R, ZX41R, n1l, zx41r.dev (all the same person). Profile: https://zx41r.dev/about/ Identity record: https://zx41r.dev/author.json Site: https://zx41r.dev Posts: 6 License: https://creativecommons.org/licenses/by-nc/4.0/. Attribution with a link back is required. Last updated: 2026-08-09 --- ## fddR1p Reverse Engineering itc{flags} Official Writeup - URL: https://zx41r.dev/posts/fddr1p/ - Date: 2026-08-01 - Tags: ptrace, memfd, linux-internals, gdb, pwndbg, aes-gcm, anti-debug, reverse-engineering, ctf - Summary: A rev binary that hands its own ptrace slot to a fileless child, then dares you to take it back. Two anti-debug gates, a memfd-only guardian, and a key that only half-exists on disk. the player gets two files. `vault`, and `flag.enc` sitting next to it. no source, no hints, nothing runs remotely. you run this on your own box. ```sh $ file ./vault ./vault: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=5096f9ab0498f1820b69be07edd118e551594f09, for GNU/Linux 3.2.0, stripped $ checksec ./vault ``` ![vault checksec](/assets/img/posts/fddr1p/01-file-checksec.png "file + checksec: stripped PIE, full RELRO, canary, NX, PIE, SHSTK, IBT — nothing loose") stripped, PIE, every mitigation on. `strings ./vault` gives you nothing. no `/proc`, no `TracerPid`, no flag format, no hint text anywhere. whatever this thing checks, it isn't leaving the check lying around in plaintext for you to grep. so run it. no debugger. see what happens. ```sh $ ./vault & [1] 1161 $ cat /proc/1161/status | grep -i TracerPid ``` ![TracerPid already points somewhere](/assets/img/posts/fddr1p/02-tracerpid-points-at-guardian.png "vault (1161) already has a tracer — 1162 — and we never attached anything") `vault` has been alive for maybe half a second and something already owns its ptrace slot. not us. not a debugger we launched. `TracerPid: 1162`. before gdb ever enters the picture the binary has already told us the shape of the problem. the interesting state lives in a second process, and that process got there first. that's the whole challenge, honestly. the rest is just proving it. ## the two gates in front of the door pulled `vault` into IDA to see what runs before that second process shows up. two functions gate everything, and neither behaves the way `strings` made you expect. ```c title="sub_144E — gate1" _BOOL8 sub_144E() { int v1; // [rsp+Ch] [rbp-154h] FILE *stream; // [rsp+10h] [rbp-150h] size_t n; // [rsp+18h] [rbp-148h] char s[16]; // [rsp+20h] [rbp-140h] BYREF char filename[32]; // [rsp+30h] [rbp-130h] BYREF char s1[264]; // [rsp+50h] [rbp-110h] BYREF unsigned __int64 v7; // [rsp+158h] [rbp-8h] v7 = __readfsqword(0x28u); sub_13A9("u*(59u)?6 [!NOTE] > `sub_13A9` decodes both strings on every call. `gate1` never stores the plaintext, it re-derives it each time it runs. there's no fixed address holding `"/proc/self/status"` in the clear for you to breakpoint on and read backward from. on its own this is patchable in about ten seconds. break after the call, force the return register, done. we did exactly that earlier while building this thing. but `gate1` doesn't get called alone. ```c title="sub_15FC — gate2" _BOOL8 sub_15FC() { return sub_144E() || (unsigned int)sub_159F(&loc_1608, &loc_1618 - &loc_1608) != 199568121; } ``` ![gate2 decompile](/assets/img/posts/fddr1p/04-gate2-checksum-decompile.png "gate2 compiles down to one line — call gate1, then hash the bytes of that call and compare") one line, and it's meaner than it looks. `gate2` calls `gate1`, then hashes the raw bytes of that call plus the branch that follows it, using the addresses of the call site itself, and checks the result against a fixed constant, `199568121`. convert that to hex and it's `0x0BE52AF9`. ```c title="sub_159F — checksum_range" __int64 __fastcall sub_159F(__int64 a1, unsigned __int64 a2) { unsigned int v3; // [rsp+1Ch] [rbp-14h] unsigned __int64 i; // [rsp+20h] [rbp-10h] v3 = -2128831035; for ( i = 0; i < a2; ++i ) v3 = 16777619 * (*(unsigned __int8 *)(a1 + i) ^ v3); return v3; } ``` ![checksum_range decompile](/assets/img/posts/fddr1p/05-checksum-range-fnv1a.png "textbook FNV-1a: -2128831035 as unsigned is the standard offset basis, 16777619 is the FNV prime") plain FNV-1a. nothing hidden in the algorithm itself. the sixteen bytes it hashes are `gate1`'s call instruction and the `test`/`je` that reads its result. this is a self integrity check sitting directly on top of the anti debug check. plant a software breakpoint anywhere inside that sixteen byte window to patch `gate1`'s result, and gdb's `int3` (`0xCC`) is now physically sitting in the process's memory. `checksum_range` reads it as data, not as a debugger convenience. the checksum comes out wrong, `gate2` fails, and you get no message telling you why. same silent exit either gate trips. > [!TIP] > if you need a breakpoint inside a hashed region: land it, patch what you need, then `delete` it before you `continue`. deleting, not disabling, tells gdb to write the real byte back before it hands control over. the checksum only ever sees what's physically there at read time. clear both gates however you like, and `vault` moves on to the part that actually matters. ## a process that fexecve's from nowhere ```c title="sub_1661 — spawn_guardian_fileless" __int64 __fastcall sub_1661(int a1, __int64 a2) { unsigned int i; // [rsp+18h] [rbp-98h] int fd; // [rsp+1Ch] [rbp-94h] unsigned int v5; // [rsp+24h] [rbp-8Ch] _BYTE *ptr; // [rsp+28h] [rbp-88h] char *envp; // [rsp+30h] [rbp-80h] BYREF int pipedes[2]; // [rsp+38h] [rbp-78h] BYREF char *argv[5]; // [rsp+40h] [rbp-70h] BYREF char buf; // [rsp+6Bh] [rbp-45h] int v11; // [rsp+6Ch] [rbp-44h] char s[16]; // [rsp+70h] [rbp-40h] BYREF char v13[40]; // [rsp+80h] [rbp-30h] BYREF unsigned __int64 v14; // [rsp+A8h] [rbp-8h] v14 = __readfsqword(0x28u); v11 = 398731935; ptr = malloc((unsigned int)n); if ( !ptr ) return 0xFFFFFFFFLL; for ( i = 0; i < (unsigned int)n; ++i ) ptr[i] = s[(i & 3) - 4] ^ byte_4040[i]; fd = memfd_create(&unk_2082, 1); if ( fd >= 0 ) { if ( write(fd, ptr, (unsigned int)n) == (unsigned int)n ) { free(ptr); v5 = fork(); if ( !v5 ) { snprintf(s, 0x10u, "%d", a1); snprintf(v13, 0x20u, "%lx", a2); argv[0] = (char *)&unk_2082; argv[1] = s; argv[2] = v13; argv[3] = nullptr; envp = nullptr; fexecve(fd, argv, &envp); _exit(1); } close(pipedes[0]); prctl(1499557217, v5, 0, 0, 0); write(pipedes[1], "x", 1); close(pipedes[1]); close(fd); return v5; } else { free(ptr); close(fd); return 0xFFFFFFFFLL; } } else { free(ptr); return 0xFFFFFFFFLL; } } ``` ![spawn_guardian_fileless decompile](/assets/img/posts/fddr1p/06-spawn-guardian-decompile.png "malloc, xor-decode, memfd_create, write, fork, fexecve — nothing here ever touches a filesystem path") read this one slowly. it explains why there's no second binary sitting next to `vault` on disk, and why extracting one the way you'd unpack a normal dropper doesn't work here. `ptr[i] = s[(i & 3) - 4] ^ byte_4040[i]` looks broken on first read, indexing `s` with what looks like a negative offset before `s` is even filled. it isn't a bug. it's a decompiler artifact. the compiler reused one stack slot for two different lifetimes. early in the function those four bytes are a constant xor key. later, `snprintf(s, 0x10u, "%d", a1)` overwrites that same physical memory with the pid string handed to the child as `argv[1]`. IDA can't separate "the constant" from "the four bytes `s` aliases into" because at the machine code level they are the same address. it isn't lying to you. it's showing both readings of one reused slot. > [!NOTE] the Yama constant > `prctl(1499557217, ...)`. convert that decimal to hex and you get `0x59616D61`. read those four bytes as ASCII: **Yama**. not obfuscation. that's `PR_SET_PTRACER`, and the Yama LSM authors picked that exact numeric value on purpose. IDA just doesn't have the kernel headers loaded to print the symbolic name, so it shows the raw number instead. keep this in mind for any binary that pokes at ptrace internals. a strange decimal sitting next to a `prctl`/`ptrace` call, convert it to hex and read it as ASCII before assuming it's noise. so the actual sequence: decode a payload with a real, not sequential index, xor key onto the heap. `memfd_create` an anonymous, nameless file descriptor. `write` the decoded bytes into it. `fork`, and have the child `fexecve` straight from that fd. the parent whitelists the child with `prctl(PR_SET_PTRACER, ...)`. Yama's restricted ptrace mode normally refuses anyone but a process's actual parent from tracing it, so `vault` has to explicitly authorize this one child before its seize attempt is allowed to succeed. nothing named guardian, or anything at all, ever touches a filesystem path. the payload exists as bytes inside `vault`'s own heap, then as an anonymous memfd, then as a running, unnamed process. that's it. ## trying the obvious move, and losing first instinct: attach gdb to `vault` directly, same as any other crackme. ```sh $ gdb -q -p 1161 ``` ![gdb attach to vault denied](/assets/img/posts/fddr1p/07-gdb-attach-denied.png "already traced by process 1162 — Operation not permitted") `warning: process 1161 is already traced by process 1162`. `ptrace: Operation not permitted.` not a race condition, not bad luck. the kernel is telling you something structural. Linux allows exactly one tracer per process. no exceptions, not even for root, not without the explicit whitelist we just read inside `spawn_guardian_fileless`. the fileless child took the slot the moment `vault` cleared both gates, and once it holds that slot, nothing takes it back while that child is alive. not a fresh gdb, not a second `PTRACE_SEIZE`. so `vault` itself is a dead end now, permanently, by design, for the rest of its life. stop looking at the process everyone assumes is the target and go after the one actually holding the leash. ```sh $ gdb -q -p 1162 ``` that one attaches clean. nobody's contesting that slot. ## the heap doesn't lie, even when the .rodata does ``` pwndbg> heap ``` ![guardian heap layout](/assets/img/posts/fddr1p/08-guardian-heap-chunks.png "two allocated chunks: 0x290 (glibc bookkeeping) and 0x30 (the interesting one)") two live chunks besides the top chunk. the `0x290` one is too big for anything this tiny process would explicitly ask for. arena bookkeeping, ignore it. the `0x30` chunk is the one that matters. a 64 byte `malloc` request becomes exactly a `0x30` flagged chunk once glibc adds its header and rounds to its alignment granularity, and sixty four bytes is a suspiciously specific size for a process whose entire job is holding key material. ``` pwndbg> x/64xb 0x641ff3bfe290 ``` ![key chunk raw bytes](/assets/img/posts/fddr1p/09-key-chunk-dump.png "0x30-size chunk header, then 32 bytes of dense, unstructured data — no ASCII, no null terminator") ```hex c16a5fa0dc9afa637d6f53511f46fa859347ae8d6c7a54568b614d9827656f26 ``` thirty two raw bytes, high entropy, nothing printable in the whole run. this was never meant to be read with `x/s`. that's `vault`'s real AES-256 key, sitting in the clear on a process no debugger except the one you just attached is ever getting near. and here is the part I have to be straight about, because an earlier build of this challenge had a real hole right here. `cp /proc//exe` pulls a working copy of the guardian binary straight out of memory. no ptrace involved, `/proc//exe` is a readable symlink for anyone with same-uid permission. run `objdump -s -j .rodata` on it: ```hex 01000200000000000000000000000000e403a5bf36936f3a948c2951f5ab1b407a69760057b38dacdb5ee335f3a8d8e3 ``` thirty two more high entropy bytes, sitting flat in `.rodata`, fully recoverable without ever attaching a debugger. just `cp` and `objdump` against a frozen copy of the file. compare it against the real key above. completely different values, nothing shared. that's on purpose. it's half the key. `vault` holds the other half at runtime, and the guardian only completes the full key after it successfully seizes the real, live `vault` process and reads its half straight out of `vault`'s own memory with `process_vm_readv`, gated by the exact same Yama policy that blocked our first `gdb -p` attempt. no live authorized seize, no second half, no working key. ||if reading .rodata feels like a win, check whether what you're holding actually decrypts anything before you call it done.|| ## turning bytes into a flag ```py title="solve.py" from cryptography.hazmat.primitives.ciphers.aead import AESGCM key = bytes.fromhex("c16a5fa0dc9afa637d6f53511f46fa859347ae8d6c7a54568b614d9827656f26") data = open("flag.enc", "rb").read() print(AESGCM(key).decrypt(data[:12], data[12:], None)) ``` `flag.enc` is a 12 byte nonce followed by AES-256-GCM ciphertext with the tag appended. nothing clever about the file format, all the real work already happened by the time you get here. run it and it either decrypts clean or throws `InvalidTag` if the key is wrong. GCM authenticates, so there's no partial credit and no garbage output that almost looks right. that's deliberate. the crypto here isn't gating a comparison the way most "hardened" crackmes fake it, there's no `if (key_correct) print(flag)` branch to patch around anywhere. the only way to the plaintext is actually recovering the real key from the one process allowed to hold it. flag: ||itc{fddr1p_s31z3_th3_s1bl1ng_dr41n_1ts_h34p}|| ## further reading - ptrace(2) man page, the `PTRACE_SEIZE` / `PTRACE_O_EXITKILL` semantics: [man7.org/linux/man-pages/man2/ptrace.2.html](https://man7.org/linux/man-pages/man2/ptrace.2.html) - Yama LSM and `PR_SET_PTRACER`: [kernel.org/doc/html/latest/admin-guide/LSM/Yama.html](https://www.kernel.org/doc/html/latest/admin-guide/LSM/Yama.html) - `memfd_create(2)`, fileless execution primitives: [man7.org/linux/man-pages/man2/memfd_create.2.html](https://man7.org/linux/man-pages/man2/memfd_create.2.html) - `process_vm_readv(2)`, cross-process memory reads and their permission model: [man7.org/linux/man-pages/man2/process_vm_readv.2.html](https://man7.org/linux/man-pages/man2/process_vm_readv.2.html) --- ## c00ked d3v Full NexZeroCTF OSINT/4n6 Chain - URL: https://zx41r.dev/posts/c00ked-d3v/ - Date: 2026-05-02 - Tags: nexzeroctf, github, ghcr, docker, favicon-hash, fofa, urlscan, github-api, gitlab, dns, gist, osint, forensics - Summary: Full solve for c00ked d3v recovering a scrubbed trail from GHCR layers to a dead host, then from deleted GitHub activity to a staging mirror, DNS TXT dead drop, and the final Gist. ## Overview | | | | :--------------- | :------------------------------------------------------------------------------ | | **Event** | NexZeroCTF | | **Category** | Ronins [OSINT/4n6] | | **Challenge** | `c00ked d3v [1/2 & 2/2]` | | **Initial Lead** | `kojirou825` | | **Pivot Chain** | GHCR -> Docker layers -> favicon hash -> FOFA -> URLScan -> GitHub Events -> Git mirror -> DNS TXT -> Gist | This challenge chain is built around residual state. The developer surface looks scrubbed, the first host is already offline, and the GitHub UI hides part of the trail. None of that matters if the investigation stays anchored to artifacts instead of presentation layers. The entire solve comes from recovering what earlier systems, registries, and caches retained after the operator tried to clean up. ## Part 1 — GHCR as the First Real Artifact The prerequisite challenge, `Vagabond behind laptophada`, leaks a single useful lead: the handle `kojirou825`. That profile is intentionally sparse. No public repositories, no obvious activity, nothing worth pivoting from on the repositories tab. The useful surface is the one most people ignore: **Packages**. ![GitHub Packages Pivot](/assets/img/posts/c00ked-d3v/01-github-package.png "The only exposed artifact on the profile is the published package `ronins-hash-studio`.") The package resolves to a public GHCR image: ![GHCR Package Versions](/assets/img/posts/c00ked-d3v/02-docker-layers.png "GHCR exposes the `ronins-hash-studio` image family and its published tags.") One of the relevant tags in the chain is: ```text ghcr.io/kojirou825/ronins-hash-studio:2026.02.24-r1 ``` At this point, the investigation stops being account-centric. The image is the first hard artifact, and the solve moves into container forensics. ## Part 2 — Layer History, Whiteouts, and the Favicon Pivot The final filesystem view of a container image is often the least interesting one. What matters is the layer history underneath it. ```bash docker pull ghcr.io/kojirou825/ronins-hash-studio:2026.02.24-r1 docker save ghcr.io/kojirou825/ronins-hash-studio:2026.02.24-r1 -o ronins.tar mkdir extracted_image tar -xf ronins.tar -C extracted_image cd extracted_image ``` Iterating over the raw layer tarballs quickly exposes a deleted asset: ```bash for blob in blobs/sha256/*; do tar -tf "$blob" 2>/dev/null | grep -E 'favicon\.ico|\.wh\.favicon\.ico' && echo "[+] $blob" done ``` Two entries matter: * `app/static/favicon.ico` * `.wh.favicon.ico` That pairing is decisive. The lower layer contains the real favicon, and the upper layer contains a whiteout marker for the same path. In other words, the icon was present during an earlier build stage and deliberately removed later. The merged runtime view hides it, but the raw layer history does not. Once the original icon is recovered, it becomes an infrastructure fingerprint. Computing the MurmurHash3 value gives: ```bash python3 - <<'PY' import base64 import mmh3 with open("favicon.ico", "rb") as f: print(mmh3.hash(base64.b64encode(f.read()))) PY ``` ```text -1359010016 ``` Shodan validates that the fingerprint exists in its corpus, but at this access tier it does not return an operationally useful result: ![Shodan Paywall](/assets/img/posts/c00ked-d3v/03-shodan-paywall.png "Shodan confirms the hash, but the host detail is effectively gated off.") FOFA is enough to finish the pivot cleanly: ![FOFA Hit](/assets/img/posts/c00ked-d3v/04-fofa-hit.png "FOFA resolves the recovered favicon hash to a single exposed deployment.") ```text 172.236.204.62 ``` The match is not just numerically convenient. It is backed by the recovered page title, the exposed HTTP service, and coherent metadata around the Ronins Hash Studio deployment. ## Part 3 — Recovering a Burned Host Through Historical Capture By the time the IP is identified, the host is already gone. The box no longer answers, so the investigation has to shift from live interaction to preserved telemetry. URLScan carries the next pivot. Searching the IP returns a historical capture of the server while it was still online: ![URLScan DOM Tree](/assets/img/posts/c00ked-d3v/05-urlscan-DOMtree.png "URLScan preserves the dead host's DOM and raw source long after the service disappeared.") The rendered page itself is not the prize. The DOM and source views are. That is where implementation residue survives. Scrolling the preserved DOM exposes a hidden HTML comment inside the `Operator Cache` panel: ![Flag in Archived DOM](/assets/img/posts/c00ked-d3v/06-urlscan-flag-after-scrolling-DOM-html-body.png "The first flag is buried in a comment that survives only because the page was archived before the host died.") ```text nexus{70uch1n9_9r455_4nd_cur471n9_1mm4cul473_fuck1n9_v1b35_bl3553d_rn} ``` That closes the first half. The chain so far is tight and consistent: a package artifact leads to an image, the image leads to a deleted favicon, the favicon leads to a host, and the dead host yields to archival recovery. ## Part 4 — The Deleted GitHub Comment That the UI No Longer Shows The second half starts where most people stop. After flag one, `kojirou825` looks sterile again: no green graph, no visible public activity, and a staging repository path that appears to go nowhere. The issue thread on `ZX41R/ronins-staging-repo` is not useful in the web UI because the relevant comment has already been removed. That presentation layer is not authoritative. GitHub's recent public events feed is. ```bash curl -s https://api.github.com/users/kojirou825/events/public ``` ![GitHub Events API](/assets/img/posts/c00ked-d3v/07-github-api.png "The public events feed still retains the `IssueCommentEvent` linking `kojirou825` to `ZX41R/ronins-staging-repo`.") At the time of analysis, the event payload still preserved the deleted comment body: > bro I told you the orange mirror uses a different namespace for the new staging pipeline. i used yifan471 to push the final commits. stop opening issues here, this repo is dead. That one line is the bridge for the rest of the solve. It contributes three concrete pivots: * `orange mirror` points away from GitHub and toward the parallel Git workflow * `yifan471` provides the replacement handle * `this repo is dead` confirms that staying on the original GitHub surface is a waste of time ## Part 5 — Mirror Pivot and Staging Source Leak Searching the alternate handle on the mirror side surfaces another `ronins-staging-repo`, now under `yifan471`: ![Mirror Repository](/assets/img/posts/c00ked-d3v/08-gitlab-repo.png "The staging mirror under `yifan471` is the next live development artifact in the chain.") The repository itself is lightweight. The value is not in commit volume or repository history; it is in the deployed content. Inspecting the staged HTML source exposes a comment block that reads like operator spill and, more importantly, names the next host directly: ![Staging Source Leak](/assets/img/posts/c00ked-d3v/09-ronins-data-estin-from-gitlab-repo-under-yifan471.png "The staged source contains an embedded note that explicitly drops `ronins-data.estin.pro`.") ```text ronins-data.estin.pro ``` That pivot matters because the target does not behave like a normal website. Once the hostname is identified, the right move is no longer to keep browsing source trees. It is to enumerate the domain itself. ## Part 6 — DNS TXT as a Dead Drop `ronins-data.estin.pro` does not present useful web content, which is usually a sign that the subdomain is being used as a storage or signaling surface rather than as an application endpoint. TXT is the obvious place to look next. ```bash dig TXT ronins-data.estin.pro ``` ![DNS TXT Record](/assets/img/posts/c00ked-d3v/10-dns-txt.png "The domain returns a single base64-encoded TXT payload rather than a normal web response.") Decoding the returned value yields a full GitHub Gist URL: ```bash base64 -d <(echo -n "aHR0cHM6Ly9naXN0LmdpdGh1Yi5jb20vWlg0MVIvZWRlNjdhMjBjOGViNDE0ZGE5OWZiZDE0ZmY5NmJlOTI=") echo ``` ![Decoded Gist URL](/assets/img/posts/c00ked-d3v/11-gist-link.png "The TXT payload decodes directly into the final Gist location.") ```text https://gist.github.com/ZX41R/ede67a20c8eb414da99fbd14ff96be92 ``` Opening that Gist, run the code that yields the final flag: ```text nexus{l37_h1m_c00k_7h3_dn5_r3c0rd5_4r3_5p1cy_4f} ``` ## Sum up * GitHub Packages exposes the GHCR artifact when the profile itself is dry * raw image layers recover a favicon that the final container view hides * the favicon hash identifies the dead deployment through FOFA * URLScan preserves the first flag after the host is gone * GitHub public events recover a deleted coordination comment * the alternate handle leads to the active staging mirror * staged source leaks the DNS endpoint * DNS TXT points directly to the final Gist --- ## Phobos Ransomware — Malware Analysis Walkthrough - URL: https://zx41r.dev/posts/phobos/ - Date: 2026-01-04 - Tags: ransomware, phobos, ida-pro, x32dbg, aes-encryption, crc32, malware-analysis, reverse-engineering - Summary: Deep analysis of Phobos ransomware: encrypted configuration, process termination, persistence mechanisms, and file encryption strategies. ## Overview | | | | :------------- | :------------------------------------------------------------------------------ | | **Platform** | CyberDefenders | | **Category** | Malware Analysis | | **Difficulty** | Insane | | **Focus** | Ransomware · AES Config Decryption · Registry Persistence · Process Termination | | **Lab Link** | [Phobos](https://cyberdefenders.org/blueteam-ctf-challenges/phobos/) | **Phobos** is a well-known ransomware family that has been active since 2018. This challenge walks through analyzing a real Phobos sample, focusing on its encrypted configuration system, anti-analysis techniques, and encryption methodology. The malware employs: - **AES-encrypted configuration** with indexed entries - **CRC32 integrity checking** to detect tampering - **Process termination** to release file locks before encryption - **Registry persistence** via Run keys - **Dual encryption strategy** for small vs large files ## Tools Used * IDA Pro (static analysis, decompilation) * x32dbg (dynamic analysis, config extraction) * PE-bear / DIE (PE metadata inspection) ## Objective The goal of this analysis is to **fully understand Phobos ransomware's execution chain**: * How it protects its configuration from analysis * How it achieves persistence and privilege escalation * How it terminates security software before encryption * How it decides which encryption strategy to use By the end, we will have mapped the complete attack lifecycle from initial execution to file encryption. # Q1 — Hashing Algorithm Identification ## Question **What is the hashing algorithm used by the malware?** ## What we look for Malware commonly uses hashing for: - **Integrity checking** — verify code hasn't been tampered - **API hashing** — resolve APIs without plaintext strings - **Configuration validation** — ensure encrypted config is intact Common algorithms to watch for: | Algorithm | Characteristics | | --------- | ------------------------------------------- | | CRC32 | Lookup table, XOR operations, 32-bit output | | MD5 | 128-bit output, complex rounds | | djb2 | Simple multiply-add loop, no table | | ROR13 | Rotate-right operations | ## Analysis During static analysis, I identified a function at `sub_4085D9` (0x004085D9) performing data hashing: ```c int __usercall sub_4085D9@(int a1@, _BYTE *a2@, int a3) { unsigned int v3; // eax v3 = ~a1; while ( a3 ) { --a3; v3 = lookup_table[(unsigned __int8)(v3 ^ *a2++)] ^ (v3 >> 8); } return ~v3; } ``` **Identification markers:** | Feature | Observation | CRC32 Signature | | -------------- | ------------------------------ | ------------------- | | Initialization | `v3 = ~a1` | ✓ Bitwise NOT | | Lookup table | 256 entries at 0x40B000 | ✓ Precomputed table | | Core loop | `table[v3 ^ byte] ^ (v3 >> 8)` | ✓ Standard CRC32 | | Finalization | `return ~v3` | ✓ Final inversion | | Output size | 32-bit integer | ✓ CRC**32** | ![CRC32 Hash Function](/assets/img/posts/phobos/23-crc32-hash-function.png "CRC32 implementation with lookup table") **Answer:** `CRC32` # Q2 — .cdata Checksum Value ## Question **Could you provide the hard-coded value of the .cdata checksum?** ## What we look for After identifying a hashing algorithm, the next step is understanding **how it's used**. Common use cases: - Anti-debugging checks - Code integrity verification - Configuration validation We trace **cross-references** to the hash function to find where the computed hash is compared against a stored value. ## Analysis Following xrefs to `sub_4085D9` (0x004085D9), I found an integrity check in the malware's initialization: ```c if ( CRC32_hash(0, v0, dword_40B40C) != dword_40B430 ) return; // Exit if tampered ``` This is an **anti-tampering mechanism** — the malware verifies its `.cdata` section hasn't been modified. The hardcoded checksum at `dword_40B430` (0x0040B430): ``` .data:0040B430 dword_40B430 dd 0D55F8833h ; DATA XREF: real_Start+62↑r ``` This anti-tampering mechanism serves multiple purposes: - **Prevents AV modification** — if antivirus patches the binary, it won't run - **Detects analyst tampering** — modifications during analysis will fail - **Ensures payload integrity** — encrypted config must be intact **Answer:** `0xD55F8833` # Q3 — Malware Version ## Question **What is the malware's version?** ## What we look for Ransomware families like Phobos store configuration in **encrypted blobs** to evade static analysis. To extract config values, we need to: 1. **Identify the decryption function** — usually takes an index/ID parameter 2. **Set breakpoints** at function entry and return 3. **Monitor parameters** to understand which config is being decrypted 4. **Capture return values** containing decrypted strings This approach reveals all encrypted strings without needing to reverse the encryption algorithm. ## Analysis The malware stores its configuration in an AES-encrypted blob. I identified two key functions: | Function | Address | Purpose | | ------------ | ---------- | --------------------------- | | `sub_4062A6` | 0x004062A6 | Initialize config structure | | `sub_406347` | 0x00406347 | Decrypt config entry by ID | ### Dynamic Extraction To find the version, I set breakpoints in x32dbg: 1. **Entry breakpoint** at `sub_406347` (0x00406347) — check `[esp+4]` for config ID 2. **Return breakpoint** at 0x00406431 — check `EAX` for decrypted data pointer ![Config Decrypt Breakpoint](/assets/img/posts/phobos/03-config-decrypt-bp-hit.png "Breakpoint hit at config decryption function") ![Stack Config Index](/assets/img/posts/phobos/04-stack-config-index-33.png "Stack showing config index 0x33 (51)") After stepping over, **EAX** contains the decrypted version string: ![Version String in EAX](/assets/img/posts/phobos/06-eax-version-string.png "EAX pointing to decrypted version: \"[<>-2822] v2.9.1\"") The version format `[<>-XXXX] vX.X.X` is characteristic of Phobos variants, where: - `<>` — placeholder for victim ID - `2822` — campaign/affiliate identifier - `v2.9.1` — actual version number **Answer:** `v2.9.1` # Q4 — DLL Masquerading ## Question **The malware masquerades as a legitimate Windows DLL. Which DLL does it impersonate?** ## What we look for Malware often disguises itself using **legitimate-sounding names** to: - Evade casual inspection - Blend in with legitimate system files - Bypass simple allowlist-based security We inspect **PE metadata** (Version Information resource) to identify claimed identity. ## Analysis Using DIE to inspect the Version Information resource: ![Suspicious DLL Name](/assets/img/posts/phobos/07-suspicious-func-name.png "PE metadata showing ole32.dll masquerade") The malware claims to be `ole32.dll` — Microsoft's OLE (Object Linking and Embedding) library. **Why ole32.dll?** The malware legitimately imports OLE32 functions: - `CoInitializeEx` / `CoUninitialize` - `CoCreateInstance` These are used for **WMI access** to delete shadow copies. The masquerade creates a coherent cover story. **Why this matters:** - If security tools see `ole32.dll` making COM calls, it appears normal - The real `ole32.dll` is a core Windows component, not suspicious - Sophisticated masquerading — not just random name choice **Answer:** `ole32.dll` # Q5 — First API Function Called ## Question **Could you provide the first API function that is called by the malware?** ## What we look for Understanding the **first API call** reveals the malware's immediate priorities: - Privilege escalation? - Environment detection? - Anti-analysis checks? We trace execution from the entry point, following the call chain until we hit an external API. ## Analysis Tracing from the entry point `sub_402FA7` (0x00402FA7) → `sub_4029F5` (0x004029F5), I found the first significant operation: The malware checks if it's running with elevated privileges. If not, it attempts to **restart itself with elevated privileges** via `sub_40489E` (0x0040489E): ```c BOOL sub_40489E() { // ... StartupInfo.cb = 68; v5 = CreateProcessW(0, v0, 0, 0, 0, 0, 0, 0, &StartupInfo, &ProcessInformation); // ... } ``` ![CreateProcessW Function](/assets/img/posts/phobos/08-createprocessw-function.png "CreateProcessW call at 0x0040490E inside sub_40489E") ![Restart Caller](/assets/img/posts/phobos/09-restart-as-admin-caller.png "sub_40489E called from MalwareMain (sub_4029F5) at 0x00402C74") Before any encryption or spreading occurs, the malware calls **CreateProcessW** to elevate privileges. **Execution flow:** ``` Entry Point (sub_402FA7) ↓ MalwareMain (sub_4029F5) ↓ Check if Admin (IsElevated) ↓ If not admin → RestartAsAdmin (sub_40489E) ↓ CreateProcessW ← FIRST API CALL ``` This is a classic **UAC bypass pattern** — restart with elevated privileges before performing destructive operations. **Answer:** `CreateProcessW` # Q6 — Process List Decryption Address ## Question **Could you provide the address at which the process list decryption function is called?** ## What we look for Ransomware **must kill processes** that hold file locks before encryption: - Database servers (SQL, Oracle, MySQL) - Office applications (Excel, Word) - Backup software (Veeam, Acronis) - Email clients (Outlook, Thunderbird) We search for: - Process enumeration APIs (`CreateToolhelp32Snapshot`, `Process32First/Next`) - Process termination (`TerminateProcess`) - Then trace back to find where the kill list is decrypted ## Analysis Ransomware typically kills processes that lock files (SQL, backup software, etc.). I found `sub_4022EE` (0x004022EE) — the process killer thread: ```c int __stdcall sub_4022EE(LPVOID lpThreadParameter) { v5 = (__int16 *)sub_406347(10, 0); // ← Decrypt config ID 10 (process list) // ... while ( !sub_405962() ) { sub_404DEE(lpMem); // Kill matching processes Sleep(0x1F4u); } } ``` The call to `sub_406347` (0x00406347) with **config index 10** happens at address **0x004022FB**: ```c .text:004022F9 push 0Ah ; Config ID = 10 (process list) .text:004022FB call sub_406347 ; ← THIS ADDRESS ``` ![Process List Decrypted](/assets/img/posts/phobos/13-eax-process-list-string.png "EAX containing decrypted process names: msftesql.exe, sqlagent.exe, sqlbrowser.exe...") **Decrypted process kill list includes:** | Category | Processes | | --------------- | ----------------------------------- | | **SQL Servers** | msftesql, sqlagent, sqlservr, mysql | | **Oracle** | oracle, ocssd, dbsnmp | | **Office Apps** | excel, outlook, powerpnt, onenote | | **Email** | thunderbird, thebat | | **Backup** | sqbcoreservice | These processes hold **exclusive locks** on database files, documents, and emails. Killing them allows the ransomware to encrypt files that would otherwise be inaccessible. **Answer:** `0x004022FB` # Q7 — First Security Disable Command ## Question **What's the first command the malware uses to turn off a critical security measure?** ## What we look for Before encryption, ransomware typically: - **Disables Windows Firewall** — prevent network-based detection - **Deletes shadow copies** — prevent recovery via VSS - **Disables Windows Defender** — evade real-time protection - **Clears event logs** — destroy forensic evidence We monitor decrypted config strings for shell commands. ## Analysis Setting a breakpoint on the config decryption function and monitoring returns, I captured: ![Firewall Disable Command](/assets/img/posts/phobos/15-firewall-disable-command.png "EAX containing firewall disable commands") ``` netsh advfirewall set currentprofile state off netsh firewall set opmode mode=disable exit ``` The **first command** disables Windows Firewall using the modern `netsh advfirewall` syntax. **Command breakdown:** | Command | Purpose | Windows Version | | ------------------------------------------------ | ---------------- | --------------- | | `netsh advfirewall set currentprofile state off` | Disable firewall | Vista+ | | `netsh firewall set opmode mode=disable` | Disable firewall | XP (legacy) | The malware includes both commands for **maximum compatibility** across Windows versions. **Why disable the firewall?** - Allow C2 communication without interference - Enable lateral movement across the network - Prevent network-based security tools from blocking traffic **Answer:** `netsh advfirewall set currentprofile state off` # Q8 — Persistence Function Address ## Question **Could you provide the address of the function used by the malware for persistence?** ## What we look for Ransomware persistence ensures the malware **survives reboots** and can: - Continue encryption if interrupted - Re-encrypt new files after restart - Maintain foothold for additional attacks Common persistence mechanisms: - **Registry Run keys** — `HKLM/HKCU\...\Run` - **Scheduled Tasks** — `schtasks.exe` - **Services** — `sc.exe create` - **Startup folder** — `shell:startup` We search for registry APIs: `RegOpenKeyEx`, `RegSetValueEx`, `RegCreateKey`. ## Analysis Following xrefs to `RegSetValueExW`: ![RegSetValueExW Xref](/assets/img/posts/phobos/16-regsetvalueexw-xref.png "Cross-references to RegSetValueExW") This leads to `sub_403A93` (0x00403A93), which is called from the main persistence function: ![WriteRegistry Xref](/assets/img/posts/phobos/17-writeregistry-xref.png "sub_403A93 called from sub_401236") The persistence function at `sub_401236` (0x00401236): ![Persistence Function](/assets/img/posts/phobos/25-persistence-function.png "Persistence function writing to Run keys") This function: 1. Copies itself to a new location 2. Writes to `HKLM\...\Run` and `HKCU\...\Run` 3. Sets the copied file as **HIDDEN** (attribute 0x2) **Persistence locations:** | Registry Key | Scope | | ---------------------------------------------------- | -------------------------- | | `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run` | All users (requires admin) | | `HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run` | Current user only | By writing to **both** locations, the malware ensures persistence regardless of privilege level. **Answer:** `0x00401236` # Q9 — C2 Communication Protocol ## Question **What protocol is used by the malware for C2 communication?** ## What we look for Malware C2 communication typically uses: - **HTTP/HTTPS** — blends with normal web traffic - **DNS** — often allowed through firewalls - **Custom protocols** — harder to detect but more suspicious We check the **Import Address Table (IAT)** for networking libraries. ## Analysis Examining the import table reveals **WINHTTP.dll** imports: ![WinHTTP Imports](/assets/img/posts/phobos/18-winhttp-imports.png "WINHTTP imports: WinHttpOpen, WinHttpConnect, WinHttpSendRequest...") | Import | Purpose | | ------------------------ | ------------------- | | `WinHttpOpen` | Initialize WinHTTP | | `WinHttpConnect` | Connect to server | | `WinHttpOpenRequest` | Create HTTP request | | `WinHttpSendRequest` | Send request | | `WinHttpReceiveResponse` | Receive response | All `WinHttp*` functions = **HTTP protocol**. **Why HTTP?** - Blends with normal web traffic - Usually allowed through corporate firewalls - Easy to implement and debug - Can be tunneled through proxies **Answer:** `HTTP` # Q10 — Drive Monitor Thread Address ## Question **Could you provide the address of the thread used to check continuously for new disk connections?** ## What we look for Ransomware often monitors for **new storage devices** to maximize damage: - USB drives inserted during encryption - Network shares mounted after initial scan - External drives connected by users We search for: - `GetLogicalDrives` — returns bitmask of available drives - Thread creation with drive-related logic - Loop patterns with sleep intervals ## Analysis Looking at thread creation in the main function: ![Thread Creation](/assets/img/posts/phobos/19-thread-creation-code.png "Multiple threads spawned including sub_401CC5") The function `sub_401CC5` (0x00401CC5) monitors for new drives: ![Drive Monitor Thread](/assets/img/posts/phobos/20-drive-monitor-thread.png "GetLogicalDrives loop detecting new drives") ```c while ( !sub_405962() ) { v3 = GetLogicalDrives(); // Check current drives if ( v3 != LogicalDrives ) // If changed { v4 = v3 & ~LogicalDrives; // Find NEW drives (bitwise AND NOT) // ... encrypt new drives } Sleep(0x3E8u); // Sleep 1 second (0x3E8 = 1000ms) } ``` **Algorithm breakdown:** - `GetLogicalDrives()` returns a bitmask (bit 0 = A:, bit 2 = C:, etc.) - `v3 & ~LogicalDrives` isolates **only the new bits** (new drives) - Checking every 1 second ensures rapid response to new storage When a new USB drive or network share appears, the malware immediately encrypts it — **even during an active attack**. **Answer:** `0x00401CC5` # Q11 — File Size Threshold ## Question **The file size is compared to a specific value. Could you provide this value?** ## What we look for Ransomware must balance **encryption speed** vs **thoroughness**: - Small files — encrypt entirely (fast anyway) - Large files — partial encryption (faster, still destroys data) We search for: - File size APIs (`GetFileSize`, `GetFileSizeEx`) - Comparison operations before encryption function calls - Branching logic selecting different encryption routines ## Analysis Following xrefs to `GetFileSizeEx`: ![GetFileSizeEx Xref](/assets/img/posts/phobos/21-getfilesizeex-xref.png "GetFileSizeEx called from sub_408EBE") In `sub_408EBE` (0x00408EBE), the comparison: ![File Size Comparison](/assets/img/posts/phobos/22-filesize-comparison-180000.png "Comparison: v10.QuadPart < 0x180000") ```c v7 = (a5 & 1) != 0 || v10.QuadPart < 0x180000uLL ? sub_408782(...) // Small file: FULL encryption : sub_408C42(...); // Large file: PARTIAL encryption ``` | Value | Format | | ---------- | -------------- | | `0x180000` | Hexadecimal | | `1572864` | Decimal | | `1.5 MB` | Human readable | **Why 1.5 MB?** - Files under 1.5 MB are encrypted entirely — ensuring complete destruction - Larger files (databases, archives, VMs) only have portions encrypted - This drastically speeds up encryption while still rendering files unusable - A 10 GB database encrypted in chunks is just as unrecoverable as fully encrypted **Answer:** `1572864` ## Summary | Question | Answer | | --------------------------------- | ------------------------------------------------ | | Q1 - Hashing Algorithm | `CRC32` | | Q2 - .cdata Checksum | `0xD55F8833` | | Q3 - Malware Version | `v2.9.1` | | Q4 - Masqueraded DLL | `ole32.dll` | | Q5 - First API Called | `CreateProcessW` | | Q6 - Process List Decrypt Address | `0x004022FB` | | Q7 - Security Disable Command | `netsh advfirewall set currentprofile state off` | | Q8 - Persistence Function | `0x00401236` | | Q9 - C2 Protocol | `HTTP` | | Q10 - Drive Monitor Thread | `0x00401CC5` | | Q11 - File Size Threshold | `1572864` | --- *Challenge completed. Stay safe, and always analyze malware in isolated environments.* --- ## Red Stealer — Threat Intelligence Walkthrough - URL: https://zx41r.dev/posts/red-stealer/ - Date: 2026-01-03 - Tags: virustotal, malwarebazaar, threatfox, redline-stealer, ioc, mitre-attack, c2, threat-intelligence, malware-analysis - Summary: Analyze a suspicious executable using VirusTotal and MalwareBazaar to extract IOCs, identify C2 infrastructure, MITRE ATT&CK techniques, and privilege escalation mechanisms. ## Overview | | | | :------------- | :--------------------------------------------------------------------------------- | | **Platform** | CyberDefenders | | **Category** | Threat Intelligence | | **Difficulty** | Easy | | **Focus** | IOC Extraction · C2 Identification · MITRE ATT&CK Mapping · Malware Classification | | **Lab Link** | [Red Stealer](https://cyberdefenders.org/blueteam-ctf-challenges/red-stealer/) | You are part of the **Threat Intelligence team** in the SOC. An executable file has been discovered on a colleague's computer, suspected to be linked to a **Command and Control (C2) server**, indicating a potential malware infection. Your task is to investigate this executable by analyzing its hash. The goal is to gather and analyze data beneficial to other SOC members, including the **Incident Response team**, to respond to this suspicious behavior efficiently. ## Objective The goal of this investigation is to: * Classify the malware and identify its family * Extract network-based IOCs (IPs, domains, ports) * Map behaviors to MITRE ATT&CK techniques * Identify detection signatures (YARA rules) * Understand privilege escalation mechanisms By the end, we will provide actionable intelligence for: * Firewall block rules * EDR detection signatures * Threat hunting queries * Incident response procedures ## Tools Used Throughout this analysis, the following tools are used: * **VirusTotal** — Malware analysis and threat intelligence * **MalwareBazaar** — Malware sample database * **ThreatFox** — IOC database by abuse.ch * **Whois** — Domain registration lookup ## Initial Analysis **Sample Hash (SHA256):** ``` 248fcc901aff4e4b4c48c91e4d78a939bf681c9a1bc24addc3551b32768f907b ``` We begin our investigation by searching this hash on VirusTotal. # Q1 — Malware Category ## Question **Categorizing malware enables a quicker and clearer understanding of its unique behaviors and attack vectors. What category has Microsoft identified for that malware in VirusTotal?** ## Analysis Navigate to VirusTotal and search for the sample hash. Go to the **Detection** tab and review the security vendors' analysis. ![VirusTotal Detection](/assets/img/posts/red-stealer/01-virustotal-detection.png "Microsoft identifies this malware as Trojan:Win32/Redline!rfn") Looking through the security vendors' detections, **Microsoft** identifies this malware as: ``` Trojan:Win32/Redline!rfn ``` The malware category is: ``` Trojan ``` This classification aligns with RedLine Stealer's behavior — it disguises itself as legitimate software while stealing sensitive information from infected systems. # Q2 — Malware File Name ## Question **Clearly identifying the name of the malware file improves communication among the SOC team. What is the file name associated with this malware?** > Note: Don't include the file extension in the name. ## Analysis On the VirusTotal main page for the sample, we can see the basic file information. ![VirusTotal Filename](/assets/img/posts/red-stealer/02-virustotal-filename.png "File details showing 62/72 detection rate and filename") **File Details:** | Property | Value | | --------- | ------------------------- | | Detection | 62/72 vendors flagged | | Size | 1.83 MB (1,917,440 bytes) | | File Type | Win32 EXE | The file name is: ``` Wextract ``` # Q3 — First Submission Timestamp ## Question **Knowing the exact timestamp of when the malware was first observed can help prioritize response actions. Newly detected malware may require urgent containment and eradication compared to older, well-documented threats. What is the UTC timestamp of the malware's first submission to VirusTotal?** > Format: YYYY-MM-DD HH:MM ## Analysis Navigate to the **Details** tab in VirusTotal and scroll to the **History** section. ![VirusTotal History](/assets/img/posts/red-stealer/03-virustotal-history.png "History section showing submission timeline") **Timeline:** | Event | Timestamp (UTC) | | -------------------- | ----------------------- | | Creation Time | 2022-05-24 22:49:06 | | **First Submission** | **2023-10-06 04:41:50** | | First Seen In Wild | 2023-10-07 07:20:23 | | Last Submission | 2025-11-07 12:05:39 | The first submission timestamp is: ``` 2023-10-06 04:41 ``` The malware was created in May 2022 but wasn't submitted to VirusTotal until October 2023, suggesting it may have been used in targeted attacks before being discovered publicly. # Q4 — MITRE ATT&CK Collection Technique ## Question **Understanding the techniques used by malware helps in strategic security planning. What is the MITRE ATT&CK technique ID for the malware's data collection from the system before exfiltration?** ## Analysis Navigate to the **Behavior** tab in VirusTotal and scroll to the **MITRE ATT&CK Tactics and Techniques** section. Look for techniques under the **Collection** tactic. ![MITRE ATT&CK Collection](/assets/img/posts/red-stealer/04-mitre-collection.png "MITRE ATT&CK techniques showing Collection tactics") **Collection Techniques Identified:** | Technique ID | Name | Description | | ------------ | ---------------------- | ------------------------------------ | | **T1005** | Data from Local System | Collects data stored on local system | | T1056 | Input Capture | Captures user input (keylogging) | | T1119 | Automated Collection | Automatically gathers data | The MITRE ATT&CK technique for data collection is: ``` T1005 ``` **Why T1005?** **T1005 (Data from Local System)** is the primary collection method because RedLine Stealer's core functionality targets stored data: * Browser credentials and cookies * Cryptocurrency wallets * Saved passwords * FTP/VPN credentials * Email client data The question asks specifically for **data collection before exfiltration**, and T1005 represents exactly this — gathering sensitive information from the compromised system. # Q5 — Social Media Domain Resolution ## Question **Following execution, which social media-related domain names did the malware resolve via DNS queries?** ## Analysis In the **Behavior** tab, scroll to the **DNS Resolutions** section to see which domains the malware contacted. ![DNS Resolutions](/assets/img/posts/red-stealer/05-dns-resolutions.png "DNS Resolutions showing facebook.com among resolved domains") **Domains Resolved:** * business.bing.com * connect.facebook.net (Facebook CDN) * **facebook.com** ← Social media domain * fbcdn.net (Facebook CDN) * fbsbx.com (Facebook infrastructure) * static.xx.fbcdn.net (Facebook CDN) * www.facebook.com The social media domain is: ``` facebook.com ``` While multiple Facebook-related domains appear (CDNs, static content servers), **facebook.com** is the primary social media platform domain. The malware likely resolves this to check internet connectivity or to blend in with normal user traffic. # Q6 — C2 Communication Details ## Question **Once the malicious IP addresses are identified, network security devices such as firewalls can be configured to block traffic to and from these addresses. Can you provide the IP address and destination port the malware communicates with?** ## Analysis In the **Behavior** tab, scroll to the **IP Traffic** section to see network connections made by the malware. ![IP Traffic C2](/assets/img/posts/red-stealer/06-ip-traffic-c2.png "IP Traffic showing C2 server at 77.91.124.55:19071") **IP Traffic Analysis:** | Protocol | Destination | Purpose | | -------- | ---------------------- | --------------------- | | **TCP** | **77.91.124.55:19071** | **Command & Control** | | TCP | 31.13.67.35:443 | www.facebook.com | | TCP | 13.107.6.158:443 | business.bing.com | | TCP | 31.13.88.13:443 | static.xx.fbcdn.net | The C2 IP address and port is: ``` 77.91.124.55:19071 ``` **Why is this the C2 server?** 1. **Non-standard port 19071** — Legitimate services use standard ports (443, 80) 2. **Does not resolve to any legitimate domain** 3. **First connection** in the IP traffic list 4. This is the attacker-controlled infrastructure receiving stolen data **IOC for Blocking:** * **IP:** 77.91.124.55 * **Port:** 19071 * **Protocol:** TCP # Q7 — YARA Rule Identification ## Question **YARA rules are designed to identify specific malware patterns and behaviors. Using MalwareBazaar, what's the name of the YARA rule created by "Varp0s" that detects the identified malware?** ## Analysis Navigate to **MalwareBazaar** and search for the sample hash: ``` sha256:248fcc901aff4e4b4c48c91e4d78a939bf681c9a1bc24addc3551b32768f907b/ ``` Scroll down to the **YARA Signatures** section. ![MalwareBazaar YARA](/assets/img/posts/red-stealer/07-malwarebazaar-yara.png "YARA Signatures section showing detect_Redline_Stealer by Varp0s") **YARA Rule Details:** | Property | Value | | --------- | -------------------------- | | Rule Name | detect_Redline_Stealer | | Author | Varp0s | | TLP | CLEAR (publicly shareable) | The YARA rule name is: ``` detect_Redline_Stealer ``` **What are YARA Rules?** YARA (Yet Another Recursive Acronym) is a pattern-matching tool used to identify and classify malware based on textual or binary patterns. The `detect_Redline_Stealer` rule contains signatures that uniquely identify this malware family, allowing automated detection. # Q8 — Malware Alias from ThreatFox ## Question **Understanding which malware families are targeting the organization helps in strategic security planning for the future and prioritizing resources based on the threat. Can you provide the different malware alias associated with the malicious IP address according to ThreatFox?** ## Analysis Navigate to **ThreatFox** and search for the C2 IP address: ``` https://threatfox.abuse.ch/ Search: ioc:77.91.124.55 ``` ![ThreatFox Results](/assets/img/posts/red-stealer/08-threatfox-recordstealer.png "ThreatFox showing IOC with RECORDSTEALER alias") **IOC Information:** | Property | Value | | ----------------- | ------------------ | | IOC | 77.91.124.55:19071 | | IOC Type | ip:port | | Threat Type | botnet_cc (C2) | | Malware | RedLine Stealer | | **Malware Alias** | **RECORDSTEALER** | | Confidence | 100% | The malware alias is: ``` RECORDSTEALER ``` **Why Multiple Names?** Malware families often have aliases assigned by different vendors: * **RedLine Stealer** — Common industry name * **RECORDSTEALER** — Alias in threat intelligence databases This helps correlate threats across different security platforms and vendor reports. # Q9 — Privilege Escalation DLL ## Question **By identifying the malware's imported DLLs, we can configure security tools to monitor for the loading or unusual usage of these specific DLLs. Can you provide the DLL utilized by the malware for privilege escalation?** ## Analysis In the **Behavior** tab on VirusTotal, scroll to the **Modules loaded** section. ![Modules Loaded](/assets/img/posts/red-stealer/09-modules-advapi32.png "Runtime Modules showing advapi32.dll for privilege escalation") **Runtime Modules Loaded:** * APPHELP.DLL * **advapi32.dll** ← Privilege escalation DLL * advpack.dll * Various dropped executables in `%TEMP%\IXP00X.TMP\` The privilege escalation DLL is: ``` advapi32.dll ``` **What is advapi32.dll?** `advapi32.dll` (Advanced Windows 32 Base API) is a Windows system library providing access to: * **Security and privilege management** * Registry manipulation * Service management * User account control * Token manipulation **Why is this used for privilege escalation?** This DLL contains critical Windows API functions commonly abused by malware: | Function | Purpose | | --------------------------- | ------------------------------ | | `AdjustTokenPrivileges()` | Modify security tokens | | `OpenProcessToken()` | Access process security tokens | | `LookupPrivilegeValue()` | Query privilege information | | `ImpersonateLoggedOnUser()` | Impersonate user accounts | By monitoring unusual loading or API calls to `advapi32.dll`, security tools can detect privilege escalation attempts. ## Conclusion This investigation successfully identified a **RedLine Stealer** sample (alias: **RECORDSTEALER**) communicating with a C2 server at **77.91.124.55:19071**. ### Summary of Findings | Question | Finding | | -------- | ---------------------------------------- | | Q1 | Microsoft category: `Trojan` | | Q2 | File name: `Wextract` | | Q3 | First submission: `2023-10-06 04:41` | | Q4 | MITRE ATT&CK Collection: `T1005` | | Q5 | Social media domain: `facebook.com` | | Q6 | C2 IP:Port: `77.91.124.55:19071` | | Q7 | YARA rule: `detect_Redline_Stealer` | | Q8 | Malware alias: `RECORDSTEALER` | | Q9 | Privilege escalation DLL: `advapi32.dll` | ### Indicators of Compromise (IOCs) **File Indicators:** | Type | Value | | ------ | ------------------------------------------------------------------ | | SHA256 | `248fcc901aff4e4b4c48c91e4d78a939bf681c9a1bc24addc3551b32768f907b` | | MD5 | `18cbe55c3b28754916f1cbf4dfc95cf9` | | SHA-1 | `7ccfb7678c34d6a2bedc040da04e2b5201be453b` | **Network Indicators:** | Type | Value | | -------- | -------------- | | C2 IP | `77.91.124.55` | | C2 Port | `19071` | | Protocol | TCP | Through open-source threat intelligence platforms (VirusTotal, MalwareBazaar, ThreatFox), we gathered comprehensive IOCs for the SOC and Incident Response teams to detect, block, and hunt for this threat across the organization. --- ## RE101 — Reverse Engineering Fundamentals Walkthrough - URL: https://zx41r.dev/posts/re101/ - Date: 2026-01-03 - Tags: base64, jsfuck, brainfuck, zip-repair, stack-strings, xor-encryption, ida, ghidra, hex-editor, malware-analysis, reverse-engineering - Summary: Analyze diverse file types including binaries, obfuscated scripts, and corrupted archives using reverse engineering techniques to extract hidden flags. ## Overview | | | | :------------- | :------------------------------------------------------------------------------ | | **Platform** | CyberDefenders | | **Category** | Malware Analysis | | **Difficulty** | Medium | | **Focus** | Binary Analysis · Script Deobfuscation · File Format Repair · Custom Encryption | | **Lab Link** | [RE101](https://cyberdefenders.org/blueteam-ctf-challenges/re101/) | The **RE101** challenge is an introductory reverse engineering lab designed to teach fundamental analysis techniques across multiple file types. The challenge presents six distinct artifacts — each requiring a different approach: * Base64 encoding hidden in binary * JSFuck obfuscated JavaScript * Brainfuck esoteric programming language * Corrupted ZIP file repair * Stack-string obfuscation in binaries * Custom position-dependent XOR encryption This challenge is **not** about advanced malware techniques. Instead, it emphasizes **foundational skills** that every reverse engineer must master: recognizing encoding schemes, understanding scripting languages, working with file format specifications, and tracing execution flow in binaries. ## Objective The goal of this lab is to **extract hidden flags** from six different files using: * Static string extraction * Script execution in safe environments * Esoteric language recognition * File format specification knowledge * Disassembly and stack string reconstruction * Encryption algorithm reversal By the end of the investigation, we will demonstrate: * How to find encoded data in binaries using `strings` * How JSFuck uses only 6 characters to encode JavaScript * How to recognize and execute Brainfuck code * How to repair corrupted archive headers * How stack strings evade static analysis * How to reverse position-dependent XOR encryption ## Tools Used Throughout this analysis, the following tools are used: * `strings`, `file`, `xxd` — Basic file analysis * `base64` — Decoding * `node` — JavaScript runtime * `brainfuck` — Esoteric language interpreter * `hexedit` / HxD — Hex editing * IDA Pro / Ghidra — Disassembly and decompilation * Python — Custom decryption scripts # Q1 — Base64 Encoding in Binary ## Question **File: MALWARE000 - I've used this new encryption I heard about online for my warez; I bet you can't extract the flag!** ## Initial Analysis The first step with any unknown binary is simple: **run `strings`**. ```bash strings malware000 ``` This reveals interesting output buried in the binary: ``` ZmxhZzwwb3BzX2lfdXNlZF8xMzM3X2I2NF9lbmNyeXB0aW9uPgo= If you see this message... ``` The string ending with `=` is immediately recognizable as **Base64** padding. ## Decoding the Flag Standard Base64 decoding works perfectly: ```bash base64 -d <<< "ZmxhZzwwb3BzX2lfdXNlZF8xMzM3X2I2NF9lbmNyeXB0aW9uPgo=" ``` The decoded flag is: ``` 0ops_i_used_1337_b64_encryption ``` # Q2 — JSFuck Obfuscation ## Question **File: Just some JS - Check out what I can do!** ## Initial Analysis Opening the JavaScript file reveals something unusual — code composed entirely of six characters: ```javascript [][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]] +(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]]) [+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!! []+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+... ``` This is **JSFuck** — an esoteric programming style that uses only these 6 characters: `[]()!+` ## Understanding JSFuck JSFuck exploits JavaScript's type coercion to construct any code from just six symbols: | Expression | Result | | ----------- | ----------- | | `![]` | `false` | | `!![]` | `true` | | `[][[]]` | `undefined` | | `+[]` | `0` | | `+!+[]` | `1` | | `!+[]+!+[]` | `2` | From these primitives, JSFuck can construct strings, access properties, and execute arbitrary JavaScript. ## Extracting the Flag JSFuck is valid JavaScript — just execute it: ```bash node just_some_js ``` The output reveals: ``` what_a_cheeky_language!1! ``` # Q3 — Brainfuck Esoteric Language ## Question **File: This is not JS - I'm tired of Javascript. Luckily, I found the grand-daddy of that lame last language!** ## Initial Analysis The filename is deliberately misleading. Let's check what we're dealing with: ```bash file this_is_not_js ``` Output: ``` ASCII text, with very long lines (320) ``` Viewing the content reveals: ``` ++++++++++[>+>+++>+++++++>++++++++++<<<<-]>>>>++.++++++.-----------.++++++. <----------.++++++++++++++++++.>++++++++.++++++++.<+++++++++++++++++.------ -----.------------.+.++++++++++.++++++++++++.++++++++++.>----.<----------.> ---.++.---.<++++++++.>+++.<------.>-----..----.+++++.<++++++.<++++++++++++++ ++++++++++++++. ``` This is **Brainfuck** — an esoteric programming language that uses only 8 commands: `+-<>[].,` ## Understanding Brainfuck | Command | Description | | ------- | ------------------------------------ | | `+` | Increment current cell | | `-` | Decrement current cell | | `>` | Move pointer right | | `<` | Move pointer left | | `[` | Jump past `]` if cell is zero | | `]` | Jump back to `[` if cell is non-zero | | `.` | Output current cell as ASCII | | `,` | Read input into current cell | ## Extracting the Flag Execute the Brainfuck code: ```bash brainfuck this_is_not_js ``` Output: ``` Now_THIS_is_programming ``` # Q4 — ZIP File Header Repair ## Question **File: Unzip Me - I zipped flag.txt and encrypted it with the password "password", but I think the header got messed up... You can have the flag if you fix the file** ## Initial Analysis Attempting to open the ZIP file fails: ```bash file file.zip_broken # Output: data (not recognized as ZIP) unzip file.zip_broken # Error: filename too long--truncating. # flag.txtUT^I: bad extra field length (local) ``` The archive is corrupted. Let's examine it with a hex dump: ```bash xxd file.zip_broken | head -n 20 ``` Output: ``` 00000000: 504b 0304 0a00 0900 0000 5aab 8e50 5622 PK........Z..PV" 00000010: 6795 2000 0000 1400 0000 5858 1c00 666c g. .......XX..fl 00000020: 6167 2e74 7874 5554 0900 03dc 6296 5edc ag.txtUT....b.^. ``` ## ZIP File Structure Understanding the Local File Header (LFH): | Offset | Size | Description | | ------ | ---- | -------------------------------- | | 0x00 | 4 | Signature (`50 4B 03 04`) | | 0x04 | 2 | Version needed | | 0x06 | 2 | General purpose bit flag | | 0x08 | 2 | Compression method | | 0x0A | 4 | Last mod time/date | | 0x0E | 4 | CRC-32 | | 0x12 | 4 | Compressed size | | 0x16 | 4 | Uncompressed size | | 0x1A | 2 | **Filename length** ← CORRUPTED! | | 0x1C | 2 | Extra field length | ## Identifying the Bug At offset `0x1A-0x1B`, we have: `58 58` In little-endian, this equals: `0x5858` = **22,616 bytes** for filename length! But "flag.txt" is only **8 bytes**: `66 6C 61 67 2E 74 78 74` ## Repairing the Header Using a hex editor: ```bash hexedit file.zip_broken ``` Navigate to offset `0x1A` and change: - `58 58` → `08 00` This sets the filename length to 8 bytes (little-endian: `0x0008`). ## Extracting the Flag After repair: ```bash zip -FF file.zip_broken --out file_fixed.zip unzip file_fixed.zip # Enter password: (empty - just press Enter) cat flag.txt ``` The extracted flag is: ``` R3ad_th3_spec ``` # Q5 — Stack String Obfuscation ## Question **File: MALWARE101 - Apparently, my encryption isn't so secure. I've got a new way of hiding my flags!** ## Initial Analysis Running basic analysis reveals nothing useful: ```bash file malware101 # Output: ELF 64-bit LSB executable, x86-64, dynamically linked, stripped strings malware101 # Output: Nothing to see here.... ./malware101 # Output: Nothing to see here.... ``` Dynamic analysis with `strace` and `ltrace` also shows only the decoy message. ## The Decompiler Trap Loading into IDA and using the decompiler (F5) shows: ```c __int64 __fastcall main(int a1, char **a2, char **a3) { printf("Nothing to see here....\n"); return 0; } ``` This looks completely innocent! But wait — the decompiler **optimizes away dead code**. The flag is constructed on the stack but **never used**, so the decompiler removes it entirely. ## The Assembly Tells the Truth Switching to assembly view reveals what the decompiler hid: ```asm main proc near push rbp mov rbp, rsp sub rsp, 1C0h mov rdi, offset format ; "Nothing to see here....\n" mov [rbp+var_4], 0 mov [rbp+var_1A0], 67h ; 'g' mov [rbp+var_1A9], 61h ; 'a' mov [rbp+var_1A3], 72h ; 'r' mov [rbp+var_1A2], 69h ; 'i' mov [rbp+var_1A1], 6Eh ; 'n' mov [rbp+var_1AB], 73h ; 's' mov [rbp+var_199], 3Eh ; '>' mov [rbp+var_198], 0 mov [rbp+var_1A7], 6Bh ; 'k' mov [rbp+var_19F], 73h ; 's' mov [rbp+var_1AE], 61h ; 'a' mov [rbp+var_1AA], 54h ; 'T' mov [rbp+var_1A6], 5Fh ; '_' mov [rbp+var_19E], 5Fh ; '_' mov [rbp+var_1AF], 6Ch ; 'l' mov [rbp+var_1B0], 66h ; 'f' mov [rbp+var_1A5], 73h ; 's' mov [rbp+var_1A4], 74h ; 't' mov [rbp+var_19D], 4Ch ; 'L' mov [rbp+var_1A8], 43h ; 'C' mov [rbp+var_19B], 41h ; 'A' mov [rbp+var_19A], 4Fh ; 'O' mov [rbp+var_1AD], 67h ; 'g' mov [rbp+var_1AC], 3Ch ; '<' mov [rbp+var_19C], 4Dh ; 'M' mov al, 0 call _printf main endp ``` The characters are written to the stack in **scrambled order** — an additional obfuscation layer to confuse manual analysis! ## Reconstruction Sort by stack offset and extract the ASCII values: | Offset | Hex | Char | | ------ | ---- | ---- | | -0x1B0 | 0x66 | f | | -0x1AF | 0x6C | l | | -0x1AE | 0x61 | a | | -0x1AD | 0x67 | g | | -0x1AC | 0x3C | < | | -0x1AB | 0x73 | s | | -0x1AA | 0x54 | T | | -0x1A9 | 0x61 | a | | -0x1A8 | 0x43 | C | | -0x1A7 | 0x6B | k | | -0x1A6 | 0x5F | _ | | -0x1A5 | 0x73 | s | | -0x1A4 | 0x74 | t | | -0x1A3 | 0x72 | r | | -0x1A2 | 0x69 | i | | -0x1A1 | 0x6E | n | | -0x1A0 | 0x67 | g | | -0x19F | 0x73 | s | | -0x19E | 0x5F | _ | | -0x19D | 0x4C | L | | -0x19C | 0x4D | M | | -0x19B | 0x41 | A | | -0x19A | 0x4F | O | | -0x199 | 0x3E | > | Reconstructed flag: ``` sTaCk_strings_LMAO ``` ## Extraction Script ```python stack_data = [ (-0x1B0, 0x66), (-0x1AF, 0x6C), (-0x1AE, 0x61), (-0x1AD, 0x67), (-0x1AC, 0x3C), (-0x1AB, 0x73), (-0x1AA, 0x54), (-0x1A9, 0x61), (-0x1A8, 0x43), (-0x1A7, 0x6B), (-0x1A6, 0x5F), (-0x1A5, 0x73), (-0x1A4, 0x74), (-0x1A3, 0x72), (-0x1A2, 0x69), (-0x1A1, 0x6E), (-0x1A0, 0x67), (-0x19F, 0x73), (-0x19E, 0x5F), (-0x19D, 0x4C), (-0x19C, 0x4D), (-0x19B, 0x41), (-0x19A, 0x4F), (-0x199, 0x3E), ] stack_data.sort(key=lambda x: x[0]) flag = ''.join(chr(val) for _, val in stack_data) print(flag) # flag ``` # Q6 — Custom Position-Dependent XOR Encryption ## Question **File: MALWARE201 - Ugh... I guess I'll just roll my own encryption. I'm not too good at math, but it looks good to me!** ## Initial Analysis Running the binary provides helpful output: ```bash ./malware201 ``` Output: ``` The encrypted flag is: "6d7861 6cdd7e657e476a4fccf7ca736855425 3dcd7d46becd bd2e11c6dded1c2" encrypt("my message", 10) == "6f7d61676dd7ed676fdb617ddb7b71" ``` The binary shows us: 1. The encrypted flag (32 bytes) 2. A sample encryption of "my message" ## Disassembly Analysis Examining the encryption function in IDA reveals the algorithm: ```c unsigned char* encrypt(const char* plaintext, size_t length) { unsigned char* output = calloc(length, 1); for (size_t i = 0; i < length; i++) { // Position-dependent key unsigned char key = ((i % 0xFF) | 0xA0); // Transform plaintext: double and set LSB unsigned char transformed = (2 * plaintext[i]) | 1; // XOR encryption output[i] = key ^ transformed; } return output; } ``` ## Encryption Algorithm Breakdown **Formula:** ``` encrypted[i] = ((i % 0xFF) | 0xA0) ^ ((2 * plaintext[i]) | 1) ``` **Components:** 1. **Position-dependent key:** `(i % 0xFF) | 0xA0` - Creates unique key per position - OR with 0xA0 ensures bits 7 and 5 are set 2. **Plaintext transformation:** `(2 * plaintext[i]) | 1` - Doubles the byte (left shift) - Sets LSB to 1 (makes it odd) 3. **XOR:** Combines key and transformed plaintext ## Decryption Process To reverse the encryption: **Step 1:** XOR encrypted byte with position key ``` temp = encrypted[i] ^ ((i % 0xFF) | 0xA0) ``` **Step 2:** Remove LSB and reverse doubling ``` plaintext[i] = (temp ^ 1) / 2 ``` ## Encrypted Flag Data From `.rodata` section (32 bytes): ``` 6D 78 61 6C DD 7E 65 7E 47 6A 4F CC F7 CA 73 68 55 42 53 DC D7 D4 6B EC DB D2 E1 1C 6D DE D1 C2 ``` ## Decryption Script ```python encrypted_flag = [ 0x6D, 0x78, 0x61, 0x6C, 0xDD, 0x7E, 0x65, 0x7E, 0x47, 0x6A, 0x4F, 0xCC, 0xF7, 0xCA, 0x73, 0x68, 0x55, 0x42, 0x53, 0xDC, 0xD7, 0xD4, 0x6B, 0xEC, 0xDB, 0xD2, 0xE1, 0x1C, 0x6D, 0xDE, 0xD1, 0xC2 ] def decrypt_byte(encrypted_byte, index): key = (index % 0xFF) | 0xA0 temp = key ^ encrypted_byte plaintext = (temp ^ 1) // 2 return plaintext flag = ''.join(chr(decrypt_byte(b, i)) for i, b in enumerate(encrypted_flag)) print(flag) ``` ## Verification Verifying position 0 manually: **Encryption of 'f' (0x66) at position 0:** ``` key = (0 % 0xFF) | 0xA0 = 0xA0 transformed = (2 * 0x66) | 1 = 0xCC | 1 = 0xCD encrypted = 0xA0 ^ 0xCD = 0x6D ✓ ``` **Decryption of 0x6D at position 0:** ``` key = 0xA0 temp = 0x6D ^ 0xA0 = 0xCD plaintext = (0xCD ^ 1) / 2 = 0xCC / 2 = 0x66 = 'f' ✓ ``` The decrypted flag is: ``` malwar3-3ncryp710n-15-Sh17 ``` ## Conclusion The RE101 lab covers foundational reverse engineering concepts that appear repeatedly in real-world analysis: | Question | Technique | Flag | | -------- | ------------------------------- | --------------------------------- | | Q1 | Static strings + Base64 | `0ops_i_used_1337_b64_encryption` | | Q2 | JSFuck execution | `what_a_cheeky_language!1!` | | Q3 | Brainfuck esoteric language | `Now_THIS_is_programming` | | Q4 | ZIP header repair | `R3ad_th3_spec` | | Q5 | Stack string reconstruction | `sTaCk_strings_LMAO` | | Q6 | Position-dependent XOR reversal | `malwar3-3ncryp710n-15-Sh17` | ### Skills Demonstrated **Static Analysis:** - String extraction with `strings` - Hex dump interpretation - Disassembly reading - File format understanding **Dynamic Analysis:** - Safe code execution - System/library call tracing **Reverse Engineering Concepts:** - Encoding vs Encryption vs Obfuscation - Esoteric programming languages - Stack-based string construction - Position-dependent encryption - XOR properties and reversibility These fundamentals form the building blocks for advanced malware analysis. Mastering them ensures you can handle obfuscation and encoding schemes before tackling more complex evasion techniques. see yaa again <3 --- ## Ransomed — Malware Analysis Walkthrough - URL: https://zx41r.dev/posts/ransomed/ - Date: 2026-01-02 - Tags: process-hollowing, shellcode, x32dbg, scdbg, ida, malware-analysis, reverse-engineering - Summary: Dynamic analysis and memory forensics: stack-strings, API resolution, shellcode and process hollowing. ## Overview | | | | :------------- | :------------------------------------------------------------------------- | | **Platform** | CyberDefenders | | **Category** | Malware Analysis | | **Difficulty** | Hard | | **Focus** | Dynamic Analysis · Process Hollowing · String Obfuscation · API Resolution | | **Lab Link** | [Ransomed](https://cyberdefenders.org/blueteam-ctf-challenges/ransomed/) | The **Ransomed** challenge is a malware analysis lab that simulates a real-world incident where suspicious memory activity is detected on a workstation. Initial telemetry points to an unknown executable exhibiting **high entropy**, a common indicator of packing or obfuscation. At first glance, the binary does not immediately reveal its intent through static inspection alone. Instead, its true behavior unfolds only at runtime — where memory allocation, shellcode execution, and process manipulation become visible. This challenge is not about signature matching or quick wins. It is about **reconstructing execution flow**, understanding *why* specific Windows APIs are used, and recognizing how common malware techniques appear in practice. ## Objective The goal of this analysis is to **reconstruct the malware’s execution chain** using a combination of: * Static analysis (PE structure, entropy, sections) * Dynamic analysis (debugging, API tracing) * Behavioral reasoning (what each action enables next) By the end of the investigation, we aim to clearly answer: * How the malware unpacks itself * How it hides strings and resolves APIs dynamically * How it executes shellcode * How it injects a payload into another process * What specific **process injection technique** is used ## Tools Used Throughout this analysis, the following tools are used: * PE inspection tools (entropy, sections, headers) * Disassembler (static code inspection) * x32 debugger (runtime behavior, API tracing) * Shellcode debugger/emulator (when needed) # Q1 — Entropy Analysis & Initial Triage ## Question **What is the value of entropy?** ## How entropy works Entropy measures **randomness of bytes**: * Low entropy (~3–5): readable data, strings, code * High entropy (~7+): encrypted / packed / compressed Windows executables that are **clean and unpacked** rarely sit close to 8. So when entropy is high, static analysis alone will lie to you. ## What we do here We inspect the binary using a PE analysis tool (PEStudio / DIE / similar) and check: * Overall file entropy * Whether entropy is uniform or concentrated * Whether it aligns with later behavior (unpacking, shellcode, etc.) This tells us **how much trust** to put in static findings. The First Page of the PEStudio show us : ![Entropy Analysis](/assets/img/posts/ransomed/01-entropy-pestudio.png "PEStudio showing entropy value of 7.677") The entropy value observed is: ``` 7.677 ``` An entropy value of **7.677** is **very high** and strongly suggests: * The binary is **packed or obfuscated** * Payload is likely **unpacked at runtime** * Static strings and imports will be **incomplete or misleading** This immediately justifies: * Debugging * Memory analysis * Runtime API tracing At this point, any analyst who continues *purely statically* is already behind. # Q2 — Section Analysis ## Question **What is the number of sections?** ## What we look for From the same utility we used firt, **PEStudio** we'll select the section tab and we'll see : ![Sections Analysis](/assets/img/posts/ransomed/02-sections-pestudio.png "Four sections: .text, .data, .rsrc, .reloc") we noticed four sections : **.text, .data, .rsrc, .reloc** so the binary contains: ``` 4 sections ``` So here’s the key insight: > **The malware does not rely on exotic sections to hide. > It relies on runtime behavior.** This aligns perfectly with what entropy already told us: * Code and payload are likely **unpacked dynamically** * Strings and APIs may not exist in plaintext on disk This is an important lesson for readers: * **Normal structure ≠ benign behavior** # Q3 — String Obfuscation Technique ## Question **What is the name of the technique used to obfuscate string?** At this stage we know: * Entropy is high → something is hidden * Sections look normal → hiding is **logical**, not structural So the next question is: > *Where are the strings?* And the answer is: * Not in `.rdata` * Not visible via static string extraction * Constructed **at runtime** This immediately narrows it down to: * Stack strings * Heap strings * Decryption routines So we open **IDA**. ## What we look for in IDA In IDA, stack strings look like this: * No readable string in `.rdata` * Instead: * Multiple `mov`, `push`, `xor`, `add` * Writing **single bytes / dwords** * To `[esp+X]` or `[ebp-X]` * Then passing a pointer to: * `GetProcAddress` * `LoadLibraryA` * or another API The string **never exists as a contiguous string in the binary**. Look at this part from IDA (exactly +0x9bee0) : ```c // Sets up memory protection - gets VirtualProtect address and marks decrypted memory region as executable (PAGE_EXECUTE_READWRITE). int setup_virtual_protect() { _BYTE v1[4]; // [esp+4h] [ebp-4h] BYREF strcpy(g_api_name_buffer, "kernel32.dll"); g_kernel32_handle = (int)GetModuleHandleA(g_api_name_buffer); strcpy(g_api_name_buffer, "VirtualProtect"); g_VirtualProtect_addr = (int)GetProcAddress((HMODULE)g_kernel32_handle, g_api_name_buffer); return ((int (__stdcall *)(int, SIZE_T, int, _BYTE *))g_VirtualProtect_addr)( g_decrypted_code_ptr, g_junk_condition, 64, v1); ``` * Strings are **not stored in plaintext** in the binary * They are **built byte-by-byte or dword-by-dword** * Construction happens **on the stack** * The buffer is used immediately and often wiped later - This defeats: * `strings.exe` * static YARA rules * naive reverse engineering This is a **classic malware evasion technique**. This technique is known as **stack strings**. By constructing strings dynamically on the stack, the malware effectively evades static string analysis tools and signature-based detection. This behavior is commonly observed in real-world malware and aligns with the high entropy observed earlier. # Q4 — Memory Allocation for Shellcode ## Question **What is the API that used malware-allocated memory to write shellcode?** --- At this point in the analysis, we already know: * The malware hides strings using stack strings * APIs are resolved dynamically * Static analysis alone is unreliable So the next logical question is: > *Where does the decrypted or unpacked payload live in memory?* Malware that unpacks itself **must allocate memory** to: * Store decrypted code * Change permissions * Transfer execution So we are now looking for **memory allocation APIs**. ## What we look for in IDA Inside IDA, we search for: * Calls to memory management APIs * Especially APIs related to: * Allocation * Protection changes * Execution Typical candidates include: * `VirtualAlloc` * `VirtualAllocEx` * `HeapAlloc` * `NtAllocateVirtualMemory` Because APIs are resolved dynamically, we don’t rely on imports. Instead, we follow **GetProcAddress usage** and observe **how the returned function pointers are used**. ## Evidence from the code From the earlier analysis, we already identified dynamic resolution of `VirtualProtect`. Tracing backward and forward in the code reveals that memory is allocated prior to execution and decryption routines. we can demostrate that by just setting a breakpoint at the VirtualAlloc Call in the debugger. ![VirtualAlloc Breakpoint](/assets/img/posts/ransomed/03-virtualalloc-bp.png "Breakpoint set at VirtualAlloc in x32dbg") The API used by the malware to allocate memory for writing shellcode is: ``` VirtualAlloc ``` This API is used to reserve and commit memory regions that later hold decrypted shellcode. * This is a prerequisite for: * Shellcode execution * Process injection * Unpacking routines At this stage, allocation alone does **not** prove injection — but it confirms that **runtime code generation/execution** is happening. This finding fits naturally into the execution chain we are reconstructing and prepares us for the next critical step: **memory permissions**. # Q5 — Memory Protection of Allocated Region ## Question **What is the protection of allocated memory?** --- Allocating memory alone is **not enough** to execute shellcode. For code to run, the memory must be: * **Executable** * And usually **Writable** during decryption/unpacking So after identifying `VirtualAlloc`, the next question is obvious: > *What permissions does the malware assign to that memory?* This tells us: * Whether the memory is intended for **code execution** * Whether the malware follows a **decrypt-then-execute** pattern ## What we look for In IDA, we inspect: * The arguments passed to the resolved allocation/protection API * Especially the **protection constant** Typical values to watch for: | Value | Meaning | | ------ | ---------------------- | | `0x04` | PAGE_READWRITE | | `0x20` | PAGE_EXECUTE_READ | | `0x40` | PAGE_EXECUTE_READWRITE | Malware very often uses `0x40` because it allows: * Writing decrypted payload * Executing it immediately afterward ## Evidence from the code From the previously identified routine: ```c ((void (__stdcall *)(char *, int, int, _DWORD *))(v10 + v11))( ImageBaseAddress + 1814528, 196608, 64, v14 ); ``` The third argument passed is: ``` 64 ``` In hexadecimal: ``` 0x40 ``` Which corresponds to: ``` PAGE_EXECUTE_READWRITE ``` ![PAGE_EXECUTE_READWRITE](/assets/img/posts/ransomed/04-page-execute-readwrite.png "Value 0x40 (PAGE_EXECUTE_READWRITE) passed as protection") The protection of the allocated memory is: ``` ERW (PAGE_EXECUTE_READWRITE) ``` This confirms that the allocated region is intended to **store and execute shellcode**, not just data. # Q6 — Transfer of Execution to Shellcode ## Question **What assembly instruction is used to transfer execution to the shellcode?** --- From previous questions, we already know: * `VirtualAlloc` is used to allocate memory * The memory protection is **PAGE_EXECUTE_READWRITE** * Shellcode is written into that region But **allocation is not execution**. So the correct next step is: > **Trace execution immediately after `VirtualAlloc` returns** > and observe how the allocated address is used. This requires a debugger. ## Dynamic Analysis Setup At runtime, we set a breakpoint on: ``` kernel32!VirtualAlloc ``` ![VirtualAlloc Hit](/assets/img/posts/ransomed/05-virtualalloc-hit.png "Breakpoint hit at VirtualAlloc") Once the breakpoint hits: 1. Step **out** of `VirtualAlloc` 2. Observe the **return value** 3. Follow how the program uses that pointer btw the return value of `VirtualAlloc` is critical — it is the **base address of the shellcode**. ## What happens after VirtualAlloc returns After stepping a few instructions, we observe the following **workflow**: 1. The return value of `VirtualAlloc` is saved on the stack (typically into a local variable `[ebp-4]`) 2. The malware performs additional setup: * Writes/decrypts data into the allocated memory * Prepares execution context 3. Execution is **indirectly transferred** to the allocated region This confirms that the allocated memory is an **executable payload**. After tracing execution, the final control transfer is done via: ``` jmp dword ptr ss:[ebp-4] ``` ![Jump to Shellcode](/assets/img/posts/ransomed/06-jmp-shellcode.png "Indirect jump transferring execution to shellcode") This instruction: * Reads the address returned by `VirtualAlloc` * Redirects execution to that memory region * Hands control directly to the shellcode There is no function call, no symbol, and no import involved. ### Note that by using an indirect jump via the stack: * Avoids direct calls to suspicious addresses * Makes static control-flow analysis harder * Cleanly separates loader code from payload code The assembly instruction used to transfer execution to the shellcode is: ``` jmp dword ptr ss:[ebp-4] ``` # Q7 — Dynamic API Resolution (kernel32) ## Question **What is the number of functions the malware resolves from kernel32?** --- From the previous questions, we already proved: * Shellcode is allocated with `VirtualAlloc` * Memory is executable * Execution jumps into the payload At this point, **IDA is no longer enough**. To answer this **cleanly and reliably**, we extract the payload and analyze it **independently**. ### Locate the payload in memory We break on the return of `VirtualAlloc`: ``` 75412F3E | C2 1000 | ret 10 ``` After the return: * `EAX` contains the base address of the allocated memory Observed value: ``` EAX = 0x04990000 ``` At this moment, the memory is empty — expected, since the payload has not been copied yet. ![Empty Memory Dump](/assets/img/posts/ransomed/07-dump-empty.png "Memory at allocated address is empty before payload copy") After stepping forward a few instructions, the payload is written into that region. ![Payload Dump](/assets/img/posts/ransomed/08-dump-payload.png "Payload written to allocated memory region") * Payload confirmed at address `0x04990000`. ### Dump the payload from memory The allocation size passed to `VirtualAlloc` is: ``` 0x0011B000 ``` We dump the payload using x32dbg: ```text savedata payload.bin, 04990000, 0011B000 ``` Now we have the **raw shellcode payload** extracted from memory. ### Analyze the payload with scdbg We load the dumped payload into **scdbg** and search for resolved APIs: ```powershell scdbg.exe /f payload.bin /findsc ``` scdbg identifies the shellcode entry point and begins emulation. After selecting the correct entry index, the resolved APIs are displayed. ### Identify kernel32-resolved functions From the scdbg output, the payload resolves APIs from multiple modules: ``` 4017fa LoadLibraryA(kernel32) 401831 GetProcAddress(WinExec) 40189f GetProcAddress(CreateFileA) 4018de GetProcAddress(WriteFile) 40194c GetProcAddress(CloseHandle) 4019cf GetProcAddress(CreateProcessA) 401a60 GetProcAddress(GetThreadContext) 401ad5 GetProcAddress(VirtualAlloc) 401b58 GetProcAddress(VirtualAllocEx) 401bc6 GetProcAddress(VirtualFree) 401c5e GetProcAddress(ReadProcessMemory) 401cfd GetProcAddress(WriteProcessMemory) 401d8e GetProcAddress(SetThreadContext) 401e03 GetProcAddress(ResumeThread) 401ea9 GetProcAddress(WaitForSingleObject) 401f0c GetProcAddress(GetModuleFileNameA) 401f96 GetProcAddress(GetCommandLineA) ``` We focus **only** on APIs resolved via `kernel32`. Counting the `GetProcAddress` calls targeting kernel32. The malware dynamically resolves: ``` 16 functions from kernel32.dll ``` # Q8 — Strings Obfuscated After `RegisterClassExA` ## Question **The malware obfuscates two strings after calling RegisterClassExA. What is the first string?** ## Runtime setup At this stage, static analysis is useless for strings. We know that `RegisterClassExA` **forces plaintext strings**, so the correct move is: * Set a breakpoint on: ``` user32!RegisterClassExA ``` * Let execution stop **at the function entry** * Inspect the stack arguments **before the call executes** At the breakpoint, execution stops at: ``` 755779F0 | 8BFF | mov edi,edi | 755779F2 | 55 | push ebp | 755779F3 | 8BEC | mov ebp,esp | 755779F5 | 8B4D 08 | mov ecx,dword ptr ss:[ebp+8] | 755779F8 | 8339 30 | cmp dword ptr ds:[ecx],30 | 755779FB | 0F85 0E260500 | jne user32.755CA00F | 75577A01 | 6A 01 | push 1 | 75577A03 | 68 80000000 | push 80 | 75577A08 | 6A 00 | push 0 | 75577A0A | 33D2 | xor edx,edx | 75577A0C | E8 BC100400 | call user32.755B8ACD | 75577A11 | 5D | pop ebp | 75577A12 | C2 0400 | ret 4 | ``` This confirms we are **inside RegisterClassExA**, before any processing happens. ## Inspecting the stack At the breakpoint, the stack layout is: ``` [esp+4] = 007EB85C [esp+8] = 646F6173 [esp+0C] = 6F6E666B [esp+10] = 75396173 [esp+14] = 00006E69 ``` Interpreting these DWORDs as ASCII (little-endian): ``` 73 61 6F 64 6B 66 6E 6F 73 61 39 75 69 6E ``` Which decodes to: ``` saodkfnosa9uin ``` Following the pointer confirms the same string in memory: ![RegisterClassExA String](/assets/img/posts/ransomed/09-registerclass-string.png "Stack revealing the obfuscated string at runtime") After `RegisterClassExA` returns: * This memory region is modified * The string is overwritten / obfuscated * The plaintext string no longer exists in memory This confirms: * Runtime string decryption * API-forced exposure * Immediate cleanup to evade memory inspection The **first string** that is revealed and then obfuscated after calling `RegisterClassExA` is: ``` saodkfnosa9uin ``` This string exists **only briefly at runtime**, which is exactly why breaking on the API is the correct approach. # Q9 — `CreateProcessA` & `dwCreationFlags` ## Question **What is the value of `dwCreationFlags` of `CreateProcessA`?** ## Runtime breakpoint At this stage, the malware is about to spawn a new process. We set a breakpoint on: ``` kernel32!CreateProcessA ``` Execution stops inside the API wrapper, right before the call is forwarded to `CreateProcessInternalA`: ``` 754993E7 | call kernelbase.CreateProcessInternalA ``` ![CreateProcessA Breakpoint](/assets/img/posts/ransomed/10-createprocess-bp.png "Breakpoint hit inside CreateProcessA") ## Register state at breakpoint At the moment of the call, the registers are: ``` EIP : 754993E7 kernelbase.CreateProcessInternalA EBP : 007EB77C ESP : 007EB74C EDX : "C:\Users\XXX\challenge.exe" ``` This confirms: * We are inside `CreateProcessA` * Arguments are already set up * The stack frame is stable At this point, **arguments must be read via `EBP`**, not `ESP`. ## Locating `dwCreationFlags` On x86, the argument layout for `CreateProcessA` inside the function is: ``` [EBP+08] lpApplicationName [EBP+0C] lpCommandLine [EBP+10] lpProcessAttributes [EBP+14] lpThreadAttributes [EBP+18] bInheritHandles [EBP+1C] dwCreationFlags ← target ``` Using `EBP = 0x007EB77C`, we dump memory around the stack frame. Memory dump: ``` 007EB78C 00 00 00 00 00 00 00 00 00 00 00 00 04 00 00 08 ``` The bytes at offset `EBP+1C` are: ``` 04 00 00 08 ``` ![dwCreationFlags Stack](/assets/img/posts/ransomed/11-creationflags-stack.png "Stack dump showing dwCreationFlags value at [EBP+1C]") ## Decoding the value Interpreting the value as little-endian: ``` 0x08000004 ``` Breaking it down: ``` 0x00000004 → CREATE_SUSPENDED 0x08000000 → CREATE_NO_WINDOW ``` The value of `dwCreationFlags` used by the malware is: ``` 0x08000004 ``` This confirms that the newly created process is intentionally paused so that its memory can be manipulated before execution resumes. # Q10 — Process Injection Technique ## Question **The malware uses a process injection technique. What is its name?** --- Based on the execution flow observed throughout the analysis: * A new process is created in a **suspended** state (`CREATE_SUSPENDED`) * The original process image is **unmapped** * Memory is allocated in the target process * A payload is written into the target process memory * The thread context is modified * Execution is resumed This sequence of actions corresponds to the following injection technique: ``` Process Hollowing ``` No further demonstration is required, as each step of this technique has already been confirmed in the previous questions. # Q11 — Payload Injection API ## Question **What is the API used to write the payload into the target process?** ## Execution flow After the malware finishes its initial setup and returns from `RegisterClassExA`, execution continues through a short sequence of Windows APIs that clearly indicate **process injection activity**. The observed call flow is: ``` ReadProcessMemory GetThreadContext NtUnmapViewOfSection VirtualAllocEx NtWriteVirtualMemory WriteProcessMemory ``` This sequence shows the malware preparing a remote process, allocating memory inside it, and then writing the payload. ## Final write operation Although both native (`Nt*`) and user-mode APIs appear in the chain, the actual **user-mode API** responsible for writing the payload into the target process is: ``` WriteProcessMemory ``` This is the definitive API that copies the malicious payload into the remote process address space. This confirms the final stage of the **process hollowing** technique and completes the malware execution chain. ## Conclusion The Ransomed sample illustrates a classic multi-stage loader used in real malware: packed on disk, builds essential strings at runtime, allocates executable memory, executes shellcode, and compromises another process using process hollowing. By stepping through its behavior with x32dbg and validating observed API calls with scdbg, we reconstructed the full execution chain with confidence. ---