@corellium/corellium-cli 1.1.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@corellium/corellium-cli",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Corellium CLI Tool",
5
5
  "scripts": {
6
6
  "corellium": "node index.js",
@@ -33,6 +33,7 @@
33
33
  "progress": "^2.0.3",
34
34
  "prompts": "^2.4.2",
35
35
  "uuid": "^8.3.2",
36
+ "websocket-stream": "^5.5.2",
36
37
  "xhr2": "^0.2.0",
37
38
  "yargs": "^17.5.1"
38
39
  },
@@ -1,4 +1,4 @@
1
- const Client = require('../Client')
1
+ const Client = require('./Client')
2
2
 
3
3
  /**
4
4
  * @typedef {object} AppsResult
@@ -0,0 +1,89 @@
1
+ const axios = require('axios').default
2
+ const { readProfile } = require('../profile')
3
+ const { createWriteStream } = require('fs')
4
+ const wsstream = require('websocket-stream')
5
+
6
+ /**
7
+ * Client Base class
8
+ */
9
+ class Client {
10
+ constructor (argv) {
11
+ this.argv = Object.assign({}, argv)
12
+ this.project = argv.project || argv.projectId
13
+ this.instance = argv.instance || argv.instanceId
14
+ this._profile = readProfile()
15
+ this._version = require('../../package.json').version
16
+ }
17
+
18
+ /**
19
+ * @protected
20
+ * @returns default headers
21
+ */
22
+ _headers = () => ({
23
+ Authorization: `Bearer ${this._profile.token}`,
24
+ 'User-Agent': `corellium-cli v${this._version}`
25
+ })
26
+
27
+ /**
28
+ * Retrieve file stream
29
+ * @protected
30
+ * @param {string} path
31
+ * @param {PathLike} outputFile
32
+ * @returns {Promise<any>}
33
+ */
34
+ _fetchStream = async (path, outputFile) => {
35
+ const response = await axios({
36
+ method: 'GET',
37
+ url: `${this._profile.endpoint}/api/${path}`,
38
+ headers: this._headers(),
39
+ responseType: 'stream'
40
+ })
41
+
42
+ const writeStream = createWriteStream(outputFile)
43
+ response.data.pipe(writeStream)
44
+
45
+ return new Promise((resolve, reject) => {
46
+ writeStream.on('finish', resolve)
47
+ writeStream.on('error', reject)
48
+ })
49
+ }
50
+
51
+ /**
52
+ * Get Websocket stream
53
+ * @protected
54
+ * @param {string} token
55
+ * @returns {wsstream.WebSocketDuplex}
56
+ */
57
+ _fetchWSS = (token) => {
58
+ const url = `wss://${this._profile.endpoint}/api/v1/agent/${token}`
59
+ return wsstream(url, ['binary'])
60
+ }
61
+
62
+ /**
63
+ * Perform HTTP transaction
64
+ * @protected
65
+ * @param {string} method
66
+ * @param {string} path
67
+ * @param {object} data
68
+ * @returns {Promise<any>}
69
+ */
70
+ _fetch = async (method, path, data) => {
71
+ const headers = this._headers()
72
+ let response
73
+ switch (method) {
74
+ case 'GET':
75
+ response = await axios.get(`${this._profile.endpoint}/api/${path}`, { headers })
76
+ break
77
+ case 'POST':
78
+ response = await axios.post(`${this._profile.endpoint}/api/${path}`, data, { headers })
79
+ break
80
+ case 'DELETE':
81
+ response = await axios.delete(`${this._profile.endpoint}/api/${path}`, { headers })
82
+ break
83
+ }
84
+ if (response.status >= 400) { throw new Error(response.statusText) }
85
+ return response.data || {}
86
+ }
87
+ }
88
+
89
+ module.exports = Client
@@ -0,0 +1,84 @@
1
+ const Client = require('./Client')
2
+
3
+ /**
4
+ * Instance class
5
+ */
6
+ class InstanceCLI extends Client {
7
+ instanceBasePath = () => `v1/instances/${this.instance}`
8
+ preauthedBasePath = () => 'v1/preauthed'
9
+
10
+ /**
11
+ * Upgrade iOS version on instance
12
+ * @returns {Promise<any>}
13
+ */
14
+ upgrade = async (options) => {
15
+ const { os, osbuild } = options
16
+ return await this._fetch('POST', `${this.instanceBasePath()}/upgrade`, { os, osbuild })
17
+ }
18
+
19
+ /**
20
+ * Get websocket stream from token
21
+ * @returns {wsstream.WebSocketDuplex}
22
+ */
23
+ wss = (token) => {
24
+ return this._fetchWSS(token)
25
+ }
26
+
27
+ /**
28
+ * Start instance
29
+ */
30
+ start = async (options) => {
31
+ const { sockcap, paused } = options
32
+ return await this._fetch('POST', `${this.instanceBasePath()}/start`, { sockcap, paused })
33
+ }
34
+
35
+ /**
36
+ * Restore backup to instance
37
+ * @returns {Promise<any>}
38
+ */
39
+ restoreBackup = async () => {
40
+ return await this._fetch('POST', `${this.instanceBasePath()}/restoreBackup`)
41
+ }
42
+
43
+ /**
44
+ * Enable monitoring
45
+ * @returns {Promise<any>}
46
+ */
47
+ enableMonitoring = async (options) => {
48
+ return await this._fetch('POST', `${this.instanceBasePath()}/netdump/enable`, options)
49
+ }
50
+
51
+ /**
52
+ * Disable monitoring
53
+ * @returns {Promise<any>}
54
+ */
55
+ disableMonitoring = async () => {
56
+ return await this._fetch('POST', `${this.instanceBasePath()}/netdump/disable`)
57
+ }
58
+
59
+ /**
60
+ * Authorize pcap download
61
+ * @returns {Promise<any>}
62
+ */
63
+ authorizePcap = async () => {
64
+ return await this._fetch('POST', `${this.instanceBasePath()}/netdump-authorize`)
65
+ }
66
+
67
+ /**
68
+ * Download file
69
+ * @returns {Promise<any>}
70
+ */
71
+ download = async (token, file, outputFile) => {
72
+ return await this._fetchStream(`${this.preauthedBasePath()}/${token}/${file}`, outputFile)
73
+ }
74
+
75
+ /**
76
+ * Fetch instance information
77
+ * @returns {Promise<any>}
78
+ */
79
+ info = async () => {
80
+ return await this._fetch('GET', `${this.instanceBasePath()}`, {})
81
+ }
82
+ }
83
+
84
+ module.exports = InstanceCLI
@@ -1,6 +1,6 @@
1
- const Client = require('./commands/Client')
2
- const util = require('./utils')
3
- const { handler: login } = require('./commands/login')
1
+ const Client = require('./Client')
2
+ const util = require('../utils')
3
+ const { handler: login } = require('../commands/login')
4
4
 
5
5
  class TrialAccount extends Client {
6
6
  constructor (argv) {
@@ -1,7 +1,7 @@
1
- const Client = require('../Client')
1
+ const Client = require('./Client')
2
2
  const {
3
3
  updateProfile
4
- } = require('../../profile')
4
+ } = require('../profile')
5
5
 
6
6
  /**
7
7
  * Webplayer class
@@ -1,4 +1,4 @@
1
- const AgentCLI = require('./Agent')
1
+ const AgentCLI = require('../../clients/Agent')
2
2
  const { displayTable } = require('../../table')
3
3
  const { validateNonEmpty } = require('../../utils')
4
4
  const { getErrorMessage } = require('../../error')
@@ -1,4 +1,4 @@
1
- const AgentCLI = require('./Agent')
1
+ const AgentCLI = require('../../clients/Agent')
2
2
  const { displayTable } = require('../../table')
3
3
  const { validateNonEmpty } = require('../../utils')
4
4
  const { getErrorMessage } = require('../../error')
@@ -1,4 +1,4 @@
1
- const AgentCLI = require('./Agent')
1
+ const AgentCLI = require('../../clients/Agent')
2
2
  const { validateNonEmpty } = require('../../utils')
3
3
  const { getErrorMessage } = require('../../error')
4
4
  const log = require('../../logging')
@@ -5,6 +5,7 @@ const subcommands = [
5
5
  require('./stop'),
6
6
  require('./start'),
7
7
  require('./upgrade'),
8
+ require('./netmon'),
8
9
  require('./restoreBackup')
9
10
  ]
10
11
 
@@ -0,0 +1,44 @@
1
+ const log = require('../../../logging')
2
+ const InstanceCLI = require('../../../clients/Instance')
3
+
4
+ async function builder (yargs) {
5
+ yargs
6
+ .positional('instanceId', {
7
+ type: 'string',
8
+ describe: 'Instance id',
9
+ demandOption: true
10
+ })
11
+ .option('verbose', {
12
+ alias: 'v',
13
+ type: 'boolean',
14
+ describe: 'Console will receive verbose error output',
15
+ demandOption: false
16
+ })
17
+ }
18
+
19
+ async function handler (argv) {
20
+ try {
21
+ const instance = new InstanceCLI(argv)
22
+ await instance.disableMonitoring()
23
+ log.info('Success')
24
+ } catch (e) {
25
+ if (e.response) {
26
+ log.error(`${e.response.status} ${e.response.data.error || e.response.statusText}`)
27
+ } else {
28
+ if (e.message) {
29
+ log.error(e.message)
30
+ } else {
31
+ log.error('Urecognized error, use --verbose for details')
32
+ }
33
+ }
34
+
35
+ if (argv.verbose) { log.error(JSON.stringify(e)) }
36
+ }
37
+ }
38
+
39
+ module.exports = {
40
+ builder,
41
+ handler,
42
+ command: 'disable <instanceId> [--verbose]',
43
+ describe: 'Disable network monitoring'
44
+ }
@@ -0,0 +1,50 @@
1
+ const log = require('../../../logging')
2
+ const InstanceCLI = require('../../../clients/Instance')
3
+
4
+ async function builder (yargs) {
5
+ yargs
6
+ .positional('instanceId', {
7
+ type: 'string',
8
+ describe: 'Instance id',
9
+ demandOption: true
10
+ })
11
+ .positional('outputFile', {
12
+ type: 'string',
13
+ describe: 'Path to output',
14
+ demandOption: true
15
+ })
16
+ .option('verbose', {
17
+ alias: 'v',
18
+ type: 'boolean',
19
+ describe: 'Console will receive verbose error output',
20
+ demandOption: false
21
+ })
22
+ }
23
+
24
+ async function handler (argv) {
25
+ try {
26
+ const instance = new InstanceCLI(argv)
27
+ const { token } = await instance.authorizePcap()
28
+ await instance.download(token, 'netdump.pcap', argv.outputFile)
29
+ log.info(`Success, pcap saved to ${argv.outputFile}`)
30
+ } catch (e) {
31
+ if (e.response) {
32
+ log.error(`${e.response.status} ${e.response.data.error || e.response.statusText}`)
33
+ } else {
34
+ if (e.message) {
35
+ log.error(e.message)
36
+ } else {
37
+ log.error('Urecognized error, use --verbose for details')
38
+ }
39
+ }
40
+
41
+ if (argv.verbose) { log.error(JSON.stringify(e)) }
42
+ }
43
+ }
44
+
45
+ module.exports = {
46
+ builder,
47
+ handler,
48
+ command: 'download <instanceId> <outputFile> [--verbose]',
49
+ describe: 'Download pcap file'
50
+ }
@@ -0,0 +1,72 @@
1
+ const log = require('../../../logging')
2
+ const InstanceCLI = require('../../../clients/Instance')
3
+
4
+ async function builder (yargs) {
5
+ yargs
6
+ .positional('instanceId', {
7
+ type: 'string',
8
+ describe: 'Instance id',
9
+ demandOption: true
10
+ })
11
+ .option('srcPorts', {
12
+ type: 'array',
13
+ describe: 'Source ports to filter'
14
+ })
15
+ .option('dstPorts', {
16
+ type: 'array',
17
+ describe: 'Destination ports to filter'
18
+ })
19
+ .option('ports', {
20
+ type: 'array',
21
+ describe: 'Source or destination ports to filter'
22
+ })
23
+ .option('processes', {
24
+ type: 'array',
25
+ describe: 'List of process names or pids to filter'
26
+ })
27
+ .option('portRanges', {
28
+ type: 'array',
29
+ describe: 'List of port ranges in that format \'[beginPort]-[endport]\''
30
+ })
31
+ .option('protocols', {
32
+ type: 'array',
33
+ describe: 'List of protocols'
34
+ })
35
+ .option('verbose', {
36
+ alias: 'v',
37
+ type: 'boolean',
38
+ describe: 'Console will receive verbose error output',
39
+ demandOption: false
40
+ })
41
+ }
42
+
43
+ async function handler (argv) {
44
+ try {
45
+ const { srcPorts, dstPorts, ports, processes, portRanges, protocols } = argv
46
+
47
+ const instance = new InstanceCLI(argv)
48
+ await instance.enableMonitoring({
49
+ srcPorts, dstPorts, ports, processes, portRanges, protocols
50
+ })
51
+ log.info('Success')
52
+ } catch (e) {
53
+ if (e.response) {
54
+ log.error(`${e.response.status} ${e.response.data.error || e.response.statusText}`)
55
+ } else {
56
+ if (e.message) {
57
+ log.error(e.message)
58
+ } else {
59
+ log.error('Urecognized error, use --verbose for details')
60
+ }
61
+ }
62
+
63
+ if (argv.verbose) { log.error(JSON.stringify(e)) }
64
+ }
65
+ }
66
+
67
+ module.exports = {
68
+ builder,
69
+ handler,
70
+ command: 'enable <instanceId> [--verbose]',
71
+ describe: 'Enable network monitoring'
72
+ }
@@ -0,0 +1,15 @@
1
+ const subcommands = [
2
+ require('./enable'),
3
+ require('./disable'),
4
+ require('./download'),
5
+ require('./stream')
6
+ ]
7
+
8
+ let _yargs
9
+
10
+ module.exports = {
11
+ builder: yargs => (_yargs = yargs) && yargs.command(subcommands).scriptName(''),
12
+ handler: argv => !argv[1] && _yargs.showHelp(),
13
+ command: 'netmon',
14
+ describe: 'Network Monitoring related commands'
15
+ }
@@ -0,0 +1,48 @@
1
+ const log = require('../../../logging')
2
+ const InstanceCLI = require('../../../clients/Instance')
3
+
4
+ async function builder (yargs) {
5
+ yargs
6
+ .positional('instanceId', {
7
+ type: 'string',
8
+ describe: 'Instance id',
9
+ demandOption: true
10
+ })
11
+ .option('verbose', {
12
+ alias: 'v',
13
+ type: 'boolean',
14
+ describe: 'Console will receive verbose error output',
15
+ demandOption: false
16
+ })
17
+ }
18
+
19
+ async function handler (argv) {
20
+ try {
21
+ const instance = new InstanceCLI(argv)
22
+ const { netdump } = await instance.info()
23
+ if (netdump && netdump.enabled) {
24
+ instance.wss(netdump.info).pipe(process.stdout)
25
+ } else {
26
+ log.error('Network monitoring is not enabled')
27
+ }
28
+ } catch (e) {
29
+ if (e.response) {
30
+ log.error(`${e.response.status} ${e.response.data.error || e.response.statusText}`)
31
+ } else {
32
+ if (e.message) {
33
+ log.error(e.message)
34
+ } else {
35
+ log.error('Urecognized error, use --verbose for details')
36
+ }
37
+ }
38
+
39
+ if (argv.verbose) { log.error(JSON.stringify(e)) }
40
+ }
41
+ }
42
+
43
+ module.exports = {
44
+ builder,
45
+ handler,
46
+ command: 'stream <instanceId> [--verbose]',
47
+ describe: 'Stream monitoring events'
48
+ }
@@ -1,5 +1,5 @@
1
1
  const log = require('../../logging')
2
- const InstanceCLI = require('./Instance')
2
+ const InstanceCLI = require('../../clients/Instance')
3
3
  const { readProfile } = require('../../profile')
4
4
  const { v4: uuidv4 } = require('uuid')
5
5
  const { basename } = require('path')
@@ -1,7 +1,7 @@
1
1
  const { getErrorMessage } = require('../../error')
2
2
  const asciiArt = require('ascii-art')
3
3
 
4
- const Client = require('../Client')
4
+ const Client = require('../../clients/Client')
5
5
  async function builder (yargs) {
6
6
  yargs.positional('instanceId', {
7
7
  type: 'string',
@@ -1,6 +1,7 @@
1
1
  const { getErrorMessage } = require('../../error')
2
2
  const asciiArt = require('ascii-art')
3
- const { getApi, waitForState } = require('../../utils')
3
+ const { waitForState } = require('../../utils')
4
+ const InstanceCLI = require('../../clients/Instance')
4
5
 
5
6
  async function builder (yargs) {
6
7
  yargs.positional('instanceId', {
@@ -17,25 +18,33 @@ async function builder (yargs) {
17
18
  type: 'boolean',
18
19
  describe: 'Wait for the instance to be fully started'
19
20
  })
21
+ .option('paused', {
22
+ type: 'boolean',
23
+ describe: 'Start instance in a paused state'
24
+ })
25
+ .option('sockcap', {
26
+ type: 'boolean',
27
+ describe: 'Start sockcap add-on if loaded'
28
+ })
20
29
  }
21
30
 
22
31
  async function handler (argv) {
23
- const api = await getApi()
24
- const instanceId = argv.instanceId
32
+ const { sockcap, paused } = argv
33
+ const instance = new InstanceCLI(argv)
25
34
  try {
26
- await api.v1StartInstance(instanceId)
35
+ await instance.start({ sockcap, paused })
27
36
  if (argv.wait) {
28
37
  // Wait 60 attempts for the instance to be created (5 minutes)
29
- await waitForState(instanceId, api.v1GetInstance.bind(api), 'on', 60)
38
+ await waitForState(argv.instanceId, instance.info.bind(instance), 'on', 60)
30
39
  }
31
40
  console.log(
32
- asciiArt.style(`Successfully started instance ${instanceId}`, 'green', true)
41
+ asciiArt.style(`Successfully started instance ${argv.instanceId}`, 'green', true)
33
42
  )
34
43
  } catch (error) {
35
44
  const errorMessage = getErrorMessage(error)
36
- console.error(`Failed to start ${instanceId}: ${errorMessage}`)
45
+ console.error(`Failed to start ${argv.instance}: ${errorMessage}`)
37
46
 
38
- if (argv.detailedErrors) {
47
+ if (argv.verbose) {
39
48
  console.log(error)
40
49
  }
41
50
  }
@@ -44,6 +53,6 @@ async function handler (argv) {
44
53
  module.exports = {
45
54
  builder,
46
55
  handler,
47
- command: 'start <instanceId> [wait]',
56
+ command: 'start <instanceId> [--wait] [--verbose] [--paused] [--sockcap]',
48
57
  describe: 'Start instance'
49
58
  }
@@ -1,5 +1,5 @@
1
1
  const log = require('../../logging')
2
- const InstanceCLI = require('./Instance')
2
+ const InstanceCLI = require('../../clients/Instance')
3
3
  const { getErrorMessage } = require('../../error')
4
4
  const { waitForState, waitForIntervalMilliSec } = require('../../utils')
5
5
 
@@ -1,4 +1,4 @@
1
- const TrialAccount = require('../TrialAccount')
1
+ const TrialAccount = require('../clients/TrialAccount')
2
2
  const { makePassword, validateNonEmpty } = require('../utils')
3
3
  const { getErrorMessage } = require('../error')
4
4
  const log = require('../logging')
@@ -7,7 +7,7 @@ async function builder (yargs) {
7
7
  yargs.options({
8
8
  endpoint: {
9
9
  type: 'input',
10
- describe: 'Server endpoint e.g. https://app.ci-4.corellium.co',
10
+ describe: 'Server endpoint e.g. https://app.corellium.co',
11
11
  demandOption: true,
12
12
  string: true,
13
13
  validateNonEmpty
@@ -1,4 +1,4 @@
1
- const Webplayer = require('./Webplayer')
1
+ const Webplayer = require('../../clients/Webplayer')
2
2
  const log = require('../../logging')
3
3
  const { displayTable } = require('../../table')
4
4
  const { validateNonEmpty } = require('../../utils')
@@ -1,4 +1,4 @@
1
- const Webplayer = require('./Webplayer')
1
+ const Webplayer = require('../../clients/Webplayer')
2
2
  const log = require('../../logging')
3
3
  const { validateNonEmpty } = require('../../utils')
4
4
  const { getErrorMessage } = require('../../error')
@@ -1,4 +1,4 @@
1
- const Webplayer = require('./Webplayer')
1
+ const Webplayer = require('../../clients/Webplayer')
2
2
  const log = require('../../logging')
3
3
  const { displayTable } = require('../../table')
4
4
  const { validateNonEmpty } = require('../../utils')
@@ -1,4 +1,4 @@
1
- const Webplayer = require('./Webplayer')
1
+ const Webplayer = require('../../clients/Webplayer')
2
2
  const log = require('../../logging')
3
3
  const { validateNonEmpty } = require('../../utils')
4
4
  const { getErrorMessage } = require('../../error')
@@ -1,45 +0,0 @@
1
- const axios = require('axios').default
2
- const { readProfile } = require('../profile')
3
-
4
- /**
5
- * Client Base class
6
- */
7
- class Client {
8
- constructor (argv) {
9
- this.argv = Object.assign({}, argv)
10
- this.project = argv.project || argv.projectId
11
- this.instance = argv.instance || argv.instanceId
12
- this._profile = readProfile()
13
- }
14
-
15
- /**
16
- * Perform HTTP transaction
17
- * @protected
18
- * @param {string} method
19
- * @param {string} path
20
- * @param {object} data
21
- * @returns {Promise<any>}
22
- */
23
- _fetch = async (method, path, data) => {
24
- const headers = {
25
- Authorization: `Bearer ${this._profile.token}`,
26
- 'User-Agent': `corellium-cli v${require('../../package.json').version}`
27
- }
28
- let response
29
- switch (method) {
30
- case 'GET':
31
- response = await axios.get(`${this._profile.endpoint}/api/${path}`, { headers })
32
- break
33
- case 'POST':
34
- response = await axios.post(`${this._profile.endpoint}/api/${path}`, data, { headers })
35
- break
36
- case 'DELETE':
37
- response = await axios.delete(`${this._profile.endpoint}/api/${path}`, { headers })
38
- break
39
- }
40
- if (response.status >= 400) { throw new Error(response.statusText) }
41
- return response.data || {}
42
- }
43
- }
44
-
45
- module.exports = Client
@@ -1,35 +0,0 @@
1
- const Client = require('../Client')
2
-
3
- /**
4
- * Instance class
5
- */
6
- class InstanceCLI extends Client {
7
- instanceBasePath = () => `v1/instances/${this.instance}`
8
-
9
- /**
10
- * Upgrade iOS version on instance
11
- * @returns {Promise<any>}
12
- */
13
- upgrade = async (options) => {
14
- const { os, osbuild } = options
15
- return await this._fetch('POST', `${this.instanceBasePath()}/upgrade`, { os, osbuild })
16
- }
17
-
18
- /**
19
- * Restore backup to instance
20
- * @returns {Promise<any>}
21
- */
22
- restoreBackup = async () => {
23
- return await this._fetch('POST', `${this.instanceBasePath()}/restoreBackup`)
24
- }
25
-
26
- /**
27
- * Fetch instance information
28
- * @returns {Promise<any>}
29
- */
30
- info = async () => {
31
- return await this._fetch('GET', `${this.instanceBasePath()}`, {})
32
- }
33
- }
34
-
35
- module.exports = InstanceCLI