From 287235796bd6425778af4f560c201912b10e2658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergi=20=C3=80lvarez=20i=20Capilla?= Date: Mon, 1 Jul 2019 16:07:06 +0200 Subject: [PATCH] Fix #143 - Initial implementation of \iz and \izj --- src/agent/index.js | 52 +++++++++++++++++++++++++--- src/agent/strings.js | 82 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 src/agent/strings.js diff --git a/src/agent/index.js b/src/agent/index.js index 9700ccdf..5c2a79de 100644 --- a/src/agent/index.js +++ b/src/agent/index.js @@ -9,6 +9,7 @@ const path = require('path'); const config = require('./config'); const io = require('./io'); const isObjC = require('./isobjc'); +const strings = require('./strings'); let Gcwd = '/'; @@ -184,6 +185,8 @@ const commandHandlers = { 'icj': listClassesJson, 'ip': listProtocols, 'ipj': listProtocolsJson, + 'iz': listStrings, + 'izj': listStringsJson, 'dd': listFileDescriptors, 'ddj': listFileDescriptorsJson, 'dd-': closeFileDescriptors, @@ -835,7 +838,7 @@ async function dumpInfoJson () { const ActivityThread = Java.use('android.app.ActivityThread'); const app = ActivityThread.currentApplication(); if (app !== null) { - const ctx = app.getApplicationContext() + const ctx = app.getApplicationContext(); if (ctx !== null) { res.dataDir = ctx.getDataDir().getAbsolutePath(); res.codeCacheDir = ctx.getCodeCacheDir().getAbsolutePath(); @@ -1523,6 +1526,45 @@ function listFileDescriptorsJson (args) { } } +function listStringsJson (args) { + if (!args || args.length !== 1) { + args = [ offset ]; + } + const base = ptr(args[0]); + const currentRange = Process.findRangeByAddress(base); + if (currentRange) { + const options = { base: base }; // filter for urls? + const length = Math.min(currentRange.size, 1024*1024*128); + const block = 1024*1024; // 512KB + if (length !== currentRange.size) { + const curSize = currentRange.size / (1024 * 1024); + console.error('Warning: this range is too big ('+curSize+'MB), truncated to ' + length / (1024*1024) + 'MB'); + } + try { + let res = []; + console.log('Reading ' + (length/(1024*1024)) + 'MB ...'); + for (let i = 0; i < length; i += block) { + const addr = currentRange.base.add(i); + const bytes = addr.readCString(block); + const blockResults = strings(bytes.split('').map(_ => _.charCodeAt(0)), options); + res.push(...blockResults); + } + return res; + } catch (e) { + console.log(e.message); + } + } + throw new Error('Memory not mapped here'); +} + +function listStrings (args) { + if (!args || args.length !== 1) { + args = [ ptr(offset) ]; + } + const base = ptr(args[0]); + return listStringsJson(args).map(({ base, text }) => padPointer(base) + ` "${text}"`).join('\n'); +} + function listProtocolsJson (args) { if (args.length === 0) { return Object.keys(ObjC.protocols); @@ -1581,9 +1623,9 @@ function listMemoryRangesHere (args) { if (args.length !== 1) { args = [ ptr(offset) ]; } - const addr = +args[0]; + const addr = ptr(args[0]); return listMemoryRangesJson() - .filter(({ base, size }) => (addr >= +base && addr < (+base + size))) + .filter(({ base, size }) => (addr.compare(base) >= 0 && addr.compare(base.add(size)) < 0)) .map(({ base, size, protection, file }) => [ padPointer(base), @@ -1992,8 +2034,8 @@ function getEnv () { const result = []; let envp = Memory.readPointer(Module.findExportByName(null, 'environ')); let env; - while (!envp.isNull() && !(env = Memory.readPointer(envp)).isNull()) { - result.push(Memory.readCString(env)); + while (!envp.isNull() && !(env = envp.readPointer()).isNull()) { + result.push(env.readCString()); envp = envp.add(Process.pointerSize); } return result; diff --git a/src/agent/strings.js b/src/agent/strings.js new file mode 100644 index 00000000..0d60a60d --- /dev/null +++ b/src/agent/strings.js @@ -0,0 +1,82 @@ +function isPrintable (ch) { + return (ch >= 32 && ch <= 126); +} + +function parseOptions (options) { + const opts = { + minLength: 15, + maxLength: 128, + filter: false, + urls: false, + base: 0 + }; + if (typeof options === 'object') { + for (let key of Object.keys(options)) { + opts[key] = options[key]; + } + } + return opts; +} + +function parseStrings (data, options) { + const opt = parseOptions(options); + const strs = []; + let str = ''; + let off = 0; + let cur = 0; + data.forEach(ch => { + if (isPrintable(ch)) { + if (str === '') { + cur = off; + } + str += String.fromCharCode(ch); + } else { + if (str.length > opt.minLength && str.length < opt.maxLength) { + let valid = true; + if (opt.filter && !isValidString(str)) { + valid = false; + } + if (opt.urls && !isValidURL(str)) { + valid = false; + } + if (valid) { + strs.push({ base: opt.base.add(cur), text: str }); + } + } + str = ''; + } + off++; + }); + return strs; +} + +function isValidString (s) { + if (s.indexOf('://') !== -1) { + return false; + } + if (+s) { + return false; + } + const invalidChars = '<\\)?@)>{~}^()=/!-"*:]%\';` $'; + for (let ic of invalidChars) { + if (s.indexOf(ic) !== -1) { + return false; + } + } + return true; +} + +function isValidURL (s) { + if (s.indexOf('://') === -1) { + return false; + } + const invalidChars = '<\\)?)>{~}^()=!-"*]\'` $'; + for (let ic of invalidChars) { + if (s.indexOf(ic) !== -1) { + return false; + } + } + return true; +} + +module.exports = parseStrings;