Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Manage Emulators automatically #55

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 45 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The current features include:
- Starting and managing an Android emulator
- Collecting HTTP(S) traffic in HAR format
- Automatic CA management and WireGuard mitmproxy setup
- Automatic management of (Android) emulators: Creating and destroying, snapshots, restarting and -building for error recovery

## Installation

Expand Down Expand Up @@ -49,7 +50,7 @@ The following example collects the traffic for an app in the Android emulator. I
snapshotName: '<snapshot name>',
startEmulatorOptions: {
emulatorName: '<emulator name>',
},
}
},
});

Expand All @@ -75,7 +76,49 @@ The following example collects the traffic for an app in the Android emulator. I
})();
```

Take a look at the [`examples/`](examples) folder for some more examples of how to use cyanoacrylate.
### Use with multiple apps

This library was designed in particular to enable long running analyses of many apps. If you want to do that, make sure to run `analysis.ensureDevice()` before every new app analysis, because it deals with any problems with the device and in particular emulator which might have come up in a previous analysis. If you leave the emulator management to cyanoacrylate, we might even rebuild the emulator completely from scratch. Also, you should wrap the app analysis in a `try {} catch () {}`-block, to catch any errors occurring in the emulator, which could be fixed by `ensureDevice` in a later run. The basic structure of an analysis might look like this:

```ts
const analysis = await startAnalysis({
platform: 'android',
runTarget: 'emulator',
capabilities: [],

targetOptions: {
// Let cyanoacrylate handle all the emulator stuff.
managed: true,
managedEmulatorOptions: {
key: 'test',
},
},
});

for (const app of apps) {
// Start the emulator and ensure that everything is set up correctly.
try {
await analysis.ensureDevice();

const appAnalysis = await analysis.startAppAnalysis(app);

// Do the app analysis, e.g. install the app, collect traffic…

const analysisResults = await appAnalysis.stop();
// Do something with the result.
} catch (error) {
if(typeof error === 'EmulatorError') {
// Handle the error, e.g. by adding the app back to the queue if you want to try again.
// EmulatorErrors are not always recoverable, though, so be careful not to create an infinite loop.
}
}

}

await analysis.stop();
```

Take a look at [`examples/mutiple-apps.ts`](examples/multiple-apps.ts) for a full working example.

## Additional metadata in exported HAR files

Expand Down
215 changes: 167 additions & 48 deletions docs/README.md

Large diffs are not rendered by default.

57 changes: 57 additions & 0 deletions examples/managed-android-emulator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* eslint-disable no-console */
import { pause, startAnalysis } from '../src/index';

// You can pass the following command line arguments:
// `npx tsx examples/managed-android-emulator.ts <path to an APK to analyze>`

(async () => {
const apkFile = process.argv[2];

if (!apkFile) throw Error('Please provide an APK file as the first argument.');

const analysis = await startAnalysis({
platform: 'android',
runTarget: 'emulator',
capabilities: ['frida', 'certificate-pinning-bypass'],

targetOptions: {
managed: true,
managedEmulatorOptions: {
key: 'examples-managed-android-emulator',

honeyData: {
deviceName: 'Joyce’s Android Emulator',
clipboard: 'CC: 5274 5520 2359 5935, CVC: 314, Exp: 01/28',
},
},

startEmulatorOptions: {
headless: true,
},
},
});

try {
await analysis.ensureDevice();

const appAnalysis = await analysis.startAppAnalysis(apkFile as `${string}.apk`);

await analysis.ensureTrackingDomainResolution();

console.log('Installing app…');
await appAnalysis.installApp();
console.log('Starting app…');
await appAnalysis.startApp();

await pause(6_000);

await appAnalysis.stop();
} catch (error: any) {
// Handle the error here, e.g. queue the app for analysis again etc.
console.error(error.message);
}

await analysis.stop();
console.log('Done.');
})();
/* eslint-enable no-console */
84 changes: 54 additions & 30 deletions examples/multiple-apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,77 @@ import path from 'path';
import { pause, startAnalysis } from '../src/index';

// You can pass the following command line arguments:
// `npx tsx examples/multiple-apps.ts <emulator name> <snapshot name> <path to a folder of single APKs>`
// `npx tsx examples/multiple-apps.ts <path to a folder of single APKs> <emulator name> <snapshot name>`

(async () => {
const emulatorName = process.argv[2] || 'emulator-name';
const snapshotName = process.argv[3] || 'snapshot-with-setup-emu';
const apkFolder = process.argv[4] || 'path/to/app-files';
const apkFolder = process.argv[2];
// If you do not specify these, cyanoacrylate will just create and manage its own emulator, prefixed
// `cyanoacrylate-examples-multiple-apps-`.
const emulatorName = process.argv[3];
const snapshotName = process.argv[4];

if (!apkFolder) throw Error('Please provide a folder of APKs as the first argument.');

const analysis = await startAnalysis({
platform: 'android',
runTarget: 'emulator',
capabilities: ['frida', 'certificate-pinning-bypass'],
targetOptions: {
snapshotName,
startEmulatorOptions: {
emulatorName,
},
},
targetOptions:
emulatorName && snapshotName
? {
snapshotName,
startEmulatorOptions: {
emulatorName,
},
managed: false,
}
: {
managed: true,
managedEmulatorOptions: {
key: 'examples-multiple-apps',
},
},
});

await analysis.ensureDevice();

// The library was designed to do this for many apps in one go,
// so you can easily loop through an array of apps.
// The library was designed to do this for many apps in one go, so you can easily loop through an array of apps.
const apks = await readdir(apkFolder);
for (const apkFile of apks) {
const appAnalysis = await analysis.startAppAnalysis(path.join(apkFolder, apkFile) as `${string}.apk`);
console.log(`Analyzing ${apkFile}…`);

try {
console.log('Ensuring device…');
await analysis.ensureDevice();
zner0L marked this conversation as resolved.
Show resolved Hide resolved
await analysis.ensureTrackingDomainResolution();

const appAnalysis = await analysis.startAppAnalysis(path.join(apkFolder, apkFile) as `${string}.apk`);

await analysis.resetDevice();
// await analysis.ensureTrackingDomainResolution();
console.log('Installing app…');
await appAnalysis.installApp();
console.log('Setting app permissions…');
await appAnalysis.setAppPermissions();

await appAnalysis.installApp();
await appAnalysis.setAppPermissions();
await appAnalysis.startTrafficCollection();
await appAnalysis.startApp();
console.log('Starting app and traffic collection…');
await appAnalysis.startTrafficCollection();
await appAnalysis.startApp();

// Pause to wait for the app to generate network traffic.
await pause(6_000);
// Pause to wait for the app to generate network traffic.
await pause(6_000);

await appAnalysis.stopTrafficCollection();
console.log('Stopping app and traffic collection…');
await appAnalysis.stopTrafficCollection();
const result = await appAnalysis.stop();

const result = await appAnalysis.stop();
console.dir(result, { depth: null });
// {
// app: { id: '<app id>', name: '<app name>', version: '<app version>', ... },
// traffic: { '2023-03-27T10:29:44.197Z': { log: ... } } <- The traffic collections are named by a timestamp and contain the collected requests in the HAR format.
// }

console.dir(result, { depth: null });
// {
// app: { id: '<app id>', name: '<app name>', version: '<app version>', ... },
// traffic: { '2023-03-27T10:29:44.197Z': { log: ... } } <- The traffic collections are named by a timestamp and contain the collected requests in the HAR format.
// }
console.log();
} catch (error: any) {
// Handle the error here, e.g. queue the app for analysis again etc.
console.error(error.message);
}
}

await analysis.stop();
Expand Down
13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cyanoacrylate",
"version": "1.2.2",
"version": "1.2.4",
"description": "Toolkit for large-scale automated traffic analysis of mobile apps on Android and iOS.",
"bugs": "https://github.com/tweaselORG/cyanoacrylate/issues",
"repository": {
Expand Down Expand Up @@ -38,12 +38,11 @@
"prepack": "rm -rf dist && yarn build && yarn typedoc",
"print-version": "echo \"// Shim to make the version available at runtime. Auto-generated, please ignore.\nexport const cyanoacrylateVersion = '$npm_package_version';\" > src/version.gen.ts",
"test": "echo 'TODO: No tests specified yet.'",
"postversion": "yarn print-version && git add src/version.gen.ts",
"watch": "parcel watch"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged && tsc && typedoc && git add docs"
"pre-commit": "lint-staged && tsc && typedoc && yarn print-version && git add docs src/version.gen.ts"
}
},
"lint-staged": {
Expand All @@ -57,13 +56,14 @@
"prettier": "@baltpeter/prettier-config",
"dependencies": {
"@types/har-format": "^1.2.10",
"andromatic": "^1.1.1",
"appstraction": "1.3.1",
"andromatic": "^1.1.2",
"appstraction": "file:.yalc/appstraction",
"autopy": "^1.1.1",
"cross-fetch": "^3.1.5",
"ctrlc-windows": "^2.1.0",
"execa": "^7.0.0",
"execa": "7.1.1",
"global-cache-dir": "^4.4.0",
"hash-object": "^5.0.1",
"js-ini": "^1.6.0",
"p-timeout": "^6.1.1",
"tempy": "^3.0.0"
Expand All @@ -83,6 +83,7 @@
"lint-staged": "13.1.1",
"parcel": "2.8.3",
"prettier": "2.8.4",
"type-fest": "^4.27.0",
"typedoc": "0.23.25",
"typedoc-plugin-markdown": "3.14.0",
"typescript": "4.9.5"
Expand Down
Loading