application.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals, print_function
  3. import collections
  4. import errno
  5. from optparse import OptionParser
  6. import os
  7. import platform
  8. import select
  9. import sys
  10. import threading
  11. import time
  12. import colorama
  13. from colorama import Fore, Style
  14. import frida
  15. if platform.system() == 'Windows':
  16. import msvcrt
  17. def input_with_timeout(timeout):
  18. if platform.system() == 'Windows':
  19. start_time = time.time()
  20. s = ''
  21. while True:
  22. while msvcrt.kbhit():
  23. c = msvcrt.getche()
  24. if ord(c) == 13: # enter_key
  25. break
  26. elif ord(c) >= 32: #space_char
  27. s += c
  28. if time.time() - start_time > timeout:
  29. return None
  30. return s
  31. else:
  32. while True:
  33. try:
  34. rlist, _, _ = select.select([sys.stdin], [], [], timeout)
  35. break
  36. except (OSError, select.error) as e:
  37. if e.args[0] != errno.EINTR:
  38. raise e
  39. if rlist:
  40. return sys.stdin.readline()
  41. else:
  42. return None
  43. def await_enter(reactor):
  44. try:
  45. while input_with_timeout(0.5) == None:
  46. if not reactor.is_running():
  47. break
  48. except KeyboardInterrupt:
  49. print('')
  50. class ConsoleState:
  51. EMPTY = 1
  52. STATUS = 2
  53. TEXT = 3
  54. class ConsoleApplication(object):
  55. def __init__(self, run_until_return=await_enter, on_stop=None):
  56. colorama.init()
  57. parser = OptionParser(usage=self._usage(), version=frida.__version__)
  58. parser.add_option("-D", "--device", help="connect to device with the given ID",
  59. metavar="ID", type='string', action='store', dest="device_id", default=None)
  60. parser.add_option("-U", "--usb", help="connect to USB device",
  61. action='store_const', const='tether', dest="device_type", default=None)
  62. parser.add_option("-R", "--remote", help="connect to remote frida-server",
  63. action='store_const', const='remote', dest="device_type", default=None)
  64. parser.add_option("-H", "--host", help="connect to remote frida-server on HOST",
  65. metavar="HOST", type='string', action='store', dest="host", default=None)
  66. if self._needs_target():
  67. def store_target(option, opt_str, target_value, parser, target_type, *args, **kwargs):
  68. if target_type == 'file':
  69. target_value = [target_value]
  70. setattr(parser.values, 'target', (target_type, target_value))
  71. parser.add_option("-f", "--file", help="spawn FILE", metavar="FILE",
  72. type='string', action='callback', callback=store_target, callback_args=('file',))
  73. parser.add_option("-n", "--attach-name", help="attach to NAME", metavar="NAME",
  74. type='string', action='callback', callback=store_target, callback_args=('name',))
  75. parser.add_option("-p", "--attach-pid", help="attach to PID", metavar="PID",
  76. type='int', action='callback', callback=store_target, callback_args=('pid',))
  77. parser.add_option("--debug", help="enable the Node.js compatible script debugger",
  78. action='store_true', dest="enable_debugger", default=False)
  79. parser.add_option("--disable-jit", help="disable JIT",
  80. action='store_true', dest="disable_jit", default=False)
  81. self._add_options(parser)
  82. (options, args) = parser.parse_args()
  83. if sys.version_info[0] < 3:
  84. input_encoding = sys.stdin.encoding or 'UTF-8'
  85. args = [arg.decode(input_encoding) for arg in args]
  86. self._device_id = options.device_id
  87. self._device_type = options.device_type
  88. self._host = options.host
  89. self._device = None
  90. self._schedule_on_output = lambda pid, fd, data: self._reactor.schedule(lambda: self._on_output(pid, fd, data))
  91. self._schedule_on_device_lost = lambda: self._reactor.schedule(self._on_device_lost)
  92. self._spawned_pid = None
  93. self._spawned_argv = None
  94. self._session = None
  95. if self._needs_target():
  96. self._enable_debugger = options.enable_debugger
  97. self._disable_jit = options.disable_jit
  98. else:
  99. self._enable_debugger = False
  100. self._disable_jit = False
  101. self._schedule_on_session_detached = lambda: self._reactor.schedule(self._on_session_detached)
  102. self._started = False
  103. self._resumed = False
  104. self._reactor = Reactor(run_until_return, on_stop)
  105. self._exit_status = None
  106. self._console_state = ConsoleState.EMPTY
  107. if sum(map(lambda v: int(v is not None), (self._device_id, self._device_type, self._host))) > 1:
  108. parser.error("Only one of -D, -U, -R, and -H may be specified")
  109. if self._needs_target():
  110. target = getattr(options, 'target', None)
  111. if target is None:
  112. if len(args) < 1:
  113. parser.error("target file, process name or pid must be specified")
  114. target = infer_target(args[0])
  115. args.pop(0)
  116. target = expand_target(target)
  117. if target[0] == 'file':
  118. argv = target[1]
  119. argv.extend(args)
  120. args = []
  121. self._target = target
  122. else:
  123. self._target = None
  124. self._initialize(parser, options, args)
  125. def run(self):
  126. mgr = frida.get_device_manager()
  127. on_devices_changed = lambda: self._reactor.schedule(self._try_start)
  128. mgr.on('changed', on_devices_changed)
  129. self._reactor.schedule(self._try_start)
  130. self._reactor.schedule(self._show_message_if_no_device, delay=0.1)
  131. self._reactor.run()
  132. if self._started:
  133. self._stop()
  134. if self._session is not None:
  135. self._session.off('detached', self._schedule_on_session_detached)
  136. self._session.detach()
  137. self._session = None
  138. if self._spawned_pid is not None:
  139. try:
  140. self._device.kill(self._spawned_pid)
  141. except:
  142. pass
  143. if self._device is not None:
  144. self._device.off('output', self._schedule_on_output)
  145. self._device.off('lost', self._schedule_on_device_lost)
  146. mgr.off('changed', on_devices_changed)
  147. frida.shutdown()
  148. sys.exit(self._exit_status)
  149. def _add_options(self, parser):
  150. pass
  151. def _initialize(self, parser, options, args):
  152. pass
  153. def _needs_target(self):
  154. return False
  155. def _start(self):
  156. pass
  157. def _stop(self):
  158. pass
  159. def _resume(self):
  160. if self._resumed:
  161. return
  162. if self._spawned_pid is not None:
  163. self._device.resume(self._spawned_pid)
  164. self._resumed = True
  165. def _exit(self, exit_status):
  166. self._exit_status = exit_status
  167. self._reactor.stop()
  168. def _try_start(self):
  169. if self._device is not None:
  170. return
  171. if self._device_id is not None:
  172. try:
  173. self._device = frida.get_device(self._device_id)
  174. except:
  175. self._update_status("Device '%s' not found" % self._device_id)
  176. self._exit(1)
  177. return
  178. elif self._device_type is not None:
  179. self._device = find_device(self._device_type)
  180. if self._device is None:
  181. return
  182. elif self._host is not None:
  183. self._device = frida.get_device_manager().add_remote_device(self._host)
  184. else:
  185. self._device = frida.get_local_device()
  186. self._device.on('output', self._schedule_on_output)
  187. self._device.on('lost', self._schedule_on_device_lost)
  188. if self._target is not None:
  189. spawning = True
  190. try:
  191. target_type, target_value = self._target
  192. if target_type == 'file':
  193. argv = target_value
  194. self._update_status("Spawning `%s`..." % " ".join(argv))
  195. self._spawned_pid = self._device.spawn(argv)
  196. self._spawned_argv = argv
  197. attach_target = self._spawned_pid
  198. else:
  199. attach_target = target_value
  200. self._update_status("Attaching...")
  201. spawning = False
  202. self._session = self._device.attach(attach_target)
  203. if self._disable_jit:
  204. self._session.disable_jit()
  205. if self._enable_debugger:
  206. self._session.enable_debugger()
  207. self._print("Debugger listening on port 5858\n")
  208. self._session.on('detached', self._schedule_on_session_detached)
  209. except Exception as e:
  210. if spawning:
  211. self._update_status("Failed to spawn: %s" % e)
  212. else:
  213. self._update_status("Failed to attach: %s" % e)
  214. self._exit(1)
  215. return
  216. self._start()
  217. self._started = True
  218. def _show_message_if_no_device(self):
  219. if self._device is None:
  220. self._print("Waiting for USB device to appear...")
  221. def _on_output(self, pid, fd, data):
  222. if data is None:
  223. return
  224. if fd == 1:
  225. prefix = "stdout> "
  226. stream = sys.stdout
  227. else:
  228. prefix = "stderr> "
  229. stream = sys.stderr
  230. encoding = stream.encoding or 'UTF-8'
  231. text = data.decode(encoding, errors='replace')
  232. if text.endswith("\n"):
  233. text = text[:-1]
  234. lines = text.split("\n")
  235. self._print(prefix + ("\n" + prefix).join(lines))
  236. def _on_device_lost(self):
  237. if self._exit_status is not None:
  238. return
  239. self._print("Device disconnected.")
  240. self._exit(1)
  241. def _on_session_detached(self):
  242. self._print("Target process terminated.")
  243. self._exit(1)
  244. def _clear_status(self):
  245. if self._console_state == ConsoleState.STATUS:
  246. print("\033[A" + (80 * " "))
  247. def _update_status(self, message):
  248. if self._console_state == ConsoleState.STATUS:
  249. cursor_position = "\033[A"
  250. else:
  251. cursor_position = ""
  252. print("%-80s" % (cursor_position + Style.BRIGHT + message + Style.RESET_ALL,))
  253. self._console_state = ConsoleState.STATUS
  254. def _print(self, *args, **kwargs):
  255. encoded_args = []
  256. if sys.version_info[0] >= 3:
  257. string_type = str
  258. decoder = "unicode-escape"
  259. else:
  260. string_type = unicode
  261. decoder = "string-escape"
  262. encoding = sys.stdout.encoding or 'UTF-8'
  263. for arg in args:
  264. if isinstance(arg, string_type):
  265. encoded_args.append(arg.encode(encoding, errors='replace').decode(decoder))
  266. else:
  267. encoded_args.append(arg)
  268. print(*encoded_args, **kwargs)
  269. self._console_state = ConsoleState.TEXT
  270. def _log(self, level, text):
  271. if level == 'info':
  272. self._print(text)
  273. else:
  274. color = Fore.RED if level == 'error' else Fore.YELLOW
  275. self._print(color + Style.BRIGHT + text + Style.RESET_ALL)
  276. def find_device(type):
  277. for device in frida.enumerate_devices():
  278. if device.type == type:
  279. return device
  280. return None
  281. def infer_target(target_value):
  282. if target_value.startswith('.') or target_value.startswith(os.path.sep) \
  283. or (platform.system() == 'Windows' \
  284. and target_value[0].isalpha() \
  285. and target_value[1] == ":" \
  286. and target_value[2] == "\\"):
  287. target_type = 'file'
  288. target_value = [target_value]
  289. else:
  290. try:
  291. target_value = int(target_value)
  292. target_type = 'pid'
  293. except:
  294. target_type = 'name'
  295. return (target_type, target_value)
  296. def expand_target(target):
  297. target_type, target_value = target
  298. if target_type == 'file':
  299. target_value = [target_value[0]]
  300. return (target_type, target_value)
  301. class Reactor(object):
  302. def __init__(self, run_until_return, on_stop=None):
  303. self._running = False
  304. self._run_until_return = run_until_return
  305. self._on_stop = on_stop
  306. self._pending = collections.deque([])
  307. self._lock = threading.Lock()
  308. self._cond = threading.Condition(self._lock)
  309. def is_running(self):
  310. with self._lock:
  311. return self._running
  312. def run(self):
  313. with self._lock:
  314. self._running = True
  315. worker = threading.Thread(target=self._run)
  316. worker.start()
  317. self._run_until_return(self)
  318. self.stop()
  319. worker.join()
  320. def _run(self):
  321. running = True
  322. while running:
  323. now = time.time()
  324. work = None
  325. timeout = None
  326. with self._lock:
  327. for item in self._pending:
  328. (f, when) = item
  329. if now >= when:
  330. work = f
  331. self._pending.remove(item)
  332. break
  333. if len(self._pending) > 0:
  334. timeout = max([min(map(lambda item: item[1], self._pending)) - now, 0])
  335. if work is not None:
  336. work()
  337. with self._lock:
  338. if self._running:
  339. self._cond.wait(timeout)
  340. running = self._running
  341. if self._on_stop is not None:
  342. self._on_stop()
  343. def stop(self):
  344. with self._lock:
  345. self._running = False
  346. self._cond.notify()
  347. def schedule(self, f, delay=None):
  348. now = time.time()
  349. if delay is not None:
  350. when = now + delay
  351. else:
  352. when = now
  353. with self._lock:
  354. self._pending.append((f, when))
  355. self._cond.notify()