From d99c3cd68356bb688da1cfa707f8e2c0faac0b87 Mon Sep 17 00:00:00 2001 From: Benjamin Altpeter Date: Tue, 21 Mar 2023 14:32:40 +0100 Subject: [PATCH] Fixes #39: Automatically install Frida in `ensureDevice()` on Android --- README.md | 40 ++--------------------- docs/README.md | 4 +-- package.json | 3 ++ src/android.ts | 74 ++++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 1 + yarn.lock | 88 ++++++++++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 168 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 88c08be..2426688 100644 --- a/README.md +++ b/README.md @@ -82,28 +82,9 @@ To use appstraction with a physical Android device, you need to enable USB debug Some functions require the device to be rooted. The steps to do this vary depending on the device. We recommend using [Magisk](https://topjohnwu.github.io/Magisk/). After you have rooted the device, you need to enable rooted debugging via Settings -> System -> Developer options -> Rooted debugging. -Some functions require Frida. If you want to use them, you need to [set up Frida](https://frida.re/docs/android/) on the emulator (make sure that the version you're installing matches the version of the Frida tools you're using): - -```sh -adb root - -# Find out the architecture of the device. -adb shell getprop ro.product.cpu.abi -# Download the correct frida-server, e.g. for ARM64: -wget https://github.com/frida/frida/releases/download/16.0.8/frida-server-16.0.8-android-arm64.xz -unxz frida-server-16.0.8-android-arm64.xz - -adb push frida-server-16.0.8-android-arm64 /data/local/tmp/frida-server -adb shell chmod 777 /data/local/tmp/frida-server - -# Test that Frida is working. You don't need to start Frida manually in later runs, appstraction will do that for you. -adb shell "/data/local/tmp/frida-server" -frida-ps -U | grep frida # should have `frida-server` -``` - ### Android emulator -Some of the functions in appstraction work without any special preparation in an emulator. You can create the emulator using Android Studio or the command line tools, e.g. like this to create an emulator with Google APIs running Android 13 (API level 33)—we recommend using x86_64 as the architecture (you can still [run ARM apps if you use Android 11 or newer](https://android-developers.googleblog.com/2020/03/run-arm-apps-on-android-emulator.html)): +Appstraction doesn't require any special preparation in an emulator. You can create the emulator using Android Studio or the command line tools, e.g. like this to create an emulator with Google APIs running Android 13 (API level 33)—we recommend using x86_64 as the architecture (you can still [run ARM apps if you use Android 11 or newer](https://android-developers.googleblog.com/2020/03/run-arm-apps-on-android-emulator.html)): ```sh # Fetch the system image. @@ -121,23 +102,6 @@ On subsequent runs, don't include the `-partition-size 8192 -wipe-data` flags, i emulator -avd "" ``` -Some functions require Frida. If you want to use them, you need to [set up Frida](https://frida.re/docs/android/) on the emulator (make sure that the version you're installing matches the version of the Frida tools you're using): - -```sh -adb root - -adb shell getprop ro.product.cpu.abi # should be x86_64 -wget https://github.com/frida/frida/releases/download/16.0.8/frida-server-16.0.8-android-x86_64.xz -unxz frida-server-16.0.8-android-x86_64.xz - -adb push frida-server-16.0.8-android-x86_64 /data/local/tmp/frida-server -adb shell chmod 777 /data/local/tmp/frida-server - -# Test that Frida is working. You don't need to start Frida manually in later runs, appstraction will do that for you. -adb shell "/data/local/tmp/frida-server" -frida-ps -U | grep frida # should have `frida-server` -``` - After you have set up the emulator to your liking, you should create a snapshot to later be able to reset the emulator to this state: ```sh @@ -188,7 +152,7 @@ Note how the first function we call after constructing the API object is `ensure This example didn't need any capabilities. Resetting the emulator and installing apps can both be done in any emulator, without the need for any special preparation. -Other functions do need capabilities, though, which you would pass to the `capabilities` array in the options. For example, reading the `SharedPreferences` requires the `frida` capability (and you need to set up Frida as described above). And for starting an app, you can optionally pass the `certificate-pinning-bypass`, which will use objection to try and bypass any certificate pinning the app may use. +Other functions do need capabilities, though, which you would pass to the `capabilities` array in the options. For example, reading the `SharedPreferences` requires the `frida` capability. And for starting an app, you can optionally pass the `certificate-pinning-bypass`, which will use objection to try and bypass any certificate pinning the app may use. ```ts (async () => { diff --git a/docs/README.md b/docs/README.md index 8eab55b..c2eb23f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -40,7 +40,7 @@ An ID of a known permission on Android. #### Defined in -[android.ts:640](https://github.com/tweaselORG/platform-apis/blob/main/src/android.ts#L640) +[android.ts:714](https://github.com/tweaselORG/platform-apis/blob/main/src/android.ts#L714) ___ @@ -274,7 +274,7 @@ The IDs of known permissions on Android. #### Defined in -[android.ts:509](https://github.com/tweaselORG/platform-apis/blob/main/src/android.ts#L509) +[android.ts:583](https://github.com/tweaselORG/platform-apis/blob/main/src/android.ts#L583) ___ diff --git a/package.json b/package.json index 287fabb..071c2d9 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ }, "prettier": "@baltpeter/prettier-config", "dependencies": { + "@napi-rs/lzma": "^1.1.2", "cross-fetch": "^3.1.5", "execa": "^6.1.0", "frida": "^16.0.8", @@ -58,6 +59,7 @@ "ipa-extract-info": "^1.2.6", "p-retry": "^5.1.2", "pkijs": "^3.0.14", + "semver": "^7.3.8", "tempy": "^3.0.0", "ts-node": "^10.9.1" }, @@ -71,6 +73,7 @@ "@types/node": "^18.11.18", "@types/plist": "^3.0.2", "@types/promise-timeout": "^1.3.0", + "@types/semver": "^7.3.13", "@typescript-eslint/eslint-plugin": "5.48.0", "eslint": "8.31.0", "eslint-plugin-eslint-comments": "3.2.0", diff --git a/src/android.ts b/src/android.ts index 11c6992..9aad705 100644 --- a/src/android.ts +++ b/src/android.ts @@ -1,8 +1,10 @@ +import { decompress as decompressXz } from '@napi-rs/lzma/xz'; import fetch from 'cross-fetch'; import { execa } from 'execa'; import frida from 'frida'; import { rm, writeFile } from 'fs/promises'; import pRetry from 'p-retry'; +import { major as semverMajor, minVersion as semverMinVersion } from 'semver'; import { temporaryFile } from 'tempy'; import type { PlatformApi, @@ -12,6 +14,7 @@ import type { SupportedRunTarget, WireGuardConfig, } from '.'; +import { dependencies } from '../package.json'; import { asyncUnimplemented, getObjFromFridaScript, isRecord, retryCondition } from './util'; const fridaScripts = { @@ -52,6 +55,76 @@ export const androidApi = >( async ensureFrida() { if (!options.capabilities.includes('frida')) return; + // Ensure that the correct version of `frida-tools` is installed for our Frida JS bindings. + if (!dependencies.frida) throw new Error('Frida dependency not found. This should never happen.'); + const { stdout: fridaToolsVersion } = await execa('frida', ['--version']); + const fridaToolsMajorVersion = semverMajor(fridaToolsVersion); + // `dependencies.frida` is not a specific version but a range, so we get the minimum possible version. + const fridaJsMajorVersion = semverMinVersion(dependencies.frida)?.major; + if (fridaToolsMajorVersion !== fridaJsMajorVersion) + throw new Error( + `frida-tools major version (${fridaToolsMajorVersion}) does not match version of the Frida JS bindings (${fridaJsMajorVersion}). You need to install version ${fridaJsMajorVersion} of frida-tools.` + ); + + // Check whether `frida-server` is already installed on the device and has the correct major version. + const { stdout: fridaServerVersion } = await execa( + 'adb', + ['shell', '/data/local/tmp/frida-server --version'], + { reject: false } + ); + const fridaServerMajorVersion = fridaServerVersion && semverMajor(fridaServerVersion); + + // Download and install `frida-server` if necessary. + if (fridaServerMajorVersion !== fridaJsMajorVersion) { + const releaseMeta = await fetch( + `https://api.github.com/repos/frida/frida/releases/tags/${fridaToolsVersion}` + ).then((r) => r.json()); + if (releaseMeta.message === 'Not Found') + throw new Error( + `No frida-server found for version ${fridaToolsVersion}. Please install frida-server manually.` + ); + + const { stdout: androidArch } = await execa('adb', ['shell', 'getprop', 'ro.product.cpu.abi']); + const archMap = { + 'arm64-v8a': 'arm64', + 'armeabi-v7a': 'arm', + armeabi: 'arm', + // eslint-disable-next-line camelcase + x86_64: 'x86_64', + x86: 'x86', + }; + const fridaArch = archMap[androidArch as keyof typeof archMap]; + if (!fridaArch) + throw new Error( + `Unsupported architecture: "${androidArch}". Please install frida-server manually.` + ); + + const asset = (releaseMeta.assets as { name: string; browser_download_url: string }[]).find((a) => + a.name.match(new RegExp(`frida-server-.+-android-${fridaArch}\\.xz`)) + ); + if (!asset) + throw new Error( + `No frida-server found for architecture "${fridaArch}". Please install frida-server manually.` + ); + + const fridaServerTmpPath = temporaryFile(); + const fridaServerXz = await fetch(asset.browser_download_url).then((res) => res.arrayBuffer()); + const fridaServerBinary = await decompressXz(Buffer.from(fridaServerXz)); + await writeFile(fridaServerTmpPath, Buffer.from(fridaServerBinary)); + + await execa('adb', ['push', fridaServerTmpPath, '/data/local/tmp/frida-server']); + await execa('adb', ['shell', 'chmod', '755', '/data/local/tmp/frida-server']); + + const { stdout: installedFridaServerVersion } = await execa( + 'adb', + ['shell', '/data/local/tmp/frida-server --version'], + { reject: false } + ); + if (installedFridaServerVersion !== fridaToolsVersion) + throw new Error(`Failed to install frida-server. Please install frida-server manually.`); + } + + // Start `frida-server` if it's not already running. const fridaCheck = await execa(`frida-ps -U | grep frida-server`, { shell: true, reject: false, @@ -60,6 +133,7 @@ export const androidApi = >( await this.requireRoot('Frida'); + await execa('adb', ['shell', 'chmod', '755', '/data/local/tmp/frida-server']); await execa('adb', ['shell', '-x', '/data/local/tmp/frida-server', '--daemonize']); const fridaIsStarted = await retryCondition( diff --git a/tsconfig.json b/tsconfig.json index 0f1a01f..34b435f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "extends": "@baltpeter/tsconfig", "include": ["src/**/*", "examples/**/*", "src/types/ipa-extract-info.d.ts"], "compilerOptions": { + "resolveJsonModule": true, "paths": { "*": ["./*", "./src/types/*"] } diff --git a/yarn.lock b/yarn.lock index c6bbff2..12830bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -246,6 +246,90 @@ resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-2.2.0.tgz#016d855b6bc459fd908095811f6826e45dd4ba64" integrity sha512-XrC0JzsqQSvOyM3t04FMLO6z5gCuhPE6k4FXuLK5xf52ZbdvcFe1yBmo7meCew9B8G2f0T9iu9t3kfTYRYROgA== +"@napi-rs/lzma-android-arm-eabi@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@napi-rs/lzma-android-arm-eabi/-/lzma-android-arm-eabi-1.1.2.tgz#3866356a277ead52c6a04abff3600ad52e45d2e6" + integrity sha512-b30+yEjSU1XucdTu2V8pGvpCVlFYlXMFHn89OjV+RhkQj4z3DwSPTZu17iea7MRSuP4bZzZkUG7t18lNDD9O4Q== + +"@napi-rs/lzma-android-arm64@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@napi-rs/lzma-android-arm64/-/lzma-android-arm64-1.1.2.tgz#6a4dcd77909c45710a598d99a769382356779ca9" + integrity sha512-SW83wFds4AK4+eNnFl8bN25ypZU12/dae3h4wxz4l1czNsxomr4zN/feuymDzRURFU1nJti4lLK6fmRJW4ej5g== + +"@napi-rs/lzma-darwin-arm64@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@napi-rs/lzma-darwin-arm64/-/lzma-darwin-arm64-1.1.2.tgz#7074cf0ce8a2d805b163d57f321df7f93c8ea70a" + integrity sha512-YM3SDU5Stt+M5tR8FQke5Puk6KIWOnSb+cfNbaM4zRvASpJVqdCEpblaSsgyuhsCfC8VjeEfohL4hkuAq5Ovzw== + +"@napi-rs/lzma-darwin-x64@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@napi-rs/lzma-darwin-x64/-/lzma-darwin-x64-1.1.2.tgz#9d1e51fd4037eb0722f9cae9c8b51b5c6e17c36a" + integrity sha512-CO+mXmUIyUZZcQngGK5vY71xBNMbgfA3tu4QVkExlQE/JfeQ+JiIOB/ksOMNMkBJdK12nSrFitCUyuZ0eS8JXQ== + +"@napi-rs/lzma-freebsd-x64@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@napi-rs/lzma-freebsd-x64/-/lzma-freebsd-x64-1.1.2.tgz#08b98cbc19e3e6db8ce81a95b1a791243bf32995" + integrity sha512-Za+QobA52xOMSlT2n30TFtA200qfZKPgSuDjVqURc+DyzcQLgG94n+C+J1YoY0iyfvlRdK0P32MBPHSq5fN/Fw== + +"@napi-rs/lzma-linux-arm-gnueabihf@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@napi-rs/lzma-linux-arm-gnueabihf/-/lzma-linux-arm-gnueabihf-1.1.2.tgz#6119d1718d82ac61ccfbb6413c24d9deef87f32b" + integrity sha512-DBLyr61rcsmjbBPiUag0O2CDUZ/g+puCqRQdgXIQkH6pDV+Mn7ey2xq12eBwmoB4qASL1xU1SVChBWjO7HIwdg== + +"@napi-rs/lzma-linux-arm64-gnu@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@napi-rs/lzma-linux-arm64-gnu/-/lzma-linux-arm64-gnu-1.1.2.tgz#2da3ee85b9fb1a6e8dd13dd09e29975abc9e433e" + integrity sha512-nR5nnc3tnXZyd1jn9dHarj8RQeUJmvm83Z46mfWFKBv/TtlP4U7eyg9n3A0eEyBTifPzuR5ZjVdaMXg0hrX9cA== + +"@napi-rs/lzma-linux-arm64-musl@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@napi-rs/lzma-linux-arm64-musl/-/lzma-linux-arm64-musl-1.1.2.tgz#be737c17646d5088a430ca6df0146f43aa05c63e" + integrity sha512-N4gC6VJds/wL6PY23Cc8Z3QuztIMpNfBo++zLu2uHdRV67/XkM7JXfsgTCaZVR59lSZw+Pvi01w5i00BhmEZ/g== + +"@napi-rs/lzma-linux-x64-gnu@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.1.2.tgz#7e7b890e4a8b8917d7733dff036e962b8cbafd7b" + integrity sha512-xfSXofZnDlbSik64AayKj5xgSVmYMsK1cmRPQGBDcpwK4qnFd+Sy/qa39+x+GT2pip9QnT6stb72BT69asTRbw== + +"@napi-rs/lzma-linux-x64-musl@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@napi-rs/lzma-linux-x64-musl/-/lzma-linux-x64-musl-1.1.2.tgz#47d0b6d7deb17b3a35b9293ae9231325aec314a8" + integrity sha512-wfrv3zRK+qw3+GT3o4FPGZWb8L0gtqpjAQgPiz91EtGyHWKZjvRz31zERUkU9DKEwSBZSxYoFu5zCVsvilQekA== + +"@napi-rs/lzma-win32-arm64-msvc@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@napi-rs/lzma-win32-arm64-msvc/-/lzma-win32-arm64-msvc-1.1.2.tgz#2582d99f20baf693bb609d0ddcaf69e49f2fdf16" + integrity sha512-9lwYWz4g3FMxt5Pu33jfGuoHvAnXaonXYMbjWCVq3t+ReHV38rEVnueWtJD2GQcrnV2Elu0zxU4zON1PwJ1KqQ== + +"@napi-rs/lzma-win32-ia32-msvc@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@napi-rs/lzma-win32-ia32-msvc/-/lzma-win32-ia32-msvc-1.1.2.tgz#d38dbb8f48b3050a17a0dd3c33a8197341624370" + integrity sha512-p9ZuF/5osCKES5oz5ZKyMck9jbVBhZ/zH4uD6fsKjLtp0VYBlvmfqSGUYQALkF6bqvL7xBeJz7qby416pnWqPg== + +"@napi-rs/lzma-win32-x64-msvc@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@napi-rs/lzma-win32-x64-msvc/-/lzma-win32-x64-msvc-1.1.2.tgz#dbc715b85e8f15363959af1dbb08844e2214c177" + integrity sha512-EwNN9eH05KkeqO300UEYqZEbqzRD3R4anUJMIvhmJI1E3a/9mUbzEByrSBsjyPfYcHF5+KYenEpuPoup9a4eHQ== + +"@napi-rs/lzma@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@napi-rs/lzma/-/lzma-1.1.2.tgz#09ce791390d819918182fe9382a7075e90d21d55" + integrity sha512-WJLn/td0F7RQmQ0mhtGVQXjfXQND8CE9hAdHJovPm6fM3aNW68chn7DCAyhs7rmhX95PovTEP0iu9TZVUtnl3w== + optionalDependencies: + "@napi-rs/lzma-android-arm-eabi" "1.1.2" + "@napi-rs/lzma-android-arm64" "1.1.2" + "@napi-rs/lzma-darwin-arm64" "1.1.2" + "@napi-rs/lzma-darwin-x64" "1.1.2" + "@napi-rs/lzma-freebsd-x64" "1.1.2" + "@napi-rs/lzma-linux-arm-gnueabihf" "1.1.2" + "@napi-rs/lzma-linux-arm64-gnu" "1.1.2" + "@napi-rs/lzma-linux-arm64-musl" "1.1.2" + "@napi-rs/lzma-linux-x64-gnu" "1.1.2" + "@napi-rs/lzma-linux-x64-musl" "1.1.2" + "@napi-rs/lzma-win32-arm64-msvc" "1.1.2" + "@napi-rs/lzma-win32-ia32-msvc" "1.1.2" + "@napi-rs/lzma-win32-x64-msvc" "1.1.2" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -976,7 +1060,7 @@ resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065" integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g== -"@types/semver@^7.3.12": +"@types/semver@^7.3.12", "@types/semver@^7.3.13": version "7.3.13" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== @@ -4057,7 +4141,7 @@ semver@^5.7.0, semver@^5.7.1: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^7.3.5, semver@^7.3.7: +semver@^7.3.5, semver@^7.3.7, semver@^7.3.8: version "7.3.8" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==