Skip to content

Commit

Permalink
Fixes #39: Automatically install Frida in ensureDevice() on Android
Browse files Browse the repository at this point in the history
  • Loading branch information
baltpeter authored and zner0L committed Mar 28, 2023
1 parent 6325184 commit d99c3cd
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 42 deletions.
40 changes: 2 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -121,23 +102,6 @@ On subsequent runs, don't include the `-partition-size 8192 -wipe-data` flags, i
emulator -avd "<emulator name>"
```

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
Expand Down Expand Up @@ -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 () => {
Expand Down
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

___

Expand Down Expand Up @@ -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)

___

Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,15 @@
},
"prettier": "@baltpeter/prettier-config",
"dependencies": {
"@napi-rs/lzma": "^1.1.2",
"cross-fetch": "^3.1.5",
"execa": "^6.1.0",
"frida": "^16.0.8",
"fs-extra": "^11.1.0",
"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"
},
Expand All @@ -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",
Expand Down
74 changes: 74 additions & 0 deletions src/android.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -12,6 +14,7 @@ import type {
SupportedRunTarget,
WireGuardConfig,
} from '.';
import { dependencies } from '../package.json';
import { asyncUnimplemented, getObjFromFridaScript, isRecord, retryCondition } from './util';

const fridaScripts = {
Expand Down Expand Up @@ -52,6 +55,76 @@ export const androidApi = <RunTarget extends SupportedRunTarget<'android'>>(
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,
Expand All @@ -60,6 +133,7 @@ export const androidApi = <RunTarget extends SupportedRunTarget<'android'>>(

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(
Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"extends": "@baltpeter/tsconfig",
"include": ["src/**/*", "examples/**/*", "src/types/ipa-extract-info.d.ts"],
"compilerOptions": {
"resolveJsonModule": true,
"paths": {
"*": ["./*", "./src/types/*"]
}
Expand Down
88 changes: 86 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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/[email protected]":
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/[email protected]":
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/[email protected]":
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/[email protected]":
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/[email protected]":
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/[email protected]":
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/[email protected]":
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/[email protected]":
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/[email protected]":
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/[email protected]":
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/[email protected]":
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/[email protected]":
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/[email protected]":
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/[email protected]":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
Expand Down Expand Up @@ -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==
Expand Down Expand Up @@ -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==
Expand Down

0 comments on commit d99c3cd

Please sign in to comment.