@patrickdk77/office-defuse 0.1.0 → 0.1.1
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/README.md +113 -26
- package/dist/biff.d.ts +62 -0
- package/dist/biff.js +95 -0
- package/dist/cfb.d.ts +58 -0
- package/dist/cfb.js +456 -0
- package/dist/context.d.ts +40 -12
- package/dist/context.js +46 -25
- package/dist/crc32.d.ts +8 -2
- package/dist/crc32.js +44 -4
- package/dist/engine.js +75 -26
- package/dist/fields.d.ts +29 -0
- package/dist/fields.js +97 -0
- package/dist/findings.js +28 -9
- package/dist/msg.d.ts +7 -0
- package/dist/msg.js +97 -0
- package/dist/objects.d.ts +42 -0
- package/dist/objects.js +134 -0
- package/dist/odf.d.ts +2 -2
- package/dist/offcrypto.d.ts +31 -0
- package/dist/offcrypto.js +408 -0
- package/dist/ole.d.ts +12 -0
- package/dist/ole.js +127 -0
- package/dist/oleobj.d.ts +74 -0
- package/dist/oleobj.js +198 -0
- package/dist/ooxml.d.ts +2 -2
- package/dist/ooxml.js +23 -71
- package/dist/package.d.ts +3 -0
- package/dist/package.js +21 -0
- package/dist/ppt.d.ts +8 -0
- package/dist/ppt.js +241 -0
- package/dist/rtf.d.ts +8 -0
- package/dist/rtf.js +539 -0
- package/dist/types.d.ts +27 -0
- package/dist/types.js +19 -0
- package/dist/word.d.ts +9 -0
- package/dist/word.js +160 -0
- package/dist/xls.d.ts +27 -0
- package/dist/xls.js +174 -0
- package/package.json +3 -3
package/dist/ppt.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.powerPointRules = powerPointRules;
|
|
37
|
+
const zlib = __importStar(require("node:zlib"));
|
|
38
|
+
const cfb_1 = require("./cfb");
|
|
39
|
+
const context_1 = require("./context");
|
|
40
|
+
const errors_1 = require("./errors");
|
|
41
|
+
const io_1 = require("./io");
|
|
42
|
+
const links_1 = require("./links");
|
|
43
|
+
const objects_1 = require("./objects");
|
|
44
|
+
const oleobj_1 = require("./oleobj");
|
|
45
|
+
const types_1 = require("./types");
|
|
46
|
+
/** Record types of the PowerPoint binary format (MS-PPT 2.13.24). */
|
|
47
|
+
const T = {
|
|
48
|
+
currentUserAtom: 0x0ff6,
|
|
49
|
+
userEditAtom: 0x0ff5,
|
|
50
|
+
persistDirectoryAtom: 0x1772,
|
|
51
|
+
exOleObjStg: 0x1011,
|
|
52
|
+
exOleObjAtom: 0x0fc3,
|
|
53
|
+
vbaInfoAtom: 0x0400,
|
|
54
|
+
interactiveInfo: 0x0ff2,
|
|
55
|
+
interactiveInfoAtom: 0x0ff3,
|
|
56
|
+
cString: 0x0fba,
|
|
57
|
+
exHyperlink: 0x0fd7,
|
|
58
|
+
exHyperlinkAtom: 0x0fd3,
|
|
59
|
+
};
|
|
60
|
+
/** ExVideoContainer, ExMIDIAudioContainer and ExWAVAudioLinkContainer, whose path names a file outside the presentation. */
|
|
61
|
+
const MEDIA_LINKS = new Set([0x1005, 0x100d, 0x1010]);
|
|
62
|
+
/** The CurrentUserAtom's header token of an encrypted presentation. */
|
|
63
|
+
const ENCRYPTED_TOKEN = 0xf3d1c4df;
|
|
64
|
+
const MAX_DEPTH = 64;
|
|
65
|
+
const u32 = (b, at) => (b[at] | (b[at + 1] << 8) | (b[at + 2] << 16) | (b[at + 3] << 24)) >>> 0;
|
|
66
|
+
const header = (b) => ({ version: b[0] & 0x0f, instance: (b[0] | (b[1] << 8)) >> 4, type: b[2] | (b[3] << 8), length: u32(b, 4) });
|
|
67
|
+
/**
|
|
68
|
+
* Inspects a PowerPoint 97 to 2003 presentation. PowerPoint finds its objects through the persist directory that the
|
|
69
|
+
* current edit names. This reads every record at the top level of the stream as well as every object the persist
|
|
70
|
+
* directories name, so an object that one route hides is still read.
|
|
71
|
+
*/
|
|
72
|
+
async function powerPointRules(ctx, cfb, stream) {
|
|
73
|
+
const location = stream.name;
|
|
74
|
+
const source = cfb.source(stream);
|
|
75
|
+
const reader = new io_1.Reader(source, stream.size, 16384, 8);
|
|
76
|
+
const persist = new Map();
|
|
77
|
+
const current = cfb.child(cfb.root, 'Current User');
|
|
78
|
+
if (current?.kind === 'stream') {
|
|
79
|
+
const cu = await cfb.source(current).read(0, 24);
|
|
80
|
+
if (cu.length >= 20 && header(cu).type === T.currentUserAtom) {
|
|
81
|
+
if (u32(cu, 12) === ENCRYPTED_TOKEN)
|
|
82
|
+
throw new errors_1.StopError(types_1.OfficeCategory.Encrypted, types_1.OfficeDetail.PasswordRequired, 'The presentation is encrypted', { stream: location });
|
|
83
|
+
await readPersist(reader, stream.size, u32(cu, 16), persist, ctx.settings.deadline);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const storages = new Map();
|
|
87
|
+
/** ExOleObjAtom types by the persist id of the storage they name: 0 embedded, 1 linked, 2 a control. */
|
|
88
|
+
const roles = new Map();
|
|
89
|
+
const vba = new Set();
|
|
90
|
+
const visited = new Set();
|
|
91
|
+
const walk = async (start, end, depth, siblings) => {
|
|
92
|
+
for (let pos = start; pos + 8 <= end;) {
|
|
93
|
+
(0, errors_1.checkTime)(ctx.settings.deadline);
|
|
94
|
+
const h = header(await reader.read(pos, 8));
|
|
95
|
+
const bodyEnd = pos + 8 + h.length;
|
|
96
|
+
if (bodyEnd > end || visited.has(pos))
|
|
97
|
+
return;
|
|
98
|
+
visited.add(pos);
|
|
99
|
+
siblings.push({ h, offset: pos });
|
|
100
|
+
if (h.type === T.exOleObjStg)
|
|
101
|
+
storages.set(pos, { offset: pos, header: h });
|
|
102
|
+
else if (h.type === T.exOleObjAtom && h.length >= 20) {
|
|
103
|
+
const b = await reader.read(pos + 8, 20);
|
|
104
|
+
roles.set(u32(b, 16), u32(b, 4));
|
|
105
|
+
}
|
|
106
|
+
else if (h.type === T.vbaInfoAtom && h.length >= 4)
|
|
107
|
+
vba.add(u32(await reader.read(pos + 8, 4), 0));
|
|
108
|
+
if (h.version === 0x0f && depth < MAX_DEPTH) {
|
|
109
|
+
const kids = [];
|
|
110
|
+
await walk(pos + 8, bodyEnd, depth + 1, kids);
|
|
111
|
+
await containerRules(ctx, reader, h, pos, kids, location);
|
|
112
|
+
}
|
|
113
|
+
pos = bodyEnd;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
// Every record at the top level, then each persist object that does not start on a record already read.
|
|
117
|
+
await walk(0, stream.size, 0, []);
|
|
118
|
+
for (const offset of persist.values()) {
|
|
119
|
+
if (visited.has(offset) || offset + 8 > stream.size)
|
|
120
|
+
continue;
|
|
121
|
+
const h = header(await reader.read(offset, 8));
|
|
122
|
+
await walk(offset, Math.min(stream.size, offset + 8 + h.length), 0, []);
|
|
123
|
+
}
|
|
124
|
+
const byOffset = new Map([...persist].map(([id, offset]) => [offset, id]));
|
|
125
|
+
for (const s of storages.values()) {
|
|
126
|
+
const id = byOffset.get(s.offset);
|
|
127
|
+
const role = id !== undefined && vba.has(id) ? 'vba' : id !== undefined && roles.get(id) === 2 ? 'control' : 'object';
|
|
128
|
+
await storageRules(ctx, source, s, role, `${location}, offset ${s.offset}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** Reads the persist directories of the current edit and every edit before it. Newer entries win. */
|
|
132
|
+
async function readPersist(reader, size, start, persist, deadline) {
|
|
133
|
+
const seen = new Set();
|
|
134
|
+
for (let edit = start; edit > 0 && edit + 36 <= size && !seen.has(edit);) {
|
|
135
|
+
(0, errors_1.checkTime)(deadline);
|
|
136
|
+
seen.add(edit);
|
|
137
|
+
const u = await reader.read(edit, 36);
|
|
138
|
+
if (header(u).type !== T.userEditAtom)
|
|
139
|
+
return;
|
|
140
|
+
const dirAt = u32(u, 20);
|
|
141
|
+
const d = await reader.read(dirAt, 8);
|
|
142
|
+
if (d.length === 8 && header(d).type === T.persistDirectoryAtom) {
|
|
143
|
+
const body = await reader.read(dirAt + 8, Math.min(header(d).length, size - dirAt - 8));
|
|
144
|
+
for (let p = 0; p + 4 <= body.length;) {
|
|
145
|
+
const entry = u32(body, p);
|
|
146
|
+
const first = entry & 0xfffff;
|
|
147
|
+
const count = entry >>> 20;
|
|
148
|
+
p += 4;
|
|
149
|
+
for (let i = 0; i < count && p + 4 <= body.length; i++, p += 4)
|
|
150
|
+
if (!persist.has(first + i))
|
|
151
|
+
persist.set(first + i, u32(body, p));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
edit = u32(u, 16);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** One ExOleObjStg: a VBA project, a control or an OLE object, stored as a compound file that may be compressed. */
|
|
158
|
+
async function storageRules(ctx, source, s, role, location) {
|
|
159
|
+
let inner;
|
|
160
|
+
if (s.header.instance === 1) {
|
|
161
|
+
const raw = await source.read(s.offset + 8, s.header.length);
|
|
162
|
+
const declared = raw.length >= 4 ? u32(raw, 0) : 0;
|
|
163
|
+
if (declared > ctx.settings.decompressedBytes)
|
|
164
|
+
throw new errors_1.StopError(types_1.OfficeCategory.Limit, types_1.OfficeDetail.DecompressedSize, `An object at ${location} declares ${declared} bytes`, { name: location, size: declared, limit: ctx.settings.decompressedBytes });
|
|
165
|
+
let bytes;
|
|
166
|
+
try {
|
|
167
|
+
bytes = zlib.inflateSync(raw.subarray(4), { maxOutputLength: Math.max(1, declared) });
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
unreadable(ctx, role, location, 'its compressed data does not inflate to its declared size');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
inner = { size: async () => bytes.length, read: async (offset, length) => bytes.subarray(offset, offset + length) };
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
const start = s.offset + 8;
|
|
177
|
+
const length = s.header.length;
|
|
178
|
+
inner = { size: async () => length, read: (offset, n) => source.read(start + offset, Math.max(0, Math.min(n, length - offset))) };
|
|
179
|
+
}
|
|
180
|
+
const head = await inner.read(0, 8);
|
|
181
|
+
let cfb;
|
|
182
|
+
try {
|
|
183
|
+
if (head.length < 8 || head[0] !== 0xd0 || head[1] !== 0xcf)
|
|
184
|
+
throw new errors_1.StopError(types_1.OfficeCategory.Container, types_1.OfficeDetail.BadHeader, 'not a compound file');
|
|
185
|
+
cfb = await cfb_1.CompoundFile.open(inner, ctx.settings, ctx.settings.deadline);
|
|
186
|
+
}
|
|
187
|
+
catch (e) {
|
|
188
|
+
if (!(e instanceof errors_1.StopError) || e.category !== types_1.OfficeCategory.Container)
|
|
189
|
+
throw e;
|
|
190
|
+
unreadable(ctx, role, location, `its storage does not read: ${e.message}`);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
(0, objects_1.nestedNotes)(ctx, cfb, location);
|
|
194
|
+
if (role === 'control')
|
|
195
|
+
ctx.report(types_1.OfficeCategory.Control, types_1.OfficeDetail.ActiveX, location, { storage: location, ...(cfb.root.clsid === '00000000-0000-0000-0000-000000000000' ? {} : { clsid: cfb.root.clsid }) });
|
|
196
|
+
await (0, objects_1.compoundRules)(ctx, cfb, location, role === 'object');
|
|
197
|
+
}
|
|
198
|
+
function unreadable(ctx, role, location, reason) {
|
|
199
|
+
if (role === 'vba')
|
|
200
|
+
ctx.report(types_1.OfficeCategory.Macro, types_1.OfficeDetail.VbaProject, location, { storage: location, reason });
|
|
201
|
+
else if (role === 'control')
|
|
202
|
+
ctx.report(types_1.OfficeCategory.Control, types_1.OfficeDetail.ActiveX, location, { storage: location, reason });
|
|
203
|
+
else
|
|
204
|
+
ctx.report(types_1.OfficeCategory.Embedded, types_1.OfficeDetail.OleObject, location, { storage: location, reason });
|
|
205
|
+
}
|
|
206
|
+
/** The rules of one container record, from its children: actions, hyperlinks and linked media. */
|
|
207
|
+
async function containerRules(ctx, reader, h, offset, kids, stream) {
|
|
208
|
+
const location = `${stream}, offset ${offset}`;
|
|
209
|
+
const strings = async () => {
|
|
210
|
+
const out = new Map();
|
|
211
|
+
for (const k of kids)
|
|
212
|
+
if (k.h.type === T.cString && !out.has(k.h.instance))
|
|
213
|
+
out.set(k.h.instance, (0, oleobj_1.utf16)(await reader.read(k.offset + 8, Math.min(k.h.length, 65536))));
|
|
214
|
+
return out;
|
|
215
|
+
};
|
|
216
|
+
if (h.type === T.interactiveInfo) {
|
|
217
|
+
const atom = kids.find(k => k.h.type === T.interactiveInfoAtom && k.h.length >= 16);
|
|
218
|
+
if (!atom)
|
|
219
|
+
return;
|
|
220
|
+
const action = (await reader.read(atom.offset + 8 + 8, 1))[0];
|
|
221
|
+
const name = (await strings()).get(2) ?? '';
|
|
222
|
+
const data = { trigger: h.instance === 1 ? 'mouse over' : 'click', ...(name ? { action: (0, context_1.clip)(name) } : {}) };
|
|
223
|
+
if (action === 2)
|
|
224
|
+
ctx.report(types_1.OfficeCategory.Action, types_1.OfficeDetail.RunProgram, location, data);
|
|
225
|
+
else if (action === 1)
|
|
226
|
+
ctx.report(types_1.OfficeCategory.Macro, types_1.OfficeDetail.Link, location, data);
|
|
227
|
+
else if (action === 5)
|
|
228
|
+
ctx.report(types_1.OfficeCategory.Action, types_1.OfficeDetail.OleVerb, location, data);
|
|
229
|
+
}
|
|
230
|
+
else if (h.type === T.exHyperlink) {
|
|
231
|
+
const s = await strings();
|
|
232
|
+
const target = s.get(1) ?? '';
|
|
233
|
+
if (target.trim() && !(0, links_1.isFragment)(target))
|
|
234
|
+
ctx.link(target, location, { source: 'PowerPoint hyperlink' });
|
|
235
|
+
}
|
|
236
|
+
else if (MEDIA_LINKS.has(h.type)) {
|
|
237
|
+
const path = (await strings()).get(0) ?? '';
|
|
238
|
+
if (path.trim())
|
|
239
|
+
ctx.external(types_1.OfficeDetail.Other, path, location, { source: 'linked sound or movie' }, 'linked media');
|
|
240
|
+
}
|
|
241
|
+
}
|
package/dist/rtf.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type RunContext } from './context';
|
|
2
|
+
import { type ByteSource } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Inspects an RTF file the way Word reads one. Objects with their data, fields, the attached template and shape
|
|
5
|
+
* hyperlinks are reported. Unknown destinations marked with \* are skipped, unknown control words are ignored, and
|
|
6
|
+
* text after the document's last brace is still read, as rtfobj reads it, and reported as trailing data.
|
|
7
|
+
*/
|
|
8
|
+
export declare function inspectRtf(ctx: RunContext, source: ByteSource, size: number): Promise<void>;
|