@powercess/qq-mcp 0.2.0
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.
Potentially problematic release.
This version of @powercess/qq-mcp might be problematic. Click here for more details.
- package/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/index.js +287 -0
- package/dist/index.js.map +1 -0
- package/dist/keyscan.js +156 -0
- package/dist/keyscan.js.map +1 -0
- package/dist/messages.js +309 -0
- package/dist/messages.js.map +1 -0
- package/dist/offline.js +316 -0
- package/dist/offline.js.map +1 -0
- package/dist/schema.js +86 -0
- package/dist/schema.js.map +1 -0
- package/dist/sqlcipher.js +273 -0
- package/dist/sqlcipher.js.map +1 -0
- package/dist/verify-worker.js +18 -0
- package/dist/verify-worker.js.map +1 -0
- package/docs/reverse-engineering.md +188 -0
- package/package.json +62 -0
- package/scripts/scan_memory.py +155 -0
- package/scripts/smoke-client.mjs +30 -0
- package/src/index.ts +341 -0
- package/src/keyscan.ts +202 -0
- package/src/messages.ts +414 -0
- package/src/offline.ts +422 -0
- package/src/schema.ts +101 -0
- package/src/sqlcipher.ts +326 -0
- package/src/verify-worker.ts +18 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"offline.js","sourceRoot":"","sources":["../src/offline.ts"],"names":[],"mappings":"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"}
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QQNT database schema extraction from the `<db>.material` key-material files.
|
|
3
|
+
*
|
|
4
|
+
* Reverse-engineered on the local Windows NTQQ 9.9.35 install:
|
|
5
|
+
* - The material file layout is: [16B salt][AES-256-CBC ciphertext]
|
|
6
|
+
* - enc_key = PBKDF2-HMAC-SHA512(passphrase, salt, 4000, 32), IV = zero block
|
|
7
|
+
* - Decrypted payload is a plaintext stream of SQL DDL (CREATE TABLE / CREATE INDEX)
|
|
8
|
+
* describing the full schema of the corresponding SQLCipher database.
|
|
9
|
+
*
|
|
10
|
+
* Pure Node.js (node:crypto), works fully offline once passphrase + material path
|
|
11
|
+
* are known (passphrase can be captured from a running QQ via the debugger helper
|
|
12
|
+
* in scripts/ — see README).
|
|
13
|
+
*/
|
|
14
|
+
import { createDecipheriv, pbkdf2Sync } from "node:crypto";
|
|
15
|
+
import { readFileSync } from "node:fs";
|
|
16
|
+
export const DEFAULT_KDF_ITER = 4000;
|
|
17
|
+
/**
|
|
18
|
+
* Decrypts a QQNT `.material` file into its plaintext payload.
|
|
19
|
+
*/
|
|
20
|
+
export function decryptMaterial(path, passphrase, kdfIter = DEFAULT_KDF_ITER) {
|
|
21
|
+
const data = readFileSync(path);
|
|
22
|
+
if (data.length < 32)
|
|
23
|
+
throw new Error(`material file too small: ${data.length} bytes`);
|
|
24
|
+
const salt = data.subarray(0, 16);
|
|
25
|
+
const body = data.subarray(16);
|
|
26
|
+
if (body.length % 16 !== 0)
|
|
27
|
+
throw new Error(`material ciphertext not block aligned: ${body.length}`);
|
|
28
|
+
const key = pbkdf2Sync(passphrase, salt, kdfIter, 32, "sha512");
|
|
29
|
+
const decipher = createDecipheriv("aes-256-cbc", key, Buffer.alloc(16, 0));
|
|
30
|
+
decipher.setAutoPadding(false);
|
|
31
|
+
const plain = Buffer.concat([decipher.update(body), decipher.final()]);
|
|
32
|
+
return { salt, plaintext: plain };
|
|
33
|
+
}
|
|
34
|
+
const CREATE_RE = /CREATE(?: UNIQUE)? (TABLE|INDEX)\s+([A-Za-z0-9_]+)\s*\(([^)]*)\)/g;
|
|
35
|
+
/**
|
|
36
|
+
* Extracts table/DDL definitions from the decrypted material payload.
|
|
37
|
+
*/
|
|
38
|
+
export function extractSchema(plain, maxStatements = 2000) {
|
|
39
|
+
const text = plain.toString("utf8");
|
|
40
|
+
const out = [];
|
|
41
|
+
const seen = new Set();
|
|
42
|
+
const re = /CREATE(?: UNIQUE)? (TABLE|INDEX)\s+([A-Za-z0-9_]+)/g;
|
|
43
|
+
let m;
|
|
44
|
+
while ((m = re.exec(text)) !== null) {
|
|
45
|
+
const kind = m[1];
|
|
46
|
+
const name = m[2];
|
|
47
|
+
if (seen.has(name))
|
|
48
|
+
continue;
|
|
49
|
+
seen.add(name);
|
|
50
|
+
// Try to capture the full statement up to the closing paren.
|
|
51
|
+
let statement;
|
|
52
|
+
const start = m.index;
|
|
53
|
+
const close = findStatementEnd(text, start);
|
|
54
|
+
if (close > start)
|
|
55
|
+
statement = text.substring(start, close);
|
|
56
|
+
else
|
|
57
|
+
statement = text.substring(start, start + Math.min(200, text.length - start));
|
|
58
|
+
const entry = { name };
|
|
59
|
+
if (kind === "TABLE" && statement) {
|
|
60
|
+
entry.statement = statement;
|
|
61
|
+
const cols = statement.match(/[\[\]]([0-9]{3,5})[\[\]]\s+(INTEGER|TEXT|BLOB|REAL|NUMERIC)/g);
|
|
62
|
+
if (cols)
|
|
63
|
+
entry.columns = [...new Set(cols)];
|
|
64
|
+
}
|
|
65
|
+
out.push(entry);
|
|
66
|
+
if (out.length >= maxStatements)
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
/** Finds the index of the balanced closing parenthesis after `start`, or -1. */
|
|
72
|
+
function findStatementEnd(text, start) {
|
|
73
|
+
let depth = 0;
|
|
74
|
+
for (let i = start; i < text.length; i++) {
|
|
75
|
+
const c = text[i];
|
|
76
|
+
if (c === "(")
|
|
77
|
+
depth++;
|
|
78
|
+
else if (c === ")") {
|
|
79
|
+
depth--;
|
|
80
|
+
if (depth === 0)
|
|
81
|
+
return i + 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return -1;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offline decryption of NTQQ (Windows QQ NT) SQLCipher databases — pure Node,
|
|
3
|
+
* no sqlcipher native dependency.
|
|
4
|
+
*
|
|
5
|
+
* Reverse-engineered layout, verified byte-exact against SQLCipher 4.12 on
|
|
6
|
+
* NTQQ 9.9.35.52892 (see docs/reverse-engineering.md):
|
|
7
|
+
*
|
|
8
|
+
* file = [1024-byte NTQQ header][salt 16B][page 2][page 3]...
|
|
9
|
+
* page = [16B salt, page 1 only][ciphertext][16B IV][32B tag]
|
|
10
|
+
* ciphertext = AES-256-CBC(plaintext) with IV from the page tail
|
|
11
|
+
* key = PBKDF2-HMAC-SHA512(passphrase, salt, 4000, 32)
|
|
12
|
+
* plaintext page 1 = [16B salt][SQLite header from offset 16 onward]
|
|
13
|
+
*
|
|
14
|
+
* The 32-byte tail tag is NOT authenticated here: no standard HMAC layout
|
|
15
|
+
* matched, and decryption does not need it (decrypted pages are validated by
|
|
16
|
+
* their SQLite structure instead).
|
|
17
|
+
*/
|
|
18
|
+
import { createDecipheriv, createCipheriv, pbkdf2Sync, createHash } from "node:crypto";
|
|
19
|
+
import { closeSync, openSync, readSync, statSync, writeSync } from "node:fs";
|
|
20
|
+
/** NTQQ custom header preceding the SQLCipher salt. */
|
|
21
|
+
export const NTQQ_HEADER_SIZE = 1024;
|
|
22
|
+
/** SQLite page size used by every NTQQ database. */
|
|
23
|
+
export const DEFAULT_PAGE_SIZE = 4096;
|
|
24
|
+
/** Bytes after the ciphertext: 16-byte IV followed by a 32-byte unchecked tag. */
|
|
25
|
+
export const CRYPTO_TAIL_SIZE = 48;
|
|
26
|
+
/** SQLCipher default KDF iteration count for this key size. */
|
|
27
|
+
export const DEFAULT_KDF_ITER = 4000;
|
|
28
|
+
const SQLITE_MAGIC = Buffer.from("SQLite format 3\0", "latin1");
|
|
29
|
+
export const DEFAULT_LAYOUT = {
|
|
30
|
+
headerSize: NTQQ_HEADER_SIZE,
|
|
31
|
+
pageSize: DEFAULT_PAGE_SIZE,
|
|
32
|
+
tailSize: CRYPTO_TAIL_SIZE,
|
|
33
|
+
};
|
|
34
|
+
function readAt(path, offset, length) {
|
|
35
|
+
const fd = openSync(path, "r");
|
|
36
|
+
try {
|
|
37
|
+
const buf = Buffer.alloc(length);
|
|
38
|
+
const n = readSync(fd, buf, 0, length, offset);
|
|
39
|
+
return buf.subarray(0, n);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
closeSync(fd);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Parses the 1024-byte NTQQ header (magic, page size, device blob, algorithms). */
|
|
46
|
+
export function parseNtqqHeader(path) {
|
|
47
|
+
const buf = readAt(path, 0, NTQQ_HEADER_SIZE);
|
|
48
|
+
if (buf.length < NTQQ_HEADER_SIZE)
|
|
49
|
+
throw new Error(`${path}: too small for an NTQQ header`);
|
|
50
|
+
if (buf.subarray(0, 15).toString("latin1") !== "SQLite header 3") {
|
|
51
|
+
throw new Error(`${path}: not an NTQQ database (bad magic ${JSON.stringify(buf.subarray(0, 15).toString("latin1"))})`);
|
|
52
|
+
}
|
|
53
|
+
const pageSize = buf.readUInt16BE(18);
|
|
54
|
+
const blobHex = buf.subarray(0x2f, 0xaf).toString("latin1");
|
|
55
|
+
if (!/^[0-9a-f]{128}$/i.test(blobHex))
|
|
56
|
+
throw new Error(`${path}: device blob missing`);
|
|
57
|
+
const text = buf.toString("latin1");
|
|
58
|
+
return {
|
|
59
|
+
pageSize: pageSize >= 512 && (pageSize & (pageSize - 1)) === 0 ? pageSize : DEFAULT_PAGE_SIZE,
|
|
60
|
+
deviceBlob: Buffer.from(blobHex, "hex"),
|
|
61
|
+
hmacAlgorithm: /HMAC_[A-Z0-9]+/.exec(text)?.[0] ?? "HMAC_SHA1",
|
|
62
|
+
formatVersion: /(\d+\.\d+\.\d+\.\d+)/.exec(text)?.[1] ?? "unknown",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Reads the 16-byte SQLCipher salt stored after the NTQQ header. */
|
|
66
|
+
export function readSalt(path, layout = DEFAULT_LAYOUT) {
|
|
67
|
+
const salt = readAt(path, layout.headerSize, 16);
|
|
68
|
+
if (salt.length !== 16)
|
|
69
|
+
throw new Error(`${path}: missing salt at ${layout.headerSize}`);
|
|
70
|
+
return Buffer.from(salt);
|
|
71
|
+
}
|
|
72
|
+
/** Derives the 32-byte AES key from the device passphrase and the database salt. */
|
|
73
|
+
export function deriveKey(passphrase, salt, opts = {}) {
|
|
74
|
+
const pass = typeof passphrase === "string" ? Buffer.from(passphrase, "utf8") : passphrase;
|
|
75
|
+
return pbkdf2Sync(pass, salt, opts.kdfIter ?? DEFAULT_KDF_ITER, 32, opts.prf ?? "sha512");
|
|
76
|
+
}
|
|
77
|
+
/** Decrypts one 4096-byte encrypted page into its SQLite plaintext form. */
|
|
78
|
+
export function decryptPage(encrypted, key, pgno, layout = DEFAULT_LAYOUT) {
|
|
79
|
+
const { pageSize, tailSize } = layout;
|
|
80
|
+
if (encrypted.length !== pageSize)
|
|
81
|
+
throw new Error(`page ${pgno}: expected ${pageSize} bytes, got ${encrypted.length}`);
|
|
82
|
+
// Page 1 prefers the salt over the SQLite magic, so its first 16 bytes are not ciphertext.
|
|
83
|
+
const ctStart = pgno === 1 ? 16 : 0;
|
|
84
|
+
const ctEnd = pageSize - tailSize;
|
|
85
|
+
const ivStart = ctEnd;
|
|
86
|
+
const decipher = createDecipheriv("aes-256-cbc", key, encrypted.subarray(ivStart, ivStart + 16));
|
|
87
|
+
decipher.setAutoPadding(false);
|
|
88
|
+
const plain = Buffer.concat([decipher.update(encrypted.subarray(ctStart, ctEnd)), decipher.final()]);
|
|
89
|
+
const page = Buffer.alloc(pageSize);
|
|
90
|
+
plain.copy(page, ctStart);
|
|
91
|
+
return page;
|
|
92
|
+
}
|
|
93
|
+
/** Inverse of {@link decryptPage}; used to build round-trip fixtures and repacked databases. */
|
|
94
|
+
export function encryptPage(plain, key, pgno, layout = DEFAULT_LAYOUT) {
|
|
95
|
+
const { pageSize, tailSize } = layout;
|
|
96
|
+
if (plain.length !== pageSize)
|
|
97
|
+
throw new Error(`page ${pgno}: expected ${pageSize} bytes, got ${plain.length}`);
|
|
98
|
+
const ctStart = pgno === 1 ? 16 : 0;
|
|
99
|
+
const ctEnd = pageSize - tailSize;
|
|
100
|
+
const page = Buffer.alloc(pageSize);
|
|
101
|
+
plain.subarray(ctStart, ctEnd).copy(page, ctStart);
|
|
102
|
+
// Deterministic IV: hash of the plaintext page, so encryption stays reproducible.
|
|
103
|
+
const iv = createHash("sha256").update(plain).digest().subarray(0, 16);
|
|
104
|
+
iv.copy(page, ctEnd);
|
|
105
|
+
const cipher = createCipheriv("aes-256-cbc", key, iv);
|
|
106
|
+
cipher.setAutoPadding(false);
|
|
107
|
+
cipher.update(page.subarray(ctStart, ctEnd)).copy(page, ctStart);
|
|
108
|
+
cipher.final();
|
|
109
|
+
return page;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Reads a `-wal` file, verifying the WAL checksum chain, and returns only the
|
|
113
|
+
* frames up to the last committed transaction.
|
|
114
|
+
*/
|
|
115
|
+
export function readWal(walPath, layout = DEFAULT_LAYOUT) {
|
|
116
|
+
const { pageSize } = layout;
|
|
117
|
+
const size = statSync(walPath).size;
|
|
118
|
+
const frameSize = 24 + pageSize;
|
|
119
|
+
if (size < 32 + frameSize)
|
|
120
|
+
return { frames: [], committedFrames: 0 };
|
|
121
|
+
const fd = openSync(walPath, "r");
|
|
122
|
+
const buf = Buffer.alloc(size);
|
|
123
|
+
try {
|
|
124
|
+
readSync(fd, buf, 0, size, 0);
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
closeSync(fd);
|
|
128
|
+
}
|
|
129
|
+
const walPageSize = buf.readUInt32BE(8);
|
|
130
|
+
if (walPageSize !== pageSize)
|
|
131
|
+
return { frames: [], committedFrames: 0 };
|
|
132
|
+
const bigEndian = (buf.readUInt32BE(0) & 1) === 1;
|
|
133
|
+
const salt1 = buf.readUInt32BE(16);
|
|
134
|
+
const salt2 = buf.readUInt32BE(20);
|
|
135
|
+
const checksum = (data, s0, s1) => {
|
|
136
|
+
for (let i = 0; i + 8 <= data.length; i += 8) {
|
|
137
|
+
const a = bigEndian ? data.readUInt32BE(i) : data.readUInt32LE(i);
|
|
138
|
+
const b = bigEndian ? data.readUInt32BE(i + 4) : data.readUInt32LE(i + 4);
|
|
139
|
+
s0 = (s0 + a + s1) >>> 0;
|
|
140
|
+
s1 = (s1 + b + s0) >>> 0;
|
|
141
|
+
}
|
|
142
|
+
return [s0, s1];
|
|
143
|
+
};
|
|
144
|
+
let [s0, s1] = checksum(buf.subarray(0, 24), 0, 0);
|
|
145
|
+
const raw = [];
|
|
146
|
+
const count = Math.floor((size - 32) / frameSize);
|
|
147
|
+
for (let i = 0; i < count; i++) {
|
|
148
|
+
const off = 32 + i * frameSize;
|
|
149
|
+
const head = buf.subarray(off, off + 24);
|
|
150
|
+
if (head.readUInt32BE(8) !== salt1 || head.readUInt32BE(12) !== salt2)
|
|
151
|
+
break;
|
|
152
|
+
[s0, s1] = checksum(head.subarray(0, 8), s0, s1);
|
|
153
|
+
[s0, s1] = checksum(buf.subarray(off + 24, off + 24 + pageSize), s0, s1);
|
|
154
|
+
if (s0 !== head.readUInt32BE(16) || s1 !== head.readUInt32BE(20))
|
|
155
|
+
break;
|
|
156
|
+
raw.push({ pgno: head.readUInt32BE(0), dbSize: head.readUInt32BE(4), page: buf.subarray(off + 24, off + 24 + pageSize) });
|
|
157
|
+
}
|
|
158
|
+
let lastCommit = -1;
|
|
159
|
+
raw.forEach((f, i) => {
|
|
160
|
+
if (f.dbSize !== 0)
|
|
161
|
+
lastCommit = i;
|
|
162
|
+
});
|
|
163
|
+
if (lastCommit < 0)
|
|
164
|
+
return { frames: [], committedFrames: 0 };
|
|
165
|
+
return { frames: raw.slice(0, lastCommit + 1), committedFrames: lastCommit + 1, dbSize: raw[lastCommit].dbSize };
|
|
166
|
+
}
|
|
167
|
+
/** Page count of an NTQQ database file. */
|
|
168
|
+
export function pageCount(dbPath, layout = DEFAULT_LAYOUT) {
|
|
169
|
+
const size = statSync(dbPath).size;
|
|
170
|
+
if (size < layout.headerSize + layout.pageSize)
|
|
171
|
+
return 0;
|
|
172
|
+
return Math.floor((size - layout.headerSize) / layout.pageSize);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Decrypts a whole NTQQ database (optionally replaying its WAL) into a plain
|
|
176
|
+
* SQLite file. Streaming: memory use is bounded by the batch size, not the
|
|
177
|
+
* database size.
|
|
178
|
+
*/
|
|
179
|
+
export function reconstructDatabase(opts) {
|
|
180
|
+
const layout = opts.layout ?? DEFAULT_LAYOUT;
|
|
181
|
+
const { dbPath, outPath } = opts;
|
|
182
|
+
const { pageSize, headerSize } = layout;
|
|
183
|
+
const total = pageCount(dbPath, layout);
|
|
184
|
+
if (total === 0)
|
|
185
|
+
throw new Error(`${dbPath}: no pages (file too small or wrong layout)`);
|
|
186
|
+
const key = opts.key ?? deriveKey(opts.passphrase ?? "", readSalt(dbPath, layout), opts.kdf);
|
|
187
|
+
const walPages = new Map();
|
|
188
|
+
let walDBSize;
|
|
189
|
+
if (opts.wal !== false) {
|
|
190
|
+
try {
|
|
191
|
+
const wal = readWal(`${dbPath}-wal`, layout);
|
|
192
|
+
if (wal.dbSize) {
|
|
193
|
+
walDBSize = wal.dbSize;
|
|
194
|
+
for (const f of wal.frames)
|
|
195
|
+
walPages.set(f.pgno, f.page);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// A missing or unreadable WAL only costs us the newest frames.
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const effectiveTotal = walDBSize ? Math.min(walDBSize, total) : total;
|
|
203
|
+
const src = openSync(dbPath, "r");
|
|
204
|
+
const dst = openSync(outPath, "w");
|
|
205
|
+
const started = Date.now();
|
|
206
|
+
try {
|
|
207
|
+
const batch = 256;
|
|
208
|
+
const inBuf = Buffer.alloc(pageSize * batch);
|
|
209
|
+
const outBuf = Buffer.alloc(pageSize * batch);
|
|
210
|
+
let done = 0;
|
|
211
|
+
while (done < effectiveTotal) {
|
|
212
|
+
const n = Math.min(batch, effectiveTotal - done);
|
|
213
|
+
const got = readSync(src, inBuf, 0, n * pageSize, headerSize + done * pageSize);
|
|
214
|
+
if (got < n * pageSize)
|
|
215
|
+
throw new Error(`${dbPath}: short read at page ${done + 1}`);
|
|
216
|
+
for (let i = 0; i < n; i++) {
|
|
217
|
+
const pgno = done + i + 1;
|
|
218
|
+
const encrypted = walPages.get(pgno) ?? inBuf.subarray(i * pageSize, (i + 1) * pageSize);
|
|
219
|
+
const plain = decryptPage(encrypted, key, pgno, layout);
|
|
220
|
+
if (pgno === 1) {
|
|
221
|
+
// SQLite needs its magic; the salt it replaces is not used by readers.
|
|
222
|
+
SQLITE_MAGIC.copy(plain, 0);
|
|
223
|
+
}
|
|
224
|
+
plain.copy(outBuf, i * pageSize);
|
|
225
|
+
}
|
|
226
|
+
writeSync(dst, outBuf, 0, n * pageSize);
|
|
227
|
+
done += n;
|
|
228
|
+
opts.onProgress?.(done, effectiveTotal);
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
outPath,
|
|
232
|
+
pageCount: effectiveTotal,
|
|
233
|
+
walPages: walPages.size,
|
|
234
|
+
bytes: effectiveTotal * pageSize,
|
|
235
|
+
durationMs: Date.now() - started,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
finally {
|
|
239
|
+
closeSync(src);
|
|
240
|
+
closeSync(dst);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* True when `passphrase` decrypts page 1 of `dbPath` into a plausible page:
|
|
245
|
+
* a SQLite header carrying the expected page size, plus a valid b-tree header
|
|
246
|
+
* for the `sqlite_schema` root page. The salt occupies bytes 0..16, the rest of
|
|
247
|
+
* the header keeps its standard offsets.
|
|
248
|
+
*/
|
|
249
|
+
export function verifyPassphrase(dbPath, passphrase, layout = DEFAULT_LAYOUT) {
|
|
250
|
+
try {
|
|
251
|
+
const key = deriveKey(passphrase, readSalt(dbPath, layout), {});
|
|
252
|
+
const page = readAt(dbPath, layout.headerSize, layout.pageSize);
|
|
253
|
+
if (page.length !== layout.pageSize)
|
|
254
|
+
return false;
|
|
255
|
+
const plain = decryptPage(page, key, 1, layout);
|
|
256
|
+
if (plain.readUInt16BE(16) !== layout.pageSize)
|
|
257
|
+
return false;
|
|
258
|
+
const pageType = plain[100];
|
|
259
|
+
const freeblock = plain.readUInt16BE(101);
|
|
260
|
+
const cellCount = plain.readUInt16BE(103);
|
|
261
|
+
if (![2, 5, 10, 13].includes(pageType))
|
|
262
|
+
return false;
|
|
263
|
+
if (freeblock !== 0 && (freeblock < 100 || freeblock >= layout.pageSize))
|
|
264
|
+
return false;
|
|
265
|
+
if (cellCount > layout.pageSize / 4)
|
|
266
|
+
return false;
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
//# sourceMappingURL=sqlcipher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlcipher.js","sourceRoot":"","sources":["../src/sqlcipher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAE7E,uDAAuD;AACvD,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AACrC,oDAAoD;AACpD,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC;AACtC,kFAAkF;AAClF,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AACnC,+DAA+D;AAC/D,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AACrC,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC;AAqBhE,MAAM,CAAC,MAAM,cAAc,GAAmB;IAC5C,UAAU,EAAE,gBAAgB;IAC5B,QAAQ,EAAE,iBAAiB;IAC3B,QAAQ,EAAE,gBAAgB;CAC3B,CAAC;AAEF,SAAS,MAAM,CAAC,IAAY,EAAE,MAAc,EAAE,MAAc;IAC1D,MAAM,EAAE,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC/B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACjC,MAAM,CAAC,GAAG,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/C,OAAO,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5B,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,EAAE,CAAC,CAAC;IAChB,CAAC;AACH,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,gBAAgB,CAAC,CAAC;IAC9C,IAAI,GAAG,CAAC,MAAM,GAAG,gBAAgB;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,gCAAgC,CAAC,CAAC;IAC5F,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,iBAAiB,EAAE,CAAC;QACjE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,qCAAqC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC;IACzH,CAAC;IACD,MAAM,QAAQ,GAAG,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC5D,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,uBAAuB,CAAC,CAAC;IACvF,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACpC,OAAO;QACL,QAAQ,EAAE,QAAQ,IAAI,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,iBAAiB;QAC7F,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC;QACvC,aAAa,EAAE,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,WAAW;QAC9D,aAAa,EAAE,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,SAAS;KACnE,CAAC;AACJ,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,SAAyB,cAAc;IAC5E,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACjD,IAAI,IAAI,CAAC,MAAM,KAAK,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,qBAAqB,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;IACzF,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC;AAQD,oFAAoF;AACpF,MAAM,UAAU,SAAS,CAAC,UAA2B,EAAE,IAAY,EAAE,OAAmB,EAAE;IACxF,MAAM,IAAI,GAAG,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;IAC3F,OAAO,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,IAAI,gBAAgB,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,IAAI,QAAQ,CAAC,CAAC;AAC5F,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,WAAW,CAAC,SAAiB,EAAE,GAAW,EAAE,IAAY,EAAE,SAAyB,cAAc;IAC/G,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;IACtC,IAAI,SAAS,CAAC,MAAM,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,cAAc,QAAQ,eAAe,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;IACxH,2FAA2F;IAC3F,MAAM,OAAO,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACpC,MAAM,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAClC,MAAM,OAAO,GAAG,KAAK,CAAC;IACtB,MAAM,QAAQ,GAAG,gBAAgB,CAAC,aAAa,EAAE,GAAG,EAAE,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC;IACjG,QAAQ,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC/B,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACrG,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACpC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC1B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,GAAW,EAAE,IAAY,EAAE,SAAyB,cAAc;IAC3G,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;IACtC,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,cAAc,QAAQ,eAAe,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IAChH,MAAM,OAAO,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACpC,MAAM,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAClC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACpC,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACnD,kFAAkF;IAClF,MAAM,EAAE,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACvE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACrB,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IACtD,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IAC7B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACjE,MAAM,CAAC,KAAK,EAAE,CAAC;IACf,OAAO,IAAI,CAAC;AACd,CAAC;AAiBD;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,OAAe,EAAE,SAAyB,cAAc;IAC9E,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;IAC5B,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;IACpC,MAAM,SAAS,GAAG,EAAE,GAAG,QAAQ,CAAC;IAChC,IAAI,IAAI,GAAG,EAAE,GAAG,SAAS;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;IACrE,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAClC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,CAAC;QACH,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAChC,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,EAAE,CAAC,CAAC;IAChB,CAAC;IACD,MAAM,WAAW,GAAG,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACxC,IAAI,WAAW,KAAK,QAAQ;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;IACxE,MAAM,SAAS,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IACnC,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,EAAU,EAAE,EAAU,EAAoB,EAAE;QAC1E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7C,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAClE,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC1E,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;YACzB,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;QACD,OAAO,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAClB,CAAC,CAAC;IACF,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACnD,MAAM,GAAG,GAAe,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC;IAClD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;QAC/B,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC;QACzC,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,KAAK;YAAE,MAAM;QAC7E,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QACjD,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,GAAG,EAAE,EAAE,GAAG,GAAG,EAAE,GAAG,QAAQ,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QACzE,IAAI,EAAE,KAAK,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;YAAE,MAAM;QACxE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,GAAG,GAAG,EAAE,EAAE,GAAG,GAAG,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC5H,CAAC;IACD,IAAI,UAAU,GAAG,CAAC,CAAC,CAAC;IACpB,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACnB,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;YAAE,UAAU,GAAG,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IACH,IAAI,UAAU,GAAG,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;IAC9D,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,GAAG,CAAC,CAAC,EAAE,eAAe,EAAE,UAAU,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC,MAAM,EAAE,CAAC;AACnH,CAAC;AA2BD,2CAA2C;AAC3C,MAAM,UAAU,SAAS,CAAC,MAAc,EAAE,SAAyB,cAAc;IAC/E,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC;IACnC,IAAI,IAAI,GAAG,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,QAAQ;QAAE,OAAO,CAAC,CAAC;IACzD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;AAClE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAwB;IAC1D,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,cAAc,CAAC;IAC7C,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IACjC,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;IACxC,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,IAAI,KAAK,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,6CAA6C,CAAC,CAAC;IACzF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,EAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IAE7F,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC3C,IAAI,SAA6B,CAAC;IAClC,IAAI,IAAI,CAAC,GAAG,KAAK,KAAK,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,MAAM,MAAM,EAAE,MAAM,CAAC,CAAC;YAC7C,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;gBACf,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC;gBACvB,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,MAAM;oBAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;YAC3D,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,+DAA+D;QACjE,CAAC;IACH,CAAC;IACD,MAAM,cAAc,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAEtE,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAClC,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,GAAG,CAAC;QAClB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,CAAC;QAC9C,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,OAAO,IAAI,GAAG,cAAc,EAAE,CAAC;YAC7B,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAAC,CAAC;YACjD,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,GAAG,QAAQ,EAAE,UAAU,GAAG,IAAI,GAAG,QAAQ,CAAC,CAAC;YAChF,IAAI,GAAG,GAAG,CAAC,GAAG,QAAQ;gBAAE,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,wBAAwB,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC;YACrF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC3B,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC1B,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,GAAG,QAAQ,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC;gBACzF,MAAM,KAAK,GAAG,WAAW,CAAC,SAAS,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;gBACxD,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;oBACf,uEAAuE;oBACvE,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;gBAC9B,CAAC;gBACD,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC;YACnC,CAAC;YACD,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC;YACxC,IAAI,IAAI,CAAC,CAAC;YACV,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO;YACL,OAAO;YACP,SAAS,EAAE,cAAc;YACzB,QAAQ,EAAE,QAAQ,CAAC,IAAI;YACvB,KAAK,EAAE,cAAc,GAAG,QAAQ;YAChC,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO;SACjC,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,GAAG,CAAC,CAAC;QACf,SAAS,CAAC,GAAG,CAAC,CAAC;IACjB,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc,EAAE,UAA2B,EAAE,SAAyB,cAAc;IACnH,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,SAAS,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;QAChE,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QAChE,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QAClD,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;QAChD,IAAI,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC,KAAK,MAAM,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC7D,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,SAAS,GAAG,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QAC1C,MAAM,SAAS,GAAG,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAC;QACrD,IAAI,SAAS,KAAK,CAAC,IAAI,CAAC,SAAS,GAAG,GAAG,IAAI,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAC;QACvF,IAAI,SAAS,GAAG,MAAM,CAAC,QAAQ,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QAClD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker that tests a slice of candidate passphrases against an NTQQ key
|
|
3
|
+
* oracle. Candidate checking is PBKDF2-bound (~1 ms each), so the parent fans
|
|
4
|
+
* the work out across cores instead of blocking the event loop.
|
|
5
|
+
*/
|
|
6
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
7
|
+
import { verifyKeyCandidate } from "./keyscan.js";
|
|
8
|
+
const { oracle, candidates } = workerData;
|
|
9
|
+
let tested = 0;
|
|
10
|
+
for (const candidate of candidates) {
|
|
11
|
+
tested++;
|
|
12
|
+
if (verifyKeyCandidate(oracle, candidate)) {
|
|
13
|
+
parentPort?.postMessage({ passphrase: candidate, tested });
|
|
14
|
+
break;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
parentPort?.postMessage({ passphrase: undefined, tested });
|
|
18
|
+
//# sourceMappingURL=verify-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-worker.js","sourceRoot":"","sources":["../src/verify-worker.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAkB,MAAM,cAAc,CAAC;AAElE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,UAAyD,CAAC;AACzF,IAAI,MAAM,GAAG,CAAC,CAAC;AACf,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;IACnC,MAAM,EAAE,CAAC;IACT,IAAI,kBAAkB,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,CAAC;QAC1C,UAAU,EAAE,WAAW,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3D,MAAM;IACR,CAAC;AACH,CAAC;AACD,UAAU,EAAE,WAAW,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# NTQQ 9.9.35.52892 — reverse-engineering findings
|
|
2
|
+
|
|
3
|
+
Session findings against **QQ NT 9.9.35.52892** (Windows, `CN059` device).
|
|
4
|
+
Kept here so no progress is lost. All commands were run locally; values shown
|
|
5
|
+
are protocol facts, not credentials.
|
|
6
|
+
|
|
7
|
+
Status board: the page cipher (previously the blocking open problem) is solved
|
|
8
|
+
and implemented in `src/sqlcipher.ts`. Offline reading of every database works
|
|
9
|
+
with a single device passphrase.
|
|
10
|
+
|
|
11
|
+
## 1. Where data lives
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
C:\Users\<user>\Documents\Tencent Files\<uin>\nt_qq\
|
|
15
|
+
nt_db\ SQLCipher-format databases (all encrypted)
|
|
16
|
+
nt_msg.db 2.4 GB — chat messages (c2c_msg_table / group_msg_table)
|
|
17
|
+
group_msg_fts.db 670 MB — FTS index
|
|
18
|
+
group_info.db 24 MB — group list, member roster, group details
|
|
19
|
+
... 20+ more
|
|
20
|
+
nt_data\ media: Pic/Video/Ptt/Emoji/File/avatar
|
|
21
|
+
nt_temp\ temp data
|
|
22
|
+
C:\Users\<user>\Documents\Tencent Files\nt_qq\global\nt_db\ login.db/sign.db/Registry.db
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 2. File format (all `.db`)
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
offset 0 "SQLite header 3\0" ← magic of NTQQ's own header framing
|
|
29
|
+
offset 16 u16 BE 0x0400, u16 BE page size (4096)
|
|
30
|
+
offset 32 "QQ_NT DB"
|
|
31
|
+
offset 40 u32 LE length (157) of the ASCII hex blob that follows at 0x2f
|
|
32
|
+
offset 0x2f 64-byte device blob, ASCII-hex encoded (128 chars), identical in
|
|
33
|
+
all 18 databases of the install → device-level, not per-database
|
|
34
|
+
offset 0xb1 "1.1.0.1", offset 0xba "HMAC_SHA1"
|
|
35
|
+
offset 1024 the SQLCipher file proper: [16B salt][page 1][page 2]...
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- `(size - 1024) % 4096 == 0` holds for every database.
|
|
39
|
+
- The salt at 1024 equals the first 16 bytes of `<db>-first.material`.
|
|
40
|
+
- `page_size` lives at file offset 18; `readSalt()`/`parseNtqqHeader()` implement
|
|
41
|
+
this header, and reject anything without the magic or the device blob.
|
|
42
|
+
|
|
43
|
+
## 3. Page cipher — SOLVED
|
|
44
|
+
|
|
45
|
+
The earlier session had the 48-byte deep key but no matching page layout
|
|
46
|
+
("≈6000 combinations, zero matches"). The missing piece was the per-page IV:
|
|
47
|
+
SQLCipher stores it **inside the page tail**, and page 1 is special.
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
page 1 : [16B salt][ciphertext: plaintext[16..4048)][16B IV][32B tag]
|
|
51
|
+
page >1: [ciphertext: plaintext[0..4048)][16B IV][32B tag]
|
|
52
|
+
key : PBKDF2-HMAC-SHA512(passphrase, salt, 4000, 32)
|
|
53
|
+
cipher : AES-256-CBC, IV = file bytes [page_end-48, page_end-32)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Consequences that made this hard to see:
|
|
57
|
+
|
|
58
|
+
- A wrong IV garbles only the first block, so a wrong layout still "looks"
|
|
59
|
+
plausible; and any `ctEnd` larger than the true one leaves earlier blocks
|
|
60
|
+
correct. Both were red herrings until tested against known plaintext.
|
|
61
|
+
- The decrypted page-1 stream lands at file offset 16: the file's first 16 bytes
|
|
62
|
+
of page 1 are the salt, and the SQLite header keeps its standard offsets
|
|
63
|
+
(page size at 16, change counter at 24, page count at 28, schema format at 44,
|
|
64
|
+
b-tree header at 100). When writing the plaintext file this repo overwrites
|
|
65
|
+
bytes 0..16 with `SQLite format 3\0` so ordinary SQLite tools open it.
|
|
66
|
+
- The plaintext header advertises `reserved = 80` (byte 20), so the usable page
|
|
67
|
+
area ends at 4016 — beyond the 4048-byte crypto region. Nothing the b-trees
|
|
68
|
+
reference lives in the IV/tag area, which is why decryption never needs it.
|
|
69
|
+
|
|
70
|
+
How it was pinned down (all reproducible):
|
|
71
|
+
|
|
72
|
+
1. `.material` decryption gave the salt and the DDL, so the passphrase path was
|
|
73
|
+
known-good (`src/schema.ts`).
|
|
74
|
+
2. A synthetic SQLCipher 4.12 database created with
|
|
75
|
+
`cipher_page_size=4096, kdf_iter=4000, HMAC_SHA1, PBKDF2_HMAC_SHA512, KEY='<passphrase>'`
|
|
76
|
+
provided known plaintext to test candidate layouts locally.
|
|
77
|
+
3. Layout candidates were scored on the real 2.4 GB database by checking
|
|
78
|
+
whether decrypted pages start with a valid b-tree header (type ∈
|
|
79
|
+
{2,5,10,13}, sane freeblock/ncell/content-start). Only
|
|
80
|
+
`[0/16 .. 4048) + IV@4048` survived: 87 % of sampled pages validated, the
|
|
81
|
+
rest being overflow/freelist pages that legitimately are not b-tree pages.
|
|
82
|
+
4. `sqlcipher3` (Python) opening the same file with those PRAGMAs produced the
|
|
83
|
+
identical row counts (c2c 5197, group 2 652 879) and the identical
|
|
84
|
+
`quick_check` verdict, confirming byte-level agreement.
|
|
85
|
+
|
|
86
|
+
Final cross-validation, reproducible: reading `group_info.db` with SQLCipher 4.12
|
|
87
|
+
and with this repo's pure-Node decryptor yields the same
|
|
88
|
+
`group_member3` content — 63 125 rows, SHA-256 `574907204a9d6e0895d3f4a23f1b8293`
|
|
89
|
+
over `60001|1002|20002` — and the same 357 `group_list` rows
|
|
90
|
+
(SHA-256 `253cfcee415401f2fb4944a38f7edb10`).
|
|
91
|
+
|
|
92
|
+
`decryptPage`/`encryptPage` in `src/sqlcipher.ts` implement both directions;
|
|
93
|
+
`encryptPage` exists for hermetic tests and for future repacking.
|
|
94
|
+
|
|
95
|
+
### The 32-byte page tag (still unverified)
|
|
96
|
+
|
|
97
|
+
Each page ends with 16 IV bytes followed by 32 bytes that are not used for
|
|
98
|
+
decryption. Every standard HMAC layout was tried and rejected: HMAC-SHA1/SHA256/
|
|
99
|
+
SHA512 × mac key lengths {32,64,96} × KDF iterations {4000,64000,256000} ×
|
|
100
|
+
salt-plain / salt^0x3a → no match over `ct`, `ct||iv`, `iv||ct`, or the whole
|
|
101
|
+
page. Conclusion: custom or keyed differently. Decryption does not need it;
|
|
102
|
+
forged pages would not be detected. Open question #1.
|
|
103
|
+
|
|
104
|
+
## 4. WAL replay
|
|
105
|
+
|
|
106
|
+
QQ keeps a hot `-wal` file. Its header is standard: magic `0x377f0682`
|
|
107
|
+
(low bit = checksum byte order = little-endian here), page size, salts and
|
|
108
|
+
checksum chain; frames use 24-byte headers with big-endian page number/db size.
|
|
109
|
+
Replaying only frames up to the last commit (`dbSize != 0`) and stopping at the
|
|
110
|
+
first checksum failure is enough: on this install 13 frames verified, 12 applied,
|
|
111
|
+
`dbSize` 620 209 — which also matches the page count in the plaintext header.
|
|
112
|
+
|
|
113
|
+
## 5. Capturing the device passphrase
|
|
114
|
+
|
|
115
|
+
Two routes, both verified:
|
|
116
|
+
|
|
117
|
+
- **Automatic (`capture_offline_key`)**: `scripts/scan_memory.py` walks the QQ
|
|
118
|
+
process's readable memory and emits printable runs (12–32 bytes) plus 16- and
|
|
119
|
+
20-byte windows of longer runs. `src/keyscan.ts` then verifies candidates in
|
|
120
|
+
parallel against a `.material` DDL blob (`CREATE TABLE` must appear) or page 1
|
|
121
|
+
of a database (page size field + b-tree header must validate). A wrong
|
|
122
|
+
passphrase fails structurally, so the reported key is always verified.
|
|
123
|
+
Typical cost: seconds of scanning, then ~1 ms per candidate (PBKDF2-bound).
|
|
124
|
+
- **Debugger**: `nt_sqlite3_key_v2` in
|
|
125
|
+
`.../9.9.35-52892/resources/app/wrapper.node` — the string
|
|
126
|
+
`nt_sqlite3_key_v2: db=%p zDb=%s` sits at RVA `0x4852D8B`, its referencing
|
|
127
|
+
LEA at `0x21B8C95`, the function at RVA `0x21B8C80`, which tail-calls the key
|
|
128
|
+
binder `0x21B8980`. Launch `QQ.exe` with `DEBUG_ONLY_THIS_PROCESS`, break at
|
|
129
|
+
the function, read `R8` (`pKey`) / `R9` (`nKey`). Observed `nKey` 16 and 20.
|
|
130
|
+
|
|
131
|
+
One passphrase decrypts all 18 databases (`verify_offline_key { all: true }`);
|
|
132
|
+
it is stable across sessions.
|
|
133
|
+
|
|
134
|
+
The passphrase exists only in process memory: scanning 9 GB of `nt_qq`,
|
|
135
|
+
`%APPDATA%`\Tencent and the QQNT install directory found no on-disk copy, and
|
|
136
|
+
the registry holds only `version` / `installFlag`. The single DPAPI blob present
|
|
137
|
+
(`global\nt_data\msf\Registry20`, label `SPINFO_NTQQ_REGISTRY20`) unwraps to 20
|
|
138
|
+
opaque bytes — registry signing data, not key material. Consequently a database
|
|
139
|
+
copied to a machine where QQ has never logged in cannot be decrypted.
|
|
140
|
+
|
|
141
|
+
## 6. Schema and message decoding
|
|
142
|
+
|
|
143
|
+
- `nt_msg.db` schema: 87 tables. Message tables keyed by numeric column names:
|
|
144
|
+
`c2c_msg_table` and `group_msg_table` share
|
|
145
|
+
`40001` msg id, `40011` msg type, `40012` sub type, `40013` direction,
|
|
146
|
+
`40020` sender uid, `40021` peer uid, `40027` conversation (group number),
|
|
147
|
+
`40030` peer QQ, `40033` sender QQ, `40050` timestamp, `40093` sender name,
|
|
148
|
+
`40800` protobuf body.
|
|
149
|
+
- `[40800]` is `MsgBody { repeated MsgContent content = 40800 }`; ids match the
|
|
150
|
+
public field research (QQBackup/nt_msg_db_util), extended here by observation.
|
|
151
|
+
`src/messages.ts` types text/image/video/file/sticker/contact/ark(interactive
|
|
152
|
+
card)/nudge/reply/forward/legacy_forward/call/system.
|
|
153
|
+
- Message-type (40011) histogram on `group_msg_table` of this account:
|
|
154
|
+
`2` normal (2.2 M), `9` reply+content (292 k), `5` nudge 戳一戳 (87 k),
|
|
155
|
+
`17` system (15 k), `8` legacy forward (13 k), `11` forward (9 k),
|
|
156
|
+
`7` video (7 k), `1` mixed (4 k).
|
|
157
|
+
- `nudge` (type 5) fields: `48210` repeated `{uid, nickname}` of the parties,
|
|
158
|
+
`48214` `<gtip>` XML with `txt="…"` fragments, `48217` repeated
|
|
159
|
+
`{key, value}` strings (`action_str`, `suffix_str`, `action_img_url`, …),
|
|
160
|
+
`48271` the same as JSON. Rendered text example:
|
|
161
|
+
`戳了戳的脸颊 被喂了一颗糖`.
|
|
162
|
+
- Contacts are exact, not heuristic:
|
|
163
|
+
`recent_contact_v3_table` (`40020` uid `u_…`, `40033` peer QQ,
|
|
164
|
+
`40090`/`40091`/`40093` names, `40050` last activity),
|
|
165
|
+
`nt_uid_mapping_table` (`1002` QQ ↔ `48902` uid), and `group_info.db`
|
|
166
|
+
`group_list` (`60001` group number, `60007` name, `60006` member count) and
|
|
167
|
+
`group_member3` (`1002` member QQ, `1000` uid, `20002` nickname,
|
|
168
|
+
`64003` group card, `64007`/`64008` join/last-speak times).
|
|
169
|
+
|
|
170
|
+
## 7. Open questions (next reverse-engineering targets)
|
|
171
|
+
|
|
172
|
+
1. The 32-byte page tag algorithm (§3) — needed only for authenticity checks.
|
|
173
|
+
2. Unmodelled `MsgContent` field ids still seen in real rows, by frequency:
|
|
174
|
+
`47416` `47411` `47415` `48752` `47716` `47707` `48751` `47419` `47405`
|
|
175
|
+
`47407` `47418` `47424` `47410` `48101` `48701`-`48704` `48420` `45534`
|
|
176
|
+
`45830` `80977` `80978` `47425` `45425`. `decodeMessageContent` reports them
|
|
177
|
+
per segment, so new work is measurable.
|
|
178
|
+
3. `msgType` semantics below the top eight values, and `40012` sub types.
|
|
179
|
+
4. Damaged index pages in `group_msg_table` (present in QQ's own database — the
|
|
180
|
+
same `quick_check` verdict comes from SQLCipher itself), i.e. what QQ's own
|
|
181
|
+
write path leaves behind after a crash.
|
|
182
|
+
|
|
183
|
+
## 8. Reference links
|
|
184
|
+
|
|
185
|
+
- QQBackup/nt_msg_db_util — field research, `40800` proto, SQLCipher PRAGMAs
|
|
186
|
+
- QQBackup/qq-win-db-key (valid for ≤9.9.30)
|
|
187
|
+
- MioYi "Reverse-Engineering the Mobile QQ (QQNT) Database" (iOS scheme)
|
|
188
|
+
- Mythologyli/qq-nt-db (older Windows scheme)
|