├── README.md ├── bypass.js └── flutter-bypass.js /README.md: -------------------------------------------------------------------------------- 1 | # Frida-Multiple-Bypass 2 | Frida Multiple Bypass (SSL Pinning + Root DEtection + Emulator Detection ) 3 | 4 | 5 | ```bash 6 | frida -U -f com.package.name -l bypass.js 7 | ``` 8 | 9 | or using codeshare 10 | 11 | ```bash 12 | frida --codeshare fdciabdul/frida-multiple-bypass -f com.package.name 13 | ``` 14 | ## Stargazers over time 15 | [![Stargazers over time](https://starchart.cc/fdciabdul/Frida-Multiple-Bypass.svg?variant=adaptive)](https://starchart.cc/fdciabdul/Frida-Multiple-Bypass) 16 | -------------------------------------------------------------------------------- /bypass.js: -------------------------------------------------------------------------------- 1 | const commonPaths = [ 2 | "/data/local/bin/su", 3 | "/data/local/su", 4 | "/data/local/xbin/su", 5 | "/dev/com.koushikdutta.superuser.daemon/", 6 | "/sbin/su", 7 | "/system/app/Superuser.apk", 8 | "/system/bin/failsafe/su", 9 | "/system/bin/su", 10 | "/su/bin/su", 11 | "/system/etc/init.d/99SuperSUDaemon", 12 | "/system/sd/xbin/su", 13 | "/system/xbin/busybox", 14 | "/system/xbin/daemonsu", 15 | "/system/xbin/su", 16 | "/system/sbin/su", 17 | "/vendor/bin/su", 18 | "/cache/su", 19 | "/data/su", 20 | "/dev/su", 21 | "/system/bin/.ext/su", 22 | "/system/usr/we-need-root/su", 23 | "/system/app/Kinguser.apk", 24 | "/data/adb/magisk", 25 | "/sbin/.magisk", 26 | "/cache/.disable_magisk", 27 | "/dev/.magisk.unblock", 28 | "/cache/magisk.log", 29 | "/data/adb/magisk.img", 30 | "/data/adb/magisk.db", 31 | "/data/adb/magisk_simple", 32 | "/init.magisk.rc", 33 | "/system/xbin/ku.sud", 34 | "/data/adb/ksu", 35 | "/data/adb/ksud", 36 | ]; 37 | 38 | const ROOTmanagementApp = [ 39 | "com.noshufou.android.su", 40 | "com.noshufou.android.su.elite", 41 | "eu.chainfire.supersu", 42 | "com.koushikdutta.superuser", 43 | "com.thirdparty.superuser", 44 | "com.yellowes.su", 45 | "com.koushikdutta.rommanager", 46 | "com.koushikdutta.rommanager.license", 47 | "com.dimonvideo.luckypatcher", 48 | "com.chelpus.lackypatch", 49 | "com.ramdroid.appquarantine", 50 | "com.ramdroid.appquarantinepro", 51 | "com.topjohnwu.magisk", 52 | "me.weishu.kernelsu", 53 | ]; 54 | 55 | /** 56 | * Bypass Emulator Detection 57 | * @param {any} function( 58 | * @returns {any} 59 | */ 60 | Java.perform(function () { 61 | 62 | Java.use("android.os.Build").PRODUCT.value = "gracerltexx"; 63 | Java.use("android.os.Build").MANUFACTURER.value = "samsung"; 64 | Java.use("android.os.Build").BRAND.value = "samsung"; 65 | Java.use("android.os.Build").DEVICE.value = "gracerlte"; 66 | Java.use("android.os.Build").MODEL.value = "SM-N935F"; 67 | Java.use("android.os.Build").HARDWARE.value = "samsungexynos8890"; 68 | Java.use("android.os.Build").FINGERPRINT.value = 69 | "samsung/gracerltexx/gracerlte:8.0.0/R16NW/N935FXXS4BRK2:user/release-keys"; 70 | 71 | 72 | try { 73 | Java.use("java.io.File").exists.implementation = function () { 74 | var name = Java.use("java.io.File").getName.call(this); 75 | var catched = 76 | ["qemud", "qemu_pipe", "drivers", "cpuinfo"].indexOf(name) > -1; 77 | if (catched) { 78 | console.log("the pipe " + name + " existence is hooked"); 79 | return false; 80 | } else { 81 | return this.exists.call(this); 82 | } 83 | }; 84 | } catch (err) { 85 | console.log("[-] java.io.File.exists never called [-]"); 86 | } 87 | 88 | // rename the package names 89 | try { 90 | Java.use("android.app.ApplicationPackageManager").getPackageInfo.overload( 91 | "java.lang.String", 92 | "int" 93 | ).implementation = function (name, flag) { 94 | var catched = 95 | ["com.example.android.apis", "com.android.development"].indexOf(name) > 96 | -1; 97 | if (catched) { 98 | console.log("the package " + name + " is renamed with fake name"); 99 | name = "fake.package.name"; 100 | } 101 | return this.getPackageInfo.call(this, name, flag); 102 | }; 103 | } catch (err) { 104 | console.log( 105 | "[-] ApplicationPackageManager.getPackageInfo never called [-]" 106 | ); 107 | } 108 | 109 | // hook the `android_getCpuFamily` method 110 | // https://android.googlesource.com/platform/ndk/+/master/sources/android/cpufeatures/cpu-features.c#1067 111 | // Note: If you pass "null" as the first parameter for "Module.findExportByName" it will search in all modules 112 | try { 113 | Interceptor.attach(Module.findExportByName(null, "android_getCpuFamily"), { 114 | onLeave: function (retval) { 115 | // const int ANDROID_CPU_FAMILY_X86 = 2; 116 | // const int ANDROID_CPU_FAMILY_X86_64 = 5; 117 | if ([2, 5].indexOf(retval) > -1) { 118 | // const int ANDROID_CPU_FAMILY_ARM64 = 4; 119 | retval.replace(4); 120 | } 121 | }, 122 | }); 123 | } catch (err) { 124 | console.log("[-] android_getCpuFamily never called [-]"); 125 | // TODO: trace RegisterNatives in case the libraries are stripped. 126 | } 127 | }); 128 | 129 | /** 130 | * Bypass Root Detection 131 | * @param {any} function( 132 | * @returns {any} 133 | */ 134 | setTimeout(function () { 135 | function stackTraceHere(isLog) { 136 | var Exception = Java.use("java.lang.Exception"); 137 | var Log = Java.use("android.util.Log"); 138 | var stackinfo = Log.getStackTraceString(Exception.$new()); 139 | if (isLog) { 140 | console.log(stackinfo); 141 | } else { 142 | return stackinfo; 143 | } 144 | } 145 | 146 | function stackTraceNativeHere(isLog) { 147 | var backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE) 148 | .map(DebugSymbol.fromAddress) 149 | .join("\n\t"); 150 | console.log(backtrace); 151 | } 152 | 153 | function bypassJavaFileCheck() { 154 | var UnixFileSystem = Java.use("java.io.UnixFileSystem"); 155 | UnixFileSystem.checkAccess.implementation = function (file, access) { 156 | var stack = stackTraceHere(false); 157 | 158 | const filename = file.getAbsolutePath(); 159 | 160 | if (filename.indexOf("magisk") >= 0) { 161 | console.log("Anti Root Detect - check file: " + filename); 162 | return false; 163 | } 164 | 165 | if (commonPaths.indexOf(filename) >= 0) { 166 | console.log("Anti Root Detect - check file: " + filename); 167 | return false; 168 | } 169 | 170 | return this.checkAccess(file, access); 171 | }; 172 | } 173 | 174 | function bypassNativeFileCheck() { 175 | var fopen = Module.findExportByName("libc.so", "fopen"); 176 | Interceptor.attach(fopen, { 177 | onEnter: function (args) { 178 | this.inputPath = args[0].readUtf8String(); 179 | }, 180 | onLeave: function (retval) { 181 | if (retval.toInt32() != 0) { 182 | if (commonPaths.indexOf(this.inputPath) >= 0) { 183 | console.log("Anti Root Detect - fopen : " + this.inputPath); 184 | retval.replace(ptr(0x0)); 185 | } 186 | } 187 | }, 188 | }); 189 | 190 | var access = Module.findExportByName("libc.so", "access"); 191 | Interceptor.attach(access, { 192 | onEnter: function (args) { 193 | this.inputPath = args[0].readUtf8String(); 194 | }, 195 | onLeave: function (retval) { 196 | if (retval.toInt32() == 0) { 197 | if (commonPaths.indexOf(this.inputPath) >= 0) { 198 | console.log("Anti Root Detect - access : " + this.inputPath); 199 | retval.replace(ptr(-1)); 200 | } 201 | } 202 | }, 203 | }); 204 | } 205 | 206 | function setProp() { 207 | var Build = Java.use("android.os.Build"); 208 | var TAGS = Build.class.getDeclaredField("TAGS"); 209 | TAGS.setAccessible(true); 210 | TAGS.set(null, "release-keys"); 211 | 212 | var FINGERPRINT = Build.class.getDeclaredField("FINGERPRINT"); 213 | FINGERPRINT.setAccessible(true); 214 | FINGERPRINT.set( 215 | null, 216 | "google/crosshatch/crosshatch:10/QQ3A.200805.001/6578210:user/release-keys" 217 | ); 218 | 219 | // Build.deriveFingerprint.inplementation = function(){ 220 | // var ret = this.deriveFingerprint() //该函数无法通过反射调用 221 | // console.log(ret) 222 | // return ret 223 | // } 224 | 225 | var system_property_get = Module.findExportByName( 226 | "libc.so", 227 | "__system_property_get" 228 | ); 229 | Interceptor.attach(system_property_get, { 230 | onEnter(args) { 231 | this.key = args[0].readCString(); 232 | this.ret = args[1]; 233 | }, 234 | onLeave(ret) { 235 | if (this.key == "ro.build.fingerprint") { 236 | var tmp = 237 | "google/crosshatch/crosshatch:10/QQ3A.200805.001/6578210:user/release-keys"; 238 | var p = Memory.allocUtf8String(tmp); 239 | Memory.copy(this.ret, p, tmp.length + 1); 240 | } 241 | }, 242 | }); 243 | } 244 | 245 | //android.app.PackageManager 246 | function bypassRootAppCheck() { 247 | var ApplicationPackageManager = Java.use( 248 | "android.app.ApplicationPackageManager" 249 | ); 250 | ApplicationPackageManager.getPackageInfo.overload( 251 | "java.lang.String", 252 | "int" 253 | ).implementation = function (str, i) { 254 | // console.log(str) 255 | if (ROOTmanagementApp.indexOf(str) >= 0) { 256 | console.log("Anti Root Detect - check package : " + str); 257 | str = "ashen.one.ye.not.found"; 258 | } 259 | return this.getPackageInfo(str, i); 260 | }; 261 | 262 | //shell pm check 263 | } 264 | 265 | function bypassShellCheck() { 266 | var String = Java.use("java.lang.String"); 267 | 268 | var ProcessImpl = Java.use("java.lang.ProcessImpl"); 269 | ProcessImpl.start.implementation = function ( 270 | cmdarray, 271 | env, 272 | dir, 273 | redirects, 274 | redirectErrorStream 275 | ) { 276 | if (cmdarray[0] == "mount") { 277 | console.log("Anti Root Detect - Shell : " + cmdarray.toString()); 278 | arguments[0] = Java.array("java.lang.String", [String.$new("")]); 279 | return ProcessImpl.start.apply(this, arguments); 280 | } 281 | 282 | if (cmdarray[0] == "getprop") { 283 | console.log("Anti Root Detect - Shell : " + cmdarray.toString()); 284 | const prop = ["ro.secure", "ro.debuggable"]; 285 | if (prop.indexOf(cmdarray[1]) >= 0) { 286 | arguments[0] = Java.array("java.lang.String", [String.$new("")]); 287 | return ProcessImpl.start.apply(this, arguments); 288 | } 289 | } 290 | 291 | if (cmdarray[0].indexOf("which") >= 0) { 292 | const prop = ["su"]; 293 | if (prop.indexOf(cmdarray[1]) >= 0) { 294 | console.log("Anti Root Detect - Shell : " + cmdarray.toString()); 295 | arguments[0] = Java.array("java.lang.String", [String.$new("")]); 296 | return ProcessImpl.start.apply(this, arguments); 297 | } 298 | } 299 | 300 | return ProcessImpl.start.apply(this, arguments); 301 | }; 302 | } 303 | 304 | console.log("Attach"); 305 | bypassNativeFileCheck(); 306 | bypassJavaFileCheck(); 307 | setProp(); 308 | bypassRootAppCheck(); 309 | bypassShellCheck(); 310 | 311 | 312 | Java.perform(function () { 313 | var RootPackages = [ 314 | "com.noshufou.android.su", 315 | "com.noshufou.android.su.elite", 316 | "eu.chainfire.supersu", 317 | "com.koushikdutta.superuser", 318 | "com.thirdparty.superuser", 319 | "com.yellowes.su", 320 | "com.koushikdutta.rommanager", 321 | "com.koushikdutta.rommanager.license", 322 | "com.dimonvideo.luckypatcher", 323 | "com.chelpus.lackypatch", 324 | "com.ramdroid.appquarantine", 325 | "com.ramdroid.appquarantinepro", 326 | "com.devadvance.rootcloak", 327 | "com.devadvance.rootcloakplus", 328 | "de.robv.android.xposed.installer", 329 | "com.saurik.substrate", 330 | "com.zachspong.temprootremovejb", 331 | "com.amphoras.hidemyroot", 332 | "com.amphoras.hidemyrootadfree", 333 | "com.formyhm.hiderootPremium", 334 | "com.formyhm.hideroot", 335 | "me.phh.superuser", 336 | "eu.chainfire.supersu.pro", 337 | "com.kingouser.com", 338 | "com.topjohnwu.magisk", 339 | ]; 340 | 341 | var RootBinaries = [ 342 | "su", 343 | "busybox", 344 | "supersu", 345 | "Superuser.apk", 346 | "KingoUser.apk", 347 | "SuperSu.apk", 348 | "magisk", 349 | ]; 350 | 351 | var RootProperties = { 352 | "ro.build.selinux": "1", 353 | "ro.debuggable": "0", 354 | "service.adb.root": "0", 355 | "ro.secure": "1", 356 | }; 357 | 358 | var RootPropertiesKeys = []; 359 | 360 | for (var k in RootProperties) RootPropertiesKeys.push(k); 361 | 362 | var PackageManager = Java.use("android.app.ApplicationPackageManager"); 363 | 364 | var Runtime = Java.use("java.lang.Runtime"); 365 | 366 | var NativeFile = Java.use("java.io.File"); 367 | 368 | var String = Java.use("java.lang.String"); 369 | 370 | var SystemProperties = Java.use("android.os.SystemProperties"); 371 | 372 | var BufferedReader = Java.use("java.io.BufferedReader"); 373 | 374 | var ProcessBuilder = Java.use("java.lang.ProcessBuilder"); 375 | 376 | var StringBuffer = Java.use("java.lang.StringBuffer"); 377 | 378 | var loaded_classes = Java.enumerateLoadedClassesSync(); 379 | 380 | send("Loaded " + loaded_classes.length + " classes!"); 381 | 382 | var useKeyInfo = false; 383 | 384 | var useProcessManager = false; 385 | 386 | send("loaded: " + loaded_classes.indexOf("java.lang.ProcessManager")); 387 | 388 | if (loaded_classes.indexOf("java.lang.ProcessManager") != -1) { 389 | try { 390 | //useProcessManager = true; 391 | //var ProcessManager = Java.use('java.lang.ProcessManager'); 392 | } catch (err) { 393 | send("ProcessManager Hook failed: " + err); 394 | } 395 | } else { 396 | send("ProcessManager hook not loaded"); 397 | } 398 | 399 | var KeyInfo = null; 400 | 401 | if (loaded_classes.indexOf("android.security.keystore.KeyInfo") != -1) { 402 | try { 403 | //useKeyInfo = true; 404 | //var KeyInfo = Java.use('android.security.keystore.KeyInfo'); 405 | } catch (err) { 406 | send("KeyInfo Hook failed: " + err); 407 | } 408 | } else { 409 | send("KeyInfo hook not loaded"); 410 | } 411 | 412 | PackageManager.getPackageInfo.overload( 413 | "java.lang.String", 414 | "int" 415 | ).implementation = function (pname, flags) { 416 | var shouldFakePackage = RootPackages.indexOf(pname) > -1; 417 | if (shouldFakePackage) { 418 | send("Bypass root check for package: " + pname); 419 | pname = "set.package.name.to.a.fake.one.so.we.can.bypass.it"; 420 | } 421 | return this.getPackageInfo 422 | .overload("java.lang.String", "int") 423 | .call(this, pname, flags); 424 | }; 425 | 426 | NativeFile.exists.implementation = function () { 427 | var name = NativeFile.getName.call(this); 428 | var shouldFakeReturn = RootBinaries.indexOf(name) > -1; 429 | if (shouldFakeReturn) { 430 | send("Bypass return value for binary: " + name); 431 | return false; 432 | } else { 433 | return this.exists.call(this); 434 | } 435 | }; 436 | 437 | var exec = Runtime.exec.overload("[Ljava.lang.String;"); 438 | var exec1 = Runtime.exec.overload("java.lang.String"); 439 | var exec2 = Runtime.exec.overload("java.lang.String", "[Ljava.lang.String;"); 440 | var exec3 = Runtime.exec.overload( 441 | "[Ljava.lang.String;", 442 | "[Ljava.lang.String;" 443 | ); 444 | var exec4 = Runtime.exec.overload( 445 | "[Ljava.lang.String;", 446 | "[Ljava.lang.String;", 447 | "java.io.File" 448 | ); 449 | var exec5 = Runtime.exec.overload( 450 | "java.lang.String", 451 | "[Ljava.lang.String;", 452 | "java.io.File" 453 | ); 454 | 455 | exec5.implementation = function (cmd, env, dir) { 456 | if ( 457 | cmd.indexOf("getprop") != -1 || 458 | cmd == "mount" || 459 | cmd.indexOf("build.prop") != -1 || 460 | cmd == "id" || 461 | cmd == "sh" 462 | ) { 463 | var fakeCmd = "grep"; 464 | send("Bypass " + cmd + " command"); 465 | return exec1.call(this, fakeCmd); 466 | } 467 | if (cmd == "su") { 468 | var fakeCmd = 469 | "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"; 470 | send("Bypass " + cmd + " command"); 471 | return exec1.call(this, fakeCmd); 472 | } 473 | return exec5.call(this, cmd, env, dir); 474 | }; 475 | 476 | exec4.implementation = function (cmdarr, env, file) { 477 | for (var i = 0; i < cmdarr.length; i = i + 1) { 478 | var tmp_cmd = cmdarr[i]; 479 | if ( 480 | tmp_cmd.indexOf("getprop") != -1 || 481 | tmp_cmd == "mount" || 482 | tmp_cmd.indexOf("build.prop") != -1 || 483 | tmp_cmd == "id" || 484 | tmp_cmd == "sh" 485 | ) { 486 | var fakeCmd = "grep"; 487 | send("Bypass " + cmdarr + " command"); 488 | return exec1.call(this, fakeCmd); 489 | } 490 | 491 | if (tmp_cmd == "su") { 492 | var fakeCmd = 493 | "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"; 494 | send("Bypass " + cmdarr + " command"); 495 | return exec1.call(this, fakeCmd); 496 | } 497 | } 498 | return exec4.call(this, cmdarr, env, file); 499 | }; 500 | 501 | exec3.implementation = function (cmdarr, envp) { 502 | for (var i = 0; i < cmdarr.length; i = i + 1) { 503 | var tmp_cmd = cmdarr[i]; 504 | if ( 505 | tmp_cmd.indexOf("getprop") != -1 || 506 | tmp_cmd == "mount" || 507 | tmp_cmd.indexOf("build.prop") != -1 || 508 | tmp_cmd == "id" || 509 | tmp_cmd == "sh" 510 | ) { 511 | var fakeCmd = "grep"; 512 | send("Bypass " + cmdarr + " command"); 513 | return exec1.call(this, fakeCmd); 514 | } 515 | 516 | if (tmp_cmd == "su") { 517 | var fakeCmd = 518 | "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"; 519 | send("Bypass " + cmdarr + " command"); 520 | return exec1.call(this, fakeCmd); 521 | } 522 | } 523 | return exec3.call(this, cmdarr, envp); 524 | }; 525 | 526 | exec2.implementation = function (cmd, env) { 527 | if ( 528 | cmd.indexOf("getprop") != -1 || 529 | cmd == "mount" || 530 | cmd.indexOf("build.prop") != -1 || 531 | cmd == "id" || 532 | cmd == "sh" 533 | ) { 534 | var fakeCmd = "grep"; 535 | send("Bypass " + cmd + " command"); 536 | return exec1.call(this, fakeCmd); 537 | } 538 | if (cmd == "su") { 539 | var fakeCmd = 540 | "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"; 541 | send("Bypass " + cmd + " command"); 542 | return exec1.call(this, fakeCmd); 543 | } 544 | return exec2.call(this, cmd, env); 545 | }; 546 | 547 | exec.implementation = function (cmd) { 548 | for (var i = 0; i < cmd.length; i = i + 1) { 549 | var tmp_cmd = cmd[i]; 550 | if ( 551 | tmp_cmd.indexOf("getprop") != -1 || 552 | tmp_cmd == "mount" || 553 | tmp_cmd.indexOf("build.prop") != -1 || 554 | tmp_cmd == "id" || 555 | tmp_cmd == "sh" 556 | ) { 557 | var fakeCmd = "grep"; 558 | send("Bypass " + cmd + " command"); 559 | return exec1.call(this, fakeCmd); 560 | } 561 | 562 | if (tmp_cmd == "su") { 563 | var fakeCmd = 564 | "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"; 565 | send("Bypass " + cmd + " command"); 566 | return exec1.call(this, fakeCmd); 567 | } 568 | } 569 | 570 | return exec.call(this, cmd); 571 | }; 572 | 573 | exec1.implementation = function (cmd) { 574 | if ( 575 | cmd.indexOf("getprop") != -1 || 576 | cmd == "mount" || 577 | cmd.indexOf("build.prop") != -1 || 578 | cmd == "id" || 579 | cmd == "sh" 580 | ) { 581 | var fakeCmd = "grep"; 582 | send("Bypass " + cmd + " command"); 583 | return exec1.call(this, fakeCmd); 584 | } 585 | if (cmd == "su") { 586 | var fakeCmd = 587 | "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"; 588 | send("Bypass " + cmd + " command"); 589 | return exec1.call(this, fakeCmd); 590 | } 591 | return exec1.call(this, cmd); 592 | }; 593 | 594 | String.contains.implementation = function (name) { 595 | if (name == "test-keys") { 596 | send("Bypass test-keys check"); 597 | return false; 598 | } 599 | return this.contains.call(this, name); 600 | }; 601 | 602 | var get = SystemProperties.get.overload("java.lang.String"); 603 | 604 | get.implementation = function (name) { 605 | if (RootPropertiesKeys.indexOf(name) != -1) { 606 | send("Bypass " + name); 607 | return RootProperties[name]; 608 | } 609 | return this.get.call(this, name); 610 | }; 611 | 612 | Interceptor.attach(Module.findExportByName("libc.so", "fopen"), { 613 | onEnter: function (args) { 614 | var path = Memory.readCString(args[0]); 615 | path = path.split("/"); 616 | var executable = path[path.length - 1]; 617 | var shouldFakeReturn = RootBinaries.indexOf(executable) > -1; 618 | if (shouldFakeReturn) { 619 | Memory.writeUtf8String(args[0], "/notexists"); 620 | send("Bypass native fopen"); 621 | } 622 | }, 623 | onLeave: function (retval) {}, 624 | }); 625 | 626 | Interceptor.attach(Module.findExportByName("libc.so", "system"), { 627 | onEnter: function (args) { 628 | var cmd = Memory.readCString(args[0]); 629 | send("SYSTEM CMD: " + cmd); 630 | if ( 631 | cmd.indexOf("getprop") != -1 || 632 | cmd == "mount" || 633 | cmd.indexOf("build.prop") != -1 || 634 | cmd == "id" 635 | ) { 636 | send("Bypass native system: " + cmd); 637 | Memory.writeUtf8String(args[0], "grep"); 638 | } 639 | if (cmd == "su") { 640 | send("Bypass native system: " + cmd); 641 | Memory.writeUtf8String( 642 | args[0], 643 | "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled" 644 | ); 645 | } 646 | }, 647 | onLeave: function (retval) {}, 648 | }); 649 | 650 | /* 651 | 652 | TO IMPLEMENT: 653 | 654 | Exec Family 655 | 656 | int execl(const char *path, const char *arg0, ..., const char *argn, (char *)0); 657 | int execle(const char *path, const char *arg0, ..., const char *argn, (char *)0, char *const envp[]); 658 | int execlp(const char *file, const char *arg0, ..., const char *argn, (char *)0); 659 | int execlpe(const char *file, const char *arg0, ..., const char *argn, (char *)0, char *const envp[]); 660 | int execv(const char *path, char *const argv[]); 661 | int execve(const char *path, char *const argv[], char *const envp[]); 662 | int execvp(const char *file, char *const argv[]); 663 | int execvpe(const char *file, char *const argv[], char *const envp[]); 664 | 665 | */ 666 | 667 | BufferedReader.readLine.overload("boolean").implementation = function () { 668 | var text = this.readLine.overload("boolean").call(this); 669 | if (text === null) { 670 | // just pass , i know it's ugly as hell but test != null won't work :( 671 | } else { 672 | var shouldFakeRead = text.indexOf("ro.build.tags=test-keys") > -1; 673 | if (shouldFakeRead) { 674 | send("Bypass build.prop file read"); 675 | text = text.replace( 676 | "ro.build.tags=test-keys", 677 | "ro.build.tags=release-keys" 678 | ); 679 | } 680 | } 681 | return text; 682 | }; 683 | 684 | var executeCommand = ProcessBuilder.command.overload("java.util.List"); 685 | 686 | ProcessBuilder.start.implementation = function () { 687 | var cmd = this.command.call(this); 688 | var shouldModifyCommand = false; 689 | for (var i = 0; i < cmd.size(); i = i + 1) { 690 | var tmp_cmd = cmd.get(i).toString(); 691 | if ( 692 | tmp_cmd.indexOf("getprop") != -1 || 693 | tmp_cmd.indexOf("mount") != -1 || 694 | tmp_cmd.indexOf("build.prop") != -1 || 695 | tmp_cmd.indexOf("id") != -1 696 | ) { 697 | shouldModifyCommand = true; 698 | } 699 | } 700 | if (shouldModifyCommand) { 701 | send("Bypass ProcessBuilder " + cmd); 702 | this.command.call(this, ["grep"]); 703 | return this.start.call(this); 704 | } 705 | if (cmd.indexOf("su") != -1) { 706 | send("Bypass ProcessBuilder " + cmd); 707 | this.command.call(this, [ 708 | "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled", 709 | ]); 710 | return this.start.call(this); 711 | } 712 | 713 | return this.start.call(this); 714 | }; 715 | 716 | if (useProcessManager) { 717 | var ProcManExec = ProcessManager.exec.overload( 718 | "[Ljava.lang.String;", 719 | "[Ljava.lang.String;", 720 | "java.io.File", 721 | "boolean" 722 | ); 723 | var ProcManExecVariant = ProcessManager.exec.overload( 724 | "[Ljava.lang.String;", 725 | "[Ljava.lang.String;", 726 | "java.lang.String", 727 | "java.io.FileDescriptor", 728 | "java.io.FileDescriptor", 729 | "java.io.FileDescriptor", 730 | "boolean" 731 | ); 732 | 733 | ProcManExec.implementation = function (cmd, env, workdir, redirectstderr) { 734 | var fake_cmd = cmd; 735 | for (var i = 0; i < cmd.length; i = i + 1) { 736 | var tmp_cmd = cmd[i]; 737 | if ( 738 | tmp_cmd.indexOf("getprop") != -1 || 739 | tmp_cmd == "mount" || 740 | tmp_cmd.indexOf("build.prop") != -1 || 741 | tmp_cmd == "id" 742 | ) { 743 | var fake_cmd = ["grep"]; 744 | send("Bypass " + cmdarr + " command"); 745 | } 746 | 747 | if (tmp_cmd == "su") { 748 | var fake_cmd = [ 749 | "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled", 750 | ]; 751 | send("Bypass " + cmdarr + " command"); 752 | } 753 | } 754 | return ProcManExec.call(this, fake_cmd, env, workdir, redirectstderr); 755 | }; 756 | 757 | ProcManExecVariant.implementation = function ( 758 | cmd, 759 | env, 760 | directory, 761 | stdin, 762 | stdout, 763 | stderr, 764 | redirect 765 | ) { 766 | var fake_cmd = cmd; 767 | for (var i = 0; i < cmd.length; i = i + 1) { 768 | var tmp_cmd = cmd[i]; 769 | if ( 770 | tmp_cmd.indexOf("getprop") != -1 || 771 | tmp_cmd == "mount" || 772 | tmp_cmd.indexOf("build.prop") != -1 || 773 | tmp_cmd == "id" 774 | ) { 775 | var fake_cmd = ["grep"]; 776 | send("Bypass " + cmdarr + " command"); 777 | } 778 | 779 | if (tmp_cmd == "su") { 780 | var fake_cmd = [ 781 | "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled", 782 | ]; 783 | send("Bypass " + cmdarr + " command"); 784 | } 785 | } 786 | return ProcManExecVariant.call( 787 | this, 788 | fake_cmd, 789 | env, 790 | directory, 791 | stdin, 792 | stdout, 793 | stderr, 794 | redirect 795 | ); 796 | }; 797 | } 798 | 799 | if (useKeyInfo) { 800 | KeyInfo.isInsideSecureHardware.implementation = function () { 801 | send("Bypass isInsideSecureHardware"); 802 | return true; 803 | }; 804 | } 805 | }); 806 | 807 | }, 0); 808 | 809 | /** 810 | * Bypass Multiple SSL Pinning 811 | * @param {any} function( 812 | * @returns {any} 813 | */ 814 | setTimeout(function () { 815 | Java.perform(function () { 816 | console.log("---"); 817 | console.log("Unpinning Android app..."); 818 | 819 | /// -- Generic hook to protect against SSLPeerUnverifiedException -- /// 820 | 821 | // In some cases, with unusual cert pinning approaches, or heavy obfuscation, we can't 822 | // match the real method & package names. This is a problem! Fortunately, we can still 823 | // always match built-in types, so here we spot all failures that use the built-in cert 824 | // error type (notably this includes OkHttp), and after the first failure, we dynamically 825 | // generate & inject a patch to completely disable the method that threw the error. 826 | try { 827 | const UnverifiedCertError = Java.use( 828 | "javax.net.ssl.SSLPeerUnverifiedException" 829 | ); 830 | UnverifiedCertError.$init.implementation = function (str) { 831 | console.log( 832 | " --> Unexpected SSL verification failure, adding dynamic patch..." 833 | ); 834 | 835 | try { 836 | const stackTrace = Java.use("java.lang.Thread") 837 | .currentThread() 838 | .getStackTrace(); 839 | const exceptionStackIndex = stackTrace.findIndex( 840 | (stack) => 841 | stack.getClassName() === 842 | "javax.net.ssl.SSLPeerUnverifiedException" 843 | ); 844 | const callingFunctionStack = stackTrace[exceptionStackIndex + 1]; 845 | 846 | const className = callingFunctionStack.getClassName(); 847 | const methodName = callingFunctionStack.getMethodName(); 848 | 849 | console.log(` Thrown by ${className}->${methodName}`); 850 | 851 | const callingClass = Java.use(className); 852 | const callingMethod = callingClass[methodName]; 853 | 854 | if (callingMethod.implementation) return; // Already patched by Frida - skip it 855 | 856 | console.log(" Attempting to patch automatically..."); 857 | const returnTypeName = callingMethod.returnType.type; 858 | 859 | callingMethod.implementation = function () { 860 | console.log( 861 | ` --> Bypassing ${className}->${methodName} (automatic exception patch)` 862 | ); 863 | 864 | // This is not a perfect fix! Most unknown cases like this are really just 865 | // checkCert(cert) methods though, so doing nothing is perfect, and if we 866 | // do need an actual return value then this is probably the best we can do, 867 | // and at least we're logging the method name so you can patch it manually: 868 | 869 | if (returnTypeName === "void") { 870 | return; 871 | } else { 872 | return null; 873 | } 874 | }; 875 | 876 | console.log( 877 | ` [+] ${className}->${methodName} (automatic exception patch)` 878 | ); 879 | } catch (e) { 880 | console.log(" [ ] Failed to automatically patch failure"); 881 | } 882 | 883 | return this.$init(str); 884 | }; 885 | console.log("[+] SSLPeerUnverifiedException auto-patcher"); 886 | } catch (err) { 887 | console.log("[ ] SSLPeerUnverifiedException auto-patcher"); 888 | } 889 | 890 | /// -- Specific targeted hooks: -- /// 891 | 892 | // HttpsURLConnection 893 | try { 894 | const HttpsURLConnection = Java.use("javax.net.ssl.HttpsURLConnection"); 895 | HttpsURLConnection.setDefaultHostnameVerifier.implementation = function ( 896 | hostnameVerifier 897 | ) { 898 | console.log( 899 | " --> Bypassing HttpsURLConnection (setDefaultHostnameVerifier)" 900 | ); 901 | return; // Do nothing, i.e. don't change the hostname verifier 902 | }; 903 | console.log("[+] HttpsURLConnection (setDefaultHostnameVerifier)"); 904 | } catch (err) { 905 | console.log("[ ] HttpsURLConnection (setDefaultHostnameVerifier)"); 906 | } 907 | try { 908 | const HttpsURLConnection = Java.use("javax.net.ssl.HttpsURLConnection"); 909 | HttpsURLConnection.setSSLSocketFactory.implementation = function ( 910 | SSLSocketFactory 911 | ) { 912 | console.log(" --> Bypassing HttpsURLConnection (setSSLSocketFactory)"); 913 | return; // Do nothing, i.e. don't change the SSL socket factory 914 | }; 915 | console.log("[+] HttpsURLConnection (setSSLSocketFactory)"); 916 | } catch (err) { 917 | console.log("[ ] HttpsURLConnection (setSSLSocketFactory)"); 918 | } 919 | try { 920 | const HttpsURLConnection = Java.use("javax.net.ssl.HttpsURLConnection"); 921 | HttpsURLConnection.setHostnameVerifier.implementation = function ( 922 | hostnameVerifier 923 | ) { 924 | console.log(" --> Bypassing HttpsURLConnection (setHostnameVerifier)"); 925 | return; // Do nothing, i.e. don't change the hostname verifier 926 | }; 927 | console.log("[+] HttpsURLConnection (setHostnameVerifier)"); 928 | } catch (err) { 929 | console.log("[ ] HttpsURLConnection (setHostnameVerifier)"); 930 | } 931 | 932 | // SSLContext 933 | try { 934 | const X509TrustManager = Java.use("javax.net.ssl.X509TrustManager"); 935 | const SSLContext = Java.use("javax.net.ssl.SSLContext"); 936 | 937 | const TrustManager = Java.registerClass({ 938 | // Implement a custom TrustManager 939 | name: "dev.asd.test.TrustManager", 940 | implements: [X509TrustManager], 941 | methods: { 942 | checkClientTrusted: function (chain, authType) {}, 943 | checkServerTrusted: function (chain, authType) {}, 944 | getAcceptedIssuers: function () { 945 | return []; 946 | }, 947 | }, 948 | }); 949 | 950 | // Prepare the TrustManager array to pass to SSLContext.init() 951 | const TrustManagers = [TrustManager.$new()]; 952 | 953 | // Get a handle on the init() on the SSLContext class 954 | const SSLContext_init = SSLContext.init.overload( 955 | "[Ljavax.net.ssl.KeyManager;", 956 | "[Ljavax.net.ssl.TrustManager;", 957 | "java.security.SecureRandom" 958 | ); 959 | 960 | // Override the init method, specifying the custom TrustManager 961 | SSLContext_init.implementation = function ( 962 | keyManager, 963 | trustManager, 964 | secureRandom 965 | ) { 966 | console.log(" --> Bypassing Trustmanager (Android < 7) request"); 967 | SSLContext_init.call(this, keyManager, TrustManagers, secureRandom); 968 | }; 969 | console.log("[+] SSLContext"); 970 | } catch (err) { 971 | console.log("[ ] SSLContext"); 972 | } 973 | 974 | // TrustManagerImpl (Android > 7) 975 | try { 976 | const array_list = Java.use("java.util.ArrayList"); 977 | const TrustManagerImpl = Java.use( 978 | "com.android.org.conscrypt.TrustManagerImpl" 979 | ); 980 | 981 | // This step is notably what defeats the most common case: network security config 982 | TrustManagerImpl.checkTrustedRecursive.implementation = function ( 983 | a1, 984 | a2, 985 | a3, 986 | a4, 987 | a5, 988 | a6 989 | ) { 990 | console.log(" --> Bypassing TrustManagerImpl checkTrusted "); 991 | return array_list.$new(); 992 | }; 993 | 994 | TrustManagerImpl.verifyChain.implementation = function ( 995 | untrustedChain, 996 | trustAnchorChain, 997 | host, 998 | clientAuth, 999 | ocspData, 1000 | tlsSctData 1001 | ) { 1002 | console.log(" --> Bypassing TrustManagerImpl verifyChain: " + host); 1003 | return untrustedChain; 1004 | }; 1005 | console.log("[+] TrustManagerImpl"); 1006 | } catch (err) { 1007 | console.log("[ ] TrustManagerImpl"); 1008 | } 1009 | 1010 | // OkHTTPv3 (quadruple bypass) 1011 | try { 1012 | // Bypass OkHTTPv3 {1} 1013 | const okhttp3_Activity_1 = Java.use("okhttp3.CertificatePinner"); 1014 | okhttp3_Activity_1.check.overload( 1015 | "java.lang.String", 1016 | "java.util.List" 1017 | ).implementation = function (a, b) { 1018 | console.log(" --> Bypassing OkHTTPv3 (list): " + a); 1019 | return; 1020 | }; 1021 | console.log("[+] OkHTTPv3 (list)"); 1022 | } catch (err) { 1023 | console.log("[ ] OkHTTPv3 (list)"); 1024 | } 1025 | try { 1026 | // Bypass OkHTTPv3 {2} 1027 | // This method of CertificatePinner.check could be found in some old Android app 1028 | const okhttp3_Activity_2 = Java.use("okhttp3.CertificatePinner"); 1029 | okhttp3_Activity_2.check.overload( 1030 | "java.lang.String", 1031 | "java.security.cert.Certificate" 1032 | ).implementation = function (a, b) { 1033 | console.log(" --> Bypassing OkHTTPv3 (cert): " + a); 1034 | return; 1035 | }; 1036 | console.log("[+] OkHTTPv3 (cert)"); 1037 | } catch (err) { 1038 | console.log("[ ] OkHTTPv3 (cert)"); 1039 | } 1040 | try { 1041 | // Bypass OkHTTPv3 {3} 1042 | const okhttp3_Activity_3 = Java.use("okhttp3.CertificatePinner"); 1043 | okhttp3_Activity_3.check.overload( 1044 | "java.lang.String", 1045 | "[Ljava.security.cert.Certificate;" 1046 | ).implementation = function (a, b) { 1047 | console.log(" --> Bypassing OkHTTPv3 (cert array): " + a); 1048 | return; 1049 | }; 1050 | console.log("[+] OkHTTPv3 (cert array)"); 1051 | } catch (err) { 1052 | console.log("[ ] OkHTTPv3 (cert array)"); 1053 | } 1054 | try { 1055 | // Bypass OkHTTPv3 {4} 1056 | const okhttp3_Activity_4 = Java.use("okhttp3.CertificatePinner"); 1057 | okhttp3_Activity_4["check$okhttp"].implementation = function (a, b) { 1058 | console.log(" --> Bypassing OkHTTPv3 ($okhttp): " + a); 1059 | return; 1060 | }; 1061 | console.log("[+] OkHTTPv3 ($okhttp)"); 1062 | } catch (err) { 1063 | console.log("[ ] OkHTTPv3 ($okhttp)"); 1064 | } 1065 | 1066 | // Trustkit (triple bypass) 1067 | try { 1068 | // Bypass Trustkit {1} 1069 | const trustkit_Activity_1 = Java.use( 1070 | "com.datatheorem.android.trustkit.pinning.OkHostnameVerifier" 1071 | ); 1072 | trustkit_Activity_1.verify.overload( 1073 | "java.lang.String", 1074 | "javax.net.ssl.SSLSession" 1075 | ).implementation = function (a, b) { 1076 | console.log( 1077 | " --> Bypassing Trustkit OkHostnameVerifier(SSLSession): " + a 1078 | ); 1079 | return true; 1080 | }; 1081 | console.log("[+] Trustkit OkHostnameVerifier(SSLSession)"); 1082 | } catch (err) { 1083 | console.log("[ ] Trustkit OkHostnameVerifier(SSLSession)"); 1084 | } 1085 | try { 1086 | // Bypass Trustkit {2} 1087 | const trustkit_Activity_2 = Java.use( 1088 | "com.datatheorem.android.trustkit.pinning.OkHostnameVerifier" 1089 | ); 1090 | trustkit_Activity_2.verify.overload( 1091 | "java.lang.String", 1092 | "java.security.cert.X509Certificate" 1093 | ).implementation = function (a, b) { 1094 | console.log(" --> Bypassing Trustkit OkHostnameVerifier(cert): " + a); 1095 | return true; 1096 | }; 1097 | console.log("[+] Trustkit OkHostnameVerifier(cert)"); 1098 | } catch (err) { 1099 | console.log("[ ] Trustkit OkHostnameVerifier(cert)"); 1100 | } 1101 | try { 1102 | // Bypass Trustkit {3} 1103 | const trustkit_PinningTrustManager = Java.use( 1104 | "com.datatheorem.android.trustkit.pinning.PinningTrustManager" 1105 | ); 1106 | trustkit_PinningTrustManager.checkServerTrusted.implementation = 1107 | function () { 1108 | console.log(" --> Bypassing Trustkit PinningTrustManager"); 1109 | }; 1110 | console.log("[+] Trustkit PinningTrustManager"); 1111 | } catch (err) { 1112 | console.log("[ ] Trustkit PinningTrustManager"); 1113 | } 1114 | 1115 | // Appcelerator Titanium 1116 | try { 1117 | const appcelerator_PinningTrustManager = Java.use( 1118 | "appcelerator.https.PinningTrustManager" 1119 | ); 1120 | appcelerator_PinningTrustManager.checkServerTrusted.implementation = 1121 | function () { 1122 | console.log(" --> Bypassing Appcelerator PinningTrustManager"); 1123 | }; 1124 | console.log("[+] Appcelerator PinningTrustManager"); 1125 | } catch (err) { 1126 | console.log("[ ] Appcelerator PinningTrustManager"); 1127 | } 1128 | 1129 | // OpenSSLSocketImpl Conscrypt 1130 | try { 1131 | const OpenSSLSocketImpl = Java.use( 1132 | "com.android.org.conscrypt.OpenSSLSocketImpl" 1133 | ); 1134 | OpenSSLSocketImpl.verifyCertificateChain.implementation = function ( 1135 | certRefs, 1136 | JavaObject, 1137 | authMethod 1138 | ) { 1139 | console.log(" --> Bypassing OpenSSLSocketImpl Conscrypt"); 1140 | }; 1141 | console.log("[+] OpenSSLSocketImpl Conscrypt"); 1142 | } catch (err) { 1143 | console.log("[ ] OpenSSLSocketImpl Conscrypt"); 1144 | } 1145 | 1146 | // OpenSSLEngineSocketImpl Conscrypt 1147 | try { 1148 | const OpenSSLEngineSocketImpl_Activity = Java.use( 1149 | "com.android.org.conscrypt.OpenSSLEngineSocketImpl" 1150 | ); 1151 | OpenSSLEngineSocketImpl_Activity.verifyCertificateChain.overload( 1152 | "[Ljava.lang.Long;", 1153 | "java.lang.String" 1154 | ).implementation = function (a, b) { 1155 | console.log(" --> Bypassing OpenSSLEngineSocketImpl Conscrypt: " + b); 1156 | }; 1157 | console.log("[+] OpenSSLEngineSocketImpl Conscrypt"); 1158 | } catch (err) { 1159 | console.log("[ ] OpenSSLEngineSocketImpl Conscrypt"); 1160 | } 1161 | 1162 | // OpenSSLSocketImpl Apache Harmony 1163 | try { 1164 | const OpenSSLSocketImpl_Harmony = Java.use( 1165 | "org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl" 1166 | ); 1167 | OpenSSLSocketImpl_Harmony.verifyCertificateChain.implementation = 1168 | function (asn1DerEncodedCertificateChain, authMethod) { 1169 | console.log(" --> Bypassing OpenSSLSocketImpl Apache Harmony"); 1170 | }; 1171 | console.log("[+] OpenSSLSocketImpl Apache Harmony"); 1172 | } catch (err) { 1173 | console.log("[ ] OpenSSLSocketImpl Apache Harmony"); 1174 | } 1175 | 1176 | // PhoneGap sslCertificateChecker (https://github.com/EddyVerbruggen/SSLCertificateChecker-PhoneGap-Plugin) 1177 | try { 1178 | const phonegap_Activity = Java.use( 1179 | "nl.xservices.plugins.sslCertificateChecker" 1180 | ); 1181 | phonegap_Activity.execute.overload( 1182 | "java.lang.String", 1183 | "org.json.JSONArray", 1184 | "org.apache.cordova.CallbackContext" 1185 | ).implementation = function (a, b, c) { 1186 | console.log(" --> Bypassing PhoneGap sslCertificateChecker: " + a); 1187 | return true; 1188 | }; 1189 | console.log("[+] PhoneGap sslCertificateChecker"); 1190 | } catch (err) { 1191 | console.log("[ ] PhoneGap sslCertificateChecker"); 1192 | } 1193 | 1194 | // IBM MobileFirst pinTrustedCertificatePublicKey (double bypass) 1195 | try { 1196 | // Bypass IBM MobileFirst {1} 1197 | const WLClient_Activity_1 = Java.use( 1198 | "com.worklight.wlclient.api.WLClient" 1199 | ); 1200 | WLClient_Activity_1.getInstance().pinTrustedCertificatePublicKey.overload( 1201 | "java.lang.String" 1202 | ).implementation = function (cert) { 1203 | console.log( 1204 | " --> Bypassing IBM MobileFirst pinTrustedCertificatePublicKey (string): " + 1205 | cert 1206 | ); 1207 | return; 1208 | }; 1209 | console.log( 1210 | "[+] IBM MobileFirst pinTrustedCertificatePublicKey (string)" 1211 | ); 1212 | } catch (err) { 1213 | console.log( 1214 | "[ ] IBM MobileFirst pinTrustedCertificatePublicKey (string)" 1215 | ); 1216 | } 1217 | try { 1218 | // Bypass IBM MobileFirst {2} 1219 | const WLClient_Activity_2 = Java.use( 1220 | "com.worklight.wlclient.api.WLClient" 1221 | ); 1222 | WLClient_Activity_2.getInstance().pinTrustedCertificatePublicKey.overload( 1223 | "[Ljava.lang.String;" 1224 | ).implementation = function (cert) { 1225 | console.log( 1226 | " --> Bypassing IBM MobileFirst pinTrustedCertificatePublicKey (string array): " + 1227 | cert 1228 | ); 1229 | return; 1230 | }; 1231 | console.log( 1232 | "[+] IBM MobileFirst pinTrustedCertificatePublicKey (string array)" 1233 | ); 1234 | } catch (err) { 1235 | console.log( 1236 | "[ ] IBM MobileFirst pinTrustedCertificatePublicKey (string array)" 1237 | ); 1238 | } 1239 | 1240 | // IBM WorkLight (ancestor of MobileFirst) HostNameVerifierWithCertificatePinning (quadruple bypass) 1241 | try { 1242 | // Bypass IBM WorkLight {1} 1243 | const worklight_Activity_1 = Java.use( 1244 | "com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning" 1245 | ); 1246 | worklight_Activity_1.verify.overload( 1247 | "java.lang.String", 1248 | "javax.net.ssl.SSLSocket" 1249 | ).implementation = function (a, b) { 1250 | console.log( 1251 | " --> Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSocket): " + 1252 | a 1253 | ); 1254 | return; 1255 | }; 1256 | console.log( 1257 | "[+] IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSocket)" 1258 | ); 1259 | } catch (err) { 1260 | console.log( 1261 | "[ ] IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSocket)" 1262 | ); 1263 | } 1264 | try { 1265 | // Bypass IBM WorkLight {2} 1266 | const worklight_Activity_2 = Java.use( 1267 | "com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning" 1268 | ); 1269 | worklight_Activity_2.verify.overload( 1270 | "java.lang.String", 1271 | "java.security.cert.X509Certificate" 1272 | ).implementation = function (a, b) { 1273 | console.log( 1274 | " --> Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning (cert): " + 1275 | a 1276 | ); 1277 | return; 1278 | }; 1279 | console.log( 1280 | "[+] IBM WorkLight HostNameVerifierWithCertificatePinning (cert)" 1281 | ); 1282 | } catch (err) { 1283 | console.log( 1284 | "[ ] IBM WorkLight HostNameVerifierWithCertificatePinning (cert)" 1285 | ); 1286 | } 1287 | try { 1288 | // Bypass IBM WorkLight {3} 1289 | const worklight_Activity_3 = Java.use( 1290 | "com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning" 1291 | ); 1292 | worklight_Activity_3.verify.overload( 1293 | "java.lang.String", 1294 | "[Ljava.lang.String;", 1295 | "[Ljava.lang.String;" 1296 | ).implementation = function (a, b) { 1297 | console.log( 1298 | " --> Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning (string string): " + 1299 | a 1300 | ); 1301 | return; 1302 | }; 1303 | console.log( 1304 | "[+] IBM WorkLight HostNameVerifierWithCertificatePinning (string string)" 1305 | ); 1306 | } catch (err) { 1307 | console.log( 1308 | "[ ] IBM WorkLight HostNameVerifierWithCertificatePinning (string string)" 1309 | ); 1310 | } 1311 | try { 1312 | // Bypass IBM WorkLight {4} 1313 | const worklight_Activity_4 = Java.use( 1314 | "com.worklight.wlclient.certificatepinning.HostNameVerifierWithCertificatePinning" 1315 | ); 1316 | worklight_Activity_4.verify.overload( 1317 | "java.lang.String", 1318 | "javax.net.ssl.SSLSession" 1319 | ).implementation = function (a, b) { 1320 | console.log( 1321 | " --> Bypassing IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSession): " + 1322 | a 1323 | ); 1324 | return true; 1325 | }; 1326 | console.log( 1327 | "[+] IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSession)" 1328 | ); 1329 | } catch (err) { 1330 | console.log( 1331 | "[ ] IBM WorkLight HostNameVerifierWithCertificatePinning (SSLSession)" 1332 | ); 1333 | } 1334 | 1335 | // Conscrypt CertPinManager 1336 | try { 1337 | const conscrypt_CertPinManager_Activity = Java.use( 1338 | "com.android.org.conscrypt.CertPinManager" 1339 | ); 1340 | conscrypt_CertPinManager_Activity.isChainValid.overload( 1341 | "java.lang.String", 1342 | "java.util.List" 1343 | ).implementation = function (a, b) { 1344 | console.log(" --> Bypassing Conscrypt CertPinManager: " + a); 1345 | return true; 1346 | }; 1347 | console.log("[+] Conscrypt CertPinManager"); 1348 | } catch (err) { 1349 | console.log("[ ] Conscrypt CertPinManager"); 1350 | } 1351 | 1352 | // CWAC-Netsecurity (unofficial back-port pinner for Android<4.2) CertPinManager 1353 | try { 1354 | const cwac_CertPinManager_Activity = Java.use( 1355 | "com.commonsware.cwac.netsecurity.conscrypt.CertPinManager" 1356 | ); 1357 | cwac_CertPinManager_Activity.isChainValid.overload( 1358 | "java.lang.String", 1359 | "java.util.List" 1360 | ).implementation = function (a, b) { 1361 | console.log(" --> Bypassing CWAC-Netsecurity CertPinManager: " + a); 1362 | return true; 1363 | }; 1364 | console.log("[+] CWAC-Netsecurity CertPinManager"); 1365 | } catch (err) { 1366 | console.log("[ ] CWAC-Netsecurity CertPinManager"); 1367 | } 1368 | 1369 | // Worklight Androidgap WLCertificatePinningPlugin 1370 | try { 1371 | const androidgap_WLCertificatePinningPlugin_Activity = Java.use( 1372 | "com.worklight.androidgap.plugin.WLCertificatePinningPlugin" 1373 | ); 1374 | androidgap_WLCertificatePinningPlugin_Activity.execute.overload( 1375 | "java.lang.String", 1376 | "org.json.JSONArray", 1377 | "org.apache.cordova.CallbackContext" 1378 | ).implementation = function (a, b, c) { 1379 | console.log( 1380 | " --> Bypassing Worklight Androidgap WLCertificatePinningPlugin: " + 1381 | a 1382 | ); 1383 | return true; 1384 | }; 1385 | console.log("[+] Worklight Androidgap WLCertificatePinningPlugin"); 1386 | } catch (err) { 1387 | console.log("[ ] Worklight Androidgap WLCertificatePinningPlugin"); 1388 | } 1389 | 1390 | // Netty FingerprintTrustManagerFactory 1391 | try { 1392 | const netty_FingerprintTrustManagerFactory = Java.use( 1393 | "io.netty.handler.ssl.util.FingerprintTrustManagerFactory" 1394 | ); 1395 | netty_FingerprintTrustManagerFactory.checkTrusted.implementation = 1396 | function (type, chain) { 1397 | console.log(" --> Bypassing Netty FingerprintTrustManagerFactory"); 1398 | }; 1399 | console.log("[+] Netty FingerprintTrustManagerFactory"); 1400 | } catch (err) { 1401 | console.log("[ ] Netty FingerprintTrustManagerFactory"); 1402 | } 1403 | 1404 | // Squareup CertificatePinner [OkHTTP Bypassing Squareup CertificatePinner (cert): " + a); 1415 | return; 1416 | }; 1417 | console.log("[+] Squareup CertificatePinner (cert)"); 1418 | } catch (err) { 1419 | console.log("[ ] Squareup CertificatePinner (cert)"); 1420 | } 1421 | try { 1422 | // Bypass Squareup CertificatePinner {2} 1423 | const Squareup_CertificatePinner_Activity_2 = Java.use( 1424 | "com.squareup.okhttp.CertificatePinner" 1425 | ); 1426 | Squareup_CertificatePinner_Activity_2.check.overload( 1427 | "java.lang.String", 1428 | "java.util.List" 1429 | ).implementation = function (a, b) { 1430 | console.log(" --> Bypassing Squareup CertificatePinner (list): " + a); 1431 | return; 1432 | }; 1433 | console.log("[+] Squareup CertificatePinner (list)"); 1434 | } catch (err) { 1435 | console.log("[ ] Squareup CertificatePinner (list)"); 1436 | } 1437 | 1438 | // Squareup OkHostnameVerifier [OkHTTP v3] (double bypass) 1439 | try { 1440 | // Bypass Squareup OkHostnameVerifier {1} 1441 | const Squareup_OkHostnameVerifier_Activity_1 = Java.use( 1442 | "com.squareup.okhttp.internal.tls.OkHostnameVerifier" 1443 | ); 1444 | Squareup_OkHostnameVerifier_Activity_1.verify.overload( 1445 | "java.lang.String", 1446 | "java.security.cert.X509Certificate" 1447 | ).implementation = function (a, b) { 1448 | console.log(" --> Bypassing Squareup OkHostnameVerifier (cert): " + a); 1449 | return true; 1450 | }; 1451 | console.log("[+] Squareup OkHostnameVerifier (cert)"); 1452 | } catch (err) { 1453 | console.log("[ ] Squareup OkHostnameVerifier (cert)"); 1454 | } 1455 | try { 1456 | // Bypass Squareup OkHostnameVerifier {2} 1457 | const Squareup_OkHostnameVerifier_Activity_2 = Java.use( 1458 | "com.squareup.okhttp.internal.tls.OkHostnameVerifier" 1459 | ); 1460 | Squareup_OkHostnameVerifier_Activity_2.verify.overload( 1461 | "java.lang.String", 1462 | "javax.net.ssl.SSLSession" 1463 | ).implementation = function (a, b) { 1464 | console.log( 1465 | " --> Bypassing Squareup OkHostnameVerifier (SSLSession): " + a 1466 | ); 1467 | return true; 1468 | }; 1469 | console.log("[+] Squareup OkHostnameVerifier (SSLSession)"); 1470 | } catch (err) { 1471 | console.log("[ ] Squareup OkHostnameVerifier (SSLSession)"); 1472 | } 1473 | 1474 | // Android WebViewClient (double bypass) 1475 | try { 1476 | // Bypass WebViewClient {1} (deprecated from Android 6) 1477 | const AndroidWebViewClient_Activity_1 = Java.use( 1478 | "android.webkit.WebViewClient" 1479 | ); 1480 | AndroidWebViewClient_Activity_1.onReceivedSslError.overload( 1481 | "android.webkit.WebView", 1482 | "android.webkit.SslErrorHandler", 1483 | "android.net.http.SslError" 1484 | ).implementation = function (obj1, obj2, obj3) { 1485 | console.log(" --> Bypassing Android WebViewClient (SslErrorHandler)"); 1486 | }; 1487 | console.log("[+] Android WebViewClient (SslErrorHandler)"); 1488 | } catch (err) { 1489 | console.log("[ ] Android WebViewClient (SslErrorHandler)"); 1490 | } 1491 | try { 1492 | // Bypass WebViewClient {2} 1493 | const AndroidWebViewClient_Activity_2 = Java.use( 1494 | "android.webkit.WebViewClient" 1495 | ); 1496 | AndroidWebViewClient_Activity_2.onReceivedSslError.overload( 1497 | "android.webkit.WebView", 1498 | "android.webkit.WebResourceRequest", 1499 | "android.webkit.WebResourceError" 1500 | ).implementation = function (obj1, obj2, obj3) { 1501 | console.log(" --> Bypassing Android WebViewClient (WebResourceError)"); 1502 | }; 1503 | console.log("[+] Android WebViewClient (WebResourceError)"); 1504 | } catch (err) { 1505 | console.log("[ ] Android WebViewClient (WebResourceError)"); 1506 | } 1507 | 1508 | // Apache Cordova WebViewClient 1509 | try { 1510 | const CordovaWebViewClient_Activity = Java.use( 1511 | "org.apache.cordova.CordovaWebViewClient" 1512 | ); 1513 | CordovaWebViewClient_Activity.onReceivedSslError.overload( 1514 | "android.webkit.WebView", 1515 | "android.webkit.SslErrorHandler", 1516 | "android.net.http.SslError" 1517 | ).implementation = function (obj1, obj2, obj3) { 1518 | console.log(" --> Bypassing Apache Cordova WebViewClient"); 1519 | obj3.proceed(); 1520 | }; 1521 | } catch (err) { 1522 | console.log("[ ] Apache Cordova WebViewClient"); 1523 | } 1524 | 1525 | // Boye AbstractVerifier 1526 | try { 1527 | const boye_AbstractVerifier = Java.use( 1528 | "ch.boye.httpclientandroidlib.conn.ssl.AbstractVerifier" 1529 | ); 1530 | boye_AbstractVerifier.verify.implementation = function (host, ssl) { 1531 | console.log(" --> Bypassing Boye AbstractVerifier: " + host); 1532 | }; 1533 | } catch (err) { 1534 | console.log("[ ] Boye AbstractVerifier"); 1535 | } 1536 | 1537 | // Appmattus 1538 | try { 1539 | const appmatus_Activity = Java.use( 1540 | "com.appmattus.certificatetransparency.internal.verifier.CertificateTransparencyInterceptor" 1541 | ); 1542 | appmatus_Activity["intercept"].implementation = function (a) { 1543 | console.log(" --> Bypassing Appmattus (Transparency)"); 1544 | return a.proceed(a.request()); 1545 | }; 1546 | console.log("[+] Appmattus (CertificateTransparencyInterceptor)"); 1547 | } catch (err) { 1548 | console.log("[ ] Appmattus (CertificateTransparencyInterceptor)"); 1549 | } 1550 | 1551 | try { 1552 | const CertificateTransparencyTrustManager = Java.use( 1553 | "com.appmattus.certificatetransparency.internal.verifier.CertificateTransparencyTrustManager" 1554 | ); 1555 | CertificateTransparencyTrustManager["checkServerTrusted"].overload( 1556 | "[Ljava.security.cert.X509Certificate;", 1557 | "java.lang.String" 1558 | ).implementation = function (x509CertificateArr, str) { 1559 | console.log( 1560 | " --> Bypassing Appmattus (CertificateTransparencyTrustManager)" 1561 | ); 1562 | }; 1563 | CertificateTransparencyTrustManager["checkServerTrusted"].overload( 1564 | "[Ljava.security.cert.X509Certificate;", 1565 | "java.lang.String", 1566 | "java.lang.String" 1567 | ).implementation = function (x509CertificateArr, str, str2) { 1568 | console.log( 1569 | " --> Bypassing Appmattus (CertificateTransparencyTrustManager)" 1570 | ); 1571 | return Java.use("java.util.ArrayList").$new(); 1572 | }; 1573 | console.log("[+] Appmattus (CertificateTransparencyTrustManager)"); 1574 | } catch (err) { 1575 | console.log("[ ] Appmattus (CertificateTransparencyTrustManager)"); 1576 | } 1577 | 1578 | console.log("Unpinning setup completed"); 1579 | console.log("---"); 1580 | }); 1581 | }, 0); 1582 | 1583 | -------------------------------------------------------------------------------- /flutter-bypass.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | "ios":{ 3 | "modulename": "Flutter", 4 | "patterns":{ 5 | "arm64": [ 6 | "FF 83 01 D1 FA 67 01 A9 F8 5F 02 A9 F6 57 03 A9 F4 4F 04 A9 FD 7B 05 A9 FD 43 01 91 F? 03 00 AA ?? 0? 40 F9 ?8 1? 40 F9 15 ?? 4? F9 B5 00 00 B4", 7 | ], 8 | }, 9 | }, 10 | "android":{ 11 | "modulename": "libflutter.so", 12 | "patterns":{ 13 | "arm64": [ 14 | "F? 0F 1C F8 F? 5? 01 A9 F? 5? 02 A9 F? ?? 03 A9 ?? ?? ?? ?? 68 1A 40 F9", 15 | "F? 43 01 D1 FE 67 01 A9 F8 5F 02 A9 F6 57 03 A9 F4 4F 04 A9 13 00 40 F9 F4 03 00 AA 68 1A 40 F9", 16 | "FF 43 01 D1 FE 67 01 A9 ?? ?? 06 94 ?? 7? 06 94 68 1A 40 F9 15 15 41 F9 B5 00 00 B4 B6 4A 40 F9", 17 | ], 18 | "arm": [ 19 | "2D E9 F? 4? D0 F8 00 80 81 46 D8 F8 18 00 D0 F8 ??", 20 | ], 21 | "x64": [ 22 | "55 41 57 41 56 41 55 41 54 53 50 49 89 f? 4c 8b 37 49 8b 46 30 4c 8b a? ?? 0? 00 00 4d 85 e? 74 1? 4d 8b", 23 | "55 41 57 41 56 41 55 41 54 53 48 83 EC 18 49 89 FF 48 8B 1F 48 8B 43 30 4C 8B A0 28 02 00 00 4D 85 E4 74", 24 | "55 41 57 41 56 41 55 41 54 53 48 83 EC 38 C6 02 50 48 8B AF A8 00 00 00 48 85 ED 74 70 48 83 7D 00 00 74" 25 | ] 26 | } 27 | } 28 | }; 29 | 30 | // Flag to check if TLS validation has already been disabled 31 | var TLSValidationDisabled = false; 32 | 33 | // Check if Java environment is available (Android) 34 | if (Java.available) { 35 | console.log("[+] Java environment detected"); 36 | Java.perform(hookSystemLoadLibrary); 37 | // Check if ObjC environment is available (iOS) 38 | } else if (ObjC.available) { 39 | console.log("[+] iOS environment detected"); 40 | } 41 | 42 | // Attempt to disable TLS validation immediately and then again after a 2 second delay 43 | disableTLSValidation(); 44 | setTimeout(disableTLSValidation, 2000, true); 45 | 46 | // Hook the system load library method to detect when libflutter.so is loaded on Android 47 | function hookSystemLoadLibrary() { 48 | const System = Java.use('java.lang.System'); 49 | const Runtime = Java.use('java.lang.Runtime'); 50 | const SystemLoad_2 = System.loadLibrary.overload('java.lang.String'); 51 | const VMStack = Java.use('dalvik.system.VMStack'); 52 | 53 | SystemLoad_2.implementation = function(library) { 54 | try { 55 | const loaded = Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), library); 56 | if (library === 'flutter') { 57 | console.log("[+] libflutter.so loaded"); 58 | disableTLSValidation(); 59 | } 60 | return loaded; 61 | } catch (ex) { 62 | console.log(ex); 63 | } 64 | }; 65 | } 66 | 67 | // Main function to disable TLS validation for Flutter 68 | function disableTLSValidation(fallback=false) { 69 | if (TLSValidationDisabled) return; 70 | 71 | var platformConfig = config[Java.available ? "android" : "ios"]; 72 | var m = Process.findModuleByName(platformConfig["modulename"]); 73 | 74 | // If there is no loaded Flutter module, the setTimeout may trigger a second time, but after that we give up 75 | if (m === null) { 76 | if (fallback) console.log("[!] Flutter module not found."); 77 | return; 78 | } 79 | 80 | if (Process.arch in platformConfig["patterns"]) 81 | { 82 | findAndPatch(m, platformConfig["patterns"][Process.arch], Java.available && Process.arch == "arm" ? 1 : 0, fallback); 83 | } 84 | else 85 | { 86 | console.log("[!] Processor architecture not supported: ", Process.arch); 87 | } 88 | 89 | if (!TLSValidationDisabled) 90 | { 91 | if (fallback){ 92 | if(m.enumerateRanges('r-x').length == 0) 93 | { 94 | console.log('[!] No memory ranges found in Flutter library. This is either a Frida bug, or the application is using some kind of RASP. Try using Frida as a Gadget or using an older Android version (https://github.com/frida/frida/issues/2266)'); 95 | } 96 | else 97 | { 98 | console.log('[!] ssl_verify_peer_cert not found. Please open an issue at https://github.com/NVISOsecurity/disable-flutter-tls-verification/issues'); 99 | } 100 | } 101 | else 102 | { 103 | console.log('[!] ssl_verify_peer_cert not found. Trying again...'); 104 | } 105 | } 106 | } 107 | 108 | // Find and patch the method in memory to disable TLS validation 109 | function findAndPatch(m, patterns, thumb, fallback) { 110 | console.log("[+] Flutter library found"); 111 | var ranges = m.enumerateRanges('r-x'); 112 | ranges.forEach(range => { 113 | patterns.forEach(pattern => { 114 | Memory.scan(range.base, range.size, pattern, { 115 | onMatch: function(address, size) { 116 | console.log('[+] ssl_verify_peer_cert found at offset: 0x' + (address - m.base).toString(16)); 117 | TLSValidationDisabled = true; 118 | hook_ssl_verify_peer_cert(address.add(thumb)); 119 | } 120 | }); 121 | }); 122 | }); 123 | } 124 | 125 | // Replace the target function's implementation to effectively disable the TLS check 126 | function hook_ssl_verify_peer_cert(address) { 127 | Interceptor.replace(address, new NativeCallback((pathPtr, flags) => { 128 | return 0; 129 | }, 'int', ['pointer', 'int'])); 130 | } 131 | --------------------------------------------------------------------------------