tracer.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals, print_function
  3. import binascii
  4. import codecs
  5. import os
  6. import platform
  7. import re
  8. import subprocess
  9. import threading
  10. import time
  11. from frida import FileMonitor
  12. from frida.core import Function, Module, ModuleFunction, ObjCMethod
  13. class TracerProfileBuilder(object):
  14. _RE_REL_ADDRESS = re.compile("(?P<module>[^\s!]+)!(?P<offset>(0x)?[0-9a-fA-F]+)")
  15. def __init__(self):
  16. self._spec = []
  17. def include_modules(self, *module_name_globs):
  18. for m in module_name_globs:
  19. self._spec.append(('include', 'module', m))
  20. return self
  21. def exclude_modules(self, *module_name_globs):
  22. for m in module_name_globs:
  23. self._spec.append(('exclude', 'module', m))
  24. return self
  25. def include(self, *function_name_globs):
  26. for f in function_name_globs:
  27. self._spec.append(('include', 'function', f))
  28. return self
  29. def exclude(self, *function_name_globs):
  30. for f in function_name_globs:
  31. self._spec.append(('exclude', 'function', f))
  32. return self
  33. def include_relative_address(self, *address_rel_offsets):
  34. for f in address_rel_offsets:
  35. m = TracerProfileBuilder._RE_REL_ADDRESS.search(f)
  36. if m is None:
  37. continue
  38. self._spec.append(('include', 'relative_function', {
  39. 'module': m.group('module'),
  40. 'offset': int(m.group('offset'), base=16)
  41. }))
  42. return self
  43. def include_imports(self, *module_name_globs):
  44. for m in module_name_globs:
  45. self._spec.append(('include', 'imports', m))
  46. return self
  47. def include_objc_method(self, *function_name_globs):
  48. for f in function_name_globs:
  49. self._spec.append(('include', 'objc_method', f))
  50. return self
  51. def build(self):
  52. return TracerProfile(self._spec)
  53. class TracerProfile(object):
  54. _BLACKLIST = set([
  55. "libSystem.B.dylib!dyld_stub_binder"
  56. ])
  57. def __init__(self, spec):
  58. self._spec = spec
  59. def resolve(self, session, log_handler=None):
  60. script = session.create_script(name="profile-resolver", source=self._create_resolver_script())
  61. script.set_log_handler(log_handler)
  62. def on_message(message, data):
  63. print(message)
  64. script.on('message', on_message)
  65. script.load()
  66. try:
  67. data = script.exports.resolve(self._spec)
  68. finally:
  69. script.unload()
  70. modules = {}
  71. for module_id, m in data['modules'].items():
  72. module = Module(m['name'], int(m['base'], 16), m['size'], m['path'], session)
  73. modules[int(module_id)] = module
  74. working_set = []
  75. for target in data['targets']:
  76. objc = target.get('objc')
  77. if objc is not None:
  78. method = objc['method']
  79. of = ObjCMethod(method['type'], objc['className'], method['name'], int(target['address'], 16))
  80. working_set.append(of)
  81. else:
  82. name = target['name']
  83. absolute_address = int(target['address'], 16)
  84. module_id = target.get('module')
  85. if module_id is not None:
  86. module = modules[module_id]
  87. relative_address = absolute_address - module.base_address
  88. exported = not target.get('private', False)
  89. mf = ModuleFunction(module, name, relative_address, exported)
  90. if not self._is_blacklisted(mf):
  91. working_set.append(mf)
  92. else:
  93. f = Function(name, absolute_address)
  94. working_set.append(f)
  95. return working_set
  96. def _is_blacklisted(self, module_function):
  97. key = module_function.module.name + "!" + module_function.name
  98. return key in TracerProfile._BLACKLIST
  99. def _create_resolver_script(self):
  100. return r""""use strict";
  101. rpc.exports = {
  102. resolve: function (spec) {
  103. var workingSet = spec.reduce(function (workingSet, item) {
  104. var operation = item[0];
  105. var scope = item[1];
  106. var param = item[2];
  107. switch (scope) {
  108. case 'module':
  109. if (operation === 'include')
  110. workingSet = includeModule(param, workingSet);
  111. else if (operation === 'exclude')
  112. workingSet = excludeModule(param, workingSet);
  113. break;
  114. case 'function':
  115. if (operation === 'include')
  116. workingSet = includeFunction(param, workingSet);
  117. else if (operation === 'exclude')
  118. workingSet = excludeFunction(param, workingSet);
  119. break;
  120. case 'relative_function':
  121. if (operation === 'include')
  122. workingSet = includeRelativeFunction(param, workingSet);
  123. break;
  124. case 'imports':
  125. if (operation === 'include')
  126. workingSet = includeImports(param, workingSet);
  127. break;
  128. case 'objc_method':
  129. if (operation === 'include')
  130. workingSet = includeObjCMethod(param, workingSet);
  131. break;
  132. }
  133. return workingSet;
  134. }, {});
  135. var modules = {};
  136. var targets = [];
  137. for (var address in workingSet) {
  138. if (workingSet.hasOwnProperty(address)) {
  139. var target = workingSet[address];
  140. var moduleId = target.module;
  141. if (moduleId !== undefined && !modules.hasOwnProperty(moduleId)) {
  142. var m = allModules()[moduleId];
  143. delete m._cachedFunctionExports;
  144. modules[moduleId] = m;
  145. }
  146. targets.push(target);
  147. }
  148. }
  149. return {
  150. modules: modules,
  151. targets: targets
  152. };
  153. }
  154. };
  155. function includeModule(pattern, workingSet) {
  156. moduleResolver().enumerateMatchesSync('exports:' + pattern + '!*').forEach(function (m) {
  157. workingSet[m.address.toString()] = moduleExportFromMatch(m);
  158. });
  159. return workingSet;
  160. }
  161. function excludeModule(pattern, workingSet) {
  162. moduleResolver().enumerateMatchesSync('exports:' + pattern + '!*').forEach(function (m) {
  163. delete workingSet[m.address.toString()];
  164. });
  165. return workingSet;
  166. }
  167. function includeFunction(pattern, workingSet) {
  168. moduleResolver().enumerateMatchesSync('exports:*!' + pattern).forEach(function (m) {
  169. workingSet[m.address.toString()] = moduleExportFromMatch(m);
  170. });
  171. return workingSet;
  172. }
  173. function excludeFunction(pattern, workingSet) {
  174. moduleResolver().enumerateMatchesSync('exports:*!' + pattern).forEach(function (m) {
  175. delete workingSet[m.address.toString()];
  176. });
  177. return workingSet;
  178. }
  179. function includeRelativeFunction(func, workingSet) {
  180. var relativeToModule = func.module;
  181. var modules = allModules();
  182. for (var moduleIndex = 0; moduleIndex !== modules.length; moduleIndex++) {
  183. var module = modules[moduleIndex];
  184. if (module.path === relativeToModule || module.name === relativeToModule) {
  185. var relativeAddress = ptr(func.offset);
  186. var absoluteAddress = module.base.add(relativeAddress);
  187. workingSet[absoluteAddress] = {
  188. name: "sub_" + relativeAddress.toString(16),
  189. address: absoluteAddress,
  190. module: moduleIndex,
  191. private: true
  192. };
  193. }
  194. }
  195. return workingSet;
  196. }
  197. function includeImports(pattern, workingSet) {
  198. var matches;
  199. if (pattern === null) {
  200. var mainModule = allModules()[0].path;
  201. matches = moduleResolver().enumerateMatchesSync('imports:' + mainModule + '!*');
  202. } else {
  203. matches = moduleResolver().enumerateMatchesSync('imports:' + pattern + '!*');
  204. }
  205. matches.map(moduleExportFromMatch).forEach(function (e) {
  206. workingSet[e.address.toString()] = e;
  207. });
  208. return workingSet;
  209. }
  210. function includeObjCMethod(pattern, workingSet) {
  211. objcResolver().enumerateMatchesSync(pattern).forEach(function (m) {
  212. workingSet[m.address.toString()] = objcMethodFromMatch(m);
  213. });
  214. return workingSet;
  215. }
  216. var cachedModuleResolver = null;
  217. function moduleResolver() {
  218. if (cachedModuleResolver === null)
  219. cachedModuleResolver = new ApiResolver('module');
  220. return cachedModuleResolver;
  221. }
  222. var cachedObjcResolver = null;
  223. function objcResolver() {
  224. if (cachedObjcResolver === null) {
  225. try {
  226. cachedObjcResolver = new ApiResolver('objc');
  227. } catch (e) {
  228. throw new Error("Objective-C runtime is not available");
  229. }
  230. }
  231. return cachedObjcResolver;
  232. }
  233. var cachedModules = null;
  234. function allModules() {
  235. if (cachedModules === null) {
  236. cachedModules = Process.enumerateModulesSync();
  237. cachedModules._idByPath = cachedModules.reduce(function (mappings, module, index) {
  238. mappings[module.path] = index;
  239. return mappings;
  240. }, {});
  241. }
  242. return cachedModules;
  243. }
  244. function moduleExportFromMatch(m) {
  245. var encodedName = m.name;
  246. var delimiterIndex = encodedName.indexOf('!');
  247. var modulePath = encodedName.substring(0, delimiterIndex);
  248. var functionName = encodedName.substring(delimiterIndex + 1);
  249. return {
  250. name: functionName,
  251. address: m.address,
  252. module: allModules()._idByPath[modulePath]
  253. };
  254. }
  255. function objcMethodFromMatch(m) {
  256. var encodedName = m.name;
  257. var methodType = encodedName[0];
  258. var delimiterIndex = encodedName.indexOf(' ', 3);
  259. var className = encodedName.substring(2, delimiterIndex);
  260. var methodName = encodedName.substring(delimiterIndex + 1, encodedName.length - 1);
  261. return {
  262. objc: {
  263. className: className,
  264. method: {
  265. type: methodType,
  266. name: methodName
  267. }
  268. },
  269. address: m.address
  270. };
  271. }
  272. """
  273. class Tracer(object):
  274. def __init__(self, reactor, repository, profile, log_handler=None):
  275. self._reactor = reactor
  276. self._repository = repository
  277. self._profile = profile
  278. self._script = None
  279. self._log_handler = log_handler
  280. def start_trace(self, session, ui):
  281. def on_create(*args):
  282. ui.on_trace_handler_create(*args)
  283. self._repository.on_create(on_create)
  284. def on_load(*args):
  285. ui.on_trace_handler_load(*args)
  286. self._repository.on_load(on_load)
  287. def on_update(function, handler, source):
  288. self._script.exports.update([{
  289. 'name': function.name,
  290. 'absolute_address': hex(function.absolute_address),
  291. 'handler': handler
  292. }])
  293. self._repository.on_update(on_update)
  294. def on_message(message, data):
  295. self._reactor.schedule(lambda: self._process_message(message, data, ui))
  296. ui.on_trace_progress('resolve')
  297. working_set = self._profile.resolve(session, log_handler=self._log_handler)
  298. ui.on_trace_progress('instrument')
  299. self._script = session.create_script(name="tracer", source=self._create_trace_script())
  300. self._script.set_log_handler(self._log_handler)
  301. self._script.on('message', on_message)
  302. self._script.load()
  303. for chunk in [working_set[i:i+1000] for i in range(0, len(working_set), 1000)]:
  304. targets = [{
  305. 'name': function.name,
  306. 'absolute_address': hex(function.absolute_address),
  307. 'handler': self._repository.ensure_handler(function)
  308. } for function in chunk]
  309. self._script.exports.add(targets)
  310. self._repository.commit_handlers()
  311. self._reactor.schedule(lambda: ui.on_trace_progress('ready'))
  312. return working_set
  313. def stop(self):
  314. if self._script is not None:
  315. try:
  316. self._script.unload()
  317. except:
  318. pass
  319. self._script = None
  320. def _create_trace_script(self):
  321. return """"use strict";
  322. var started = Date.now();
  323. var handlers = {};
  324. var state = {};
  325. var pending = [];
  326. var timer = null;
  327. installFlushBeforeExitHandlers();
  328. rpc.exports = {
  329. add: function (targets) {
  330. targets.forEach(function (target) {
  331. var h = [parseHandler(target)];
  332. var name = target.name;
  333. var targetAddress = target.absolute_address;
  334. target = null;
  335. handlers[targetAddress] = h;
  336. function invokeCallback(callback, context, param) {
  337. if (callback === undefined)
  338. return;
  339. var timestamp = Date.now() - started;
  340. var threadId = context.threadId;
  341. var depth = context.depth;
  342. function log(message) {
  343. emit([timestamp, threadId, depth, targetAddress, message]);
  344. }
  345. callback.call(context, log, param, state);
  346. }
  347. try {
  348. Interceptor.attach(ptr(targetAddress), {
  349. onEnter: function (args) {
  350. invokeCallback(h[0].onEnter, this, args);
  351. },
  352. onLeave: function (retval) {
  353. invokeCallback(h[0].onLeave, this, retval);
  354. }
  355. });
  356. } catch (e) {
  357. send({
  358. from: "/targets",
  359. name: '+error',
  360. payload: {
  361. message: "Skipping '" + name + "': " + e.message
  362. }
  363. });
  364. }
  365. });
  366. },
  367. update: function (targets) {
  368. targets.forEach(function (target) {
  369. handlers[target.absolute_address][0] = parseHandler(target);
  370. });
  371. }
  372. };
  373. function emit(event) {
  374. pending.push(event);
  375. if (timer === null)
  376. timer = setTimeout(flush, 50);
  377. }
  378. function flush() {
  379. if (timer !== null) {
  380. clearTimeout(timer);
  381. timer = null;
  382. }
  383. if (pending.length === 0)
  384. return;
  385. var items = pending;
  386. pending = [];
  387. send({
  388. from: "/events",
  389. name: '+add',
  390. payload: {
  391. items: items
  392. }
  393. });
  394. }
  395. function parseHandler(target) {
  396. try {
  397. return (1, eval)("(" + target.handler + ")");
  398. } catch (e) {
  399. send({
  400. from: "/targets",
  401. name: '+error',
  402. payload: {
  403. message: "Invalid handler for '" + target.name + "': " + e.message
  404. }
  405. });
  406. return {};
  407. }
  408. }
  409. function installFlushBeforeExitHandlers() {
  410. if (Process.platform === 'windows') {
  411. attachFlushBeforeExitHandler("kernel32.dll", "ExitProcess");
  412. } else {
  413. attachFlushBeforeExitHandler(null, "abort");
  414. attachFlushBeforeExitHandler(null, "exit");
  415. }
  416. }
  417. function attachFlushBeforeExitHandler(module, name) {
  418. Interceptor.attach(Module.findExportByName(module, name), performFlushBeforeExit);
  419. }
  420. function performFlushBeforeExit() {
  421. flush();
  422. send({
  423. from: "/events",
  424. name: '+flush',
  425. payload: {}
  426. });
  427. recv('+flush-ack', function () {}).wait();
  428. }
  429. """
  430. def _process_message(self, message, data, ui):
  431. handled = False
  432. if message['type'] == 'send':
  433. stanza = message['payload']
  434. if stanza['from'] == "/events":
  435. if stanza['name'] == '+add':
  436. events = [(timestamp, thread_id, depth, int(target_address.rstrip("L"), 16), message) for timestamp, thread_id, depth, target_address, message in stanza['payload']['items']]
  437. ui.on_trace_events(events)
  438. handled = True
  439. elif stanza['name'] == '+flush':
  440. try:
  441. self._script.post({ 'type': '+flush-ack' })
  442. except Exception as e:
  443. pass
  444. handled = True
  445. elif stanza['from'] == "/targets" and stanza['name'] == '+error':
  446. ui.on_trace_error(stanza['payload'])
  447. handled = True
  448. if not handled:
  449. print(message)
  450. class Repository(object):
  451. def __init__(self):
  452. self._on_create_callback = None
  453. self._on_load_callback = None
  454. self._on_update_callback = None
  455. def ensure_handler(self, function):
  456. raise NotImplementedError("not implemented")
  457. def commit_handlers(self):
  458. pass
  459. def on_create(self, callback):
  460. self._on_create_callback = callback
  461. def on_load(self, callback):
  462. self._on_load_callback = callback
  463. def on_update(self, callback):
  464. self._on_update_callback = callback
  465. def _notify_create(self, function, handler, source):
  466. if self._on_create_callback is not None:
  467. self._on_create_callback(function, handler, source)
  468. def _notify_load(self, function, handler, source):
  469. if self._on_load_callback is not None:
  470. self._on_load_callback(function, handler, source)
  471. def _notify_update(self, function, handler, source):
  472. if self._on_update_callback is not None:
  473. self._on_update_callback(function, handler, source)
  474. def _create_stub_handler(self, function):
  475. if isinstance(function, ObjCMethod):
  476. display_name = function.display_name()
  477. _nonlocal_i = {'val': 2}
  478. def objc_arg(m):
  479. r = ':" + args[%d] + " ' % _nonlocal_i['val']
  480. _nonlocal_i['val'] += 1
  481. return r
  482. log_str = '"' + re.sub(r':', objc_arg, display_name) + '"'
  483. else:
  484. display_name = function.name
  485. args = ""
  486. argc = 0
  487. varargs = False
  488. try:
  489. with open(os.devnull, 'w') as devnull:
  490. man_argv = ["man"]
  491. if platform.system() != "Darwin":
  492. man_argv.extend(["-E", "UTF-8"])
  493. man_argv.extend(["-P", "col -b", "2", function.name])
  494. output = subprocess.check_output(man_argv, stderr=devnull)
  495. match = re.search(r"^SYNOPSIS(?:.|\n)*?((?:^.+$\n)* {5}\w+ \**?" + function.name + r"\((?:.+\,\s*?$\n)*?(?:.+\;$\n))(?:.|\n)*^DESCRIPTION", output.decode('UTF-8', errors='replace'), re.MULTILINE)
  496. if match:
  497. decl = match.group(1)
  498. for argm in re.finditer(r"([^* ]*)\s*(,|\))", decl):
  499. arg = argm.group(1)
  500. if arg == 'void':
  501. continue
  502. if arg == '...':
  503. args += '+ ", ..."'
  504. varargs = True
  505. continue
  506. args += '%(pre)s%(arg)s=" + args[%(argc)s]' % {"arg": arg, "argc": argc, "pre": '"' if argc == 0 else '+ ", '}
  507. argc += 1
  508. except Exception as e:
  509. pass
  510. if args == "":
  511. args = '""'
  512. log_str = '"%(name)s(" + %(args)s + ")"' % { "name": function.name, "args": args }
  513. return """\
  514. /*
  515. * Auto-generated by Frida. Please modify to match the signature of %(display_name)s.
  516. * This stub is currently auto-generated from manpages when available.
  517. *
  518. * For full API reference, see: http://www.frida.re/docs/javascript-api/
  519. */
  520. {
  521. /**
  522. * Called synchronously when about to call %(display_name)s.
  523. *
  524. * @this {object} - Object allowing you to store state for use in onLeave.
  525. * @param {function} log - Call this function with a string to be presented to the user.
  526. * @param {array} args - Function arguments represented as an array of NativePointer objects.
  527. * For example use Memory.readUtf8String(args[0]) if the first argument is a pointer to a C string encoded as UTF-8.
  528. * It is also possible to modify arguments by assigning a NativePointer object to an element of this array.
  529. * @param {object} state - Object allowing you to keep state across function calls.
  530. * Only one JavaScript function will execute at a time, so do not worry about race-conditions.
  531. * However, do not use this to store function arguments across onEnter/onLeave, but instead
  532. * use "this" which is an object for keeping state local to an invocation.
  533. */
  534. onEnter: function (log, args, state) {
  535. log(%(log_str)s);
  536. },
  537. /**
  538. * Called synchronously when about to return from %(display_name)s.
  539. *
  540. * See onEnter for details.
  541. *
  542. * @this {object} - Object allowing you to access state stored in onEnter.
  543. * @param {function} log - Call this function with a string to be presented to the user.
  544. * @param {NativePointer} retval - Return value represented as a NativePointer object.
  545. * @param {object} state - Object allowing you to keep state across function calls.
  546. */
  547. onLeave: function (log, retval, state) {
  548. }
  549. }
  550. """ % {"display_name": display_name, "log_str": log_str}
  551. class MemoryRepository(Repository):
  552. def __init__(self):
  553. super(MemoryRepository, self).__init__()
  554. self._handlers = {}
  555. def ensure_handler(self, function):
  556. handler = self._handlers.get(function)
  557. if handler is None:
  558. handler = self._create_stub_handler(function)
  559. self._handlers[function] = handler
  560. self._notify_create(function, handler, "memory")
  561. else:
  562. self._notify_load(function, handler, "memory")
  563. return handler
  564. class FileRepository(Repository):
  565. def __init__(self, reactor):
  566. super(FileRepository, self).__init__()
  567. self._reactor = reactor
  568. self._handler_by_address = {}
  569. self._handler_by_file = {}
  570. self._changed_files = set()
  571. self._last_change_id = 0
  572. self._repo_dir = os.path.join(os.getcwd(), "__handlers__")
  573. self._repo_monitors = {}
  574. def ensure_handler(self, function):
  575. entry = self._handler_by_address.get(function.absolute_address)
  576. if entry is not None:
  577. (function, handler, handler_file) = entry
  578. return handler
  579. handler = None
  580. handler_files_to_try = []
  581. if isinstance(function, ModuleFunction):
  582. module_dir = os.path.join(self._repo_dir, to_filename(function.module.name))
  583. module_handler_file = os.path.join(module_dir, to_handler_filename(function.name))
  584. handler_files_to_try.append(module_handler_file)
  585. any_module_handler_file = os.path.join(self._repo_dir, to_handler_filename(function.name))
  586. handler_files_to_try.append(any_module_handler_file)
  587. for handler_file in handler_files_to_try:
  588. if os.path.isfile(handler_file):
  589. with codecs.open(handler_file, 'r', 'utf-8') as f:
  590. handler = f.read()
  591. self._notify_load(function, handler, handler_file)
  592. break
  593. if handler is None:
  594. handler = self._create_stub_handler(function)
  595. handler_file = handler_files_to_try[0]
  596. handler_dir = os.path.dirname(handler_file)
  597. if not os.path.isdir(handler_dir):
  598. os.makedirs(handler_dir)
  599. with open(handler_file, 'w') as f:
  600. f.write(handler)
  601. self._notify_create(function, handler, handler_file)
  602. entry = (function, handler, handler_file)
  603. self._handler_by_address[function.absolute_address] = entry
  604. self._handler_by_file[handler_file] = entry
  605. self._ensure_monitor(handler_file)
  606. return handler
  607. def _ensure_monitor(self, handler_file):
  608. handler_dir = os.path.dirname(handler_file)
  609. monitor = self._repo_monitors.get(handler_dir)
  610. if monitor is None:
  611. monitor = FileMonitor(handler_dir)
  612. monitor.on('change', self._on_change)
  613. self._repo_monitors[handler_dir] = monitor
  614. def commit_handlers(self):
  615. for monitor in self._repo_monitors.values():
  616. monitor.enable()
  617. def _on_change(self, changed_file, other_file, event_type):
  618. if changed_file not in self._handler_by_file or event_type == 'changes-done-hint':
  619. return
  620. self._changed_files.add(changed_file)
  621. self._last_change_id += 1
  622. change_id = self._last_change_id
  623. self._reactor.schedule(lambda: self._sync_handlers(change_id), delay=0.05)
  624. def _sync_handlers(self, change_id):
  625. if change_id != self._last_change_id:
  626. return
  627. changes = self._changed_files.copy()
  628. self._changed_files.clear()
  629. for changed_handler_file in changes:
  630. (function, old_handler, handler_file) = self._handler_by_file[changed_handler_file]
  631. with codecs.open(handler_file, 'r', 'utf-8') as f:
  632. new_handler = f.read()
  633. changed = new_handler != old_handler
  634. if changed:
  635. entry = (function, new_handler, handler_file)
  636. self._handler_by_address[function.absolute_address] = entry
  637. self._handler_by_file[handler_file] = entry
  638. self._notify_update(function, new_handler, handler_file)
  639. class UI(object):
  640. def on_trace_progress(self, operation):
  641. pass
  642. def on_trace_error(self, error):
  643. pass
  644. def on_trace_events(self, events):
  645. pass
  646. def on_trace_handler_create(self, function, handler, source):
  647. pass
  648. def on_trace_handler_load(self, function, handler, source):
  649. pass
  650. def main():
  651. from colorama import Fore, Style
  652. from frida.application import ConsoleApplication, input_with_timeout
  653. class TracerApplication(ConsoleApplication, UI):
  654. def __init__(self):
  655. super(TracerApplication, self).__init__(self._await_ctrl_c)
  656. self._palette = [Fore.CYAN, Fore.MAGENTA, Fore.YELLOW, Fore.GREEN, Fore.RED, Fore.BLUE]
  657. self._next_color = 0
  658. self._attributes_by_thread_id = {}
  659. self._last_event_tid = -1
  660. def _add_options(self, parser):
  661. pb = TracerProfileBuilder()
  662. def process_builder_arg(option, opt_str, value, parser, method, **kwargs):
  663. method(value)
  664. parser.add_option("-I", "--include-module", help="include MODULE", metavar="MODULE",
  665. type='string', action='callback', callback=process_builder_arg, callback_args=(pb.include_modules,))
  666. parser.add_option("-X", "--exclude-module", help="exclude MODULE", metavar="MODULE",
  667. type='string', action='callback', callback=process_builder_arg, callback_args=(pb.exclude_modules,))
  668. parser.add_option("-i", "--include", help="include FUNCTION", metavar="FUNCTION",
  669. type='string', action='callback', callback=process_builder_arg, callback_args=(pb.include,))
  670. parser.add_option("-x", "--exclude", help="exclude FUNCTION", metavar="FUNCTION",
  671. type='string', action='callback', callback=process_builder_arg, callback_args=(pb.exclude,))
  672. parser.add_option("-a", "--add", help="add MODULE!OFFSET", metavar="MODULE!OFFSET",
  673. type='string', action='callback', callback=process_builder_arg, callback_args=(pb.include_relative_address,))
  674. parser.add_option("-T", "--include-imports", help="include program's imports",
  675. action='callback', callback=process_builder_arg, callback_args=(pb.include_imports,))
  676. parser.add_option("-t", "--include-module-imports", help="include MODULE imports", metavar="MODULE",
  677. type='string', action='callback', callback=process_builder_arg, callback_args=(pb.include_imports,))
  678. parser.add_option("-m", "--include-objc-method", help="include OBJC_METHOD", metavar="OBJC_METHOD",
  679. type='string', action='callback', callback=process_builder_arg, callback_args=(pb.include_objc_method,))
  680. self._profile_builder = pb
  681. def _usage(self):
  682. return "usage: %prog [options] target"
  683. def _initialize(self, parser, options, args):
  684. self._tracer = None
  685. self._targets = None
  686. self._profile = self._profile_builder.build()
  687. def _needs_target(self):
  688. return True
  689. def _start(self):
  690. self._tracer = Tracer(self._reactor, FileRepository(self._reactor), self._profile, log_handler=self._log)
  691. try:
  692. self._targets = self._tracer.start_trace(self._session, self)
  693. except Exception as e:
  694. self._update_status("Failed to start tracing: {error}".format(error=e))
  695. self._exit(1)
  696. def _stop(self):
  697. self._print("Stopping...")
  698. self._tracer.stop()
  699. self._tracer = None
  700. def _await_ctrl_c(self, reactor):
  701. while reactor.is_running():
  702. try:
  703. input_with_timeout(0.5)
  704. except KeyboardInterrupt:
  705. break
  706. def on_trace_progress(self, operation):
  707. if operation == 'resolve':
  708. self._update_status("Resolving functions...")
  709. elif operation == 'instrument':
  710. self._update_status("Instrumenting functions...")
  711. elif operation == 'ready':
  712. if len(self._targets) == 1:
  713. plural = ""
  714. else:
  715. plural = "s"
  716. self._update_status("Started tracing %d function%s. Press Ctrl+C to stop." % (len(self._targets), plural))
  717. self._resume()
  718. def on_trace_error(self, error):
  719. self._print(Fore.RED + Style.BRIGHT + "Error" + Style.RESET_ALL + ": " + error['message'])
  720. def on_trace_events(self, events):
  721. no_attributes = Style.RESET_ALL
  722. for timestamp, thread_id, depth, target_address, message in events:
  723. indent = depth * " | "
  724. attributes = self._get_attributes(thread_id)
  725. if thread_id != self._last_event_tid:
  726. self._print("%s /* TID 0x%x */%s" % (attributes, thread_id, Style.RESET_ALL))
  727. self._last_event_tid = thread_id
  728. self._print("%6d ms %s%s%s%s" % (timestamp, attributes, indent, message, no_attributes))
  729. def on_trace_handler_create(self, function, handler, source):
  730. self._print("%s: Auto-generated handler at \"%s\"" % (function, source.replace("\\", "\\\\")))
  731. def on_trace_handler_load(self, function, handler, source):
  732. self._print("%s: Loaded handler at \"%s\"" % (function, source.replace("\\", "\\\\")))
  733. def _get_attributes(self, thread_id):
  734. attributes = self._attributes_by_thread_id.get(thread_id, None)
  735. if attributes is None:
  736. color = self._next_color
  737. self._next_color += 1
  738. attributes = self._palette[color % len(self._palette)]
  739. if (1 + int(color / len(self._palette))) % 2 == 0:
  740. attributes += Style.BRIGHT
  741. self._attributes_by_thread_id[thread_id] = attributes
  742. return attributes
  743. app = TracerApplication()
  744. app.run()
  745. def to_filename(name):
  746. result = ""
  747. for c in name:
  748. if c.isalnum() or c == ".":
  749. result += c
  750. else:
  751. result += "_"
  752. return result
  753. def to_handler_filename(name):
  754. full_filename = to_filename(name)
  755. if len(full_filename) <= 41:
  756. return full_filename + ".js"
  757. crc = binascii.crc32(full_filename.encode())
  758. return full_filename[0:32] + "_%08x.js" % crc
  759. if __name__ == '__main__':
  760. main()