UNPKG

@corellium/corellium-api

Version:

Supported nodejs library for interacting with the Corellium service and VMs

1,191 lines (1,016 loc) 42.7 kB
'use strict' const { describe, it, before, beforeEach, after } = require('mocha') const assert = require('assert') const fs = require('fs') const path = require('path') const stream = require('stream') const wtfnode = require('wtfnode') const Corellium = require('../src/corellium').Corellium const { Input } = require('../src/input') const CONFIGURATION = require('./config.json') const { setFlagIfHookFailedDecorator, validateConfig, destroyInstance } = require('./testUtils') /** @typedef {import('../src/instance.js')} Instance */ /** @typedef {import('../src/project.js')} Project */ process.title = 'integration-tests' process.on('SIGUSR2', () => wtfnode.dump()) global.hookOrTestFailed = false async function turnOff (instance) { await instance.stop() await instance.waitForState('off') assert.strictEqual(instance.state, 'off') assert.notEqual(instance.stateChanged, null) } async function turnOn (instance) { await instance.start() await instance.waitForState('on') assert.strictEqual(instance.state, 'on') assert.notEqual(instance.stateChanged, null) } describe('Corellium API', function () { let BASE_LIFECYCLE_TIMEOUT = 0 let BASE_SNAPSHOT_TIMEOUT = 0 let INSTANCE_VERSIONS = [] if (CONFIGURATION.testFlavor === 'ranchu') { this.slow(10000) this.timeout(60000) BASE_LIFECYCLE_TIMEOUT = 40000 BASE_SNAPSHOT_TIMEOUT = 60000 INSTANCE_VERSIONS = ['7.1.2', '8.1.0', '9.0.0', '10.0.0', '11.0.0', '12.0.0'] } else { this.slow(40000) this.timeout(50000) BASE_LIFECYCLE_TIMEOUT = 700000 BASE_SNAPSHOT_TIMEOUT = 300000 INSTANCE_VERSIONS = ['10.3.3', '11.4.1', '12.4.1', '13.7', '14.1'] } const instanceMap = new Map() let corellium = null let loggedIn = false before( 'should have a configuration', setFlagIfHookFailedDecorator(() => validateConfig(CONFIGURATION)) ) before( 'should log in', setFlagIfHookFailedDecorator(async function () { corellium = new Corellium(CONFIGURATION) await corellium.login() const token = await corellium.token assert(token && token.token, 'Token was never set, login must have silently failed') loggedIn = true }) ) INSTANCE_VERSIONS.forEach(instanceVersion => { after(setFlagIfHookFailedDecorator(() => { this.timeout(80000) destroyInstance(instanceMap, instanceVersion) })) }) describe('projects', function () { let project = /** @type {Project} */ (null) before( 'should be logged in', setFlagIfHookFailedDecorator(function () { assert(loggedIn, 'All tests will fail as login failed') }) ) it('lists projects', async function () { project = await corellium.projects().then(projects => { const foundProject = projects.find(project => project.info.name === CONFIGURATION.project) assert( foundProject !== undefined, new Error( `Your test configuration specifies a project named "${CONFIGURATION.project}", but no such project was found on ${CONFIGURATION.endpoint}` ) ) return foundProject }) }) it('lists files', async function () { const files = await corellium.files() assert(Array.isArray(files)) }) it(`has room for ${INSTANCE_VERSIONS.length} new VMs (get quota / quotasUsed)`, async function () { assert(project, 'Unable to test as no project was returned from previous tests') assert(project.quotas !== project.quotasUsed) if (project.quotas.cores - project.quotasUsed.cores < 2 * INSTANCE_VERSIONS.length) { throw new Error( `no room for an extra device to be made, please free at least ${ 2 * INSTANCE_VERSIONS.length } cores` ) } }) INSTANCE_VERSIONS.forEach(instanceVersion => { it(`can start create ${instanceVersion}`, async function () { assert(project, 'Unable to test as no project was returned from previous tests') const name = `API Test ${instanceVersion}` let instanceConfig = {} if (CONFIGURATION.testFlavor === 'ranchu') { instanceConfig = { flavor: CONFIGURATION.testFlavor, name: name, os: instanceVersion } } else { instanceConfig = { flavor: CONFIGURATION.testFlavor, name: name, os: instanceVersion, bootOptions: { udid: '9564a02c6255d8c449a3f691aeb8296dd352f3d6' } } } const instance = await project.createInstance(instanceConfig) instanceMap.set(instanceVersion, instance) await instance.waitForState('creating') assert.strictEqual(instance.flavor, CONFIGURATION.testFlavor) assert.strictEqual(instance.name, name) }) }) it('can list supported devices', async function () { const supportedDevices = await corellium.supported() const firmware = supportedDevices.find(device => device.name === CONFIGURATION.testFlavor) assert(firmware) }) it('can get teams and users', async function () { const teamsAndUsers = await corellium.getTeamsAndUsers() teamsAndUsers.users.forEach((value, key) => { assert.strictEqual(value, corellium._users.get(key)) }) teamsAndUsers.teams.forEach((value, key) => { assert.strictEqual(value, corellium._teams.get(key)) }) }) it('can get roles', async function () { const roles = await corellium.roles() assert(roles, 'Roles should not be undefined, even if there have been no roles') }) it('can get instance', async function () { const instance = instanceMap.get(INSTANCE_VERSIONS[0]) await instance.waitForState('on') const foundInstance = await corellium.getInstance(instance.id) assert(foundInstance.id === instance.id) }) it('can get instance and bypass instance on error', async function () { const instance = instanceMap.get(INSTANCE_VERSIONS[0]) const foundInstance = await corellium.getInstance({ id: instance.id, throwIfNotOn: false }) assert(foundInstance.id === instance.id) }) // Not visible to cloud users with one project: it('can add and remove keys', async function () { const keyInfo = await project .addKey( 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCqpvRmc/JQoH9P6XVlHnod0wRCg+7iSGfpyoBoe+nWwp2iEqPyM7A2RzW7ZIX2FZmlD5ldR6Oj5Z+LUR/GXfCFQvpQkidL5htzGMoI59SwntpSMvHlFLOcbyS7VmI4MKbdIF+UrelPCcCJjOaZIFOJfNtuLWDx0L14jW/4wflzcj6Fd1rBTVh2SB3mvhsraOuv9an74zr/PMSHtpFnt5m4SYWpE4HLTf0FJksEe/Qda9jQu5i86Mhu6ewSAVccUDLzgz6E4i8hvSqfctcYGT7asqxsubPTpTPfuOkc3WOxlqZYnnAbpGh8NvCu9uC+5gfWRcLoyRBE4J2Y3wcfOueP example-key' ) .then(projectKey => { assert(projectKey.label === 'example-key', 'label defaults to public key comment') assert(projectKey.fingerprint === '9c:71:e5:40:08:fb:cd:88:1b:6d:8e:4f:c0:4c:0f:dd') return projectKey }) .catch(error => { throw error }) const keys = await project.keys() assert(!!keys.find(key => key.identifier === keyInfo.identifier)) await project.deleteKey(keyInfo.identifier) }) it('can refresh', async function () { const tempName = project.info.name await project.refresh() assert(tempName === project.info.name) }) INSTANCE_VERSIONS.forEach(instanceVersion => { it(`can getInstance ${instanceVersion}`, async function () { const instanceFromMap = instanceMap.get(instanceVersion) assert(instanceFromMap, 'Instance failed to create, test will fail') const instance = await project.getInstance(instanceFromMap.id) assert(instance.id === instanceFromMap.id) }) }) it('can get openvpn profile', async function () { const expected = Buffer.from('client') await project .vpnConfig('ovpn', undefined) .then(profile => { assert(profile.length > expected.length) assert(profile.compare(expected, 0, expected.length, 0, expected.length) === 0) return profile }) .catch(error => { // Hack to ignore onsite installs for this test if (!error.toString().includes('500 Internal Server Error')) { throw error } console.log('Forcing pass, this does not appear to be a server which supports vpns') return undefined }) }) it('can get tunnelblick profile', async function () { const expected = Buffer.from('504b0304', 'hex') await project .vpnConfig('tblk', undefined) .then(profile => { assert(profile.length > expected.length) assert(profile.compare(expected, 0, expected.length, 0, expected.length) === 0) return profile }) .catch(error => { // Hack to ignore onsite installs for this test if (!error.toString().includes('500 Internal Server Error')) { throw error } console.log('Forcing pass, this does not appear to be a server which supports vpns') return undefined }) }) INSTANCE_VERSIONS.forEach(instanceVersion => { it(`can finish create ${instanceVersion}`, async function () { this.slow(BASE_LIFECYCLE_TIMEOUT) this.timeout(BASE_LIFECYCLE_TIMEOUT + 30000) const instance = instanceMap.get(instanceVersion) assert(instance, 'Instance failed to create, test will fail') await instance.finishRestore() }) }) }) INSTANCE_VERSIONS.forEach(instanceVersion => { describe(`panics ${instanceVersion}`, function () { before( 'should have an instance', setFlagIfHookFailedDecorator(function () { assert( instanceMap.get(instanceVersion), 'No instances available for testing, tests will fail' ) }) ) it('can request panics', async function () { const instance = instanceMap.get(instanceVersion) const panics = instance.panics() assert(panics, 'Panics should not be undefined, even if there have been no panics') }) it('can clear panics', async function () { const instance = instanceMap.get(instanceVersion) instance.clearPanics() }) }) }) INSTANCE_VERSIONS.forEach(instanceVersion => { describe(`instances ${instanceVersion}`, function () { before( 'should have an instance', setFlagIfHookFailedDecorator(async function () { assert( instanceMap.get(instanceVersion), 'No instances available for testing, tests will fail' ) const instance = instanceMap.get(instanceVersion) await instance.waitForState('on') }) ) describe(`device lifecycle ${instanceVersion}`, function () { this.slow(BASE_LIFECYCLE_TIMEOUT / 2) this.timeout(BASE_LIFECYCLE_TIMEOUT) beforeEach(async function () { const instance = instanceMap.get(instanceVersion) await instance.update() }) it('can pause', async function () { const instance = instanceMap.get(instanceVersion) await instance.waitForState('on') await instance.pause() await instance.waitForState('paused') }) it('can unpause', async function () { const instance = instanceMap.get(instanceVersion) if (instance.state !== 'paused') { await instance.pause() await instance.waitForState('paused') } await instance.unpause() await instance.waitForState('on') }) it('can reboot', async function () { const instance = instanceMap.get(instanceVersion) await instance.reboot() }) it('can stop', async function () { const instance = instanceMap.get(instanceVersion) if (instance.state !== 'on') { await turnOn(instance) } await turnOff(instance) }) it('can start', async function () { const instance = instanceMap.get(instanceVersion) if (instance.state !== 'off') { await turnOff(instance) } await turnOn(instance) }) }) describe(`snapshots ${instanceVersion}`, function () { this.slow(BASE_SNAPSHOT_TIMEOUT) this.timeout(BASE_SNAPSHOT_TIMEOUT * 2) before( 'should have an up-to-date instance', setFlagIfHookFailedDecorator(async function () { const instance = instanceMap.get(instanceVersion) await instance.update() }) ) it('has a fresh snapshot', async function () { const instance = instanceMap.get(instanceVersion) const snapshots = await instance.snapshots() const fresh = snapshots.find(snap => snap.fresh) assert(fresh.status.created === true) // MIDDLEWARE-672 : Ensure snapshot has an actual date object assert(isNaN(fresh.created.getDate()) === false) }) let latestSnapshot if (CONFIGURATION.testFlavor === 'ranchu') { it('refuses to take snapshot if instance is on', async function () { const instance = instanceMap.get(instanceVersion) if (instance.state !== 'on') { await turnOn(instance) } await assert.rejects(() => instance.takeSnapshot()) }) it('can take snapshot if instance is off', async function () { const instance = instanceMap.get(instanceVersion) if (instance.state !== 'off') { await turnOff(instance) } latestSnapshot = await instance.takeSnapshot() while (latestSnapshot.status.created !== true) { await latestSnapshot.update() } }) } else { it('can take snapshot if instance is on', async function () { const instance = instanceMap.get(instanceVersion) latestSnapshot = await instance.takeSnapshot() while (latestSnapshot.status.created !== true) { await latestSnapshot.update() } }) } it('can restore a snapshot', async function () { assert(latestSnapshot, 'This test cannot run because there is no latestSnapshot to use') const instance = instanceMap.get(instanceVersion) if (CONFIGURATION.testFlavor === 'ranchu') { await latestSnapshot.restore() } else { await instance.pause() await instance.waitForState('paused') await latestSnapshot.restore() await instance.waitForAgentReady() } }) it('can delete a snapshot', async function () { assert(latestSnapshot, 'This test cannot run because there is no latestSnapshot to use') const instance = instanceMap.get(instanceVersion) assert((await instance.snapshots()).some(snapshot => snapshot.id === latestSnapshot.id)) await latestSnapshot.delete() while ((await instance.snapshots()).some(snapshot => snapshot.id === latestSnapshot.id)); }) after('should be on', async function () { const instance = instanceMap.get(instanceVersion) await instance.update() await turnOn(instance) }) }) it('can take a screenshot', async function () { const expected = Buffer.from('89504E470D0A1A0A', 'hex') const instance = instanceMap.get(instanceVersion) await instance.takeScreenshot().then(png => { assert(png.length > expected.length) assert(png.compare(expected, 0, expected.length, 0, expected.length) === 0) }) }) it('can rename', async function () { const instance = instanceMap.get(instanceVersion) const instanceName = instance.name async function rename (name) { await instance.rename(name) await instance.update() assert.strictEqual(instance.name, name) } await rename('test rename foo') await rename(instanceName) }) it('has a console log', async function () { const instance = instanceMap.get(instanceVersion) const log = await instance.consoleLog() if (log === undefined) { throw new Error('Unable to acquire any console log') } }) it('has a console', async function () { const instance = instanceMap.get(instanceVersion) const consoleStream = await instance.console() // Wait for the socket to open before killing it, // otherwise this will throw an error consoleStream.socket.on('open', function () { consoleStream.socket.close() }) // When the socket closes, it will be safe to destroy the console duplexify object consoleStream.socket.on('close', function () { consoleStream.destroy() }) }) it('can send input as an instance of Input', async function () { const input = new Input() const instance = instanceMap.get(instanceVersion) instance.sendInput(input.pressRelease('home')) }) it('can send input as an array of steps', async function () { const instance = instanceMap.get(instanceVersion) instance.sendInput([ { buttons: ['finger'], position: [[300, 600]], wait: 0 }, { buttons: [], wait: 100 } ]) }) if (CONFIGURATION.testFlavor === 'ranchu') { // Peripherals/sensor are only supported for android currently describe(`peripherals ${instanceVersion}`, function () { before( setFlagIfHookFailedDecorator(async function () { this.timeout(BASE_LIFECYCLE_TIMEOUT + 60000) const instance = instanceMap.get(instanceVersion) await instance.waitForState('on') }) ) it('can get peripheral data', async function () { const instance = instanceMap.get(instanceVersion) const peripheralData = await instance.getPeripherals() assert(peripheralData !== undefined && Object.keys(peripheralData).length > 0) }) it('can set and get updated peripheral data', async function () { const instance = instanceMap.get(instanceVersion) await instance.modifyPeripherals({ batteryCapacity: '42' }) const peripheralData = await instance.getPeripherals() assert(peripheralData !== undefined && parseInt(peripheralData.batteryCapacity) === 42) }) }) } describe(`agent ${instanceVersion}`, function () { let agent let installSuccess = false before( setFlagIfHookFailedDecorator(async function () { this.timeout(BASE_LIFECYCLE_TIMEOUT + 60000) const instance = instanceMap.get(instanceVersion) await instance.waitForState('on') await instance.waitForAgentReady() }) ) beforeEach(async function () { const instance = instanceMap.get(instanceVersion) if (agent === undefined || !agent.connected) { agent = await instance.agent() await agent.ready() } }) after( setFlagIfHookFailedDecorator(async function () { if (agent !== undefined && agent.connected) agent.disconnect() }) ) it('can list device apps', async function () { const appList = await agent.appList() assert(appList !== undefined && appList.length > 0) }) it('can use shellExec', async function () { const uname = await agent.shellExec('uname') assert(uname.output !== undefined && uname.output.length > 0) assert(uname.success, true) }) it('uses the correct Websocket url', async function () { if (/^https/.test(CONFIGURATION.endpoint)) { assert.match(agent.ws.url, /wss/) } else { assert.match(agent.ws.url, /ws/) } }) describe(`Files ${instanceVersion}`, function () { const expectedData = Buffer.from('D1FF', 'hex') let testPath it('can get temp file', async function () { testPath = await agent.tempFile() }) it('can upload a file', async function () { const rs = stream.Readable.from(expectedData) let lastStatus try { await agent.upload(testPath, rs, (_progress, status) => { lastStatus = status }) } catch (err) { assert(false, `Error uploading file during '${lastStatus} stage: ${err}`) } }) it('can stat a file', async function () { const stat = await agent.stat(testPath) assert.strictEqual(stat.name, testPath) }) it('can change a files attributes', async function () { await agent.changeFileAttributes(testPath, { mode: 511 }) const stat = await agent.stat(testPath) assert.strictEqual(stat.mode, 33279) }) it('can download files', async function () { try { const downloaded = await new Promise(resolve => { const rs = agent.download(testPath) const bufs = [] rs.on('data', function (chunk) { bufs.push(chunk) }) rs.on('end', function () { resolve(Buffer.concat(bufs)) }) }) assert(Buffer.compare(downloaded, expectedData) === 0) } catch (err) { assert(false, `Error reading downloadable file ${err}`) } }) it('can delete files', async function () { await agent.deleteFile(testPath).then(path => { assert(path === undefined) }) // We should get an OperationFailed since the file is gone try { await agent.stat(testPath) } catch (error) { if (CONFIGURATION.testFlavor === 'ranchu') { assert(error.toString().includes('No such file or directory')) } else { assert(error.toString().includes("Stat of file '" + testPath + "' failed.")) } } }) }) describe(`configuration profiles ${instanceVersion}`, function () { if (CONFIGURATION.testFlavor === 'ranchu') { // These are unimplemented on ranchu devices it('cannot use profile/list', async function () { assert.rejects(() => agent.profileList()) }) it('cannot use profile/install', async function () { assert.rejects(() => agent.installProfile('test')) }) it('cannot use profile/remove', async function () { assert.rejects(() => agent.removeProfile('test')) }) it('cannot use profile/get', async function () { assert.rejects(() => agent.getProfile('test')) }) } else { const profileID = 'TBA' it.skip('can use profile/list', async function () { await agent.profileList() }) it.skip('can use profile/install', async function () { const profile = fs.readFileSync(path.join(__dirname, 'TBA.mobileconfig')) await agent.installProfile(profile) }) it.skip('can use profile/get', async function () { await agent.getProfile(profileID) }) it.skip('can use profile/remove', async function () { await agent.removeProfile(profileID) }) } }) describe(`provisioning profiles ${instanceVersion}`, function () { if (CONFIGURATION.testFlavor === 'ranchu') { // These are unimplemented on ranchu devices it('cannot use provisioning/list', async function () { assert.rejects(() => agent.listProvisioningProfiles()) }) it('cannot use provisioning/install', async function () { assert.rejects(() => agent.installProvisioningProfile('test', true)) }) it('cannot use provisioning/remove', async function () { assert.rejects(() => agent.removeProvisioningProfile('test')) }) it('cannot use provisioning/preapprove', async function () { assert.rejects(() => agent.preApproveProvisioningProfile()) }) } else { const certID = 'TBA' const profileID = 'TBA' it.skip('can use provisioning/install', async function () { const profile = fs.readFileSync(path.join(__dirname, 'embedded.mobileprovision')) await agent.installProvisioningProfile(profile, true) }) it.skip('can use provisioning/list', async function () { await agent.listProvisioningProfiles() }) it.skip('can use provisioning/remove', async function () { await agent.removeProvisioningProfile(profileID) }) it.skip('can use provisioning/preapprove', async function () { await agent.preApproveProvisioningProfile(certID, profileID) }) } }) describe(`locks ${instanceVersion}`, function () { if (CONFIGURATION.testFlavor === 'ranchu') { // These are unimplemented on ranchu devices it('cannot use lock', async function () { assert.rejects(() => agent.lockDevice()) }) it('cannot use unlock', async function () { assert.rejects(() => agent.unlockDevice()) }) it('cannot use acquireDisableAutolockAssertion', async function () { assert.rejects(() => agent.acquireDisableAutolockAssertion()) }) it('cannot use releaseDisableAutolockAssertion', async function () { assert.rejects(() => agent.releaseDisableAutolockAssertion()) }) } else { it('can use lock', async function () { await agent.lockDevice() }) it('can use unlock', async function () { await agent.unlockDevice() }) it('can use acquireDisableAutolockAssertion', async function () { await agent.acquireDisableAutolockAssertion() }) it('can use releaseDisableAutolockAssertion', async function () { await agent.releaseDisableAutolockAssertion() }) } }) describe(`UI automation ${instanceVersion}`, function () { if (CONFIGURATION.testFlavor === 'ranchu') { // These are unimplemented on ranchu devices it('cannot use enableUIAutomation', async function () { assert.rejects(() => agent.enableUIAutomation()) }) it('cannot use disableUIAutomation', async function () { assert.rejects(() => agent.disableUIAutomation()) }) } else { it('can use enableUIAutomation', async function () { await agent.enableUIAutomation() }) it('can use disableUIAutomation', async function () { await agent.disableUIAutomation() }) } }) describe(`WiFi ${instanceVersion}`, function () { if (CONFIGURATION.testFlavor === 'ranchu') { // These are unimplemented on ranchu devices it('cannot use connectToWifi', async function () { assert.rejects(() => agent.connectToWifi()) }) it('cannot use disconnectFromWifi', async function () { assert.rejects(() => agent.disconnectFromWifi()) }) } else { it('can use disconnectFromWifi', async function () { await agent.disconnectFromWifi() // Wait a bit to avoid WiFi connection race on some devices await new Promise(resolve => setTimeout(resolve, 1000)) }) it('can use connectToWifi', async function () { await agent.connectToWifi() }) } }) let bundleID = '' if (CONFIGURATION.testFlavor === 'ranchu') bundleID = 'com.corellium.test.app' else bundleID = 'com.corellium.Red' describe(`Applications ${instanceVersion}`, function () { it('can install a signed apk/ipa', function () { this.slow(50000) this.timeout(100000) let appFile = '' if (CONFIGURATION.testFlavor === 'ranchu') { appFile = 'api-test.apk' } else { appFile = 'Red.ipa' } return agent .installFile(fs.createReadStream(path.join(__dirname, appFile))) .then(() => (installSuccess = true)) }) it('can run an app', async function () { assert(installSuccess, 'This test cannot run because application installation failed') await agent.run(bundleID) }) it('can kill an app', async function () { assert(installSuccess, 'This test cannot run because application installation failed') await agent.kill(bundleID) }) }) describe(`crash watcher ${instanceVersion}`, function () { let crashListener before( setFlagIfHookFailedDecorator(async function () { const instance = instanceMap.get(instanceVersion) await instance.waitForState('on') await instance.waitForAgentReady() crashListener = await instance.newAgent() }) ) after( setFlagIfHookFailedDecorator(async function () { if (crashListener !== undefined && crashListener.connected) crashListener.disconnect() }) ) it('can catch an expected crash', function () { return new Promise(resolve => { assert(installSuccess, 'This test cannot run because application installation failed') let targetLine = '' if (CONFIGURATION.testFlavor === 'ranchu') { targetLine = 'com.corellium.test.app' } else { targetLine = 'com.apple.Maps' } return crashListener.ready().then(() => { crashListener .crashes(targetLine, (err, crashReport) => { assert(!err, err) assert(crashReport !== undefined, 'The crash report is undefined') assert( crashReport.includes(targetLine), `The crash reported doesn't include "${targetLine}":\n\n${crashReport}` ) resolve() }) .catch(error => { if (error.message && error.message.includes('disconnected')) { return } throw error }) if (CONFIGURATION.testFlavor === 'ranchu') { return agent.runActivity( 'com.corellium.test.app', 'com.corellium.test.app/com.corellium.test.app.CrashActivity' ) } else { return agent.run('com.apple.Maps') } }) }) }) }) describe(`SSL pinning control ${instanceVersion}`, function () { if (CONFIGURATION.testFlavor === 'ranchu') { // These are unimplemented on ranchu devices it('cannot use enableSSLPinning', async function () { assert.rejects(() => agent.enableSSLPinning()) }) it('cannot use isSSLPinningEnabled', async function () { assert.rejects(() => agent.isSSLPinningEnabled()) }) it('cannot use disableSSLPinning', async function () { assert.rejects(() => agent.disableSSLPinning()) }) } else { it('can use enableSSLPinning', async function () { await agent.enableSSLPinning() }) it('can use isSSLPinningEnabled', async function () { assert(await agent.isSSLPinningEnabled()) }) it('can use disableSSLPinning', async function () { await agent.disableSSLPinning() }) } }) describe(`Network Monitor ${instanceVersion}`, function () { let netmon after( 'disconnect network monitor', setFlagIfHookFailedDecorator(function () { if (CONFIGURATION.testFlavor !== 'ranchu') { agent.kill('com.saurik.Cydia') } netmon.disconnect() }) ) it('can get monitor', async function () { const instance = instanceMap.get(instanceVersion) netmon = await instance.newNetworkMonitor() assert.strictEqual(netmon !== undefined, true, 'Expected monitor to be returned') }) it('uses the correct Websocket url', async function () { if (/^https/.test(CONFIGURATION.endpoint)) { assert.match(netmon.instance._agent.ws.url, /wss/) } else { assert.match(netmon.instance._agent.ws.url, /ws/) } }) it('can start monitor', async function () { assert.strictEqual( await netmon.start({ truncatePcap: true }), true, 'Expected the network monitor to start and return true' ) }) it('can monitor data', async function () { assert(installSuccess, 'This test cannot run because application installation failed.') return new Promise(resolve => { this.slow(80000) this.timeout(100000) netmon.handleMessage(message => { const hostHeader = message.request.headers.find(header => header.key === 'Host') if (CONFIGURATION.testFlavor === 'ranchu') { if (hostHeader.value === 'corellium.com') { netmon.handleMessage(null) resolve() } } else { if (hostHeader.value === 'cydia.zodttd.com') { netmon.handleMessage(null) resolve() } } }) // The test application gets ECONNREFUSEDs if it's run too soon after // Network Monitor starts. return new Promise(resolve => setTimeout(resolve, 1000 * 5)).then(async () => { if (CONFIGURATION.testFlavor === 'ranchu') { await agent.runActivity( 'com.corellium.test.app', 'com.corellium.test.app/com.corellium.test.app.NetworkActivity' ) resolve() } else { return agent.run('com.saurik.Cydia') } }) }) }) it('can stop monitor', async function () { assert.strictEqual( await netmon.stop(), true, 'Expected the network monitor to stop and return true' ) }) it('can clear log', async function () { await netmon.clearLog() }) }) describe(`Frida ${instanceVersion}`, function () { let pid = 0 let name = '' if (CONFIGURATION.testFlavor === 'ranchu') { it('can get process list', async function () { const procList = await agent.runFridaPs() const lines = procList.output.trim().split('\n') lines.shift() lines.shift() for (const line of lines) { [pid, name] = line.trim().split(/\s+/) if (name === 'keystore') { break } } assert(pid !== 0) }) } else { it('cannot get process list', async function () { assert.rejects(() => agent.runFridaPs()) }) } it('can get console', async function () { const instance = instanceMap.get(instanceVersion) const consoleStream = await instance.fridaConsole() consoleStream.socket.on('close', function () { consoleStream.destroy() }) consoleStream.socket.close() }) describe('frida attaching and execution', function () { if (CONFIGURATION.testFlavor === 'ranchu') { it('can attach frida', async function () { if (name === '') { name = 'keystore' } await agent.runFrida(pid, name) let processList do { processList = await agent.runFridaPs() } while (!(processList.attached && processList.attached.target_name === name)) }) it('can get frida scripts', async function () { const fridaScripts = await agent.stat('/data/corellium/frida/scripts/') const scriptList = fridaScripts.entries.map(entry => entry.name) let s = '' for (s of scriptList) { if (s === 'hook_native.js') break } assert(s !== '') }) it.skip('can execute script', async function () { const instance = instanceMap.get(instanceVersion) await instance.executeFridaScript('/data/corellium/frida/scripts/hook_native.js') await new Promise(resolve => setTimeout(resolve, 5000)) const fridaConsole = await instance.fridaConsole() const fridaOutput = await new Promise(resolve => { const w = new stream.Writable({ write (chunk, _encoding, _callback) { fridaConsole.socket.close() resolve(chunk) } }) fridaConsole.pipe(w) }) assert(fridaOutput.toString().includes('Hook android_log_write()')) }) it('can detach frida', async function () { await agent.runFridaKill() }) } else { it('cannot attach frida', async function () { assert.rejects(() => agent.runFrida(1, 'launchd')) }) it('cannot get frida scripts', async function () { assert.rejects(() => agent.stat('/data/corellium/frida/scripts/')) }) it.skip('cannot execute script', async function () { const instance = instanceMap.get(instanceVersion) await instance.executeFridaScript('/data/corellium/frida/scripts/hook_native.js') }) it('cannot detach frida', async function () { assert.rejects(() => agent.runFridaKill()) }) } }) }) describe(`app clean up ${instanceVersion}`, function () { it('can uninstall an app', async function () { assert(installSuccess, 'This test cannot run because application installation failed') let lastStatus try { await agent.uninstall('com.corellium.test.app', (_progress, status) => { lastStatus = status }) } catch (err) { assert(false, `Error uninstalling app during '${lastStatus} stage: ${err}`) } }) }) describe(`CoreTrace ${instanceVersion}`, function () { let pid = 0 it('can get thread list', async function () { const instance = instanceMap.get(instanceVersion) const threadList = await instance.getCoreTraceThreadList() for (const p of threadList) { if (p.name.includes('corelliumd')) { pid = p.pid break } } assert(pid !== 0) }) it('can set filter', async function () { const instance = instanceMap.get(instanceVersion) await instance.setCoreTraceFilter([pid], [], []) }) it('can start capture', async function () { const instance = instanceMap.get(instanceVersion) await instance.startCoreTrace() }) it.skip('can capture data', async function () { let statTarget = '' const instance = instanceMap.get(instanceVersion) if (CONFIGURATION.testFlavor === 'ranchu') statTarget = '/data/corellium/frida/scripts/' else statTarget = '/var/mobile/Media' await agent.stat(statTarget) await new Promise(resolve => setTimeout(resolve, 9000)) const log = await instance.downloadCoreTraceLog() assert(log !== undefined) assert(log.toString().includes(':corelliumd')) }) it('can stop capture', async function () { const instance = instanceMap.get(instanceVersion) await instance.stopCoreTrace() }) it('can clear filter', async function () { const instance = instanceMap.get(instanceVersion) await instance.clearCoreTraceFilter() }) it('can clear log', async function () { const instance = instanceMap.get(instanceVersion) await instance.clearCoreTraceLog() }) }) }) }) }) })