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

Fixes #81: Supervision mode #82

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
35 changes: 18 additions & 17 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ An ID of a known permission on Android.

#### Defined in

[android.ts:913](https://github.com/tweaselORG/appstraction/blob/main/src/android.ts#L913)
[android.ts:955](https://github.com/tweaselORG/appstraction/blob/main/src/android.ts#L955)

___

Expand Down Expand Up @@ -81,7 +81,7 @@ A supported attribute for the `getDeviceAttribute()` function, depending on the

#### Defined in

[index.ts:400](https://github.com/tweaselORG/appstraction/blob/main/src/index.ts#L400)
[index.ts:439](https://github.com/tweaselORG/appstraction/blob/main/src/index.ts#L439)

___

Expand All @@ -100,7 +100,7 @@ The options for each attribute available through the `getDeviceAttribute()` func

#### Defined in

[index.ts:406](https://github.com/tweaselORG/appstraction/blob/main/src/index.ts#L406)
[index.ts:445](https://github.com/tweaselORG/appstraction/blob/main/src/index.ts#L445)

___

Expand All @@ -112,7 +112,7 @@ An ID of a known permission on iOS.

#### Defined in

[ios.ts:455](https://github.com/tweaselORG/appstraction/blob/main/src/ios.ts#L455)
[ios.ts:627](https://github.com/tweaselORG/appstraction/blob/main/src/ios.ts#L627)

___

Expand Down Expand Up @@ -176,6 +176,7 @@ Functions that are available for the platforms.
| `target.platform` | `Platform` | The platform this instance is configured for, i.e. `ios` or `android`. |
| `target.runTarget` | `RunTarget` | The run target this instance is configured for, i.e. `device` or `emulator`. |
| `uninstallApp` | (`appId`: `string`) => `Promise`<`void`\> | Uninstall the app with the given app ID. Will not fail if the app is not installed. This also removes any data stored by the app. |
| `unlockScreen` | () => `Promise`<`void`\> | Simulates key presses to unlock the screen. This only works if no passcode is set on the device. |
| `waitForDevice` | (`tries?`: `number`) => `Promise`<`void`\> | Wait until the device or emulator has been connected and has booted up completely. |

#### Defined in
Expand All @@ -200,7 +201,7 @@ The options for the `platformApi()` function.

#### Defined in

[index.ts:338](https://github.com/tweaselORG/appstraction/blob/main/src/index.ts#L338)
[index.ts:368](https://github.com/tweaselORG/appstraction/blob/main/src/index.ts#L368)

___

Expand All @@ -219,7 +220,7 @@ Connection details for a proxy.

#### Defined in

[index.ts:414](https://github.com/tweaselORG/appstraction/blob/main/src/index.ts#L414)
[index.ts:453](https://github.com/tweaselORG/appstraction/blob/main/src/index.ts#L453)

___

Expand All @@ -243,19 +244,19 @@ The options for a specific platform/run target combination.
| `android` | { `device`: `unknown` ; `emulator`: `unknown` } | The options for the Android platform. |
| `android.device` | `unknown` | The options for the Android physical device run target. |
| `android.emulator` | `unknown` | The options for the Android emulator run target. |
| `ios` | { `device`: ``"ssh"`` extends `Capability` ? { `ip`: `string` ; `rootPw?`: `string` } : `unknown` ; `emulator`: `never` } | The options for the iOS platform. |
| `ios.device` | ``"ssh"`` extends `Capability` ? { `ip`: `string` ; `rootPw?`: `string` } : `unknown` | The options for the iOS physical device run target. |
| `ios` | { `device`: ``"ssh"`` extends `Capability` ? { `ip`: `string` ; `rootPw?`: `string` } : `unknown` & ``"supervision"`` extends `Capability` ? { `supervisionKeyPassword?`: `string` } : `unknown` ; `emulator`: `never` } | The options for the iOS platform. |
| `ios.device` | ``"ssh"`` extends `Capability` ? { `ip`: `string` ; `rootPw?`: `string` } : `unknown` & ``"supervision"`` extends `Capability` ? { `supervisionKeyPassword?`: `string` } : `unknown` | The options for the iOS physical device run target. |
| `ios.emulator` | `never` | The options for the iOS emulator run target. |

#### Defined in

[index.ts:365](https://github.com/tweaselORG/appstraction/blob/main/src/index.ts#L365)
[index.ts:395](https://github.com/tweaselORG/appstraction/blob/main/src/index.ts#L395)

___

### SupportedCapability

Ƭ **SupportedCapability**<`Platform`\>: `Platform` extends ``"android"`` ? ``"wireguard"`` \| ``"root"`` \| ``"frida"`` \| ``"certificate-pinning-bypass"`` : `Platform` extends ``"ios"`` ? ``"ssh"`` \| ``"frida"`` \| ``"certificate-pinning-bypass"`` : `never`
Ƭ **SupportedCapability**<`Platform`\>: `Platform` extends ``"android"`` ? ``"wireguard"`` \| ``"root"`` \| ``"frida"`` \| ``"certificate-pinning-bypass"`` : `Platform` extends ``"ios"`` ? ``"ssh"`` \| ``"frida"`` \| ``"certificate-pinning-bypass"`` \| ``"supervision"`` : `never`

A capability for the `platformApi()` function.

Expand All @@ -267,7 +268,7 @@ A capability for the `platformApi()` function.

#### Defined in

[index.ts:393](https://github.com/tweaselORG/appstraction/blob/main/src/index.ts#L393)
[index.ts:432](https://github.com/tweaselORG/appstraction/blob/main/src/index.ts#L432)

___

Expand Down Expand Up @@ -309,7 +310,7 @@ Configuration string for WireGuard.

#### Defined in

[index.ts:421](https://github.com/tweaselORG/appstraction/blob/main/src/index.ts#L421)
[index.ts:460](https://github.com/tweaselORG/appstraction/blob/main/src/index.ts#L460)

## Variables

Expand All @@ -321,7 +322,7 @@ The IDs of known permissions on Android.

#### Defined in

[android.ts:782](https://github.com/tweaselORG/appstraction/blob/main/src/android.ts#L782)
[android.ts:824](https://github.com/tweaselORG/appstraction/blob/main/src/android.ts#L824)

___

Expand All @@ -333,7 +334,7 @@ The IDs of known permissions on iOS.

#### Defined in

[ios.ts:438](https://github.com/tweaselORG/appstraction/blob/main/src/ios.ts#L438)
[ios.ts:610](https://github.com/tweaselORG/appstraction/blob/main/src/ios.ts#L610)

## Functions

Expand Down Expand Up @@ -372,7 +373,7 @@ An object with the properties listed above, or `undefined` if the file doesn't e

#### Defined in

[util.ts:68](https://github.com/tweaselORG/appstraction/blob/main/src/util.ts#L68)
[utils/index.ts:63](https://github.com/tweaselORG/appstraction/blob/main/src/utils/index.ts#L63)

___

Expand All @@ -394,7 +395,7 @@ Pause for a given duration.

#### Defined in

[util.ts:45](https://github.com/tweaselORG/appstraction/blob/main/src/util.ts#L45)
[utils/index.ts:40](https://github.com/tweaselORG/appstraction/blob/main/src/utils/index.ts#L40)

___

Expand Down Expand Up @@ -426,4 +427,4 @@ The API object for the given platform and run target.

#### Defined in

[index.ts:430](https://github.com/tweaselORG/appstraction/blob/main/src/index.ts#L430)
[index.ts:469](https://github.com/tweaselORG/appstraction/blob/main/src/index.ts#L469)
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,19 @@
"@napi-rs/lzma": "^1.1.2",
"andromatic": "^1.0.0",
"autopy": "^1.1.1",
"bplist-creator": "^0.1.1",
"cross-fetch": "^3.1.5",
"execa": "^6.1.0",
"file-type": "^18.3.0",
"frida": "^16.0.8",
"fs-extra": "^11.1.0",
"global-cache-dir": "^5.0.0",
"ipa-extract-info": "^1.2.6",
"node-forge": "^1.3.1",
"node-ssh": "^13.1.0",
"p-retry": "^5.1.2",
"pkijs": "^3.0.14",
"semver": "^7.3.8",
"simple-plist": "^1.3.1",
"tempy": "^3.0.0",
"ts-node": "^10.9.1",
"yauzl": "^2.10.0"
Expand All @@ -78,6 +81,7 @@
"@parcel/transformer-typescript-types": "2.8.2",
"@types/fs-extra": "^11.0.0",
"@types/node": "^18.11.18",
"@types/node-forge": "^1.3.2",
"@types/plist": "^3.0.2",
"@types/promise-timeout": "^1.3.0",
"@types/semver": "^7.3.13",
Expand All @@ -94,5 +98,8 @@
"typedoc": "^0.23.26",
"typedoc-plugin-markdown": "3.14.0",
"typescript": "4.9.4"
},
"engines": {
"node": ">=15.0.0"
}
}
24 changes: 17 additions & 7 deletions src/android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { createHash, randomUUID } from 'crypto';
import { fileTypeFromFile } from 'file-type';
import frida from 'frida';
import { open, rm, writeFile } from 'fs/promises';
import forge from 'node-forge';
import pRetry from 'p-retry';
import { basename, dirname } from 'path';
import { major as semverMajor, minVersion as semverMinVersion } from 'semver';
Expand All @@ -20,20 +21,18 @@ import type {
} from '.';
import { dependencies } from '../package.json';
import { venvOptions } from '../scripts/common/python';
import type { ParametersExceptFirst, XapkManifest } from './util';
import type { ParametersExceptFirst, XapkManifest } from './utils';
import {
asyncUnimplemented,
escapeArg,
escapeCommand,
forEachInZip,
getFileFromZip,
getObjFromFridaScript,
isRecord,
parseAppMeta,
parsePemCertificateFromFile,
retryCondition,
tmpFileFromZipEntry,
} from './util';
} from './utils';
import { certSubjectToAsn1, parsePemCertificateFromFile } from './utils/crypto';
import { forEachInZip, getFileFromZip, tmpFileFromZipEntry } from './utils/zip';

const adb = (...args: ParametersExceptFirst<typeof runAndroidDevTool>) => runAndroidDevTool('adb', args[0], args[1]);
const venv = getVenv(venvOptions);
Expand Down Expand Up @@ -231,7 +230,9 @@ export const androidApi = <RunTarget extends SupportedRunTarget<'android'>>(
getCertificateSubjectHashOld: async (path: string) => {
const { cert } = await parsePemCertificateFromFile(path);

const hash = createHash('md5').update(Buffer.from(cert.subject.valueBeforeDecode)).digest();
const hash = createHash('md5')
.update(forge.asn1.toDer(certSubjectToAsn1(cert)).toHex(), 'hex')
.digest();
const truncated = hash.subarray(0, 4);
const ulong = (truncated[0]! | (truncated[1]! << 8) | (truncated[2]! << 16) | (truncated[3]! << 24)) >>> 0;

Expand Down Expand Up @@ -808,6 +809,15 @@ export const androidApi = <RunTarget extends SupportedRunTarget<'android'>>(
)
throw new Error('Failed to set proxy.');
},
unlockScreen: async () => {
const { stdout } = await adb(['shell', 'dumpsys', 'window']);
if (stdout.includes('mAwake=false'))
// The screen is off, simulate lock button press
await adb(['shell', 'input', 'keyevent', '26']);
if (stdout.includes('mShowingLockscreen=true') || stdout.includes('mDreamingLockscreen=true'))
// The screen is locked, simulate a menu key press
await adb(['shell', 'input', 'keyevent', '82']);
},
});

/** The IDs of known permissions on Android. */
Expand Down
45 changes: 40 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { AndroidPermission } from './android';
import { androidApi } from './android';
import type { IosPermission } from './ios';
import { iosApi } from './ios';
import type { ParametersExceptFirst } from './util';
import type { ParametersExceptFirst } from './utils';

/** A platform that is supported by this library. */
export type SupportedPlatform = 'android' | 'ios';
Expand Down Expand Up @@ -285,6 +285,9 @@ export type PlatformApi<
: Platform extends 'ios'
? (proxy: Proxy | null) => Promise<void>
: never;

/** Simulates key presses to unlock the screen. This only works if no passcode is set on the device. */
unlockScreen: () => Promise<void>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screen unlocking seems like something worthy of inclusion in the README feature list.

/**
* An indicator for what platform and run target this instance of PlatformApi is configured for. This is useful
* mostly to write typeguards.
Expand Down Expand Up @@ -326,6 +329,29 @@ export type PlatformApi<
*/
setupEnvironment: () => Promise<void>;
ensureFrida: () => Promise<void>;
/**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should advertise enabling and disabling supervision without resetting on rooted devices in the feature list as well.

* Ensures that the current host is configured to supervise the connected device. If this is not the case,
* it sets the host to be the (only) supervisor by installing its certificate on the device. This will
* overwrite parts of the exisiting CloudConfiguration. If there is no host certificate yet, or it has
* expired, it will be generated.
*
* Might restart the device, if a new configuration is pushed. You are advised to wait for the device.
*
* @param forceNewKey If set to `true`, a new host key will be generated and set up, even if there is
* already a valid old one.
*/
ensureSupervision: (options?: { forceNewKey?: boolean }) => Promise<void>;
/**
* Removes all configured supervision hosts from the device.
*
* Will restart the device. You are advised to wait for the device.
*/
removeSupervision: () => Promise<void>;
/**
* Restarts the device only in userspace, e.g. to keep the jailbroken kernel running. You might want to
* wait for the device to ensure it is available again.
*/
userspaceRestart: () => Promise<void>;
}
: never;
};
Expand Down Expand Up @@ -382,22 +408,31 @@ export type RunTargetOptions<
/** The options for the iOS emulator run target. */
emulator: never;
/** The options for the iOS physical device run target. */
device: 'ssh' extends Capability
device: ('ssh' extends Capability
? {
/** The password of the root user on the device, defaults to `alpine` if not set. */
rootPw?: string;
/** The device's IP address. */
ip: string;
}
: unknown;
: unknown) &
('supervision' extends Capability
? {
/**
* The password of the private key of the supervision certificate, defaults to `appstraction` if
* not set.
*/
supervisionKeyPassword?: string;
}
: unknown);
Comment on lines +419 to +427
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't we say we wanted to disable the capability until it is actually needed for anything?

};
};

/** A capability for the `platformApi()` function. */
export type SupportedCapability<Platform extends SupportedPlatform> = Platform extends 'android'
? 'wireguard' | 'root' | 'frida' | 'certificate-pinning-bypass'
: Platform extends 'ios'
? 'ssh' | 'frida' | 'certificate-pinning-bypass'
? 'ssh' | 'frida' | 'certificate-pinning-bypass' | 'supervision'
: never;

/** A supported attribute for the `getDeviceAttribute()` function, depending on the platform. */
Expand Down Expand Up @@ -450,5 +485,5 @@ export function platformApi<

export { androidPermissions } from './android';
export { iosPermissions } from './ios';
export { parseAppMeta, pause } from './util';
export { parseAppMeta, pause } from './utils';
export { IosPermission, AndroidPermission };
Loading