PageRenderTime 29ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/sulley/sessions.py

http://sulley.googlecode.com/
Python | 1095 lines | 949 code | 45 blank | 101 comment | 28 complexity | 0807244f4a9c387041e5a06c14ffae27 MD5 | raw file
Possible License(s): AGPL-1.0
  1. import os
  2. import re
  3. import sys
  4. import zlib
  5. import time
  6. import socket
  7. import httplib
  8. import cPickle
  9. import threading
  10. import BaseHTTPServer
  11. import blocks
  12. import pedrpc
  13. import pgraph
  14. import sex
  15. import primitives
  16. ########################################################################################################################
  17. class target:
  18. '''
  19. Target descriptor container.
  20. '''
  21. def __init__ (self, host, port, **kwargs):
  22. '''
  23. @type host: String
  24. @param host: Hostname or IP address of target system
  25. @type port: Integer
  26. @param port: Port of target service
  27. '''
  28. self.host = host
  29. self.port = port
  30. # set these manually once target is instantiated.
  31. self.netmon = None
  32. self.procmon = None
  33. self.vmcontrol = None
  34. self.netmon_options = {}
  35. self.procmon_options = {}
  36. self.vmcontrol_options = {}
  37. def pedrpc_connect (self):
  38. '''
  39. # pass specified target parameters to the PED-RPC server.
  40. '''
  41. # wait for the process monitor to come alive and then set its options.
  42. if self.procmon:
  43. while 1:
  44. try:
  45. if self.procmon.alive():
  46. break
  47. except:
  48. pass
  49. time.sleep(1)
  50. # connection established.
  51. for key in self.procmon_options.keys():
  52. eval('self.procmon.set_%s(self.procmon_options["%s"])' % (key, key))
  53. # wait for the network monitor to come alive and then set its options.
  54. if self.netmon:
  55. while 1:
  56. try:
  57. if self.netmon.alive():
  58. break
  59. except:
  60. pass
  61. time.sleep(1)
  62. # connection established.
  63. for key in self.netmon_options.keys():
  64. eval('self.netmon.set_%s(self.netmon_options["%s"])' % (key, key))
  65. ########################################################################################################################
  66. class connection (pgraph.edge.edge):
  67. def __init__ (self, src, dst, callback=None):
  68. '''
  69. Extends pgraph.edge with a callback option. This allows us to register a function to call between node
  70. transmissions to implement functionality such as challenge response systems. The callback method must follow
  71. this prototype::
  72. def callback(session, node, edge, sock)
  73. Where node is the node about to be sent, edge is the last edge along the current fuzz path to "node", session
  74. is a pointer to the session instance which is useful for snagging data such as sesson.last_recv which contains
  75. the data returned from the last socket transmission and sock is the live socket. A callback is also useful in
  76. situations where, for example, the size of the next packet is specified in the first packet.
  77. @type src: Integer
  78. @param src: Edge source ID
  79. @type dst: Integer
  80. @param dst: Edge destination ID
  81. @type callback: Function
  82. @param callback: (Optional, def=None) Callback function to pass received data to between node xmits
  83. '''
  84. # run the parent classes initialization routine first.
  85. pgraph.edge.edge.__init__(self, src, dst)
  86. self.callback = callback
  87. ########################################################################################################################
  88. class session (pgraph.graph):
  89. def __init__ (self, session_filename=None, skip=0, sleep_time=1.0, log_level=2, proto="tcp", bind=None, restart_interval=0, timeout=5.0, web_port=26000, crash_threshold=3, restart_sleep_time=300):
  90. '''
  91. Extends pgraph.graph and provides a container for architecting protocol dialogs.
  92. @type session_filename: String
  93. @kwarg session_filename: (Optional, def=None) Filename to serialize persistant data to
  94. @type skip: Integer
  95. @kwarg skip: (Optional, def=0) Number of test cases to skip
  96. @type sleep_time: Float
  97. @kwarg sleep_time: (Optional, def=1.0) Time to sleep in between tests
  98. @type log_level: Integer
  99. @kwarg log_level: (Optional, def=2) Set the log level, higher number == more log messages
  100. @type proto: String
  101. @kwarg proto: (Optional, def="tcp") Communication protocol ("tcp", "udp", "ssl")
  102. @type bind: Tuple (host, port)
  103. @kwarg bind: (Optional, def=random) Socket bind address and port
  104. @type timeout: Float
  105. @kwarg timeout: (Optional, def=5.0) Seconds to wait for a send/recv prior to timing out
  106. @type restart_interval: Integer
  107. @kwarg restart_interval (Optional, def=0) Restart the target after n test cases, disable by setting to 0
  108. @type crash_threshold: Integer
  109. @kwarg crash_threshold (Optional, def=3) Maximum number of crashes allowed before a node is exhaust
  110. @type restart_sleep_time: Integer
  111. @kwarg restart_sleep_time: Optional, def=300) Time in seconds to sleep when target can't be restarted
  112. '''
  113. # run the parent classes initialization routine first.
  114. pgraph.graph.__init__(self)
  115. self.session_filename = session_filename
  116. self.skip = skip
  117. self.sleep_time = sleep_time
  118. self.log_level = log_level
  119. self.proto = proto.lower()
  120. self.bind = bind
  121. self.ssl = False
  122. self.restart_interval = restart_interval
  123. self.timeout = timeout
  124. self.web_port = web_port
  125. self.crash_threshold = crash_threshold
  126. self.restart_sleep_time = restart_sleep_time
  127. self.total_num_mutations = 0
  128. self.total_mutant_index = 0
  129. self.fuzz_node = None
  130. self.targets = []
  131. self.netmon_results = {}
  132. self.procmon_results = {}
  133. self.pause_flag = False
  134. self.crashing_primitives = {}
  135. if self.proto == "tcp":
  136. self.proto = socket.SOCK_STREAM
  137. elif self.proto == "ssl":
  138. self.proto = socket.SOCK_STREAM
  139. self.ssl = True
  140. elif self.proto == "udp":
  141. self.proto = socket.SOCK_DGRAM
  142. else:
  143. raise sex.error("INVALID PROTOCOL SPECIFIED: %s" % self.proto)
  144. # import settings if they exist.
  145. self.import_file()
  146. # create a root node. we do this because we need to start fuzzing from a single point and the user may want
  147. # to specify a number of initial requests.
  148. self.root = pgraph.node()
  149. self.root.name = "__ROOT_NODE__"
  150. self.root.label = self.root.name
  151. self.last_recv = None
  152. self.add_node(self.root)
  153. ####################################################################################################################
  154. def add_node (self, node):
  155. '''
  156. Add a pgraph node to the graph. We overload this routine to automatically generate and assign an ID whenever a
  157. node is added.
  158. @type node: pGRAPH Node
  159. @param node: Node to add to session graph
  160. '''
  161. node.number = len(self.nodes)
  162. node.id = len(self.nodes)
  163. if not self.nodes.has_key(node.id):
  164. self.nodes[node.id] = node
  165. return self
  166. ####################################################################################################################
  167. def add_target (self, target):
  168. '''
  169. Add a target to the session. Multiple targets can be added for parallel fuzzing.
  170. @type target: session.target
  171. @param target: Target to add to session
  172. '''
  173. # pass specified target parameters to the PED-RPC server.
  174. target.pedrpc_connect()
  175. # add target to internal list.
  176. self.targets.append(target)
  177. ####################################################################################################################
  178. def connect (self, src, dst=None, callback=None):
  179. '''
  180. Create a connection between the two requests (nodes) and register an optional callback to process in between
  181. transmissions of the source and destination request. Leverage this functionality to handle situations such as
  182. challenge response systems. The session class maintains a top level node that all initial requests must be
  183. connected to. Example::
  184. sess = sessions.session()
  185. sess.connect(sess.root, s_get("HTTP"))
  186. If given only a single parameter, sess.connect() will default to attaching the supplied node to the root node.
  187. This is a convenient alias and is identical to the second line from the above example::
  188. sess.connect(s_get("HTTP"))
  189. If you register callback method, it must follow this prototype::
  190. def callback(session, node, edge, sock)
  191. Where node is the node about to be sent, edge is the last edge along the current fuzz path to "node", session
  192. is a pointer to the session instance which is useful for snagging data such as sesson.last_recv which contains
  193. the data returned from the last socket transmission and sock is the live socket. A callback is also useful in
  194. situations where, for example, the size of the next packet is specified in the first packet. As another
  195. example, if you need to fill in the dynamic IP address of the target register a callback that snags the IP
  196. from sock.getpeername()[0].
  197. @type src: String or Request (Node)
  198. @param src: Source request name or request node
  199. @type dst: String or Request (Node)
  200. @param dst: Destination request name or request node
  201. @type callback: Function
  202. @param callback: (Optional, def=None) Callback function to pass received data to between node xmits
  203. @rtype: pgraph.edge
  204. @return: The edge between the src and dst.
  205. '''
  206. # if only a source was provided, then make it the destination and set the source to the root node.
  207. if not dst:
  208. dst = src
  209. src = self.root
  210. # if source or destination is a name, resolve the actual node.
  211. if type(src) is str:
  212. src = self.find_node("name", src)
  213. if type(dst) is str:
  214. dst = self.find_node("name", dst)
  215. # if source or destination is not in the graph, add it.
  216. if src != self.root and not self.find_node("name", src.name):
  217. self.add_node(src)
  218. if not self.find_node("name", dst.name):
  219. self.add_node(dst)
  220. # create an edge between the two nodes and add it to the graph.
  221. edge = connection(src.id, dst.id, callback)
  222. self.add_edge(edge)
  223. return edge
  224. ####################################################################################################################
  225. def export_file (self):
  226. '''
  227. Dump various object values to disk.
  228. @see: import_file()
  229. '''
  230. if not self.session_filename:
  231. return
  232. data = {}
  233. data["session_filename"] = self.session_filename
  234. data["skip"] = self.total_mutant_index
  235. data["sleep_time"] = self.sleep_time
  236. data["restart_sleep_time"] = self.restart_sleep_time
  237. data["log_level"] = self.log_level
  238. data["proto"] = self.proto
  239. data["restart_interval"] = self.restart_interval
  240. data["timeout"] = self.timeout
  241. data["web_port"] = self.web_port
  242. data["crash_threshold"] = self.crash_threshold
  243. data["total_num_mutations"] = self.total_num_mutations
  244. data["total_mutant_index"] = self.total_mutant_index
  245. data["netmon_results"] = self.netmon_results
  246. data["procmon_results"] = self.procmon_results
  247. data["pause_flag"] = self.pause_flag
  248. fh = open(self.session_filename, "wb+")
  249. fh.write(zlib.compress(cPickle.dumps(data, protocol=2)))
  250. fh.close()
  251. ####################################################################################################################
  252. def fuzz (self, this_node=None, path=[]):
  253. '''
  254. Call this routine to get the ball rolling. No arguments are necessary as they are both utilized internally
  255. during the recursive traversal of the session graph.
  256. @type this_node: request (node)
  257. @param this_node: (Optional, def=None) Current node that is being fuzzed.
  258. @type path: List
  259. @param path: (Optional, def=[]) Nodes along the path to the current one being fuzzed.
  260. '''
  261. # if no node is specified, then we start from the root node and initialize the session.
  262. if not this_node:
  263. # we can't fuzz if we don't have at least one target and one request.
  264. if not self.targets:
  265. raise sex.error("NO TARGETS SPECIFIED IN SESSION")
  266. if not self.edges_from(self.root.id):
  267. raise sex.error("NO REQUESTS SPECIFIED IN SESSION")
  268. this_node = self.root
  269. try: self.server_init()
  270. except: return
  271. # XXX - TODO - complete parallel fuzzing, will likely have to thread out each target
  272. target = self.targets[0]
  273. # step through every edge from the current node.
  274. for edge in self.edges_from(this_node.id):
  275. # the destination node is the one actually being fuzzed.
  276. self.fuzz_node = self.nodes[edge.dst]
  277. num_mutations = self.fuzz_node.num_mutations()
  278. # keep track of the path as we fuzz through it, don't count the root node.
  279. # we keep track of edges as opposed to nodes because if there is more then one path through a set of
  280. # given nodes we don't want any ambiguity.
  281. path.append(edge)
  282. current_path = " -> ".join([self.nodes[e.src].name for e in path[1:]])
  283. current_path += " -> %s" % self.fuzz_node.name
  284. self.log("current fuzz path: %s" % current_path, 2)
  285. self.log("fuzzed %d of %d total cases" % (self.total_mutant_index, self.total_num_mutations), 2)
  286. done_with_fuzz_node = False
  287. crash_count = 0
  288. # loop through all possible mutations of the fuzz node.
  289. while not done_with_fuzz_node:
  290. # if we need to pause, do so.
  291. self.pause()
  292. # if we have exhausted the mutations of the fuzz node, break out of the while(1).
  293. # note: when mutate() returns False, the node has been reverted to the default (valid) state.
  294. if not self.fuzz_node.mutate():
  295. self.log("all possible mutations for current fuzz node exhausted", 2)
  296. done_with_fuzz_node = True
  297. continue
  298. # make a record in the session that a mutation was made.
  299. self.total_mutant_index += 1
  300. # if we've hit the restart interval, restart the target.
  301. if self.restart_interval and self.total_mutant_index % self.restart_interval == 0:
  302. self.log("restart interval of %d reached" % self.restart_interval)
  303. self.restart_target(target)
  304. # exception error handling routine, print log message and restart target.
  305. def error_handler (e, msg, target, sock=None):
  306. if sock:
  307. sock.close()
  308. msg += "\nException caught: %s" % repr(e)
  309. msg += "\nRestarting target and trying again"
  310. self.log(msg)
  311. self.restart_target(target)
  312. # if we don't need to skip the current test case.
  313. if self.total_mutant_index > self.skip:
  314. self.log("fuzzing %d of %d" % (self.fuzz_node.mutant_index, num_mutations), 2)
  315. # attempt to complete a fuzz transmission. keep trying until we are successful, whenever a failure
  316. # occurs, restart the target.
  317. while 1:
  318. # instruct the debugger/sniffer that we are about to send a new fuzz.
  319. if target.procmon:
  320. try:
  321. target.procmon.pre_send(self.total_mutant_index)
  322. except Exception, e:
  323. error_handler(e, "failed on procmon.pre_send()", target)
  324. continue
  325. if target.netmon:
  326. try:
  327. target.netmon.pre_send(self.total_mutant_index)
  328. except Exception, e:
  329. error_handler(e, "failed on netmon.pre_send()", target)
  330. continue
  331. try:
  332. # establish a connection to the target.
  333. sock = socket.socket(socket.AF_INET, self.proto)
  334. except Exception, e:
  335. error_handler(e, "failed creating socket", target)
  336. continue
  337. if self.bind:
  338. try:
  339. sock.bind(self.bind)
  340. except Exception, e:
  341. error_handler(e, "failed binding on socket", target, sock)
  342. continue
  343. try:
  344. sock.settimeout(self.timeout)
  345. sock.connect((target.host, target.port))
  346. except Exception, e:
  347. error_handler(e, "failed connecting on socket", target, sock)
  348. continue
  349. # if SSL is requested, then enable it.
  350. if self.ssl:
  351. try:
  352. ssl = socket.ssl(sock)
  353. sock = httplib.FakeSocket(sock, ssl)
  354. except Exception, e:
  355. error_handler(e, "failed ssl setup", target, sock)
  356. continue
  357. # if the user registered a pre-send function, pass it the sock and let it do the deed.
  358. try:
  359. self.pre_send(sock)
  360. except Exception, e:
  361. error_handler(e, "pre_send() failed", target, sock)
  362. continue
  363. # send out valid requests for each node in the current path up to the node we are fuzzing.
  364. try:
  365. for e in path[:-1]:
  366. node = self.nodes[e.dst]
  367. self.transmit(sock, node, e, target)
  368. except Exception, e:
  369. error_handler(e, "failed transmitting a node up the path", target, sock)
  370. continue
  371. # now send the current node we are fuzzing.
  372. try:
  373. self.transmit(sock, self.fuzz_node, edge, target)
  374. except Exception, e:
  375. error_handler(e, "failed transmitting fuzz node", target, sock)
  376. continue
  377. # if we reach this point the send was successful for break out of the while(1).
  378. break
  379. # if the user registered a post-send function, pass it the sock and let it do the deed.
  380. # we do this outside the try/except loop because if our fuzz causes a crash then the post_send()
  381. # will likely fail and we don't want to sit in an endless loop.
  382. try:
  383. self.post_send(sock)
  384. except Exception, e:
  385. error_handler(e, "post_send() failed", target, sock)
  386. # done with the socket.
  387. sock.close()
  388. # delay in between test cases.
  389. self.log("sleeping for %f seconds" % self.sleep_time, 5)
  390. time.sleep(self.sleep_time)
  391. # poll the PED-RPC endpoints (netmon, procmon etc...) for the target.
  392. self.poll_pedrpc(target)
  393. # serialize the current session state to disk.
  394. self.export_file()
  395. # recursively fuzz the remainder of the nodes in the session graph.
  396. self.fuzz(self.fuzz_node, path)
  397. # finished with the last node on the path, pop it off the path stack.
  398. if path:
  399. path.pop()
  400. # loop to keep the main thread running and be able to receive signals
  401. if self.signal_module:
  402. # wait for a signal only if fuzzing is finished (this function is recursive)
  403. # if fuzzing is not finished, web interface thread will catch it
  404. if self.total_mutant_index == self.total_num_mutations:
  405. import signal
  406. while True:
  407. signal.pause()
  408. ####################################################################################################################
  409. def import_file (self):
  410. '''
  411. Load varous object values from disk.
  412. @see: export_file()
  413. '''
  414. try:
  415. fh = open(self.session_filename, "rb")
  416. data = cPickle.loads(zlib.decompress(fh.read()))
  417. fh.close()
  418. except:
  419. return
  420. # update the skip variable to pick up fuzzing from last test case.
  421. self.skip = data["total_mutant_index"]
  422. self.session_filename = data["session_filename"]
  423. self.sleep_time = data["sleep_time"]
  424. self.restart_sleep_time = data["restart_sleep_time"]
  425. self.log_level = data["log_level"]
  426. self.proto = data["proto"]
  427. self.restart_interval = data["restart_interval"]
  428. self.timeout = data["timeout"]
  429. self.web_port = data["web_port"]
  430. self.crash_threshold = data["crash_threshold"]
  431. self.total_num_mutations = data["total_num_mutations"]
  432. self.total_mutant_index = data["total_mutant_index"]
  433. self.netmon_results = data["netmon_results"]
  434. self.procmon_results = data["procmon_results"]
  435. self.pause_flag = data["pause_flag"]
  436. ####################################################################################################################
  437. def log (self, msg, level=1):
  438. '''
  439. If the supplied message falls under the current log level, print the specified message to screen.
  440. @type msg: String
  441. @param msg: Message to log
  442. '''
  443. if self.log_level >= level:
  444. print "[%s] %s" % (time.strftime("%I:%M.%S"), msg)
  445. ####################################################################################################################
  446. def num_mutations (self, this_node=None, path=[]):
  447. '''
  448. Number of total mutations in the graph. The logic of this routine is identical to that of fuzz(). See fuzz()
  449. for inline comments. The member varialbe self.total_num_mutations is updated appropriately by this routine.
  450. @type this_node: request (node)
  451. @param this_node: (Optional, def=None) Current node that is being fuzzed.
  452. @type path: List
  453. @param path: (Optional, def=[]) Nodes along the path to the current one being fuzzed.
  454. @rtype: Integer
  455. @return: Total number of mutations in this session.
  456. '''
  457. if not this_node:
  458. this_node = self.root
  459. self.total_num_mutations = 0
  460. for edge in self.edges_from(this_node.id):
  461. next_node = self.nodes[edge.dst]
  462. self.total_num_mutations += next_node.num_mutations()
  463. if edge.src != self.root.id:
  464. path.append(edge)
  465. self.num_mutations(next_node, path)
  466. # finished with the last node on the path, pop it off the path stack.
  467. if path:
  468. path.pop()
  469. return self.total_num_mutations
  470. ####################################################################################################################
  471. def pause (self):
  472. '''
  473. If thet pause flag is raised, enter an endless loop until it is lowered.
  474. '''
  475. while 1:
  476. if self.pause_flag:
  477. time.sleep(1)
  478. else:
  479. break
  480. ####################################################################################################################
  481. def poll_pedrpc (self, target):
  482. '''
  483. Poll the PED-RPC endpoints (netmon, procmon etc...) for the target.
  484. @type target: session.target
  485. @param target: Session target whose PED-RPC services we are polling
  486. '''
  487. # kill the pcap thread and see how many bytes the sniffer recorded.
  488. if target.netmon:
  489. bytes = target.netmon.post_send()
  490. self.log("netmon captured %d bytes for test case #%d" % (bytes, self.total_mutant_index), 2)
  491. self.netmon_results[self.total_mutant_index] = bytes
  492. # check if our fuzz crashed the target. procmon.post_send() returns False if the target access violated.
  493. if target.procmon and not target.procmon.post_send():
  494. self.log("procmon detected access violation on test case #%d" % self.total_mutant_index)
  495. # retrieve the primitive that caused the crash and increment it's individual crash count.
  496. self.crashing_primitives[self.fuzz_node.mutant] = self.crashing_primitives.get(self.fuzz_node.mutant, 0) + 1
  497. # notify with as much information as possible.
  498. if not self.fuzz_node.mutant.name: msg = "primitive lacks a name, "
  499. else: msg = "primitive name: %s, " % self.fuzz_node.mutant.name
  500. msg += "type: %s, default value: %s" % (self.fuzz_node.mutant.s_type, self.fuzz_node.mutant.original_value)
  501. self.log(msg)
  502. # print crash synopsis
  503. self.procmon_results[self.total_mutant_index] = target.procmon.get_crash_synopsis()
  504. self.log(self.procmon_results[self.total_mutant_index].split("\n")[0], 2)
  505. # if the user-supplied crash threshold is reached, exhaust this node.
  506. if self.crashing_primitives[self.fuzz_node.mutant] >= self.crash_threshold:
  507. # as long as we're not a group and not a repeat.
  508. if not isinstance(self.fuzz_node.mutant, primitives.group):
  509. if not isinstance(self.fuzz_node.mutant, blocks.repeat):
  510. skipped = self.fuzz_node.mutant.exhaust()
  511. self.log("crash threshold reached for this primitive, exhausting %d mutants." % skipped)
  512. self.total_mutant_index += skipped
  513. self.fuzz_node.mutant_index += skipped
  514. # start the target back up.
  515. self.restart_target(target, stop_first=False)
  516. ####################################################################################################################
  517. def post_send (self, sock):
  518. '''
  519. Overload or replace this routine to specify actions to run after to each fuzz request. The order of events is
  520. as follows::
  521. pre_send() - req - callback ... req - callback - post_send()
  522. When fuzzing RPC for example, register this method to tear down the RPC request.
  523. @see: pre_send()
  524. @type sock: Socket
  525. @param sock: Connected socket to target
  526. '''
  527. # default to doing nothing.
  528. pass
  529. ####################################################################################################################
  530. def pre_send (self, sock):
  531. '''
  532. Overload or replace this routine to specify actions to run prior to each fuzz request. The order of events is
  533. as follows::
  534. pre_send() - req - callback ... req - callback - post_send()
  535. When fuzzing RPC for example, register this method to establish the RPC bind.
  536. @see: pre_send()
  537. @type sock: Socket
  538. @param sock: Connected socket to target
  539. '''
  540. # default to doing nothing.
  541. pass
  542. ####################################################################################################################
  543. def restart_target (self, target, stop_first=True):
  544. '''
  545. Restart the fuzz target. If a VMControl is available revert the snapshot, if a process monitor is available
  546. restart the target process. Otherwise, do nothing.
  547. @type target: session.target
  548. @param target: Target we are restarting
  549. '''
  550. # vm restarting is the preferred method so try that first.
  551. if target.vmcontrol:
  552. self.log("restarting target virtual machine")
  553. target.vmcontrol.restart_target()
  554. # if we have a connected process monitor, restart the target process.
  555. elif target.procmon:
  556. self.log("restarting target process")
  557. if stop_first:
  558. target.procmon.stop_target()
  559. target.procmon.start_target()
  560. # give the process a few seconds to settle in.
  561. time.sleep(3)
  562. # otherwise all we can do is wait a while for the target to recover on its own.
  563. else:
  564. self.log("no vmcontrol or procmon channel available ... sleeping for %d seconds" % self.restart_sleep_time)
  565. time.sleep(self.restart_sleep_time)
  566. # pass specified target parameters to the PED-RPC server to re-establish connections.
  567. target.pedrpc_connect()
  568. ####################################################################################################################
  569. def server_init (self):
  570. '''
  571. Called by fuzz() on first run (not on recursive re-entry) to initialize variables, web interface, etc...
  572. '''
  573. self.total_mutant_index = 0
  574. self.total_num_mutations = self.num_mutations()
  575. # web interface thread doesn't catch KeyboardInterrupt
  576. # add a signal handler, and exit on SIGINT
  577. # XXX - should wait for the end of the ongoing test case, and stop gracefully netmon and procmon
  578. # - doesn't work on OS where the signal module isn't available
  579. try:
  580. import signal
  581. self.signal_module = True
  582. except:
  583. self.signal_module = False
  584. if self.signal_module:
  585. def exit_abruptly(signal, frame):
  586. '''Save current settings (just in case) and exit'''
  587. self.export_file()
  588. self.log("SIGINT received ... exiting")
  589. sys.exit(0)
  590. signal.signal(signal.SIGINT, exit_abruptly)
  591. # spawn the web interface.
  592. t = web_interface_thread(self)
  593. t.start()
  594. ####################################################################################################################
  595. def transmit (self, sock, node, edge, target):
  596. '''
  597. Render and transmit a node, process callbacks accordingly.
  598. @type sock: Socket
  599. @param sock: Socket to transmit node on
  600. @type node: Request (Node)
  601. @param node: Request/Node to transmit
  602. @type edge: Connection (pgraph.edge)
  603. @param edge: Edge along the current fuzz path from "node" to next node.
  604. @type target: session.target
  605. @param target: Target we are transmitting to
  606. '''
  607. data = None
  608. # if the edge has a callback, process it. the callback has the option to render the node, modify it and return.
  609. if edge.callback:
  610. data = edge.callback(self, node, edge, sock)
  611. self.log("xmitting: [%d.%d]" % (node.id, self.total_mutant_index), level=2)
  612. # if no data was returned by the callback, render the node here.
  613. if not data:
  614. data = node.render()
  615. # if data length is > 65507 and proto is UDP, truncate it.
  616. # XXX - this logic does not prevent duplicate test cases, need to address this in the future.
  617. if self.proto == socket.SOCK_DGRAM:
  618. # max UDP packet size.
  619. # XXX - anyone know how to determine this value smarter?
  620. MAX_UDP = 65507
  621. if os.name != "nt" and os.uname()[0] == "Darwin":
  622. MAX_UDP = 9216
  623. if len(data) > MAX_UDP:
  624. self.log("Too much data for UDP, truncating to %d bytes" % MAX_UDP)
  625. data = data[:MAX_UDP]
  626. try:
  627. sock.send(data)
  628. except Exception, inst:
  629. self.log("Socket error, send: %s" % inst[1])
  630. if self.proto == socket.SOCK_STREAM or socket.SOCK_DGRAM:
  631. # XXX - might have a need to increase this at some point. (possibly make it a class parameter)
  632. try:
  633. self.last_recv = sock.recv(10000)
  634. except Exception, e:
  635. self.log("Nothing received on socket.", 5)
  636. self.last_recv = ""
  637. else:
  638. self.last_recv = ""
  639. if len(self.last_recv) > 0:
  640. self.log("received: [%d] %s" % (len(self.last_recv), self.last_recv), level=10)
  641. ########################################################################################################################
  642. class web_interface_handler (BaseHTTPServer.BaseHTTPRequestHandler):
  643. def __init__(self, request, client_address, server):
  644. BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request, client_address, server)
  645. self.session = None
  646. def commify (self, number):
  647. number = str(number)
  648. processing = 1
  649. regex = re.compile(r"^(-?\d+)(\d{3})")
  650. while processing:
  651. (number, processing) = regex.subn(r"\1,\2",number)
  652. return number
  653. def do_GET (self):
  654. self.do_everything()
  655. def do_HEAD (self):
  656. self.do_everything()
  657. def do_POST (self):
  658. self.do_everything()
  659. def do_everything (self):
  660. if "pause" in self.path:
  661. self.session.pause_flag = True
  662. if "resume" in self.path:
  663. self.session.pause_flag = False
  664. self.send_response(200)
  665. self.send_header('Content-type', 'text/html')
  666. self.end_headers()
  667. if "view_crash" in self.path:
  668. response = self.view_crash(self.path)
  669. elif "view_pcap" in self.path:
  670. response = self.view_pcap(self.path)
  671. else:
  672. response = self.view_index()
  673. self.wfile.write(response)
  674. def log_error (self, *args, **kwargs):
  675. pass
  676. def log_message (self, *args, **kwargs):
  677. pass
  678. def version_string (self):
  679. return "Sulley Fuzz Session"
  680. def view_crash (self, path):
  681. test_number = int(path.split("/")[-1])
  682. return "<html><pre>%s</pre></html>" % self.session.procmon_results[test_number]
  683. def view_pcap (self, path):
  684. return path
  685. def view_index (self):
  686. response = """
  687. <html>
  688. <head>
  689. <title>Sulley Fuzz Control</title>
  690. <style>
  691. a:link {color: #FF8200; text-decoration: none;}
  692. a:visited {color: #FF8200; text-decoration: none;}
  693. a:hover {color: #C5C5C5; text-decoration: none;}
  694. body
  695. {
  696. background-color: #000000;
  697. font-family: Arial, Helvetica, sans-serif;
  698. font-size: 12px;
  699. color: #FFFFFF;
  700. }
  701. td
  702. {
  703. font-family: Arial, Helvetica, sans-serif;
  704. font-size: 12px;
  705. color: #A0B0B0;
  706. }
  707. .fixed
  708. {
  709. font-family: Courier New;
  710. font-size: 12px;
  711. color: #A0B0B0;
  712. }
  713. .input
  714. {
  715. font-family: Arial, Helvetica, sans-serif;
  716. font-size: 11px;
  717. color: #FFFFFF;
  718. background-color: #333333;
  719. border: thin none;
  720. height: 20px;
  721. }
  722. </style>
  723. </head>
  724. <body>
  725. <center>
  726. <table border=0 cellpadding=5 cellspacing=0 width=750><tr><td>
  727. <!-- begin bounding table -->
  728. <table border=0 cellpadding=5 cellspacing=0 width="100%%">
  729. <tr bgcolor="#333333">
  730. <td><div style="font-size: 20px;">Sulley Fuzz Control</div></td>
  731. <td align=right><div style="font-weight: bold; font-size: 20px;">%(status)s</div></td>
  732. </tr>
  733. <tr bgcolor="#111111">
  734. <td colspan=2 align="center">
  735. <table border=0 cellpadding=0 cellspacing=5>
  736. <tr bgcolor="#111111">
  737. <td><b>Total:</b></td>
  738. <td>%(total_mutant_index)s</td>
  739. <td>of</td>
  740. <td>%(total_num_mutations)s</td>
  741. <td class="fixed">%(progress_total_bar)s</td>
  742. <td>%(progress_total)s</td>
  743. </tr>
  744. <tr bgcolor="#111111">
  745. <td><b>%(current_name)s:</b></td>
  746. <td>%(current_mutant_index)s</td>
  747. <td>of</td>
  748. <td>%(current_num_mutations)s</td>
  749. <td class="fixed">%(progress_current_bar)s</td>
  750. <td>%(progress_current)s</td>
  751. </tr>
  752. </table>
  753. </td>
  754. </tr>
  755. <tr>
  756. <td>
  757. <form method=get action="/pause">
  758. <input class="input" type="submit" value="Pause">
  759. </form>
  760. </td>
  761. <td align=right>
  762. <form method=get action="/resume">
  763. <input class="input" type="submit" value="Resume">
  764. </form>
  765. </td>
  766. </tr>
  767. </table>
  768. <!-- begin procmon results -->
  769. <table border=0 cellpadding=5 cellspacing=0 width="100%%">
  770. <tr bgcolor="#333333">
  771. <td nowrap>Test Case #</td>
  772. <td>Crash Synopsis</td>
  773. <td nowrap>Captured Bytes</td>
  774. </tr>
  775. """
  776. keys = self.session.procmon_results.keys()
  777. keys.sort()
  778. for key in keys:
  779. val = self.session.procmon_results[key]
  780. bytes = "&nbsp;"
  781. if self.session.netmon_results.has_key(key):
  782. bytes = self.commify(self.session.netmon_results[key])
  783. response += '<tr><td class="fixed"><a href="/view_crash/%d">%06d</a></td><td>%s</td><td align=right>%s</td></tr>' % (key, key, val.split("\n")[0], bytes)
  784. response += """
  785. <!-- end procmon results -->
  786. </table>
  787. <!-- end bounding table -->
  788. </td></tr></table>
  789. </center>
  790. </body>
  791. </html>
  792. """
  793. # what is the fuzzing status.
  794. if self.session.pause_flag:
  795. status = "<font color=red>PAUSED</font>"
  796. else:
  797. status = "<font color=green>RUNNING</font>"
  798. # if there is a current fuzz node.
  799. if self.session.fuzz_node:
  800. # which node (request) are we currently fuzzing.
  801. if self.session.fuzz_node.name:
  802. current_name = self.session.fuzz_node.name
  803. else:
  804. current_name = "[N/A]"
  805. # render sweet progress bars.
  806. progress_current = float(self.session.fuzz_node.mutant_index) / float(self.session.fuzz_node.num_mutations())
  807. num_bars = int(progress_current * 50)
  808. progress_current_bar = "[" + "=" * num_bars + "&nbsp;" * (50 - num_bars) + "]"
  809. progress_current = "%.3f%%" % (progress_current * 100)
  810. progress_total = float(self.session.total_mutant_index) / float(self.session.total_num_mutations)
  811. num_bars = int(progress_total * 50)
  812. progress_total_bar = "[" + "=" * num_bars + "&nbsp;" * (50 - num_bars) + "]"
  813. progress_total = "%.3f%%" % (progress_total * 100)
  814. response %= \
  815. {
  816. "current_mutant_index" : self.commify(self.session.fuzz_node.mutant_index),
  817. "current_name" : current_name,
  818. "current_num_mutations" : self.commify(self.session.fuzz_node.num_mutations()),
  819. "progress_current" : progress_current,
  820. "progress_current_bar" : progress_current_bar,
  821. "progress_total" : progress_total,
  822. "progress_total_bar" : progress_total_bar,
  823. "status" : status,
  824. "total_mutant_index" : self.commify(self.session.total_mutant_index),
  825. "total_num_mutations" : self.commify(self.session.total_num_mutations),
  826. }
  827. else:
  828. response %= \
  829. {
  830. "current_mutant_index" : "",
  831. "current_name" : "",
  832. "current_num_mutations" : "",
  833. "progress_current" : "",
  834. "progress_current_bar" : "",
  835. "progress_total" : "",
  836. "progress_total_bar" : "",
  837. "status" : "<font color=yellow>UNAVAILABLE</font>",
  838. "total_mutant_index" : "",
  839. "total_num_mutations" : "",
  840. }
  841. return response
  842. ########################################################################################################################
  843. class web_interface_server (BaseHTTPServer.HTTPServer):
  844. '''
  845. http://docs.python.org/lib/module-BaseHTTPServer.html
  846. '''
  847. def __init__(self, server_address, RequestHandlerClass, session):
  848. BaseHTTPServer.HTTPServer.__init__(self, server_address, RequestHandlerClass)
  849. self.RequestHandlerClass.session = session
  850. ########################################################################################################################
  851. class web_interface_thread (threading.Thread):
  852. def __init__ (self, session):
  853. threading.Thread.__init__(self)
  854. self.session = session
  855. self.server = None
  856. def run (self):
  857. self.server = web_interface_server(('', self.session.web_port), web_interface_handler, self.session)
  858. self.server.serve_forever()