-
Notifications
You must be signed in to change notification settings - Fork 6
/
ti_unpack.js
181 lines (171 loc) · 6.26 KB
/
ti_unpack.js
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
/*
reads AssetCryptImpl files and return struct with Titanium file names and source codes.
*/
var fs = require('fs'),
path = require('path'),
lineReader = require('line-reader'),
java = require('java');
java.classpath.push(__dirname+path.sep+'java/commons-lang-2.6.jar');
java.options.push('-Xrs'); // reduce signal os signals
var resp = {};
var meta = {
totalBytes : 0,
ti_version : -1,
alloy : false
};
var classes = {
charset : java.import('java.nio.charset.Charset'),
integer : java.import('java.lang.Integer'),
string : java.import('java.lang.String'),
escapeu : java.import('org.apache.commons.lang.StringEscapeUtils'),
charbuf : java.import('java.nio.CharBuffer'),
_javalog : java.import('java.util.logging.LogManager')
};
var _config = {
smali : '',
java : '',
apk : '',
debug : true
};
var init = function(config, onReady) {
// config
for (var _c in config) _config[_c] = config[_c];
// turn standard java logger off
classes._javalog.getLogManagerSync().resetSync();
// ready
onReady();
};
var decrypt = function(onReady) {
// read AssetCryptImpl.smali (for ranges)
// get bytes from smali
var bytesC = { start:false, bufferlen:0, charbuf:'', line:'', array:[] };
var count = 0;
if (_config.smali!='' && _config.java!='') {
lineReader.eachLine(_config.smali, function(line, last){
bytesC.line = line;
if (line.indexOf('private static initAssetsBytes()Ljava/nio/CharBuffer')!=-1) {
bytesC.start = true;
} else if (bytesC.start && line.indexOf('const v0, ')!=-1) {
// titanium < v5
meta.ti_version = -5;
bytesC.line = line.split('const v0, ').join('').trim();
bytesC.bufferlen = classes.integer.decodeSync(bytesC.line);
bytesC.charbuf = classes.charbuf.allocateSync(bytesC.bufferlen);
} else if (bytesC.start && line.indexOf('const/16 v0, ')!=-1) {
// titanium v5.x +
meta.ti_version = 5;
bytesC.line = line.split('const/16 v0, ').join('').trim();
bytesC.bufferlen = classes.integer.decodeSync(bytesC.line);
bytesC.charbuf = classes.charbuf.allocateSync(bytesC.bufferlen);
} else if (bytesC.start && line.indexOf('const-string v1')!=-1) {
// content
bytesC.line = line.split('const-string v1, "').join('').trim();
bytesC.line = bytesC.line.slice(0,-1); // remove last " char
bytesC.line = classes.escapeu.unescapeJavaSync(bytesC.line);
bytesC.charbuf.append(bytesC.line);
} else if (line.indexOf('rewind()Ljava/nio/Buffer;')!=-1) {
bytesC.charbuf.rewind();
if (_config.debug) console.log('decoding bytes ...');
/* */
bytesC.assetBytes = classes.charset.forNameSync('ISO-8859-1').encodeSync(bytesC.charbuf).arraySync();
if (_config.debug) console.log('converting into java array of bytes ... takes some time');
var iii, _cnt=0, _inbytes = [];
for (iii in bytesC.assetBytes) {
_inbytes.push(java.newByte(bytesC.assetBytes[iii]));
}
_inbytes2 = java.newArray("byte",_inbytes);
//read file byte ranges from AssetCryptImpl.java
var passed_maps = false;
if (_config.debug) console.log('extracting file ranges ...');
meta.totalBytes = 0;
lineReader.eachLine(_config.java, function(line2, last2) {
var tmp = {};
if (line2.indexOf('hashMap.put')!=-1) {
tmp.file = line2.split(',')[0].split('hashMap.put(').join('').split('"').join('').trim();
tmp.offset = line2.split(',')[1].split('new Range(').join('').trim();
tmp.length = line2.split(',')[2].split('));').join('').trim();
try {
resp[tmp.file] = {
offset : classes.integer.decodeSync(tmp.offset),
bytes : classes.integer.decodeSync(tmp.length)
};
resp[tmp.file].content = _filterDataInRange(tmp.file, _inbytes2, resp[tmp.file].offset, resp[tmp.file].bytes);
meta.totalBytes += resp[tmp.file].bytes;
} catch(eeee) {
}
passed_maps = true;
} else {
if (passed_maps) {
onReady(false, resp);
return false;
}
}
});
}
});
} else {
onReady(true,{});
}
};
var _filterDataInRange = function(filename, ibytes, offset, length) {
var _resp = '', _respb = '', _bytes_len=ibytes.length;
var key = java.import('javax.crypto.spec.SecretKeySpec');
// FIRST ATTEMPT
try {
// titanium below 3.2.2 and 3.4.0 decryption requires byteslen - 1
_bytes_len = ibytes.length-1;
var secretKeySpec = new key( ibytes,
_bytes_len - classes.integer.decodeSync("0x10"),
classes.integer.decodeSync("0x10"),
'AES');
var _cipher = java.import('javax.crypto.Cipher').getInstanceSync('AES');
var _decrypt_mode = 2; //cipher["DECRYPT_MODE"];
_cipher.initSync(_decrypt_mode, secretKeySpec);
try {
_respb = _cipher.doFinalSync(ibytes, offset, length);
_resp = String.fromCharCode.apply(null, new Uint16Array(_respb));
} catch(e1a) {
_respb = _cipher.doFinalSync(ibytes, offset-1, length); //some files have the offset padded
_resp = String.fromCharCode.apply(null, new Uint16Array(_respb));
}
} catch(e1) {
_resp = '';
}
// SECOND ATTEMPT
if (_resp=='') {
try {
// titanium over v3.4.0
_bytes_len = ibytes.length;
var secretKeySpec = new key( ibytes,
_bytes_len - classes.integer.decodeSync("0x10"),
classes.integer.decodeSync("0x10"),
'AES');
var _cipher = java.import('javax.crypto.Cipher').getInstanceSync('AES');
var _decrypt_mode = 2; //cipher["DECRYPT_MODE"];
_cipher.initSync(_decrypt_mode, secretKeySpec);
try {
_respb = _cipher.doFinalSync(ibytes, offset, length);
_resp = String.fromCharCode.apply(null, new Uint16Array(_respb));
} catch(e1a) {
_respb = _cipher.doFinalSync(ibytes, offset-1, length); //some files have the offset padded
_resp = String.fromCharCode.apply(null, new Uint16Array(_respb));
}
} catch(e2) {
_resp = '';
}
}
if (_resp!='' && _config.debug) console.log('file:'+filename+', decrypted !');
return _resp;
};
var extract = function(outputdir, onReady) {
// writes the decrypted files from memory (var resp) into the given directory (creating as needed)
};
exports.init = init;
exports.decrypt = decrypt;
exports.extract = extract;
/* uncomment for testing
init({ smali:'test/AssetCryptImpl.smali', java:'test/AssetCryptImpl.java' }, function() {
decrypt(function(err, full) {
if (!err) console.log(full);
});
});*/