-
Notifications
You must be signed in to change notification settings - Fork 820
/
crypto.ts
359 lines (296 loc) · 13.3 KB
/
crypto.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
import { debug } from "console";
import { colors as c } from "../lib/color";
import { fsend } from "../lib/helpers";
import { IJob } from "../lib/interfaces";
import { jobs } from "../lib/jobs";
import { arrayBufferToHex, hexToString } from "./lib/helpers"
// Encryption algorithms implemented by this module.
const CCAlgorithm = {
0: { name: "kCCAlgorithmAES128", blocksize: 16 },
1: { name: "kCCAlgorithmDES", blocksize: 8 },
2: { name: "kCCAlgorithm3DES", blocksize: 8 },
3: { name: "kCCAlgorithmCAST", blocksize: 8 },
4: { name: "kCCAlgorithmRC4", blocksize: 8 },
5: { name: "kCCAlgorithmRC2", blocksize: 8 }
};
// Encryption algorithms implemented by this module.
const CCOperation = {
0: "kCCEncrypt",
1: "kCCDecrypt"
};
// Options flags, passed to CCCryptorCreate().
const CCOption = {
1: "kCCOptionPKCS7Padding",
2: "kCCOptionECBMode"
};
// alg for pbkdf. Right now only pbkdf2 is supported by CommonCrypto
const CCPBKDFAlgorithm = {
2: "kCCPBKDF2"
};
// alg for prt for pbkdf
const CCPseudoRandomAlgorithm = {
1: "kCCPRFHmacAlgSHA1",
2: "kCCPRFHmacAlgSHA224",
3: "kCCPRFHmacAlgSHA256",
4: "kCCPRFHmacAlgSHA384",
5: "kCCPRFHmacAlgSHA512"
};
export namespace ioscrypto {
// ident for crypto hooks job
let cryptoidentifier: string = null;
// operation being performed 0=encrypt 1=decrypt
let op = 0;
// needed to keep track of CCAlgorithm so we can know
// blocksize from CCCryptorCreate to CCCryptorUpdate
let alg = 0;
// keep track of all the output bytes.
// this is necessary because CCCryptorUpdate needs to be
// append the final block from CCCryptorFinal
let dataOutBytes = null;
const secrandomcopybytes = (ident: string): InvocationListener => {
const hook = "SecRandomCopyBytes";
return Interceptor.attach(
Module.getExportByName(null, hook), {
onEnter(args) {
this.secrandomcopybytes = {};
this.secrandomcopybytes.rnd = args[0].toInt32();
this.secrandomcopybytes.count = args[1].toInt32();
this.bytes = args[2];
},
onLeave(retval) {
this.secrandomcopybytes.bytes = arrayBufferToHex(this.bytes.readByteArray(this.secrandomcopybytes.count));
fsend(ident, hook, this.secrandomcopybytes);
}
});
};
const cckeyderivationpbkdf = (ident: string): InvocationListener => {
const hook = "CCKeyDerivationPBKDF";
return Interceptor.attach(
Module.getExportByName(null, hook), {
onEnter(args) {
this.cckeyderivationpbkdf = {};
// args[0] "kCCPBKDF2" is the only alg supported by CommonCrypto
this.cckeyderivationpbkdf.algorithm = CCPBKDFAlgorithm[args[0].toInt32()];
// args[1] The text password used as input to the derivation
// function. The actual octets present in this string
// will be used with no additional processing. It's
// extremely important that the same encoding and
// normalization be used each time this routine is
// called if the same key is expected to be derived.
// args[2] The length of the text password in bytes.
const passwordPtr = args[1];
const passwordLen = args[2].toInt32();
const passwordBytes = arrayBufferToHex(passwordPtr.readByteArray(passwordLen));
try {
this.cckeyderivationpbkdf.password = hexToString(passwordBytes);
} catch {
this.cckeyderivationpbkdf.password = passwordBytes;
}
// args[3] The salt byte values used as input to the derivation function.
// args[4] The length of the salt in bytes.
const saltPtr = args[3];
const saltLen = args[4].toInt32();
this.cckeyderivationpbkdf.saltBytes = arrayBufferToHex(saltPtr.readByteArray(saltLen));
// args[5] The Pseudo Random Algorithm to use for the derivation iterations.
this.cckeyderivationpbkdf.prf = CCPseudoRandomAlgorithm[args[5].toInt32()];
// args[6] The number of rounds of the Pseudo Random Algorithm to use.
this.cckeyderivationpbkdf.rounds = args[6].toInt32();
// args[7] The resulting derived key produced by the function.
// The space for this must be provided by the caller.
this.derivedKeyPtr = args[7];
// args[8] The expected length of the derived key in bytes.
this.derivedKeyLen = args[8].toInt32();
},
onLeave(retval) {
this.cckeyderivationpbkdf.derivedKey = arrayBufferToHex(this.derivedKeyPtr.readByteArray(this.derivedKeyLen));
fsend(ident, hook, this.cckeyderivationpbkdf);
}
});
};
const cccrypt = (ident: string): InvocationListener => {
const hook = "CCCrypt";
return Interceptor.attach(
Module.getExportByName(null, hook), {
onEnter(args) {
this.cccrpyt = {};
// args[0] Defines the basic operation: kCCEncrypt or kCCDecrypt.
this.op = args[0].toInt32();
this.cccrpyt.op = CCOperation[this.op];
// args[1] Defines the encryption algorithm.
this.alg = args[1].toInt32();
this.cccrpyt.alg = CCAlgorithm[alg].name;
// args[2] A word of flags defining options. See discussion for the CCOptions type.
this.cccrpyt.options = CCOption[args[2].toInt32()];
// args[3] Raw key material, length keyLength bytes.
// args[4] Length of key material. Must be appropriate
// for the select algorithm. Some algorithms may
// provide for varying key lengths.
const key = args[3];
this.cccrpyt.keyLength = args[4].toInt32();
this.cccrpyt.key = arrayBufferToHex(key.readByteArray(this.cccrpyt.keyLength));
// args[5] Initialization vector, optional. Used for
// Cipher Block Chaining (CBC) mode. If present,
// must be the same length as the selected
// algorithm's block size. If CBC mode is
// selected (by the absence of any mode bits in
// the options flags) and no IV is present, a
// NULL (all zeroes) IV will be used. This is
// ignored if ECB mode is used or if a stream
// cipher algorithm is selected.
const iv = args[5];
this.cccrpyt.iv = arrayBufferToHex(iv.readByteArray(CCAlgorithm[alg].blocksize));
// args[6] Data to encrypt or decrypt, length dataInLength bytes.
// args[7] Length of data to encrypt or decrypt.
const dataInPtr = args[6];
const dataInLength = args[7].toInt32();
const dataInHex = arrayBufferToHex(dataInPtr.readByteArray(dataInLength));
this.cccrpyt.dataIn = this.op ? dataInHex : hexToString(dataInHex);
// args[8] Result is written here. Allocated by caller.
// Encryption and decryption can be performed
// "in-place", with the same buffer used for
// input and output.
this.dataOut = args[8];
// args[9] The size of the dataOut buffer in bytes.
this.dataOutAvailable = args[9].toInt32();
// args[10] On successful return, the number of bytes written
// to dataOut. If kCCBufferTooSmall is returned as
// a result of insufficient buffer space being
// provided, the required buffer space is returned
// here.
this.dataOutMoved = args[10];
},
onLeave(retval) {
const dataOutHex = arrayBufferToHex(this.dataOut.readByteArray(this.dataOutAvailable));
this.cccrpyt.dataOut = this.op ? hexToString(dataOutHex) : dataOutHex;
fsend(ident, hook, this.cccrpyt);
}
});
};
const cccryptorcreate = (ident: string): InvocationListener => {
const hook = "CCCryptorCreate";
return Interceptor.attach(
Module.getExportByName(null, hook), {
onEnter(args) {
this.cccryptorcreate = {};
// args[0] Defines the basic operation: kCCEncrypt or kCCDecrypt.
op = args[0].toInt32()
this.cccryptorcreate.op = CCOperation[op];
// args[1] Defines the encryption algorithm.
alg = args[1].toInt32()
this.cccryptorcreate.alg = CCAlgorithm[alg].name;
// args[2] A word of flags defining options. See discussion for the CCOptions type.
const option = args[2].toInt32();
this.cccryptorcreate.options = CCOption[option];
// args[3] Raw key material, length keyLength bytes.
// args[4] Length of key material. Must be appropriate
// for the select algorithm. Some algorithms may
// provide for varying key lengths.
const keyPtr = args[3];
this.cccryptorcreate.keyLength = args[4].toInt32();
this.cccryptorcreate.key = arrayBufferToHex(keyPtr.readByteArray(this.cccryptorcreate.keyLength));
// args[5] Initialization vector, optional. Used for
// Cipher Block Chaining (CBC) mode. If present,
// must be the same length as the selected
// algorithm's block size. If CBC mode is
// selected (by the absence of any mode bits in
// the options flags) and no IV is present, a
// NULL (all zeroes) IV will be used. This is
// ignored if ECB mode is used or if a stream
// cipher algorithm is selected.
const ivPtr = args[5];
this.cccryptorcreate.iv = arrayBufferToHex(ivPtr.readByteArray(CCAlgorithm[alg].blocksize));
},
onLeave(retval) {
fsend(ident, hook, this.cccryptorcreate);
}
});
};
const cccryptorupdate = (ident: string): InvocationListener => {
const hook = "CCCryptorUpdate";
return Interceptor.attach(
Module.getExportByName(null, hook), {
onEnter(args) {
this.cccryptorupdate = {};
// reset for the next operation.
dataOutBytes = "";
// args[1] Data to process, length dataInLength bytes.
const dataInPtr = args[1];
// args[2] Length of data to process.
this.dataInLength = args[2].toInt32();
// args[3] Result is written here. Allocated by caller.
// Encryption and decryption can be performed
// "in-place", with the same buffer used for
// input and output.
this.dataOutPtr = args[3];
// args[4] The size of the dataOut buffer in bytes.
this.dataOutAvailable = args[4].toInt32();
const dataIn = arrayBufferToHex(dataInPtr.readByteArray(this.dataInLength));
this.cccryptorupdate.dataIn = op ? dataIn : hexToString(dataIn);
},
onLeave(retval) {
const blocksize = CCAlgorithm[alg].blocksize
// if the messsage is longer than 1 block then we need to
// remember everything before the final block
if (this.dataInLength > blocksize) {
// TODO: There is sometimes padding added to the end of this message
// someone please fix this in a pull request. it is super hacky.
dataOutBytes = arrayBufferToHex(this.dataOutPtr.readByteArray(this.dataOutAvailable)).split("000000")[0];
this.cccryptorupdate.dataOut = dataOutBytes;
}
fsend(ident, hook, this.cccryptorupdate);
}
});
};
const cccryptorfinal = (ident: string): InvocationListener => {
const hook = "CCCryptorFinal"
return Interceptor.attach(
Module.getExportByName(null, hook), {
onEnter(args) {
this.cccryptorfinal = {}
// args[1] Result is written here. Allocated by caller.
// Encryption and decryption can be performed
// "in-place", with the same buffer used for
// input and output.
this.dataOutPtr = args[1];
// args[2] The size of the dataOut buffer in bytes.
this.dataOutAvailable = args[2].toInt32();
},
onLeave(retval) {
// var dataOutHex = arrayBufferToHex(this.dataOutPtr.readByteArray(this.dataOutAvailable))
// this.cccryptorfinal.dataOut = op ? hexToString(dataOutHex) : dataOutHex
// append the final block the any previous blocks that might exist
dataOutBytes += arrayBufferToHex(this.dataOutPtr.readByteArray(this.dataOutAvailable));
this.cccryptorfinal.dataOut = this.op ? hexToString(dataOutBytes) : dataOutBytes;
// this.cccryptorfinal.dataOut = dataOutBytes
fsend(ident, hook, this.cccryptorfinal);
}
});
};
export const disable = (): void => {
// if we already have a job registered then kill it
if (jobs.hasIdent(cryptoidentifier)) {
send(c.red(`Killing `) + `${cryptoidentifier}`);
jobs.kill(cryptoidentifier);
}
};
export const monitor = (): void => {
// if we already have a job registered then return
if (jobs.hasIdent(cryptoidentifier)) {
send(`${c.greenBright("Job already registered")}: ${c.blueBright(cryptoidentifier)}`);
return;
}
const job: IJob = {
identifier: jobs.identifier(),
invocations: [],
type: "ios-crypto-monitor",
};
cryptoidentifier = job.identifier
job.invocations.push(secrandomcopybytes(job.identifier));
job.invocations.push(cckeyderivationpbkdf(job.identifier));
job.invocations.push(cccrypt(job.identifier));
job.invocations.push(cccryptorcreate(job.identifier));
job.invocations.push(cccryptorupdate(job.identifier));
job.invocations.push(cccryptorfinal(job.identifier));
jobs.add(job);
};
}