diff --git a/package-lock.json b/package-lock.json index 3776dab..daf7bbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "frida-cshell", - "version": "1.4.1", + "version": "1.4.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "frida-cshell", - "version": "1.4.1", + "version": "1.4.2", "devDependencies": { "@eslint/js": "^9.10.0", "@types/frida-gum": "^18.7", diff --git a/package.json b/package.json index 53cd52d..be9d960 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "frida-cshell", - "version": "1.4.1", + "version": "1.4.2", "description": "Frida's CShell", "scripts": { "prepare": "npm run build && npm run version && npm run package && npm run copy", diff --git a/src/breakpoints/bp.ts b/src/breakpoints/bp.ts index e082d0e..33f13ac 100644 --- a/src/breakpoints/bp.ts +++ b/src/breakpoints/bp.ts @@ -191,13 +191,16 @@ export class Bp { private startCoverage(threadId: ThreadId, ctx: CpuContext) { Output.clearLine(); Output.writeln(Output.yellow('-'.repeat(80))); - Output.write(`${Output.yellow('|')} Start Trace `); - Output.write(`${Output.green(`#${this._idx}`)} `); - Output.write(`[${this._type}] `); - Output.write(`${Output.yellow(this.literal)} `); - Output.write(`@ $pc=${Output.blue(Format.toHexString(ctx.pc))} `); - Output.write(`$tid=${threadId}, depth=${this._depth}`); - Output.writeln(); + Output.writeln( + [ + `${Output.yellow('|')} Start Trace`, + Output.green(`#${this._idx}`), + `[${this._type}]`, + Output.yellow(this.literal), + `@ $pc=${Output.blue(Format.toHexString(ctx.pc))}`, + `$tid=${threadId}, depth=${this._depth}`, + ].join(' '), + ); Output.writeln(Output.yellow('-'.repeat(80))); } @@ -212,14 +215,16 @@ export class Bp { Output.writeln(Output.blue('-'.repeat(80))); Output.clearLine(); - Output.writeln(Output.yellow('-'.repeat(80))); - Output.write(`${Output.yellow('|')} Stop Trace `); - Output.write(`${Output.green(`#${this._idx}`)} `); - Output.write(`[${this._type}] `); - Output.write(`${Output.yellow(this.literal)} `); - Output.write(`@ $pc=${Output.blue(Format.toHexString(ctx.pc))} `); - Output.write(`$tid=${threadId}`); - Output.writeln(); + Output.writeln( + [ + `${Output.yellow('|')} Stop Trace`, + Output.green(`#${this._idx}`), + `[${this._type}]`, + Output.yellow(this.literal), + `@ $pc=${Output.blue(Format.toHexString(ctx.pc))}`, + `$tid=${threadId}, depth=${this._depth}`, + ].join(' '), + ); Output.writeln(Output.yellow('-'.repeat(80))); Traces.delete(threadId); @@ -239,13 +244,16 @@ export class Bp { else if (this._hits > 0) this._hits--; Output.clearLine(); Output.writeln(Output.yellow('-'.repeat(80))); - Output.write(`${Output.yellow('|')} Break `); - Output.write(`${Output.green(`#${this._idx}`)} `); - Output.write(`[${this._type}] `); - Output.write(`${Output.yellow(this.literal)} `); - Output.write(`@ $pc=${Output.blue(Format.toHexString(ctx.pc))} `); - Output.write(`$tid=${threadId}`); - Output.writeln(); + Output.writeln( + [ + `${Output.yellow('|')} Break`, + Output.green(`#${this._idx}`), + `[${this._type}]`, + Output.yellow(this.literal), + `@ $pc=${Output.blue(Format.toHexString(ctx.pc))}`, + `$tid=${threadId}`, + ].join(' '), + ); Output.writeln(Output.yellow('-'.repeat(80))); Regs.setThreadId(threadId); Regs.setContext(ctx); @@ -275,7 +283,7 @@ export class Bp { } catch (error) { if (error instanceof Error) { Output.writeln(`ERROR: ${error.message}`); - Output.writeln(`${error.stack}`, true); + Output.verboseWriteln(`${error.stack}`); } else { Output.writeln(`ERROR: Unknown error`); } @@ -325,13 +333,16 @@ export class Bp { Output.clearLine(); Output.writeln(Output.yellow('-'.repeat(80))); - Output.write(`${Output.yellow('|')} Break `); - Output.write(`${Output.green(`#${this._idx}`)} `); - Output.write(`[${this._type}] `); - Output.write(`${Output.yellow(this.literal)} `); - Output.write(`@ $pc=${Output.blue(Format.toHexString(details.from))} `); - Output.write(`$addr=${Output.blue(Format.toHexString(details.address))}`); - Output.writeln(); + Output.writeln( + [ + `${Output.yellow('|')} Break`, + Output.green(`#${this._idx}`), + `[${this._type}]`, + Output.yellow(this.literal), + `@ $pc=${Output.blue(Format.toHexString(details.from))}`, + `$addr=${Output.blue(Format.toHexString(details.address))}`, + ].join(' '), + ); Output.writeln(Output.yellow('-'.repeat(80))); Regs.setAddress(details.address); Regs.setPc(details.from); diff --git a/src/cmdlets/assembly.ts b/src/cmdlets/assembly.ts index 4134c28..588688c 100644 --- a/src/cmdlets/assembly.ts +++ b/src/cmdlets/assembly.ts @@ -10,8 +10,7 @@ const USAGE: string = `Usage: l l address - show disassembly listing address the address/symbol to disassemble - bytes the number of instructions to disassemble (default ${DEFAULT_LENGTH}) -`; + bytes the number of instructions to disassemble (default ${DEFAULT_LENGTH})`; export class AssemblyCmdLet extends CmdLet { name = 'l'; @@ -72,6 +71,7 @@ export class AssemblyCmdLet extends CmdLet { Output.writeln( `${Output.bold(idx)}: ${Output.green(Format.toHexString(cursor))}: ${Output.yellow(insn.toString().padEnd(40))} ${Output.blue(bytesStr)}`, + true, ); cursor = cursor.add(insn.size); @@ -125,7 +125,7 @@ export class AssemblyCmdLet extends CmdLet { } public usage(): Var { - Output.write(USAGE); + Output.writeln(USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/bp.ts b/src/cmdlets/bp.ts index 9a5d2ac..b6b282e 100644 --- a/src/cmdlets/bp.ts +++ b/src/cmdlets/bp.ts @@ -76,7 +76,7 @@ abstract class TypedBpCmdLet extends CmdLet implements InputInterceptLine { ); Bps.all() .filter(bp => bp.type === this.bpType) - .forEach(bp => Output.writeln(bp.toString())); + .forEach(bp => Output.writeln(bp.toString(), true)); return Var.ZERO; } else { const bp = Bps.get(this.bpType, index); @@ -90,7 +90,7 @@ abstract class TypedBpCmdLet extends CmdLet implements InputInterceptLine { public usage(): Var { const create = this.usageCreate(); const modify = this.usageModify(); - const INSN_BP_USAGE: string = `Usage: ${this.name} + const USAGE: string = `Usage: ${this.name} ${Output.bold('show:')} ${this.name} - show all ${this.bpType} breakpoints @@ -109,9 +109,9 @@ ${Output.bold('delete:')} ${this.name} ${NUM_CHAR}n # - delete a ${this.bpType} breakpoint ${NUM_CHAR}n the number of the breakpoint to delete -${Output.bold('NOTE:')} Set hits to '*' for unlimited breakpoint. -`; - Output.write(INSN_BP_USAGE); +${Output.bold('NOTE:')} Set hits to '*' for unlimited breakpoint.`; + + Output.writeln(USAGE); return Var.ZERO; } diff --git a/src/cmdlets/bt.ts b/src/cmdlets/bt.ts index 712c6a8..69e10e3 100644 --- a/src/cmdlets/bt.ts +++ b/src/cmdlets/bt.ts @@ -9,8 +9,7 @@ const USAGE: string = `Usage: bt bt - show the backtrace for the current thread in a breakpoint bt name - show backtrace for thread - thread the name of the thread to show backtrace for -`; + thread the name of the thread to show backtrace for`; export class BtCmdLet extends CmdLet { name = 'bt'; @@ -51,17 +50,20 @@ export class BtCmdLet extends CmdLet { .forEach(s => { const prefix = s.moduleName === null ? '' : `${s.moduleName}!`; const name = `${prefix}${s.name}`; - Output.write( - `${Output.green(name.padEnd(40, '.'))} ${Output.yellow(Format.toHexString(s.address))}`, - ); + let fileInfo = ''; if (s.fileName !== null && s.lineNumber !== null) { if (s.fileName.length !== 0 && s.lineNumber !== 0) { - Output.write( - `\t${Output.blue(s.fileName)}:${Output.blue(s.lineNumber.toString())} `, - ); + fileInfo = `\t${Output.blue(s.fileName)}:${Output.blue(s.lineNumber.toString())}`; } } - Output.writeln(); + Output.writeln( + [ + Output.green(name.padEnd(40, '.')), + Output.yellow(Format.toHexString(s.address)), + fileInfo, + ].join(' '), + true, + ); }); } @@ -100,7 +102,7 @@ export class BtCmdLet extends CmdLet { } public usage(): Var { - Output.write(USAGE); + Output.writeln(USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/copy.ts b/src/cmdlets/copy.ts index 1c40b92..e84abf5 100644 --- a/src/cmdlets/copy.ts +++ b/src/cmdlets/copy.ts @@ -10,8 +10,7 @@ const USAGE: string = `Usage: cp cp dest src bytes - copy data dest the address/symbol to write to src the address/symbol to read from - bytes the numer of bytes to read -`; + bytes the numer of bytes to read`; export class CopyCmdLet extends CmdLet { name = 'cp'; @@ -43,7 +42,7 @@ export class CopyCmdLet extends CmdLet { } public usage(): Var { - Output.write(USAGE); + Output.writeln(USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/dump.ts b/src/cmdlets/dump.ts index 8eacdff..c497ff0 100644 --- a/src/cmdlets/dump.ts +++ b/src/cmdlets/dump.ts @@ -5,6 +5,7 @@ import { Token } from '../io/token.js'; import { Var } from '../vars/var.js'; import { Mem } from '../memory/mem.js'; +const ROW_WIDTH: number = 16; const DEFAULT_COUNT: number = 32; const USAGE: string = `Usage: d @@ -16,8 +17,7 @@ d address - show data d address bytes - show data adress the address/symbol to read from count the count of fields to read (default ${DEFAULT_COUNT}) - width the width of each field in the output (1, 2, 4 or 8) -`; + width the width of each field in the output (1, 2, 4 or 8)`; export class DumpCmdLet extends CmdLet { name = 'd'; @@ -45,65 +45,88 @@ export class DumpCmdLet extends CmdLet { const length = count * width; const bytes = Mem.readBytes(address, length); - switch (width) { - case 1: { - const dump = hexdump(bytes.buffer as ArrayBuffer, { - length, - header: true, - ansi: true, - address: address, - }); - const prefixed = dump.replace( - new RegExp('\\n', 'g'), - `\n${Output.green('0x')}`, - ); - Output.writeln(` ${prefixed}`); - break; - } - default: { - const output = Memory.alloc(length); - output.writeByteArray(bytes.buffer as ArrayBuffer); - Output.write(' '.repeat(2 + Process.pointerSize * 2)); - for (let i = 0; i < 16; i++) { - if (i % width !== 0) continue; - const hdr = i.toString(16).toUpperCase(); - const padLen = width * 2 + 1 - hdr.length; - Output.write(` ${hdr.padStart(padLen, ' ')}`); - } - for (let i = 0; i < count; i++) { - const offset = i * width; - if (offset % 16 == 0) { - Output.writeln(); - Output.write( - `${Output.green(Format.toHexString(address.add(offset)))} `, - ); - } + const output = Memory.alloc(length); + output.writeByteArray(bytes.buffer as ArrayBuffer); + const headerPrefix = ' '.repeat(1 + Process.pointerSize * 2); + const headers = [...Array(ROW_WIDTH).keys()] + .map(i => { + if (i % width !== 0) return ''; + const hdr = i.toString(16).toUpperCase(); + const padLen = width * 2 + 1 - hdr.length; + return ` ${hdr.padStart(padLen, ' ')}`; + }) + .join(''); + const byteHeaders = width === 1 ? '0123456789ABCDEF' : ''; + Output.writeln([headerPrefix, headers, byteHeaders].join(' '), true); + + const startAddress = address.and(~(ROW_WIDTH - 1)); + const endAddress = address + .add(length) + .add(ROW_WIDTH - 1) + .and(~(ROW_WIDTH - 1)); + const numChunks = endAddress.sub(startAddress).toUInt32() / ROW_WIDTH; - const cursor = output.add(offset); + const rows = [...Array(numChunks).keys()] + .map(i => { + return startAddress.add(i * ROW_WIDTH); + }) + .map(rowAddress => { + const headerPrefix = `${Output.green(Format.toHexString(rowAddress))}`; + const values = [...Array(ROW_WIDTH / width).keys()].map(i => { + const offset = i * width; + const rowCursor = rowAddress.add(offset); + const limit = address.add(length); + if (rowCursor < address) return ''.padStart(width * 2, ' '); + if (rowCursor >= limit) return ''.padStart(width * 2, ' '); switch (width) { + case 1: { + const val = rowCursor.readU8(); + const str = val.toString(16).padStart(2, '0'); + return `${Output.yellow(str)}`; + } case 2: { - const val = cursor.readU16(); + const val = rowCursor.readU16(); const str = val.toString(16).padStart(4, '0'); - Output.write(`${Output.yellow(str)} `); - break; + return `${Output.yellow(str)}`; } case 4: { - const val = cursor.readU32(); + const val = rowCursor.readU32(); const str = val.toString(16).padStart(8, '0'); - Output.write(`${Output.yellow(str)} `); - break; + return `${Output.yellow(str)}`; } case 8: { - const val = cursor.readU64(); + const val = rowCursor.readU64(); const str = val.toString(16).padStart(16, '0'); - Output.write(`${Output.yellow(str)} `); - break; + return `${Output.yellow(str)}`; + } + default: { + throw new Error(`invalid width: ${width}`); } } + }); + if (width === 1) { + const hexDigits = [...Array(ROW_WIDTH).keys()] + .map(i => { + const rowCursor = rowAddress.add(i); + const limit = address.add(length); + if (rowCursor < address) return ' '; + if (rowCursor >= limit) return ' '; + const val = rowCursor.readU8(); + if (val >= 32 && val <= 126) { + return String.fromCharCode(val); + } else { + return '.'; + } + }) + .join(''); + return `${headerPrefix} ${values.join(' ')} ${Output.yellow(hexDigits)}`; + } else { + return `${headerPrefix} ${values.join(' ')}`; } - Output.writeln(); - } - } + }); + rows.forEach(l => { + Output.writeln(l, true); + }); } catch (error) { throw new Error( `failed to read ${Format.toHexString(count)} bytes from ${Format.toHexString(address)}, ${error}`, @@ -112,7 +135,7 @@ export class DumpCmdLet extends CmdLet { } public usage(): Var { - Output.write(USAGE); + Output.writeln(USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/fd.ts b/src/cmdlets/fd.ts index 592e0fd..3a71849 100644 --- a/src/cmdlets/fd.ts +++ b/src/cmdlets/fd.ts @@ -19,8 +19,7 @@ const USAGE: string = `Usage: fd fd - show all the open file descriptors for the process fd idx - show the given file descriptor - idx the number of file descriptor to show -`; + idx the number of file descriptor to show`; type Fds = { [key: number]: string; @@ -51,7 +50,14 @@ export class FdCmdLet extends CmdLet { if (v0 === null) { const fds = this.readFds(); for (const [fd, path] of Object.entries(fds)) { - Output.writeln(`Fd: ${fd.toString().padStart(3, ' ')}, Path: ${path}`); + Output.writeln( + [ + 'Fd: ', + `${Output.yellow(fd.toString().padStart(3, ' '))},`, + `Path: ${Output.blue(path)}`, + ].join(' '), + true, + ); } return Var.ZERO; } else { @@ -59,7 +65,13 @@ export class FdCmdLet extends CmdLet { const path = this.readFds()[fd]; if (path === undefined) throw new Error(`fd: ${fd} not found`); - Output.writeln(`Fd: ${fd.toString().padStart(3, ' ')}, Path: ${path}`); + Output.writeln( + [ + 'Fd: ', + `${Output.yellow(fd.toString().padStart(3, ' '))},`, + `Path: ${Output.blue(path)}`, + ].join(' '), + ); return new Var(uint64(fd), `Fd: ${fd}`); } @@ -218,7 +230,7 @@ export class FdCmdLet extends CmdLet { } public usage(): Var { - Output.write(USAGE); + Output.writeln(USAGE); return Var.ZERO; } diff --git a/src/cmdlets/grep.ts b/src/cmdlets/grep.ts new file mode 100644 index 0000000..2e9e32d --- /dev/null +++ b/src/cmdlets/grep.ts @@ -0,0 +1,47 @@ +import { CmdLet } from '../commands/cmdlet.js'; +import { Output } from '../io/output.js'; +import { Token } from '../io/token.js'; +import { Var } from '../vars/var.js'; + +const USAGE: string = `Usage: grep + +grep - clear output filter + +grep regex - filter output + regex the regex to use to filter the output`; + +export class GrepCmdLet extends CmdLet { + name = 'grep'; + category = 'misc'; + help = 'filter output'; + + public runSync(tokens: Token[]): Var { + const vars = this.transformOptional(tokens, [], [this.parseLiteral]); + if (vars === null) return this.usage(); + const [_, [filter]] = vars as [[], [string | null]]; + if (filter === null) { + Output.clearFilter(); + Output.writeln('output filter cleared'); + } else { + try { + Output.setFilter(filter); + Output.writeln( + [ + 'output filter set to ', + Output.blue("'"), + Output.green(filter), + Output.blue("'"), + ].join(''), + ); + } catch { + Output.writeln(`invalid regex: ${filter}`); + } + } + return Var.ZERO; + } + + public usage(): Var { + Output.writeln(USAGE); + return Var.ZERO; + } +} diff --git a/src/cmdlets/history.ts b/src/cmdlets/history.ts index 1d6c1d3..ef67684 100644 --- a/src/cmdlets/history.ts +++ b/src/cmdlets/history.ts @@ -9,8 +9,7 @@ const USAGE: string = `Usage: h h - show history h index - rerun history item - index the index of the item to rerun -`; + index the index of the item to rerun`; export class HistoryCmdLet extends CmdLet { name = 'h'; @@ -29,7 +28,10 @@ export class HistoryCmdLet extends CmdLet { if (v0 === null) { const history = Array.from(History.all()); for (const [i, value] of history.entries()) { - Output.writeln(`${i.toString().padStart(3, ' ')}: ${value}`); + Output.writeln( + [`${i.toString().padStart(3, ' ')}:`, value].join(' '), + true, + ); } return Var.ZERO; } else { @@ -40,7 +42,7 @@ export class HistoryCmdLet extends CmdLet { } public usage(): Var { - Output.write(USAGE); + Output.writeln(USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/js.ts b/src/cmdlets/js.ts index e733a53..3b9792b 100644 --- a/src/cmdlets/js.ts +++ b/src/cmdlets/js.ts @@ -55,12 +55,12 @@ import { ThreadCmdLet } from './thread.js'; import { VarCmdLet } from './var.js'; import { VmCmdLet } from './vm.js'; import { WriteCmdLet } from './write.js'; +import { GrepCmdLet } from './grep.js'; const USAGE: string = `Usage: js js path - load JS script - path the absolute path of the script to load (note that paths with spaces must be quoted) -`; + path the absolute path of the script to load (note that paths with spaces must be quoted)`; export class JsCmdLet extends CmdLet { name = 'js'; @@ -97,6 +97,7 @@ export class JsCmdLet extends CmdLet { Format: Format, FunctionEntryBpCmdLet: FunctionEntryBpCmdLet, FunctionExitBpCmdLet: FunctionExitBpCmdLet, + GrepCmdLet: GrepCmdLet, HelpCmdLet: HelpCmdLet, History: History, HistoryCmdLet: HistoryCmdLet, @@ -147,7 +148,7 @@ export class JsCmdLet extends CmdLet { } public usage(): Var { - Output.write(USAGE); + Output.writeln(USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/ld.ts b/src/cmdlets/ld.ts index bc29a53..4a4b5f6 100644 --- a/src/cmdlets/ld.ts +++ b/src/cmdlets/ld.ts @@ -8,8 +8,7 @@ const USAGE: string = `Usage: ld ld - load a module ld path - load a module - path the absolute path of the module to load (note that paths with spaces must be quoted) -`; + path the absolute path of the module to load (note that paths with spaces must be quoted)`; export class LdCmdLet extends CmdLet { name = 'ld'; @@ -33,7 +32,7 @@ export class LdCmdLet extends CmdLet { } public usage(): Var { - Output.write(USAGE); + Output.writeln(USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/math.ts b/src/cmdlets/math.ts index 80c2546..0267eb9 100644 --- a/src/cmdlets/math.ts +++ b/src/cmdlets/math.ts @@ -34,9 +34,8 @@ abstract class BinaryOpCmdLet extends CmdLet { ${this.name} op1 op2 - ${this.OPERATION} two values together op1 the first operand on which to perform the operation - op2 the second operand on which to perform the operation -`; - Output.write(usage); + op2 the second operand on which to perform the operation`; + Output.writeln(usage); return Var.ZERO; } @@ -105,9 +104,8 @@ abstract class UnaryOpCmdLet extends CmdLet { const usage: string = `Usage: ${this.name} ${this.name} op - perform a ${this.OPERATION} operation on an operand - op the operand on which to operate -`; - Output.write(usage); + op the operand on which to operate`; + Output.writeln(usage); return Var.ZERO; } @@ -420,9 +418,8 @@ export class EndianCmdLet extends UnaryOpCmdLet { ${this.name} width val - ${this.OPERATION} of an operand width the width of the operand (1, 2, 4 or 8) - op the operand on which to perform the operation -`; - Output.write(usage); + op the operand on which to perform the operation`; + Output.writeln(usage); return Var.ZERO; } } diff --git a/src/cmdlets/mod.ts b/src/cmdlets/mod.ts index faea3f4..36938f9 100644 --- a/src/cmdlets/mod.ts +++ b/src/cmdlets/mod.ts @@ -13,8 +13,7 @@ mod address - show module for address address the address/symbol to show module information for mod name - show named module - name the name of the module to show information for -`; + name the name of the module to show information for`; export class ModCmdLet extends CmdLet { name = 'mod'; @@ -54,15 +53,17 @@ export class ModCmdLet extends CmdLet { return v0; } - private printModule(m: Module) { + private printModule(m: Module, filtered: boolean = true) { const limit = m.base.add(m.size); - Output.write( - `${Output.green(Format.toHexString(m.base))}-${Output.green(Format.toHexString(limit))} `, + Output.writeln( + [ + `${Output.green(Format.toHexString(m.base))}-${Output.green(Format.toHexString(limit))}`, + Output.bold(Format.toSize(m.size)), + Output.yellow(m.name.padEnd(30, ' ')), + Output.blue(m.path), + ].join(' '), + filtered, ); - Output.write(`${Output.bold(Format.toSize(m.size))} `); - Output.write(`${Output.yellow(m.name.padEnd(30, ' '))} `); - Output.write(`${Output.blue(m.path)}`); - Output.writeln(); } private runShowNamed(tokens: Token[]): Var | null { @@ -86,7 +87,7 @@ export class ModCmdLet extends CmdLet { ); modules.sort(); modules.forEach(m => { - this.printModule(m); + this.printModule(m, true); }); if (modules.length === 1) { const module = modules[0] as Module; @@ -110,7 +111,7 @@ export class ModCmdLet extends CmdLet { } public usage(): Var { - Output.write(USAGE); + Output.writeln(USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/print.ts b/src/cmdlets/print.ts index f5d8c98..42be11e 100644 --- a/src/cmdlets/print.ts +++ b/src/cmdlets/print.ts @@ -7,8 +7,7 @@ const USAGE: string = `Usage: p p - print an expression p exp - print an expression - exp the expression to print -`; + exp the expression to print`; export class PrintCmdLet extends CmdLet { name = 'p'; @@ -30,7 +29,7 @@ export class PrintCmdLet extends CmdLet { } public usage(): Var { - Output.write(USAGE); + Output.writeln(USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/read.ts b/src/cmdlets/read.ts index 6e5a6bd..5ea923f 100644 --- a/src/cmdlets/read.ts +++ b/src/cmdlets/read.ts @@ -66,7 +66,7 @@ r n address - read 'n' bytes from memory n the number of bytes to read (1, 2, 4 or 8). address the address/symbol to read from`; - Output.write(usage); + Output.writeln(usage); return Var.ZERO; } } diff --git a/src/cmdlets/reg.ts b/src/cmdlets/reg.ts index d1420e4..03f7027 100644 --- a/src/cmdlets/reg.ts +++ b/src/cmdlets/reg.ts @@ -13,8 +13,7 @@ R name - display the value of a named register R name value - assign a value to a register name the name of the register to assign - value the value to assign -`; + value the value to assign`; export class RegCmdLet extends CmdLet { name = 'R'; @@ -36,6 +35,7 @@ export class RegCmdLet extends CmdLet { for (const [key, value] of Regs.all()) { Output.writeln( `${Output.bold(key.padEnd(4, ' '))}: ${value.toString()}`, + true, ); } return Vars.getRet(); @@ -63,7 +63,7 @@ export class RegCmdLet extends CmdLet { } public usage(): Var { - Output.write(USAGE); + Output.writeln(USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/sh.ts b/src/cmdlets/sh.ts index 9383113..d3f6b1b 100644 --- a/src/cmdlets/sh.ts +++ b/src/cmdlets/sh.ts @@ -17,8 +17,7 @@ const WNOHANG: number = 1; type Pipe = { readFd: number; writeFd: number }; const USAGE: string = `Usage: fd -sh - run a shell -`; +sh - run a shell`; export class ShCmdLet extends CmdLet { name = 'sh'; @@ -88,7 +87,7 @@ export class ShCmdLet extends CmdLet { throw new Error(`failed to getenv("SHELL"), errno: ${getenvErrno}`); const shellPath = shellVar.readUtf8String(); - Output.writeln(`SHELL: ${shellPath}`, true); + Output.verboseWriteln(`SHELL: ${shellPath}`); if (shellPath === null) throw new Error('failed to read SHELL'); @@ -206,7 +205,7 @@ export class ShCmdLet extends CmdLet { if (this.fnClose === null || this.fnWaitPid === null) throw new Error('failed to find necessary native functions'); - Output.writeln(`child pid: ${childPid}`, true); + Output.verboseWriteln(`child pid: ${childPid}`); const { value: closeChildRet, errno: closeChildErrno } = this.fnClose( toChildReadFd, ) as UnixSystemFunctionResult; @@ -234,7 +233,7 @@ export class ShCmdLet extends CmdLet { Input.setInterceptRaw(onRaw); - Output.writeln(`reading pid: ${childPid}`, true); + Output.verboseWriteln(`reading pid: ${childPid}`); for ( let buf = await input.read(READ_SIZE); @@ -245,7 +244,7 @@ export class ShCmdLet extends CmdLet { Output.write(str); } - Output.writeln(`waiting pid: ${childPid}`, true); + Output.verboseWriteln(`waiting pid: ${childPid}`); const pStatus = Memory.alloc(INT_SIZE); @@ -289,7 +288,7 @@ export class ShCmdLet extends CmdLet { } public usage(): Var { - Output.write(USAGE); + Output.writeln(USAGE); return Var.ZERO; } diff --git a/src/cmdlets/src.ts b/src/cmdlets/src.ts index 1b98ede..4918f7f 100644 --- a/src/cmdlets/src.ts +++ b/src/cmdlets/src.ts @@ -10,8 +10,7 @@ import { Vars } from '../vars/vars.js'; const USAGE: string = `Usage: src src path - load script - path the absolute path of the script to load (note that paths with spaces must be quoted) -`; + path the absolute path of the script to load (note that paths with spaces must be quoted)`; export class SrcCmdLet extends CmdLet { name = 'src'; @@ -58,8 +57,7 @@ export class SrcCmdLet extends CmdLet { if (line.length === 0) continue; if (line.charAt(0) === '#') continue; - Output.write(Output.bold(Input.PROMPT)); - Output.writeln(line); + Output.writeln(`${Output.bold(Input.PROMPT)}${line}`); if (line.trim().length === 0) continue; @@ -76,7 +74,7 @@ export class SrcCmdLet extends CmdLet { } public usage(): Var { - Output.write(USAGE); + Output.writeln(USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/string.ts b/src/cmdlets/string.ts index a620725..f951550 100644 --- a/src/cmdlets/string.ts +++ b/src/cmdlets/string.ts @@ -9,8 +9,7 @@ const MAX_STRING_LENGTH: number = 1024; const USAGE: string = `Usage: ds ds address - show string - adress the address/symbol to show -`; + adress the address/symbol to show`; export class DumpStringCmdLet extends CmdLet { name = 'ds'; @@ -58,22 +57,20 @@ export class DumpStringCmdLet extends CmdLet { `No string found at ${Output.green(name)}: ${Output.yellow(Format.toHexString(address))}`, ); } else { - Output.write(Output.green(name)); - Output.write(' = '); - Output.write(Output.blue("'")); - Output.write(Output.yellow(value)); - Output.write(Output.blue("'")); - Output.write(', length: '); - Output.write(Output.blue(value.length.toString())); - Output.write(' ('); - Output.write(Output.blue(`0x${value.length.toString(16)}`)); - Output.write(')'); - Output.writeln(); + Output.writeln( + [ + Output.green(name), + '=', + `${Output.blue("'")}${Output.yellow(value)}${Output.blue("'")},`, + `length: ${Output.blue(value.length.toString())},`, + `(${Output.blue(`0x${value.length.toString(16)}`)})`, + ].join(' '), + ); } } public usage(): Var { - Output.write(USAGE); + Output.writeln(USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/sym.ts b/src/cmdlets/sym.ts index 4a94410..6824f13 100644 --- a/src/cmdlets/sym.ts +++ b/src/cmdlets/sym.ts @@ -15,8 +15,7 @@ sym name - display address information for a named symbol name the name of the symbol to lookup sym addr - display symbol information associated with an address - addr the address of the symbol to lookup -`; + addr the address of the symbol to lookup`; export class SymCmdLet extends CmdLet { name = 'sym'; @@ -148,7 +147,7 @@ export class SymCmdLet extends CmdLet { Output.yellow(Format.toHexString(value.address)), `[${Output.blue(value.type)}]`, ].join(' '), - ).forEach(s => Output.writeln(s)); + ).forEach(s => Output.writeln(s, true)); const values = Array.from(dict.values()); if (values.length === 1) { @@ -185,7 +184,7 @@ export class SymCmdLet extends CmdLet { } public usage(): Var { - Output.write(USAGE); + Output.writeln(USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/thread.ts b/src/cmdlets/thread.ts index 96f808d..5d6c45a 100644 --- a/src/cmdlets/thread.ts +++ b/src/cmdlets/thread.ts @@ -9,8 +9,7 @@ const USAGE: string = `Usage: t t - show all threads t name - show named thread - name the name of the thread to show information for -`; + name the name of the thread to show information for`; export class ThreadCmdLet extends CmdLet { name = 't'; @@ -49,7 +48,7 @@ export class ThreadCmdLet extends CmdLet { } } - private printThread(t: ThreadDetails) { + private printThread(t: ThreadDetails, filtered: boolean = true) { Output.writeln( [ `${Output.yellow(t.id.toString().padStart(5, ' '))}:`, @@ -58,6 +57,7 @@ export class ThreadCmdLet extends CmdLet { `pc: ${Output.yellow(Format.toHexString(t.context.pc))}`, `sp: ${Output.yellow(Format.toHexString(t.context.sp))}`, ].join(' '), + filtered, ); } @@ -78,7 +78,7 @@ export class ThreadCmdLet extends CmdLet { } default: matches.forEach(t => { - this.printThread(t); + this.printThread(t, true); }); return Var.ZERO; } @@ -99,14 +99,14 @@ export class ThreadCmdLet extends CmdLet { } default: threads.forEach(t => { - this.printThread(t); + this.printThread(t, true); }); return Var.ZERO; } } public usage(): Var { - Output.write(USAGE); + Output.writeln(USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/var.ts b/src/cmdlets/var.ts index f477a90..e3c6e72 100644 --- a/src/cmdlets/var.ts +++ b/src/cmdlets/var.ts @@ -17,8 +17,7 @@ v name value - assign a value to a variable value the value to assign v name ${DELETE_CHAR} - delete a variable - name the name of the variable to delete -`; + name the name of the variable to delete`; export class VarCmdLet extends CmdLet { name = 'v'; @@ -73,6 +72,7 @@ export class VarCmdLet extends CmdLet { `${Output.green(key.padEnd(25, ' '))}:`, `${Output.yellow(value.toString())}`, ].join(' '), + true, ); } return Vars.getRet(); @@ -94,7 +94,7 @@ export class VarCmdLet extends CmdLet { } public usage(): Var { - Output.write(USAGE); + Output.writeln(USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/verbose.ts b/src/cmdlets/verbose.ts new file mode 100644 index 0000000..1ad6110 --- /dev/null +++ b/src/cmdlets/verbose.ts @@ -0,0 +1,30 @@ +import { CmdLet } from '../commands/cmdlet.js'; +import { Output } from '../io/output.js'; +import { Token } from '../io/token.js'; +import { Var } from '../vars/var.js'; + +const USAGE: string = `Usage: verbose +verbose - toggle verbose mode`; + +export class VerboseCmdLet extends CmdLet { + name = 'verbose'; + category = 'misc'; + help = 'toggle verbose mode'; + + public runSync(_tokens: Token[]): Var { + const verbose = !Output.getVerbose(); + if (verbose) { + Output.writeln(`verbose mode ${Output.green('enabled')}`); + } else { + Output.writeln(`verbose mode ${Output.red('disabled')}`); + } + Output.setVerbose(verbose); + + return Var.ZERO; + } + + public usage(): Var { + Output.writeln(USAGE); + return Var.ZERO; + } +} diff --git a/src/cmdlets/vm.ts b/src/cmdlets/vm.ts index f0d98c6..9e9bc8c 100644 --- a/src/cmdlets/vm.ts +++ b/src/cmdlets/vm.ts @@ -13,8 +13,7 @@ vm address - show mapping for address address the address/symbol to show mapping information for vm module - show mappings for a module - module the name of the module to show mapping information for -`; + module the name of the module to show mapping information for`; export class VmCmdLet extends CmdLet { name = 'vm'; @@ -76,20 +75,24 @@ export class VmCmdLet extends CmdLet { return v0; } - private printMapping(r: RangeDetails) { + private printMapping(r: RangeDetails, filter: boolean = true) { const limit = r.base.add(r.size); - Output.write( - `\t${Output.green(Format.toHexString(r.base))}-${Output.green(Format.toHexString(limit))} `, - ); - Output.write(`${Output.bold(Output.yellow(r.protection))} `); - Output.write(`${Output.bold(Format.toSize(r.size))} `); + let fileInfo = ''; if (r.file !== undefined) { - Output.write( - `offset: ${Output.bold(Format.toHexString(r.file.offset))}, `, - ); - Output.write(`name: ${Output.blue(r.file.path)}`); + fileInfo = [ + `offset: ${Output.yellow(Format.toHexString(r.file.offset))},`, + `name: ${Output.blue(r.file.path)}`, + ].join(' '); } - Output.writeln(); + Output.writeln( + [ + `\t${Output.green(Format.toHexString(r.base))}-${Output.green(Format.toHexString(limit))}`, + `${Output.bold(Output.yellow(r.protection))}`, + `${Output.bold(Format.toSize(r.size))}`, + fileInfo, + ].join(' '), + filter, + ); } private runShowNamed(tokens: Token[]): Var | null { @@ -99,7 +102,7 @@ export class VmCmdLet extends CmdLet { if (name === null) { Process.enumerateRanges('---').forEach(r => { - this.printMapping(r); + this.printMapping(r, true); }); return Var.ZERO; } else if (Regex.isGlob(name)) { @@ -112,7 +115,7 @@ export class VmCmdLet extends CmdLet { modules.sort(); modules.forEach(m => { m.enumerateRanges('---').forEach(r => { - this.printMapping(r); + this.printMapping(r, true); }); }); if (modules.length === 1) { @@ -132,14 +135,14 @@ export class VmCmdLet extends CmdLet { } mod.enumerateRanges('---').forEach(r => { - this.printMapping(r); + this.printMapping(r, true); }); return new Var(uint64(mod.base.toString()), `Module: ${mod.name}`); } } public usage(): Var { - Output.write(USAGE); + Output.writeln(USAGE); return Var.ZERO; } } diff --git a/src/cmdlets/write.ts b/src/cmdlets/write.ts index f0fb54e..c9094ff 100644 --- a/src/cmdlets/write.ts +++ b/src/cmdlets/write.ts @@ -107,9 +107,8 @@ export class WriteCmdLet extends CmdLet { w n address value - write 'n' bytes to memory n the number of bytes to read (1, 2, 4 or 8). address the address/symbol to write to - value the value to write - `; - Output.write(usage); + value the value to write`; + Output.writeln(usage); return Var.ZERO; } } diff --git a/src/commands/cmdlets.ts b/src/commands/cmdlets.ts index 86850b6..0211ea4 100644 --- a/src/commands/cmdlets.ts +++ b/src/commands/cmdlets.ts @@ -45,6 +45,8 @@ import { JsCmdLet } from '../cmdlets/js.js'; import { PrintCmdLet } from '../cmdlets/print.js'; import { ShCmdLet } from '../cmdlets/sh.js'; import { SrcCmdLet } from '../cmdlets/src.js'; +import { VerboseCmdLet } from '../cmdlets/verbose.js'; +import { GrepCmdLet } from '../cmdlets/grep.js'; export class CmdLets { private static byName: Map = new Map(); @@ -65,6 +67,7 @@ export class CmdLets { this.registerCmdletType(EndianCmdLet); this.registerCmdletType(ExitCmdLet); this.registerCmdletType(FdCmdLet); + this.registerCmdletType(GrepCmdLet); this.registerCmdletType(FunctionEntryBpCmdLet); this.registerCmdletType(FunctionExitBpCmdLet); this.registerCmdletType(HelpCmdLet); @@ -88,6 +91,7 @@ export class CmdLets { this.registerCmdletType(ThreadCmdLet); this.registerCmdletType(UniqueBlockTraceBpCmdLet); this.registerCmdletType(VarCmdLet); + this.registerCmdletType(VerboseCmdLet); this.registerCmdletType(VmCmdLet); this.registerCmdletType(WriteCmdLet); this.registerCmdletType(WriteBpCmdLet); diff --git a/src/commands/command.ts b/src/commands/command.ts index 5a8d8e8..b04c5a8 100644 --- a/src/commands/command.ts +++ b/src/commands/command.ts @@ -57,9 +57,12 @@ export class Command { } args.forEach((param, index) => { - Output.writeln( - `\t${index}: ${Format.toHexString(param)} ${Format.toDecString(param)}`, - true, + Output.verboseWriteln( + [ + `\t${index}:`, + Format.toHexString(param), + Format.toDecString(param), + ].join(' '), ); }); diff --git a/src/entrypoint.ts b/src/entrypoint.ts index d037434..019abe6 100644 --- a/src/entrypoint.ts +++ b/src/entrypoint.ts @@ -25,7 +25,7 @@ rpc.exports = { init(stage: string, params: InitParams | null = null) { const verbose = params?.verbose ?? false; Output.setVerbose(verbose); - Output.writeln(`init - stage: ${stage}, verbose: ${verbose}`, true); + Output.verboseWriteln(`init - stage: ${stage}, verbose: ${verbose}`); Output.banner(); Process.setExceptionHandler(exceptionHandler); SrcCmdLet.loadInitScript(DEFAULT_SRC_PATH); diff --git a/src/io/input.ts b/src/io/input.ts index fe738bd..726d757 100644 --- a/src/io/input.ts +++ b/src/io/input.ts @@ -15,6 +15,7 @@ const CLEAR_CHAR: string = 'c'; export class Input { public static readonly PROMPT: string = '-> '; + public static readonly FILTERED_PROMPT: string = '~> '; private static readonly EDIT_PROMPT: string = '. '; private static buffer: string = ''; @@ -108,7 +109,7 @@ export class Input { } catch (error) { if (error instanceof Error) { Output.writeln(`ERROR: ${error.message}`); - Output.writeln(`${error.stack}`, true); + Output.verboseWriteln(`${error.stack}`); } else { Output.writeln(`ERROR: Unknown error`); } @@ -120,7 +121,11 @@ export class Input { public static prompt() { Output.clearLine(); if (this.interceptLine === null) { - Output.write(Output.bold(this.PROMPT)); + if (Output.isFiltered()) { + Output.write(Output.bold(this.FILTERED_PROMPT)); + } else { + Output.write(Output.bold(this.PROMPT)); + } } else { Output.write(Output.bold(this.EDIT_PROMPT)); } diff --git a/src/io/output.ts b/src/io/output.ts index e0e688e..ec4f3fe 100644 --- a/src/io/output.ts +++ b/src/io/output.ts @@ -25,6 +25,7 @@ export class Output { private static verbose: boolean = false; private static indent: boolean = false; + private static filter: RegExp | null = null; public static banner() { this.shell @@ -53,30 +54,50 @@ export class Output { this.writeln(`\tName: ${this.green(first.name)}`); } - public static writeln( - buffer: string | null = null, - verbose: boolean = false, - ) { - this.write(`${buffer ?? ''}\n`, verbose); + public static verboseWriteln(buffer: string | null) { + this.dowrite(`${buffer ?? ''}\n`, true, false); } - public static write(buffer: string | null = null, verbose: boolean = false) { + public static writeln(buffer: string | null = null, filter: boolean = false) { + this.dowrite(`${buffer ?? ''}\n`, false, filter); + } + + public static write(buffer: string | null = null, filter: boolean = false) { + this.dowrite(buffer, false, filter); + } + + private static dowrite( + buffer: string | null = null, + verbose: boolean, + filter: boolean, + ) { if (verbose && !this.verbose) return; if (buffer === null) return; + const filterExpression = (l: string) => + filter === false || + l.length === 0 || + Output.filter === null || + Output.filter.test(l); + if (this.indent) { - const trimmed = buffer.endsWith('\n') - ? buffer.slice(0, buffer.length - 1) - : buffer; - const fixed = trimmed.replace( - new RegExp('\\n', 'g'), - `\r\n${Output.yellow('| ')}`, - ); - const output = buffer.endsWith('\n') ? `${fixed}\r\n` : fixed; - send(['frida:stderr', `${Output.yellow('| ')}${output}`]); + if (buffer.endsWith('\n')) { + const lines = buffer.slice(0, buffer.length - 1).split('\n'); + const fixed = lines + .filter(filterExpression) + .join(`\r\n${Output.yellow('| ')}`); + send(['frida:stderr', `${Output.yellow('| ')}${fixed}\r\n`]); + } else { + const lines = buffer.split('\n'); + const fixed = lines + .filter(filterExpression) + .join(`\r\n${Output.yellow('| ')}`); + send(['frida:stderr', `${Output.yellow('| ')}${fixed}`]); + } } else { - const fixed = buffer.replace(new RegExp('\\n', 'g'), '\r\n'); + const lines = buffer.split('\n'); + const fixed = lines.filter(filterExpression).join(`\r\n`); send(['frida:stderr', fixed]); } } @@ -91,6 +112,10 @@ export class Output { Output.writeln(`ret: ${Output.bold(Vars.getRet().toString())}`); } + public static getVerbose(): boolean { + return this.verbose; + } + public static setVerbose(verbose: boolean) { this.verbose = verbose; } @@ -118,4 +143,16 @@ export class Output { public static red(input: string): string { return `${CharCode.RED}${input}${CharCode.RESET}`; } + + public static setFilter(filter: string) { + this.filter = new RegExp(filter); + } + + public static clearFilter() { + this.filter = null; + } + + public static isFiltered() { + return this.filter !== null; + } } diff --git a/src/traces/block.ts b/src/traces/block.ts index c839f70..e59f6a7 100644 --- a/src/traces/block.ts +++ b/src/traces/block.ts @@ -69,11 +69,8 @@ export class BlockTrace implements Trace { } numOutput += 1; const idx = `${numOutput.toString().padStart(4, ' ')}. `; - Output.write(Output.bold(idx)); - if (currentDepth > 0) { - Output.write('\t'.repeat(currentDepth)); - } - Output.writeln(name); + const depth = currentDepth > 0 ? '\t'.repeat(currentDepth) : ''; + Output.writeln(`${depth}${Output.bold(idx)}${name}`); break; } case 4: { diff --git a/src/traces/call.ts b/src/traces/call.ts index 624819a..a99055b 100644 --- a/src/traces/call.ts +++ b/src/traces/call.ts @@ -54,9 +54,10 @@ export class CallTrace implements Trace { if (toName === null) continue; if (first) { + const idx = `${numOutput.toString().padStart(4, ' ')}. `; const fromName = Traces.getAddressString(from as NativePointer); if (fromName === null) continue; - Output.writeln(fromName); + Output.writeln(`${Output.bold(idx)}${fromName}`); currentDepth = 1; first = false; } @@ -66,11 +67,8 @@ export class CallTrace implements Trace { } numOutput += 1; const idx = `${numOutput.toString().padStart(4, ' ')}. `; - Output.write(Output.bold(idx)); - if (currentDepth > 0) { - Output.write('\t'.repeat(currentDepth)); - } - Output.writeln(toName); + const depth = currentDepth > 0 ? '\t'.repeat(currentDepth) : ''; + Output.writeln(`${depth}${Output.bold(idx)}${toName}`); } else if (kind === 'ret') { if (currentDepth > 0) { currentDepth -= 1;