tweaselORG / cyanoacrylate

Toolkit for large-scale automated traffic analysis of mobile apps on Android and iOS.
MIT License
5 stars 1 forks source link

Create a CLI for cyanoacrylate #15

Closed baltpeter closed 1 year ago

baltpeter commented 1 year ago

There are definitely common use cases for CA that shouldn't require you to write a custom JS script, e.g. capturing an app's traffic for a few seconds without interaction.

For those, it would be much nicer to have a CLI that you can just call. We have decided to make that a part of CA directly, instead of making it a separate package.

baltpeter commented 1 year ago

For now, I'll only implement a "record traffic" command that you give an app and a duration, and it will then run the app for that duration and spit out a HAR file of the recorded traffic at the end.

baltpeter commented 1 year ago

Which CLI library do we want to use? For thesis-mobile-consent-dialogs and backghup, I've used yargs because of its simplicity but I think it might not have all the features we'll need here. Also, I've encountered a few annoying rough edges (e.g. the help command output is not wrapper if you're using ESM).
For the dattel CLI, I used oclif.

I'll do a bit of research into what other options there are.

baltpeter commented 1 year ago

These are the options I considered (mostly compiled from https://openbase.com/categories/js/best-nodejs-cli-libraries?vs=commander%2Cyargs%2Cprompts):

baltpeter commented 1 year ago

I started setting up oclif but already ran into two problems before I even really started:

baltpeter commented 1 year ago

I'm tempted to just try commander instead. Losing the nice oclif features would be sad (e.g. commander has no plans to support shell autocomplete), but I'm not sure whether it's worth sinking too much more time into this. I guess, worst case, we can always migrate to oclif later if we do decide we need those features.

baltpeter commented 1 year ago

Well, unfortunately commander really is pretty limited. :/

baltpeter commented 1 year ago

Looks like oclif doesn't have these limitations: https://oclif.io/docs/flags

baltpeter commented 1 year ago
  • We have currently configured Parcel to only build the index.ts but oclif expects a folder in dist with a file for each command.

Why does everything have to be complicated? Parcel supports globs on the command line, but not in package.json.

baltpeter commented 1 year ago

Trying to get the oclif hello world example to run, I was struggling with this error:

❯ yarn oclif manifest
yarn run v1.22.19
$ /home/benni/coding/JS/tweasel/cyanoacrylate/node_modules/.bin/oclif manifest
 ›   Error Plugin: cyanoacrylate: command hello not found

Now, I've found the problem. Turns out, Parcel completely strips the export default for some reason. o.O If I manually add that to the built files, it works.

baltpeter commented 1 year ago

It doesn't strip the export default if I add "isLibrary": true… That just cost me an hour. -.-

baltpeter commented 1 year ago

For the record (maybe this helps someone else), here's the oclif and Parcel configuration in package.json that seems to work so far:

{
    "oclif": {
        "bin": "cyanoacrylate",
        "dirname": "cyanoacrylate",
        "commands": "./dist/cli/commands",
        "plugins": [
            "@oclif/plugin-help",
            "@oclif/plugin-autocomplete"
        ]
    },
    "targets": {
        "library": {
            "distDir": "./dist",
            "source": "./src/index.ts",
            "context": "node",
            "isLibrary": true,
            "outputFormat": "esmodule",
            "optimize": false
        },
        "types": {
            "source": "./src/index.ts",
            "distDir": "./dist"
        },
        "cli": {
            "distDir": "./dist/cli",
            "source": [
                "./src/cli/index.ts",
                "./src/cli/commands/hello.ts"
            ],
            "context": "node",
            "isLibrary": true,
            "outputFormat": "esmodule",
            "optimize": false
        }
    }
}

Downside: You have to manually add each command to targets.cli.source.

Update: That wasn't quite it.

baltpeter commented 1 year ago

Their boilerplate generates a ./bin/run file that acts as the bin script in the package.json. That is however incompatible with type: module (which we are using).

I just noticed that there is official documentation on how to solve that, after all: https://oclif.io/docs/esm

baltpeter commented 1 year ago

Next problem: import { startAnalysis } from '../../index'; doesn't work. Parcel just doesn't include the code from that import in the bundle.

It replaces it with var $6NsKs = parcelRequire("6NsKs");, but that fails when running:

 ›   ModuleLoadError Plugin: cyanoacrylate: [MODULE_NOT_FOUND] import() failed to load 
 ›   /home/benni/coding/JS/tweasel/cyanoacrylate/dist/cli/commands/record-traffic.js: Cannot find module '6NsKs'
 ›   Code: MODULE_NOT_FOUND

For parcelRequire() to work, the module would have to be included in $parcel$modules but it isn't:

var $parcel$modules = {};
var $parcel$inits = {};

var parcelRequire = $parcel$global["parcelRequire2a89"];
if (parcelRequire == null) {
  parcelRequire = function(id) {
    if (id in $parcel$modules) {
      return $parcel$modules[id].exports;
    }
    if (id in $parcel$inits) {
      var init = $parcel$inits[id];
      delete $parcel$inits[id];
      var module = {id: id, exports: {}};
      $parcel$modules[id] = module;
      init.call(module.exports, module, module.exports);
      return module.exports;
    }
    var err = new Error("Cannot find module '" + id + "'");
    err.code = 'MODULE_NOT_FOUND';
    throw err;
  };

  parcelRequire.register = function register(id, init) {
    $parcel$inits[id] = init;
  };

  $parcel$global["parcelRequire2a89"] = parcelRequire;
}
baltpeter commented 1 year ago

This appears to be because Parcel thinks it has already built the main library. If I remove the library target, the bundle is produced correctly. But I do want both, of course. :D

The targets documentation doesn't have any info on how to solve that problem.

baltpeter commented 1 year ago

I think I finally have the magic combination of properties to get this to work (:|). You need to combine the library and cli targets, but still have a types target:

{
    "targets": {
        "types": {
            "source": "./src/index.ts",
            "distDir": "./dist"
        },
        "everything": {
            "distDir": "./dist",
            "source": [
                "./src/index.ts",
                "./src/cli/index.ts",
                "./src/cli/commands/record-traffic.ts"
            ],
            "context": "node",
            "isLibrary": true,
            "outputFormat": "esmodule",
            "optimize": false
        }
    }
}

Oh, and you can't name that combined target main. That will fail with Error: Bundles must have unique names.

baltpeter commented 1 year ago

Oh no, turns out including the entire library code in the CLI bundle doesn't work either. That breaks the path for the mitmproxy addons:

{
    spawnargs: [
      'mitmdump',
      '--mode',
      'wireguard',
      '-s',
      '/home/benni/coding/JS/tweasel/cyanoacrylate/dist/cli/mitmproxy-addons/ipcEventsAddon.py',
      '-s',
      '/home/benni/coding/JS/tweasel/cyanoacrylate/dist/cli/mitmproxy-addons/har_dump.py',
      '--set',
      'hardump=/tmp/f9144eb72e9ba6da19efa1530a12a672.har',
      '--set',
      'ipcPipeFd=3'
    ],

}
baltpeter commented 1 year ago

I've given up on including the CLI in here. Instead, I have created a new tweasel-cli package.

PR: https://github.com/tweaselORG/cli/pull/1