From 99b6adcd5d1a9283d511587e947aa93fa59b329c Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Wed, 18 Oct 2023 17:19:52 +0200 Subject: [PATCH] Remove unnecessary (maybe problematic) setTimeout delays --- android-certificate-unpinning.js | 154 +++++++++++++++---------------- android-proxy-override.js | 133 +++++++++++++------------- 2 files changed, 142 insertions(+), 145 deletions(-) diff --git a/android-certificate-unpinning.js b/android-certificate-unpinning.js index 279736f..dce3153 100644 --- a/android-certificate-unpinning.js +++ b/android-certificate-unpinning.js @@ -368,94 +368,92 @@ const getJavaClassIfExists = (clsName) => { } } -setTimeout(function () { - Java.perform(function () { - if (DEBUG_MODE) console.log('\n === Disabling all recognized unpinning libraries ==='); +Java.perform(function () { + if (DEBUG_MODE) console.log('\n === Disabling all recognized unpinning libraries ==='); - const classesToPatch = Object.keys(PINNING_FIXES); + const classesToPatch = Object.keys(PINNING_FIXES); - classesToPatch.forEach((targetClassName) => { - const TargetClass = getJavaClassIfExists(targetClassName); - if (!TargetClass) { - // We skip patches for any classes that don't seem to be present. This is common - // as not all libraries we handle are necessarily used. - if (DEBUG_MODE) console.log(`[ ] ${targetClassName} *`); + classesToPatch.forEach((targetClassName) => { + const TargetClass = getJavaClassIfExists(targetClassName); + if (!TargetClass) { + // We skip patches for any classes that don't seem to be present. This is common + // as not all libraries we handle are necessarily used. + if (DEBUG_MODE) console.log(`[ ] ${targetClassName} *`); + return; + } + + const patches = PINNING_FIXES[targetClassName]; + + let patchApplied = false; + + patches.forEach(({ methodName, getMethod, overload, replacement }) => { + const namedTargetMethod = getMethod + ? getMethod(TargetClass) + : TargetClass[methodName]; + + const methodDescription = `${methodName}${ + overload === '*' + ? '(*)' + : overload + ? '(' + overload.map((argType) => { + // Simplify arg names to just the class name for simpler logs: + const argClassName = argType.split('.').slice(-1)[0]; + if (argType.startsWith('[L')) return `${argClassName}[]`; + else return argClassName; + }).join(', ') + ')' + // No overload: + : '' + }` + + let targetMethodImplementations = []; + try { + if (namedTargetMethod) { + if (!overload) { + // No overload specified + targetMethodImplementations = [namedTargetMethod]; + } else if (overload === '*') { + // Targetting _all_ overloads + targetMethodImplementations = namedTargetMethod.overloads; + } else { + // Or targetting a specific overload: + targetMethodImplementations = [namedTargetMethod.overload(...overload)]; + } + } + } catch (e) { + // Overload not present + } + + + // We skip patches for any methods that don't seem to be present. This is rarer, but does + // happen due to methods that only appear in certain library versions or whose signatures + // have changed over time. + if (targetMethodImplementations.length === 0) { + if (DEBUG_MODE) console.log(`[ ] ${targetClassName} ${methodDescription}`); return; } - const patches = PINNING_FIXES[targetClassName]; + targetMethodImplementations.forEach((targetMethod, i) => { + const patchName = `${targetClassName} ${methodDescription}${ + targetMethodImplementations.length > 1 ? ` (${i})` : '' + }`; - let patchApplied = false; - - patches.forEach(({ methodName, getMethod, overload, replacement }) => { - const namedTargetMethod = getMethod - ? getMethod(TargetClass) - : TargetClass[methodName]; - - const methodDescription = `${methodName}${ - overload === '*' - ? '(*)' - : overload - ? '(' + overload.map((argType) => { - // Simplify arg names to just the class name for simpler logs: - const argClassName = argType.split('.').slice(-1)[0]; - if (argType.startsWith('[L')) return `${argClassName}[]`; - else return argClassName; - }).join(', ') + ')' - // No overload: - : '' - }` - - let targetMethodImplementations = []; try { - if (namedTargetMethod) { - if (!overload) { - // No overload specified - targetMethodImplementations = [namedTargetMethod]; - } else if (overload === '*') { - // Targetting _all_ overloads - targetMethodImplementations = namedTargetMethod.overloads; - } else { - // Or targetting a specific overload: - targetMethodImplementations = [namedTargetMethod.overload(...overload)]; - } - } + targetMethod.implementation = replacement(targetMethod); + + if (DEBUG_MODE) console.log(`[+] ${patchName}`); + patchApplied = true; } catch (e) { - // Overload not present + // In theory, errors like this should never happen - it means the patch is broken + // (e.g. some dynamic patch building fails completely) + console.error(`[!] ERROR: ${patchName} failed: ${e}`); } - - - // We skip patches for any methods that don't seem to be present. This is rarer, but does - // happen due to methods that only appear in certain library versions or whose signatures - // have changed over time. - if (targetMethodImplementations.length === 0) { - if (DEBUG_MODE) console.log(`[ ] ${targetClassName} ${methodDescription}`); - return; - } - - targetMethodImplementations.forEach((targetMethod, i) => { - const patchName = `${targetClassName} ${methodDescription}${ - targetMethodImplementations.length > 1 ? ` (${i})` : '' - }`; - - try { - targetMethod.implementation = replacement(targetMethod); - - if (DEBUG_MODE) console.log(`[+] ${patchName}`); - patchApplied = true; - } catch (e) { - // In theory, errors like this should never happen - it means the patch is broken - // (e.g. some dynamic patch building fails completely) - console.error(`[!] ERROR: ${patchName} failed: ${e}`); - } - }) - }); - - if (!patchApplied) { - console.warn(`[!] Matched class ${targetClassName} but could not patch any methods`); - } + }) }); - console.log('== Certificate unpinning completed =='); + if (!patchApplied) { + console.warn(`[!] Matched class ${targetClassName} but could not patch any methods`); + } }); + + console.log('== Certificate unpinning completed =='); }); \ No newline at end of file diff --git a/android-proxy-override.js b/android-proxy-override.js index a32c76f..4e5e9ca 100644 --- a/android-proxy-override.js +++ b/android-proxy-override.js @@ -13,81 +13,80 @@ * in the native connect() hook script. */ -setTimeout(() => { - Java.perform(() => { - // Set default JVM system properties for the proxy address. Notably these are used - // to initialize WebView configuration. - Java.use('java.lang.System').setProperty('http.proxyHost', PROXY_HOST); - Java.use('java.lang.System').setProperty('http.proxyPort', PROXY_PORT.toString()); - Java.use('java.lang.System').setProperty('https.proxyHost', PROXY_HOST); - Java.use('java.lang.System').setProperty('https.proxyPort', PROXY_PORT.toString()); +Java.perform(() => { + // Set default JVM system properties for the proxy address. Notably these are used + // to initialize WebView configuration. + Java.use('java.lang.System').setProperty('http.proxyHost', PROXY_HOST); + Java.use('java.lang.System').setProperty('http.proxyPort', PROXY_PORT.toString()); + Java.use('java.lang.System').setProperty('https.proxyHost', PROXY_HOST); + Java.use('java.lang.System').setProperty('https.proxyPort', PROXY_PORT.toString()); - Java.use('java.lang.System').clearProperty('http.nonProxyHosts'); - Java.use('java.lang.System').clearProperty('https.nonProxyHosts'); + Java.use('java.lang.System').clearProperty('http.nonProxyHosts'); + Java.use('java.lang.System').clearProperty('https.nonProxyHosts'); - // Some Android internals attempt to reset these settings to match the device configuration. - // We block that directly here: - const controlledSystemProperties = [ - 'http.proxyHost', - 'http.proxyPort', - 'https.proxyHost', - 'https.proxyPort', - 'http.nonProxyHosts', - 'https.nonProxyHosts' - ]; - Java.use('java.lang.System').clearProperty.implementation = function (property) { - if (controlledSystemProperties.includes(property)) { - if (DEBUG_MODE) console.log(`Ignoring attempt to clear ${property} system property`); - return this.getProperty(property); - } - return this.clearProperty(...arguments); + // Some Android internals attempt to reset these settings to match the device configuration. + // We block that directly here: + const controlledSystemProperties = [ + 'http.proxyHost', + 'http.proxyPort', + 'https.proxyHost', + 'https.proxyPort', + 'http.nonProxyHosts', + 'https.nonProxyHosts' + ]; + Java.use('java.lang.System').clearProperty.implementation = function (property) { + if (controlledSystemProperties.includes(property)) { + if (DEBUG_MODE) console.log(`Ignoring attempt to clear ${property} system property`); + return this.getProperty(property); } - Java.use('java.lang.System').setProperty.implementation = function (property) { - if (controlledSystemProperties.includes(property)) { - if (DEBUG_MODE) console.log(`Ignoring attempt to override ${property} system property`); - return this.getProperty(property); - } - return this.setProperty(...arguments); + return this.clearProperty(...arguments); + } + Java.use('java.lang.System').setProperty.implementation = function (property) { + if (controlledSystemProperties.includes(property)) { + if (DEBUG_MODE) console.log(`Ignoring attempt to override ${property} system property`); + return this.getProperty(property); } + return this.setProperty(...arguments); + } - // Configure the app's proxy directly, via the app connectivity manager service: - const ConnectivityManager = Java.use('android.net.ConnectivityManager'); - const ProxyInfo = Java.use('android.net.ProxyInfo'); - ConnectivityManager.getDefaultProxy.implementation = () => ProxyInfo.$new(PROXY_HOST, PROXY_PORT, ''); - // (Not clear if this works 100% - implying there are ConnectivityManager subclasses handling this) + // Configure the app's proxy directly, via the app connectivity manager service: + const ConnectivityManager = Java.use('android.net.ConnectivityManager'); + const ProxyInfo = Java.use('android.net.ProxyInfo'); + ConnectivityManager.getDefaultProxy.implementation = () => ProxyInfo.$new(PROXY_HOST, PROXY_PORT, ''); + // (Not clear if this works 100% - implying there are ConnectivityManager subclasses handling this) - console.log(`== Proxy system configuration overridden to ${PROXY_HOST}:${PROXY_PORT} ==`); + console.log(`== Proxy system configuration overridden to ${PROXY_HOST}:${PROXY_PORT} ==`); - // Configure the proxy indirectly, by overriding the return value for all ProxySelectors everywhere: - const Collections = Java.use('java.util.Collections'); - const ProxyType = Java.use('java.net.Proxy$Type'); - const InetSocketAddress = Java.use('java.net.InetSocketAddress'); - const ProxyCls = Java.use('java.net.Proxy'); // 'Proxy' is reserved in JS + // Configure the proxy indirectly, by overriding the return value for all ProxySelectors everywhere: + const Collections = Java.use('java.util.Collections'); + const ProxyType = Java.use('java.net.Proxy$Type'); + const InetSocketAddress = Java.use('java.net.InetSocketAddress'); + const ProxyCls = Java.use('java.net.Proxy'); // 'Proxy' is reserved in JS - const targetProxy = ProxyCls.$new( - ProxyType.HTTP.value, - InetSocketAddress.$new(PROXY_HOST, PROXY_PORT) + const targetProxy = ProxyCls.$new( + ProxyType.HTTP.value, + InetSocketAddress.$new(PROXY_HOST, PROXY_PORT) + ); + const getTargetProxyList = () => Collections.singletonList(targetProxy); + + const ProxySelector = Java.use('java.net.ProxySelector'); + + // Find every implementation of ProxySelector by quickly scanning method signatures, and + // then checking whether each match actually implements java.net.ProxySelector: + const proxySelectorClasses = Java.enumerateMethods('*!select(java.net.URI): java.util.List/s') + .flatMap((matchingLoader) => matchingLoader.classes + .map((classData) => Java.use(classData.name)) + .filter((Cls) => ProxySelector.class.isAssignableFrom(Cls.class)) ); - const getTargetProxyList = () => Collections.singletonList(targetProxy); - const ProxySelector = Java.use('java.net.ProxySelector'); - - // Find every implementation of ProxySelector by quickly scanning method signatures, and - // then checking whether each match actually implements java.net.ProxySelector: - const proxySelectorClasses = Java.enumerateMethods('*!select(java.net.URI): java.util.List/s') - .flatMap((matchingLoader) => matchingLoader.classes - .map((classData) => Java.use(classData.name)) - .filter((Cls) => ProxySelector.class.isAssignableFrom(Cls.class)) - ); - - // Replace the 'select' of every implementation, so they all send traffic to us: - proxySelectorClasses.forEach(ProxySelectorCls => { - if (DEBUG_MODE) { - console.log('Rewriting', ProxySelectorCls.toString()); - } - ProxySelectorCls.select.implementation = () => getTargetProxyList() - }); - - console.log(`== Proxy configuration overridden to ${PROXY_HOST}:${PROXY_PORT} ==`); + // Replace the 'select' of every implementation, so they all send traffic to us: + proxySelectorClasses.forEach(ProxySelectorCls => { + if (DEBUG_MODE) { + console.log('Rewriting', ProxySelectorCls.toString()); + } + ProxySelectorCls.select.implementation = () => getTargetProxyList() }); -}); \ No newline at end of file + + console.log(`== Proxy configuration overridden to ${PROXY_HOST}:${PROXY_PORT} ==`); +}); +