├── README.md ├── SwitchConfig_HookUpdate.js ├── antiFridaBypass.js ├── hookEvent.js ├── hookLog.js ├── hook_RegisterNatives.js ├── hook_art.js ├── hook_artmethod.js ├── hook_ssl.js ├── hook_ssl2.js ├── hook_vpn.js ├── hook_vpn_new.js ├── hookflutter.js ├── myhexdump.py ├── okhttp_poker.js ├── printstack.js ├── r0capture.py ├── root.js ├── script.js ├── ssl_pinning_frida.js └── tongsha_hook.js /README.md: -------------------------------------------------------------------------------- 1 | # frida_hook_script 2 | frida常用的hook脚本 3 | -------------------------------------------------------------------------------- /SwitchConfig_HookUpdate.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function pass_ssl(){ 4 | var ClassName = "com.android.org.conscrypt.Platform"; 5 | var Platform = Java.use(ClassName); 6 | var targetMethod = "checkServerTrusted"; 7 | var len = Platform[targetMethod].overloads.length; 8 | console.log(len); 9 | for (var i = 0; i < len; ++i) { 10 | Platform[targetMethod].overloads[i].implementation = function () { 11 | console.log("class:", ClassName, "target:", targetMethod, " i:", i, arguments); 12 | }; 13 | } 14 | } 15 | 16 | // hook强制更新 17 | function hook_update(){ 18 | var appUpdateActivity = Java.use("com.huawei.updatesdk.service.otaupdate.AppUpdateActivity"); 19 | appUpdateActivity.onCreate.implementation = function (aaa){ 20 | console.log("--------hook update--------") 21 | showStacks() 22 | this.finish(); 23 | }; 24 | } 25 | 26 | function main(){ 27 | Java.perform(function(){ 28 | var SwitchConfig = Java.use('mtopsdk.mtop.global.SwitchConfig') 29 | SwitchConfig.isGlobalSpdySwitchOpen.implementation = function(){ 30 | console.log('SwitchConfig') 31 | return false 32 | } 33 | pass_ssl(); 34 | hook_update(); 35 | }) 36 | } 37 | //frida -U com.taobao.taobao -l hook_socker.js 38 | 39 | 40 | setTimeout(main, 100); -------------------------------------------------------------------------------- /antiFridaBypass.js: -------------------------------------------------------------------------------- 1 | function hook_strstr() { 2 | var pfn_strstr = Module.findExportByName("libc.so", "strstr"); 3 | Interceptor.attach(pfn_strstr, { 4 | onEnter: function (args) { 5 | var str1 = Memory.readCString(args[0]); 6 | var str2 = Memory.readCString(args[1]); 7 | if (str2.indexOf("SigBlk") !== -1 || 8 | str2.indexOf("gdbus") !== -1 || 9 | str2.indexOf("frida") !== -1 || 10 | str2.indexOf("gum-js-loop") !== -1 || 11 | str2.indexOf("gmain") !== -1 || 12 | str2.indexOf("linjector") !== -1 13 | ) { 14 | console.log("str1:%s - str2:%s\n", str1, str2); 15 | this.hook = true; 16 | } 17 | }, 18 | onLeave: function (retval) { 19 | if (this.hook) { 20 | retval.replace(0x0); 21 | } 22 | } 23 | }); 24 | } 25 | 26 | function hook_pthread() { 27 | 28 | var pthread_create_addr = Module.findExportByName(null, 'pthread_create'); 29 | console.log("pthread_create_addr,", pthread_create_addr); 30 | 31 | var pthread_create = new NativeFunction(pthread_create_addr, "int", ["pointer", "pointer", "pointer", "pointer"]); 32 | 33 | Interceptor.replace(pthread_create_addr, new NativeCallback(function (parg0, parg1, parg2, parg3) { 34 | var so_name = Process.findModuleByAddress(parg2).name; 35 | var so_path = Process.findModuleByAddress(parg2).path; 36 | var so_base = Module.getBaseAddress(so_name); 37 | var offset = parg2 - so_base; 38 | console.log("so_name", so_name, "offset", offset, "path", so_path, "parg2", parg2); 39 | var PC = 0; 40 | if ((so_name.indexOf("libmsaoaidsec.so") > -1) || (so_name.indexOf("xxxx") > -1)) { 41 | console.log("find thread func offset", so_name, offset); 42 | if ((69929 === offset)) { 43 | console.log("anti bypass"); 44 | } else if (67988 === offset) { 45 | console.log("anti bypass"); 46 | } else if (110308 === offset) { 47 | console.log("anti bypass"); 48 | } else { 49 | PC = pthread_create(parg0, parg1, parg2, parg3); 50 | console.log("ordinary sequence", PC) 51 | } 52 | } else { 53 | PC = pthread_create(parg0, parg1, parg2, parg3); 54 | // console.log("ordinary sequence", PC) 55 | } 56 | return PC; 57 | }, "int", ["pointer", "pointer", "pointer", "pointer"])) 58 | 59 | } 60 | 61 | hook_strstr(); 62 | //hook_pthread(); 63 | -------------------------------------------------------------------------------- /hookEvent.js: -------------------------------------------------------------------------------- 1 | var jclazz = null; 2 | var jobj = null; 3 | 4 | function getObjClassName(obj) { 5 | if (!jclazz) { 6 | var jclazz = Java.use("java.lang.Class"); 7 | } 8 | if (!jobj) { 9 | var jobj = Java.use("java.lang.Object"); 10 | } 11 | return jclazz.getName.call(jobj.getClass.call(obj)); 12 | } 13 | 14 | function watch(obj, mtdName) { 15 | var listener_name = getObjClassName(obj); 16 | var target = Java.use(listener_name); 17 | if (!target || !mtdName in target) { 18 | return; 19 | } 20 | // send("[WatchEvent] hooking " + mtdName + ": " + listener_name); 21 | target[mtdName].overloads.forEach(function (overload) { 22 | overload.implementation = function () { 23 | //send("[WatchEvent] " + mtdName + ": " + getObjClassName(this)); 24 | console.log("[WatchEvent] " + mtdName + ": " + getObjClassName(this)) 25 | return this[mtdName].apply(this, arguments); 26 | }; 27 | }) 28 | } 29 | 30 | function OnClickListener() { 31 | Java.perform(function () { 32 | 33 | //以spawn启动进程的模式来attach的话 34 | Java.use("android.view.View").setOnClickListener.implementation = function (listener) { 35 | if (listener != null) { 36 | watch(listener, 'onClick'); 37 | } 38 | return this.setOnClickListener(listener); 39 | }; 40 | 41 | //如果frida以attach的模式进行attch的话 42 | Java.choose("android.view.View$ListenerInfo", { 43 | onMatch: function (instance) { 44 | instance = instance.mOnClickListener.value; 45 | if (instance) { 46 | console.log("mOnClickListener name is :" + getObjClassName(instance)); 47 | watch(instance, 'onClick'); 48 | } 49 | }, 50 | onComplete: function () { 51 | } 52 | }) 53 | }) 54 | } 55 | setImmediate(OnClickListener); -------------------------------------------------------------------------------- /hookLog.js: -------------------------------------------------------------------------------- 1 | Java.perform(function () { 2 | function hook_log() { 3 | dmLogout(TAG, "do hook log"); 4 | var Log = Java.use('android.util.Log'); 5 | Log.v.overload('java.lang.String', 'java.lang.String').implementation = function (tag, content) { 6 | dmLogout(tag + " v", content); 7 | }; 8 | Log.d.overload('java.lang.String', 'java.lang.String').implementation = function (tag, content) { 9 | dmLogout(tag + " d", content); 10 | }; 11 | Log.w.overload('java.lang.String', 'java.lang.String').implementation = function (tag, content) { 12 | dmLogout(tag + " w", content); 13 | }; 14 | Log.i.overload('java.lang.String', 'java.lang.String').implementation = function (tag, content) { 15 | dmLogout(tag + " i", content); 16 | }; 17 | Log.e.overload('java.lang.String', 'java.lang.String').implementation = function (tag, content) { 18 | dmLogout(tag + " e", content); 19 | }; 20 | } 21 | hook_log() 22 | }) -------------------------------------------------------------------------------- /hook_RegisterNatives.js: -------------------------------------------------------------------------------- 1 | 2 | function find_RegisterNatives(params) { 3 | var symbols = Module.enumerateSymbolsSync("libart.so"); 4 | var addrRegisterNatives = null; 5 | for (var i = 0; i < symbols.length; i++) { 6 | var symbol = symbols[i]; 7 | 8 | //_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi 9 | if (symbol.name.indexOf("art") >= 0 && 10 | symbol.name.indexOf("JNI") >= 0 && 11 | symbol.name.indexOf("RegisterNatives") >= 0 && 12 | symbol.name.indexOf("CheckJNI") < 0) { 13 | addrRegisterNatives = symbol.address; 14 | console.log("RegisterNatives is at ", symbol.address, symbol.name); 15 | hook_RegisterNatives(addrRegisterNatives) 16 | } 17 | } 18 | 19 | } 20 | 21 | function hook_RegisterNatives(addrRegisterNatives) { 22 | 23 | if (addrRegisterNatives != null) { 24 | Interceptor.attach(addrRegisterNatives, { 25 | onEnter: function (args) { 26 | console.log("[RegisterNatives] method_count:", args[3]); 27 | var env = args[0]; 28 | var java_class = args[1]; 29 | var class_name = Java.vm.tryGetEnv().getClassName(java_class); 30 | //console.log(class_name); 31 | 32 | var methods_ptr = ptr(args[2]); 33 | 34 | var method_count = parseInt(args[3]); 35 | for (var i = 0; i < method_count; i++) { 36 | var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3)); 37 | var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize)); 38 | var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2)); 39 | 40 | var name = Memory.readCString(name_ptr); 41 | var sig = Memory.readCString(sig_ptr); 42 | var find_module = Process.findModuleByAddress(fnPtr_ptr); 43 | console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, " fnOffset:", ptr(fnPtr_ptr).sub(find_module.base), " callee:", DebugSymbol.fromAddress(this.returnAddress)); 44 | 45 | } 46 | } 47 | }); 48 | } 49 | } 50 | 51 | setImmediate(find_RegisterNatives); 52 | -------------------------------------------------------------------------------- /hook_art.js: -------------------------------------------------------------------------------- 1 | 2 | const STD_STRING_SIZE = 3 * Process.pointerSize; 3 | class StdString { 4 | constructor() { 5 | this.handle = Memory.alloc(STD_STRING_SIZE); 6 | } 7 | 8 | dispose() { 9 | const [data, isTiny] = this._getData(); 10 | if (!isTiny) { 11 | Java.api.$delete(data); 12 | } 13 | } 14 | 15 | disposeToString() { 16 | const result = this.toString(); 17 | this.dispose(); 18 | return result; 19 | } 20 | 21 | toString() { 22 | const [data] = this._getData(); 23 | return data.readUtf8String(); 24 | } 25 | 26 | _getData() { 27 | const str = this.handle; 28 | const isTiny = (str.readU8() & 1) === 0; 29 | const data = isTiny ? str.add(1) : str.add(2 * Process.pointerSize).readPointer(); 30 | return [data, isTiny]; 31 | } 32 | } 33 | 34 | function prettyMethod(method_id, withSignature) { 35 | const result = new StdString(); 36 | Java.api['art::ArtMethod::PrettyMethod'](result, method_id, withSignature ? 1 : 0); 37 | return result.disposeToString(); 38 | } 39 | 40 | /* 41 | GetFieldID is at 0xe39b87c5 _ZN3art3JNI10GetFieldIDEP7_JNIEnvP7_jclassPKcS6_ 42 | GetMethodID is at 0xe39a1a19 _ZN3art3JNI11GetMethodIDEP7_JNIEnvP7_jclassPKcS6_ 43 | NewStringUTF is at 0xe39cff25 _ZN3art3JNI12NewStringUTFEP7_JNIEnvPKc 44 | RegisterNatives is at 0xe39e08fd _ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi 45 | GetStaticFieldID is at 0xe39c9635 _ZN3art3JNI16GetStaticFieldIDEP7_JNIEnvP7_jclassPKcS6_ 46 | GetStaticMethodID is at 0xe39be0ed _ZN3art3JNI17GetStaticMethodIDEP7_JNIEnvP7_jclassPKcS6_ 47 | GetStringUTFChars is at 0xe39d06e5 _ZN3art3JNI17GetStringUTFCharsEP7_JNIEnvP8_jstringPh 48 | FindClass is at 0xe399ae5d _ZN3art3JNI9FindClassEP7_JNIEnvPKc 49 | */ 50 | 51 | function hook_libart() { 52 | var symbols = Module.enumerateSymbolsSync("libart.so"); 53 | var addrGetStringUTFChars = null; 54 | var addrNewStringUTF = null; 55 | var addrFindClass = null; 56 | var addrGetMethodID = null; 57 | var addrGetStaticMethodID = null; 58 | var addrGetFieldID = null; 59 | var addrGetStaticFieldID = null; 60 | var addrRegisterNatives = null; 61 | var so_name = "lib"; //TODO 这里写需要过滤的so 62 | 63 | for (var i = 0; i < symbols.length; i++) { 64 | var symbol = symbols[i]; 65 | if (symbol.name.indexOf("art") >= 0 && 66 | symbol.name.indexOf("JNI") >= 0 && 67 | symbol.name.indexOf("CheckJNI") < 0 && 68 | symbol.name.indexOf("_ZN3art3JNIILb0") >= 0 69 | ) { 70 | if (symbol.name.indexOf("GetStringUTFChars") >= 0) { 71 | addrGetStringUTFChars = symbol.address; 72 | console.log("GetStringUTFChars is at ", symbol.address, symbol.name); 73 | } else if (symbol.name.indexOf("NewStringUTF") >= 0) { 74 | addrNewStringUTF = symbol.address; 75 | console.log("NewStringUTF is at ", symbol.address, symbol.name); 76 | } else if (symbol.name.indexOf("FindClass") >= 0) { 77 | addrFindClass = symbol.address; 78 | console.log("FindClass is at ", symbol.address, symbol.name); 79 | } else if (symbol.name.indexOf("GetMethodID") >= 0) { 80 | addrGetMethodID = symbol.address; 81 | console.log("GetMethodID is at ", symbol.address, symbol.name); 82 | } else if (symbol.name.indexOf("GetStaticMethodID") >= 0) { 83 | addrGetStaticMethodID = symbol.address; 84 | console.log("GetStaticMethodID is at ", symbol.address, symbol.name); 85 | } else if (symbol.name.indexOf("GetFieldID") >= 0) { 86 | addrGetFieldID = symbol.address; 87 | console.log("GetFieldID is at ", symbol.address, symbol.name); 88 | } else if (symbol.name.indexOf("GetStaticFieldID") >= 0) { 89 | addrGetStaticFieldID = symbol.address; 90 | console.log("GetStaticFieldID is at ", symbol.address, symbol.name); 91 | } else if (symbol.name.indexOf("RegisterNatives") >= 0) { 92 | addrRegisterNatives = symbol.address; 93 | console.log("RegisterNatives is at ", symbol.address, symbol.name); 94 | } else if (symbol.name.indexOf("CallStatic") >= 0) { 95 | console.log("CallStatic is at ", symbol.address, symbol.name); 96 | Interceptor.attach(symbol.address, { 97 | onEnter: function (args) { 98 | var module = Process.findModuleByAddress(this.returnAddress); 99 | if (module != null && module.name.indexOf(so_name) == 0) { 100 | var java_class = args[1]; 101 | var mid = args[2]; 102 | var class_name = Java.vm.tryGetEnv().getClassName(java_class); 103 | if (class_name.indexOf("java.") == -1 && class_name.indexOf("android.") == -1) { 104 | var method_name = prettyMethod(mid, 1); 105 | console.log("<>CallStatic:", DebugSymbol.fromAddress(this.returnAddress), class_name, method_name); 106 | } 107 | } 108 | }, 109 | onLeave: function (retval) { } 110 | }); 111 | } else if (symbol.name.indexOf("CallNonvirtual") >= 0) { 112 | console.log("CallNonvirtual is at ", symbol.address, symbol.name); 113 | Interceptor.attach(symbol.address, { 114 | onEnter: function (args) { 115 | var module = Process.findModuleByAddress(this.returnAddress); 116 | if (module != null && module.name.indexOf(so_name) == 0) { 117 | var jobject = args[1]; 118 | var jclass = args[2]; 119 | var jmethodID = args[3]; 120 | var obj_class_name = Java.vm.tryGetEnv().getObjectClassName(jobject); 121 | var class_name = Java.vm.tryGetEnv().getClassName(jclass); 122 | if (class_name.indexOf("java.") == -1 && class_name.indexOf("android.") == -1) { 123 | var method_name = prettyMethod(jmethodID, 1); 124 | console.log("<>CallNonvirtual:", DebugSymbol.fromAddress(this.returnAddress), class_name, obj_class_name, method_name); 125 | } 126 | } 127 | }, 128 | onLeave: function (retval) { } 129 | }); 130 | } else if (symbol.name.indexOf("Call") >= 0 && symbol.name.indexOf("Method") >= 0) { 131 | console.log("Call<>Method is at ", symbol.address, symbol.name); 132 | Interceptor.attach(symbol.address, { 133 | onEnter: function (args) { 134 | var module = Process.findModuleByAddress(this.returnAddress); 135 | if (module != null && module.name.indexOf(so_name) == 0) { 136 | var java_class = args[1]; 137 | var mid = args[2]; 138 | var class_name = Java.vm.tryGetEnv().getObjectClassName(java_class); 139 | if (class_name.indexOf("java.") == -1 && class_name.indexOf("android.") == -1) { 140 | var method_name = prettyMethod(mid, 1); 141 | console.log("<>Call<>Method:", DebugSymbol.fromAddress(this.returnAddress), class_name, method_name); 142 | } 143 | } 144 | }, 145 | onLeave: function (retval) { } 146 | }); 147 | } 148 | } 149 | } 150 | 151 | if (addrGetStringUTFChars != null) { 152 | Interceptor.attach(addrGetStringUTFChars, { 153 | onEnter: function (args) { 154 | }, 155 | onLeave: function (retval) { 156 | if (retval != null) { 157 | var module = Process.findModuleByAddress(this.returnAddress); 158 | if (module != null && module.name.indexOf(so_name) == 0) { 159 | var bytes = Memory.readCString(retval); 160 | console.log("[GetStringUTFChars] result:" + bytes, DebugSymbol.fromAddress(this.returnAddress)); 161 | } 162 | } 163 | } 164 | }); 165 | } 166 | if (addrNewStringUTF != null) { 167 | Interceptor.attach(addrNewStringUTF, { 168 | onEnter: function (args) { 169 | if (args[1] != null) { 170 | var module = Process.findModuleByAddress(this.returnAddress); 171 | if (module != null && module.name.indexOf(so_name) == 0) { 172 | var string = Memory.readCString(args[1]); 173 | console.log("[NewStringUTF] bytes:" + string, DebugSymbol.fromAddress(this.returnAddress)); 174 | } 175 | 176 | } 177 | }, 178 | onLeave: function (retval) { } 179 | }); 180 | } 181 | 182 | if (addrFindClass != null) { 183 | Interceptor.attach(addrFindClass, { 184 | onEnter: function (args) { 185 | if (args[1] != null) { 186 | var module = Process.findModuleByAddress(this.returnAddress); 187 | if (module != null && module.name.indexOf(so_name) == 0) { 188 | var name = Memory.readCString(args[1]); 189 | console.log("[FindClass] name:" + name, DebugSymbol.fromAddress(this.returnAddress)); 190 | } 191 | } 192 | }, 193 | onLeave: function (retval) { } 194 | }); 195 | } 196 | if (addrGetMethodID != null) { 197 | Interceptor.attach(addrGetMethodID, { 198 | onEnter: function (args) { 199 | if (args[2] != null) { 200 | var clazz = args[1]; 201 | var class_name = Java.vm.tryGetEnv().getClassName(clazz); 202 | var module = Process.findModuleByAddress(this.returnAddress); 203 | if (module != null && module.name.indexOf(so_name) == 0) { 204 | var name = Memory.readCString(args[2]); 205 | if (args[3] != null) { 206 | var sig = Memory.readCString(args[3]); 207 | console.log("[GetMethodID] class_name:" + class_name + " name:" + name + ", sig:" + sig, DebugSymbol.fromAddress(this.returnAddress)); 208 | } else { 209 | console.log("[GetMethodID] class_name:" + class_name + " name:" + name, DebugSymbol.fromAddress(this.returnAddress)); 210 | } 211 | } 212 | } 213 | }, 214 | onLeave: function (retval) { } 215 | }); 216 | } 217 | if (addrGetStaticMethodID != null) { 218 | Interceptor.attach(addrGetStaticMethodID, { 219 | onEnter: function (args) { 220 | if (args[2] != null) { 221 | var clazz = args[1]; 222 | var class_name = Java.vm.tryGetEnv().getClassName(clazz); 223 | var module = Process.findModuleByAddress(this.returnAddress); 224 | if (module != null && module.name.indexOf(so_name) == 0) { 225 | var name = Memory.readCString(args[2]); 226 | if (args[3] != null) { 227 | var sig = Memory.readCString(args[3]); 228 | console.log("[GetStaticMethodID] class_name:" + class_name + " name:" + name + ", sig:" + sig, DebugSymbol.fromAddress(this.returnAddress)); 229 | } else { 230 | console.log("[GetStaticMethodID] class_name:" + class_name + " name:" + name, DebugSymbol.fromAddress(this.returnAddress)); 231 | } 232 | } 233 | } 234 | }, 235 | onLeave: function (retval) { } 236 | }); 237 | } 238 | if (addrGetFieldID != null) { 239 | Interceptor.attach(addrGetFieldID, { 240 | onEnter: function (args) { 241 | if (args[2] != null) { 242 | var module = Process.findModuleByAddress(this.returnAddress); 243 | if (module != null && module.name.indexOf(so_name) == 0) { 244 | var name = Memory.readCString(args[2]); 245 | if (args[3] != null) { 246 | var sig = Memory.readCString(args[3]); 247 | console.log("[GetFieldID] name:" + name + ", sig:" + sig, DebugSymbol.fromAddress(this.returnAddress)); 248 | } else { 249 | console.log("[GetFieldID] name:" + name, DebugSymbol.fromAddress(this.returnAddress)); 250 | } 251 | } 252 | } 253 | }, 254 | onLeave: function (retval) { } 255 | }); 256 | } 257 | if (addrGetStaticFieldID != null) { 258 | Interceptor.attach(addrGetStaticFieldID, { 259 | onEnter: function (args) { 260 | if (args[2] != null) { 261 | var module = Process.findModuleByAddress(this.returnAddress); 262 | if (module != null && module.name.indexOf(so_name) == 0) { 263 | var name = Memory.readCString(args[2]); 264 | if (args[3] != null) { 265 | var sig = Memory.readCString(args[3]); 266 | console.log("[GetStaticFieldID] name:" + name + ", sig:" + sig, DebugSymbol.fromAddress(this.returnAddress)); 267 | } else { 268 | console.log("[GetStaticFieldID] name:" + name, DebugSymbol.fromAddress(this.returnAddress)); 269 | } 270 | } 271 | } 272 | }, 273 | onLeave: function (retval) { } 274 | }); 275 | } 276 | 277 | if (addrRegisterNatives != null) { 278 | Interceptor.attach(addrRegisterNatives, { 279 | onEnter: function (args) { 280 | console.log("[RegisterNatives] method_count:", args[3], DebugSymbol.fromAddress(this.returnAddress)); 281 | var env = args[0]; 282 | var java_class = args[1]; 283 | var class_name = Java.vm.tryGetEnv().getClassName(java_class); 284 | 285 | var methods_ptr = ptr(args[2]); 286 | 287 | var method_count = parseInt(args[3]); 288 | for (var i = 0; i < method_count; i++) { 289 | var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3)); 290 | var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize)); 291 | var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2)); 292 | 293 | var name = Memory.readCString(name_ptr); 294 | var sig = Memory.readCString(sig_ptr); 295 | var find_module = Process.findModuleByAddress(fnPtr_ptr); 296 | console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base)); 297 | 298 | } 299 | }, 300 | onLeave: function (retval) { } 301 | }); 302 | } 303 | } 304 | 305 | setImmediate(hook_libart); 306 | 307 | -------------------------------------------------------------------------------- /hook_artmethod.js: -------------------------------------------------------------------------------- 1 | 2 | const STD_STRING_SIZE = 3 * Process.pointerSize; 3 | class StdString { 4 | constructor() { 5 | this.handle = Memory.alloc(STD_STRING_SIZE); 6 | } 7 | 8 | dispose() { 9 | const [data, isTiny] = this._getData(); 10 | if (!isTiny) { 11 | Java.api.$delete(data); 12 | } 13 | } 14 | 15 | disposeToString() { 16 | const result = this.toString(); 17 | this.dispose(); 18 | return result; 19 | } 20 | 21 | toString() { 22 | const [data] = this._getData(); 23 | return data.readUtf8String(); 24 | } 25 | 26 | _getData() { 27 | const str = this.handle; 28 | const isTiny = (str.readU8() & 1) === 0; 29 | const data = isTiny ? str.add(1) : str.add(2 * Process.pointerSize).readPointer(); 30 | return [data, isTiny]; 31 | } 32 | } 33 | 34 | function prettyMethod(method_id, withSignature) { 35 | const result = new StdString(); 36 | Java.api['art::ArtMethod::PrettyMethod'](result, method_id, withSignature ? 1 : 0); 37 | return result.disposeToString(); 38 | } 39 | 40 | function hook_dlopen(module_name, fun) { 41 | var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext"); 42 | 43 | if (android_dlopen_ext) { 44 | Interceptor.attach(android_dlopen_ext, { 45 | onEnter: function (args) { 46 | var pathptr = args[0]; 47 | if (pathptr) { 48 | this.path = (pathptr).readCString(); 49 | if (this.path.indexOf(module_name) >= 0) { 50 | this.canhook = true; 51 | console.log("android_dlopen_ext:", this.path); 52 | } 53 | } 54 | }, 55 | onLeave: function (retval) { 56 | if (this.canhook) { 57 | fun(); 58 | } 59 | } 60 | }); 61 | } 62 | var dlopen = Module.findExportByName(null, "dlopen"); 63 | if (dlopen) { 64 | Interceptor.attach(dlopen, { 65 | onEnter: function (args) { 66 | var pathptr = args[0]; 67 | if (pathptr) { 68 | this.path = (pathptr).readCString(); 69 | if (this.path.indexOf(module_name) >= 0) { 70 | this.canhook = true; 71 | console.log("dlopen:", this.path); 72 | } 73 | } 74 | }, 75 | onLeave: function (retval) { 76 | if (this.canhook) { 77 | fun(); 78 | } 79 | } 80 | }); 81 | } 82 | console.log("android_dlopen_ext:", android_dlopen_ext, "dlopen:", dlopen); 83 | } 84 | 85 | 86 | function hook_native() { 87 | var module_libart = Process.findModuleByName("libart.so"); 88 | var symbols = module_libart.enumerateSymbols(); 89 | var ArtMethod_Invoke = null; 90 | for (var i = 0; i < symbols.length; i++) { 91 | var symbol = symbols[i]; 92 | var address = symbol.address; 93 | var name = symbol.name; 94 | var indexArtMethod = name.indexOf("ArtMethod"); 95 | var indexInvoke = name.indexOf("Invoke"); 96 | var indexThread = name.indexOf("Thread"); 97 | if (indexArtMethod >= 0 98 | && indexInvoke >= 0 99 | && indexThread >= 0 100 | && indexArtMethod < indexInvoke 101 | && indexInvoke < indexThread) { 102 | console.log(name); 103 | ArtMethod_Invoke = address; 104 | } 105 | } 106 | if (ArtMethod_Invoke) { 107 | Interceptor.attach(ArtMethod_Invoke, { 108 | onEnter: function (args) { 109 | var method_name = prettyMethod(args[0], 0); 110 | if (!(method_name.indexOf("java.") == 0 || method_name.indexOf("android.") == 0)) { 111 | console.log("ArtMethod Invoke:" + method_name + ' called from:\n' + 112 | Thread.backtrace(this.context, Backtracer.ACCURATE) 113 | .map(DebugSymbol.fromAddress).join('\n') + '\n'); 114 | } 115 | } 116 | }); 117 | } 118 | } 119 | 120 | function main() { 121 | hook_dlopen("libart.so", hook_native); 122 | hook_native(); 123 | } 124 | 125 | setImmediate(main); -------------------------------------------------------------------------------- /hook_ssl.js: -------------------------------------------------------------------------------- 1 | function hook_ssl() { 2 | Java.perform(function () { 3 | var ClassName = "com.android.org.conscrypt.Platform"; 4 | var Platform = Java.use(ClassName); 5 | var targetMethod = "checkServerTrusted"; 6 | var len = Platform[targetMethod].overloads.length; 7 | console.log(len); 8 | for (var i = 0; i < len; ++i) { 9 | Platform[targetMethod].overloads[i].implementation = function () { 10 | console.log("class:", ClassName, "target:", targetMethod, " i:", i, arguments); 11 | }; 12 | } 13 | }); 14 | } 15 | 16 | setTimeout(hook_ssl, 100); -------------------------------------------------------------------------------- /hook_ssl2.js: -------------------------------------------------------------------------------- 1 | 2 | function hook_ssl() { 3 | var cert_dex = Java.openClassFile("/data/local/tmp/certs.dex"); 4 | 5 | Java.perform(function () { 6 | cert_dex.load(); 7 | var certs = Java.use("com.example.certs"); 8 | var ClassName = "com.android.org.conscrypt.Platform"; 9 | var Platform = Java.use(ClassName); 10 | var targetMethod = "checkServerTrusted"; 11 | var len = Platform[targetMethod].overloads.length; 12 | console.log("checkServerTrusted overloads:", len); 13 | Platform.checkServerTrusted.overload('javax.net.ssl.X509TrustManager', '[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'com.android.org.conscrypt.ConscryptEngine').implementation = 14 | function(tm, chain, authType, engine) { 15 | var result = this.checkServerTrusted(tm, chain, authType, engine); 16 | console.log("checkServerTrusted 1 authType:", authType, " engine:", engine); 17 | return result; 18 | }; 19 | 20 | Platform.checkServerTrusted.overload('javax.net.ssl.X509TrustManager', '[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'com.android.org.conscrypt.AbstractConscryptSocket').implementation = 21 | function(tm, chain, authType, socket) { 22 | var s = socket.toString(); 23 | var addr = s.substring(s.indexOf("[") + 1, s.indexOf(",")); 24 | var host = addr.split("=")[1].split("/")[0]; 25 | if (host == "") { 26 | host = addr.split("=")[1].split("/")[1]; 27 | } 28 | var r = certs.save_cert(host, chain); 29 | console.log("checkServerTrusted 2 authType:", authType, " socket:", socket, host); 30 | var tmp_chain = certs.get_cert(host); 31 | var result = this.checkServerTrusted(tm, tmp_chain, authType, socket); 32 | return result; 33 | }; 34 | 35 | Platform.checkClientTrusted.overload('javax.net.ssl.X509TrustManager', '[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'com.android.org.conscrypt.ConscryptEngine').implementation = 36 | function(tm, chain, authType, engine) { 37 | var result = this.checkClientTrusted(tm, chain, authType, engine); 38 | console.log("checkClientTrusted 1 authType:", authType, " engine:", engine); 39 | return result; 40 | }; 41 | 42 | Platform.checkClientTrusted.overload('javax.net.ssl.X509TrustManager', '[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'com.android.org.conscrypt.AbstractConscryptSocket').implementation = 43 | function(tm, chain, authType, socket) { 44 | var result = this.checkClientTrusted(tm, chain, authType, socket); 45 | console.log("checkClientTrusted 2 authType:", authType, " socket:", socket); 46 | return result; 47 | }; 48 | 49 | }); 50 | } 51 | 52 | setTimeout(hook_ssl, 100); -------------------------------------------------------------------------------- /hook_vpn.js: -------------------------------------------------------------------------------- 1 | function main(){ 2 | Java.perform(function (){ 3 | Java.use("android.net.NetworkInfo").isConnected.implementation = function(){ 4 | console.log("first called!") 5 | return false 6 | } 7 | 8 | Java.use("java.net.NetworkInterface").getName.implementation = function(){ 9 | console.log("second called!") 10 | return "" 11 | // return null 12 | } 13 | 14 | Java.use("android.net.NetworkCapabilities").hasTransport.implementation=function(){ 15 | console.log("third called!") 16 | return false 17 | } 18 | //android.net.ConnectivityManager.getNetworkCapabilities 19 | }) 20 | } 21 | 22 | setImmediate(main) -------------------------------------------------------------------------------- /hook_vpn_new.js: -------------------------------------------------------------------------------- 1 | function main() { 2 | Java.perform(function () { 3 | var NetworkInfo = Java.use("android.net.NetworkInfo") 4 | NetworkInfo.isConnected.implementation = function(){ 5 | console.log("first called!") 6 | var result = this.isConnected() 7 | console.log('result',result) 8 | return false 9 | // return result 10 | } 11 | var network = Java.use('java.net.NetworkInterface') 12 | network.getName.implementation = function () { 13 | console.log("second called!") 14 | var name = this.getName() 15 | console.log('name:' + name) 16 | if (name == "tun0") { 17 | // var result = Java.use('java.lang.String').$new('rmnet_data0') 18 | var result = Java.use('java.lang.String').$new('ccmni1') 19 | console.log('hook result:' + result) 20 | return result 21 | } else { 22 | return name 23 | } 24 | } 25 | 26 | var NetworkCapabilities = Java.use("android.net.NetworkCapabilities") 27 | NetworkCapabilities.hasTransport.overload('int').implementation = function (arg) { 28 | console.log("third called!",arg) 29 | var result = this.hasTransport(arg) 30 | console.log('result',result) 31 | // return result 32 | return false 33 | } 34 | var ConnectivityManager = Java.use("android.net.ConnectivityManager") 35 | ConnectivityManager.getNetworkCapabilities.implementation = function (arg) { 36 | console.log("four called!",arg) 37 | var result = this.getNetworkCapabilities(arg) 38 | console.log('result',result) 39 | return result 40 | // return false 41 | } 42 | // android.net.ConnectivityManager.getNetworkCapabilities 43 | }) 44 | } 45 | 46 | setImmediate(main,1000) 47 | 48 | 49 | -------------------------------------------------------------------------------- /hookflutter.js: -------------------------------------------------------------------------------- 1 | // frida -U -f cn.missfresh.application -l ms.js --no-pause 2 | 3 | function Where(stack){ 4 | var at = "" 5 | for(var i = 0; i < stack.length; ++i){ 6 | at += stack[i].toString() + "\n" 7 | } 8 | return at 9 | } 10 | 11 | function hook_libart() { 12 | var symbols = Module.enumerateSymbolsSync("libart.so"); 13 | var addrGetStringUTFChars = null; 14 | var addrNewStringUTF = null; 15 | var addrFindClass = null; 16 | var addrGetMethodID = null; 17 | var addrGetStaticMethodID = null; 18 | var addrGetFieldID = null; 19 | var addrGetStaticFieldID = null; 20 | var addrRegisterNatives = null; 21 | for (var i = 0; i < symbols.length; i++) { 22 | var symbol = symbols[i]; 23 | if (symbol.name.indexOf("art") >= 0 && 24 | symbol.name.indexOf("JNI") >= 0 && 25 | symbol.name.indexOf("CheckJNI") < 0 26 | ) { 27 | if (symbol.name.indexOf("GetStringUTFChars") >= 0) { 28 | addrGetStringUTFChars = symbol.address; 29 | console.log("GetStringUTFChars is at ", symbol.address, symbol.name); 30 | } else if (symbol.name.indexOf("NewStringUTF") >= 0) { 31 | addrNewStringUTF = symbol.address; 32 | console.log("NewStringUTF is at ", symbol.address, symbol.name); 33 | } else if (symbol.name.indexOf("FindClass") >= 0) { 34 | // addrFindClass = symbol.address; 35 | console.log("FindClass is at ", symbol.address, symbol.name); 36 | } else if (symbol.name.indexOf("GetMethodID") >= 0) { 37 | // addrGetMethodID = symbol.address; 38 | console.log("GetMethodID is at ", symbol.address, symbol.name); 39 | } else if (symbol.name.indexOf("GetStaticMethodID") >= 0) { 40 | // addrGetStaticMethodID = symbol.address; 41 | console.log("GetStaticMethodID is at ", symbol.address, symbol.name); 42 | } else if (symbol.name.indexOf("GetFieldID") >= 0) { 43 | // addrGetFieldID = symbol.address; 44 | console.log("GetFieldID is at ", symbol.address, symbol.name); 45 | } else if (symbol.name.indexOf("GetStaticFieldID") >= 0) { 46 | // addrGetStaticFieldID = symbol.address; 47 | console.log("GetStaticFieldID is at ", symbol.address, symbol.name); 48 | } else if (symbol.name.indexOf("RegisterNatives") >= 0) { 49 | // addrRegisterNatives = symbol.address; 50 | console.log("RegisterNatives is at ", symbol.address, symbol.name); 51 | } 52 | } 53 | } 54 | 55 | if (addrGetStringUTFChars != null) { 56 | Interceptor.attach(addrGetStringUTFChars, { 57 | onEnter: function (args) {}, 58 | onLeave: function (retval) { 59 | if (retval != null) { 60 | var bytes = Memory.readCString(retval); 61 | if(bytes != null) { 62 | if(bytes.toString().indexOf("mfsnm") >= 0 ) 63 | { 64 | console.log("[GetStringUTFChars] result:" + bytes); 65 | } 66 | } 67 | 68 | } 69 | } 70 | }); 71 | } 72 | if (addrNewStringUTF != null) { 73 | Interceptor.attach(addrNewStringUTF, { 74 | onEnter: function (args) { 75 | if (args[1] != null) { 76 | var string = Memory.readCString(args[1]); 77 | 78 | if(string != null) { 79 | if(string.toString().indexOf("mfsnm") >= 0 ) 80 | { 81 | console.log("[NewStringUTF] bytes:" + string); 82 | 83 | var threadef = Java.use('java.lang.Thread'); 84 | var threadinstance = threadef.$new(); 85 | 86 | var stack = threadinstance.currentThread().getStackTrace(); 87 | console.log("Rc Full call stack:" + Where(stack)); 88 | 89 | console.log(Thread.backtrace(this.context, Backtracer.FUZZY) 90 | .map(DebugSymbol.fromAddress).join("\n")) 91 | 92 | } 93 | } 94 | 95 | // if(string.toString().length > 50 && string.toString().length < 100 ){ 96 | 97 | // var threadef = Java.use('java.lang.Thread'); 98 | // var threadinstance = threadef.$new(); 99 | 100 | // var stack = threadinstance.currentThread().getStackTrace(); 101 | // console.log("Rc Full call stack:" + Where(stack)); 102 | 103 | 104 | // console.log(Thread.backtrace(this.context, Backtracer.FUZZY) 105 | // .map(DebugSymbol.fromAddress).join("\n")) 106 | 107 | // } 108 | } 109 | }, 110 | onLeave: function (retval) {} 111 | }); 112 | } 113 | if (addrFindClass != null) { 114 | Interceptor.attach(addrFindClass, { 115 | onEnter: function (args) { 116 | if (args[1] != null) { 117 | var name = Memory.readCString(args[1]); 118 | console.log("[FindClass] name:" + name); 119 | } 120 | }, 121 | onLeave: function (retval) {} 122 | }); 123 | } 124 | if (addrGetMethodID != null) { 125 | Interceptor.attach(addrGetMethodID, { 126 | onEnter: function (args) { 127 | if (args[2] != null) { 128 | var name = Memory.readCString(args[2]); 129 | if (args[3] != null) { 130 | var sig = Memory.readCString(args[3]); 131 | console.log("[GetMethodID] name:" + name + ", sig:" + sig); 132 | } else { 133 | console.log("[GetMethodID] name:" + name); 134 | } 135 | 136 | } 137 | }, 138 | onLeave: function (retval) {} 139 | }); 140 | } 141 | if (addrGetStaticMethodID != null) { 142 | interceptor.attach(addrGetStaticMethodID, { 143 | onEnter: function (args) { 144 | if (args[2] != null) { 145 | var name = Memory.readCString(args[2]); 146 | if (args[3] != null) { 147 | var sig = Memory.readCString(args[3]); 148 | console.log("[GetStaticMethodID] name:" + name + ", sig:" + sig); 149 | } else { 150 | console.log("[GetStaticMethodID] name:" + name); 151 | } 152 | 153 | } 154 | }, 155 | onLeave: function (retval) {} 156 | }); 157 | } 158 | if (addrGetFieldID != null) { 159 | Interceptor.attach(addrGetFieldID, { 160 | onEnter: function (args) { 161 | if (args[2] != null) { 162 | var name = Memory.readCString(args[2]); 163 | if (args[3] != null) { 164 | var sig = Memory.readCString(args[3]); 165 | console.log("[GetFieldID] name:" + name + ", sig:" + sig); 166 | } else { 167 | console.log("[GetFieldID] name:" + name); 168 | } 169 | 170 | } 171 | }, 172 | onLeave: function (retval) {} 173 | }); 174 | } 175 | if (addrGetStaticFieldID != null) { 176 | Interceptor.attach(addrGetStaticFieldID, { 177 | onEnter: function (args) { 178 | if (args[2] != null) { 179 | var name = Memory.readCString(args[2]); 180 | if (args[3] != null) { 181 | var sig = Memory.readCString(args[3]); 182 | console.log("[GetStaticFieldID] name:" + name + ", sig:" + sig); 183 | } else { 184 | console.log("[GetStaticFieldID] name:" + name); 185 | } 186 | 187 | } 188 | }, 189 | onLeave: function (retval) {} 190 | }); 191 | } 192 | 193 | if (addrRegisterNatives != null) { 194 | Interceptor.attach(addrRegisterNatives, { 195 | onEnter: function (args) { 196 | console.log("[RegisterNatives] method_count:", args[3]); 197 | var env = args[0]; 198 | var java_class = args[1]; 199 | var class_name = Java.vm.tryGetEnv().getClassName(java_class); 200 | 201 | var methods_ptr = ptr(args[2]); 202 | 203 | var method_count = parseInt(args[3]); 204 | for (var i = 0; i < method_count; i++) { 205 | var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3)); 206 | var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize)); 207 | var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2)); 208 | 209 | var name = Memory.readCString(name_ptr); 210 | var sig = Memory.readCString(sig_ptr); 211 | var find_module = Process.findModuleByAddress(fnPtr_ptr); 212 | console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base)); 213 | 214 | } 215 | }, 216 | onLeave: function (retval) {} 217 | }); 218 | } 219 | } 220 | 221 | function hook_ssl_verify_result(address) 222 | { 223 | Interceptor.attach(address, { 224 | onEnter: function(args) { 225 | console.log("Disabling SSL validation") 226 | }, 227 | onLeave: function(retval) 228 | { 229 | console.log("Retval: " + retval) 230 | retval.replace(0x1); 231 | } 232 | }); 233 | 234 | } 235 | 236 | // ssl_client 237 | function hookFlutter() { 238 | 239 | var m = Process.findModuleByName("libflutter.so"); 240 | console.log(m.base); 241 | 242 | var pattern = "FF C3 01 D1 FD 7B 01 A9 FC 6F 02 A9 FA 67 03 A9 F8 5F 04 A9 F6 57 05 A9 F4 4F 06 A9 08 0A 80 52 48 00 00 39"; 243 | // var pattern = "FF C3 01 D1 FD 7B 01 A9 FC 6F 02 A9 FA 67 03 A9 F8 5F 04 A9 F6 57 05 A9 F4 4F 06 A9"; 244 | 245 | var res = Memory.scan(m.base, m.size, pattern, { 246 | 247 | onMatch: function(address, size){ 248 | 249 | console.log('[+] ssl_verify_result found at: ' + address.toString()); 250 | 251 | hook_ssl_verify_result(address); 252 | 253 | }, 254 | 255 | onError: function(reason){ 256 | 257 | console.log('[!] There was an error scanning memory'); 258 | 259 | }, 260 | 261 | onComplete: function() { 262 | 263 | console.log("All done") 264 | 265 | } 266 | 267 | }); 268 | 269 | } 270 | 271 | function disablePinning() 272 | { 273 | var m = Process.findModuleByName("libflutter.so"); 274 | console.log(m); 275 | 276 | var pattern = "2D E9 F0 4F 85 B0 06 46 50 20 10 70" 277 | 278 | var res = Memory.scan(m.base, m.size, pattern, { 279 | onMatch: function(address, size){ 280 | console.log('[+] ssl_verify_result found at: ' + address.toString()); 281 | // Add 0x01 because it's a THUMB function 282 | hook_ssl_verify_result(address.add(0x01)); 283 | }, 284 | 285 | onError: function(reason){ 286 | console.log('[!] There was an error scanning memory'); 287 | }, 288 | onComplete: function() 289 | { 290 | console.log("All done") 291 | } 292 | }); 293 | } 294 | 295 | // setImmediate(function(){ 296 | 297 | function main() { 298 | // /* 299 | Java.perform(function () { 300 | var s = Java.use('ob.a0') 301 | s.b.implementation = function () { 302 | console.log('------>') 303 | this.b() 304 | } 305 | // 64位用这个 306 | hookFlutter(); 307 | 308 | // 32位用这个 309 | // disablePinning(); 310 | 311 | }); 312 | // */ 313 | 314 | } 315 | 316 | // ); 317 | 318 | // App加壳了,所以加个时间延迟 319 | setTimeout(main, 10000); 320 | -------------------------------------------------------------------------------- /myhexdump.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # !/usr/bin/env python 3 | # -*- coding: latin-1 -*- 4 | 5 | # <-- removing this magic comment breaks Python 3.4 on Windows 6 | """ 7 | 1. Dump binary data to the following text format: 8 | 9 | 00000000: 00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00 ...[hexdump].... 10 | 00000010: 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........ 11 | 12 | It is similar to the one used by: 13 | Scapy 14 | 00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00 ...[hexdump].... 15 | 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF .."3DUfw........ 16 | 17 | Far Manager 18 | 000000000: 00 00 00 5B 68 65 78 64 ¦ 75 6D 70 5D 00 00 00 00 [hexdump] 19 | 000000010: 00 11 22 33 44 55 66 77 ¦ 88 99 AA BB CC DD EE FF ?"3DUfwˆ™ª»ÌÝîÿ 20 | 21 | 22 | 2. Restore binary data from the formats above as well 23 | as from less exotic strings of raw hex 24 | 25 | """ 26 | 27 | __version__ = '3.3' 28 | __author__ = 'anatoly techtonik ' 29 | __license__ = 'Public Domain' 30 | 31 | __history__ = \ 32 | """ 33 | 3.3 (2015-01-22) 34 | * accept input from sys.stdin if "-" is specified 35 | for both dump and restore (issue #1) 36 | * new normalize_py() helper to set sys.stdout to 37 | binary mode on Windows 38 | 39 | 3.2 (2015-07-02) 40 | * hexdump is now packaged as .zip on all platforms 41 | (on Linux created archive was tar.gz) 42 | * .zip is executable! try `python hexdump-3.2.zip` 43 | * dump() now accepts configurable separator, patch 44 | by Ian Land (PR #3) 45 | 46 | 3.1 (2014-10-20) 47 | * implemented workaround against mysterious coding 48 | issue with Python 3 (see revision 51302cf) 49 | * fix Python 3 installs for systems where UTF-8 is 50 | not default (Windows), thanks to George Schizas 51 | (the problem was caused by reading of README.txt) 52 | 53 | 3.0 (2014-09-07) 54 | * remove unused int2byte() helper 55 | * add dehex(text) helper to convert hex string 56 | to binary data 57 | * add 'size' argument to dump() helper to specify 58 | length of chunks 59 | 60 | 2.0 (2014-02-02) 61 | * add --restore option to command line mode to get 62 | binary data back from hex dump 63 | * support saving test output with `--test logfile` 64 | * restore() from hex strings without spaces 65 | * restore() now raises TypeError if input data is 66 | not string 67 | * hexdump() and dumpgen() now don't return unicode 68 | strings in Python 2.x when generator is requested 69 | 70 | 1.0 (2013-12-30) 71 | * length of address is reduced from 10 to 8 72 | * hexdump() got new 'result' keyword argument, it 73 | can be either 'print', 'generator' or 'return' 74 | * actual dumping logic is now in new dumpgen() 75 | generator function 76 | * new dump(binary) function that takes binary data 77 | and returns string like "66 6F 72 6D 61 74" 78 | * new genchunks(mixed, size) function that chunks 79 | both sequences and file like objects 80 | 81 | 0.5 (2013-06-10) 82 | * hexdump is now also a command line utility (no 83 | restore yet) 84 | 85 | 0.4 (2013-06-09) 86 | * fix installation with Python 3 for non English 87 | versions of Windows, thanks to George Schizas 88 | 89 | 0.3 (2013-04-29) 90 | * fully Python 3 compatible 91 | 92 | 0.2 (2013-04-28) 93 | * restore() to recover binary data from a hex dump in 94 | native, Far Manager and Scapy text formats (others 95 | might work as well) 96 | * restore() is Python 3 compatible 97 | 98 | 0.1 (2013-04-28) 99 | * working hexdump() function for Python 2 100 | """ 101 | 102 | import binascii # binascii is required for Python 3 103 | import sys 104 | 105 | # --- constants 106 | PY3K = sys.version_info >= (3, 0) 107 | 108 | 109 | # --- workaround against Python consistency issues 110 | def normalize_py(): 111 | ''' Problem 001 - sys.stdout in Python is by default opened in 112 | text mode, and writes to this stdout produce corrupted binary 113 | data on Windows 114 | 115 | python -c "import sys; sys.stdout.write('_\n_')" > file 116 | python -c "print(repr(open('file', 'rb').read()))" 117 | ''' 118 | if sys.platform == "win32": 119 | # set sys.stdout to binary mode on Windows 120 | import os, msvcrt 121 | msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) 122 | 123 | 124 | # --- - chunking helpers 125 | def chunks(seq, size): 126 | '''Generator that cuts sequence (bytes, memoryview, etc.) 127 | into chunks of given size. If `seq` length is not multiply 128 | of `size`, the lengh of the last chunk returned will be 129 | less than requested. 130 | 131 | >>> list( chunks([1,2,3,4,5,6,7], 3) ) 132 | [[1, 2, 3], [4, 5, 6], [7]] 133 | ''' 134 | d, m = divmod(len(seq), size) 135 | for i in range(d): 136 | yield seq[i * size:(i + 1) * size] 137 | if m: 138 | yield seq[d * size:] 139 | 140 | 141 | def chunkread(f, size): 142 | '''Generator that reads from file like object. May return less 143 | data than requested on the last read.''' 144 | c = f.read(size) 145 | while len(c): 146 | yield c 147 | c = f.read(size) 148 | 149 | 150 | def genchunks(mixed, size): 151 | '''Generator to chunk binary sequences or file like objects. 152 | The size of the last chunk returned may be less than 153 | requested.''' 154 | if hasattr(mixed, 'read'): 155 | return chunkread(mixed, size) 156 | else: 157 | return chunks(mixed, size) 158 | 159 | 160 | # --- - /chunking helpers 161 | 162 | 163 | def dehex(hextext): 164 | """ 165 | Convert from hex string to binary data stripping 166 | whitespaces from `hextext` if necessary. 167 | """ 168 | if PY3K: 169 | return bytes.fromhex(hextext) 170 | else: 171 | hextext = "".join(hextext.split()) 172 | return hextext.decode('hex') 173 | 174 | 175 | def dump(binary, size=2, sep=' '): 176 | ''' 177 | Convert binary data (bytes in Python 3 and str in 178 | Python 2) to hex string like '00 DE AD BE EF'. 179 | `size` argument specifies length of text chunks 180 | and `sep` sets chunk separator. 181 | ''' 182 | hexstr = binascii.hexlify(binary) 183 | if PY3K: 184 | hexstr = hexstr.decode('ascii') 185 | return sep.join(chunks(hexstr.upper(), size)) 186 | 187 | 188 | def dumpgen(data, only_str): 189 | ''' 190 | Generator that produces strings: 191 | 192 | '00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................' 193 | ''' 194 | generator = genchunks(data, 16) 195 | for addr, d in enumerate(generator): 196 | line = "" 197 | if not only_str: 198 | # 00000000: 199 | line = '%08X: ' % (addr * 16) 200 | # 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 201 | dumpstr = dump(d) 202 | line += dumpstr[:8 * 3] 203 | if len(d) > 8: # insert separator if needed 204 | line += ' ' + dumpstr[8 * 3:] 205 | # ................ 206 | # calculate indentation, which may be different for the last line 207 | pad = 2 208 | if len(d) < 16: 209 | pad += 3 * (16 - len(d)) 210 | if len(d) <= 8: 211 | pad += 1 212 | line += ' ' * pad 213 | 214 | for byte in d: 215 | # printable ASCII range 0x20 to 0x7E 216 | if not PY3K: 217 | byte = ord(byte) 218 | if 0x20 <= byte <= 0x7E: 219 | line += chr(byte) 220 | else: 221 | line += '.' 222 | yield line 223 | 224 | 225 | def hexdump(data, result='print', only_str=False): 226 | ''' 227 | Transform binary data to the hex dump text format: 228 | 229 | 00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 230 | 231 | [x] data argument as a binary string 232 | [x] data argument as a file like object 233 | 234 | Returns result depending on the `result` argument: 235 | 'print' - prints line by line 236 | 'return' - returns single string 237 | 'generator' - returns generator that produces lines 238 | ''' 239 | if PY3K and type(data) == str: 240 | raise TypeError('Abstract unicode data (expected bytes sequence)') 241 | 242 | gen = dumpgen(data, only_str=only_str) 243 | if result == 'generator': 244 | return gen 245 | elif result == 'return': 246 | return '\n'.join(gen) 247 | elif result == 'print': 248 | for line in gen: 249 | print(line) 250 | else: 251 | raise ValueError('Unknown value of `result` argument') 252 | 253 | 254 | def restore(dump): 255 | ''' 256 | Restore binary data from a hex dump. 257 | [x] dump argument as a string 258 | [ ] dump argument as a line iterator 259 | 260 | Supported formats: 261 | [x] hexdump.hexdump 262 | [x] Scapy 263 | [x] Far Manager 264 | ''' 265 | minhexwidth = 2 * 16 # minimal width of the hex part - 00000... style 266 | bytehexwidth = 3 * 16 - 1 # min width for a bytewise dump - 00 00 ... style 267 | 268 | result = bytes() if PY3K else '' 269 | if type(dump) != str: 270 | raise TypeError('Invalid data for restore') 271 | 272 | text = dump.strip() # ignore surrounding empty lines 273 | for line in text.split('\n'): 274 | # strip address part 275 | addrend = line.find(':') 276 | if 0 < addrend < minhexwidth: # : is not in ascii part 277 | line = line[addrend + 1:] 278 | line = line.lstrip() 279 | # check dump type 280 | if line[2] == ' ': # 00 00 00 ... type of dump 281 | # check separator 282 | sepstart = (2 + 1) * 7 + 2 # ('00'+' ')*7+'00' 283 | sep = line[sepstart:sepstart + 3] 284 | if sep[:2] == ' ' and sep[2:] != ' ': # ...00 00 00 00... 285 | hexdata = line[:bytehexwidth + 1] 286 | elif sep[2:] == ' ': # ...00 00 | 00 00... - Far Manager 287 | hexdata = line[:sepstart] + line[sepstart + 3:bytehexwidth + 2] 288 | else: # ...00 00 00 00... - Scapy, no separator 289 | hexdata = line[:bytehexwidth] 290 | line = hexdata 291 | result += dehex(line) 292 | return result 293 | 294 | 295 | def runtest(logfile=None): 296 | '''Run hexdump tests. Requires hexfile.bin to be in the same 297 | directory as hexdump.py itself''' 298 | 299 | class TeeOutput(object): 300 | def __init__(self, stream1, stream2): 301 | self.outputs = [stream1, stream2] 302 | 303 | # -- methods from sys.stdout / sys.stderr 304 | def write(self, data): 305 | for stream in self.outputs: 306 | if PY3K: 307 | if 'b' in stream.mode: 308 | data = data.encode('utf-8') 309 | stream.write(data) 310 | stream.flush() 311 | 312 | def tell(self): 313 | raise IOError 314 | 315 | def flush(self): 316 | for stream in self.outputs: 317 | stream.flush() 318 | # --/ sys.stdout 319 | 320 | if logfile: 321 | openlog = open(logfile, 'wb') 322 | # copy stdout and stderr streams to log file 323 | savedstd = sys.stderr, sys.stdout 324 | sys.stderr = TeeOutput(sys.stderr, openlog) 325 | sys.stdout = TeeOutput(sys.stdout, openlog) 326 | 327 | def echo(msg, linefeed=True): 328 | sys.stdout.write(msg) 329 | if linefeed: 330 | sys.stdout.write('\n') 331 | 332 | expected = '''\ 333 | 00000000: 00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00 ...[hexdump].... 334 | 00000010: 00 11 22 33 44 55 66 77 88 99 0A BB CC DD EE FF .."3DUfw........\ 335 | ''' 336 | 337 | # get path to hexfile.bin 338 | # this doesn't work from .zip 339 | # import os.path as osp 340 | # hexfile = osp.dirname(osp.abspath(__file__)) + '/hexfile.bin' 341 | # this doesn't work either 342 | # hexfile = osp.dirname(sys.modules[__name__].__file__) + '/hexfile.bin' 343 | # this works 344 | import pkgutil 345 | bin = pkgutil.get_data('hexdump', 'data/hexfile.bin') 346 | 347 | # varios length of input data 348 | hexdump(b'zzzz' * 12) 349 | hexdump(b'o' * 17) 350 | hexdump(b'p' * 24) 351 | hexdump(b'q' * 26) 352 | # allowable character set filter 353 | hexdump(b'line\nfeed\r\ntest') 354 | hexdump(b'\x00\x00\x00\x5B\x68\x65\x78\x64\x75\x6D\x70\x5D\x00\x00\x00\x00' 355 | b'\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\x0A\xBB\xCC\xDD\xEE\xFF') 356 | print('---') 357 | # dumping file-like binary object to screen (default behavior) 358 | hexdump(bin) 359 | print('return output') 360 | hexout = hexdump(bin, result='return') 361 | assert hexout == expected, 'returned hex didn\'t match' 362 | print('return generator') 363 | hexgen = hexdump(bin, result='generator') 364 | assert next(hexgen) == expected.split('\n')[0], 'hex generator 1 didn\'t match' 365 | assert next(hexgen) == expected.split('\n')[1], 'hex generator 2 didn\'t match' 366 | 367 | # binary restore test 368 | bindata = restore( 369 | ''' 370 | 00000000: 00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00 ...[hexdump].... 371 | 00000010: 00 11 22 33 44 55 66 77 88 99 0A BB CC DD EE FF .."3DUfw........ 372 | ''') 373 | echo('restore check ', linefeed=False) 374 | assert bin == bindata, 'restore check failed' 375 | echo('passed') 376 | 377 | far = \ 378 | ''' 379 | 000000000: 00 00 00 5B 68 65 78 64 ¦ 75 6D 70 5D 00 00 00 00 [hexdump] 380 | 000000010: 00 11 22 33 44 55 66 77 ¦ 88 99 0A BB CC DD EE FF ?"3DUfwˆ™ª»ÌÝîÿ 381 | ''' 382 | echo('restore far format ', linefeed=False) 383 | assert bin == restore(far), 'far format check failed' 384 | echo('passed') 385 | 386 | scapy = '''\ 387 | 00 00 00 5B 68 65 78 64 75 6D 70 5D 00 00 00 00 ...[hexdump].... 388 | 00 11 22 33 44 55 66 77 88 99 0A BB CC DD EE FF .."3DUfw........ 389 | ''' 390 | echo('restore scapy format ', linefeed=False) 391 | assert bin == restore(scapy), 'scapy format check failed' 392 | echo('passed') 393 | 394 | if not PY3K: 395 | assert restore('5B68657864756D705D') == '[hexdump]', 'no space check failed' 396 | assert dump('\\\xa1\xab\x1e', sep='').lower() == '5ca1ab1e' 397 | else: 398 | assert restore('5B68657864756D705D') == b'[hexdump]', 'no space check failed' 399 | assert dump(b'\\\xa1\xab\x1e', sep='').lower() == '5ca1ab1e' 400 | 401 | print('---[test file hexdumping]---') 402 | 403 | import os 404 | import tempfile 405 | hexfile = tempfile.NamedTemporaryFile(delete=False) 406 | try: 407 | hexfile.write(bin) 408 | hexfile.close() 409 | hexdump(open(hexfile.name, 'rb')) 410 | finally: 411 | os.remove(hexfile.name) 412 | if logfile: 413 | sys.stderr, sys.stdout = savedstd 414 | openlog.close() 415 | 416 | 417 | def main(): 418 | from optparse import OptionParser 419 | parser = OptionParser(usage=''' 420 | %prog [binfile|-] 421 | %prog -r hexfile 422 | %prog --test [logfile]''', version=__version__) 423 | parser.add_option('-r', '--restore', action='store_true', 424 | help='restore binary from hex dump') 425 | parser.add_option('--test', action='store_true', help='run hexdump sanity checks') 426 | 427 | options, args = parser.parse_args() 428 | 429 | if options.test: 430 | if args: 431 | runtest(logfile=args[0]) 432 | else: 433 | runtest() 434 | elif not args or len(args) > 1: 435 | parser.print_help() 436 | sys.exit(-1) 437 | else: 438 | ## dump file 439 | if not options.restore: 440 | # [x] memory effective dump 441 | if args[0] == '-': 442 | if not PY3K: 443 | hexdump(sys.stdin) 444 | else: 445 | hexdump(sys.stdin.buffer) 446 | else: 447 | hexdump(open(args[0], 'rb')) 448 | 449 | ## restore file 450 | else: 451 | # prepare input stream 452 | if args[0] == '-': 453 | instream = sys.stdin 454 | else: 455 | if PY3K: 456 | instream = open(args[0]) 457 | else: 458 | instream = open(args[0], 'rb') 459 | 460 | # output stream 461 | # [ ] memory efficient restore 462 | if PY3K: 463 | sys.stdout.buffer.write(restore(instream.read())) 464 | else: 465 | # Windows - binary mode for sys.stdout to prevent data corruption 466 | normalize_py() 467 | sys.stdout.write(restore(instream.read())) 468 | 469 | 470 | if __name__ == '__main__': 471 | main() 472 | 473 | # [x] file restore from command line utility 474 | # [ ] write dump with LF on Windows for consistency 475 | # [ ] encoding param for hexdump()ing Python 3 str if anybody requests that 476 | 477 | # [ ] document chunking API 478 | # [ ] document hexdump API 479 | # [ ] blog about sys.stdout text mode problem on Windows 480 | -------------------------------------------------------------------------------- /okhttp_poker.js: -------------------------------------------------------------------------------- 1 | /** 2 | 使用说明 3 | 首先将 okhttpfind.dex 拷贝到 /data/local/tmp/ 目录下 4 | 例:frida -U -l okhttp_poker.js -f com.example.demo --no-pause 5 | 接下来使用okhttp的所有请求将被拦截并打印出来; 6 | 扩展函数: 7 | find() 检查是否使用了Okhttp & 是否可能被混淆 & 寻找okhttp3关键类及函数 8 | switchLoader(\"okhttp3.OkHttpClient\") 参数:静态分析到的okhttpclient类名 9 | hold() 开启HOOK拦截 10 | history() 打印可重新发送的请求 11 | resend(index) 重新发送请求 12 | 13 | 备注 : okhtpfind.dex 内包含了 更改了包名的okio以及Gson,以及Java写的寻找okhttp特征的代码。 14 | okhttpfind.dex 源码链接 https://github.com/siyujie/okhttp_find 15 | 16 | 原理:由于所有使用的okhttp框架的App发出的请求都是通过RealCall.java发出的,那么我们可以hook此类拿到request和response, 17 | 也可以缓存下来每一个请求的call对象,进行再次请求,所以选择了此处进行hook。 18 | 19 | */ 20 | var Cls_Call = "okhttp3.Call"; 21 | var Cls_CallBack = "okhttp3.Callback"; 22 | var Cls_OkHttpClient = "okhttp3.OkHttpClient"; 23 | var Cls_Request = "okhttp3.Request"; 24 | var Cls_Response = "okhttp3.Response"; 25 | var Cls_ResponseBody = "okhttp3.ResponseBody"; 26 | var Cls_okio_Buffer = "okio.Buffer"; 27 | var F_header_namesAndValues = "namesAndValues"; 28 | var F_req_body = "body"; 29 | var F_req_headers = "headers"; 30 | var F_req_method = "method"; 31 | var F_req_url = "url"; 32 | var F_rsp$builder_body = "body"; 33 | var F_rsp_body = "body"; 34 | var F_rsp_code = "code"; 35 | var F_rsp_headers = "headers"; 36 | var F_rsp_message = "message"; 37 | var F_rsp_request = "request"; 38 | var M_CallBack_onFailure = "onFailure"; 39 | var M_CallBack_onResponse = "onResponse"; 40 | var M_Call_enqueue = "enqueue"; 41 | var M_Call_execute = "execute"; 42 | var M_Call_request = "request"; 43 | var M_Client_newCall = "newCall"; 44 | var M_buffer_readByteArray = "readByteArray"; 45 | var M_contentType_charset = "charset"; 46 | var M_reqbody_contentLength = "contentLength"; 47 | var M_reqbody_contentType = "contentType"; 48 | var M_reqbody_writeTo = "writeTo"; 49 | var M_rsp$builder_build = "build"; 50 | var M_rspBody_contentLength = "contentLength"; 51 | var M_rspBody_contentType = "contentType"; 52 | var M_rspBody_create = "create"; 53 | var M_rspBody_source = "source"; 54 | var M_rsp_newBuilder = "newBuilder"; 55 | 56 | //---------------------------------- 57 | var JavaStringWapper = null; 58 | var JavaIntegerWapper = null; 59 | var JavaStringBufferWapper = null; 60 | var GsonWapper = null; 61 | var ListWapper = null; 62 | var ArrayListWapper = null; 63 | var ArraysWapper = null; 64 | var CharsetWapper = null; 65 | var CharacterWapper = null; 66 | 67 | var OkioByteStrngWapper = null; 68 | var OkioBufferWapper = null; 69 | 70 | var OkHttpClientWapper = null; 71 | var ResponseBodyWapper = null; 72 | var BufferWapper = null; 73 | var Utils = null; 74 | //---------------------------------- 75 | var CallCache = [] 76 | var hookedArray = [] 77 | var filterArray = ["JPG", "jpg", "PNG", "png", "WEBP", "webp", "JPEG", "jpeg", "GIF", "gif",".zip", ".data"] 78 | 79 | 80 | function buildNewResponse(responseObject) { 81 | var newResponse = null; 82 | Java.perform(function () { 83 | try { 84 | var logString = JavaStringBufferWapper.$new() 85 | 86 | logString.append("").append("\n"); 87 | logString.append("┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────").append("\n"); 88 | 89 | newResponse = printAll(responseObject, logString) 90 | 91 | logString.append("└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────").append("\n"); 92 | logString.append("").append("\n"); 93 | 94 | console.log(logString) 95 | } catch (error) { 96 | console.log("printAll ERROR : " + error); 97 | } 98 | }) 99 | return newResponse; 100 | } 101 | 102 | 103 | function printAll(responseObject, logString) { 104 | try { 105 | var request = getFieldValue(responseObject, F_rsp_request) 106 | printerRequest(request, logString) 107 | } catch (error) { 108 | console.log("print request error : ", error.stack) 109 | return responseObject; 110 | } 111 | var newResponse = printerResponse(responseObject, logString) 112 | return newResponse; 113 | } 114 | 115 | 116 | function printerRequest(request, logString) { 117 | var defChatset = CharsetWapper.forName("UTF-8") 118 | //URL 119 | var httpUrl = getFieldValue(request, F_req_url) 120 | logString.append("| URL: " + httpUrl).append("\n") 121 | logString.append("|").append("\n") 122 | logString.append("| Method: " + getFieldValue(request, F_req_method)).append("\n") 123 | logString.append("|").append("\n") 124 | var requestBody = getFieldValue(request, F_req_body); 125 | var hasRequestBody = true 126 | if (null == requestBody) { 127 | hasRequestBody = false 128 | } 129 | //Headers 130 | var requestHeaders = getFieldValue(request, F_req_headers) 131 | var headersList = headersToList(requestHeaders) 132 | var headersSize = getHeaderSize(headersList) 133 | 134 | logString.append("| Request Headers: ").append("" + headersSize).append("\n") 135 | if (hasRequestBody) { 136 | var requestBody = getWrapper(requestBody) 137 | var contentType = requestBody[M_reqbody_contentType]() 138 | if (null != contentType) { 139 | logString.append("| ┌─" + "Content-Type: " + contentType).append("\n") 140 | } 141 | var contentLength = requestBody[M_reqbody_contentLength]() 142 | if (contentLength != -1) { 143 | var tag = headersSize == 0 ? "└─" : "┌─" 144 | logString.append("| " + tag + "Content-Length: " + contentLength).append("\n") 145 | } 146 | } 147 | if (headersSize == 0) { 148 | logString.append("| no headers").append("\n") 149 | } 150 | for (var i = 0; i < headersSize; i++) { 151 | var name = getHeaderName(headersList, i) 152 | if (!JavaStringWapper.$new("Content-Type").equalsIgnoreCase(name) && !JavaStringWapper.$new("Content-Length").equalsIgnoreCase(name)) { 153 | var value = getHeaderValue(headersList, i) 154 | var tag = i == (headersSize - 1) ? "└─" : "┌─" 155 | logString.append("| " + tag + name + ": " + value).append("\n") 156 | } 157 | } 158 | var shielded = filterUrl(httpUrl.toString()) 159 | if (shielded) { 160 | logString.append("|" + " File Request Body Omit.....").append("\n") 161 | return; 162 | } 163 | logString.append("|").append("\n") 164 | if (!hasRequestBody) { 165 | logString.append("|" + "--> END ").append("\n") 166 | } else if (bodyEncoded(headersList)) { 167 | logString.append("|" + "--> END (encoded body omitted > bodyEncoded)").append("\n") 168 | } else { 169 | logString.append("| Request Body:").append("\n") 170 | var buffer = BufferWapper.$new() 171 | requestBody[M_reqbody_writeTo](buffer) 172 | var reqByteString = getByteString(buffer) 173 | 174 | var charset = defChatset 175 | var contentType = requestBody[M_reqbody_contentType]() 176 | if (null != contentType) { 177 | var appcharset = contentType[M_contentType_charset](defChatset); 178 | if (null != appcharset) { 179 | charset = appcharset; 180 | } 181 | } 182 | //LOG Request Body 183 | try { 184 | if (isPlaintext(reqByteString)) { 185 | logString.append(splitLine(readBufferString(reqByteString, charset), "| ")).append("\n") 186 | logString.append("|").append("\n") 187 | logString.append("|" + "--> END ").append("\n") 188 | } else { 189 | logString.append(splitLine(hexToUtf8(reqByteString.hex()), "| ")).append("\n") 190 | logString.append("|").append("\n"); 191 | logString.append("|" + "--> END (binary body omitted -> isPlaintext)").append("\n") 192 | } 193 | } catch (error) { 194 | logString.append(splitLine(hexToUtf8(reqByteString.hex()), "| ")).append("\n") 195 | logString.append("|").append("\n"); 196 | logString.append("|" + "--> END (binary body omitted -> isPlaintext)").append("\n") 197 | } 198 | } 199 | logString.append("|").append("\n"); 200 | } 201 | 202 | 203 | function printerResponse(response, logString) { 204 | var newResponse = null; 205 | try { 206 | var defChatset = CharsetWapper.forName("UTF-8") 207 | 208 | var request = getFieldValue(response, F_rsp_request) 209 | var url = getFieldValue(request, F_req_url) 210 | var shielded = filterUrl(url.toString()) 211 | if (shielded) { 212 | logString.append("|" + " File Response Body Omit.....").append("\n") 213 | return response; 214 | } 215 | //URL 216 | logString.append("| URL: " + url).append("\n") 217 | logString.append("|").append("\n") 218 | logString.append("| Status Code: " + getFieldValue(response, F_rsp_code) + " / " + getFieldValue(response, F_rsp_message)).append("\n") 219 | logString.append("|").append("\n") 220 | var responseBodyObj = getFieldValue(response, F_rsp_body) 221 | var responseBody = getWrapper(responseBodyObj) 222 | var contentLength = responseBody[M_rspBody_contentLength]() 223 | //Headers 224 | var resp_headers = getFieldValue(response, F_rsp_headers) 225 | var respHeadersList = headersToList(resp_headers) 226 | var respHeaderSize = getHeaderSize(respHeadersList) 227 | logString.append("| Response Headers: ").append("" + respHeaderSize).append("\n") 228 | if (respHeaderSize == 0) { 229 | logString.append("| no headers").append("\n") 230 | } 231 | for (var i = 0; i < respHeaderSize; i++) { 232 | var tag = i == (respHeaderSize - 1) ? "└─" : "┌─" 233 | logString.append("| " + tag + getHeaderName(respHeadersList, i) + ": " + getHeaderValue(respHeadersList, i)).append("\n") 234 | } 235 | //Body 236 | var content = ""; 237 | var nobody = !hasBody(response, respHeadersList) 238 | if (nobody) { 239 | logString.append("| No Response Body : " + response).append("\n") 240 | logString.append("|" + "<-- END HTTP").append("\n") 241 | } else if (bodyEncoded(respHeadersList)) { 242 | logString.append("|" + "<-- END HTTP (encoded body omitted)").append("\n") 243 | } else { 244 | logString.append("| ").append("\n"); 245 | logString.append("| Response Body:").append("\n") 246 | var source = responseBody[M_rspBody_source]() 247 | var rspByteString = getByteString(source) 248 | var charset = defChatset 249 | var contentType = responseBody[M_rspBody_contentType]() 250 | if (null != contentType) { 251 | var appcharset = contentType[M_contentType_charset](defChatset) 252 | if (null != appcharset) { 253 | charset = appcharset 254 | } 255 | } 256 | //newResponse 257 | var mediaType = responseBody[M_rspBody_contentType]() 258 | var newBody = null; 259 | try { 260 | newBody = ResponseBodyWapper[M_rspBody_create](mediaType, rspByteString.toByteArray()) 261 | } catch (error) { 262 | newBody = ResponseBodyWapper[M_rspBody_create](mediaType, readBufferString(rspByteString, charset)) 263 | } 264 | var newBuilder = null; 265 | if ("" == M_rsp_newBuilder) { 266 | var ResponseBuilderClazz = response.class.getDeclaredClasses()[0] 267 | newBuilder = Java.use(ResponseBuilderClazz.getName()).$new(response) 268 | } else { 269 | newBuilder = response[M_rsp_newBuilder]() 270 | } 271 | var bodyField = newBuilder.class.getDeclaredField(F_rsp$builder_body) 272 | bodyField.setAccessible(true) 273 | bodyField.set(newBuilder, newBody) 274 | newResponse = newBuilder[M_rsp$builder_build]() 275 | 276 | if (!isPlaintext(rspByteString)) { 277 | logString.append("|" + "<-- END HTTP (binary body omitted)").append("\n"); 278 | } 279 | if (contentLength != 0) { 280 | try { 281 | var content = readBufferString(rspByteString, charset) 282 | logString.append(splitLine(content, "| ")).append("\n") 283 | } catch (error) { 284 | logString.append(splitLine(hexToUtf8(rspByteString.hex()), "| ")).append("\n") 285 | } 286 | 287 | logString.append("| ").append("\n"); 288 | } 289 | logString.append("|" + "<-- END HTTP").append("\n"); 290 | } 291 | } catch (error) { 292 | logString.append("print response error : " + error).append("\n") 293 | if (null == newResponse) { 294 | return response; 295 | } 296 | } 297 | return newResponse; 298 | } 299 | 300 | /** 301 | * hex to string 302 | */ 303 | function hexToUtf8(hex) { 304 | try { 305 | return decodeURIComponent('%' + hex.match(/.{1,2}/g).join('%')); 306 | } catch (error) { 307 | return "hex[" + hex + "]"; 308 | } 309 | } 310 | 311 | /** 312 | */ 313 | function getFieldValue(object, fieldName) { 314 | var field = object.class.getDeclaredField(fieldName); 315 | field.setAccessible(true) 316 | var fieldValue = field.get(object) 317 | if (null == fieldValue) { 318 | return null; 319 | } 320 | var FieldClazz = Java.use(fieldValue.$className) 321 | var fieldValueWapper = Java.cast(fieldValue, FieldClazz) 322 | return fieldValueWapper 323 | } 324 | /** 325 | */ 326 | function getWrapper(javaobject) { 327 | return Java.cast(javaobject, Java.use(javaobject.$className)) 328 | } 329 | 330 | /** 331 | */ 332 | function headersToList(headers) { 333 | var gson = GsonWapper.$new() 334 | var namesAndValues = getFieldValue(headers, F_header_namesAndValues) 335 | var jsonString = gson.toJson(namesAndValues) 336 | var namesAndValuesList = Java.cast(gson.fromJson(jsonString, ListWapper.class), ListWapper) 337 | return namesAndValuesList; 338 | } 339 | 340 | function getHeaderSize(namesAndValuesList) { 341 | return namesAndValuesList.size() / 2 342 | } 343 | 344 | function getHeaderName(namesAndValuesList, index) { 345 | return namesAndValuesList.get(index * 2) 346 | } 347 | function getHeaderValue(namesAndValuesList, index) { 348 | return namesAndValuesList.get((index * 2) + 1) 349 | } 350 | 351 | function getByHeader(namesAndValuesList, name) { 352 | var nameString = JavaStringWapper.$new(name) 353 | Java.perform(function () { 354 | var length = namesAndValuesList.size() 355 | var nameByList = ""; 356 | do { 357 | length -= 2; 358 | if (length < 0) { 359 | return null; 360 | } 361 | // console.log("namesAndValuesList: "+namesAndValuesList.$className) 362 | nameByList = namesAndValuesList.get(JavaIntegerWapper.valueOf(length).intValue()) 363 | } while (!nameString.equalsIgnoreCase(nameByList)); 364 | return namesAndValuesList.get(length + 1); 365 | 366 | }) 367 | } 368 | 369 | function bodyEncoded(namesAndValuesList) { 370 | if (null == namesAndValuesList) return false; 371 | var contentEncoding = getByHeader(namesAndValuesList, "Content-Encoding") 372 | var bodyEncoded = contentEncoding != null && !JavaStringWapper.$new("identity").equalsIgnoreCase(contentEncoding) 373 | return bodyEncoded 374 | 375 | } 376 | 377 | function hasBody(response, namesAndValuesList) { 378 | var request = getFieldValue(response, F_rsp_request) 379 | var m = getFieldValue(request, F_req_method); 380 | if (JavaStringWapper.$new("HEAD").equals(m)) { 381 | return false; 382 | } 383 | var Transfer_Encoding = ""; 384 | var respHeaderSize = getHeaderSize(namesAndValuesList) 385 | for (var i = 0; i < respHeaderSize; i++) { 386 | if (JavaStringWapper.$new("Transfer-Encoding").equals(getHeaderName(namesAndValuesList, i))) { 387 | Transfer_Encoding = getHeaderValue(namesAndValuesList, i); 388 | break 389 | } 390 | } 391 | var code = getFieldValue(response, F_rsp_code) 392 | if (((code >= 100 && code < 200) || code == 204 || code == 304) 393 | && response[M_rspBody_contentLength] == -1 394 | && !JavaStringWapper.$new("chunked").equalsIgnoreCase(Transfer_Encoding) 395 | ) { 396 | return false; 397 | } 398 | return true; 399 | } 400 | 401 | 402 | function isPlaintext(byteString) { 403 | try { 404 | var bufferSize = byteString.size() 405 | var buffer = NewBuffer(byteString) 406 | for (var i = 0; i < 16; i++) { 407 | if (bufferSize == 0) { 408 | console.log("bufferSize == 0") 409 | break 410 | } 411 | var codePoint = buffer.readUtf8CodePoint() 412 | if (CharacterWapper.isISOControl(codePoint) && !CharacterWapper.isWhitespace(codePoint)) { 413 | return false; 414 | } 415 | } 416 | return true; 417 | } catch (error) { 418 | // console.log(error) 419 | // console.log(Java.use("android.util.Log").getStackTraceString(error)) 420 | return false; 421 | } 422 | } 423 | 424 | function getByteString(buffer) { 425 | var bytearray = buffer[M_buffer_readByteArray](); 426 | var byteString = OkioByteStrngWapper.of(bytearray) 427 | return byteString; 428 | } 429 | 430 | function NewBuffer(byteString) { 431 | var buffer = OkioBufferWapper.$new() 432 | byteString.write(buffer) 433 | return buffer; 434 | } 435 | 436 | function readBufferString(byteString, chatset) { 437 | var byteArray = byteString.toByteArray(); 438 | var str = JavaStringWapper.$new(byteArray, chatset) 439 | return str; 440 | } 441 | 442 | function splitLine(string, tag) { 443 | var newSB = JavaStringBufferWapper.$new() 444 | var newString = JavaStringWapper.$new(string) 445 | var lineNum = Math.ceil(newString.length() / 150) 446 | for (var i = 0; i < lineNum; i++) { 447 | var start = i * 150; 448 | var end = (i + 1) * 150 449 | newSB.append(tag) 450 | if (end > newString.length()) { 451 | newSB.append(newString.substring(start, newString.length())) 452 | } else { 453 | newSB.append(newString.substring(start, end)) 454 | } 455 | newSB.append("\n") 456 | } 457 | var lineStr = ""; 458 | if (newSB.length() > 0) { 459 | lineStr = newSB.deleteCharAt(newSB.length() - 1).toString() 460 | } 461 | return lineStr 462 | } 463 | 464 | /** 465 | * 466 | */ 467 | function alreadyHook(str) { 468 | for (var i = 0; i < hookedArray.length; i++) { 469 | if (str == hookedArray[i]) { 470 | return true; 471 | } 472 | } 473 | return false; 474 | } 475 | 476 | /** 477 | * 478 | */ 479 | function filterUrl(url) { 480 | for (var i = 0; i < filterArray.length; i++) { 481 | if (url.indexOf(filterArray[i]) != -1) { 482 | // console.log(url + " ?? " + filterArray[i]) 483 | return true; 484 | } 485 | } 486 | return false; 487 | } 488 | 489 | function hookRealCall(realCallClassName) { 490 | Java.perform(function () { 491 | console.log(" ........... hookRealCall : " + realCallClassName) 492 | var RealCall = Java.use(realCallClassName) 493 | if ("" != Cls_CallBack) { 494 | //异步 495 | RealCall[M_Call_enqueue].overload(Cls_CallBack).implementation = function (callback) { 496 | // console.log("-------------------------------------HOOK SUCCESS 异步--------------------------------------------------") 497 | var realCallBack = Java.use(callback.$className) 498 | realCallBack[M_CallBack_onResponse].overload(Cls_Call,Cls_Response).implementation = function(call, response){ 499 | var newResponse = buildNewResponse(response) 500 | this[M_CallBack_onResponse](call,newResponse) 501 | } 502 | this[M_Call_enqueue](callback) 503 | realCallBack.$dispose 504 | } 505 | } 506 | //同步 507 | RealCall[M_Call_execute].overload().implementation = function () { 508 | // console.log("-------------------------------------HOOK SUCCESS 同步--------------------------------------------------") 509 | var response = this[M_Call_execute]() 510 | var newResponse = buildNewResponse(response) 511 | return newResponse; 512 | } 513 | }) 514 | } 515 | 516 | /** 517 | * check className & filter 518 | */ 519 | function checkClass(name) { 520 | if (name.startsWith("com.") 521 | || name.startsWith("cn.") 522 | || name.startsWith("io.") 523 | || name.startsWith("org.") 524 | || name.startsWith("android") 525 | || name.startsWith("kotlin") 526 | || name.startsWith("[") 527 | || name.startsWith("java") 528 | || name.startsWith("sun.") 529 | || name.startsWith("net.") 530 | || name.indexOf(".") < 0 531 | || name.startsWith("dalvik") 532 | 533 | ) { 534 | return false; 535 | } 536 | return true; 537 | } 538 | 539 | /** 540 | * print request history 541 | */ 542 | function history() { 543 | Java.perform(function () { 544 | try { 545 | console.log("") 546 | console.log("History Size : " + CallCache.length) 547 | for (var i = 0; i < CallCache.length; i++) { 548 | var call = CallCache[i] 549 | if ("" != M_Call_request) { 550 | console.log("-----> index[" + i + "]" + " >> " + call[M_Call_request]()) 551 | } else { 552 | console.log("-----> index[" + i + "]" + " ???? M_Call_execute = \"\"") 553 | } 554 | console.log("") 555 | } 556 | console.log("") 557 | } catch (error) { 558 | console.log(error) 559 | } 560 | }) 561 | } 562 | 563 | /** 564 | * resend request 565 | */ 566 | function resend(index) { 567 | Java.perform(function () { 568 | try { 569 | console.log("resend >> " + index) 570 | var call = CallCache[index] 571 | if ("" != M_Call_execute) { 572 | call[M_Call_execute]() 573 | } else { 574 | console.log("M_Call_execute = null") 575 | } 576 | } catch (error) { 577 | console.log("Error : " + error) 578 | } 579 | }) 580 | } 581 | 582 | /** 583 | * 开启HOOK拦截 584 | */ 585 | function hold() { 586 | Java.perform(function () { 587 | // 588 | Utils = Java.use("com.singleman.okhttp.Utils") 589 | //Init common 590 | JavaStringWapper = Java.use("java.lang.String") 591 | JavaStringBufferWapper = Java.use("java.lang.StringBuilder") 592 | JavaIntegerWapper = Java.use("java.lang.Integer") 593 | GsonWapper = Java.use("com.singleman.gson.Gson") 594 | ListWapper = Java.use("java.util.List") 595 | ArraysWapper = Java.use("java.util.Arrays") 596 | ArrayListWapper = Java.use("java.util.ArrayList") 597 | CharsetWapper = Java.use("java.nio.charset.Charset") 598 | CharacterWapper = Java.use("java.lang.Character") 599 | 600 | OkioByteStrngWapper = Java.use("com.singleman.okio.ByteString") 601 | OkioBufferWapper = Java.use("com.singleman.okio.Buffer") 602 | 603 | //Init OKHTTP 604 | OkHttpClientWapper = Java.use(Cls_OkHttpClient) 605 | ResponseBodyWapper = Java.use(Cls_ResponseBody) 606 | BufferWapper = Java.use(Cls_okio_Buffer) 607 | 608 | //Start Hook 609 | OkHttpClientWapper[M_Client_newCall].overload(Cls_Request).implementation = function (request) { 610 | var call = this[M_Client_newCall](request) 611 | try { 612 | CallCache.push(call["clone"]()) 613 | } catch (error) { 614 | console.log("not fount clone method!") 615 | } 616 | var realCallClassName = call.$className 617 | if (!alreadyHook(realCallClassName)) { 618 | hookedArray.push(realCallClassName) 619 | hookRealCall(realCallClassName) 620 | } 621 | return call; 622 | } 623 | }) 624 | } 625 | 626 | function switchLoader(clientName) { 627 | Java.perform(function () { 628 | if ("" != clientName) { 629 | try { 630 | var clz = Java.classFactory.loader.findClass(clientName) 631 | console.log("") 632 | console.log(">>>>>>>>>>>>> ", clz, " <<<<<<<<<<<<<<<<") 633 | } catch (error) { 634 | console.log(error) 635 | Java.enumerateClassLoaders({ 636 | onMatch: function (loader) { 637 | try { 638 | if (loader.findClass(clientName)) { 639 | Java.classFactory.loader = loader 640 | console.log("") 641 | console.log("Switch ClassLoader To : ", loader) 642 | console.log("") 643 | } 644 | } catch (error) { 645 | // console.log(error) 646 | } 647 | }, 648 | onComplete: function () { 649 | console.log("") 650 | console.log("Switch ClassLoader Complete !") 651 | console.log("") 652 | } 653 | }) 654 | } 655 | } 656 | Java.openClassFile("/data/local/tmp/okhttpfind.dex").load() 657 | }) 658 | } 659 | 660 | /** 661 | * find & print used location 662 | */ 663 | function find() { 664 | Java.perform(function () { 665 | ArraysWapper = Java.use("java.util.Arrays") 666 | ArrayListWapper = Java.use("java.util.ArrayList") 667 | var isSupport = false; 668 | var clz_Protocol = null; 669 | try { 670 | var clazzNameList = Java.enumerateLoadedClassesSync() 671 | if (clazzNameList.length == 0) { 672 | console.log("ERROR >> [enumerateLoadedClasses] return null !!!!!!") 673 | return 674 | } 675 | for (var i = 0; i < clazzNameList.length; i++) { 676 | var name = clazzNameList[i] 677 | if (!checkClass(name)) { 678 | continue 679 | } 680 | try { 681 | var loadedClazz = Java.classFactory.loader.loadClass(name); 682 | if (loadedClazz.isEnum()) { 683 | var Protocol = Java.use(name); 684 | var toString = ArraysWapper.toString(Protocol.values()); 685 | if (toString.indexOf("http/1.0") != -1 686 | && toString.indexOf("http/1.1") != -1 687 | && toString.indexOf("spdy/3.1") != -1 688 | && toString.indexOf("h2") != -1 689 | ) { 690 | clz_Protocol = loadedClazz; 691 | break; 692 | } 693 | } 694 | } catch (error) { 695 | } 696 | } 697 | if (null == clz_Protocol) { 698 | console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~ 寻找okhttp特征失败,请确认是否使用okhttp ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 699 | return 700 | } 701 | //enum values >> Not to be confused with! 702 | var okhttp_pn = clz_Protocol.getPackage().getName(); 703 | var likelyOkHttpClient = okhttp_pn + ".OkHttpClient" 704 | try { 705 | var clz_okclient = Java.use(likelyOkHttpClient).class 706 | if (null != clz_okclient) { 707 | console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 未 混 淆 (仅参考)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 708 | isSupport = true; 709 | } 710 | } catch (error) { 711 | console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 被 混 淆 (仅参考)~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 712 | isSupport = true; 713 | } 714 | 715 | } catch (error) { 716 | console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~未使用okhttp~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 717 | isSupport = false; 718 | } 719 | 720 | if (!isSupport) { 721 | console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~ 寻找okhttp特征失败,请确认是否使用okhttp ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 722 | return 723 | } 724 | 725 | var likelyClazzList = ArrayListWapper.$new() 726 | for (var i = 0; i < clazzNameList.length; i++) { 727 | var name = clazzNameList[i] 728 | if (!checkClass(name)) { 729 | continue 730 | } 731 | try { 732 | var loadedClazz = Java.classFactory.loader.loadClass(name); 733 | likelyClazzList.add(loadedClazz) 734 | } catch (error) { 735 | } 736 | } 737 | 738 | console.log("likelyClazzList size :" + likelyClazzList.size()) 739 | if (likelyClazzList.size() == 0) { 740 | console.log("Please make a network request and try again!") 741 | } 742 | 743 | console.log("") 744 | console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Start Find~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 745 | console.log("") 746 | try { 747 | var OkHttpFinder = Java.use("com.singleman.okhttp.OkHttpFinder") 748 | OkHttpFinder.getInstance().findClassInit(likelyClazzList) 749 | 750 | console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Find Result~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 751 | 752 | var OkCompatClazz = Java.use("com.singleman.okhttp.OkCompat").class 753 | var fields = OkCompatClazz.getDeclaredFields(); 754 | for (var i = 0; i < fields.length; i++) { 755 | var field = fields[i] 756 | field.setAccessible(true); 757 | var name = field.getName() 758 | var value = field.get(null) 759 | console.log("var " + name + " = \"" + value + "\";") 760 | } 761 | console.log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Find Complete~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 762 | 763 | } catch (error) { 764 | console.log(error) 765 | //console.log(Java.use("android.util.Log").getStackTraceString(error)) 766 | } 767 | }) 768 | } 769 | 770 | /** 771 | */ 772 | function main() { 773 | Java.perform(function () { 774 | Java.openClassFile("/data/local/tmp/okhttpfind.dex").load() 775 | var version = Java.use("com.singleman.SingleMan").class.getDeclaredField("version").get(null) 776 | console.log(""); 777 | console.log("------------------------- OkHttp Poker by SingleMan [" + version + "]------------------------------------"); 778 | console.log("API:") 779 | console.log(" >>> find() 检查是否使用了Okhttp & 是否可能被混淆 & 寻找okhttp3关键类及函数"); 780 | console.log(" >>> switchLoader(\"okhttp3.OkHttpClient\") 参数:静态分析到的okhttpclient类名"); 781 | console.log(" >>> hold() 开启HOOK拦截"); 782 | console.log(" >>> history() 打印可重新发送的请求"); 783 | console.log(" >>> resend(index) 重新发送请求"); 784 | console.log("----------------------------------------------------------------------------------------"); 785 | 786 | }) 787 | } 788 | 789 | setImmediate(main) 790 | -------------------------------------------------------------------------------- /printstack.js: -------------------------------------------------------------------------------- 1 | function printStack(name) { 2 | Java.perform(function () { 3 | var Exception = Java.use("java.lang.Exception"); 4 | var ins = Exception.$new("Exception"); 5 | var straces = ins.getStackTrace(); 6 | if (straces != undefined && straces != null) { 7 | var strace = straces.toString(); 8 | var replaceStr = strace.replace(/,/g, "\\n"); 9 | console.log("=============================" + name + " Stack strat======================="); 10 | console.log(replaceStr); 11 | console.log("=============================" + name + " Stack end=======================\r\n"); 12 | Exception.$dispose(); 13 | } 14 | }); 15 | } 16 | 17 | function showStacks() { 18 | Java.perform(function () { 19 | console.log( 20 | Java.use("android.util.Log") 21 | .getStackTraceString( 22 | Java.use("java.lang.Throwable").$new() 23 | ) 24 | ); 25 | }) 26 | 27 | } -------------------------------------------------------------------------------- /r0capture.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Google Inc. All Rights Reserved. 2 | 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | """Decrypts and logs a process's SSL traffic. 16 | Hooks the functions SSL_read() and SSL_write() in a given process and logs the 17 | decrypted data to the console and/or to a pcap file. 18 | Typical usage example: 19 | ssl_log("wget", "log.pcap", True) 20 | Dependencies: 21 | frida (https://www.frida.re/): 22 | sudo pip install frida 23 | hexdump (https://bitbucket.org/techtonik/hexdump/) if using verbose output: 24 | sudo pip install hexdump 25 | """ 26 | 27 | __author__ = "geffner@google.com (Jason Geffner)" 28 | __version__ = "2.0" 29 | 30 | """ 31 | # r0capture 32 | 33 | ID: r0ysue 34 | 35 | 安卓应用层抓包通杀脚本 36 | 37 | https://github.com/r0ysue/r0capture 38 | 39 | ## 简介 40 | 41 | - 仅限安卓平台,测试安卓7、8、9、10 可用 ; 42 | - 无视所有证书校验或绑定,无视任何证书; 43 | - 通杀TCP/IP四层模型中的应用层中的全部协议; 44 | - 通杀协议包括:Http,WebSocket,Ftp,Xmpp,Imap,Smtp,Protobuf等等、以及它们的SSL版本; 45 | - 通杀所有应用层框架,包括HttpUrlConnection、Okhttp1/3/4、Retrofit/Volley等等; 46 | """ 47 | 48 | # Windows版本需要安装库: 49 | # pip install 'win_inet_pton' 50 | # pip install hexdump 51 | import argparse 52 | import os 53 | import pprint 54 | import random 55 | import signal 56 | import socket 57 | import struct 58 | import sys 59 | import time 60 | from pathlib import Path 61 | 62 | import frida 63 | from loguru import logger 64 | 65 | try: 66 | if os.name == 'nt': 67 | import win_inet_pton 68 | except ImportError: 69 | # win_inet_pton import error 70 | pass 71 | 72 | try: 73 | import myhexdump as hexdump # pylint: disable=g-import-not-at-top 74 | except ImportError: 75 | pass 76 | try: 77 | from shutil import get_terminal_size as get_terminal_size 78 | except: 79 | try: 80 | from backports.shutil_get_terminal_size import get_terminal_size as get_terminal_size 81 | except: 82 | pass 83 | 84 | try: 85 | import click 86 | except: 87 | class click: 88 | @staticmethod 89 | def secho(message=None, **kwargs): 90 | print(message) 91 | 92 | @staticmethod 93 | def style(**kwargs): 94 | raise Exception("unsupported style") 95 | banner = """ 96 | -------------------------------------------------------------------------------------------- 97 | .oooo. . 98 | d8P'`Y8b .o8 99 | oooo d8b 888 888 .ooooo. .oooo. oo.ooooo. .o888oo oooo oooo oooo d8b .ooooo. 100 | `888""8P 888 888 d88' `"Y8 `P )88b 888' `88b 888 `888 `888 `888""8P d88' `88b 101 | 888 888 888 888 .oP"888 888 888 888 888 888 888 888ooo888 102 | 888 `88b d88' 888 .o8 d8( 888 888 888 888 . 888 888 888 888 .o 103 | d888b `Y8bd8P' `Y8bod8P' `Y888""8o 888bod8P' "888" `V88V"V8P' d888b `Y8bod8P' 104 | 888 105 | o888o 106 | https://github.com/r0ysue/r0capture 107 | --------------------------------------------------------------------------------------------\n 108 | """ 109 | 110 | 111 | def show_banner(): 112 | colors = ['bright_red', 'bright_green', 'bright_blue', 'cyan', 'magenta'] 113 | try: 114 | click.style('color test', fg='bright_red') 115 | except: 116 | colors = ['red', 'green', 'blue', 'cyan', 'magenta'] 117 | try: 118 | columns = get_terminal_size().columns 119 | if columns >= len(banner.splitlines()[1]): 120 | for line in banner.splitlines(): 121 | click.secho(line, fg=random.choice(colors)) 122 | except: 123 | pass 124 | 125 | 126 | # ssl_session[] = (, 127 | # ) 128 | ssl_sessions = {} 129 | 130 | 131 | def ssl_log(process, pcap=None, host=False, verbose=False, isUsb=False, ssllib="", isSpawn=True, wait=0): 132 | """Decrypts and logs a process's SSL traffic. 133 | Hooks the functions SSL_read() and SSL_write() in a given process and logs 134 | the decrypted data to the console and/or to a pcap file. 135 | Args: 136 | process: The target process's name (as a string) or process ID (as an int). 137 | pcap: The file path to which the pcap file should be written. 138 | verbose: If True, log the decrypted traffic to the console. 139 | Raises: 140 | NotImplementedError: Not running on a Linux or macOS system. 141 | """ 142 | 143 | # if platform.system() not in ("Darwin", "Linux"): 144 | # raise NotImplementedError("This function is only implemented for Linux and " 145 | # "macOS systems.") 146 | 147 | def log_pcap(pcap_file, ssl_session_id, function, src_addr, src_port, 148 | dst_addr, dst_port, data): 149 | """Writes the captured data to a pcap file. 150 | Args: 151 | pcap_file: The opened pcap file. 152 | ssl_session_id: The SSL session ID for the communication. 153 | function: The function that was intercepted ("SSL_read" or "SSL_write"). 154 | src_addr: The source address of the logged packet. 155 | src_port: The source port of the logged packet. 156 | dst_addr: The destination address of the logged packet. 157 | dst_port: The destination port of the logged packet. 158 | data: The decrypted packet data. 159 | """ 160 | t = time.time() 161 | 162 | if ssl_session_id not in ssl_sessions: 163 | ssl_sessions[ssl_session_id] = (random.randint(0, 0xFFFFFFFF), 164 | random.randint(0, 0xFFFFFFFF)) 165 | client_sent, server_sent = ssl_sessions[ssl_session_id] 166 | 167 | if function == "SSL_read": 168 | seq, ack = (server_sent, client_sent) 169 | else: 170 | seq, ack = (client_sent, server_sent) 171 | 172 | for writes in ( 173 | # PCAP record (packet) header 174 | ("=I", int(t)), # Timestamp seconds 175 | ("=I", int((t * 1000000) % 1000000)), # Timestamp microseconds 176 | ("=I", 40 + len(data)), # Number of octets saved 177 | ("=i", 40 + len(data)), # Actual length of packet 178 | # IPv4 header 179 | (">B", 0x45), # Version and Header Length 180 | (">B", 0), # Type of Service 181 | (">H", 40 + len(data)), # Total Length 182 | (">H", 0), # Identification 183 | (">H", 0x4000), # Flags and Fragment Offset 184 | (">B", 0xFF), # Time to Live 185 | (">B", 6), # Protocol 186 | (">H", 0), # Header Checksum 187 | (">I", src_addr), # Source Address 188 | (">I", dst_addr), # Destination Address 189 | # TCP header 190 | (">H", src_port), # Source Port 191 | (">H", dst_port), # Destination Port 192 | (">I", seq), # Sequence Number 193 | (">I", ack), # Acknowledgment Number 194 | (">H", 0x5018), # Header Length and Flags 195 | (">H", 0xFFFF), # Window Size 196 | (">H", 0), # Checksum 197 | (">H", 0)): # Urgent Pointer 198 | pcap_file.write(struct.pack(writes[0], writes[1])) 199 | pcap_file.write(data) 200 | 201 | if function == "SSL_read": 202 | server_sent += len(data) 203 | else: 204 | client_sent += len(data) 205 | ssl_sessions[ssl_session_id] = (client_sent, server_sent) 206 | 207 | def on_message(message, data): 208 | """Callback for errors and messages sent from Frida-injected JavaScript. 209 | Logs captured packet data received from JavaScript to the console and/or a 210 | pcap file. See https://www.frida.re/docs/messages/ for more detail on 211 | Frida's messages. 212 | Args: 213 | message: A dictionary containing the message "type" and other fields 214 | dependent on message type. 215 | data: The string of captured decrypted data. 216 | """ 217 | if message["type"] == "error": 218 | logger.info(f"{message}") 219 | os.kill(os.getpid(), signal.SIGTERM) 220 | return 221 | if len(data) == 1: 222 | logger.info(f'{message["payload"]["function"]}') 223 | logger.info(f'{message["payload"]["stack"]}') 224 | return 225 | p = message["payload"] 226 | if verbose: 227 | src_addr = socket.inet_ntop(socket.AF_INET, 228 | struct.pack(">I", p["src_addr"])) 229 | dst_addr = socket.inet_ntop(socket.AF_INET, 230 | struct.pack(">I", p["dst_addr"])) 231 | session_id = p['ssl_session_id'] 232 | logger.info(f"SSL Session: {session_id}") 233 | logger.info("[%s] %s:%d --> %s:%d" % ( 234 | p["function"], 235 | src_addr, 236 | p["src_port"], 237 | dst_addr, 238 | p["dst_port"])) 239 | gen = hexdump.hexdump(data, result="generator",only_str=True) 240 | str_gen = ''.join(gen) 241 | logger.info(f"{str_gen}") 242 | logger.info(f"{p['stack']}") 243 | if pcap: 244 | log_pcap(pcap_file, p["ssl_session_id"], p["function"], p["src_addr"], 245 | p["src_port"], p["dst_addr"], p["dst_port"], data) 246 | 247 | if isUsb: 248 | try: 249 | device = frida.get_usb_device() 250 | except: 251 | device = frida.get_remote_device() 252 | else: 253 | if host: 254 | manager = frida.get_device_manager() 255 | device = manager.add_remote_device(host) 256 | else: 257 | device = frida.get_local_device() 258 | 259 | if isSpawn: 260 | pid = device.spawn([process]) 261 | time.sleep(1) 262 | session = device.attach(pid) 263 | time.sleep(1) 264 | device.resume(pid) 265 | else: 266 | print("attach") 267 | session = device.attach(process) 268 | if wait > 0: 269 | print(f"wait for {wait} seconds") 270 | time.sleep(wait) 271 | 272 | # session = frida.attach(process) 273 | 274 | # pid = device.spawn([process]) 275 | # pid = process 276 | # session = device.attach(pid) 277 | # device.resume(pid) 278 | if pcap: 279 | pcap_file = open(pcap, "wb", 0) 280 | for writes in ( 281 | ("=I", 0xa1b2c3d4), # Magic number 282 | ("=H", 2), # Major version number 283 | ("=H", 4), # Minor version number 284 | ("=i", time.timezone), # GMT to local correction 285 | ("=I", 0), # Accuracy of timestamps 286 | ("=I", 65535), # Max length of captured packets 287 | ("=I", 228)): # Data link type (LINKTYPE_IPV4) 288 | pcap_file.write(struct.pack(writes[0], writes[1])) 289 | 290 | with open(Path(__file__).resolve().parent.joinpath("./script.js"), encoding="utf-8") as f: 291 | _FRIDA_SCRIPT = f.read() 292 | # _FRIDA_SCRIPT = session.create_script(content) 293 | # print(_FRIDA_SCRIPT) 294 | script = session.create_script(_FRIDA_SCRIPT) 295 | script.on("message", on_message) 296 | script.load() 297 | 298 | if ssllib != "": 299 | script.exports.setssllib(ssllib) 300 | 301 | print("Press Ctrl+C to stop logging.") 302 | 303 | def stoplog(signum, frame): 304 | print('You have stoped logging.') 305 | session.detach() 306 | if pcap: 307 | pcap_file.flush() 308 | pcap_file.close() 309 | exit() 310 | 311 | signal.signal(signal.SIGINT, stoplog) 312 | signal.signal(signal.SIGTERM, stoplog) 313 | sys.stdin.read() 314 | 315 | 316 | if __name__ == "__main__": 317 | show_banner() 318 | 319 | 320 | class ArgParser(argparse.ArgumentParser): 321 | 322 | def error(self, message): 323 | print("ssl_logger v" + __version__) 324 | print("by " + __author__) 325 | print("Modified by BigFaceCat") 326 | print("Error: " + message) 327 | print() 328 | print(self.format_help().replace("usage:", "Usage:")) 329 | self.exit(0) 330 | 331 | 332 | parser = ArgParser( 333 | add_help=False, 334 | description="Decrypts and logs a process's SSL traffic.", 335 | formatter_class=argparse.RawDescriptionHelpFormatter, 336 | epilog=r""" 337 | Examples: 338 | %(prog)s -pcap ssl.pcap openssl 339 | %(prog)s -verbose 31337 340 | %(prog)s -pcap log.pcap -verbose wget 341 | %(prog)s -pcap log.pcap -ssl "*libssl.so*" com.bigfacecat.testdemo 342 | """) 343 | 344 | args = parser.add_argument_group("Arguments") 345 | args.add_argument("-pcap", '-p', metavar="", required=False, 346 | help="Name of PCAP file to write") 347 | args.add_argument("-host", '-H', metavar="<192.168.1.1:27042>", required=False, 348 | help="connect to remote frida-server on HOST") 349 | args.add_argument("-verbose", "-v", required=False, action="store_const", default=True, 350 | const=True, help="Show verbose output") 351 | args.add_argument("process", metavar="", 352 | help="Process whose SSL calls to log") 353 | args.add_argument("-ssl", default="", metavar="", 354 | help="SSL library to hook") 355 | args.add_argument("--isUsb", "-U", default=False, action="store_true", 356 | help="connect to USB device") 357 | args.add_argument("--isSpawn", "-f", default=False, action="store_true", 358 | help="if spawned app") 359 | args.add_argument("-wait", "-w", type=int, metavar="", default=0, 360 | help="Time to wait for the process") 361 | 362 | parsed = parser.parse_args() 363 | logger.add(f"{parsed.process.replace('.','_')}-{int(time.time())}.log", rotation="500MB", encoding="utf-8", enqueue=True, retention="10 days") 364 | 365 | ssl_log( 366 | int(parsed.process) if parsed.process.isdigit() else parsed.process, 367 | parsed.pcap, 368 | parsed.host, 369 | parsed.verbose, 370 | isUsb=parsed.isUsb, 371 | isSpawn=parsed.isSpawn, 372 | ssllib=parsed.ssl, 373 | wait=parsed.wait 374 | ) 375 | -------------------------------------------------------------------------------- /root.js: -------------------------------------------------------------------------------- 1 | /* 2 | Original author: Daniele Linguaglossa 3 | 28/07/2021 - Edited by Simone Quatrini 4 | Code amended to correctly run on the latest frida version 5 | Added controls to exclude Magisk Manager 6 | */ 7 | 8 | Java.perform(function() { 9 | var RootPackages = ["com.noshufou.android.su", "com.noshufou.android.su.elite", "eu.chainfire.supersu", 10 | "com.koushikdutta.superuser", "com.thirdparty.superuser", "com.yellowes.su", "com.koushikdutta.rommanager", 11 | "com.koushikdutta.rommanager.license", "com.dimonvideo.luckypatcher", "com.chelpus.lackypatch", 12 | "com.ramdroid.appquarantine", "com.ramdroid.appquarantinepro", "com.devadvance.rootcloak", "com.devadvance.rootcloakplus", 13 | "de.robv.android.xposed.installer", "com.saurik.substrate", "com.zachspong.temprootremovejb", "com.amphoras.hidemyroot", 14 | "com.amphoras.hidemyrootadfree", "com.formyhm.hiderootPremium", "com.formyhm.hideroot", "me.phh.superuser", 15 | "eu.chainfire.supersu.pro", "com.kingouser.com", "com.topjohnwu.magisk" 16 | ]; 17 | 18 | var RootBinaries = ["su", "busybox", "supersu", "Superuser.apk", "KingoUser.apk", "SuperSu.apk", "magisk"]; 19 | 20 | var RootProperties = { 21 | "ro.build.selinux": "1", 22 | "ro.debuggable": "0", 23 | "service.adb.root": "0", 24 | "ro.secure": "1" 25 | }; 26 | 27 | var RootPropertiesKeys = []; 28 | 29 | for (var k in RootProperties) RootPropertiesKeys.push(k); 30 | 31 | var PackageManager = Java.use("android.app.ApplicationPackageManager"); 32 | 33 | var Runtime = Java.use('java.lang.Runtime'); 34 | 35 | var NativeFile = Java.use('java.io.File'); 36 | 37 | var String = Java.use('java.lang.String'); 38 | 39 | var SystemProperties = Java.use('android.os.SystemProperties'); 40 | 41 | var BufferedReader = Java.use('java.io.BufferedReader'); 42 | 43 | var ProcessBuilder = Java.use('java.lang.ProcessBuilder'); 44 | 45 | var StringBuffer = Java.use('java.lang.StringBuffer'); 46 | 47 | var loaded_classes = Java.enumerateLoadedClassesSync(); 48 | 49 | send("Loaded " + loaded_classes.length + " classes!"); 50 | 51 | var useKeyInfo = false; 52 | 53 | var useProcessManager = false; 54 | 55 | send("loaded: " + loaded_classes.indexOf('java.lang.ProcessManager')); 56 | 57 | if (loaded_classes.indexOf('java.lang.ProcessManager') != -1) { 58 | try { 59 | //useProcessManager = true; 60 | //var ProcessManager = Java.use('java.lang.ProcessManager'); 61 | } catch (err) { 62 | send("ProcessManager Hook failed: " + err); 63 | } 64 | } else { 65 | send("ProcessManager hook not loaded"); 66 | } 67 | 68 | var KeyInfo = null; 69 | 70 | if (loaded_classes.indexOf('android.security.keystore.KeyInfo') != -1) { 71 | try { 72 | //useKeyInfo = true; 73 | //var KeyInfo = Java.use('android.security.keystore.KeyInfo'); 74 | } catch (err) { 75 | send("KeyInfo Hook failed: " + err); 76 | } 77 | } else { 78 | send("KeyInfo hook not loaded"); 79 | } 80 | 81 | PackageManager.getPackageInfo.overload('java.lang.String', 'int').implementation = function(pname, flags) { 82 | var shouldFakePackage = (RootPackages.indexOf(pname) > -1); 83 | if (shouldFakePackage) { 84 | send("Bypass root check for package: " + pname); 85 | pname = "set.package.name.to.a.fake.one.so.we.can.bypass.it"; 86 | } 87 | return this.getPackageInfo.overload('java.lang.String', 'int').call(this, pname, flags); 88 | }; 89 | 90 | NativeFile.exists.implementation = function() { 91 | var name = NativeFile.getName.call(this); 92 | var shouldFakeReturn = (RootBinaries.indexOf(name) > -1); 93 | if (shouldFakeReturn) { 94 | send("Bypass return value for binary: " + name); 95 | return false; 96 | } else { 97 | return this.exists.call(this); 98 | } 99 | }; 100 | 101 | var exec = Runtime.exec.overload('[Ljava.lang.String;'); 102 | var exec1 = Runtime.exec.overload('java.lang.String'); 103 | var exec2 = Runtime.exec.overload('java.lang.String', '[Ljava.lang.String;'); 104 | var exec3 = Runtime.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;'); 105 | var exec4 = Runtime.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;', 'java.io.File'); 106 | var exec5 = Runtime.exec.overload('java.lang.String', '[Ljava.lang.String;', 'java.io.File'); 107 | 108 | exec5.implementation = function(cmd, env, dir) { 109 | if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id" || cmd == "sh") { 110 | var fakeCmd = "grep"; 111 | send("Bypass " + cmd + " command"); 112 | return exec1.call(this, fakeCmd); 113 | } 114 | if (cmd == "su") { 115 | var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"; 116 | send("Bypass " + cmd + " command"); 117 | return exec1.call(this, fakeCmd); 118 | } 119 | return exec5.call(this, cmd, env, dir); 120 | }; 121 | 122 | exec4.implementation = function(cmdarr, env, file) { 123 | for (var i = 0; i < cmdarr.length; i = i + 1) { 124 | var tmp_cmd = cmdarr[i]; 125 | if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id" || tmp_cmd == "sh") { 126 | var fakeCmd = "grep"; 127 | send("Bypass " + cmdarr + " command"); 128 | return exec1.call(this, fakeCmd); 129 | } 130 | 131 | if (tmp_cmd == "su") { 132 | var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"; 133 | send("Bypass " + cmdarr + " command"); 134 | return exec1.call(this, fakeCmd); 135 | } 136 | } 137 | return exec4.call(this, cmdarr, env, file); 138 | }; 139 | 140 | exec3.implementation = function(cmdarr, envp) { 141 | for (var i = 0; i < cmdarr.length; i = i + 1) { 142 | var tmp_cmd = cmdarr[i]; 143 | if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id" || tmp_cmd == "sh") { 144 | var fakeCmd = "grep"; 145 | send("Bypass " + cmdarr + " command"); 146 | return exec1.call(this, fakeCmd); 147 | } 148 | 149 | if (tmp_cmd == "su") { 150 | var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"; 151 | send("Bypass " + cmdarr + " command"); 152 | return exec1.call(this, fakeCmd); 153 | } 154 | } 155 | return exec3.call(this, cmdarr, envp); 156 | }; 157 | 158 | exec2.implementation = function(cmd, env) { 159 | if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id" || cmd == "sh") { 160 | var fakeCmd = "grep"; 161 | send("Bypass " + cmd + " command"); 162 | return exec1.call(this, fakeCmd); 163 | } 164 | if (cmd == "su") { 165 | var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"; 166 | send("Bypass " + cmd + " command"); 167 | return exec1.call(this, fakeCmd); 168 | } 169 | return exec2.call(this, cmd, env); 170 | }; 171 | 172 | exec.implementation = function(cmd) { 173 | for (var i = 0; i < cmd.length; i = i + 1) { 174 | var tmp_cmd = cmd[i]; 175 | if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id" || tmp_cmd == "sh") { 176 | var fakeCmd = "grep"; 177 | send("Bypass " + cmd + " command"); 178 | return exec1.call(this, fakeCmd); 179 | } 180 | 181 | if (tmp_cmd == "su") { 182 | var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"; 183 | send("Bypass " + cmd + " command"); 184 | return exec1.call(this, fakeCmd); 185 | } 186 | } 187 | 188 | return exec.call(this, cmd); 189 | }; 190 | 191 | exec1.implementation = function(cmd) { 192 | if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id" || cmd == "sh") { 193 | var fakeCmd = "grep"; 194 | send("Bypass " + cmd + " command"); 195 | return exec1.call(this, fakeCmd); 196 | } 197 | if (cmd == "su") { 198 | var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"; 199 | send("Bypass " + cmd + " command"); 200 | return exec1.call(this, fakeCmd); 201 | } 202 | return exec1.call(this, cmd); 203 | }; 204 | 205 | String.contains.implementation = function(name) { 206 | if (name == "test-keys") { 207 | send("Bypass test-keys check"); 208 | return false; 209 | } 210 | return this.contains.call(this, name); 211 | }; 212 | 213 | var get = SystemProperties.get.overload('java.lang.String'); 214 | 215 | get.implementation = function(name) { 216 | if (RootPropertiesKeys.indexOf(name) != -1) { 217 | send("Bypass " + name); 218 | return RootProperties[name]; 219 | } 220 | return this.get.call(this, name); 221 | }; 222 | 223 | Interceptor.attach(Module.findExportByName("libc.so", "fopen"), { 224 | onEnter: function(args) { 225 | var path = Memory.readCString(args[0]); 226 | path = path.split("/"); 227 | var executable = path[path.length - 1]; 228 | var shouldFakeReturn = (RootBinaries.indexOf(executable) > -1) 229 | if (shouldFakeReturn) { 230 | Memory.writeUtf8String(args[0], "/notexists"); 231 | send("Bypass native fopen"); 232 | } 233 | }, 234 | onLeave: function(retval) { 235 | 236 | } 237 | }); 238 | 239 | Interceptor.attach(Module.findExportByName("libc.so", "system"), { 240 | onEnter: function(args) { 241 | var cmd = Memory.readCString(args[0]); 242 | send("SYSTEM CMD: " + cmd); 243 | if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id") { 244 | send("Bypass native system: " + cmd); 245 | Memory.writeUtf8String(args[0], "grep"); 246 | } 247 | if (cmd == "su") { 248 | send("Bypass native system: " + cmd); 249 | Memory.writeUtf8String(args[0], "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"); 250 | } 251 | }, 252 | onLeave: function(retval) { 253 | 254 | } 255 | }); 256 | 257 | /* 258 | 259 | TO IMPLEMENT: 260 | 261 | Exec Family 262 | 263 | int execl(const char *path, const char *arg0, ..., const char *argn, (char *)0); 264 | int execle(const char *path, const char *arg0, ..., const char *argn, (char *)0, char *const envp[]); 265 | int execlp(const char *file, const char *arg0, ..., const char *argn, (char *)0); 266 | int execlpe(const char *file, const char *arg0, ..., const char *argn, (char *)0, char *const envp[]); 267 | int execv(const char *path, char *const argv[]); 268 | int execve(const char *path, char *const argv[], char *const envp[]); 269 | int execvp(const char *file, char *const argv[]); 270 | int execvpe(const char *file, char *const argv[], char *const envp[]); 271 | 272 | */ 273 | 274 | 275 | BufferedReader.readLine.overload('boolean').implementation = function() { 276 | var text = this.readLine.overload('boolean').call(this); 277 | if (text === null) { 278 | // just pass , i know it's ugly as hell but test != null won't work :( 279 | } else { 280 | var shouldFakeRead = (text.indexOf("ro.build.tags=test-keys") > -1); 281 | if (shouldFakeRead) { 282 | send("Bypass build.prop file read"); 283 | text = text.replace("ro.build.tags=test-keys", "ro.build.tags=release-keys"); 284 | } 285 | } 286 | return text; 287 | }; 288 | 289 | var executeCommand = ProcessBuilder.command.overload('java.util.List'); 290 | 291 | ProcessBuilder.start.implementation = function() { 292 | var cmd = this.command.call(this); 293 | var shouldModifyCommand = false; 294 | for (var i = 0; i < cmd.size(); i = i + 1) { 295 | var tmp_cmd = cmd.get(i).toString(); 296 | if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd.indexOf("mount") != -1 || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd.indexOf("id") != -1) { 297 | shouldModifyCommand = true; 298 | } 299 | } 300 | if (shouldModifyCommand) { 301 | send("Bypass ProcessBuilder " + cmd); 302 | this.command.call(this, ["grep"]); 303 | return this.start.call(this); 304 | } 305 | if (cmd.indexOf("su") != -1) { 306 | send("Bypass ProcessBuilder " + cmd); 307 | this.command.call(this, ["justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"]); 308 | return this.start.call(this); 309 | } 310 | 311 | return this.start.call(this); 312 | }; 313 | 314 | if (useProcessManager) { 315 | var ProcManExec = ProcessManager.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;', 'java.io.File', 'boolean'); 316 | var ProcManExecVariant = ProcessManager.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;', 'java.lang.String', 'java.io.FileDescriptor', 'java.io.FileDescriptor', 'java.io.FileDescriptor', 'boolean'); 317 | 318 | ProcManExec.implementation = function(cmd, env, workdir, redirectstderr) { 319 | var fake_cmd = cmd; 320 | for (var i = 0; i < cmd.length; i = i + 1) { 321 | var tmp_cmd = cmd[i]; 322 | if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id") { 323 | var fake_cmd = ["grep"]; 324 | send("Bypass " + cmdarr + " command"); 325 | } 326 | 327 | if (tmp_cmd == "su") { 328 | var fake_cmd = ["justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"]; 329 | send("Bypass " + cmdarr + " command"); 330 | } 331 | } 332 | return ProcManExec.call(this, fake_cmd, env, workdir, redirectstderr); 333 | }; 334 | 335 | ProcManExecVariant.implementation = function(cmd, env, directory, stdin, stdout, stderr, redirect) { 336 | var fake_cmd = cmd; 337 | for (var i = 0; i < cmd.length; i = i + 1) { 338 | var tmp_cmd = cmd[i]; 339 | if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id") { 340 | var fake_cmd = ["grep"]; 341 | send("Bypass " + cmdarr + " command"); 342 | } 343 | 344 | if (tmp_cmd == "su") { 345 | var fake_cmd = ["justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"]; 346 | send("Bypass " + cmdarr + " command"); 347 | } 348 | } 349 | return ProcManExecVariant.call(this, fake_cmd, env, directory, stdin, stdout, stderr, redirect); 350 | }; 351 | } 352 | 353 | if (useKeyInfo) { 354 | KeyInfo.isInsideSecureHardware.implementation = function() { 355 | send("Bypass isInsideSecureHardware"); 356 | return true; 357 | } 358 | } 359 | 360 | }); -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Initializes 'addresses' dictionary and NativeFunctions. 3 | */ 4 | "use strict"; 5 | rpc.exports = { 6 | setssllib: function (name) { 7 | console.log("setSSLLib => " + name); 8 | libname = name; 9 | initializeGlobals(); 10 | return; 11 | } 12 | }; 13 | 14 | var addresses = {}; 15 | var SSL_get_fd = null; 16 | var SSL_get_session = null; 17 | var SSL_SESSION_get_id = null; 18 | var getpeername = null; 19 | var getsockname = null; 20 | var ntohs = null; 21 | var ntohl = null; 22 | var SSLstackwrite = null; 23 | var SSLstackread = null; 24 | 25 | var libname = "*libssl*"; 26 | 27 | function uuid(len, radix) { 28 | var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); 29 | var uuid = [], i; 30 | radix = radix || chars.length; 31 | 32 | if (len) { 33 | // Compact form 34 | for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]; 35 | } else { 36 | // rfc4122, version 4 form 37 | var r; 38 | 39 | // rfc4122 requires these characters 40 | uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; 41 | uuid[14] = '4'; 42 | 43 | // Fill in random data. At i==19 set the high bits of clock sequence as 44 | // per rfc4122, sec. 4.1.5 45 | for (i = 0; i < 36; i++) { 46 | if (!uuid[i]) { 47 | r = 0 | Math.random() * 16; 48 | uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; 49 | } 50 | } 51 | } 52 | 53 | return uuid.join(''); 54 | } 55 | function return_zero(args) { 56 | return 0; 57 | } 58 | function initializeGlobals() { 59 | var resolver = new ApiResolver("module"); 60 | var exps = [ 61 | [Process.platform == "darwin" ? "*libboringssl*" : "*libssl*", ["SSL_read", "SSL_write", "SSL_get_fd", "SSL_get_session", "SSL_SESSION_get_id"]], // for ios and Android 62 | [Process.platform == "darwin" ? "*libsystem*" : "*libc*", ["getpeername", "getsockname", "ntohs", "ntohl"]] 63 | ]; 64 | // console.log(exps) 65 | for (var i = 0; i < exps.length; i++) { 66 | var lib = exps[i][0]; 67 | var names = exps[i][1]; 68 | for (var j = 0; j < names.length; j++) { 69 | var name = names[j]; 70 | // console.log("exports:" + lib + "!" + name) 71 | var matches = resolver.enumerateMatchesSync("exports:" + lib + "!" + name); 72 | if (matches.length == 0) { 73 | if (name == "SSL_get_fd") { 74 | addresses["SSL_get_fd"] = 0; 75 | continue; 76 | } 77 | throw "Could not find " + lib + "!" + name; 78 | } 79 | else if (matches.length != 1) { 80 | // Sometimes Frida returns duplicates. 81 | var address = 0; 82 | var s = ""; 83 | var duplicates_only = true; 84 | for (var k = 0; k < matches.length; k++) { 85 | if (s.length != 0) { 86 | s += ", "; 87 | } 88 | s += matches[k].name + "@" + matches[k].address; 89 | if (address == 0) { 90 | address = matches[k].address; 91 | } 92 | else if (!address.equals(matches[k].address)) { 93 | duplicates_only = false; 94 | } 95 | } 96 | if (!duplicates_only) { 97 | throw "More than one match found for " + lib + "!" + name + ": " + s; 98 | } 99 | } 100 | addresses[name] = matches[0].address; 101 | } 102 | } 103 | if (addresses["SSL_get_fd"] == 0) { 104 | SSL_get_fd = return_zero; 105 | } else { 106 | SSL_get_fd = new NativeFunction(addresses["SSL_get_fd"], "int", ["pointer"]); 107 | } 108 | SSL_get_session = new NativeFunction(addresses["SSL_get_session"], "pointer", ["pointer"]); 109 | SSL_SESSION_get_id = new NativeFunction(addresses["SSL_SESSION_get_id"], "pointer", ["pointer", "pointer"]); 110 | getpeername = new NativeFunction(addresses["getpeername"], "int", ["int", "pointer", "pointer"]); 111 | getsockname = new NativeFunction(addresses["getsockname"], "int", ["int", "pointer", "pointer"]); 112 | ntohs = new NativeFunction(addresses["ntohs"], "uint16", ["uint16"]); 113 | ntohl = new NativeFunction(addresses["ntohl"], "uint32", ["uint32"]); 114 | } 115 | initializeGlobals(); 116 | 117 | function ipToNumber(ip) { 118 | var num = 0; 119 | if (ip == "") { 120 | return num; 121 | } 122 | var aNum = ip.split("."); 123 | if (aNum.length != 4) { 124 | return num; 125 | } 126 | num += parseInt(aNum[0]) << 0; 127 | num += parseInt(aNum[1]) << 8; 128 | num += parseInt(aNum[2]) << 16; 129 | num += parseInt(aNum[3]) << 24; 130 | num = num >>> 0;//这个很关键,不然可能会出现负数的情况 131 | return num; 132 | } 133 | 134 | /** 135 | * Returns a dictionary of a sockfd's "src_addr", "src_port", "dst_addr", and 136 | * "dst_port". 137 | * @param {int} sockfd The file descriptor of the socket to inspect. 138 | * @param {boolean} isRead If true, the context is an SSL_read call. If 139 | * false, the context is an SSL_write call. 140 | * @return {dict} Dictionary of sockfd's "src_addr", "src_port", "dst_addr", 141 | * and "dst_port". 142 | */ 143 | function getPortsAndAddresses(sockfd, isRead) { 144 | var message = {}; 145 | var src_dst = ["src", "dst"]; 146 | for (var i = 0; i < src_dst.length; i++) { 147 | if ((src_dst[i] == "src") ^ isRead) { 148 | var sockAddr = Socket.localAddress(sockfd) 149 | } 150 | else { 151 | var sockAddr = Socket.peerAddress(sockfd) 152 | } 153 | if (sockAddr == null) { 154 | // 网络超时or其他原因可能导致socket被关闭 155 | message[src_dst[i] + "_port"] = 0 156 | message[src_dst[i] + "_addr"] = 0 157 | } else { 158 | message[src_dst[i] + "_port"] = (sockAddr.port & 0xFFFF) 159 | message[src_dst[i] + "_addr"] = ntohl(ipToNumber(sockAddr.ip.split(":").pop())) 160 | } 161 | } 162 | return message; 163 | } 164 | /** 165 | * Get the session_id of SSL object and return it as a hex string. 166 | * @param {!NativePointer} ssl A pointer to an SSL object. 167 | * @return {dict} A string representing the session_id of the SSL object's 168 | * SSL_SESSION. For example, 169 | * "59FD71B7B90202F359D89E66AE4E61247954E28431F6C6AC46625D472FF76336". 170 | */ 171 | function getSslSessionId(ssl) { 172 | var session = SSL_get_session(ssl); 173 | if (session == 0) { 174 | return 0; 175 | } 176 | var len = Memory.alloc(4); 177 | var p = SSL_SESSION_get_id(session, len); 178 | len = Memory.readU32(len); 179 | var session_id = ""; 180 | for (var i = 0; i < len; i++) { 181 | // Read a byte, convert it to a hex string (0xAB ==> "AB"), and append 182 | // it to session_id. 183 | session_id += 184 | ("0" + Memory.readU8(p.add(i)).toString(16).toUpperCase()).substr(-2); 185 | } 186 | return session_id; 187 | } 188 | 189 | Interceptor.attach(addresses["SSL_read"], 190 | { 191 | onEnter: function (args) { 192 | var message = getPortsAndAddresses(SSL_get_fd(args[0]), true); 193 | message["ssl_session_id"] = getSslSessionId(args[0]); 194 | message["function"] = "SSL_read"; 195 | message["stack"] = SSLstackread; 196 | this.message = message; 197 | this.buf = args[1]; 198 | }, 199 | onLeave: function (retval) { 200 | retval |= 0; // Cast retval to 32-bit integer. 201 | if (retval <= 0) { 202 | return; 203 | } 204 | send(this.message, Memory.readByteArray(this.buf, retval)); 205 | } 206 | }); 207 | 208 | Interceptor.attach(addresses["SSL_write"], 209 | { 210 | onEnter: function (args) { 211 | var message = getPortsAndAddresses(SSL_get_fd(args[0]), false); 212 | message["ssl_session_id"] = getSslSessionId(args[0]); 213 | message["function"] = "SSL_write"; 214 | message["stack"] = SSLstackwrite; 215 | send(message, Memory.readByteArray(args[1], parseInt(args[2]))); 216 | }, 217 | onLeave: function (retval) { 218 | } 219 | }); 220 | 221 | if (Java.available) { 222 | Java.perform(function () { 223 | function storeP12(pri, p7, p12Path, p12Password) { 224 | var X509Certificate = Java.use("java.security.cert.X509Certificate") 225 | var p7X509 = Java.cast(p7, X509Certificate); 226 | var chain = Java.array("java.security.cert.X509Certificate", [p7X509]) 227 | var ks = Java.use("java.security.KeyStore").getInstance("PKCS12", "BC"); 228 | ks.load(null, null); 229 | ks.setKeyEntry("client", pri, Java.use('java.lang.String').$new(p12Password).toCharArray(), chain); 230 | try { 231 | var out = Java.use("java.io.FileOutputStream").$new(p12Path); 232 | ks.store(out, Java.use('java.lang.String').$new(p12Password).toCharArray()) 233 | } catch (exp) { 234 | console.log(exp) 235 | } 236 | } 237 | //在服务器校验客户端的情形下,帮助dump客户端证书,并保存为p12的格式,证书密码为r0ysue 238 | Java.use("java.security.KeyStore$PrivateKeyEntry").getPrivateKey.implementation = function () { 239 | var result = this.getPrivateKey() 240 | var packageName = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext().getPackageName(); 241 | storeP12(this.getPrivateKey(), this.getCertificate(), '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12', 'r0ysue'); 242 | var message = {}; 243 | message["function"] = "dumpClinetCertificate=>" + '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12' + ' pwd: r0ysue'; 244 | message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()); 245 | var data = Memory.alloc(1); 246 | send(message, Memory.readByteArray(data, 1)) 247 | return result; 248 | } 249 | Java.use("java.security.KeyStore$PrivateKeyEntry").getCertificateChain.implementation = function () { 250 | var result = this.getCertificateChain() 251 | var packageName = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext().getPackageName(); 252 | storeP12(this.getPrivateKey(), this.getCertificate(), '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12', 'r0ysue'); 253 | var message = {}; 254 | message["function"] = "dumpClinetCertificate=>" + '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12' + ' pwd: r0ysue'; 255 | message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()); 256 | var data = Memory.alloc(1); 257 | send(message, Memory.readByteArray(data, 1)) 258 | return result; 259 | } 260 | 261 | //SSLpinning helper 帮助定位证书绑定的关键代码a 262 | Java.use("java.io.File").$init.overload('java.io.File', 'java.lang.String').implementation = function (file, cert) { 263 | var result = this.$init(file, cert) 264 | var stack = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()); 265 | if (file.getPath().indexOf("cacert") >= 0 && stack.indexOf("X509TrustManagerExtensions.checkServerTrusted") >= 0) { 266 | var message = {}; 267 | message["function"] = "SSLpinning position locator => " + file.getPath() + " " + cert; 268 | message["stack"] = stack; 269 | var data = Memory.alloc(1); 270 | send(message, Memory.readByteArray(data, 1)) 271 | } 272 | return result; 273 | } 274 | 275 | 276 | Java.use("java.net.SocketOutputStream").socketWrite0.overload('java.io.FileDescriptor', '[B', 'int', 'int').implementation = function (fd, bytearry, offset, byteCount) { 277 | var result = this.socketWrite0(fd, bytearry, offset, byteCount); 278 | var message = {}; 279 | message["function"] = "HTTP_send"; 280 | message["ssl_session_id"] = ""; 281 | message["src_addr"] = ntohl(ipToNumber((this.socket.value.getLocalAddress().toString().split(":")[0]).split("/").pop())); 282 | message["src_port"] = parseInt(this.socket.value.getLocalPort().toString()); 283 | message["dst_addr"] = ntohl(ipToNumber((this.socket.value.getRemoteSocketAddress().toString().split(":")[0]).split("/").pop())); 284 | message["dst_port"] = parseInt(this.socket.value.getRemoteSocketAddress().toString().split(":").pop()); 285 | message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString(); 286 | var ptr = Memory.alloc(byteCount); 287 | for (var i = 0; i < byteCount; ++i) 288 | Memory.writeS8(ptr.add(i), bytearry[offset + i]); 289 | send(message, Memory.readByteArray(ptr, byteCount)) 290 | return result; 291 | } 292 | Java.use("java.net.SocketInputStream").socketRead0.overload('java.io.FileDescriptor', '[B', 'int', 'int', 'int').implementation = function (fd, bytearry, offset, byteCount, timeout) { 293 | var result = this.socketRead0(fd, bytearry, offset, byteCount, timeout); 294 | var message = {}; 295 | message["function"] = "HTTP_recv"; 296 | message["ssl_session_id"] = ""; 297 | message["src_addr"] = ntohl(ipToNumber((this.socket.value.getRemoteSocketAddress().toString().split(":")[0]).split("/").pop())); 298 | message["src_port"] = parseInt(this.socket.value.getRemoteSocketAddress().toString().split(":").pop()); 299 | message["dst_addr"] = ntohl(ipToNumber((this.socket.value.getLocalAddress().toString().split(":")[0]).split("/").pop())); 300 | message["dst_port"] = parseInt(this.socket.value.getLocalPort()); 301 | message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString(); 302 | if (result > 0) { 303 | var ptr = Memory.alloc(result); 304 | for (var i = 0; i < result; ++i) 305 | Memory.writeS8(ptr.add(i), bytearry[offset + i]); 306 | send(message, Memory.readByteArray(ptr, result)) 307 | } 308 | return result; 309 | } 310 | 311 | if (parseFloat(Java.androidVersion) > 8) { 312 | Java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream").write.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) { 313 | var result = this.write(bytearry, int1, int2); 314 | SSLstackwrite = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString(); 315 | return result; 316 | } 317 | Java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream").read.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) { 318 | var result = this.read(bytearry, int1, int2); 319 | SSLstackread = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString(); 320 | return result; 321 | } 322 | } 323 | else { 324 | Java.use("com.android.org.conscrypt.OpenSSLSocketImpl$SSLOutputStream").write.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) { 325 | var result = this.write(bytearry, int1, int2); 326 | SSLstackwrite = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString(); 327 | return result; 328 | } 329 | Java.use("com.android.org.conscrypt.OpenSSLSocketImpl$SSLInputStream").read.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) { 330 | var result = this.read(bytearry, int1, int2); 331 | SSLstackread = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString(); 332 | return result; 333 | } 334 | 335 | } 336 | } 337 | 338 | ) 339 | } 340 | -------------------------------------------------------------------------------- /ssl_pinning_frida.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This script combines, fixes & extends a long list of other scripts, most notably including: 3 | * 4 | * - https://codeshare.frida.re/@akabe1/frida-multiple-unpinning/ 5 | * - https://codeshare.frida.re/@avltree9798/universal-android-ssl-pinning-bypass/ 6 | * - https://pastebin.com/TVJD63uM 7 | */ 8 | 9 | setTimeout(function () { 10 | Java.perform(function () { 11 | console.log("---"); 12 | console.log("Unpinning Android app..."); 13 | 14 | /// -- Generic hook to protect against SSLPeerUnverifiedException -- /// 15 | 16 | // In some cases, with unusual cert pinning approaches, or heavy obfuscation, we can't 17 | // match the real method & package names. This is a problem! Fortunately, we can still 18 | // always match built-in types, so here we spot all failures that use the built-in cert 19 | // error type (notably this includes OkHttp), and after the first failure, we dynamically 20 | // generate & inject a patch to completely disable the method that threw the error. 21 | try { 22 | const UnverifiedCertError = Java.use('javax.net.ssl.SSLPeerUnverifiedException'); 23 | UnverifiedCertError.$init.implementation = function (str) { 24 | console.log(' --> Unexpected SSL verification failure, adding dynamic patch...'); 25 | 26 | try { 27 | const stackTrace = Java.use('java.lang.Thread').currentThread().getStackTrace(); 28 | const exceptionStackIndex = stackTrace.findIndex(stack => 29 | stack.getClassName() === "javax.net.ssl.SSLPeerUnverifiedException" 30 | ); 31 | const callingFunctionStack = stackTrace[exceptionStackIndex + 1]; 32 | 33 | const className = callingFunctionStack.getClassName(); 34 | const methodName = callingFunctionStack.getMethodName(); 35 | 36 | console.log(` Thrown by ${className}->${methodName}`); 37 | 38 | const callingClass = Java.use(className); 39 | const callingMethod = callingClass[methodName]; 40 | 41 | if (callingMethod.implementation) return; // Already patched by Frida - skip it 42 | 43 | console.log(' Attempting to patch automatically...'); 44 | const returnTypeName = callingMethod.returnType.type; 45 | 46 | callingMethod.implementation = function () { 47 | console.log(` --> Bypassing ${className}->${methodName} (automatic exception patch)`); 48 | 49 | // This is not a perfect fix! Most unknown cases like this are really just 50 | // checkCert(cert) methods though, so doing nothing is perfect, and if we 51 | // do need an actual return value then this is probably the best we can do, 52 | // and at least we're logging the method name so you can patch it manually: 53 | 54 | if (returnTypeName === 'void') { 55 | return; 56 | } else { 57 | return null; 58 | } 59 | }; 60 | 61 | console.log(` [+] ${className}->${methodName} (automatic exception patch)`); 62 | } catch (e) { 63 | console.log(' [ ] Failed to automatically patch failure'); 64 | } 65 | 66 | return this.$init(str); 67 | }; 68 | console.log('[+] SSLPeerUnverifiedException auto-patcher'); 69 | } catch (err) { 70 | console.log('[ ] SSLPeerUnverifiedException auto-patcher'); 71 | } 72 | 73 | /// -- Specific targeted hooks: -- /// 74 | 75 | // HttpsURLConnection 76 | try { 77 | const HttpsURLConnection = Java.use("javax.net.ssl.HttpsURLConnection"); 78 | HttpsURLConnection.setDefaultHostnameVerifier.implementation = function (hostnameVerifier) { 79 | console.log(' --> Bypassing HttpsURLConnection (setDefaultHostnameVerifier)'); 80 | return; // Do nothing, i.e. don't change the hostname verifier 81 | }; 82 | console.log('[+] HttpsURLConnection (setDefaultHostnameVerifier)'); 83 | } catch (err) { 84 | console.log('[ ] HttpsURLConnection (setDefaultHostnameVerifier)'); 85 | } 86 | try { 87 | const HttpsURLConnection = Java.use("javax.net.ssl.HttpsURLConnection"); 88 | HttpsURLConnection.setSSLSocketFactory.implementation = function (SSLSocketFactory) { 89 | console.log(' --> Bypassing HttpsURLConnection (setSSLSocketFactory)'); 90 | return; // Do nothing, i.e. don't change the SSL socket factory 91 | }; 92 | console.log('[+] HttpsURLConnection (setSSLSocketFactory)'); 93 | } catch (err) { 94 | console.log('[ ] HttpsURLConnection (setSSLSocketFactory)'); 95 | } 96 | try { 97 | const HttpsURLConnection = Java.use("javax.net.ssl.HttpsURLConnection"); 98 | HttpsURLConnection.setHostnameVerifier.implementation = function (hostnameVerifier) { 99 | console.log(' --> Bypassing HttpsURLConnection (setHostnameVerifier)'); 100 | return; // Do nothing, i.e. don't change the hostname verifier 101 | }; 102 | console.log('[+] HttpsURLConnection (setHostnameVerifier)'); 103 | } catch (err) { 104 | console.log('[ ] HttpsURLConnection (setHostnameVerifier)'); 105 | } 106 | 107 | // SSLContext 108 | try { 109 | const X509TrustManager = Java.use('javax.net.ssl.X509TrustManager'); 110 | const SSLContext = Java.use('javax.net.ssl.SSLContext'); 111 | 112 | const TrustManager = Java.registerClass({ 113 | // Implement a custom TrustManager 114 | name: 'dev.asd.test.TrustManager', 115 | implements: [X509TrustManager], 116 | methods: { 117 | checkClientTrusted: function (chain, authType) { }, 118 | checkServerTrusted: function (chain, authType) { }, 119 | getAcceptedIssuers: function () { return []; } 120 | } 121 | }); 122 | 123 | // Prepare the TrustManager array to pass to SSLContext.init() 124 | const TrustManagers = [TrustManager.$new()]; 125 | 126 | // Get a handle on the init() on the SSLContext class 127 | const SSLContext_init = SSLContext.init.overload( 128 | '[Ljavax.net.ssl.KeyManager;', '[Ljavax.net.ssl.TrustManager;', 'java.security.SecureRandom' 129 | ); 130 | 131 | // Override the init method, specifying the custom TrustManager 132 | SSLContext_init.implementation = function (keyManager, trustManager, secureRandom) { 133 | console.log(' --> Bypassing Trustmanager (Android < 7) request'); 134 | SSLContext_init.call(this, keyManager, TrustManagers, secureRandom); 135 | }; 136 | console.log('[+] SSLContext'); 137 | } catch (err) { 138 | console.log('[ ] SSLContext'); 139 | } 140 | 141 | // TrustManagerImpl (Android > 7) 142 | try { 143 | const array_list = Java.use("java.util.ArrayList"); 144 | const TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl'); 145 | 146 | // This step is notably what defeats the most common case: network security config 147 | TrustManagerImpl.checkTrustedRecursive.implementation = function(a1, a2, a3, a4, a5, a6) { 148 | console.log(' --> Bypassing TrustManagerImpl checkTrusted '); 149 | return array_list.$new(); 150 | } 151 | 152 | TrustManagerImpl.verifyChain.implementation = function (untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) { 153 | console.log(' --> Bypassing TrustManagerImpl verifyChain: ' + host); 154 | return untrustedChain; 155 | }; 156 | console.log('[+] TrustManagerImpl'); 157 | } catch (err) { 158 | console.log('[ ] TrustManagerImpl'); 159 | } 160 | 161 | // OkHTTPv3 (quadruple bypass) 162 | try { 163 | // Bypass OkHTTPv3 {1} 164 | const okhttp3_Activity_1 = Java.use('okhttp3.CertificatePinner'); 165 | okhttp3_Activity_1.check.overload('java.lang.String', 'java.util.List').implementation = function (a, b) { 166 | console.log(' --> Bypassing OkHTTPv3 (list): ' + a); 167 | return; 168 | }; 169 | console.log('[+] OkHTTPv3 (list)'); 170 | } catch (err) { 171 | console.log('[ ] OkHTTPv3 (list)'); 172 | } 173 | try { 174 | // Bypass OkHTTPv3 {2} 175 | // This method of CertificatePinner.check could be found in some old Android app 176 | const okhttp3_Activity_2 = Java.use('okhttp3.CertificatePinner'); 177 | okhttp3_Activity_2.check.overload('java.lang.String', 'java.security.cert.Certificate').implementation = function (a, b) { 178 | console.log(' --> Bypassing OkHTTPv3 (cert): ' + a); 179 | return; 180 | }; 181 | console.log('[+] OkHTTPv3 (cert)'); 182 | } catch (err) { 183 | console.log('[ ] OkHTTPv3 (cert)'); 184 | } 185 | try { 186 | // Bypass OkHTTPv3 {3} 187 | const okhttp3_Activity_3 = Java.use('okhttp3.CertificatePinner'); 188 | okhttp3_Activity_3.check.overload('java.lang.String', '[Ljava.security.cert.Certificate;').implementation = function (a, b) { 189 | console.log(' --> Bypassing OkHTTPv3 (cert array): ' + a); 190 | return; 191 | }; 192 | console.log('[+] OkHTTPv3 (cert array)'); 193 | } catch (err) { 194 | console.log('[ ] OkHTTPv3 (cert array)'); 195 | } 196 | try { 197 | // Bypass OkHTTPv3 {4} 198 | const okhttp3_Activity_4 = Java.use('okhttp3.CertificatePinner'); 199 | okhttp3_Activity_4['check$okhttp'].implementation = function (a, b) { 200 | console.log(' --> Bypassing OkHTTPv3 ($okhttp): ' + a); 201 | return; 202 | }; 203 | console.log('[+] OkHTTPv3 ($okhttp)'); 204 | } catch (err) { 205 | console.log('[ ] OkHTTPv3 ($okhttp)'); 206 | } 207 | 208 | // Trustkit (triple bypass) 209 | try { 210 | // Bypass Trustkit {1} 211 | const trustkit_Activity_1 = Java.use('com.datatheorem.android.trustkit.pinning.OkHostnameVerifier'); 212 | trustkit_Activity_1.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function (a, b) { 213 | console.log(' --> Bypassing Trustkit OkHostnameVerifier(SSLSession): ' + a); 214 | return true; 215 | }; 216 | console.log('[+] Trustkit OkHostnameVerifier(SSLSession)'); 217 | } catch (err) { 218 | console.log('[ ] Trustkit OkHostnameVerifier(SSLSession)'); 219 | } 220 | try { 221 | // Bypass Trustkit {2} 222 | const trustkit_Activity_2 = Java.use('com.datatheorem.android.trustkit.pinning.OkHostnameVerifier'); 223 | trustkit_Activity_2.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function (a, b) { 224 | console.log(' --> Bypassing Trustkit OkHostnameVerifier(cert): ' + a); 225 | return true; 226 | }; 227 | console.log('[+] Trustkit OkHostnameVerifier(cert)'); 228 | } catch (err) { 229 | console.log('[ ] Trustkit OkHostnameVerifier(cert)'); 230 | } 231 | try { 232 | // Bypass Trustkit {3} 233 | const trustkit_PinningTrustManager = Java.use('com.datatheorem.android.trustkit.pinning.PinningTrustManager'); 234 | trustkit_PinningTrustManager.checkServerTrusted.implementation = function () { 235 | console.log(' --> Bypassing Trustkit PinningTrustManager'); 236 | }; 237 | console.log('[+] Trustkit PinningTrustManager'); 238 | } catch (err) { 239 | console.log('[ ] Trustkit PinningTrustManager'); 240 | } 241 | 242 | // Appcelerator Titanium 243 | try { 244 | const appcelerator_PinningTrustManager = Java.use('appcelerator.https.PinningTrustManager'); 245 | appcelerator_PinningTrustManager.checkServerTrusted.implementation = function () { 246 | console.log(' --> Bypassing Appcelerator PinningTrustManager'); 247 | }; 248 | console.log('[+] Appcelerator PinningTrustManager'); 249 | } catch (err) { 250 | console.log('[ ] Appcelerator PinningTrustManager'); 251 | } 252 | 253 | // OpenSSLSocketImpl Conscrypt 254 | try { 255 | const OpenSSLSocketImpl = Java.use('com.android.org.conscrypt.OpenSSLSocketImpl'); 256 | OpenSSLSocketImpl.verifyCertificateChain.implementation = function (certRefs, JavaObject, authMethod) { 257 | console.log(' --> Bypassing OpenSSLSocketImpl Conscrypt'); 258 | }; 259 | console.log('[+] OpenSSLSocketImpl Conscrypt'); 260 | } catch (err) { 261 | console.log('[ ] OpenSSLSocketImpl Conscrypt'); 262 | } 263 | 264 | // OpenSSLEngineSocketImpl Conscrypt 265 | try { 266 | const OpenSSLEngineSocketImpl_Activity = Java.use('com.android.org.conscrypt.OpenSSLEngineSocketImpl'); 267 | OpenSSLEngineSocketImpl_Activity.verifyCertificateChain.overload('[Ljava.lang.Long;', 'java.lang.String').implementation = function (a, b) { 268 | console.log(' --> Bypassing OpenSSLEngineSocketImpl Conscrypt: ' + b); 269 | }; 270 | console.log('[+] OpenSSLEngineSocketImpl Conscrypt'); 271 | } catch (err) { 272 | console.log('[ ] OpenSSLEngineSocketImpl Conscrypt'); 273 | } 274 | 275 | // OpenSSLSocketImpl Apache Harmony 276 | try { 277 | const OpenSSLSocketImpl_Harmony = Java.use('org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl'); 278 | OpenSSLSocketImpl_Harmony.verifyCertificateChain.implementation = function (asn1DerEncodedCertificateChain, authMethod) { 279 | console.log(' --> Bypassing OpenSSLSocketImpl Apache Harmony'); 280 | }; 281 | console.log('[+] OpenSSLSocketImpl Apache Harmony'); 282 | } catch (err) { 283 | console.log('[ ] OpenSSLSocketImpl Apache Harmony'); 284 | } 285 | 286 | // PhoneGap sslCertificateChecker (https://github.com/EddyVerbruggen/SSLCertificateChecker-PhoneGap-Plugin) 287 | try { 288 | const phonegap_Activity = Java.use('nl.xservices.plugins.sslCertificateChecker'); 289 | phonegap_Activity.execute.overload('java.lang.String', 'org.json.JSONArray', 'org.apache.cordova.CallbackContext').implementation = function (a, b, c) { 290 | console.log(' --> Bypassing PhoneGap sslCertificateChecker: ' + a); 291 | return true; 292 | }; 293 | console.log('[+] PhoneGap sslCertificateChecker'); 294 | } catch (err) { 295 | console.log('[ ] PhoneGap sslCertificateChecker'); 296 | } 297 | 298 | // IBM MobileFirst pinTrustedCertificatePublicKey (double bypass) 299 | try { 300 | // Bypass IBM MobileFirst {1} 301 | const WLClient_Activity_1 = Java.use('com.worklight.wlclient.api.WLClient'); 302 | WLClient_Activity_1.getInstance().pinTrustedCertificatePublicKey.overload('java.lang.String').implementation = function (cert) { 303 | console.log(' --> Bypassing IBM MobileFirst pinTrustedCertificatePublicKey (string): ' + cert); 304 | return; 305 | }; 306 | console.log('[+] IBM MobileFirst pinTrustedCertificatePublicKey (string)'); 307 | } catch (err) { 308 | console.log('[ ] IBM MobileFirst pinTrustedCertificatePublicKey (string)'); 309 | } 310 | try { 311 | // Bypass IBM MobileFirst {2} 312 | const WLClient_Activity_2 = Java.use('com.worklight.wlclient.api.WLClient'); 313 | WLClient_Activity_2.getInstance().pinTrustedCertificatePublicKey.overload('[Ljava.lang.String;').implementation = function (cert) { 314 | console.log(' --> Bypassing IBM MobileFirst pinTrustedCertificatePublicKey (string array): ' + cert); 315 | return; 316 | }; 317 | console.log('[+] IBM MobileFirst pinTrustedCertificatePublicKey (string array)'); 318 | } catch (err) { 319 | console.log('[ ] IBM MobileFirst pinTrustedCertificatePublicKey (string array)'); 320 | } 321 | 322 | // IBM WorkLight (ancestor of MobileFirst) HostNameVerifierWithCertificatePinning (quadruple bypass) 323 | try { 324 | // Bypass IBM WorkLight {1} 325 | const worklight_Activity_1 = Java.use('com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning'); 326 | worklight_Activity_1.verify.overload('java.lang.String', 'javax.net.ssl.SSLSocket').implementation = function (a, b) { 327 | console.log(' --> Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSocket): ' + a); 328 | return; 329 | }; 330 | console.log('[+] IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSocket)'); 331 | } catch (err) { 332 | console.log('[ ] IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSocket)'); 333 | } 334 | try { 335 | // Bypass IBM WorkLight {2} 336 | const worklight_Activity_2 = Java.use('com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning'); 337 | worklight_Activity_2.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function (a, b) { 338 | console.log(' --> Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning (cert): ' + a); 339 | return; 340 | }; 341 | console.log('[+] IBM WorkLight HostNameVerifierWithCertificatePinning (cert)'); 342 | } catch (err) { 343 | console.log('[ ] IBM WorkLight HostNameVerifierWithCertificatePinning (cert)'); 344 | } 345 | try { 346 | // Bypass IBM WorkLight {3} 347 | const worklight_Activity_3 = Java.use('com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning'); 348 | worklight_Activity_3.verify.overload('java.lang.String', '[Ljava.lang.String;', '[Ljava.lang.String;').implementation = function (a, b) { 349 | console.log(' --> Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning (string string): ' + a); 350 | return; 351 | }; 352 | console.log('[+] IBM WorkLight HostNameVerifierWithCertificatePinning (string string)'); 353 | } catch (err) { 354 | console.log('[ ] IBM WorkLight HostNameVerifierWithCertificatePinning (string string)'); 355 | } 356 | try { 357 | // Bypass IBM WorkLight {4} 358 | const worklight_Activity_4 = Java.use('com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning'); 359 | worklight_Activity_4.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function (a, b) { 360 | console.log(' --> Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSession): ' + a); 361 | return true; 362 | }; 363 | console.log('[+] IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSession)'); 364 | } catch (err) { 365 | console.log('[ ] IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSession)'); 366 | } 367 | 368 | // Conscrypt CertPinManager 369 | try { 370 | const conscrypt_CertPinManager_Activity = Java.use('com.android.org.conscrypt.CertPinManager'); 371 | conscrypt_CertPinManager_Activity.isChainValid.overload('java.lang.String', 'java.util.List').implementation = function (a, b) { 372 | console.log(' --> Bypassing Conscrypt CertPinManager: ' + a); 373 | return true; 374 | }; 375 | console.log('[+] Conscrypt CertPinManager'); 376 | } catch (err) { 377 | console.log('[ ] Conscrypt CertPinManager'); 378 | } 379 | 380 | // CWAC-Netsecurity (unofficial back-port pinner for Android<4.2) CertPinManager 381 | try { 382 | const cwac_CertPinManager_Activity = Java.use('com.commonsware.cwac.netsecurity.conscrypt.CertPinManager'); 383 | cwac_CertPinManager_Activity.isChainValid.overload('java.lang.String', 'java.util.List').implementation = function (a, b) { 384 | console.log(' --> Bypassing CWAC-Netsecurity CertPinManager: ' + a); 385 | return true; 386 | }; 387 | console.log('[+] CWAC-Netsecurity CertPinManager'); 388 | } catch (err) { 389 | console.log('[ ] CWAC-Netsecurity CertPinManager'); 390 | } 391 | 392 | // Worklight Androidgap WLCertificatePinningPlugin 393 | try { 394 | const androidgap_WLCertificatePinningPlugin_Activity = Java.use('com.worklight.androidgap.plugin.WLCertificatePinningPlugin'); 395 | androidgap_WLCertificatePinningPlugin_Activity.execute.overload('java.lang.String', 'org.json.JSONArray', 'org.apache.cordova.CallbackContext').implementation = function (a, b, c) { 396 | console.log(' --> Bypassing Worklight Androidgap WLCertificatePinningPlugin: ' + a); 397 | return true; 398 | }; 399 | console.log('[+] Worklight Androidgap WLCertificatePinningPlugin'); 400 | } catch (err) { 401 | console.log('[ ] Worklight Androidgap WLCertificatePinningPlugin'); 402 | } 403 | 404 | // Netty FingerprintTrustManagerFactory 405 | try { 406 | const netty_FingerprintTrustManagerFactory = Java.use('io.netty.handler.ssl.util.FingerprintTrustManagerFactory'); 407 | netty_FingerprintTrustManagerFactory.checkTrusted.implementation = function (type, chain) { 408 | console.log(' --> Bypassing Netty FingerprintTrustManagerFactory'); 409 | }; 410 | console.log('[+] Netty FingerprintTrustManagerFactory'); 411 | } catch (err) { 412 | console.log('[ ] Netty FingerprintTrustManagerFactory'); 413 | } 414 | 415 | // Squareup CertificatePinner [OkHTTP Bypassing Squareup CertificatePinner (cert): ' + a); 421 | return; 422 | }; 423 | console.log('[+] Squareup CertificatePinner (cert)'); 424 | } catch (err) { 425 | console.log('[ ] Squareup CertificatePinner (cert)'); 426 | } 427 | try { 428 | // Bypass Squareup CertificatePinner {2} 429 | const Squareup_CertificatePinner_Activity_2 = Java.use('com.squareup.okhttp.CertificatePinner'); 430 | Squareup_CertificatePinner_Activity_2.check.overload('java.lang.String', 'java.util.List').implementation = function (a, b) { 431 | console.log(' --> Bypassing Squareup CertificatePinner (list): ' + a); 432 | return; 433 | }; 434 | console.log('[+] Squareup CertificatePinner (list)'); 435 | } catch (err) { 436 | console.log('[ ] Squareup CertificatePinner (list)'); 437 | } 438 | 439 | // Squareup OkHostnameVerifier [OkHTTP v3] (double bypass) 440 | try { 441 | // Bypass Squareup OkHostnameVerifier {1} 442 | const Squareup_OkHostnameVerifier_Activity_1 = Java.use('com.squareup.okhttp.internal.tls.OkHostnameVerifier'); 443 | Squareup_OkHostnameVerifier_Activity_1.verify.overload('java.lang.String', 'java.security.cert.X509Certificate').implementation = function (a, b) { 444 | console.log(' --> Bypassing Squareup OkHostnameVerifier (cert): ' + a); 445 | return true; 446 | }; 447 | console.log('[+] Squareup OkHostnameVerifier (cert)'); 448 | } catch (err) { 449 | console.log('[ ] Squareup OkHostnameVerifier (cert)'); 450 | } 451 | try { 452 | // Bypass Squareup OkHostnameVerifier {2} 453 | const Squareup_OkHostnameVerifier_Activity_2 = Java.use('com.squareup.okhttp.internal.tls.OkHostnameVerifier'); 454 | Squareup_OkHostnameVerifier_Activity_2.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function (a, b) { 455 | console.log(' --> Bypassing Squareup OkHostnameVerifier (SSLSession): ' + a); 456 | return true; 457 | }; 458 | console.log('[+] Squareup OkHostnameVerifier (SSLSession)'); 459 | } catch (err) { 460 | console.log('[ ] Squareup OkHostnameVerifier (SSLSession)'); 461 | } 462 | 463 | // Android WebViewClient (double bypass) 464 | try { 465 | // Bypass WebViewClient {1} (deprecated from Android 6) 466 | const AndroidWebViewClient_Activity_1 = Java.use('android.webkit.WebViewClient'); 467 | AndroidWebViewClient_Activity_1.onReceivedSslError.overload('android.webkit.WebView', 'android.webkit.SslErrorHandler', 'android.net.http.SslError').implementation = function (obj1, obj2, obj3) { 468 | console.log(' --> Bypassing Android WebViewClient (SslErrorHandler)'); 469 | }; 470 | console.log('[+] Android WebViewClient (SslErrorHandler)'); 471 | } catch (err) { 472 | console.log('[ ] Android WebViewClient (SslErrorHandler)'); 473 | } 474 | try { 475 | // Bypass WebViewClient {2} 476 | const AndroidWebViewClient_Activity_2 = Java.use('android.webkit.WebViewClient'); 477 | AndroidWebViewClient_Activity_2.onReceivedSslError.overload('android.webkit.WebView', 'android.webkit.WebResourceRequest', 'android.webkit.WebResourceError').implementation = function (obj1, obj2, obj3) { 478 | console.log(' --> Bypassing Android WebViewClient (WebResourceError)'); 479 | }; 480 | console.log('[+] Android WebViewClient (WebResourceError)'); 481 | } catch (err) { 482 | console.log('[ ] Android WebViewClient (WebResourceError)'); 483 | } 484 | 485 | // Apache Cordova WebViewClient 486 | try { 487 | const CordovaWebViewClient_Activity = Java.use('org.apache.cordova.CordovaWebViewClient'); 488 | CordovaWebViewClient_Activity.onReceivedSslError.overload('android.webkit.WebView', 'android.webkit.SslErrorHandler', 'android.net.http.SslError').implementation = function (obj1, obj2, obj3) { 489 | console.log(' --> Bypassing Apache Cordova WebViewClient'); 490 | obj3.proceed(); 491 | }; 492 | } catch (err) { 493 | console.log('[ ] Apache Cordova WebViewClient'); 494 | } 495 | 496 | // Boye AbstractVerifier 497 | try { 498 | const boye_AbstractVerifier = Java.use('ch.boye.httpclientandroidlib.conn.ssl.AbstractVerifier'); 499 | boye_AbstractVerifier.verify.implementation = function (host, ssl) { 500 | console.log(' --> Bypassing Boye AbstractVerifier: ' + host); 501 | }; 502 | } catch (err) { 503 | console.log('[ ] Boye AbstractVerifier'); 504 | } 505 | 506 | console.log("Unpinning setup completed"); 507 | console.log("---"); 508 | }); 509 | 510 | }, 0); -------------------------------------------------------------------------------- /tongsha_hook.js: -------------------------------------------------------------------------------- 1 | //自吐 2 | Java.perform(function () { 3 | 4 | function showStacks() { 5 | console.log( 6 | Java.use("android.util.Log") 7 | .getStackTraceString( 8 | Java.use("java.lang.Throwable").$new() 9 | ) 10 | ); 11 | } 12 | 13 | var ByteString = Java.use("com.android.okhttp.okio.ByteString"); 14 | 15 | function toBase64(tag, data) { 16 | console.log(tag + " Base64: ", ByteString.of(data).base64()); 17 | } 18 | 19 | function toHex(tag, data) { 20 | console.log(tag + " Hex: ", ByteString.of(data).hex()); 21 | } 22 | 23 | function toUtf8(tag, data) { 24 | console.log(tag + " Utf8: ", ByteString.of(data).utf8()); 25 | } 26 | 27 | 28 | // MD5/SHA等 29 | var messageDigest = Java.use("java.security.MessageDigest"); 30 | messageDigest.update.overload('byte').implementation = function (data) { 31 | showStacks() 32 | console.log("MessageDigest.update('byte') is called!"); 33 | return this.update(data); 34 | } 35 | messageDigest.update.overload('java.nio.ByteBuffer').implementation = function (data) { 36 | showStacks() 37 | console.log("MessageDigest.update('java.nio.ByteBuffer') is called!"); 38 | return this.update(data); 39 | } 40 | messageDigest.update.overload('[B').implementation = function (data) { 41 | showStacks() 42 | console.log("MessageDigest.update('[B') is called!"); 43 | var algorithm = this.getAlgorithm(); 44 | var tag = algorithm + " update data"; 45 | toUtf8(tag, data); 46 | toHex(tag, data); 47 | toBase64(tag, data); 48 | console.log("======================================================="); 49 | return this.update(data); 50 | } 51 | messageDigest.update.overload('[B', 'int', 'int').implementation = function (data, start, length) { 52 | showStacks() 53 | console.log("MessageDigest.update('[B', 'int', 'int') is called!"); 54 | var algorithm = this.getAlgorithm(); 55 | var tag = algorithm + " update data"; 56 | toUtf8(tag, data); 57 | toHex(tag, data); 58 | toBase64(tag, data); 59 | console.log("=======================================================", start, length); 60 | return this.update(data, start, length); 61 | } 62 | messageDigest.digest.overload().implementation = function () { 63 | showStacks() 64 | console.log("MessageDigest.digest() is called!"); 65 | var result = this.digest(); 66 | var algorithm = this.getAlgorithm(); 67 | var tag = algorithm + " digest result"; 68 | toHex(tag, result); 69 | toBase64(tag, result); 70 | console.log("======================================================="); 71 | return result; 72 | } 73 | messageDigest.digest.overload('[B').implementation = function (data) { 74 | showStacks() 75 | console.log("MessageDigest.digest('[B') is called!"); 76 | var algorithm = this.getAlgorithm(); 77 | var tag = algorithm + " digest data"; 78 | toUtf8(tag, data); 79 | toHex(tag, data); 80 | toBase64(tag, data); 81 | var result = this.digest(data); 82 | var tags = algorithm + " digest result"; 83 | toHex(tags, result); 84 | toBase64(tags, result); 85 | console.log("======================================================="); 86 | return result; 87 | } 88 | messageDigest.digest.overload('[B', 'int', 'int').implementation = function (data, start, length) { 89 | showStacks() 90 | console.log("MessageDigest.digest('[B', 'int', 'int') is called!"); 91 | var algorithm = this.getAlgorithm(); 92 | var tag = algorithm + " digest data"; 93 | toUtf8(tag, data); 94 | toHex(tag, data); 95 | toBase64(tag, data); 96 | var result = this.digest(data, start, length); 97 | var tags = algorithm + " digest result"; 98 | toHex(tags, result); 99 | toBase64(tags, result); 100 | console.log("=======================================================", start, length); 101 | return result; 102 | } 103 | 104 | //Mac 105 | var mac = Java.use("javax.crypto.Mac"); 106 | mac.init.overload('java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function (key, AlgorithmParameterSpec) { 107 | showStacks() 108 | console.log("Mac.init('java.security.Key', 'java.security.spec.AlgorithmParameterSpec') is called!"); 109 | return this.init(key, AlgorithmParameterSpec); 110 | } 111 | mac.init.overload('java.security.Key').implementation = function (key) { 112 | showStacks() 113 | console.log("Mac.init('java.security.Key') is called!"); 114 | var algorithm = this.getAlgorithm(); 115 | var tag = algorithm + " init Key"; 116 | var keyBytes = key.getEncoded(); 117 | toUtf8(tag, keyBytes); 118 | toHex(tag, keyBytes); 119 | toBase64(tag, keyBytes); 120 | console.log("======================================================="); 121 | return this.init(key); 122 | } 123 | mac.update.overload('byte').implementation = function (data) { 124 | showStacks() 125 | console.log("Mac.update('byte') is called!"); 126 | return this.update(data); 127 | } 128 | mac.update.overload('java.nio.ByteBuffer').implementation = function (data) { 129 | showStacks() 130 | console.log("Mac.update('java.nio.ByteBuffer') is called!"); 131 | return this.update(data); 132 | } 133 | mac.update.overload('[B').implementation = function (data) { 134 | showStacks() 135 | console.log("Mac.update('[B') is called!"); 136 | var algorithm = this.getAlgorithm(); 137 | var tag = algorithm + " update data"; 138 | toUtf8(tag, data); 139 | toHex(tag, data); 140 | toBase64(tag, data); 141 | console.log("======================================================="); 142 | return this.update(data); 143 | } 144 | mac.update.overload('[B', 'int', 'int').implementation = function (data, start, length) { 145 | showStacks() 146 | console.log("Mac.update('[B', 'int', 'int') is called!"); 147 | var algorithm = this.getAlgorithm(); 148 | var tag = algorithm + " update data"; 149 | toUtf8(tag, data); 150 | toHex(tag, data); 151 | toBase64(tag, data); 152 | console.log("=======================================================", start, length); 153 | return this.update(data, start, length); 154 | } 155 | mac.doFinal.overload().implementation = function () { 156 | showStacks() 157 | console.log("Mac.doFinal() is called!"); 158 | var result = this.doFinal(); 159 | var algorithm = this.getAlgorithm(); 160 | var tag = algorithm + " doFinal result"; 161 | toHex(tag, result); 162 | toBase64(tag, result); 163 | console.log("======================================================="); 164 | return result; 165 | } 166 | 167 | //cipher 168 | var cipher = Java.use("javax.crypto.Cipher"); 169 | cipher.init.overload('int', 'java.security.cert.Certificate').implementation = function () { 170 | showStacks() 171 | console.log("Cipher.init('int', 'java.security.cert.Certificate') is called!"); 172 | return this.init.apply(this, arguments); 173 | } 174 | cipher.init.overload('int', 'java.security.Key', 'java.security.SecureRandom').implementation = function () { 175 | showStacks() 176 | console.log("Cipher.init('int', 'java.security.Key', 'java.security.SecureRandom') is called!"); 177 | return this.init.apply(this, arguments); 178 | } 179 | cipher.init.overload('int', 'java.security.cert.Certificate', 'java.security.SecureRandom').implementation = function () { 180 | showStacks() 181 | console.log("Cipher.init('int', 'java.security.cert.Certificate', 'java.security.SecureRandom') is called!"); 182 | return this.init.apply(this, arguments); 183 | } 184 | cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom').implementation = function () { 185 | showStacks() 186 | console.log("Cipher.init('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom') is called!"); 187 | return this.init.apply(this, arguments); 188 | } 189 | cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom').implementation = function () { 190 | showStacks() 191 | console.log("Cipher.init('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom') is called!"); 192 | return this.init.apply(this, arguments); 193 | } 194 | cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters').implementation = function () { 195 | showStacks() 196 | console.log("Cipher.init('int', 'java.security.Key', 'java.security.AlgorithmParameters') is called!"); 197 | return this.init.apply(this, arguments); 198 | } 199 | 200 | cipher.init.overload('int', 'java.security.Key').implementation = function () { 201 | showStacks() 202 | console.log("Cipher.init('int', 'java.security.Key') is called!"); 203 | var algorithm = this.getAlgorithm(); 204 | var tag = algorithm + " init Key"; 205 | var className = JSON.stringify(arguments[1]); 206 | if (className.indexOf("OpenSSLRSAPrivateKey") === -1) { 207 | var keyBytes = arguments[1].getEncoded(); 208 | toUtf8(tag, keyBytes); 209 | toHex(tag, keyBytes); 210 | toBase64(tag, keyBytes); 211 | } 212 | console.log("======================================================="); 213 | return this.init.apply(this, arguments); 214 | } 215 | cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function () { 216 | showStacks() 217 | console.log("Cipher.init('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec') is called!"); 218 | var algorithm = this.getAlgorithm(); 219 | var tag = algorithm + " init Key"; 220 | var keyBytes = arguments[1].getEncoded(); 221 | toUtf8(tag, keyBytes); 222 | toHex(tag, keyBytes); 223 | toBase64(tag, keyBytes); 224 | var tags = algorithm + " init iv"; 225 | var iv = Java.cast(arguments[2], Java.use("javax.crypto.spec.IvParameterSpec")); 226 | var ivBytes = iv.getIV(); 227 | toUtf8(tags, ivBytes); 228 | toHex(tags, ivBytes); 229 | toBase64(tags, ivBytes); 230 | console.log("======================================================="); 231 | return this.init.apply(this, arguments); 232 | } 233 | 234 | cipher.doFinal.overload('java.nio.ByteBuffer', 'java.nio.ByteBuffer').implementation = function () { 235 | showStacks(); 236 | console.log("Cipher.doFinal('java.nio.ByteBuffer', 'java.nio.ByteBuffer') is called!"); 237 | return this.doFinal.apply(this, arguments); 238 | } 239 | cipher.doFinal.overload('[B', 'int').implementation = function () { 240 | showStacks(); 241 | console.log("Cipher.doFinal('[B', 'int') is called!"); 242 | return this.doFinal.apply(this, arguments); 243 | } 244 | cipher.doFinal.overload('[B', 'int', 'int', '[B').implementation = function () { 245 | showStacks(); 246 | console.log("Cipher.doFinal('[B', 'int', 'int', '[B') is called!"); 247 | return this.doFinal.apply(this, arguments); 248 | } 249 | cipher.doFinal.overload('[B', 'int', 'int', '[B', 'int').implementation = function () { 250 | showStacks(); 251 | console.log("Cipher.doFinal('[B', 'int', 'int', '[B', 'int') is called!"); 252 | return this.doFinal.apply(this, arguments); 253 | } 254 | cipher.doFinal.overload().implementation = function () { 255 | showStacks(); 256 | console.log("Cipher.doFinal() is called!"); 257 | return this.doFinal.apply(this, arguments); 258 | } 259 | 260 | cipher.doFinal.overload('[B').implementation = function () { 261 | console.log("Cipher.doFinal('[B') is called!"); 262 | showStacks(); 263 | var algorithm = this.getAlgorithm(); 264 | var tag = algorithm + " doFinal data"; 265 | var data = arguments[0]; 266 | toUtf8(tag, data); 267 | toHex(tag, data); 268 | toBase64(tag, data); 269 | var result = this.doFinal.apply(this, arguments); 270 | var tags = algorithm + " doFinal result"; 271 | toHex(tags, result); 272 | toBase64(tags, result); 273 | console.log("======================================================="); 274 | return result; 275 | } 276 | cipher.doFinal.overload('[B', 'int', 'int').implementation = function () { 277 | console.log("Cipher.doFinal('[B', 'int', 'int') is called!"); 278 | showStacks(); 279 | var algorithm = this.getAlgorithm(); 280 | var tag = algorithm + " doFinal data"; 281 | var data = arguments[0]; 282 | toUtf8(tag, data); 283 | toHex(tag, data); 284 | toBase64(tag, data); 285 | var result = this.doFinal.apply(this, arguments); 286 | var tags = algorithm + " doFinal result"; 287 | toHex(tags, result); 288 | toBase64(tags, result); 289 | console.log("=======================================================", arguments[1], arguments[2]); 290 | return result; 291 | } 292 | 293 | //signature 294 | var signature = Java.use("java.security.Signature"); 295 | signature.update.overload('byte').implementation = function (data) { 296 | showStacks() 297 | console.log("Signature.update('byte') is called!"); 298 | return this.update(data); 299 | } 300 | signature.update.overload('java.nio.ByteBuffer').implementation = function (data) { 301 | showStacks() 302 | console.log("Signature.update('java.nio.ByteBuffer') is called!"); 303 | return this.update(data); 304 | } 305 | signature.update.overload('[B', 'int', 'int').implementation = function (data, start, length) { 306 | showStacks() 307 | console.log("Signature.update('[B', 'int', 'int') is called!"); 308 | var algorithm = this.getAlgorithm(); 309 | var tag = algorithm + " update data"; 310 | toUtf8(tag, data); 311 | toHex(tag, data); 312 | toBase64(tag, data); 313 | console.log("=======================================================", start, length); 314 | return this.update(data, start, length); 315 | } 316 | signature.sign.overload('[B', 'int', 'int').implementation = function () { 317 | showStacks() 318 | console.log("Signature.sign('[B', 'int', 'int') is called!"); 319 | return this.sign.apply(this, arguments); 320 | } 321 | signature.sign.overload().implementation = function () { 322 | showStacks() 323 | console.log("Signature.sign() is called!"); 324 | var result = this.sign(); 325 | var algorithm = this.getAlgorithm(); 326 | var tag = algorithm + " sign result"; 327 | toHex(tag, result); 328 | toBase64(tag, result); 329 | console.log("======================================================="); 330 | return result; 331 | } 332 | 333 | }); 334 | --------------------------------------------------------------------------------