bagbak
Version:
Dump iOS app from a jailbroken device, based on frida.re
152 lines (118 loc) • 4.26 kB
JavaScript
import chalk from 'chalk';
import { Command } from 'commander';
import { DeviceManager, getDevice, getRemoteDevice, getUsbDevice, Device, Scope } from 'frida';
import { BagBak } from '../index.js';
import { enableDebug, version } from '../lib/utils.js';
/**
*
* @param {Command} cmd
* @returns {Promise<Device>} device
*/
function getDeviceFromOptions(cmd) {
let count = 0;
if (cmd.device) count++;
if (cmd.usb) count++;
if (cmd.remote) count++;
if (cmd.host) count++;
if (count === 0 || cmd.usb) {
return getUsbDevice();
}
if (count > 1)
throw new Error('Only one of --device, --usb, --remote, --host can be specified');
if (cmd.device) {
return getDevice(cmd.device);
} else if (cmd.remote) {
return getRemoteDevice();
} else if (cmd.host) {
const manager = new DeviceManager();
return manager.addRemoteDevice(cmd.host);
}
}
async function main() {
const program = new Command();
program
.name('bagbak')
.option('-l, --list', 'list apps')
.option('-j, --json', 'output as json (only works with --list)')
.option('-U, --usb', 'connect to USB device (default)')
.option('-R, --remote', 'connect to remote frida-server')
.option('-D, --device <uuid>', 'connect to device with the given ID')
.option('-H, --host <host>', 'connect to remote frida-server on HOST')
.option('-f, --force', 'override existing files')
.option('-d, --debug', 'enable debug output')
.option('-r, --raw', 'dump raw app bundle to directory (no ipa)')
.option('-o, --output <output>', 'ipa filename or directory to dump to')
.usage('[bundle id or name]')
.version(await version());
program.parse(process.argv);
if (program.debug)
enableDebug(true);
const device = await getDeviceFromOptions(program);
const info = await device.querySystemParameters();
if (info.access !== 'full' || info.os.id !== 'ios' || info.platform !== 'darwin' || info.arch !== 'arm64') {
console.error('This tool requires a jailbroken 64bit iOS device');
process.exit(1);
}
if (program.list) {
const apps = await device.enumerateApplications({ scope: Scope.Metadata });
if (program.json) {
console.log(JSON.stringify(apps, null, 2));
return;
}
const verWidth = Math.max(...apps.map(app => app.parameters?.version?.length || 0));
const idWidth = Math.max(...apps.map(app => app.identifier.length));
console.log(
chalk.gray('Version'.padStart(verWidth)),
chalk.gray('Identifier'.padEnd(idWidth)),
chalk.gray('Name'),
);
console.log(chalk.gray('─'.repeat(10 + verWidth + idWidth)));
for (const app of apps) {
console.log(
chalk.yellowBright((app.parameters?.version || '').padStart(verWidth)),
chalk.greenBright(app.identifier.padEnd(idWidth)),
app.name
);
}
return;
}
if (program.args.length === 1) {
const target = program.args[0];
const apps = await device.enumerateApplications({ scope: Scope.Metadata });
const app = apps.find(app => app.name === target || app.identifier === target);
if (!app)
throw new Error(`Unable to find app ${target}`);
const job = new BagBak(device, app);
let files = 0;
let folders = 0;
job
.on('mkdir', (remote) => {
folders++;
})
.on('download', (remote, size) => {
})
.on('progress', (remote, downloaded, size) => {
})
.on('done', (remote) => {
process.stdout.write(`\r${chalk.greenBright('[info]')} downloaded ${files++} files and ${folders} folders`);
})
.on('sshBegin', () => {
console.log(chalk.greenBright('[info]'), 'pulling app bundle from device, please be patient');
})
.on('sshFinish', () => {
process.stdout.write('\n');
console.log(chalk.greenBright('[info]'), 'app bundle downloaded');
})
.on('patch', (remote) => {
console.log(chalk.redBright('[decrypt]'), remote);
})
const saved = program.raw ?
await job.dump(program.output || '.', program.force) :
await job.pack(program.output);
console.log(`Saved to ${chalk.yellow(saved)}`);
return;
}
program.help();
}
main();