· 6 years ago · Sep 25, 2019, 07:54 AM
1# PEDA - Python Exploit Development Assistance for GDB
2#
3# Copyright (C) 2012 Long Le Dinh <longld at vnsecurity.net>
4#
5# License: see LICENSE file for details
6#
7
8from __future__ import absolute_import
9from __future__ import division
10from __future__ import print_function
11
12import re
13import os
14import sys
15import shlex
16import string
17import time
18import signal
19import traceback
20import codecs
21
22# point to absolute path of peda.py
23PEDAFILE = os.path.abspath(os.path.expanduser(__file__))
24if os.path.islink(PEDAFILE):
25 PEDAFILE = os.readlink(PEDAFILE)
26sys.path.insert(0, os.path.dirname(PEDAFILE) + "/lib/")
27
28# Use six library to provide Python 2/3 compatibility
29import six
30from six.moves import range
31from six.moves import input
32try:
33 import six.moves.cPickle as pickle
34except ImportError:
35 import pickle
36
37
38
39from skeleton import *
40from shellcode import *
41from utils import *
42import config
43from nasm import *
44
45if sys.version_info.major is 3:
46 from urllib.request import urlopen
47 from urllib.parse import urlencode
48 pyversion = 3
49else:
50 from urllib import urlopen
51 from urllib import urlencode
52 pyversion = 2
53
54REGISTERS = {
55 8 : ["al", "ah", "bl", "bh", "cl", "ch", "dl", "dh"],
56 16: ["ax", "bx", "cx", "dx"],
57 32: ["eax", "ebx", "ecx", "edx", "esi", "edi", "ebp", "esp", "eip"],
58 64: ["rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp", "rip",
59 "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"]
60}
61
62###########################################################################
63class PEDA(object):
64 """
65 Class for actual functions of PEDA commands
66 """
67 def __init__(self):
68 self.SAVED_COMMANDS = {} # saved GDB user's commands
69
70
71 ####################################
72 # GDB Interaction / Misc Utils #
73 ####################################
74 def execute(self, gdb_command):
75 """
76 Wrapper for gdb.execute, catch the exception so it will not stop python script
77
78 Args:
79 - gdb_command (String)
80
81 Returns:
82 - True if execution succeed (Bool)
83 """
84 try:
85 gdb.execute(gdb_command)
86 return True
87 except Exception as e:
88 if config.Option.get("debug") == "on":
89 msg('Exception (%s): %s' % (gdb_command, e), "red")
90 traceback.print_exc()
91 return False
92
93 def execute_redirect(self, gdb_command, silent=False):
94 """
95 Execute a gdb command and capture its output
96
97 Args:
98 - gdb_command (String)
99 - silent: discard command's output, redirect to /dev/null (Bool)
100
101 Returns:
102 - output of command (String)
103 """
104 result = None
105 #init redirection
106 if silent:
107 logfd = open(os.path.devnull, "r+")
108 else:
109 logfd = tmpfile()
110 logname = logfd.name
111 gdb.execute('set logging off') # prevent nested call
112 gdb.execute('set height 0') # disable paging
113 gdb.execute('set logging file %s' % logname)
114 gdb.execute('set logging overwrite on')
115 gdb.execute('set logging redirect on')
116 gdb.execute('set logging on')
117 try:
118 gdb.execute(gdb_command)
119 gdb.flush()
120 gdb.execute('set logging off')
121 if not silent:
122 logfd.flush()
123 result = logfd.read()
124 logfd.close()
125 except Exception as e:
126 gdb.execute('set logging off') #to be sure
127 if config.Option.get("debug") == "on":
128 msg('Exception (%s): %s' % (gdb_command, e), "red")
129 traceback.print_exc()
130 logfd.close()
131 if config.Option.get("verbose") == "on":
132 msg(result)
133 return result
134
135 def parse_and_eval(self, exp):
136 """
137 Work around implementation for gdb.parse_and_eval with enhancements
138
139 Args:
140 - exp: expression to evaluate (String)
141
142 Returns:
143 - value of expression
144 """
145
146 regs = sum(REGISTERS.values(), [])
147 for r in regs:
148 if "$"+r not in exp and "e"+r not in exp and "r"+r not in exp:
149 exp = exp.replace(r, "$%s" % r)
150
151 p = re.compile("(.*)\[(.*)\]") # DWORD PTR [esi+eax*1]
152 matches = p.search(exp)
153 if not matches:
154 p = re.compile("(.*).s:(0x.*)") # DWORD PTR ds:0xdeadbeef
155 matches = p.search(exp)
156
157 if matches:
158 mod = "w"
159 if "BYTE" in matches.group(1):
160 mod = "b"
161 elif "QWORD" in matches.group(1):
162 mod = "g"
163 elif "DWORD" in matches.group(1):
164 mod = "w"
165 elif "WORD" in matches.group(1):
166 mod = "h"
167
168 out = self.execute_redirect("x/%sx %s" % (mod, matches.group(2)))
169 if not out:
170 return None
171 else:
172 return out.split(":\t")[-1].strip()
173
174 else:
175 out = self.execute_redirect("print %s" % exp)
176 if not out:
177 return None
178 else:
179 out = gdb.history(0).__str__()
180 out = out.encode('ascii', 'ignore')
181 out = decode_string_escape(out)
182 return out.strip()
183
184 def string_to_argv(self, str):
185 """
186 Convert a string to argv list, pre-processing register and variable values
187
188 Args:
189 - str: input string (String)
190
191 Returns:
192 - argv list (List)
193 """
194 try:
195 str = str.encode('ascii', 'ignore')
196 except:
197 pass
198 str = decode_string_escape(str)
199 args = shlex.split(str)
200 # need more processing here
201 for idx, a in enumerate(args):
202 a = a.strip(",")
203 if a.startswith("$"): # try to get register/variable value
204 v = self.parse_and_eval(a)
205 if v != None and v != "void":
206 if v.startswith("0x"): # int
207 args[idx] = v.split()[0] # workaround for 0xdeadbeef <symbol+x>
208 else: # string, complex data
209 args[idx] = v
210 elif a.startswith("+"): # relative value to prev arg
211 adder = to_int(self.parse_and_eval(a[1:]))
212 if adder is not None:
213 args[idx] = "%s" % to_hex(to_int(args[idx-1]) + adder)
214 elif is_math_exp(a):
215 try:
216 v = eval("%s" % a)
217 # XXX hack to avoid builtin functions/types
218 if not isinstance(v, six.string_types + six.integer_types):
219 continue
220 args[idx] = "%s" % (to_hex(v) if to_int(v) != None else v)
221 except:
222 pass
223 if config.Option.get("verbose") == "on":
224 msg(args)
225 return args
226
227
228 ################################
229 # GDB User-Defined Helpers #
230 ################################
231 def save_user_command(self, cmd):
232 """
233 Save user-defined command and deactivate it
234
235 Args:
236 - cmd: user-defined command (String)
237
238 Returns:
239 - True if success to save (Bool)
240 """
241 commands = self.execute_redirect("show user %s" % cmd)
242 if not commands:
243 return False
244
245 commands = "\n".join(commands.splitlines()[1:])
246 commands = "define %s\n" % cmd + commands + "end\n"
247 self.SAVED_COMMANDS[cmd] = commands
248 tmp = tmpfile()
249 tmp.write("define %s\nend\n" % cmd)
250 tmp.flush()
251 result = self.execute("source %s" % tmp.name)
252 tmp.close()
253 return result
254
255 def define_user_command(self, cmd, code):
256 """
257 Define a user-defined command, overwrite the old content
258
259 Args:
260 - cmd: user-defined command (String)
261 - code: gdb script code to append (String)
262
263 Returns:
264 - True if success to define (Bool)
265 """
266 commands = "define %s\n" % cmd + code + "\nend\n"
267 tmp = tmpfile(is_binary_file=False)
268 tmp.write(commands)
269 tmp.flush()
270 result = self.execute("source %s" % tmp.name)
271 tmp.close()
272 return result
273
274 def append_user_command(self, cmd, code):
275 """
276 Append code to a user-defined command, define new command if not exist
277
278 Args:
279 - cmd: user-defined command (String)
280 - code: gdb script code to append (String)
281
282 Returns:
283 - True if success to append (Bool)
284 """
285
286 commands = self.execute_redirect("show user %s" % cmd)
287 if not commands:
288 return self.define_user_command(cmd, code)
289 # else
290 commands = "\n".join(commands.splitlines()[1:])
291 if code in commands:
292 return True
293
294 commands = "define %s\n" % cmd + commands + code + "\nend\n"
295 tmp = tmpfile()
296 tmp.write(commands)
297 tmp.flush()
298 result = self.execute("source %s" % tmp.name)
299 tmp.close()
300 return result
301
302 def restore_user_command(self, cmd):
303 """
304 Restore saved user-defined command
305
306 Args:
307 - cmd: user-defined command (String)
308
309 Returns:
310 - True if success to restore (Bool)
311 """
312 if cmd == "all":
313 commands = "\n".join(self.SAVED_COMMANDS.values())
314 self.SAVED_COMMANDS = {}
315 else:
316 if cmd not in self.SAVED_COMMANDS:
317 return False
318 else:
319 commands = self.SAVED_COMMANDS[cmd]
320 self.SAVED_COMMANDS.pop(cmd)
321 tmp = tmpfile()
322 tmp.write(commands)
323 tmp.flush()
324 result = self.execute("source %s" % tmp.name)
325 tmp.close()
326
327 return result
328
329 def run_gdbscript_code(self, code):
330 """
331 Run basic gdbscript code as it is typed in interactively
332
333 Args:
334 - code: gdbscript code, lines are splitted by "\n" or ";" (String)
335
336 Returns:
337 - True if success to run (Bool)
338 """
339 tmp = tmpfile()
340 tmp.write(code.replace(";", "\n"))
341 tmp.flush()
342 result = self.execute("source %s" % tmp.name)
343 tmp.close()
344 return result
345
346 #########################
347 # Debugging Helpers #
348 #########################
349 @memoized
350 def is_target_remote(self):
351 """
352 Check if current target is remote
353
354 Returns:
355 - True if target is remote (Bool)
356 """
357 out = self.execute_redirect("info program")
358 if out and "serial line" in out: # remote target
359 return True
360
361 return False
362
363 @memoized
364 def getfile(self):
365 """
366 Get exec file of debugged program
367
368 Returns:
369 - full path to executable file (String)
370 """
371 result = None
372 out = self.execute_redirect('info files')
373 if out and '"' in out:
374 p = re.compile(".*exec file:\s*`(.*)'")
375 m = p.search(out)
376 if m:
377 result = m.group(1)
378 else: # stripped file, get symbol file
379 p = re.compile("Symbols from \"([^\"]*)")
380 m = p.search(out)
381 if m:
382 result = m.group(1)
383
384 return result
385
386 def get_status(self):
387 """
388 Get execution status of debugged program
389
390 Returns:
391 - current status of program (String)
392 STOPPED - not being run
393 BREAKPOINT - breakpoint hit
394 SIGXXX - stopped by signal XXX
395 UNKNOWN - unknown, not implemented
396 """
397 status = "UNKNOWN"
398 out = self.execute_redirect("info program")
399 for line in out.splitlines():
400 if line.startswith("It stopped"):
401 if "signal" in line: # stopped by signal
402 status = line.split("signal")[1].split(",")[0].strip()
403 break
404 if "breakpoint" in line: # breakpoint hit
405 status = "BREAKPOINT"
406 break
407 if "not being run" in line:
408 status = "STOPPED"
409 break
410 return status
411
412 @memoized
413 def getpid(self):
414 """
415 Get PID of the debugged process
416
417 Returns:
418 - pid (Int)
419 """
420
421 out = None
422 status = self.get_status()
423 if not status or status == "STOPPED":
424 return None
425
426 if self.is_target_remote(): # remote target
427 ctx = config.Option.get("context")
428 config.Option.set("context", None)
429 try:
430 out = self.execute_redirect("call getpid()")
431 except:
432 pass
433
434 config.Option.set("context", ctx)
435
436 if out is None:
437 return None
438 else:
439 out = self.execute_redirect("print $")
440 if out:
441 return to_int(out.split("=")[1])
442 else:
443 return None
444
445 pid = gdb.selected_inferior().pid
446 return int(pid) if pid else None
447
448 def getos(self):
449 """
450 Get running OS info
451
452 Returns:
453 - os version (String)
454 """
455 # TODO: get remote os by calling uname()
456 return os.uname()[0]
457
458 @memoized
459 def getarch(self):
460 """
461 Get architecture of debugged program
462
463 Returns:
464 - tuple of architecture info (arch (String), bits (Int))
465 """
466 arch = "unknown"
467 bits = 32
468 out = self.execute_redirect('maintenance info sections ?').splitlines()
469 for line in out:
470 if "file type" in line:
471 arch = line.split()[-1][:-1]
472 break
473 if "64" in arch:
474 bits = 64
475 return (arch, bits)
476
477 def intsize(self):
478 """
479 Get dword size of debugged program
480
481 Returns:
482 - size (Int)
483 + intsize = 4/8 for 32/64-bits arch
484 """
485
486 (arch, bits) = self.getarch()
487 return bits // 8
488
489 def getregs(self, reglist=None):
490 """
491 Get value of some or all registers
492
493 Returns:
494 - dictionary of {regname(String) : value(Int)}
495 """
496 if reglist:
497 reglist = reglist.replace(",", " ")
498 else:
499 reglist = ""
500 regs = self.execute_redirect("info registers %s" % reglist)
501 if not regs:
502 return None
503
504 result = {}
505 if regs:
506 for r in regs.splitlines():
507 r = r.split()
508 if len(r) > 1 and to_int(r[1]) is not None:
509 result[r[0]] = to_int(r[1])
510
511 return result
512
513 def getreg(self, register):
514 """
515 Get value of a specific register
516
517 Args:
518 - register: register name (String)
519
520 Returns:
521 - register value (Int)
522 """
523 r = register.lower()
524 regs = self.execute_redirect("info registers %s" % r)
525 if regs:
526 regs = regs.splitlines()
527 if len(regs) > 1:
528 return None
529 else:
530 result = to_int(regs[0].split()[1])
531 return result
532
533 return None
534
535 def set_breakpoint(self, location, temp=0, hard=0):
536 """
537 Wrapper for GDB break command
538 - location: target function or address (String ot Int)
539
540 Returns:
541 - True if can set breakpoint
542 """
543 cmd = "break"
544 if hard:
545 cmd = "h" + cmd
546 if temp:
547 cmd = "t" + cmd
548
549 if to_int(location) is not None:
550 return peda.execute("%s *0x%x" % (cmd, to_int(location)))
551 else:
552 return peda.execute("%s %s" % (cmd, location))
553
554 def get_breakpoint(self, num):
555 """
556 Get info of a specific breakpoint
557 TODO: support catchpoint, watchpoint
558
559 Args:
560 - num: breakpoint number
561
562 Returns:
563 - tuple (Num(Int), Type(String), Disp(Bool), Enb(Bool), Address(Int), What(String), commands(String))
564 """
565 out = self.execute_redirect("info breakpoints %d" % num)
566 if not out or "No breakpoint" in out:
567 return None
568
569 lines = out.splitlines()[1:]
570 # breakpoint regex
571 p = re.compile("^(\d*)\s*(.*breakpoint)\s*(keep|del)\s*(y|n)\s*(0x[^ ]*)\s*(.*)")
572 m = p.match(lines[0])
573 if not m:
574 # catchpoint/watchpoint regex
575 p = re.compile("^(\d*)\s*(.*point)\s*(keep|del)\s*(y|n)\s*(.*)")
576 m = p.match(lines[0])
577 if not m:
578 return None
579 else:
580 (num, type, disp, enb, what) = m.groups()
581 addr = ''
582 else:
583 (num, type, disp, enb, addr, what) = m.groups()
584
585 disp = True if disp == "keep" else False
586 enb = True if enb == "y" else False
587 addr = to_int(addr)
588 m = re.match("in.*at(.*:\d*)", what)
589 if m:
590 what = m.group(1)
591 else:
592 if addr: # breakpoint
593 what = ""
594
595 commands = ""
596 if len(lines) > 1:
597 for line in lines[1:]:
598 if "already hit" in line: continue
599 commands += line + "\n"
600
601 return (num, type, disp, enb, addr, what, commands.rstrip())
602
603 def get_breakpoints(self):
604 """
605 Get list of current breakpoints
606
607 Returns:
608 - list of tuple (Num(Int), Type(String), Disp(Bool), Nnb(Bool), Address(Int), commands(String))
609 """
610 result = []
611 out = self.execute_redirect("info breakpoints")
612 if not out:
613 return []
614
615 bplist = []
616 for line in out.splitlines():
617 m = re.match("^(\d*).*", line)
618 if m and to_int(m.group(1)):
619 bplist += [to_int(m.group(1))]
620
621 for num in bplist:
622 r = self.get_breakpoint(num)
623 if r:
624 result += [r]
625 return result
626
627 def save_breakpoints(self, filename):
628 """
629 Save current breakpoints to file as a script
630
631 Args:
632 - filename: target file (String)
633
634 Returns:
635 - True if success to save (Bool)
636 """
637 # use built-in command for gdb 7.2+
638 result = self.execute_redirect("save breakpoints %s" % filename)
639 if result == '':
640 return True
641
642 bplist = self.get_breakpoints()
643 if not bplist:
644 return False
645
646 try:
647 fd = open(filename, "w")
648 for (num, type, disp, enb, addr, what, commands) in bplist:
649 m = re.match("(.*)point", type)
650 if m:
651 cmd = m.group(1).split()[-1]
652 else:
653 cmd = "break"
654 if "hw" in type and cmd == "break":
655 cmd = "h" + cmd
656 if "read" in type:
657 cmd = "r" + cmd
658 if "acc" in type:
659 cmd = "a" + cmd
660
661 if not disp:
662 cmd = "t" + cmd
663 if what:
664 location = what
665 else:
666 location = "*0x%x" % addr
667 text = "%s %s" % (cmd, location)
668 if commands:
669 if "stop only" not in commands:
670 text += "\ncommands\n%s\nend" % commands
671 else:
672 text += commands.split("stop only", 1)[1]
673 fd.write(text + "\n")
674 fd.close()
675 return True
676 except:
677 return False
678
679 def get_config_filename(self, name):
680 filename = peda.getfile()
681 if not filename:
682 filename = peda.getpid()
683 if not filename:
684 filename = 'unknown'
685
686 filename = os.path.basename("%s" % filename)
687 tmpl_name = config.Option.get(name)
688 if tmpl_name:
689 return tmpl_name.replace("#FILENAME#", filename)
690 else:
691 return "peda-%s-%s" % (name, filename)
692
693 def save_session(self, filename=None):
694 """
695 Save current working gdb session to file as a script
696
697 Args:
698 - filename: target file (String)
699
700 Returns:
701 - True if success to save (Bool)
702 """
703 session = ""
704 if not filename:
705 filename = self.get_config_filename("session")
706
707 # exec-wrapper
708 out = self.execute_redirect("show exec-wrapper")
709 wrapper = out.split('"')[1]
710 if wrapper:
711 session += "set exec-wrapper %s\n" % wrapper
712
713 try:
714 # save breakpoints
715 self.save_breakpoints(filename)
716 fd = open(filename, "a+")
717 fd.write("\n" + session)
718 fd.close()
719 return True
720 except:
721 return False
722
723 def restore_session(self, filename=None):
724 """
725 Restore previous saved working gdb session from file
726
727 Args:
728 - filename: source file (String)
729
730 Returns:
731 - True if success to restore (Bool)
732 """
733 if not filename:
734 filename = self.get_config_filename("session")
735
736 # temporarily save and clear breakpoints
737 tmp = tmpfile()
738 self.save_breakpoints(tmp.name)
739 self.execute("delete")
740 result = self.execute("source %s" % filename)
741 if not result:
742 self.execute("source %s" % tmp.name)
743 tmp.close()
744 return result
745
746 @memoized
747 def assemble(self, asmcode, bits=None):
748 """
749 Assemble ASM instructions using NASM
750 - asmcode: input ASM instructions, multiple instructions are separated by ";" (String)
751
752 Returns:
753 - bin code (raw bytes)
754 """
755 if bits is None:
756 (arch, bits) = self.getarch()
757 return Nasm.assemble(asmcode, bits)
758
759 def disassemble(self, *arg):
760 """
761 Wrapper for disassemble command
762 - arg: args for disassemble command
763
764 Returns:
765 - text code (String)
766 """
767 code = ""
768 modif = ""
769 arg = list(arg)
770 if len(arg) > 1:
771 if "/" in arg[0]:
772 modif = arg[0]
773 arg = arg[1:]
774 if len(arg) == 1 and to_int(arg[0]) != None:
775 arg += [to_hex(to_int(arg[0]) + 32)]
776
777 self.execute("set disassembly-flavor intel")
778 out = self.execute_redirect("disassemble %s %s" % (modif, ",".join(arg)))
779 if not out:
780 return None
781 else:
782 code = out
783
784 return code
785
786 @memoized
787 def prev_inst(self, address, count=1):
788 """
789 Get previous instructions at an address
790
791 Args:
792 - address: address to get previous instruction (Int)
793 - count: number of instructions to read (Int)
794
795 Returns:
796 - list of tuple (address(Int), code(String))
797 """
798 result = []
799 backward = 64+16*count
800 for i in range(backward):
801 if self.getpid() and not self.is_address(address-backward+i):
802 continue
803
804 code = self.execute_redirect("disassemble %s, %s" % (to_hex(address-backward+i), to_hex(address+1)))
805 if code and ("%x" % address) in code:
806 lines = code.strip().splitlines()[1:-1]
807 if len(lines) > count and "(bad)" not in " ".join(lines):
808 for line in lines[-count-1:-1]:
809 (addr, code) = line.split(":", 1)
810 addr = re.search("(0x[^ ]*)", addr).group(1)
811 result += [(to_int(addr), code)]
812 return result
813 return None
814
815 @memoized
816 def current_inst(self, address):
817 """
818 Parse instruction at an address
819
820 Args:
821 - address: address to get next instruction (Int)
822
823 Returns:
824 - tuple of (address(Int), code(String))
825 """
826 out = self.execute_redirect("x/i 0x%x" % address)
827 if not out:
828 return None
829
830 (addr, code) = out.split(":", 1)
831 addr = re.search("(0x[^ ]*)", addr).group(1)
832 addr = to_int(addr)
833 code = code.strip()
834
835 return (addr, code)
836
837 @memoized
838 def next_inst(self, address, count=1):
839 """
840 Get next instructions at an address
841
842 Args:
843 - address: address to get next instruction (Int)
844 - count: number of instructions to read (Int)
845
846 Returns:
847 - - list of tuple (address(Int), code(String))
848 """
849 result = []
850 code = self.execute_redirect("x/%di 0x%x" % (count+1, address))
851 if not code:
852 return None
853
854 lines = code.strip().splitlines()
855 for i in range(1, count+1):
856 (addr, code) = lines[i].split(":", 1)
857 addr = re.search("(0x[^ ]*)", addr).group(1)
858 result += [(to_int(addr), code)]
859 return result
860
861 @memoized
862 def disassemble_around(self, address, count=8):
863 """
864 Disassemble instructions nearby current PC or an address
865
866 Args:
867 - address: start address to disassemble around (Int)
868 - count: number of instructions to disassemble
869
870 Returns:
871 - text code (String)
872 """
873 count = min(count, 256)
874 pc = address
875 if pc is None:
876 return None
877
878 # check if address is reachable
879 if not self.execute_redirect("x/x 0x%x" % pc):
880 return None
881
882 prev_code = self.prev_inst(pc, count//2-1)
883 if prev_code:
884 start = prev_code[0][0]
885 else:
886 start = pc
887 if start == pc:
888 count = count//2
889
890 code = self.execute_redirect("x/%di 0x%x" % (count, start))
891 if "0x%x" % pc not in code:
892 code = self.execute_redirect("x/%di 0x%x" % (count//2, pc))
893
894 return code.rstrip()
895
896 @memoized
897 def xrefs(self, search="", filename=None):
898 """
899 Search for all call references or data access to a function/variable
900
901 Args:
902 - search: function or variable to search for (String)
903 - filename: binary/library to search (String)
904
905 Returns:
906 - list of tuple (address(Int), asm instruction(String))
907 """
908 result = []
909 if not filename:
910 filename = self.getfile()
911
912 if not filename:
913 return None
914 vmap = self.get_vmmap(filename)
915 elfbase = vmap[0][0] if vmap else 0
916
917 if to_int(search) is not None:
918 search = "%x" % to_int(search)
919
920 search_data = 1
921 if search == "":
922 search_data = 0
923
924 out = execute_external_command("%s -M intel -z --prefix-address -d '%s' | grep '%s'" % (config.OBJDUMP, filename, search))
925
926 for line in out.splitlines():
927 if not line: continue
928 addr = to_int("0x" + line.split()[0].strip())
929 if not addr: continue
930
931 # update with runtime values
932 if addr < elfbase:
933 addr += elfbase
934 out = self.execute_redirect("x/i 0x%x" % addr)
935 if out:
936 line = out
937 p = re.compile("\s*(0x[^ ]*).*?:\s*([^ ]*)\s*(.*)")
938 else:
939 p = re.compile("(.*?)\s*<.*?>\s*([^ ]*)\s*(.*)")
940
941 m = p.search(line)
942 if m:
943 (address, opcode, opers) = m.groups()
944 if "call" in opcode and search in opers:
945 result += [(addr, line.strip())]
946 if search_data:
947 if "mov" in opcode and search in opers:
948 result += [(addr, line.strip())]
949
950 return result
951
952 def _get_function_args_32(self, code, argc=None):
953 """
954 Guess the number of arguments passed to a function - i386
955 """
956 if not argc:
957 argc = 0
958 p = re.compile(".*mov.*\[esp(.*)\],")
959 matches = p.findall(code)
960 if matches:
961 l = len(matches)
962 for v in matches:
963 if v.startswith("+"):
964 offset = to_int(v[1:])
965 if offset is not None and (offset//4) > l:
966 continue
967 argc += 1
968 else: # try with push style
969 argc = code.count("push")
970
971 argc = min(argc, 6)
972 if argc == 0:
973 return []
974
975 args = []
976 sp = self.getreg("sp")
977 mem = self.dumpmem(sp, sp+4*argc)
978 for i in range(argc):
979 args += [struct.unpack("<L", mem[i*4:(i+1)*4])[0]]
980
981 return args
982
983 def _get_function_args_64(self, code, argc=None):
984 """
985 Guess the number of arguments passed to a function - x86_64
986 """
987
988 # just retrieve max 6 args
989 arg_order = ["rdi", "rsi", "rdx", "rcx", "r8", "r9"]
990 p = re.compile(":\s*([^ ]*)\s*(.*),")
991 matches = p.findall(code)
992 regs = [r for (_, r) in matches]
993 p = re.compile(("di|si|dx|cx|r8|r9"))
994 m = p.findall(" ".join(regs))
995 m = list(set(m)) # uniqify
996 argc = 0
997 if "si" in m and "di" not in m: # dirty fix
998 argc += 1
999 argc += m.count("di")
1000 if argc > 0:
1001 argc += m.count("si")
1002 if argc > 1:
1003 argc += m.count("dx")
1004 if argc > 2:
1005 argc += m.count("cx")
1006 if argc > 3:
1007 argc += m.count("r8")
1008 if argc > 4:
1009 argc += m.count("r9")
1010
1011 if argc == 0:
1012 return []
1013
1014 args = []
1015 regs = self.getregs()
1016 for i in range(argc):
1017 args += [regs[arg_order[i]]]
1018
1019 return args
1020
1021 def get_function_args(self, argc=None):
1022 """
1023 Get the guessed arguments passed to a function when stopped at a call instruction
1024
1025 Args:
1026 - argc: force to get specific number of arguments (Int)
1027
1028 Returns:
1029 - list of arguments (List)
1030 """
1031
1032 args = []
1033 regs = self.getregs()
1034 if regs is None:
1035 return []
1036
1037 (arch, bits) = self.getarch()
1038 pc = self.getreg("pc")
1039 prev_insts = self.prev_inst(pc, 12)
1040
1041 code = ""
1042 if not prev_insts:
1043 return []
1044
1045 for (addr, inst) in prev_insts[::-1]:
1046 if "call" in inst.strip().split()[0]:
1047 break
1048 code = "0x%x:%s\n" % (addr, inst) + code
1049
1050 if "i386" in arch:
1051 args = self._get_function_args_32(code, argc)
1052 if "64" in arch:
1053 args = self._get_function_args_64(code, argc)
1054
1055 return args
1056
1057 @memoized
1058 def backtrace_depth(self, sp=None):
1059 """
1060 Get number of frames in backtrace
1061
1062 Args:
1063 - sp: stack pointer address, for caching (Int)
1064
1065 Returns:
1066 - depth: number of frames (Int)
1067 """
1068 backtrace = self.execute_redirect("backtrace")
1069 return backtrace.count("#")
1070
1071 def stepuntil(self, inst, mapname=None, depth=None):
1072 """
1073 Step execution until next "inst" instruction within a specific memory range
1074
1075 Args:
1076 - inst: the instruction to reach (String)
1077 - mapname: name of virtual memory region to check for the instruction (String)
1078 - depth: backtrace depth (Int)
1079
1080 Returns:
1081 - tuple of (depth, instruction)
1082 + depth: current backtrace depth (Int)
1083 + instruction: current instruction (String)
1084 """
1085
1086 if not self.getpid():
1087 return None
1088
1089 maxdepth = to_int(config.Option.get("tracedepth"))
1090 if not maxdepth:
1091 maxdepth = 0xffffffff
1092
1093 maps = self.get_vmmap()
1094 binname = self.getfile()
1095 if mapname is None:
1096 mapname = binname
1097 mapname = mapname.replace(" ", "").split(",") + [binname]
1098 targetmap = []
1099 for m in mapname:
1100 targetmap += self.get_vmmap(m)
1101 binmap = self.get_vmmap("binary")
1102
1103 current_instruction = ""
1104 pc = self.getreg("pc")
1105
1106 if depth is None:
1107 current_depth = self.backtrace_depth(self.getreg("sp"))
1108 else:
1109 current_depth = depth
1110 old_status = self.get_status()
1111
1112 while True:
1113 status = self.get_status()
1114 if status != old_status:
1115 if "SIG" in status and status[3:] not in ["TRAP"] and not to_int(status[3:]): # ignore TRAP and numbered signals
1116 current_instruction = "Interrupted: %s" % status
1117 call_depth = current_depth
1118 break
1119 if "STOP" in status:
1120 current_instruction = "End of execution"
1121 call_depth = current_depth
1122 break
1123
1124 call_depth = self.backtrace_depth(self.getreg("sp"))
1125 current_instruction = self.execute_redirect("x/i $pc")
1126 if not current_instruction:
1127 current_instruction = "End of execution"
1128 break
1129
1130 p = re.compile(".*?(0x[^ :]*)")
1131 addr = p.search(current_instruction).group(1)
1132 addr = to_int(addr)
1133 if addr is None:
1134 break
1135
1136 #p = re.compile(".*?:\s*([^ ]*)")
1137 p = re.compile(".*?:\s*(.*)")
1138 code = p.match(current_instruction).group(1)
1139 found = 0
1140 for i in inst.replace(",", " ").split():
1141 if re.match(i.strip(), code.strip()):
1142 if self.is_address(addr, targetmap) and addr != pc:
1143 found = 1
1144 break
1145 if found != 0:
1146 break
1147 self.execute_redirect("stepi", silent=True)
1148 if not self.is_address(addr, targetmap) or call_depth > maxdepth:
1149 self.execute_redirect("finish", silent=True)
1150 pc = 0
1151
1152 return (call_depth - current_depth, current_instruction.strip())
1153
1154 def get_eflags(self):
1155 """
1156 Get flags value from EFLAGS register
1157
1158 Returns:
1159 - dictionary of named flags
1160 """
1161
1162 # Eflags bit masks, source vdb
1163 EFLAGS_CF = 1 << 0
1164 EFLAGS_PF = 1 << 2
1165 EFLAGS_AF = 1 << 4
1166 EFLAGS_ZF = 1 << 6
1167 EFLAGS_SF = 1 << 7
1168 EFLAGS_TF = 1 << 8
1169 EFLAGS_IF = 1 << 9
1170 EFLAGS_DF = 1 << 10
1171 EFLAGS_OF = 1 << 11
1172
1173 flags = {"CF":0, "PF":0, "AF":0, "ZF":0, "SF":0, "TF":0, "IF":0, "DF":0, "OF":0}
1174 eflags = self.getreg("eflags")
1175 if not eflags:
1176 return None
1177 flags["CF"] = bool(eflags & EFLAGS_CF)
1178 flags["PF"] = bool(eflags & EFLAGS_PF)
1179 flags["AF"] = bool(eflags & EFLAGS_AF)
1180 flags["ZF"] = bool(eflags & EFLAGS_ZF)
1181 flags["SF"] = bool(eflags & EFLAGS_SF)
1182 flags["TF"] = bool(eflags & EFLAGS_TF)
1183 flags["IF"] = bool(eflags & EFLAGS_IF)
1184 flags["DF"] = bool(eflags & EFLAGS_DF)
1185 flags["OF"] = bool(eflags & EFLAGS_OF)
1186
1187 return flags
1188
1189 def set_eflags(self, flagname, value):
1190 """
1191 Set/clear/toggle value of a flag register
1192
1193 Returns:
1194 - True if success (Bool)
1195 """
1196
1197 # Eflags bit masks, source vdb
1198 EFLAGS_CF = 1 << 0
1199 EFLAGS_PF = 1 << 2
1200 EFLAGS_AF = 1 << 4
1201 EFLAGS_ZF = 1 << 6
1202 EFLAGS_SF = 1 << 7
1203 EFLAGS_TF = 1 << 8
1204 EFLAGS_IF = 1 << 9
1205 EFLAGS_DF = 1 << 10
1206 EFLAGS_OF = 1 << 11
1207
1208 flags = {"carry": "CF", "parity": "PF", "adjust": "AF", "zero": "ZF", "sign": "SF",
1209 "trap": "TF", "interrupt": "IF", "direction": "DF", "overflow": "OF"}
1210
1211 flagname = flagname.lower()
1212
1213 if flagname not in flags:
1214 return False
1215
1216 eflags = self.get_eflags()
1217 if not eflags:
1218 return False
1219
1220 # If value doesn't match the current, or we want to toggle, toggle
1221 if value is None or eflags[flags[flagname]] != value:
1222 reg_eflags = self.getreg("eflags")
1223 reg_eflags ^= eval("EFLAGS_%s" % flags[flagname])
1224 result = self.execute("set $eflags = 0x%x" % reg_eflags)
1225 return result
1226
1227 return True
1228
1229 def eval_target(self, inst):
1230 """
1231 Evaluate target address of an instruction, used for jumpto decision
1232
1233 Args:
1234 - inst: AMS instruction text (String)
1235
1236 Returns:
1237 - target address (Int)
1238 """
1239
1240 target = None
1241 inst = inst.strip()
1242 opcode = inst.split(":\t")[-1].split()[0]
1243 # this regex includes x86_64 RIP relateive address reference
1244 p = re.compile(".*?:\s*[^ ]*\s*(.* PTR ).*(0x[^ ]*)")
1245 m = p.search(inst)
1246 if not m:
1247 p = re.compile(".*?:\s.*(0x[^ ]*)")
1248 m = p.search(inst)
1249 if m:
1250 target = m.group(1)
1251 else:
1252 target = None
1253 else:
1254 if "]" in m.group(2): # e.g DWORD PTR [ebx+0xc]
1255 p = re.compile(".*?:\s*[^ ]*\s*(.* PTR ).*\[(.*)\]")
1256 m = p.search(inst)
1257 target = self.parse_and_eval("%s[%s]" % (m.group(1), m.group(2).strip()))
1258
1259 return to_int(target)
1260
1261 def testjump(self, inst=None):
1262 """
1263 Test if jump instruction is taken or not
1264
1265 Returns:
1266 - (status, address of target jumped instruction)
1267 """
1268
1269 flags = self.get_eflags()
1270 if not flags:
1271 return None
1272
1273 if not inst:
1274 pc = self.getreg("pc")
1275 inst = self.execute_redirect("x/i 0x%x" % pc)
1276 if not inst:
1277 return None
1278
1279 opcode = inst.split(":\t")[-1].split()[0]
1280 next_addr = self.eval_target(inst)
1281 if next_addr is None:
1282 next_addr = 0
1283
1284 if opcode == "jmp":
1285 return next_addr
1286 if opcode == "je" and flags["ZF"]:
1287 return next_addr
1288 if opcode == "jne" and not flags["ZF"]:
1289 return next_addr
1290 if opcode == "jg" and not flags["ZF"] and (flags["SF"] == flags["OF"]):
1291 return next_addr
1292 if opcode == "jge" and (flags["SF"] == flags["OF"]):
1293 return next_addr
1294 if opcode == "ja" and not flags["CF"] and not flags["ZF"]:
1295 return next_addr
1296 if opcode == "jae" and not flags["CF"]:
1297 return next_addr
1298 if opcode == "jl" and (flags["SF"] != flags["OF"]):
1299 return next_addr
1300 if opcode == "jle" and (flags["ZF"] or (flags["SF"] != flags["OF"])):
1301 return next_addr
1302 if opcode == "jb" and flags["CF"]:
1303 return next_addr
1304 if opcode == "jbe" and (flags["CF"] or flags["ZF"]):
1305 return next_addr
1306 if opcode == "jo" and flags["OF"]:
1307 return next_addr
1308 if opcode == "jno" and not flags["OF"]:
1309 return next_addr
1310 if opcode == "jz" and flags["ZF"]:
1311 return next_addr
1312 if opcode == "jnz" and flags["OF"]:
1313 return next_addr
1314
1315 return None
1316
1317 def take_snapshot(self):
1318 """
1319 Take a snapshot of current process
1320 Warning: this is not thread safe, do not use with multithread program
1321
1322 Returns:
1323 - dictionary of snapshot data
1324 """
1325 if not self.getpid():
1326 return None
1327
1328 maps = self.get_vmmap()
1329 if not maps:
1330 return None
1331
1332 snapshot = {}
1333 # get registers
1334 snapshot["reg"] = self.getregs()
1335 # get writable memory regions
1336 snapshot["mem"] = {}
1337 for (start, end, perm, _) in maps:
1338 if "w" in perm:
1339 snapshot["mem"][start] = self.dumpmem(start, end)
1340
1341 return snapshot
1342
1343 def save_snapshot(self, filename=None):
1344 """
1345 Save a snapshot of current process to file
1346 Warning: this is not thread safe, do not use with multithread program
1347
1348 Args:
1349 - filename: target file to save snapshot
1350
1351 Returns:
1352 - Bool
1353 """
1354 if not filename:
1355 filename = self.get_config_filename("snapshot")
1356
1357 snapshot = self.take_snapshot()
1358 if not snapshot:
1359 return False
1360 # dump to file
1361 fd = open(filename, "wb")
1362 pickle.dump(snapshot, fd, pickle.HIGHEST_PROTOCOL)
1363 fd.close()
1364
1365 return True
1366
1367 def give_snapshot(self, snapshot):
1368 """
1369 Restore a saved snapshot of current process
1370 Warning: this is not thread safe, do not use with multithread program
1371
1372 Returns:
1373 - Bool
1374 """
1375 if not snapshot or not self.getpid():
1376 return False
1377
1378 # restore memory regions
1379 for (addr, buf) in snapshot["mem"].items():
1380 self.writemem(addr, buf)
1381
1382 # restore registers, SP will be the last one
1383 for (r, v) in snapshot["reg"].items():
1384 self.execute("set $%s = 0x%x" % (r, v))
1385 if r.endswith("sp"):
1386 sp = v
1387 self.execute("set $sp = 0x%x" % sp)
1388
1389 return True
1390
1391 def restore_snapshot(self, filename=None):
1392 """
1393 Restore a saved snapshot of current process from file
1394 Warning: this is not thread safe, do not use with multithread program
1395
1396 Args:
1397 - file: saved snapshot
1398
1399 Returns:
1400 - Bool
1401 """
1402 if not filename:
1403 filename = self.get_config_filename("snapshot")
1404
1405 fd = open(filename, "rb")
1406 snapshot = pickle.load(fd)
1407 return self.give_snapshot(snapshot)
1408
1409
1410 #########################
1411 # Memory Operations #
1412 #########################
1413 @memoized
1414 def get_vmmap(self, name=None):
1415 """
1416 Get virtual memory mapping address ranges of debugged process
1417
1418 Args:
1419 - name: name/address of binary/library to get mapping range (String)
1420 + name = "binary" means debugged program
1421 + name = "all" means all virtual maps
1422
1423 Returns:
1424 - list of virtual mapping ranges (start(Int), end(Int), permission(String), mapname(String))
1425
1426 """
1427 def _get_offline_maps():
1428 name = self.getfile()
1429 if not name:
1430 return None
1431 headers = self.elfheader()
1432 binmap = []
1433 hlist = [x for x in headers.items() if x[1][2] == 'code']
1434 hlist = sorted(hlist, key=lambda x:x[1][0])
1435 binmap += [(hlist[0][1][0], hlist[-1][1][1], "rx-p", name)]
1436
1437 hlist = [x for x in headers.items() if x[1][2] == 'rodata']
1438 hlist = sorted(hlist, key=lambda x:x[1][0])
1439 binmap += [(hlist[0][1][0], hlist[-1][1][1], "r--p", name)]
1440
1441 hlist = [x for x in headers.items() if x[1][2] == 'data']
1442 hlist = sorted(hlist, key=lambda x:x[1][0])
1443 binmap += [(hlist[0][1][0], hlist[-1][1][1], "rw-p", name)]
1444
1445 return binmap
1446
1447 def _get_allmaps_osx(pid, remote=False):
1448 maps = []
1449 #_DATA 00007fff77975000-00007fff77976000 [ 4K] rw-/rw- SM=COW /usr/lib/system/libremovefile.dylib
1450 pattern = re.compile("([^\n]*)\s* ([0-9a-f][^-\s]*)-([^\s]*) \[.*\]\s([^/]*).* (.*)")
1451
1452 if remote: # remote target, not yet supported
1453 return maps
1454 else: # local target
1455 try: out = execute_external_command("/usr/bin/vmmap -w %s" % self.getpid())
1456 except: error_msg("could not read vmmap of process")
1457
1458 matches = pattern.findall(out)
1459 if matches:
1460 for (name, start, end, perm, mapname) in matches:
1461 if name.startswith("Stack"):
1462 mapname = "[stack]"
1463 start = to_int("0x%s" % start)
1464 end = to_int("0x%s" % end)
1465 if mapname == "":
1466 mapname = name.strip()
1467 maps += [(start, end, perm, mapname)]
1468 return maps
1469
1470
1471 def _get_allmaps_freebsd(pid, remote=False):
1472 maps = []
1473 mpath = "/proc/%s/map" % pid
1474 # 0x8048000 0x8049000 1 0 0xc36afdd0 r-x 1 0 0x1000 COW NC vnode /path/to/file NCH -1
1475 pattern = re.compile("0x([0-9a-f]*) 0x([0-9a-f]*)(?: [^ ]*){3} ([rwx-]*)(?: [^ ]*){6} ([^ ]*)")
1476
1477 if remote: # remote target, not yet supported
1478 return maps
1479 else: # local target
1480 try: out = open(mpath).read()
1481 except: error_msg("could not open %s; is procfs mounted?" % mpath)
1482
1483 matches = pattern.findall(out)
1484 if matches:
1485 for (start, end, perm, mapname) in matches:
1486 if start[:2] in ["bf", "7f", "ff"] and "rw" in perm:
1487 mapname = "[stack]"
1488 start = to_int("0x%s" % start)
1489 end = to_int("0x%s" % end)
1490 if mapname == "-":
1491 if start == maps[-1][1] and maps[-1][-1][0] == "/":
1492 mapname = maps[-1][-1]
1493 else:
1494 mapname = "mapped"
1495 maps += [(start, end, perm, mapname)]
1496 return maps
1497
1498 def _get_allmaps_linux(pid, remote=False):
1499 maps = []
1500 mpath = "/proc/%s/maps" % pid
1501 #00400000-0040b000 r-xp 00000000 08:02 538840 /path/to/file
1502 pattern = re.compile("([0-9a-f]*)-([0-9a-f]*) ([rwxps-]*)(?: [^ ]*){3} *(.*)")
1503
1504 if remote: # remote target
1505 tmp = tmpfile()
1506 self.execute("remote get %s %s" % (mpath, tmp.name))
1507 tmp.seek(0)
1508 out = tmp.read()
1509 tmp.close()
1510 else: # local target
1511 out = open(mpath).read()
1512
1513 matches = pattern.findall(out)
1514 if matches:
1515 for (start, end, perm, mapname) in matches:
1516 start = to_int("0x%s" % start)
1517 end = to_int("0x%s" % end)
1518 if mapname == "":
1519 mapname = "mapped"
1520 maps += [(start, end, perm, mapname)]
1521 return maps
1522
1523 result = []
1524 pid = self.getpid()
1525 if not pid: # not running, try to use elfheader()
1526 try:
1527 return _get_offline_maps()
1528 except:
1529 return []
1530
1531 # retrieve all maps
1532 os = self.getos()
1533 rmt = self.is_target_remote()
1534 maps = []
1535 try:
1536 if os == "FreeBSD": maps = _get_allmaps_freebsd(pid, rmt)
1537 elif os == "Linux" : maps = _get_allmaps_linux(pid, rmt)
1538 elif os == "Darwin" : maps = _get_allmaps_osx(pid, rmt)
1539 except Exception as e:
1540 if config.Option.get("debug") == "on":
1541 msg("Exception: %s" %e)
1542 traceback.print_exc()
1543
1544 # select maps matched specific name
1545 if name == "binary":
1546 name = self.getfile()
1547 if name is None or name == "all":
1548 name = ""
1549
1550 if to_int(name) is None:
1551 for (start, end, perm, mapname) in maps:
1552 if name in mapname:
1553 result += [(start, end, perm, mapname)]
1554 else:
1555 addr = to_int(name)
1556 for (start, end, perm, mapname) in maps:
1557 if start <= addr and addr < end:
1558 result += [(start, end, perm, mapname)]
1559
1560 return result
1561
1562 @memoized
1563 def get_vmrange(self, address, maps=None):
1564 """
1565 Get virtual memory mapping range of an address
1566
1567 Args:
1568 - address: target address (Int)
1569 - maps: only find in provided maps (List)
1570
1571 Returns:
1572 - tuple of virtual memory info (start, end, perm, mapname)
1573 """
1574 if address is None:
1575 return None
1576 if maps is None:
1577 maps = self.get_vmmap()
1578 if maps:
1579 for (start, end, perm, mapname) in maps:
1580 if start <= address and end > address:
1581 return (start, end, perm, mapname)
1582 # failed to get the vmmap
1583 else:
1584 try:
1585 gdb.selected_inferior().read_memory(address, 1)
1586 start = address & 0xfffffffffffff000
1587 end = start + 0x1000
1588 return (start, end, 'rwx', 'unknown')
1589 except:
1590 return None
1591
1592
1593 @memoized
1594 def is_executable(self, address, maps=None):
1595 """
1596 Check if an address is executable
1597
1598 Args:
1599 - address: target address (Int)
1600 - maps: only check in provided maps (List)
1601
1602 Returns:
1603 - True if address belongs to an executable address range (Bool)
1604 """
1605 vmrange = self.get_vmrange(address, maps)
1606 if vmrange and "x" in vmrange[2]:
1607 return True
1608 else:
1609 return False
1610
1611 @memoized
1612 def is_writable(self, address, maps=None):
1613 """
1614 Check if an address is writable
1615
1616 Args:
1617 - address: target address (Int)
1618 - maps: only check in provided maps (List)
1619
1620 Returns:
1621 - True if address belongs to a writable address range (Bool)
1622 """
1623 vmrange = self.get_vmrange(address, maps)
1624 if vmrange and "w" in vmrange[2]:
1625 return True
1626 else:
1627 return False
1628
1629 @memoized
1630 def is_address(self, value, maps=None):
1631 """
1632 Check if a value is a valid address (belongs to a memory region)
1633
1634 Args:
1635 - value (Int)
1636 - maps: only check in provided maps (List)
1637
1638 Returns:
1639 - True if value belongs to an address range (Bool)
1640 """
1641 vmrange = self.get_vmrange(value, maps)
1642 return vmrange is not None
1643
1644 @memoized
1645 def get_disasm(self, address, count=1):
1646 """
1647 Get the ASM code of instruction at address
1648
1649 Args:
1650 - address: address to read instruction (Int)
1651 - count: number of code lines (Int)
1652
1653 Returns:
1654 - asm code (String)
1655 """
1656 code = self.execute_redirect("x/%di 0x%x" % (count, address))
1657 if code:
1658 return code.rstrip()
1659 else:
1660 return ""
1661
1662 def dumpmem(self, start, end):
1663 """
1664 Dump process memory from start to end
1665
1666 Args:
1667 - start: start address (Int)
1668 - end: end address (Int)
1669
1670 Returns:
1671 - memory content (raw bytes)
1672 """
1673 mem = None
1674 logfd = tmpfile(is_binary_file=True)
1675 logname = logfd.name
1676 out = self.execute_redirect("dump memory %s 0x%x 0x%x" % (logname, start, end))
1677 if out is None:
1678 return None
1679 else:
1680 logfd.flush()
1681 mem = logfd.read()
1682 logfd.close()
1683
1684 return mem
1685
1686 def readmem(self, address, size):
1687 """
1688 Read content of memory at an address
1689
1690 Args:
1691 - address: start address to read (Int)
1692 - size: bytes to read (Int)
1693
1694 Returns:
1695 - memory content (raw bytes)
1696 """
1697 # try fast dumpmem if it works
1698 mem = self.dumpmem(address, address+size)
1699 if mem is not None:
1700 return mem
1701
1702 # failed to dump, use slow x/gx way
1703 mem = ""
1704 out = self.execute_redirect("x/%dbx 0x%x" % (size, address))
1705 if out:
1706 for line in out.splitlines():
1707 bytes = line.split(":\t")[-1].split()
1708 mem += "".join([chr(int(c, 0)) for c in bytes])
1709
1710 return mem
1711
1712 def read_int(self, address, intsize=None):
1713 """
1714 Read an interger value from memory
1715
1716 Args:
1717 - address: address to read (Int)
1718 - intsize: force read size (Int)
1719
1720 Returns:
1721 - mem value (Int)
1722 """
1723 if not intsize:
1724 intsize = self.intsize()
1725 value = self.readmem(address, intsize)
1726 if value:
1727 value = to_int("0x" + codecs.encode(value[::-1], 'hex'))
1728 return value
1729 else:
1730 return None
1731
1732
1733 def read_long(self, address):
1734 """
1735 Read a long long value from memory
1736
1737 Args:
1738 - address: address to read (Int)
1739
1740 Returns:
1741 - mem value (Long Long)
1742 """
1743 return self.read_int(address, 8)
1744
1745 def writemem(self, address, buf):
1746 """
1747 Write buf to memory start at an address
1748
1749 Args:
1750 - address: start address to write (Int)
1751 - buf: data to write (raw bytes)
1752
1753 Returns:
1754 - number of written bytes (Int)
1755 """
1756 out = None
1757 if not buf:
1758 return 0
1759
1760 if self.getpid():
1761 # try fast restore mem
1762 tmp = tmpfile(is_binary_file=True)
1763 tmp.write(buf)
1764 tmp.flush()
1765 out = self.execute_redirect("restore %s binary 0x%x" % (tmp.name, address))
1766 tmp.close()
1767 if not out: # try the slow way
1768 for i in range(len(buf)):
1769 if not self.execute("set {char}0x%x = 0x%x" % (address+i, ord(buf[i]))):
1770 return i
1771 return i+1
1772 elif "error" in out: # failed to write the whole buf, find written byte
1773 for i in range(0, len(buf), 1):
1774 if not self.is_address(address+i):
1775 return i
1776 else:
1777 return len(buf)
1778
1779 def write_int(self, address, value, intsize=None):
1780 """
1781 Write an interger value to memory
1782
1783 Args:
1784 - address: address to read (Int)
1785 - value: int to write to (Int)
1786 - intsize: force write size (Int)
1787
1788 Returns:
1789 - Bool
1790 """
1791 if not intsize:
1792 intsize = self.intsize()
1793 buf = hex2str(value, intsize).ljust(intsize, "\x00")[:intsize]
1794 saved = self.readmem(address, intsize)
1795 if not saved:
1796 return False
1797
1798 ret = self.writemem(address, buf)
1799 if ret != intsize:
1800 self.writemem(address, saved)
1801 return False
1802 return True
1803
1804 def write_long(self, address, value):
1805 """
1806 Write a long long value to memory
1807
1808 Args:
1809 - address: address to read (Int)
1810 - value: value to write to
1811
1812 Returns:
1813 - Bool
1814 """
1815 return self.write_int(address, value, 8)
1816
1817 def cmpmem(self, start, end, buf):
1818 """
1819 Compare contents of a memory region with a buffer
1820
1821 Args:
1822 - start: start address (Int)
1823 - end: end address (Int)
1824 - buf: raw bytes
1825
1826 Returns:
1827 - dictionary of array of diffed bytes in hex (Dictionary)
1828 {123: [("A", "B"), ("C", "C"))]}
1829 """
1830 line_len = 32
1831 if end < start:
1832 (start, end) = (end, start)
1833
1834 mem = self.dumpmem(start, end)
1835 if mem is None:
1836 return None
1837
1838 length = min(len(mem), len(buf))
1839 result = {}
1840 lineno = 0
1841 for i in range(length//line_len):
1842 diff = 0
1843 bytes_ = []
1844 for j in range(line_len):
1845 offset = i*line_len+j
1846 bytes_ += [(mem[offset:offset + 1], buf[offset:offset + 1])]
1847 if mem[offset] != buf[offset]:
1848 diff = 1
1849 if diff == 1:
1850 result[start+lineno] = bytes_
1851 lineno += line_len
1852
1853 bytes_ = []
1854 diff = 0
1855 for i in range(length % line_len):
1856 offset = lineno+i
1857 bytes_ += [(mem[offset:offset + 1], buf[offset:offset + 1])]
1858 if mem[offset] != buf[offset]:
1859 diff = 1
1860 if diff == 1:
1861 result[start+lineno] = bytes_
1862
1863 return result
1864
1865 def xormem(self, start, end, key):
1866 """
1867 XOR a memory region with a key
1868
1869 Args:
1870 - start: start address (Int)
1871 - end: end address (Int)
1872 - key: XOR key (String)
1873
1874 Returns:
1875 - xored memory content (raw bytes)
1876 """
1877 mem = self.dumpmem(start, end)
1878 if mem is None:
1879 return None
1880
1881 if to_int(key) != None:
1882 key = hex2str(to_int(key), self.intsize())
1883 mem = list(bytes_iterator(mem))
1884 for index, char in enumerate(mem):
1885 key_idx = index % len(key)
1886 mem[index] = chr(ord(char) ^ ord(key[key_idx]))
1887
1888 buf = b"".join([to_binary_string(x) for x in mem])
1889 bytes = self.writemem(start, buf)
1890 return buf
1891
1892 def searchmem(self, start, end, search, mem=None):
1893 """
1894 Search for all instances of a pattern in memory from start to end
1895
1896 Args:
1897 - start: start address (Int)
1898 - end: end address (Int)
1899 - search: string or python regex pattern (String)
1900 - mem: cached mem to not re-read for repeated searches (raw bytes)
1901
1902 Returns:
1903 - list of found result: (address(Int), hex encoded value(String))
1904
1905 """
1906
1907 result = []
1908 if end < start:
1909 (start, end) = (end, start)
1910
1911 if mem is None:
1912 mem = self.dumpmem(start, end)
1913
1914 if not mem:
1915 return result
1916
1917 if isinstance(search, six.string_types) and search.startswith("0x"):
1918 # hex number
1919 search = search[2:]
1920 if len(search) %2 != 0:
1921 search = "0" + search
1922 search = codecs.decode(search, 'hex')[::-1]
1923 search = re.escape(search)
1924
1925 # Convert search to bytes if is not already
1926 if not isinstance(search, bytes):
1927 search = search.encode('utf-8')
1928
1929 try:
1930 p = re.compile(search)
1931 except:
1932 search = re.escape(search)
1933 p = re.compile(search)
1934
1935 found = list(p.finditer(mem))
1936 for m in found:
1937 index = 1
1938 if m.start() == m.end() and m.lastindex:
1939 index = m.lastindex+1
1940 for i in range(0,index):
1941 if m.start(i) != m.end(i):
1942 result += [(start + m.start(i), codecs.encode(mem[m.start(i):m.end(i)], 'hex'))]
1943
1944 return result
1945
1946 def searchmem_by_range(self, mapname, search):
1947 """
1948 Search for all instances of a pattern in virtual memory ranges
1949
1950 Args:
1951 - search: string or python regex pattern (String)
1952 - mapname: name of virtual memory range (String)
1953
1954 Returns:
1955 - list of found result: (address(Int), hex encoded value(String))
1956 """
1957
1958 result = []
1959 ranges = self.get_vmmap(mapname)
1960 if ranges:
1961 for (start, end, perm, name) in ranges:
1962 if "r" in perm:
1963 result += self.searchmem(start, end, search)
1964
1965 return result
1966
1967 @memoized
1968 def search_reference(self, search, mapname=None):
1969 """
1970 Search for all references to a value in memory ranges
1971
1972 Args:
1973 - search: string or python regex pattern (String)
1974 - mapname: name of target virtual memory range (String)
1975
1976 Returns:
1977 - list of found result: (address(int), hex encoded value(String))
1978 """
1979
1980 maps = self.get_vmmap()
1981 ranges = self.get_vmmap(mapname)
1982 result = []
1983 search_result = []
1984 for (start, end, perm, name) in maps:
1985 if "r" in perm:
1986 search_result += self.searchmem(start, end, search)
1987
1988 for (start, end, perm, name) in ranges:
1989 for (a, v) in search_result:
1990 result += self.searchmem(start, end, to_address(a))
1991
1992 return result
1993
1994 @memoized
1995 def search_address(self, searchfor="stack", belongto="binary"):
1996 """
1997 Search for all valid addresses in memory ranges
1998
1999 Args:
2000 - searchfor: memory region to search for addresses (String)
2001 - belongto: memory region that target addresses belong to (String)
2002
2003 Returns:
2004 - list of found result: (address(Int), value(Int))
2005 """
2006
2007 result = []
2008 maps = self.get_vmmap()
2009 if maps is None:
2010 return result
2011
2012 searchfor_ranges = self.get_vmmap(searchfor)
2013 belongto_ranges = self.get_vmmap(belongto)
2014 step = self.intsize()
2015 for (start, end, _, _) in searchfor_ranges[::-1]: # dirty trick, to search in rw-p mem first
2016 mem = self.dumpmem(start, end)
2017 if not mem:
2018 continue
2019 for i in range(0, len(mem), step):
2020 search = "0x" + codecs.encode(mem[i:i+step][::-1], 'hex').decode('utf-8')
2021 addr = to_int(search)
2022 if self.is_address(addr, belongto_ranges):
2023 result += [(start+i, addr)]
2024
2025 return result
2026
2027 @memoized
2028 def search_pointer(self, searchfor="stack", belongto="binary"):
2029 """
2030 Search for all valid pointers in memory ranges
2031
2032 Args:
2033 - searchfor: memory region to search for pointers (String)
2034 - belongto: memory region that pointed addresses belong to (String)
2035
2036 Returns:
2037 - list of found result: (address(Int), value(Int))
2038 """
2039
2040 search_result = []
2041 result = []
2042 maps = self.get_vmmap()
2043 searchfor_ranges = self.get_vmmap(searchfor)
2044 belongto_ranges = self.get_vmmap(belongto)
2045 step = self.intsize()
2046 for (start, end, _, _) in searchfor_ranges[::-1]:
2047 mem = self.dumpmem(start, end)
2048 if not mem:
2049 continue
2050 for i in range(0, len(mem), step):
2051 search = "0x" + codecs.encode(mem[i:i+step][::-1], 'hex').decode('utf-8')
2052 addr = to_int(search)
2053 if self.is_address(addr):
2054 (v, t, vn) = self.examine_mem_value(addr)
2055 if t != 'value':
2056 if self.is_address(to_int(vn), belongto_ranges):
2057 if (to_int(v), v) not in search_result:
2058 search_result += [(to_int(v), v)]
2059
2060 for (a, v) in search_result:
2061 result += self.searchmem(start, end, to_address(a), mem)
2062
2063 return result
2064
2065 @memoized
2066 def examine_mem_value(self, value):
2067 """
2068 Examine a value in memory for its type and reference
2069
2070 Args:
2071 - value: value to examine (Int)
2072
2073 Returns:
2074 - tuple of (value(Int), type(String), next_value(Int))
2075 """
2076 def examine_data(value, bits=32):
2077 out = self.execute_redirect("x/%sx 0x%x" % ("g" if bits == 64 else "w", value))
2078 if out:
2079 v = out.split(":\t")[-1].strip()
2080 if is_printable(int2hexstr(to_int(v), bits//8)):
2081 out = self.execute_redirect("x/s 0x%x" % value)
2082 return out
2083
2084 result = (None, None, None)
2085 if value is None:
2086 return result
2087
2088 maps = self.get_vmmap()
2089 binmap = self.get_vmmap("binary")
2090
2091 (arch, bits) = self.getarch()
2092 if not self.is_address(value): # a value
2093 result = (to_hex(value), "value", "")
2094 return result
2095 else:
2096 (_, _, _, mapname) = self.get_vmrange(value)
2097
2098 # check for writable first so rwxp mem will be treated as data
2099 if self.is_writable(value): # writable data address
2100 out = examine_data(value, bits)
2101 if out:
2102 result = (to_hex(value), "data", out.split(":", 1)[1].strip())
2103
2104 elif self.is_executable(value): # code/rodata address
2105 if self.is_address(value, binmap):
2106 headers = self.elfheader()
2107 else:
2108 headers = self.elfheader_solib(mapname)
2109
2110 if headers:
2111 headers = sorted(headers.items(), key=lambda x: x[1][1])
2112 for (k, (start, end, type)) in headers:
2113 if value >= start and value < end:
2114 if type == "code":
2115 out = self.get_disasm(value)
2116 p = re.compile(".*?0x[^ ]*?\s(.*)")
2117 m = p.search(out)
2118 result = (to_hex(value), "code", m.group(1))
2119 else: # rodata address
2120 out = examine_data(value, bits)
2121 result = (to_hex(value), "rodata", out.split(":", 1)[1].strip())
2122 break
2123
2124 if result[0] is None: # not fall to any header section
2125 out = examine_data(value, bits)
2126 result = (to_hex(value), "rodata", out.split(":", 1)[1].strip())
2127
2128 else: # not belong to any lib: [heap], [vdso], [vsyscall], etc
2129 out = self.get_disasm(value)
2130 if "(bad)" in out:
2131 out = examine_data(value, bits)
2132 result = (to_hex(value), "rodata", out.split(":", 1)[1].strip())
2133 else:
2134 p = re.compile(".*?0x[^ ]*?\s(.*)")
2135 m = p.search(out)
2136 result = (to_hex(value), "code", m.group(1))
2137
2138 else: # readonly data address
2139 out = examine_data(value, bits)
2140 if out:
2141 result = (to_hex(value), "rodata", out.split(":", 1)[1].strip())
2142 else:
2143 result = (to_hex(value), "rodata", "MemError")
2144
2145 return result
2146
2147 @memoized
2148 def examine_mem_reference(self, value, depth=5):
2149 """
2150 Deeply examine a value in memory for its references
2151
2152 Args:
2153 - value: value to examine (Int)
2154
2155 Returns:
2156 - list of tuple of (value(Int), type(String), next_value(Int))
2157 """
2158 result = []
2159 if depth <= 0:
2160 depth = 0xffffffff
2161
2162 (v, t, vn) = self.examine_mem_value(value)
2163 while vn is not None:
2164 if len(result) > depth:
2165 _v, _t, _vn = result[-1]
2166 result[-1] = (_v, _t, "--> ...")
2167 break
2168
2169 result += [(v, t, vn)]
2170 if v == vn or to_int(v) == to_int(vn): # point to self
2171 break
2172 if to_int(vn) is None:
2173 break
2174 if to_int(vn) in [to_int(v) for (v, _, _) in result]: # point back to previous value
2175 break
2176 (v, t, vn) = self.examine_mem_value(to_int(vn))
2177
2178 return result
2179
2180 @memoized
2181 def format_search_result(self, result, display=256):
2182 """
2183 Format the result from various memory search commands
2184
2185 Args:
2186 - result: result of search commands (List)
2187 - display: number of items to display
2188
2189 Returns:
2190 - text: formatted text (String)
2191 """
2192
2193 text = ""
2194 if not result:
2195 text = "Not found"
2196 else:
2197 maxlen = 0
2198 maps = self.get_vmmap()
2199 shortmaps = []
2200 for (start, end, perm, name) in maps:
2201 shortname = os.path.basename(name)
2202 if shortname.startswith("lib"):
2203 shortname = shortname.split("-")[0]
2204 shortmaps += [(start, end, perm, shortname)]
2205
2206 count = len(result)
2207 if display != 0:
2208 count = min(count, display)
2209 text += "Found %d results, display max %d items:\n" % (len(result), count)
2210 for (addr, v) in result[:count]:
2211 vmrange = self.get_vmrange(addr, shortmaps)
2212 maxlen = max(maxlen, len(vmrange[3]))
2213
2214 for (addr, v) in result[:count]:
2215 vmrange = self.get_vmrange(addr, shortmaps)
2216 chain = self.examine_mem_reference(addr)
2217 text += "%s : %s" % (vmrange[3].rjust(maxlen), format_reference_chain(chain) + "\n")
2218
2219 return text
2220
2221
2222 ##########################
2223 # Exploit Helpers #
2224 ##########################
2225 @memoized
2226 def elfentry(self):
2227 """
2228 Get entry point address of debugged ELF file
2229
2230 Returns:
2231 - entry address (Int)
2232 """
2233 out = self.execute_redirect("info files")
2234 p = re.compile("Entry point: ([^\s]*)")
2235 if out:
2236 m = p.search(out)
2237 if m:
2238 return to_int(m.group(1))
2239 return None
2240
2241 @memoized
2242 def elfheader(self, name=None):
2243 """
2244 Get headers information of debugged ELF file
2245
2246 Args:
2247 - name: specific header name (String)
2248
2249 Returns:
2250 - dictionary of headers {name(String): (start(Int), end(Int), type(String))}
2251 """
2252 elfinfo = {}
2253 elfbase = 0
2254 if self.getpid():
2255 binmap = self.get_vmmap("binary")
2256 elfbase = binmap[0][0] if binmap else 0
2257
2258 out = self.execute_redirect("maintenance info sections")
2259 if not out:
2260 return {}
2261
2262 p = re.compile("\s*(0x[^-]*)->(0x[^ ]*) at (.*):\s*([^ ]*)\s*(.*)")
2263 matches = p.findall(out)
2264
2265 for (start, end, offset, hname, attr) in matches:
2266 start, end, offset = to_int(start), to_int(end), to_int(offset)
2267 # skip unuseful header
2268 if start < offset:
2269 continue
2270 # if PIE binary, update with runtime address
2271 if start < elfbase:
2272 start += elfbase
2273 end += elfbase
2274
2275 if "CODE" in attr:
2276 htype = "code"
2277 elif "READONLY" in attr:
2278 htype = "rodata"
2279 else:
2280 htype = "data"
2281
2282 elfinfo[hname.strip()] = (start, end, htype)
2283
2284 result = {}
2285 if name is None:
2286 result = elfinfo
2287 else:
2288 if name in elfinfo:
2289 result[name] = elfinfo[name]
2290 else:
2291 for (k, v) in elfinfo.items():
2292 if name in k:
2293 result[k] = v
2294 return result
2295
2296 @memoized
2297 def elfsymbols(self, pattern=None):
2298 """
2299 Get all non-debugging symbol information of debugged ELF file
2300
2301 Returns:
2302 - dictionary of (address(Int), symname(String))
2303 """
2304 headers = self.elfheader()
2305 if ".plt" not in headers: # static binary
2306 return {}
2307
2308 binmap = self.get_vmmap("binary")
2309 elfbase = binmap[0][0] if binmap else 0
2310
2311 # get the .dynstr header
2312 headers = self.elfheader()
2313 if ".dynstr" not in headers:
2314 return {}
2315 (start, end, _) = headers[".dynstr"]
2316 mem = self.dumpmem(start, end)
2317 if not mem and self.getfile():
2318 fd = open(self.getfile())
2319 fd.seek(start, 0)
2320 mem = fd.read(end-start)
2321 fd.close()
2322
2323 # Convert names into strings
2324 dynstrings = [name.decode('utf-8') for name in mem.split(b"\x00")]
2325
2326 if pattern:
2327 dynstrings = [s for s in dynstrings if re.search(pattern, s)]
2328
2329 # get symname@plt info
2330 symbols = {}
2331 for symname in dynstrings:
2332 if not symname: continue
2333 symname += "@plt"
2334 out = self.execute_redirect("info functions %s" % symname)
2335 if not out: continue
2336 m = re.findall(".*(0x[^ ]*)\s*%s" % re.escape(symname), out)
2337 for addr in m:
2338 addr = to_int(addr)
2339 if self.is_address(addr, binmap):
2340 if symname not in symbols:
2341 symbols[symname] = addr
2342 break
2343
2344 # if PIE binary, update with runtime address
2345 for (k, v) in symbols.items():
2346 if v < elfbase:
2347 symbols[k] = v + elfbase
2348
2349 return symbols
2350
2351 @memoized
2352 def elfsymbol(self, symname=None):
2353 """
2354 Get non-debugging symbol information of debugged ELF file
2355
2356 Args:
2357 - name: target function name (String), special cases:
2358 + "data": data transfer functions
2359 + "exec": exec helper functions
2360
2361 Returns:
2362 - if exact name is not provided: dictionary of tuple (symname, plt_entry)
2363 - if exact name is provided: dictionary of tuple (symname, plt_entry, got_entry, reloc_entry)
2364 """
2365 datafuncs = ["printf", "puts", "gets", "cpy"]
2366 execfuncs = ["system", "exec", "mprotect", "mmap", "syscall"]
2367 result = {}
2368 if not symname or symname in ["data", "exec"]:
2369 symbols = self.elfsymbols()
2370 else:
2371 symbols = self.elfsymbols(symname)
2372
2373 if not symname:
2374 result = symbols
2375 else:
2376 sname = symname.replace("@plt", "") + "@plt"
2377 if sname in symbols:
2378 plt_addr = symbols[sname]
2379 result[sname] = plt_addr # plt entry
2380 out = self.get_disasm(plt_addr, 2)
2381 for line in out.splitlines():
2382 if "jmp" in line:
2383 addr = to_int("0x" + line.strip().rsplit("0x")[-1].split()[0])
2384 result[sname.replace("@plt","@got")] = addr # got entry
2385 if "push" in line:
2386 addr = to_int("0x" + line.strip().rsplit("0x")[-1])
2387 result[sname.replace("@plt","@reloc")] = addr # reloc offset
2388 else:
2389 keywords = [symname]
2390 if symname == "data":
2391 keywords = datafuncs
2392 if symname == "exec":
2393 keywords = execfuncs
2394 for (k, v) in symbols.items():
2395 for f in keywords:
2396 if f in k:
2397 result[k] = v
2398
2399 return result
2400
2401 @memoized
2402 def main_entry(self):
2403 """
2404 Get address of main function of stripped ELF file
2405
2406 Returns:
2407 - main function address (Int)
2408 """
2409 refs = self.xrefs("__libc_start_main@plt")
2410 if refs:
2411 inst = self.prev_inst(refs[0][0])
2412 if inst:
2413 addr = re.search(".*(0x.*)", inst[0][1])
2414 if addr:
2415 return to_int(addr.group(1))
2416 return None
2417
2418 @memoized
2419 def readelf_header(self, filename, name=None):
2420 """
2421 Get headers information of an ELF file using 'readelf'
2422
2423 Args:
2424 - filename: ELF file (String)
2425 - name: specific header name (String)
2426
2427 Returns:
2428 - dictionary of headers (name(String), value(Int)) (Dict)
2429 """
2430 elfinfo = {}
2431 vmap = self.get_vmmap(filename)
2432 elfbase = vmap[0][0] if vmap else 0
2433 out = execute_external_command("%s -W -S %s" % (config.READELF, filename))
2434 if not out:
2435 return {}
2436 p = re.compile(".*\[.*\] (\.[^ ]*) [^0-9]* ([^ ]*) [^ ]* ([^ ]*)(.*)")
2437 matches = p.findall(out)
2438 if not matches:
2439 return result
2440
2441 for (hname, start, size, attr) in matches:
2442 start, end = to_int("0x"+start), to_int("0x"+start) + to_int("0x"+size)
2443 # if PIE binary or DSO, update with runtime address
2444 if start < elfbase:
2445 start += elfbase
2446 if end < elfbase:
2447 end += elfbase
2448
2449 if "X" in attr:
2450 htype = "code"
2451 elif "W" in attr:
2452 htype = "data"
2453 else:
2454 htype = "rodata"
2455 elfinfo[hname.strip()] = (start, end, htype)
2456
2457 result = {}
2458 if name is None:
2459 result = elfinfo
2460 else:
2461 if name in elfinfo:
2462 result[name] = elfinfo[name]
2463 else:
2464 for (k, v) in elfinfo.items():
2465 if name in k:
2466 result[k] = v
2467 return result
2468
2469 @memoized
2470 def elfheader_solib(self, solib=None, name=None):
2471 """
2472 Get headers information of Shared Object Libraries linked to target
2473
2474 Args:
2475 - solib: shared library name (String)
2476 - name: specific header name (String)
2477
2478 Returns:
2479 - dictionary of headers {name(String): start(Int), end(Int), type(String))
2480 """
2481 # hardcoded ELF header type
2482 header_type = {"code": [".text", ".fini", ".init", ".plt", "__libc_freeres_fn"],
2483 "data": [".dynamic", ".data", ".ctors", ".dtors", ".jrc", ".got", ".got.plt",
2484 ".bss", ".tdata", ".tbss", ".data.rel.ro", ".fini_array",
2485 "__libc_subfreeres", "__libc_thread_subfreeres"]
2486 }
2487
2488 @memoized
2489 def _elfheader_solib_all():
2490 out = self.execute_redirect("info files")
2491 if not out:
2492 return None
2493
2494 p = re.compile("[^\n]*\s*(0x[^ ]*) - (0x[^ ]*) is (\.[^ ]*) in (.*)")
2495 soheaders = p.findall(out)
2496
2497 result = []
2498 for (start, end, hname, libname) in soheaders:
2499 start, end = to_int(start), to_int(end)
2500 result += [(start, end, hname, os.path.realpath(libname))] # tricky, return the realpath version of libraries
2501 return result
2502
2503 elfinfo = {}
2504
2505 headers = _elfheader_solib_all()
2506 if not headers:
2507 return {}
2508
2509 if solib is None:
2510 return headers
2511
2512 vmap = self.get_vmmap(solib)
2513 elfbase = vmap[0][0] if vmap else 0
2514
2515 for (start, end, hname, libname) in headers:
2516 if solib in libname:
2517 # if PIE binary or DSO, update with runtime address
2518 if start < elfbase:
2519 start += elfbase
2520 if end < elfbase:
2521 end += elfbase
2522 # determine the type
2523 htype = "rodata"
2524 if hname in header_type["code"]:
2525 htype = "code"
2526 elif hname in header_type["data"]:
2527 htype = "data"
2528 elfinfo[hname.strip()] = (start, end, htype)
2529
2530 result = {}
2531 if name is None:
2532 result = elfinfo
2533 else:
2534 if name in elfinfo:
2535 result[name] = elfinfo[name]
2536 else:
2537 for (k, v) in elfinfo.items():
2538 if name in k:
2539 result[k] = v
2540 return result
2541
2542 def checksec(self, filename=None):
2543 """
2544 Check for various security options of binary (ref: http://www.trapkit.de/tools/checksec.sh)
2545
2546 Args:
2547 - file: path name of file to check (String)
2548
2549 Returns:
2550 - dictionary of (setting(String), status(Int)) (Dict)
2551 """
2552 result = {}
2553 result["RELRO"] = 0
2554 result["CANARY"] = 0
2555 result["NX"] = 1
2556 result["PIE"] = 0
2557 result["FORTIFY"] = 0
2558
2559 if filename is None:
2560 filename = self.getfile()
2561
2562 if not filename:
2563 return None
2564
2565 out = execute_external_command("%s -W -a \"%s\" 2>&1" % (config.READELF, filename))
2566 if "Error:" in out:
2567 return None
2568
2569 for line in out.splitlines():
2570 if "GNU_RELRO" in line:
2571 result["RELRO"] |= 2
2572 if "BIND_NOW" in line:
2573 result["RELRO"] |= 1
2574 if "__stack_chk_fail" in line:
2575 result["CANARY"] = 1
2576 if "GNU_STACK" in line and "RWE" in line:
2577 result["NX"] = 0
2578 if "Type:" in line and "DYN (" in line:
2579 result["PIE"] = 4 # Dynamic Shared Object
2580 if "(DEBUG)" in line and result["PIE"] == 4:
2581 result["PIE"] = 1
2582 if "_chk@" in line:
2583 result["FORTIFY"] = 1
2584
2585 if result["RELRO"] == 1:
2586 result["RELRO"] = 0 # ? | BIND_NOW + NO GNU_RELRO = NO PROTECTION
2587 # result["RELRO"] == 2 # Partial | NO BIND_NOW + GNU_RELRO
2588 # result["RELRO"] == 3 # Full | BIND_NOW + GNU_RELRO
2589 return result
2590
2591 def _verify_rop_gadget(self, start, end, depth=5):
2592 """
2593 Verify ROP gadget code from start to end with max number of instructions
2594
2595 Args:
2596 - start: start address (Int)
2597 - end: end addres (Int)
2598 - depth: number of instructions (Int)
2599
2600 Returns:
2601 - list of valid gadgets (address(Int), asmcode(String))
2602 """
2603
2604 result = []
2605 valid = 0
2606 out = self.execute_redirect("disassemble 0x%x, 0x%x" % (start, end+1))
2607 if not out:
2608 return []
2609
2610 code = out.splitlines()[1:-1]
2611 for line in code:
2612 if "bad" in line:
2613 return []
2614 (addr, code) = line.strip().split(":", 1)
2615 addr = to_int(addr.split()[0])
2616 result += [(addr, " ".join(code.strip().split()))]
2617 if "ret" in code:
2618 return result
2619 if len(result) > depth:
2620 break
2621
2622 return []
2623
2624 @memoized
2625 def search_asm(self, start, end, asmcode, rop=0):
2626 """
2627 Search for ASM instructions in memory
2628
2629 Args:
2630 - start: start address (Int)
2631 - end: end address (Int)
2632 - asmcode: assembly instruction (String)
2633 + multiple instructions are separated by ";"
2634 + wildcard ? supported, will be replaced by registers or multi-bytes
2635
2636 Returns:
2637 - list of (address(Int), hexbyte(String))
2638 """
2639 wildcard = asmcode.count('?')
2640 magic_bytes = ["0x00", "0xff", "0xdead", "0xdeadbeef", "0xdeadbeefdeadbeef"]
2641
2642 ops = [x for x in asmcode.split(';') if x]
2643 def buildcode(code=b"", pos=0, depth=0):
2644 if depth == wildcard and pos == len(ops):
2645 yield code
2646 return
2647
2648 c = ops[pos].count('?')
2649 if c > 2: return
2650 elif c == 0:
2651 asm = self.assemble(ops[pos])
2652 if asm:
2653 for code in buildcode(code + asm, pos+1, depth):
2654 yield code
2655 else:
2656 save = ops[pos]
2657 for regs in REGISTERS.values():
2658 for reg in regs:
2659 ops[pos] = save.replace("?", reg, 1)
2660 for asmcode_reg in buildcode(code, pos, depth+1):
2661 yield asmcode_reg
2662 for byte in magic_bytes:
2663 ops[pos] = save.replace("?", byte, 1)
2664 for asmcode_mem in buildcode(code, pos, depth+1):
2665 yield asmcode_mem
2666 ops[pos] = save
2667
2668 searches = []
2669
2670 def decode_hex_escape(str_):
2671 """Decode string as hex and escape for regex"""
2672 return re.escape(codecs.decode(str_, 'hex'))
2673
2674 for machine_code in buildcode():
2675 search = re.escape(machine_code)
2676 search = search.replace(decode_hex_escape(b"dead"), b"..")\
2677 .replace(decode_hex_escape(b"beef"), b"..")\
2678 .replace(decode_hex_escape(b"00"), b".")\
2679 .replace(decode_hex_escape(b"ff"), b".")
2680
2681 if rop and 'ret' not in asmcode:
2682 search += b".{0,24}\\xc3"
2683 searches.append(search)
2684
2685 if not searches:
2686 warning_msg("invalid asmcode: '%s'" % asmcode)
2687 return []
2688
2689 search = b"(?=(" + b"|".join(searches) + b"))"
2690 candidates = self.searchmem(start, end, search)
2691
2692 if rop:
2693 result = {}
2694 for (a, v) in candidates:
2695 gadget = self._verify_rop_gadget(a, a+len(v)//2 - 1)
2696 # gadget format: [(address, asmcode), (address, asmcode), ...]
2697 if gadget != []:
2698 blen = gadget[-1][0] - gadget[0][0] + 1
2699 bytes = v[:2*blen]
2700 asmcode_rs = "; ".join([c for _, c in gadget])
2701 if re.search(re.escape(asmcode).replace("\ ",".*").replace("\?",".*"), asmcode_rs)\
2702 and a not in result:
2703 result[a] = (bytes, asmcode_rs)
2704 result = list(result.items())
2705 else:
2706 result = []
2707 for (a, v) in candidates:
2708 asmcode = self.execute_redirect("disassemble 0x%x, 0x%x" % (a, a+(len(v)//2)))
2709 if asmcode:
2710 asmcode = "\n".join(asmcode.splitlines()[1:-1])
2711 matches = re.findall(".*:([^\n]*)", asmcode)
2712 result += [(a, (v, ";".join(matches).strip()))]
2713
2714 return result
2715
2716 def dumprop(self, start, end, keyword=None, depth=5):
2717 """
2718 Dump unique ROP gadgets in memory
2719
2720 Args:
2721 - start: start address (Int)
2722 - end: end address (Int)
2723 - keyword: to match start of gadgets (String)
2724
2725 Returns:
2726 - dictionary of (address(Int), asmcode(String))
2727 """
2728
2729 EXTRA_WORDS = ["BYTE ", " WORD", "DWORD ", "FWORD ", "QWORD ", "PTR ", "FAR "]
2730 result = {}
2731 mem = self.dumpmem(start, end)
2732 if mem is None:
2733 return {}
2734
2735 if keyword:
2736 search = keyword
2737 else:
2738 search = ""
2739
2740 if len(mem) > 20000: # limit backward depth if searching in large mem
2741 depth = 3
2742 found = re.finditer(b"\xc3", mem)
2743 found = list(found)
2744 for m in found:
2745 idx = start+m.start()
2746 for i in range(1, 24):
2747 gadget = self._verify_rop_gadget(idx-i, idx, depth)
2748 if gadget != []:
2749 k = "; ".join([v for (a, v) in gadget])
2750 if k.startswith(search):
2751 for w in EXTRA_WORDS:
2752 k = k.replace(w, "")
2753 if k not in result:
2754 result[k] = gadget[0][0]
2755 return result
2756
2757 def common_rop_gadget(self, mapname=None):
2758 """
2759 Get common rop gadgets in binary: ret, popret, pop2ret, pop3ret, add [mem] reg, add reg [mem]
2760
2761 Returns:
2762 - dictionary of (gadget(String), address(Int))
2763 """
2764
2765 def _valid_register_opcode(bytes_):
2766 if not bytes_:
2767 return False
2768
2769 for c in bytes_iterator(bytes_):
2770 if ord(c) not in list(range(0x58, 0x60)):
2771 return False
2772 return True
2773
2774 result = {}
2775 if mapname is None:
2776 mapname = "binary"
2777 maps = self.get_vmmap(mapname)
2778 if maps is None:
2779 return result
2780
2781 for (start, end, _, _) in maps:
2782 if not self.is_executable(start, maps): continue
2783
2784 mem = self.dumpmem(start, end)
2785 found = self.searchmem(start, end, b"....\xc3", mem)
2786 for (a, v) in found:
2787 v = codecs.decode(v, 'hex')
2788 if "ret" not in result:
2789 result["ret"] = a+4
2790 if "leaveret" not in result:
2791 if v[-2] == "\xc9":
2792 result["leaveret"] = a+3
2793 if "popret" not in result:
2794 if _valid_register_opcode(v[-2:-1]):
2795 result["popret"] = a+3
2796 if "pop2ret" not in result:
2797 if _valid_register_opcode(v[-3:-1]):
2798 result["pop2ret"] = a+2
2799 if "pop3ret" not in result:
2800 if _valid_register_opcode(v[-4:-1]):
2801 result["pop3ret"] = a+1
2802 if "pop4ret" not in result:
2803 if _valid_register_opcode(v[-5:-1]):
2804 result["pop4ret"] = a
2805
2806 # search for add esp, byte 0xNN
2807 found = self.searchmem(start, end, b"\x83\xc4([^\xc3]){0,24}\xc3", mem)
2808 # search for add esp, 0xNNNN
2809 found += self.searchmem(start, end, b"\x81\xc4([^\xc3]){0,24}\xc3", mem)
2810 for (a, v) in found:
2811 if v.startswith(b"81"):
2812 offset = to_int("0x" + codecs.encode(codecs.decode(v, 'hex')[2:5][::-1], 'hex').decode('utf-8'))
2813 elif v.startswith(b"83"):
2814 offset = to_int("0x" + v[4:6].decode('utf-8'))
2815 gg = self._verify_rop_gadget(a, a+len(v)//2-1)
2816 for (_, c) in gg:
2817 if "pop" in c:
2818 offset += 4
2819 gadget = "addesp_%d" % offset
2820 if gadget not in result:
2821 result[gadget] = a
2822
2823 return result
2824
2825 def search_jmpcall(self, start, end, regname=None):
2826 """
2827 Search memory for jmp/call reg instructions
2828
2829 Args:
2830 - start: start address (Int)
2831 - end: end address (Int)
2832 - reg: register name (String)
2833
2834 Returns:
2835 - list of (address(Int), instruction(String))
2836 """
2837
2838 result = []
2839 REG = {0: "eax", 1: "ecx", 2: "edx", 3: "ebx", 4: "esp", 5: "ebp", 6: "esi", 7:"edi"}
2840 P2REG = {0: "[eax]", 1: "[ecx]", 2: "[edx]", 3: "[ebx]", 6: "[esi]", 7:"[edi]"}
2841 OPCODE = {0xe: "jmp", 0xd: "call"}
2842 P2OPCODE = {0x1: "call", 0x2: "jmp"}
2843 JMPREG = [b"\xff" + bytes_chr(i) for i in range(0xe0, 0xe8)]
2844 JMPREG += [b"\xff" + bytes_chr(i) for i in range(0x20, 0x28)]
2845 CALLREG = [b"\xff" + bytes_chr(i) for i in range(0xd0, 0xd8)]
2846 CALLREG += [b"\xff" + bytes_chr(i) for i in range(0x10, 0x18)]
2847 JMPCALL = JMPREG + CALLREG
2848
2849 if regname is None:
2850 regname = ""
2851 regname = regname.lower()
2852 pattern = re.compile(b'|'.join(JMPCALL).replace(b' ', b'\ '))
2853 mem = self.dumpmem(start, end)
2854 found = pattern.finditer(mem)
2855 (arch, bits) = self.getarch()
2856 for m in list(found):
2857 inst = ""
2858 addr = start + m.start()
2859 opcode = codecs.encode(m.group()[1:2], 'hex')
2860 type = int(opcode[0:1], 16)
2861 reg = int(opcode[1:2], 16)
2862 if type in OPCODE:
2863 inst = OPCODE[type] + " " + REG[reg]
2864
2865 if type in P2OPCODE and reg in P2REG:
2866 inst = P2OPCODE[type] + " " + P2REG[reg]
2867
2868 if inst != "" and regname[-2:] in inst.split()[-1]:
2869 if bits == 64:
2870 inst = inst.replace("e", "r")
2871 result += [(addr, inst)]
2872
2873 return result
2874
2875 def search_substr(self, start, end, search, mem=None):
2876 """
2877 Search for substrings of a given string/number in memory
2878
2879 Args:
2880 - start: start address (Int)
2881 - end: end address (Int)
2882 - search: string to search for (String)
2883 - mem: cached memory (raw bytes)
2884
2885 Returns:
2886 - list of tuple (substr(String), address(Int))
2887 """
2888 def substr(s1, s2):
2889 "Search for a string in another string"
2890 s1 = to_binary_string(s1)
2891 s2 = to_binary_string(s2)
2892 i = 1
2893 found = 0
2894 while i <= len(s1):
2895 if s2.find(s1[:i]) != -1:
2896 found = 1
2897 i += 1
2898 if s1[:i-1][-1:] == b"\x00":
2899 break
2900 else:
2901 break
2902 if found == 1:
2903 return i-1
2904 else:
2905 return -1
2906
2907 result = []
2908 if end < start:
2909 start, end = end, start
2910
2911 if mem is None:
2912 mem = self.dumpmem(start, end)
2913
2914 if search[:2] == "0x": # hex number
2915 search = search[2:]
2916 if len(search) %2 != 0:
2917 search = "0" + search
2918 search = codecs.decode(search, 'hex')[::-1]
2919 search = to_binary_string(decode_string_escape(search))
2920 while search:
2921 l = len(search)
2922 i = substr(search, mem)
2923 if i != -1:
2924 sub = search[:i]
2925 addr = start + mem.find(sub)
2926 if not check_badchars(addr):
2927 result.append((sub, addr))
2928 else:
2929 result.append((search, -1))
2930 return result
2931 search = search[i:]
2932 return result
2933
2934
2935 ##############################
2936 # ROP Payload Generation #
2937 ##############################
2938 def payload_copybytes(self, target=None, data=None, template=0):
2939 """
2940 Suggest function for ret2plt exploit and generate payload for it
2941
2942 Args:
2943 - target: address to copy data to (Int)
2944 - data: (String)
2945 Returns:
2946 - python code template (String)
2947 """
2948 result = ""
2949 funcs = ["strcpy", "sprintf", "strncpy", "snprintf", "memcpy"]
2950
2951 symbols = self.elfsymbols()
2952 transfer = ""
2953 for f in funcs:
2954 if f+"@plt" in symbols:
2955 transfer = f
2956 break
2957 if transfer == "":
2958 warning_msg("No copy function available")
2959 return None
2960
2961 headers = self.elfheader()
2962 start = min([v[0] for (k, v) in headers.items() if v[0] > 0])
2963 end = max([v[1] for (k, v) in headers.items() if v[2] != "data"])
2964 symbols = self.elfsymbol(transfer)
2965 if not symbols:
2966 warning_msg("Unable to find symbols")
2967 return None
2968
2969 plt_func = transfer + "_plt"
2970 plt_addr = symbols[transfer+"@plt"]
2971 gadgets = self.common_rop_gadget()
2972 function_template = "\n".join([
2973 "popret = 0x%x" % gadgets["popret"],
2974 "pop2ret = 0x%x" % gadgets["pop2ret"],
2975 "pop3ret = 0x%x" % gadgets["pop3ret"],
2976 "def %s_payload(target, bytes):" % transfer,
2977 " %s = 0x%x" % (plt_func, plt_addr),
2978 " payload = []",
2979 " offset = 0",
2980 " for (str, addr) in bytes:",
2981 "",
2982 ])
2983 if "ncp" in transfer or "mem" in transfer: # memcpy() style
2984 function_template += "\n".join([
2985 " payload += [%s, pop3ret, target+offset, addr, len(str)]" % plt_func,
2986 " offset += len(str)",
2987 ])
2988 elif "snp" in transfer: # snprintf()
2989 function_template += "\n".join([
2990 " payload += [%s, pop3ret, target+offset, len(str)+1, addr]" % plt_func,
2991 " offset += len(str)",
2992 ])
2993 else:
2994 function_template += "\n".join([
2995 " payload += [%s, pop2ret, target+offset, addr]" % plt_func,
2996 " offset += len(str)",
2997 ])
2998 function_template += "\n".join(["",
2999 " return payload",
3000 "",
3001 "payload = []"
3002 ])
3003
3004 if target is None:
3005 if template != 0:
3006 return function_template
3007 else:
3008 return ""
3009
3010 #text = "\n_payload = []\n"
3011 text = "\n"
3012 mem = self.dumpmem(start, end)
3013 bytes = self.search_substr(start, end, data, mem)
3014
3015 if to_int(target) is not None:
3016 target = to_hex(target)
3017 text += "# %s <= %s\n" % (target, repr(data))
3018 if not bytes:
3019 text += "***Failed***\n"
3020 else:
3021 text += "bytes = [\n"
3022 for (s, a) in bytes:
3023 if a != -1:
3024 text += " (%s, %s),\n" % (repr(s), to_hex(a))
3025 else:
3026 text += " (%s, ***Failed***),\n" % repr(s)
3027 text += "\n".join([
3028 "]",
3029 "payload += %s_payload(%s, bytes)" % (transfer, target),
3030 "",
3031 ])
3032
3033 return text
3034
3035
3036###########################################################################
3037class PEDACmd(object):
3038 """
3039 Class for PEDA commands that interact with GDB
3040 """
3041 commands = []
3042 def __init__(self):
3043 # list of all available commands
3044 self.commands = [c for c in dir(self) if callable(getattr(self, c)) and not c.startswith("_")]
3045
3046 ##################
3047 # Misc Utils #
3048 ##################
3049 def _missing_argument(self):
3050 """
3051 Raise exception for missing argument, for internal use
3052 """
3053 text = "missing argument"
3054 error_msg(text)
3055 raise Exception(text)
3056
3057 def _is_running(self):
3058 """
3059 Check if program is running, for internal use
3060 """
3061 pid = peda.getpid()
3062 if pid is None:
3063 text = "not running or target is remote"
3064 warning_msg(text)
3065 return None
3066 #raise Exception(text)
3067 else:
3068 return pid
3069
3070 def reload(self, *arg):
3071 """
3072 Reload PEDA sources, keep current options untouch
3073 Usage:
3074 MYNAME [name]
3075 """
3076 (modname,) = normalize_argv(arg, 1)
3077 # save current PEDA options
3078 saved_opt = config.Option
3079 peda_path = os.path.dirname(PEDAFILE) + "/lib/"
3080 if not modname:
3081 modname = "PEDA" # just for notification
3082 ret = peda.execute("source %s" % PEDAFILE)
3083 else:
3084 if not modname.endswith(".py"):
3085 modname = modname + ".py"
3086 filepath = "%s/%s" % (peda_path, modname)
3087 if os.path.exists(filepath):
3088 ret = peda.execute("source %s" % filepath)
3089 peda.execute("source %s" % PEDAFILE)
3090 else:
3091 ret = False
3092
3093 config.Option = saved_opt
3094 if ret:
3095 msg("%s reloaded!" % modname, "blue")
3096 else:
3097 msg("Failed to reload %s source from: %s" % (modname, peda_path))
3098 return
3099
3100 def _get_helptext(self, *arg):
3101 """
3102 Get the help text, for internal use by help command and other aliases
3103 """
3104
3105 (cmd,) = normalize_argv(arg, 1)
3106 helptext = ""
3107 if cmd is None:
3108 helptext = red("PEDA", "bold") + blue(" - Python Exploit Development Assistance for GDB", "bold") + "\n"
3109 helptext += "For latest update, check peda project page: %s\n" % green("https://github.com/longld/peda/")
3110 helptext += "List of \"peda\" subcommands, type the subcommand to invoke it:\n"
3111 i = 0
3112 for cmd in self.commands:
3113 if cmd.startswith("_"): continue # skip internal use commands
3114 func = getattr(self, cmd)
3115 helptext += "%s -- %s\n" % (cmd, green(trim(func.__doc__.strip("\n").splitlines()[0])))
3116 helptext += "\nType \"help\" followed by subcommand for full documentation."
3117 else:
3118 if cmd in self.commands:
3119 func = getattr(self, cmd)
3120 lines = trim(func.__doc__).splitlines()
3121 helptext += green(lines[0]) + "\n"
3122 for line in lines[1:]:
3123 if "Usage:" in line:
3124 helptext += blue(line) + "\n"
3125 else:
3126 helptext += line + "\n"
3127 else:
3128 for c in self.commands:
3129 if not c.startswith("_") and cmd in c:
3130 func = getattr(self, c)
3131 helptext += "%s -- %s\n" % (c, green(trim(func.__doc__.strip("\n").splitlines()[0])))
3132
3133 return helptext
3134
3135 def help(self, *arg):
3136 """
3137 Print the usage manual for PEDA commands
3138 Usage:
3139 MYNAME
3140 MYNAME command
3141 """
3142
3143 msg(self._get_helptext(*arg))
3144
3145 return
3146 help.options = commands
3147
3148 def pyhelp(self, *arg):
3149 """
3150 Wrapper for python built-in help
3151 Usage:
3152 MYNAME (enter interactive help)
3153 MYNAME help_request
3154 """
3155 (request,) = normalize_argv(arg, 1)
3156 if request is None:
3157 help()
3158 return
3159
3160 peda_methods = ["%s" % c for c in dir(PEDA) if callable(getattr(PEDA, c)) and \
3161 not c.startswith("_")]
3162
3163 if request in peda_methods:
3164 request = "peda.%s" % request
3165 try:
3166 if request.lower().startswith("peda"):
3167 request = eval(request)
3168 help(request)
3169 return
3170
3171 if "." in request:
3172 module, _, function = request.rpartition('.')
3173 if module:
3174 module = module.split(".")[0]
3175 __import__(module)
3176 mod = sys.modules[module]
3177 if function:
3178 request = getattr(mod, function)
3179 else:
3180 request = mod
3181 else:
3182 mod = sys.modules['__main__']
3183 request = getattr(mod, request)
3184
3185 # wrapper for python built-in help
3186 help(request)
3187 except: # fallback to built-in help
3188 try:
3189 help(request)
3190 except Exception as e:
3191 if config.Option.get("debug") == "on":
3192 msg('Exception (%s): %s' % ('pyhelp', e), "red")
3193 traceback.print_exc()
3194 msg("no Python documentation found for '%s'" % request)
3195
3196 return
3197 pyhelp.options = ["%s" % c for c in dir(PEDA) if callable(getattr(PEDA, c)) and \
3198 not c.startswith("_")]
3199
3200
3201 def save(self, *arg):
3202 """
3203 Save configured options to config file
3204 """
3205 config.Option.save()
3206
3207 def load(self, *arg):
3208 """
3209 Load configured options from config file
3210 """
3211 config.Option.load()
3212
3213 # show [option | args | env]
3214 def show(self, *arg):
3215 """
3216 Show various PEDA options and other settings
3217 Usage:
3218 MYNAME option [optname]
3219 MYNAME (show all options)
3220 MYNAME args
3221 MYNAME env [envname]
3222 """
3223 # show options
3224 def _show_option(name=None):
3225 if name is None:
3226 name = ""
3227 filename = peda.getfile()
3228 if filename:
3229 filename = os.path.basename(filename)
3230 else:
3231 filename = None
3232 for (k, v) in sorted(config.Option.show(name).items()):
3233 if filename and isinstance(v, str) and "#FILENAME#" in v:
3234 v = v.replace("#FILENAME#", filename)
3235 msg("%s = %s" % (k, repr(v)))
3236 return
3237
3238 # show args
3239 def _show_arg():
3240 arg = peda.execute_redirect("show args")
3241 arg = arg.split("started is ")[1][1:-3]
3242 arg = (peda.string_to_argv(arg))
3243 if not arg:
3244 msg("No argument")
3245 for (i, a) in enumerate(arg):
3246 text = "arg[%d]: %s" % ((i+1), a if is_printable(a) else to_hexstr(a))
3247 msg(text)
3248 return
3249
3250 # show envs
3251 def _show_env(name=None):
3252 if name is None:
3253 name = ""
3254 env = peda.execute_redirect("show env")
3255 for line in env.splitlines():
3256 (k, v) = line.split("=", 1)
3257 if k.startswith(name):
3258 msg("%s = %s" % (k, v if is_printable(v) else to_hexstr(v)))
3259 return
3260
3261 (opt, name) = normalize_argv(arg, 2)
3262
3263 if opt is None or opt.startswith("opt"):
3264 _show_option(name)
3265 elif opt.startswith("arg"):
3266 _show_arg()
3267 elif opt.startswith("env"):
3268 _show_env(name)
3269 else:
3270 msg("Unknown show option: %s" % opt)
3271 return
3272 show.options = ["option", "arg", "env"]
3273
3274 # set [option | arg | env]
3275 def set(self, *arg):
3276 """
3277 Set various PEDA options and other settings
3278 Usage:
3279 MYNAME option name value
3280 MYNAME arg string
3281 MYNAME env name value
3282 support input non-printable chars, e.g MYNAME env EGG "\\x90"*1000
3283 """
3284 # set options
3285 def _set_option(name, value):
3286 if name in config.Option.options:
3287 config.Option.set(name, value)
3288 msg("%s = %s" % (name, repr(value)))
3289 else:
3290 msg("Unknown option: %s" % name)
3291 return
3292
3293 # set args
3294 def _set_arg(*arg):
3295 cmd = "set args"
3296 for a in arg:
3297 try:
3298 s = eval('%s' % a)
3299 if isinstance(s, six.integer_types + six.string_types):
3300 a = s
3301 except:
3302 pass
3303 cmd += " '%s'" % a
3304 peda.execute(cmd)
3305 return
3306
3307 # set env
3308 def _set_env(name, value):
3309 env = peda.execute_redirect("show env")
3310 cmd = "set env %s " % name
3311 try:
3312 value = eval('%s' % value)
3313 except:
3314 pass
3315 cmd += '%s' % value
3316 peda.execute(cmd)
3317
3318 return
3319
3320 (opt, name, value) = normalize_argv(arg, 3)
3321 if opt is None:
3322 self._missing_argument()
3323
3324 if opt.startswith("opt"):
3325 if value is None:
3326 self._missing_argument()
3327 _set_option(name, value)
3328 elif opt.startswith("arg"):
3329 _set_arg(*arg[1:])
3330 elif opt.startswith("env"):
3331 _set_env(name, value)
3332 else:
3333 msg("Unknown set option: %s" % known_args.opt)
3334 return
3335 set.options = ["option", "arg", "env"]
3336
3337 def hexprint(self, *arg):
3338 """
3339 Display hexified of data in memory
3340 Usage:
3341 MYNAME address (display 16 bytes from address)
3342 MYNAME address count
3343 MYNAME address /count (display "count" lines, 16-bytes each)
3344 """
3345 (address, count) = normalize_argv(arg, 2)
3346 if address is None:
3347 self._missing_argument()
3348
3349 if count is None:
3350 count = 16
3351
3352 if not to_int(count) and count.startswith("/"):
3353 count = to_int(count[1:])
3354 count = count * 16 if count else None
3355
3356 bytes_ = peda.dumpmem(address, address+count)
3357 if bytes_ is None:
3358 warning_msg("cannot retrieve memory content")
3359 else:
3360 hexstr = to_hexstr(bytes_)
3361 linelen = 16 # display 16-bytes per line
3362 i = 0
3363 text = ""
3364 while hexstr:
3365 text += '%s : "%s"\n' % (blue(to_address(address+i*linelen)), hexstr[:linelen*4])
3366 hexstr = hexstr[linelen*4:]
3367 i += 1
3368 pager(text)
3369
3370 return
3371
3372 def hexdump(self, *arg):
3373 """
3374 Display hex/ascii dump of data in memory
3375 Usage:
3376 MYNAME address (dump 16 bytes from address)
3377 MYNAME address count
3378 MYNAME address /count (dump "count" lines, 16-bytes each)
3379 """
3380 def ascii_char(ch):
3381 if ord(ch) >= 0x20 and ord(ch) < 0x7e:
3382 return chr(ord(ch)) # Ensure we return a str
3383 else:
3384 return "."
3385
3386 (address, count) = normalize_argv(arg, 2)
3387 if address is None:
3388 self._missing_argument()
3389
3390 if count is None:
3391 count = 16
3392
3393 if not to_int(count) and count.startswith("/"):
3394 count = to_int(count[1:])
3395 count = count * 16 if count else None
3396
3397 bytes_ = peda.dumpmem(address, address+count)
3398 if bytes_ is None:
3399 warning_msg("cannot retrieve memory content")
3400 else:
3401 linelen = 16 # display 16-bytes per line
3402 i = 0
3403 text = ""
3404 while bytes_:
3405 buf = bytes_[:linelen]
3406 hexbytes = " ".join(["%02x" % ord(c) for c in bytes_iterator(buf)])
3407 asciibytes = "".join([ascii_char(c) for c in bytes_iterator(buf)])
3408 text += '%s : %s %s\n' % (blue(to_address(address+i*linelen)), hexbytes.ljust(linelen*3), asciibytes)
3409 bytes_ = bytes_[linelen:]
3410 i += 1
3411 pager(text)
3412
3413 return
3414
3415 def aslr(self, *arg):
3416 """
3417 Show/set ASLR setting of GDB
3418 Usage:
3419 MYNAME [on|off]
3420 """
3421 (option,) = normalize_argv(arg, 1)
3422 if option is None:
3423 out = peda.execute_redirect("show disable-randomization")
3424 if not out:
3425 warning_msg("ASLR setting is unknown or not available")
3426 return
3427
3428 if "is off" in out:
3429 msg("ASLR is %s" % green("ON"))
3430 if "is on" in out:
3431 msg("ASLR is %s" % red("OFF"))
3432 else:
3433 option = option.strip().lower()
3434 if option in ["on", "off"]:
3435 peda.execute("set disable-randomization %s" % ("off" if option == "on" else "on"))
3436
3437 return
3438
3439 def xprint(self, *arg):
3440 """
3441 Extra support to GDB's print command
3442 Usage:
3443 MYNAME expression
3444 """
3445 text = ""
3446 exp = " ".join(list(arg))
3447 m = re.search(".*\[(.*)\]|.*?s:(0x[^ ]*)", exp)
3448 if m:
3449 addr = peda.parse_and_eval(m.group(1))
3450 if to_int(addr):
3451 text += "[0x%x]: " % to_int(addr)
3452
3453 out = peda.parse_and_eval(exp)
3454 if to_int(out):
3455 chain = peda.examine_mem_reference(to_int(out))
3456 text += format_reference_chain(chain)
3457 msg(text)
3458 return
3459
3460 def distance(self, *arg):
3461 """
3462 Calculate distance between two addresses
3463 Usage:
3464 MYNAME address (calculate from current $SP to address)
3465 MYNAME address1 address2
3466 """
3467 (start, end) = normalize_argv(arg, 2)
3468 if to_int(start) is None or (to_int(end) is None and not self._is_running()):
3469 self._missing_argument()
3470
3471 sp = None
3472 if end is None:
3473 sp = peda.getreg("sp")
3474 end = start
3475 start = sp
3476
3477 dist = end - start
3478 text = "From 0x%x%s to 0x%x: " % (start, " (SP)" if start == sp else "", end)
3479 text += "%d bytes, %d dwords%s" % (dist, dist//4, " (+%d bytes)" % (dist%4) if (dist%4 != 0) else "")
3480 msg(text)
3481
3482 return
3483
3484 def session(self, *arg):
3485 """
3486 Save/restore a working gdb session to file as a script
3487 Usage:
3488 MYNAME save [filename]
3489 MYNAME restore [filename]
3490 """
3491 options = ["save", "restore", "autosave"]
3492 (option, filename) = normalize_argv(arg, 2)
3493 if option not in options:
3494 self._missing_argument()
3495
3496 if not filename:
3497 filename = peda.get_config_filename("session")
3498
3499 if option == "save":
3500 if peda.save_session(filename):
3501 msg("Saved GDB session to file %s" % filename)
3502 else:
3503 msg("Failed to save GDB session")
3504
3505 if option == "restore":
3506 if peda.restore_session(filename):
3507 msg("Restored GDB session from file %s" % filename)
3508 else:
3509 msg("Failed to restore GDB session")
3510
3511 if option == "autosave":
3512 if config.Option.get("autosave") == "on":
3513 peda.save_session(filename)
3514
3515 return
3516 session.options = ["save", "restore"]
3517
3518 #################################
3519 # Debugging Helper Commands #
3520 #################################
3521 def procinfo(self, *arg):
3522 """
3523 Display various info from /proc/pid/
3524 Usage:
3525 MYNAME [pid]
3526 """
3527 options = ["exe", "fd", "pid", "ppid", "uid", "gid"]
3528
3529 if peda.getos() != "Linux":
3530 warning_msg("this command is only available on Linux")
3531
3532 (pid,) = normalize_argv(arg, 1)
3533
3534 if not pid:
3535 pid = peda.getpid()
3536
3537 if not pid:
3538 return
3539
3540 info = {}
3541 try:
3542 info["exe"] = os.path.realpath("/proc/%d/exe" % pid)
3543 except:
3544 warning_msg("cannot access /proc/%d/" % pid)
3545 return
3546
3547 # fd list
3548 info["fd"] = {}
3549 fdlist = os.listdir("/proc/%d/fd" % pid)
3550 for fd in fdlist:
3551 rpath = os.readlink("/proc/%d/fd/%s" % (pid, fd))
3552 sock = re.search("socket:\[(.*)\]", rpath)
3553 if sock:
3554 spath = execute_external_command("netstat -aen | grep %s" % sock.group(1))
3555 if spath:
3556 rpath = spath.strip()
3557 info["fd"][to_int(fd)] = rpath
3558
3559 # uid/gid, pid, ppid
3560 info["pid"] = pid
3561 status = open("/proc/%d/status" % pid).read()
3562 ppid = re.search("PPid:\s*([^\s]*)", status).group(1)
3563 info["ppid"] = to_int(ppid) if ppid else -1
3564 uid = re.search("Uid:\s*([^\n]*)", status).group(1)
3565 info["uid"] = [to_int(id) for id in uid.split()]
3566 gid = re.search("Gid:\s*([^\n]*)", status).group(1)
3567 info["gid"] = [to_int(id) for id in gid.split()]
3568
3569 for opt in options:
3570 if opt == "fd":
3571 for (fd, path) in info[opt].items():
3572 msg("fd[%d] -> %s" % (fd, path))
3573 else:
3574 msg("%s = %s" % (opt, info[opt]))
3575 return
3576
3577 # getfile()
3578 def getfile(self):
3579 """
3580 Get exec filename of current debugged process
3581 Usage:
3582 MYNAME
3583 """
3584 filename = peda.getfile()
3585 if filename == None:
3586 msg("No file specified")
3587 else:
3588 msg(filename)
3589 return
3590
3591 # getpid()
3592 def getpid(self):
3593 """
3594 Get PID of current debugged process
3595 Usage:
3596 MYNAME
3597 """
3598 pid = self._is_running()
3599 msg(pid)
3600 return
3601
3602 # disassemble()
3603 def pdisass(self, *arg):
3604 """
3605 Format output of gdb disassemble command with colors
3606 Usage:
3607 MYNAME "args for gdb disassemble command"
3608 MYNAME address /NN: equivalent to "x/NNi address"
3609 """
3610 (address, fmt_count) = normalize_argv(arg, 2)
3611 if isinstance(fmt_count, str) and fmt_count.startswith("/"):
3612 count = to_int(fmt_count[1:])
3613 if not count or to_int(address) is None:
3614 self._missing_argument()
3615 else:
3616 code = peda.get_disasm(address, count)
3617 else:
3618 code = peda.disassemble(*arg)
3619 msg(format_disasm_code(code))
3620
3621 return
3622
3623 # disassemble_around
3624 def nearpc(self, *arg):
3625 """
3626 Disassemble instructions nearby current PC or given address
3627 Usage:
3628 MYNAME [count]
3629 MYNAME address [count]
3630 count is maximum 256
3631 """
3632 (address, count) = normalize_argv(arg, 2)
3633 address = to_int(address)
3634
3635 count = to_int(count)
3636 if address is not None and address < 0x40000:
3637 count = address
3638 address = None
3639
3640 if address is None:
3641 address = peda.getreg("pc")
3642
3643 if count is None:
3644 code = peda.disassemble_around(address)
3645 else:
3646 code = peda.disassemble_around(address, count)
3647
3648 if code:
3649 msg(format_disasm_code(code, address))
3650 else:
3651 error_msg("invalid $pc address or instruction count")
3652 return
3653
3654 def waitfor(self, *arg):
3655 """
3656 Try to attach to new forked process; mimic "attach -waitfor"
3657 Usage:
3658 MYNAME [cmdname]
3659 MYNAME [cmdname] -c (auto continue after attached)
3660 """
3661 (name, opt) = normalize_argv(arg, 2)
3662 if name == "-c":
3663 opt = name
3664 name = None
3665
3666 if name is None:
3667 filename = peda.getfile()
3668 if filename is None:
3669 warning_msg("please specify the file to debug or process name to attach")
3670 return
3671 else:
3672 name = os.path.basename(filename)
3673
3674 msg("Trying to attach to new forked process (%s), Ctrl-C to stop..." % name)
3675 cmd = "ps axo pid,command | grep %s | grep -v grep" % name
3676 getpids = []
3677 out = execute_external_command(cmd)
3678 for line in out.splitlines():
3679 getpids += [line.split()[0].strip()]
3680
3681 while True:
3682 found = 0
3683 out = execute_external_command(cmd)
3684 for line in out.splitlines():
3685 line = line.split()
3686 pid = line[0].strip()
3687 cmdname = line[1].strip()
3688 if name not in cmdname: continue
3689 if pid not in getpids:
3690 found = 1
3691 break
3692
3693 if found == 1:
3694 msg("Attching to pid: %s, cmdname: %s" % (pid, cmdname))
3695 if peda.getpid():
3696 peda.execute("detach")
3697 out = peda.execute_redirect("attach %s" % pid)
3698 msg(out)
3699 out = peda.execute_redirect("file %s" % cmdname) # reload symbol file
3700 msg(out)
3701 if opt == "-c":
3702 peda.execute("continue")
3703 return
3704 time.sleep(0.5)
3705 return
3706
3707 def pltbreak(self, *arg):
3708 """
3709 Set breakpoint at PLT functions match name regex
3710 Usage:
3711 MYNAME [name]
3712 """
3713 (name,) = normalize_argv(arg, 1)
3714 if not name:
3715 name = ""
3716 headers = peda.elfheader()
3717 end = headers[".bss"]
3718 symbols = peda.elfsymbol(name)
3719 if len(symbols) == 0:
3720 msg("File not specified or PLT symbols not found")
3721 return
3722 else:
3723 # Traverse symbols in order to have more predictable output
3724 for symname in sorted(symbols):
3725 if "plt" not in symname: continue
3726 if name in symname: # fixme(longld) bounds checking?
3727 line = peda.execute_redirect("break %s" % symname)
3728 msg("%s (%s)" % (line.strip("\n"), symname))
3729 return
3730
3731 def xrefs(self, *arg):
3732 """
3733 Search for all call/data access references to a function/variable
3734 Usage:
3735 MYNAME pattern
3736 MYNAME pattern file/mapname
3737 """
3738 (search, filename) = normalize_argv(arg, 2)
3739 if search is None:
3740 search = "" # search for all call references
3741 else:
3742 search = arg[0]
3743
3744 if filename is not None: # get full path to file if mapname is provided
3745 vmap = peda.get_vmmap(filename)
3746 if vmap:
3747 filename = vmap[0][3]
3748
3749 result = peda.xrefs(search, filename)
3750 if result:
3751 if search != "":
3752 msg("All references to '%s':" % search)
3753 else:
3754 msg("All call references")
3755 for (addr, code) in result:
3756 msg("%s" % (code))
3757 else:
3758 msg("Not found")
3759 return
3760
3761 def deactive(self, *arg):
3762 """
3763 Bypass a function by ignoring its execution (eg sleep/alarm)
3764 Usage:
3765 MYNAME function
3766 MYNAME function del (re-active)
3767 """
3768 (function, action) = normalize_argv(arg, 2)
3769 if function is None:
3770 self._missing_argument()
3771
3772 if to_int(function):
3773 function = "0x%x" % function
3774
3775 bnum = "$deactive_%s_bnum" % function
3776 if action and "del" in action:
3777 peda.execute("delete %s" % bnum)
3778 peda.execute("set %s = \"void\"" % bnum)
3779 msg("'%s' re-activated" % function)
3780 return
3781
3782 if "void" not in peda.execute_redirect("p %s" % bnum):
3783 out = peda.execute_redirect("info breakpoints %s" % bnum)
3784 if out:
3785 msg("Already deactivated '%s'" % function)
3786 msg(out)
3787 return
3788 else:
3789 peda.execute("set %s = \"void\"" % bnum)
3790
3791 (arch, bits) = peda.getarch()
3792 if not function.startswith("0x"): # named function
3793 symbol = peda.elfsymbol(function)
3794 if not symbol:
3795 warning_msg("cannot retrieve info of function '%s'" % function)
3796 return
3797 peda.execute("break *0x%x" % symbol[function + "@plt"])
3798
3799 else: # addressed function
3800 peda.execute("break *%s" % function)
3801
3802 peda.execute("set %s = $bpnum" % bnum)
3803 tmpfd = tmpfile()
3804 if "i386" in arch:
3805 tmpfd.write("\n".join([
3806 "commands $bpnum",
3807 "silent",
3808 "set $eax = 0",
3809 "return",
3810 "continue",
3811 "end"]))
3812 if "64" in arch:
3813 tmpfd.write("\n".join([
3814 "commands $bpnum",
3815 "silent",
3816 "set $rax = 0",
3817 "return",
3818 "continue",
3819 "end"]))
3820 tmpfd.flush()
3821 peda.execute("source %s" % tmpfd.name)
3822 tmpfd.close()
3823 out = peda.execute_redirect("info breakpoints %s" % bnum)
3824 if out:
3825 msg("'%s' deactivated" % function)
3826 msg(out)
3827 return
3828
3829 def unptrace(self, *arg):
3830 """
3831 Disable anti-ptrace detection
3832 Usage:
3833 MYNAME
3834 MYNAME del
3835 """
3836 (action,) = normalize_argv(arg, 1)
3837
3838 self.deactive("ptrace", action)
3839
3840 if not action and "void" in peda.execute_redirect("p $deactive_ptrace_bnum"):
3841 # cannot deactive vi plt entry, try syscall method
3842 msg("Try to patch 'ptrace' via syscall")
3843 peda.execute("catch syscall ptrace")
3844 peda.execute("set $deactive_ptrace_bnum = $bpnum")
3845 tmpfd = tmpfile()
3846 (arch, bits) = peda.getarch()
3847 if "i386" in arch:
3848 tmpfd.write("\n".join([
3849 "commands $bpnum",
3850 "silent",
3851 "if (*(int*)($esp+4) == 0 || $ebx == 0)",
3852 " set $eax = 0",
3853 "end",
3854 "continue",
3855 "end"]))
3856 if "64" in arch:
3857 tmpfd.write("\n".join([
3858 "commands $bpnum",
3859 "silent",
3860 "if ($rdi == 0)",
3861 " set $rax = 0",
3862 "end",
3863 "continue",
3864 "end"]))
3865 tmpfd.flush()
3866 peda.execute("source %s" % tmpfd.name)
3867 tmpfd.close()
3868 out = peda.execute_redirect("info breakpoints $deactive_ptrace_bnum")
3869 if out:
3870 msg("'ptrace' deactivated")
3871 msg(out)
3872 return
3873
3874 # get_function_args()
3875 def dumpargs(self, *arg):
3876 """
3877 Display arguments passed to a function when stopped at a call instruction
3878 Usage:
3879 MYNAME [count]
3880 count: force to display "count args" instead of guessing
3881 """
3882
3883 (count,) = normalize_argv(arg, 1)
3884 if not self._is_running():
3885 return
3886
3887 args = peda.get_function_args(count)
3888 if args:
3889 msg("Guessed arguments:")
3890 for (i, a) in enumerate(args):
3891 chain = peda.examine_mem_reference(a)
3892 msg("arg[%d]: %s" % (i, format_reference_chain(chain)))
3893 else:
3894 msg("No argument")
3895
3896 return
3897
3898 def xuntil(self, *arg):
3899 """
3900 Continue execution until an address or function
3901 Usage:
3902 MYNAME address | function
3903 """
3904 (address,) = normalize_argv(arg, 1)
3905 if to_int(address) is None:
3906 peda.execute("tbreak %s" % address)
3907 else:
3908 peda.execute("tbreak *0x%x" % address)
3909 pc = peda.getreg("pc")
3910 if pc is None:
3911 peda.execute("run")
3912 else:
3913 peda.execute("continue")
3914 return
3915
3916 def goto(self, *arg):
3917 """
3918 Continue execution at an address
3919 Usage:
3920 MYNAME address
3921 """
3922 (address,) = normalize_argv(arg, 1)
3923 if to_int(address) is None:
3924 self._missing_argument()
3925
3926 peda.execute("set $pc = 0x%x" % address)
3927 peda.execute("stop")
3928 return
3929
3930 def skipi(self, *arg):
3931 """
3932 Skip execution of next count instructions
3933 Usage:
3934 MYNAME [count]
3935 """
3936 (count,) = normalize_argv(arg, 1)
3937 if to_int(count) is None:
3938 count = 1
3939
3940 if not self._is_running():
3941 return
3942
3943 next_code = peda.next_inst(peda.getreg("pc"), count)
3944 if not next_code:
3945 warning_msg("failed to get next instructions")
3946 return
3947 last_addr = next_code[-1][0]
3948 peda.execute("set $pc = 0x%x" % last_addr)
3949 peda.execute("stop")
3950 return
3951
3952 def start(self, *arg):
3953 """
3954 Start debugged program and stop at most convenient entry
3955 Usage:
3956 MYNAME
3957 """
3958 entries = ["main"]
3959 main_addr = peda.main_entry()
3960 if main_addr:
3961 entries += ["*0x%x" % main_addr]
3962 entries += ["__libc_start_main@plt"]
3963 entries += ["_start"]
3964 entries += ["_init"]
3965
3966 started = 0
3967 for e in entries:
3968 out = peda.execute_redirect("tbreak %s" % e)
3969 if out and "breakpoint" in out:
3970 peda.execute("run %s" % ' '.join(arg))
3971 started = 1
3972 break
3973
3974 if not started: # try ELF entry point or just "run" as the last resort
3975 elf_entry = peda.elfentry()
3976 if elf_entry:
3977 out = peda.execute_redirect("tbreak *%s" % elf_entry)
3978
3979 peda.execute("run")
3980
3981 return
3982
3983 # stepuntil()
3984 def stepuntil(self, *arg):
3985 """
3986 Step until a desired instruction in specific memory range
3987 Usage:
3988 MYNAME "inst1,inst2" (step to next inst in binary)
3989 MYNAME "inst1,inst2" mapname1,mapname2
3990 """
3991 (insts, mapname) = normalize_argv(arg, 2)
3992 if insts is None:
3993 self._missing_argument()
3994
3995 if not self._is_running():
3996 return
3997
3998 peda.save_user_command("hook-stop") # disable hook-stop to speedup
3999 msg("Stepping through, Ctrl-C to stop...")
4000 result = peda.stepuntil(insts, mapname)
4001 peda.restore_user_command("hook-stop")
4002
4003 if result:
4004 peda.execute("stop")
4005 return
4006
4007 # wrapper for stepuntil("call")
4008 def nextcall(self, *arg):
4009 """
4010 Step until next 'call' instruction in specific memory range
4011 Usage:
4012 MYNAME [keyword] [mapname1,mapname2]
4013 """
4014 (keyword, mapname) = normalize_argv(arg, 2)
4015
4016 if keyword:
4017 self.stepuntil("call.*%s" % keyword, mapname)
4018 else:
4019 self.stepuntil("call", mapname)
4020 return
4021
4022 # wrapper for stepuntil("j")
4023 def nextjmp(self, *arg):
4024 """
4025 Step until next 'j*' instruction in specific memory range
4026 Usage:
4027 MYNAME [keyword] [mapname1,mapname2]
4028 """
4029 (keyword, mapname) = normalize_argv(arg, 2)
4030
4031 if keyword:
4032 self.stepuntil("j.*%s" % keyword, mapname)
4033 else:
4034 self.stepuntil("j", mapname)
4035 return
4036
4037 #stepuntil()
4038 def tracecall(self, *arg):
4039 """
4040 Trace function calls made by the program
4041 Usage:
4042 MYNAME ["func1,func2"] [mapname1,mapname2]
4043 MYNAME ["-func1,func2"] [mapname1,mapname2] (inverse)
4044 default is to trace internal calls made by the program
4045 """
4046 (funcs, mapname) = normalize_argv(arg, 2)
4047
4048 if not self._is_running():
4049 return
4050
4051 if not mapname:
4052 mapname = "binary"
4053
4054 fnames = [""]
4055 if funcs:
4056 if to_int(funcs):
4057 funcs = "0x%x" % funcs
4058 fnames = funcs.replace(",", " ").split()
4059 for (idx, fn) in enumerate(fnames):
4060 if to_int(fn):
4061 fnames[idx] = "0x%x" % to_int(fn)
4062
4063 inverse = 0
4064 for (idx, fn) in enumerate(fnames):
4065 if fn.startswith("-"): # inverse trace
4066 fnames[idx] = fn[1:]
4067 inverse = 1
4068
4069 binname = peda.getfile()
4070 logname = peda.get_config_filename("tracelog")
4071
4072 if mapname is None:
4073 mapname = binname
4074
4075 peda.save_user_command("hook-stop") # disable hook-stop to speedup
4076 msg("Tracing calls %s '%s', Ctrl-C to stop..." % ("match" if not inverse else "not match", ",".join(fnames)))
4077 prev_depth = peda.backtrace_depth(peda.getreg("sp"))
4078
4079 logfd = open(logname, "w")
4080 while True:
4081 result = peda.stepuntil("call", mapname, prev_depth)
4082 if result is None:
4083 break
4084 (call_depth, code) = result
4085 prev_depth += call_depth
4086 if not code.startswith("=>"):
4087 break
4088
4089 if not inverse:
4090 matched = False
4091 for fn in fnames:
4092 fn = fn.strip()
4093 if re.search(fn, code.split(":\t")[-1]):
4094 matched = True
4095 break
4096 else:
4097 matched = True
4098 for fn in fnames:
4099 fn = fn.strip()
4100 if re.search(fn, code.split(":\t")[-1]):
4101 matched = False
4102 break
4103
4104 if matched:
4105 code = format_disasm_code(code)
4106 msg("%s%s%s" % (" "*(prev_depth-1), " dep:%02d " % (prev_depth-1), colorize(code.strip())), teefd=logfd)
4107 args = peda.get_function_args()
4108 if args:
4109 for (i, a) in enumerate(args):
4110 chain = peda.examine_mem_reference(a)
4111 text = "%s |-- arg[%d]: %s" % (" "*(prev_depth-1), i, format_reference_chain(chain))
4112 msg(text, teefd=logfd)
4113
4114 msg(code, "red")
4115 peda.restore_user_command("hook-stop")
4116 if "STOP" not in peda.get_status():
4117 peda.execute("stop")
4118 logfd.close()
4119 msg("Saved trace information in file %s, view with 'less -r file'" % logname)
4120 return
4121
4122 # stepuntil()
4123 def traceinst(self, *arg):
4124 """
4125 Trace specific instructions executed by the program
4126 Usage:
4127 MYNAME ["inst1,inst2"] [mapname1,mapname2]
4128 MYNAME count (trace execution of next count instrcutions)
4129 default is to trace instructions inside the program
4130 """
4131 (insts, mapname) = normalize_argv(arg, 2)
4132
4133 if not self._is_running():
4134 return
4135
4136 if not mapname:
4137 mapname = "binary"
4138
4139 instlist = [".*"]
4140 count = -1
4141 if insts:
4142 if to_int(insts):
4143 count = insts
4144 else:
4145 instlist = insts.replace(",", " ").split()
4146
4147 binname = peda.getfile()
4148 logname = peda.get_config_filename("tracelog")
4149
4150 if mapname is None:
4151 mapname = binname
4152
4153 peda.save_user_command("hook-stop") # disable hook-stop to speedup
4154 msg("Tracing instructions match '%s', Ctrl-C to stop..." % (",".join(instlist)))
4155 prev_depth = peda.backtrace_depth(peda.getreg("sp"))
4156 logfd = open(logname, "w")
4157
4158 p = re.compile(".*?:\s*[^ ]*\s*([^,]*),(.*)")
4159 while count:
4160 result = peda.stepuntil(",".join(instlist), mapname, prev_depth)
4161 if result is None:
4162 break
4163 (call_depth, code) = result
4164 prev_depth += call_depth
4165 if not code.startswith("=>"):
4166 break
4167
4168 # special case for JUMP inst
4169 prev_code = ""
4170 if re.search("j[^m]", code.split(":\t")[-1].split()[0]):
4171 prev_insts = peda.prev_inst(peda.getreg("pc"))
4172 if prev_insts:
4173 prev_code = "0x%x:%s" % prev_insts[0]
4174 msg("%s%s%s" % (" "*(prev_depth-1), " dep:%02d " % (prev_depth-1), prev_code), teefd=logfd)
4175
4176 text = "%s%s%s" % (" "*(prev_depth-1), " dep:%02d " % (prev_depth-1), code.strip())
4177 msg(text, teefd=logfd)
4178
4179 if re.search("call", code.split(":\t")[-1].split()[0]):
4180 args = peda.get_function_args()
4181 if args:
4182 for (i, a) in enumerate(args):
4183 chain = peda.examine_mem_reference(a)
4184 text = "%s |-- arg[%d]: %s" % (" "*(prev_depth-1), i, format_reference_chain(chain))
4185 msg(text, teefd=logfd)
4186
4187 # get registers info if any
4188 (arch, bits) = peda.getarch()
4189 code = code.split("#")[0].strip("=>")
4190 if prev_code:
4191 m = p.search(prev_code)
4192 else:
4193 m = p.search(code)
4194
4195 if m:
4196 for op in m.groups():
4197 if op.startswith("0x"): continue
4198 v = to_int(peda.parse_and_eval(op))
4199 chain = peda.examine_mem_reference(v)
4200 text = "%s |-- %03s: %s" % (" "*(prev_depth-1), op, format_reference_chain(chain))
4201 msg(text, teefd=logfd)
4202
4203 count -= 1
4204
4205 msg(code, "red")
4206 peda.restore_user_command("hook-stop")
4207 logfd.close()
4208 msg("Saved trace information in file %s, view with 'less -r file'" % logname)
4209 return
4210
4211 def profile(self, *arg):
4212 """
4213 Simple profiling to count executed instructions in the program
4214 Usage:
4215 MYNAME count [keyword]
4216 default is to count instructions inside the program only
4217 count = 0: run until end of execution
4218 keyword: only display stats for instructions matched it
4219 """
4220 (count, keyword) = normalize_argv(arg, 2)
4221
4222 if count is None:
4223 self._missing_argument()
4224
4225 if not self._is_running():
4226 return
4227
4228 if keyword is None or keyword == "all":
4229 keyword = ""
4230
4231 keyword = keyword.replace(" ", "").split(",")
4232
4233 peda.save_user_command("hook-stop") # disable hook-stop to speedup
4234 msg("Stepping %s instructions, Ctrl-C to stop..." % ("%d" % count if count else "all"))
4235
4236 if count == 0:
4237 count = -1
4238 stats = {}
4239 total = 0
4240 binmap = peda.get_vmmap("binary")
4241 try:
4242 while count != 0:
4243 pc = peda.getreg("pc")
4244 if not peda.is_address(pc):
4245 break
4246 code = peda.get_disasm(pc)
4247 if not code:
4248 break
4249 if peda.is_address(pc, binmap):
4250 for k in keyword:
4251 if k in code.split(":\t")[-1]:
4252 code = code.strip("=>").strip()
4253 stats.setdefault(code, 0)
4254 stats[code] += 1
4255 break
4256 peda.execute_redirect("stepi", silent=True)
4257 else:
4258 peda.execute_redirect("stepi", silent=True)
4259 peda.execute_redirect("finish", silent=True)
4260 count -= 1
4261 total += 1
4262 except:
4263 pass
4264
4265 peda.restore_user_command("hook-stop")
4266 text = "Executed %d instructions\n" % total
4267 text += "%s %s\n" % (blue("Run-count", "bold"), blue("Instruction", "bold"))
4268 for (code, count) in sorted(stats.items(), key = lambda x: x[1], reverse=True):
4269 text += "%8d: %s\n" % (count, code)
4270 pager(text)
4271
4272 return
4273
4274 @msg.bufferize
4275 def context_register(self, *arg):
4276 """
4277 Display register information of current execution context
4278 Usage:
4279 MYNAME
4280 """
4281 if not self._is_running():
4282 return
4283
4284 pc = peda.getreg("pc")
4285 # display register info
4286 msg("[%s]" % "registers".center(78, "-"), "blue")
4287 self.xinfo("register")
4288
4289 return
4290
4291 @msg.bufferize
4292 def context_code(self, *arg):
4293 """
4294 Display nearby disassembly at $PC of current execution context
4295 Usage:
4296 MYNAME [linecount]
4297 """
4298 (count,) = normalize_argv(arg, 1)
4299
4300 if count is None:
4301 size = config.Option.get("code_size")
4302 try:
4303 count = int(size)
4304 except ValueError:
4305 count = 8
4306
4307 if not self._is_running():
4308 return
4309
4310 pc = peda.getreg("pc")
4311 if peda.is_address(pc):
4312 inst = peda.get_disasm(pc)
4313 else:
4314 inst = None
4315
4316 text = blue("[%s]" % "code".center(78, "-"))
4317 msg(text)
4318 if inst: # valid $PC
4319 text = ""
4320 opcode = inst.split(":\t")[-1].split()[0]
4321 # stopped at function call
4322 if "call" in opcode:
4323 text += peda.disassemble_around(pc, count)
4324 msg(format_disasm_code(text, pc))
4325 self.dumpargs()
4326 # stopped at jump
4327 elif "j" in opcode:
4328 jumpto = peda.testjump(inst)
4329 if jumpto: # JUMP is taken
4330 code = peda.disassemble_around(pc, count)
4331 code = code.splitlines()
4332 pc_idx = 999
4333 for (idx, line) in enumerate(code):
4334 if ("0x%x" % pc) in line.split(":")[0]:
4335 pc_idx = idx
4336 if idx <= pc_idx:
4337 text += line + "\n"
4338 else:
4339 text += " | %s\n" % line.strip()
4340 text = format_disasm_code(text, pc) + "\n"
4341 text += " |->"
4342 code = peda.get_disasm(jumpto, count//2)
4343 if not code:
4344 code = " Cannot evaluate jump destination\n"
4345
4346 code = code.splitlines()
4347 text += red(code[0]) + "\n"
4348 for line in code[1:]:
4349 text += " %s\n" % line.strip()
4350 text += red("JUMP is taken".rjust(79))
4351 else: # JUMP is NOT taken
4352 text += format_disasm_code(peda.disassemble_around(pc, count), pc)
4353 text += "\n" + green("JUMP is NOT taken".rjust(79))
4354
4355 msg(text.rstrip())
4356 # stopped at other instructions
4357 else:
4358 text += peda.disassemble_around(pc, count)
4359 msg(format_disasm_code(text, pc))
4360 else: # invalid $PC
4361 msg("Invalid $PC address: 0x%x" % pc, "red")
4362
4363 return
4364
4365 @msg.bufferize
4366 def context_stack(self, *arg):
4367 """
4368 Display stack of current execution context
4369 Usage:
4370 MYNAME [linecount]
4371 """
4372 (count,) = normalize_argv(arg, 1)
4373
4374 if count is None:
4375 size = config.Option.get("stack_size")
4376 try:
4377 count = int(size)
4378 except ValueError:
4379 count = 8
4380
4381 if not self._is_running():
4382 return
4383
4384 text = blue("[%s]" % "stack".center(78, "-"))
4385 msg(text)
4386 sp = peda.getreg("sp")
4387 if peda.is_address(sp):
4388 self.telescope(sp, count)
4389 else:
4390 msg("Invalid $SP address: 0x%x" % sp, "red")
4391
4392 return
4393
4394 def context(self, *arg):
4395 """
4396 Display various information of current execution context
4397 Usage:
4398 MYNAME [reg,code,stack,all] [code/stack length]
4399 """
4400
4401 (opt, count) = normalize_argv(arg, 2)
4402
4403 if opt is None:
4404 opt = config.Option.get("context")
4405 if opt == "all":
4406 opt = "register,code,stack"
4407
4408 opt = opt.replace(" ", "").split(",")
4409
4410 if not opt:
4411 return
4412
4413 if not self._is_running():
4414 return
4415
4416 clearscr = config.Option.get("clearscr")
4417 if clearscr == "on":
4418 msg("\033[2J\033[0;0H")
4419
4420 status = peda.get_status()
4421 # display registers
4422 if "reg" in opt or "register" in opt:
4423 self.context_register()
4424
4425 # display assembly code
4426 if "code" in opt:
4427 self.context_code(count)
4428
4429 # display stack content, forced in case SIGSEGV
4430 if "stack" in opt or "SIGSEGV" in status:
4431 self.context_stack(count)
4432 msg("[%s]" % ("-"*78), "blue")
4433 msg("Legend: %s, %s, %s, value" % (red("code"), blue("data"), green("rodata")))
4434
4435 # display stopped reason
4436 if "SIG" in status:
4437 msg("Stopped reason: %s" % red(status))
4438
4439 return
4440
4441
4442 #################################
4443 # Memory Operation Commands #
4444 #################################
4445 # get_vmmap()
4446 def vmmap(self, *arg):
4447 """
4448 Get virtual mapping address ranges of section(s) in debugged process
4449 Usage:
4450 MYNAME [mapname] (e.g binary, all, libc, stack)
4451 MYNAME address (find mapname contains this address)
4452 MYNAME (equiv to cat /proc/pid/maps)
4453 """
4454
4455 (mapname,) = normalize_argv(arg, 1)
4456 if not self._is_running():
4457 maps = peda.get_vmmap()
4458 elif to_int(mapname) is None:
4459 maps = peda.get_vmmap(mapname)
4460 else:
4461 addr = to_int(mapname)
4462 maps = []
4463 allmaps = peda.get_vmmap()
4464 if allmaps is not None:
4465 for (start, end, perm, name) in allmaps:
4466 if addr >= start and addr < end:
4467 maps += [(start, end, perm, name)]
4468
4469 if maps is not None and len(maps) > 0:
4470 l = 10 if peda.intsize() == 4 else 18
4471 msg("%s %s %s\t%s" % ("Start".ljust(l, " "), "End".ljust(l, " "), "Perm", "Name"), "blue", "bold")
4472 for (start, end, perm, name) in maps:
4473 color = "red" if "rwx" in perm else None
4474 msg("%s %s %s\t%s" % (to_address(start).ljust(l, " "), to_address(end).ljust(l, " "), perm, name), color)
4475 else:
4476 warning_msg("not found or cannot access procfs")
4477 return
4478
4479 # writemem()
4480 def patch(self, *arg):
4481 """
4482 Patch memory start at an address with string/hexstring/int
4483 Usage:
4484 MYNAME address (multiple lines input)
4485 MYNAME address "string"
4486 MYNAME from_address to_address "string"
4487 MYNAME (will patch at current $pc)
4488 """
4489
4490 (address, data, byte) = normalize_argv(arg, 3)
4491 address = to_int(address)
4492 end_address = None
4493 if address is None:
4494 address = peda.getreg("pc")
4495
4496 if byte is not None and to_int(data) is not None:
4497 end_address, data = to_int(data), byte
4498 if end_address < address:
4499 address, end_address = end_address, address
4500
4501 if data is None:
4502 data = ""
4503 while True:
4504 line = input("patch> ")
4505 if line.strip() == "": continue
4506 if line == "end":
4507 break
4508 user_input = line.strip()
4509 if user_input.startswith("0x"):
4510 data += hex2str(user_input)
4511 else:
4512 data += eval("%s" % user_input)
4513
4514 if to_int(data) is not None:
4515 data = hex2str(to_int(data), peda.intsize())
4516
4517 data = to_binary_string(data)
4518 data = data.replace(b"\\\\", b"\\")
4519 if end_address:
4520 data *= (end_address-address + 1) // len(data)
4521 bytes_ = peda.writemem(address, data)
4522 if bytes_ >= 0:
4523 msg("Written %d bytes to 0x%x" % (bytes_, address))
4524 else:
4525 warning_msg("Failed to patch memory, try 'set write on' first for offline patching")
4526 return
4527
4528 # dumpmem()
4529 def dumpmem(self, *arg):
4530 """
4531 Dump content of a memory region to raw binary file
4532 Usage:
4533 MYNAME file start end
4534 MYNAME file mapname
4535 """
4536 (filename, start, end) = normalize_argv(arg, 3)
4537 if end is not None and to_int(end):
4538 if end < start:
4539 start, end = end, start
4540 ret = peda.execute("dump memory %s 0x%x 0x%x" % (filename, start, end))
4541 if not ret:
4542 warning_msg("failed to dump memory")
4543 else:
4544 msg("Dumped %d bytes to '%s'" % (end-start, filename))
4545 elif start is not None: # dump by mapname
4546 maps = peda.get_vmmap(start)
4547 if maps:
4548 fd = open(filename, "wb")
4549 count = 0
4550 for (start, end, _, _) in maps:
4551 mem = peda.dumpmem(start, end)
4552 if mem is None: # nullify unreadable memory
4553 mem = "\x00"*(end-start)
4554 fd.write(mem)
4555 count += end - start
4556 fd.close()
4557 msg("Dumped %d bytes to '%s'" % (count, filename))
4558 else:
4559 warning_msg("invalid mapname")
4560 else:
4561 self._missing_argument()
4562
4563 return
4564
4565 # loadmem()
4566 def loadmem(self, *arg):
4567 """
4568 Load contents of a raw binary file to memory
4569 Usage:
4570 MYNAME file address [size]
4571 """
4572 mem = ""
4573 (filename, address, size) = normalize_argv(arg, 3)
4574 address = to_int(address)
4575 size = to_int(size)
4576 if filename is not None:
4577 try:
4578 mem = open(filename, "rb").read()
4579 except:
4580 pass
4581 if mem == "":
4582 error_msg("cannot read data or filename is empty")
4583 return
4584 if size is not None and size < len(mem):
4585 mem = mem[:size]
4586 bytes = peda.writemem(address, mem)
4587 if bytes > 0:
4588 msg("Written %d bytes to 0x%x" % (bytes, address))
4589 else:
4590 warning_msg("failed to load filename to memory")
4591 else:
4592 self._missing_argument()
4593 return
4594
4595 # cmpmem()
4596 def cmpmem(self, *arg):
4597 """
4598 Compare content of a memory region with a file
4599 Usage:
4600 MYNAME start end file
4601 """
4602 (start, end, filename) = normalize_argv(arg, 3)
4603 if filename is None:
4604 self._missing_argument()
4605
4606 try:
4607 buf = open(filename, "rb").read()
4608 except:
4609 error_msg("cannot read data from filename %s" % filename)
4610 return
4611
4612 result = peda.cmpmem(start, end, buf)
4613
4614 if result is None:
4615 warning_msg("failed to perform comparison")
4616 elif result == {}:
4617 msg("mem and filename are identical")
4618 else:
4619 msg("--- mem: %s -> %s" % (arg[0], arg[1]), "green", "bold")
4620 msg("+++ filename: %s" % arg[2], "blue", "bold")
4621 for (addr, bytes_) in result.items():
4622 msg("@@ 0x%x @@" % addr, "red")
4623 line_1 = "- "
4624 line_2 = "+ "
4625 for (mem_val, file_val) in bytes_:
4626 m_byte = "%02X " % ord(mem_val)
4627 f_byte = "%02X " % ord(file_val)
4628 if mem_val == file_val:
4629 line_1 += m_byte
4630 line_2 += f_byte
4631 else:
4632 line_1 += green(m_byte)
4633 line_2 += blue(f_byte)
4634 msg(line_1)
4635 msg(line_2)
4636 return
4637
4638 # xormem()
4639 def xormem(self, *arg):
4640 """
4641 XOR a memory region with a key
4642 Usage:
4643 MYNAME start end key
4644 """
4645 (start, end, key) = normalize_argv(arg, 3)
4646 if key is None:
4647 self._missing_argument()
4648
4649 result = peda.xormem(start, end, key)
4650 if result is not None:
4651 msg("XORed data (first 32 bytes):")
4652 msg('"' + to_hexstr(result[:32]) + '"')
4653 return
4654
4655 # searchmem(), searchmem_by_range()
4656 def searchmem(self, *arg):
4657 """
4658 Search for a pattern in memory; support regex search
4659 Usage:
4660 MYNAME pattern start end
4661 MYNAME pattern mapname
4662 """
4663 (pattern, start, end) = normalize_argv(arg, 3)
4664 (pattern, mapname) = normalize_argv(arg, 2)
4665 if pattern is None:
4666 self._missing_argument()
4667
4668 pattern = arg[0]
4669 result = []
4670 if end is None and to_int(mapname):
4671 vmrange = peda.get_vmrange(mapname)
4672 if vmrange:
4673 (start, end, _, _) = vmrange
4674
4675 if end is None:
4676 msg("Searching for %s in: %s ranges" % (repr(pattern), mapname))
4677 result = peda.searchmem_by_range(mapname, pattern)
4678 else:
4679 msg("Searching for %s in range: 0x%x - 0x%x" % (repr(pattern), start, end))
4680 result = peda.searchmem(start, end, pattern)
4681
4682 text = peda.format_search_result(result)
4683 pager(text)
4684
4685 return
4686
4687 # search_reference()
4688 def refsearch(self, *arg):
4689 """
4690 Search for all references to a value in memory ranges
4691 Usage:
4692 MYNAME value mapname
4693 MYNAME value (search in all memory ranges)
4694 """
4695 (search, mapname) = normalize_argv(arg, 2)
4696 if search is None:
4697 self._missing_argument()
4698
4699 search = arg[0]
4700 if mapname is None:
4701 mapname = "all"
4702 msg("Searching for reference to: %s in: %s ranges" % (repr(search), mapname))
4703 result = peda.search_reference(search, mapname)
4704
4705 text = peda.format_search_result(result)
4706 pager(text)
4707
4708 return
4709
4710 # search_address(), search_pointer()
4711 def lookup(self, *arg):
4712 """
4713 Search for all addresses/references to addresses which belong to a memory range
4714 Usage:
4715 MYNAME address searchfor belongto
4716 MYNAME pointer searchfor belongto
4717 """
4718 (option, searchfor, belongto) = normalize_argv(arg, 3)
4719 if option is None:
4720 self._missing_argument()
4721
4722 result = []
4723 if searchfor is None:
4724 searchfor = "stack"
4725 if belongto is None:
4726 belongto = "binary"
4727
4728 if option == "pointer":
4729 msg("Searching for pointers on: %s pointed to: %s, this may take minutes to complete..." % (searchfor, belongto))
4730 result = peda.search_pointer(searchfor, belongto)
4731 if option == "address":
4732 msg("Searching for addresses on: %s belong to: %s, this may take minutes to complete..." % (searchfor, belongto))
4733 result = peda.search_address(searchfor, belongto)
4734
4735 text = peda.format_search_result(result, 0)
4736 pager(text)
4737
4738 return
4739 lookup.options = ["address", "pointer"]
4740
4741 # examine_mem_reference()
4742 def telescope(self, *arg):
4743 """
4744 Display memory content at an address with smart dereferences
4745 Usage:
4746 MYNAME [linecount] (analyze at current $SP)
4747 MYNAME address [linecount]
4748 """
4749
4750 (address, count) = normalize_argv(arg, 2)
4751
4752 if self._is_running():
4753 sp = peda.getreg("sp")
4754 else:
4755 sp = None
4756
4757 if count is None:
4758 count = 8
4759 if address is None:
4760 address = sp
4761 elif address < 0x1000:
4762 count = address
4763 address = sp
4764
4765 if not address:
4766 return
4767
4768 step = peda.intsize()
4769 if not peda.is_address(address): # cannot determine address
4770 for i in range(count):
4771 if not peda.execute("x/%sx 0x%x" % ("g" if step == 8 else "w", address + i*step)):
4772 break
4773 return
4774
4775 result = []
4776 for i in range(count):
4777 value = address + i*step
4778 if peda.is_address(value):
4779 result += [peda.examine_mem_reference(value)]
4780 else:
4781 result += [None]
4782 idx = 0
4783 text = ""
4784 for chain in result:
4785 text += "%04d| " % (idx)
4786 text += format_reference_chain(chain)
4787 text += "\n"
4788 idx += step
4789
4790 pager(text)
4791
4792 return
4793
4794 def eflags(self, *arg):
4795 """
4796 Display/set/clear/toggle value of eflags register
4797 Usage:
4798 MYNAME
4799 MYNAME [set|clear|toggle] flagname
4800 """
4801 FLAGS = ["CF", "PF", "AF", "ZF", "SF", "TF", "IF", "DF", "OF"]
4802 FLAGS_TEXT = ["Carry", "Parity", "Adjust", "Zero", "Sign", "Trap",
4803 "Interrupt", "Direction", "Overflow"]
4804
4805 (option, flagname) = normalize_argv(arg, 2)
4806 if not self._is_running():
4807 return
4808
4809 elif option and not flagname:
4810 self._missing_argument()
4811
4812 elif option is None: # display eflags
4813 flags = peda.get_eflags()
4814 text = ""
4815 for (i, f) in enumerate(FLAGS):
4816 if flags[f]:
4817 text += "%s " % red(FLAGS_TEXT[i].upper(), "bold")
4818 else:
4819 text += "%s " % green(FLAGS_TEXT[i].lower())
4820
4821 eflags = peda.getreg("eflags")
4822 msg("%s: 0x%x (%s)" % (green("EFLAGS"), eflags, text.strip()))
4823
4824 elif option == "set":
4825 peda.set_eflags(flagname, True)
4826
4827 elif option == "clear":
4828 peda.set_eflags(flagname, False)
4829
4830 elif option == "toggle":
4831 peda.set_eflags(flagname, None)
4832
4833 return
4834 eflags.options = ["set", "clear", "toggle"]
4835
4836 def xinfo(self, *arg):
4837 """
4838 Display detail information of address/registers
4839 Usage:
4840 MYNAME address
4841 MYNAME register [reg1 reg2]
4842 """
4843
4844 (address, regname) = normalize_argv(arg, 2)
4845 if address is None:
4846 self._missing_argument()
4847
4848 text = ""
4849 if not self._is_running():
4850 return
4851
4852 def get_reg_text(r, v):
4853 text = green("%s" % r.upper().ljust(3)) + ": "
4854 chain = peda.examine_mem_reference(v)
4855 text += format_reference_chain(chain)
4856 text += "\n"
4857 return text
4858
4859 (arch, bits) = peda.getarch()
4860 if str(address).startswith("r"):
4861 # Register
4862 regs = peda.getregs(" ".join(arg[1:]))
4863 if regname is None:
4864 for r in REGISTERS[bits]:
4865 if r in regs:
4866 text += get_reg_text(r, regs[r])
4867 else:
4868 for (r, v) in sorted(regs.items()):
4869 text += get_reg_text(r, v)
4870 if text:
4871 msg(text.strip())
4872 if regname is None or "eflags" in regname:
4873 self.eflags()
4874 return
4875
4876 elif to_int(address) is None:
4877 warning_msg("not a register nor an address")
4878 else:
4879 # Address
4880 chain = peda.examine_mem_reference(address, depth=0)
4881 text += format_reference_chain(chain) + "\n"
4882 vmrange = peda.get_vmrange(address)
4883 if vmrange:
4884 (start, end, perm, name) = vmrange
4885 text += "Virtual memory mapping:\n"
4886 text += green("Start : %s\n" % to_address(start))
4887 text += green("End : %s\n" % to_address(end))
4888 text += yellow("Offset: 0x%x\n" % (address-start))
4889 text += red("Perm : %s\n" % perm)
4890 text += blue("Name : %s" % name)
4891 msg(text)
4892
4893 return
4894 xinfo.options = ["register"]
4895
4896 def strings(self, *arg):
4897 """
4898 Display printable strings in memory
4899 Usage:
4900 MYNAME start end [minlen]
4901 MYNAME mapname [minlen]
4902 MYNAME (display all printable strings in binary - slow)
4903 """
4904 (start, end, minlen) = normalize_argv(arg, 3)
4905
4906 mapname = None
4907 if start is None:
4908 mapname = "binary"
4909 elif to_int(start) is None or (end < start):
4910 (mapname, minlen) = normalize_argv(arg, 2)
4911
4912 if minlen is None:
4913 minlen = 1
4914
4915 if mapname:
4916 maps = peda.get_vmmap(mapname)
4917 else:
4918 maps = [(start, end, None, None)]
4919
4920 if not maps:
4921 warning_msg("failed to get memory map for %s" % mapname)
4922 return
4923
4924 text = ""
4925 regex_pattern = "[%s]{%d,}" % (re.escape(string.printable), minlen)
4926 p = re.compile(regex_pattern.encode('utf-8'))
4927 for (start, end, _, _) in maps:
4928 mem = peda.dumpmem(start, end)
4929 if not mem: continue
4930 found = p.finditer(mem)
4931 if not found: continue
4932
4933 for m in found:
4934 text += "0x%x: %s\n" % (start+m.start(), string_repr(mem[m.start():m.end()].strip(), show_quotes=False))
4935
4936 pager(text)
4937 return
4938
4939 def sgrep(self, *arg):
4940 """
4941 Search for full strings contain the given pattern
4942 Usage:
4943 MYNAME pattern start end
4944 MYNAME pattern mapname
4945 MYNAME pattern
4946 """
4947 (pattern,) = normalize_argv(arg, 1)
4948
4949 if pattern is None:
4950 self._missing_argument()
4951 arg = list(arg[1:])
4952 if not arg:
4953 arg = ["binary"]
4954
4955 pattern = "[^\x00]*%s[^\x00]*" % pattern
4956 self.searchmem(pattern, *arg)
4957
4958 return
4959
4960
4961 ###############################
4962 # Exploit Helper Commands #
4963 ###############################
4964 # elfheader()
4965 def elfheader(self, *arg):
4966 """
4967 Get headers information from debugged ELF file
4968 Usage:
4969 MYNAME [header_name]
4970 """
4971
4972 (name,) = normalize_argv(arg, 1)
4973 result = peda.elfheader(name)
4974 if len(result) == 0:
4975 warning_msg("%s not found, did you specify the FILE to debug?" % (name if name else "headers"))
4976 elif len(result) == 1:
4977 (k, (start, end, type)) = list(result.items())[0]
4978 msg("%s: 0x%x - 0x%x (%s)" % (k, start, end, type))
4979 else:
4980 for (k, (start, end, type)) in sorted(result.items(), key=lambda x: x[1]):
4981 msg("%s = 0x%x" % (k, start))
4982 return
4983
4984 # readelf_header(), elfheader_solib()
4985 def readelf(self, *arg):
4986 """
4987 Get headers information from an ELF file
4988 Usage:
4989 MYNAME mapname [header_name]
4990 MYNAME filename [header_name]
4991 """
4992
4993 (filename, hname) = normalize_argv(arg, 2)
4994 result = {}
4995 maps = peda.get_vmmap()
4996 if filename is None: # fallback to elfheader()
4997 result = peda.elfheader()
4998 else:
4999 result = peda.elfheader_solib(filename, hname)
5000
5001 if not result:
5002 result = peda.readelf_header(filename, hname)
5003 if len(result) == 0:
5004 warning_msg("%s or %s not found" % (filename, hname))
5005 elif len(result) == 1:
5006 (k, (start, end, type)) = list(result.items())[0]
5007 msg("%s: 0x%x - 0x%x (%s)" % (k, start, end, type))
5008 else:
5009 for (k, (start, end, type)) in sorted(result.items(), key=lambda x: x[1]):
5010 msg("%s = 0x%x" % (k, start))
5011 return
5012
5013 # elfsymbol()
5014 def elfsymbol(self, *arg):
5015 """
5016 Get non-debugging symbol information from an ELF file
5017 Usage:
5018 MYNAME symbol_name
5019 """
5020 (name,) = normalize_argv(arg, 1)
5021 if not peda.getfile():
5022 warning_msg("please specify a file to debug")
5023 return
5024
5025 result = peda.elfsymbol(name)
5026 if len(result) == 0:
5027 msg("'%s': no match found" % (name if name else "plt symbols"))
5028 else:
5029 if ("%s@got" % name) not in result:
5030 msg("Found %d symbols" % len(result))
5031 else:
5032 msg("Detail symbol info")
5033 for (k, v) in sorted(result.items(), key=lambda x: x[1]):
5034 msg("%s = %s" % (k, "0x%x" % v if v else repr(v)))
5035 return
5036
5037 # checksec()
5038 def checksec(self, *arg):
5039 """
5040 Check for various security options of binary
5041 For full features, use http://www.trapkit.de/tools/checksec.sh
5042 Usage:
5043 MYNAME [file]
5044 """
5045 (filename,) = normalize_argv(arg, 1)
5046 colorcodes = {
5047 0: red("disabled"),
5048 1: green("ENABLED"),
5049 2: yellow("Partial"),
5050 3: green("FULL"),
5051 4: yellow("Dynamic Shared Object"),
5052 }
5053
5054 result = peda.checksec(filename)
5055 if result:
5056 for (k, v) in sorted(result.items()):
5057 msg("%s: %s" % (k.ljust(10), colorcodes[v]))
5058 return
5059
5060 def nxtest(self, *arg):
5061 """
5062 Perform real NX test to see if it is enabled/supported by OS
5063 Usage:
5064 MYNAME [address]
5065 """
5066 (address,) = normalize_argv(arg, 1)
5067
5068 exec_wrapper = peda.execute_redirect("show exec-wrapper").split('"')[1]
5069 if exec_wrapper != "":
5070 peda.execute("unset exec-wrapper")
5071
5072 if not peda.getpid(): # start program if not running
5073 peda.execute("start")
5074
5075 # set current PC => address, continue
5076 pc = peda.getreg("pc")
5077 sp = peda.getreg("sp")
5078 if not address:
5079 address = sp
5080 peda.execute("set $pc = 0x%x" % address)
5081 # set value at address => 0xcc
5082 peda.execute("set *0x%x = 0x%x" % (address, 0xcccccccc))
5083 peda.execute("set *0x%x = 0x%x" % (address+4, 0xcccccccc))
5084 out = peda.execute_redirect("continue")
5085 text = "NX test at %s: " % (to_address(address) if address != sp else "stack")
5086
5087 if out:
5088 if "SIGSEGV" in out:
5089 text += red("Non-Executable")
5090 elif "SIGTRAP" in out:
5091 text += green("Executable")
5092 else:
5093 text += "Failed to test"
5094
5095 msg(text)
5096 # restore exec-wrapper
5097 if exec_wrapper != "":
5098 peda.execute("set exec-wrapper %s" % exec_wrapper)
5099
5100 return
5101
5102 # search_asm()
5103 def asmsearch(self, *arg):
5104 """
5105 Search for ASM instructions in memory
5106 Usage:
5107 MYNAME "asmcode" start end
5108 MYNAME "asmcode" mapname
5109 """
5110 (asmcode, start, end) = normalize_argv(arg, 3)
5111 if asmcode is None:
5112 self._missing_argument()
5113
5114 if not self._is_running():
5115 return
5116
5117 asmcode = arg[0]
5118 result = []
5119 if end is None:
5120 mapname = start
5121 if mapname is None:
5122 mapname = "binary"
5123 maps = peda.get_vmmap(mapname)
5124 msg("Searching for ASM code: %s in: %s ranges" % (repr(asmcode), mapname))
5125 for (start, end, _, _) in maps:
5126 if not peda.is_executable(start, maps): continue # skip non-executable page
5127 result += peda.search_asm(start, end, asmcode)
5128 else:
5129 msg("Searching for ASM code: %s in range: 0x%x - 0x%x" % (repr(asmcode), start, end))
5130 result = peda.search_asm(start, end, asmcode)
5131
5132 text = "Not found"
5133 if result:
5134 text = ""
5135 for (addr, (byte, code)) in result:
5136 text += "%s : (%s)\t%s\n" % (to_address(addr), byte.decode('utf-8'), code)
5137 pager(text)
5138
5139 return
5140
5141 # search_asm()
5142 def ropsearch(self, *arg):
5143 """
5144 Search for ROP gadgets in memory
5145 Note: only for simple gadgets, for full ROP search try: http://ropshell.com
5146 Usage:
5147 MYNAME "gadget" start end
5148 MYNAME "gadget" pagename
5149 """
5150
5151 (asmcode, start, end) = normalize_argv(arg, 3)
5152 if asmcode is None:
5153 self._missing_argument()
5154
5155 if not self._is_running():
5156 return
5157
5158 asmcode = arg[0]
5159 result = []
5160 if end is None:
5161 if start is None:
5162 mapname = "binary"
5163 else:
5164 mapname = start
5165 maps = peda.get_vmmap(mapname)
5166 msg("Searching for ROP gadget: %s in: %s ranges" % (repr(asmcode), mapname))
5167 for (start, end, _, _) in maps:
5168 if not peda.is_executable(start, maps): continue # skip non-executable page
5169 result += peda.search_asm(start, end, asmcode, rop=1)
5170 else:
5171 msg("Searching for ROP gadget: %s in range: 0x%x - 0x%x" % (repr(asmcode), start, end))
5172 result = peda.search_asm(start, end, asmcode, rop=1)
5173
5174 result = sorted(result, key=lambda x: len(x[1][0]))
5175 text = "Not found"
5176 if result:
5177 text = ""
5178 for (addr, (byte, code)) in result:
5179 text += "%s : (%s)\t%s\n" % (to_address(addr), byte, code)
5180 pager(text)
5181
5182 return
5183
5184 # dumprop()
5185 def dumprop(self, *arg):
5186 """
5187 Dump all ROP gadgets in specific memory range
5188 Note: only for simple gadgets, for full ROP search try: http://ropshell.com
5189 Warning: this can be very slow, do not run for big memory range
5190 Usage:
5191 MYNAME start end [keyword] [depth]
5192 MYNAME mapname [keyword]
5193 default gadget instruction depth is: 5
5194 """
5195
5196 (start, end, keyword, depth) = normalize_argv(arg, 4)
5197 filename = peda.getfile()
5198 if filename is None:
5199 warning_msg("please specify a filename to debug")
5200 return
5201
5202 filename = os.path.basename(filename)
5203 mapname = None
5204 if start is None:
5205 mapname = "binary"
5206 elif end is None:
5207 mapname = start
5208 elif to_int(end) is None:
5209 mapname = start
5210 keyword = end
5211
5212 if depth is None:
5213 depth = 5
5214
5215 result = {}
5216 warning_msg("this can be very slow, do not run for large memory range")
5217 if mapname:
5218 maps = peda.get_vmmap(mapname)
5219 for (start, end, _, _) in maps:
5220 if not peda.is_executable(start, maps): continue # skip non-executable page
5221 result.update(peda.dumprop(start, end, keyword))
5222 else:
5223 result.update(peda.dumprop(start, end, keyword))
5224
5225 text = "Not found"
5226 if len(result) > 0:
5227 text = ""
5228 outfile = "%s-rop.txt" % filename
5229 fd = open(outfile, "w")
5230 msg("Writing ROP gadgets to file: %s ..." % outfile)
5231 for (code, addr) in sorted(result.items(), key = lambda x:len(x[0])):
5232 text += "0x%x: %s\n" % (addr, code)
5233 fd.write("0x%x: %s\n" % (addr, code))
5234 fd.close()
5235
5236 pager(text)
5237 return
5238
5239 # common_rop_gadget()
5240 def ropgadget(self, *arg):
5241 """
5242 Get common ROP gadgets of binary or library
5243 Usage:
5244 MYNAME [mapname]
5245 """
5246
5247 (mapname,) = normalize_argv(arg, 1)
5248 result = peda.common_rop_gadget(mapname)
5249 if not result:
5250 msg("Not found")
5251 else:
5252 text = ""
5253 for (k, v) in sorted(result.items(), key=lambda x: len(x[0]) if not x[0].startswith("add") else int(x[0].split("_")[1])):
5254 text += "%s = 0x%x\n" % (k, v)
5255 pager(text)
5256
5257 return
5258
5259 # search_jmpcall()
5260 def jmpcall(self, *arg):
5261 """
5262 Search for JMP/CALL instructions in memory
5263 Usage:
5264 MYNAME (search all JMP/CALL in current binary)
5265 MYNAME reg [mapname]
5266 MYNAME reg start end
5267 """
5268
5269 (reg, start, end) = normalize_argv(arg, 3)
5270 result = []
5271 if not self._is_running():
5272 return
5273
5274 mapname = None
5275 if start is None:
5276 mapname = "binary"
5277 elif end is None:
5278 mapname = start
5279
5280 if mapname:
5281 maps = peda.get_vmmap(mapname)
5282 for (start, end, _, _) in maps:
5283 if not peda.is_executable(start, maps): continue
5284 result += peda.search_jmpcall(start, end, reg)
5285 else:
5286 result = peda.search_jmpcall(start, end, reg)
5287
5288 if not result:
5289 msg("Not found")
5290 else:
5291 text = ""
5292 for (a, v) in result:
5293 text += "0x%x : %s\n" % (a, v)
5294 pager(text)
5295
5296 return
5297
5298 # cyclic_pattern()
5299 def pattern_create(self, *arg):
5300 """
5301 Generate a cyclic pattern
5302 Set "pattern" option for basic/extended pattern type
5303 Usage:
5304 MYNAME size [file]
5305 """
5306
5307 (size, filename) = normalize_argv(arg, 2)
5308 if size is None:
5309 self._missing_argument()
5310
5311 pattern = cyclic_pattern(size)
5312 if filename is not None:
5313 open(filename, "wb").write(pattern)
5314 msg("Writing pattern of %d chars to filename \"%s\"" % (len(pattern), filename))
5315 else:
5316 msg("'" + pattern.decode('utf-8') + "'")
5317
5318 return
5319
5320 # cyclic_pattern()
5321 def pattern_offset(self, *arg):
5322 """
5323 Search for offset of a value in cyclic pattern
5324 Set "pattern" option for basic/extended pattern type
5325 Usage:
5326 MYNAME value
5327 """
5328
5329 (value,) = normalize_argv(arg, 1)
5330 if value is None:
5331 self._missing_argument()
5332
5333 pos = cyclic_pattern_offset(value)
5334 if pos is None:
5335 msg("%s not found in pattern buffer" % value)
5336 else:
5337 msg("%s found at offset: %d" % (value, pos))
5338
5339 return
5340
5341 # cyclic_pattern(), searchmem_*()
5342 def pattern_search(self, *arg):
5343 """
5344 Search a cyclic pattern in registers and memory
5345 Set "pattern" option for basic/extended pattern type
5346 Usage:
5347 MYNAME
5348 """
5349 def nearby_offset(v):
5350 for offset in range(-128, 128, 4):
5351 pos = cyclic_pattern_offset(v + offset)
5352 if pos is not None:
5353 return (pos, offset)
5354 return None
5355
5356 if not self._is_running():
5357 return
5358
5359 reg_result = {}
5360 regs = peda.getregs()
5361
5362 # search for registers with value in pattern buffer
5363 for (r, v) in regs.items():
5364 if len(to_hex(v)) < 8: continue
5365 res = nearby_offset(v)
5366 if res:
5367 reg_result[r] = res
5368
5369 if reg_result:
5370 msg("Registers contain pattern buffer:", "red")
5371 for (r, (p, o)) in reg_result.items():
5372 msg("%s+%d found at offset: %d" % (r.upper(), o, p))
5373 else:
5374 msg("No register contains pattern buffer")
5375
5376 # search for registers which point to pattern buffer
5377 reg_result = {}
5378 for (r, v) in regs.items():
5379 if not peda.is_address(v): continue
5380 chain = peda.examine_mem_reference(v)
5381 (v, t, vn) = chain[-1]
5382 if not vn: continue
5383 o = cyclic_pattern_offset(vn.strip("'").strip('"')[:4])
5384 if o is not None:
5385 reg_result[r] = (len(chain), len(vn)-2, o)
5386
5387 if reg_result:
5388 msg("Registers point to pattern buffer:", "yellow")
5389 for (r, (d, l, o)) in reg_result.items():
5390 msg("[%s] %s offset %d - size ~%d" % (r.upper(), "-->"*d, o, l))
5391 else:
5392 msg("No register points to pattern buffer")
5393
5394 # search for pattern buffer in memory
5395 maps = peda.get_vmmap()
5396 search_result = []
5397 for (start, end, perm, name) in maps:
5398 if "w" not in perm: continue # only search in writable memory
5399 res = cyclic_pattern_search(peda.dumpmem(start, end))
5400 for (a, l, o) in res:
5401 a += start
5402 search_result += [(a, l, o)]
5403
5404 sp = peda.getreg("sp")
5405 if search_result:
5406 msg("Pattern buffer found at:", "green")
5407 for (a, l, o) in search_result:
5408 ranges = peda.get_vmrange(a)
5409 text = "%s : offset %4d - size %4d" % (to_address(a), o, l)
5410 if ranges[3] == "[stack]":
5411 text += " ($sp + %s [%d dwords])" % (to_hex(a-sp), (a-sp)//4)
5412 else:
5413 text += " (%s)" % ranges[3]
5414 msg(text)
5415 else:
5416 msg("Pattern buffer not found in memory")
5417
5418 # search for references to pattern buffer in memory
5419 ref_result = []
5420 for (a, l, o) in search_result:
5421 res = peda.searchmem_by_range("all", "0x%x" % a)
5422 ref_result += [(x[0], a) for x in res]
5423 if len(ref_result) > 0:
5424 msg("References to pattern buffer found at:", "blue")
5425 for (a, v) in ref_result:
5426 ranges = peda.get_vmrange(a)
5427 text = "%s : %s" % (to_address(a), to_address(v))
5428 if ranges[3] == "[stack]":
5429 text += " ($sp + %s [%d dwords])" % (to_hex(a-sp), (a-sp)//4)
5430 else:
5431 text += " (%s)" % ranges[3]
5432 msg(text)
5433 else:
5434 msg("Reference to pattern buffer not found in memory")
5435
5436 return
5437
5438 # cyclic_pattern(), writemem()
5439 def pattern_patch(self, *arg):
5440 """
5441 Write a cyclic pattern to memory
5442 Set "pattern" option for basic/extended pattern type
5443 Usage:
5444 MYNAME address size
5445 """
5446
5447 (address, size) = normalize_argv(arg, 2)
5448 if size is None:
5449 self._missing_argument()
5450
5451 pattern = cyclic_pattern(size)
5452 num_bytes_written = peda.writemem(address, pattern)
5453 if num_bytes_written:
5454 msg("Written %d chars of cyclic pattern to 0x%x" % (size, address))
5455 else:
5456 msg("Failed to write to memory")
5457
5458 return
5459
5460 # cyclic_pattern()
5461 def pattern_arg(self, *arg):
5462 """
5463 Set argument list with cyclic pattern
5464 Set "pattern" option for basic/extended pattern type
5465 Usage:
5466 MYNAME size1 [size2,offset2] ...
5467 """
5468
5469 if not arg:
5470 self._missing_argument()
5471
5472 arglist = []
5473 for a in arg:
5474 (size, offset) = (a + ",").split(",")[:2]
5475 if offset:
5476 offset = to_int(offset)
5477 else:
5478 offset = 0
5479 size = to_int(size)
5480 if size is None or offset is None:
5481 self._missing_argument()
5482
5483 # try to generate unique, non-overlapped patterns
5484 if arglist and offset == 0:
5485 offset = sum(arglist[-1])
5486 arglist += [(size, offset)]
5487
5488 patterns = []
5489 for (s, o) in arglist:
5490 patterns += ["\'%s\'" % cyclic_pattern(s, o).decode('utf-8')]
5491 peda.execute("set arg %s" % " ".join(patterns))
5492 msg("Set %d arguments to program" % len(patterns))
5493
5494 return
5495
5496 # cyclic_pattern()
5497 def pattern_env(self, *arg):
5498 """
5499 Set environment variable with a cyclic pattern
5500 Set "pattern" option for basic/extended pattern type
5501 Usage:
5502 MYNAME ENVNAME size[,offset]
5503 """
5504
5505 (env, size) = normalize_argv(arg, 2)
5506 if size is None:
5507 self._missing_argument()
5508
5509 (size, offset) = (arg[1] + ",").split(",")[:2]
5510 size = to_int(size)
5511 if offset:
5512 offset = to_int(offset)
5513 else:
5514 offset = 0
5515 if size is None or offset is None:
5516 self._missing_argument()
5517
5518 peda.execute("set env %s %s" % (env, cyclic_pattern(size, offset).decode('utf-8')))
5519 msg("Set environment %s = cyclic_pattern(%d, %d)" % (env, size, offset))
5520
5521 return
5522
5523 def pattern(self, *arg):
5524 """
5525 Generate, search, or write a cyclic pattern to memory
5526 Set "pattern" option for basic/extended pattern type
5527 Usage:
5528 MYNAME create size [file]
5529 MYNAME offset value
5530 MYNAME search
5531 MYNAME patch address size
5532 MYNAME arg size1 [size2,offset2]
5533 MYNAME env size[,offset]
5534 """
5535
5536 options = ["create", "offset", "search", "patch", "arg", "env"]
5537 (opt,) = normalize_argv(arg, 1)
5538 if opt is None or opt not in options:
5539 self._missing_argument()
5540
5541 func = getattr(self, "pattern_%s" % opt)
5542 func(*arg[1:])
5543
5544 return
5545 pattern.options = ["create", "offset", "search", "patch", "arg", "env"]
5546
5547 def substr(self, *arg):
5548 """
5549 Search for substrings of a given string/number in memory
5550 Commonly used for ret2strcpy ROP exploit
5551 Usage:
5552 MYNAME "string" start end
5553 MYNAME "string" [mapname] (default is search in current binary)
5554 """
5555 (search, start, end) = normalize_argv(arg, 3)
5556 if search is None:
5557 self._missing_argument()
5558
5559 result = []
5560 search = arg[0]
5561 mapname = None
5562 if start is None:
5563 mapname = "binary"
5564 elif end is None:
5565 mapname = start
5566
5567 if mapname:
5568 msg("Searching for sub strings of: %s in: %s ranges" % (repr(search), mapname))
5569 maps = peda.get_vmmap(mapname)
5570 for (start, end, perm, _) in maps:
5571 if perm == "---p": # skip private range
5572 continue
5573 result = peda.search_substr(start, end, search)
5574 if result: # return the first found result
5575 break
5576 else:
5577 msg("Searching for sub strings of: %s in range: 0x%x - 0x%x" % (repr(search), start, end))
5578 result = peda.search_substr(start, end, search)
5579
5580 if result:
5581 msg("# (address, target_offset), # value (address=0xffffffff means not found)")
5582 offset = 0
5583 for (k, v) in result:
5584 msg("(0x%x, %d), # %s" % ((0xffffffff if v == -1 else v), offset, string_repr(k)))
5585 offset += len(k)
5586 else:
5587 msg("Not found")
5588
5589 return
5590
5591 def assemble(self, *arg):
5592 """
5593 On the fly assemble and execute instructions using NASM
5594 Usage:
5595 MYNAME [mode] [address]
5596 mode: -b16 / -b32 / -b64
5597 """
5598 (mode, address) = normalize_argv(arg, 2)
5599
5600 exec_mode = 0
5601 write_mode = 0
5602 if to_int(mode) is not None:
5603 address, mode = mode, None
5604
5605 (arch, bits) = peda.getarch()
5606 if mode is None:
5607 mode = bits
5608 else:
5609 mode = to_int(mode[2:])
5610 if mode not in [16, 32, 64]:
5611 self._missing_argument()
5612
5613 if self._is_running() and address == peda.getreg("pc"):
5614 write_mode = exec_mode = 1
5615
5616 line = peda.execute_redirect("show write")
5617 if line and "on" in line.split()[-1]:
5618 write_mode = 1
5619
5620 if address is None or mode != bits:
5621 write_mode = exec_mode = 0
5622
5623 if write_mode:
5624 msg("Instruction will be written to 0x%x" % address)
5625 else:
5626 msg("Instructions will be written to stdout")
5627
5628 msg("Type instructions (NASM syntax), one or more per line separated by \";\"")
5629 msg("End with a line saying just \"end\"")
5630
5631 if not write_mode:
5632 address = 0xdeadbeef
5633
5634 inst_list = []
5635 inst_code = b""
5636 # fetch instruction loop
5637 while True:
5638 inst = input("iasm|0x%x> " % address)
5639 if inst == "end":
5640 break
5641 if inst == "":
5642 continue
5643 bincode = peda.assemble(inst, mode)
5644 size = len(bincode)
5645 if size == 0:
5646 continue
5647 inst_list.append((size, bincode, inst))
5648 if write_mode:
5649 peda.writemem(address, bincode)
5650 # execute assembled code
5651 if exec_mode:
5652 peda.execute("stepi %d" % (inst.count(";")+1))
5653
5654 address += size
5655 inst_code += bincode
5656 msg("hexify: \"%s\"" % to_hexstr(bincode))
5657
5658 text = Nasm.format_shellcode(b"".join([x[1] for x in inst_list]), mode)
5659 if text:
5660 msg("Assembled%s instructions:" % ("/Executed" if exec_mode else ""))
5661 msg(text)
5662 msg("hexify: \"%s\"" % to_hexstr(inst_code))
5663
5664 return
5665
5666
5667 ####################################
5668 # Payload/Shellcode Generation #
5669 ####################################
5670 def skeleton(self, *arg):
5671 """
5672 Generate python exploit code template
5673 Usage:
5674 MYNAME type [file]
5675 type = argv: local exploit via argument
5676 type = env: local exploit via crafted environment (including NULL byte)
5677 type = stdin: local exploit via stdin
5678 type = remote: remote exploit via TCP socket
5679 """
5680 options = ["argv", "stdin", "env", "remote"]
5681 (opt, outfile) = normalize_argv(arg, 2)
5682 if opt not in options:
5683 self._missing_argument()
5684
5685 pattern = cyclic_pattern(20000).decode('utf-8')
5686 if opt == "argv":
5687 code = ExploitSkeleton().skeleton_local_argv
5688 if opt == "env":
5689 code = ExploitSkeleton().skeleton_local_env
5690 if opt == "stdin":
5691 code = ExploitSkeleton().skeleton_local_stdin
5692 if opt == "remote":
5693 code = ExploitSkeleton().skeleton_remote_tcp
5694
5695 if outfile:
5696 msg("Writing skeleton code to file \"%s\"" % outfile)
5697 open(outfile, "w").write(code.strip("\n"))
5698 os.chmod(outfile, 0o755)
5699 open("pattern.txt", "w").write(pattern)
5700 else:
5701 msg(code)
5702
5703 return
5704 skeleton.options = ["argv", "stdin", "env", "remote"]
5705
5706 def shellcode(self, *arg):
5707 """
5708 Generate or download common shellcodes.
5709 Usage:
5710 MYNAME generate [arch/]platform type [port] [host]
5711 MYNAME search keyword (use % for any character wildcard)
5712 MYNAME display shellcodeId (shellcodeId as appears in search results)
5713 MYNAME zsc [generate customize shellcode]
5714
5715 For generate option:
5716 default port for bindport shellcode: 16706 (0x4142)
5717 default host/port for connect back shellcode: 127.127.127.127/16706
5718 supported arch: x86
5719 """
5720 def list_shellcode():
5721 """
5722 List available shellcodes
5723 """
5724 text = "Available shellcodes:\n"
5725 for arch in SHELLCODES:
5726 for platform in SHELLCODES[arch]:
5727 for sctype in SHELLCODES[arch][platform]:
5728 text += " %s/%s %s\n" % (arch, platform, sctype)
5729 msg(text)
5730
5731 """ Multiple variable name for different modes """
5732 (mode, platform, sctype, port, host) = normalize_argv(arg, 5)
5733 (mode, keyword) = normalize_argv(arg, 2)
5734 (mode, shellcodeId) = normalize_argv(arg, 2)
5735
5736 if mode == "generate":
5737 arch = "x86"
5738 if platform and "/" in platform:
5739 (arch, platform) = platform.split("/")
5740
5741 if platform not in SHELLCODES[arch] or not sctype:
5742 list_shellcode()
5743 return
5744 #dbg_print_vars(arch, platform, sctype, port, host)
5745 try:
5746 sc = Shellcode(arch, platform).shellcode(sctype, port, host)
5747 except Exception as e:
5748 self._missing_argument()
5749
5750 if not sc:
5751 msg("Unknown shellcode")
5752 return
5753
5754 hexstr = to_hexstr(sc)
5755 linelen = 16 # display 16-bytes per line
5756 i = 0
5757 text = "# %s/%s/%s: %d bytes\n" % (arch, platform, sctype, len(sc))
5758 if sctype in ["bindport", "connect"]:
5759 text += "# port=%s, host=%s\n" % (port if port else '16706', host if host else '127.127.127.127')
5760 text += "shellcode = (\n"
5761 while hexstr:
5762 text += ' "%s"\n' % (hexstr[:linelen*4])
5763 hexstr = hexstr[linelen*4:]
5764 i += 1
5765 text += ")"
5766 msg(text)
5767
5768 # search shellcodes on shell-storm.org
5769 elif mode == "search":
5770 if keyword is None:
5771 self._missing_argument()
5772
5773 res_dl = Shellcode().search(keyword)
5774 if not res_dl:
5775 msg("Shellcode not found or cannot retrieve the result")
5776 return
5777
5778 msg("Found %d shellcodes" % len(res_dl))
5779 msg("%s\t%s" %(blue("ScId"), blue("Title")))
5780 text = ""
5781 for data_d in res_dl:
5782 text += "[%s]\t%s - %s\n" %(yellow(data_d['ScId']), data_d['ScArch'], data_d['ScTitle'])
5783 pager(text)
5784
5785 # download shellcodes from shell-storm.org
5786 elif mode == "display":
5787 if to_int(shellcodeId) is None:
5788 self._missing_argument()
5789
5790 res = Shellcode().display(shellcodeId)
5791 if not res:
5792 msg("Shellcode id not found or cannot retrieve the result")
5793 return
5794
5795 msg(res)
5796 #OWASP ZSC API Z3r0D4y.Com
5797 elif mode == "zsc":
5798 'os lists'
5799 oslist = ['linux_x86','linux_x64','linux_arm','linux_mips','freebsd_x86',
5800 'freebsd_x64','windows_x86','windows_x64','osx','solaris_x64','solaris_x86']
5801 'functions'
5802 joblist = ['exec(\'/path/file\')','chmod(\'/path/file\',\'permission number\')','write(\'/path/file\',\'text to write\')',
5803 'file_create(\'/path/file\',\'text to write\')','dir_create(\'/path/folder\')','download(\'url\',\'filename\')',
5804 'download_execute(\'url\',\'filename\',\'command to execute\')','system(\'command to execute\')']
5805 'encode types'
5806 encodelist = ['none','xor_random','xor_yourvalue','add_random','add_yourvalue','sub_random',
5807 'sub_yourvalue','inc','inc_timeyouwant','dec','dec_timeyouwant','mix_all']
5808 try:
5809 while True:
5810 for os in oslist:
5811 msg('%s %s'%(yellow('[+]'),green(os)))
5812 if pyversion is 2:
5813 os = input('%s'%blue('os:'))
5814 if pyversion is 3:
5815 os = input('%s'%blue('os:'))
5816 if os in oslist: #check if os exist
5817 break
5818 else:
5819 warning_msg("Wrong input! Try Again.")
5820 while True:
5821 for job in joblist:
5822 msg('%s %s'%(yellow('[+]'),green(job)))
5823 if pyversion is 2:
5824 job = raw_input('%s'%blue('job:'))
5825 if pyversion is 3:
5826 job = input('%s'%blue('job:'))
5827 if job != '':
5828 break
5829 else:
5830 warning_msg("Please enter a function.")
5831 while True:
5832 for encode in encodelist:
5833 msg('%s %s'%(yellow('[+]'),green(encode)))
5834 if pyversion is 2:
5835 encode = raw_input('%s'%blue('encode:'))
5836 if pyversion is 3:
5837 encode = input('%s'%blue('encode:'))
5838 if encode != '':
5839 break
5840 else:
5841 warning_msg("Please enter a encode type.")
5842 except (KeyboardInterrupt, SystemExit):
5843 warning_msg("Aborted by user")
5844 result = Shellcode().zsc(os,job,encode)
5845 if result is not None:
5846 msg(result)
5847 else:
5848 pass
5849 return
5850 else:
5851 self._missing_argument()
5852
5853 return
5854 shellcode.options = ["generate", "search", "display","zsc"]
5855
5856 def gennop(self, *arg):
5857 """
5858 Generate abitrary length NOP sled using given characters
5859 Usage:
5860 MYNAME size [chars]
5861 """
5862 (size, chars) = normalize_argv(arg, 2)
5863 if size is None:
5864 self._missing_argument()
5865
5866 nops = Shellcode.gennop(size, chars)
5867 msg(repr(nops))
5868
5869 return
5870
5871 def payload(self, *arg):
5872 """
5873 Generate various type of ROP payload using ret2plt
5874 Usage:
5875 MYNAME copybytes (generate function template for ret2strcpy style payload)
5876 MYNAME copybytes dest1 data1 dest2 data2 ...
5877 """
5878 (option,) = normalize_argv(arg, 1)
5879 if option is None:
5880 self._missing_argument()
5881
5882 if option == "copybytes":
5883 result = peda.payload_copybytes(template=1) # function template
5884 arg = arg[1:]
5885 while len(arg) > 0:
5886 (target, data) = normalize_argv(arg, 2)
5887 if data is None:
5888 break
5889 if to_int(data) is None:
5890 if data[0] == "[" and data[-1] == "]":
5891 data = eval(data)
5892 data = list2hexstr(data, peda.intsize())
5893 else:
5894 data = "0x%x" % data
5895 result += peda.payload_copybytes(target, data)
5896 arg = arg[2:]
5897
5898 if not result:
5899 msg("Failed to construct payload")
5900 else:
5901 text = ""
5902 indent = to_int(config.Option.get("indent"))
5903 for line in result.splitlines():
5904 text += " "*indent + line + "\n"
5905 msg(text)
5906 filename = peda.get_config_filename("payload")
5907 open(filename, "w").write(text)
5908
5909 return
5910 payload.options = ["copybytes"]
5911
5912 def snapshot(self, *arg):
5913 """
5914 Save/restore process's snapshot to/from file
5915 Usage:
5916 MYNAME save file
5917 MYNAME restore file
5918 Warning: this is not thread safe, do not use with multithread program
5919 """
5920 options = ["save", "restore"]
5921 (opt, filename) = normalize_argv(arg, 2)
5922 if opt not in options:
5923 self._missing_argument()
5924
5925 if not filename:
5926 filename = peda.get_config_filename("snapshot")
5927
5928 if opt == "save":
5929 if peda.save_snapshot(filename):
5930 msg("Saved process's snapshot to filename '%s'" % filename)
5931 else:
5932 msg("Failed to save process's snapshot")
5933
5934 if opt == "restore":
5935 if peda.restore_snapshot(filename):
5936 msg("Restored process's snapshot from filename '%s'" % filename)
5937 peda.execute("stop")
5938 else:
5939 msg("Failed to restore process's snapshot")
5940
5941 return
5942 snapshot.options = ["save", "restore"]
5943
5944 def crashdump(self, *arg):
5945 """
5946 Display crashdump info and save to file
5947 Usage:
5948 MYNAME [reason_text]
5949 """
5950 (reason,) = normalize_argv(arg, 1)
5951 if not reason:
5952 reason = "Interactive dump"
5953
5954 logname = peda.get_config_filename("crashlog")
5955 logfd = open(logname, "a")
5956 config.Option.set("_teefd", logfd)
5957 msg("[%s]" % "START OF CRASH DUMP".center(78, "-"))
5958 msg("Timestamp: %s" % time.ctime())
5959 msg("Reason: %s" % red(reason))
5960
5961 # exploitability
5962 pc = peda.getreg("pc")
5963 if not peda.is_address(pc):
5964 exp = red("EXPLOITABLE")
5965 else:
5966 exp = "Unknown"
5967 msg("Exploitability: %s" % exp)
5968
5969 # registers, code, stack
5970 self.context_register()
5971 self.context_code(16)
5972 self.context_stack()
5973
5974 # backtrace
5975 msg("[%s]" % "backtrace (innermost 10 frames)".center(78, "-"), "blue")
5976 msg(peda.execute_redirect("backtrace 10"))
5977
5978 msg("[%s]\n" % "END OF CRASH DUMP".center(78, "-"))
5979 config.Option.set("_teefd", "")
5980 logfd.close()
5981
5982 return
5983
5984 def utils(self, *arg):
5985 """
5986 Miscelaneous utilities from utils module
5987 Usage:
5988 MYNAME command arg
5989 """
5990 (command, carg) = normalize_argv(arg, 2)
5991 cmds = ["int2hexstr", "list2hexstr", "str2intlist"]
5992 if not command or command not in cmds or not carg:
5993 self._missing_argument()
5994
5995 func = globals()[command]
5996 if command == "int2hexstr":
5997 if to_int(carg) is None:
5998 msg("Not a number")
5999 return
6000 result = func(to_int(carg))
6001 result = to_hexstr(result)
6002
6003 if command == "list2hexstr":
6004 if to_int(carg) is not None:
6005 msg("Not a list")
6006 return
6007 result = func(eval("%s" % carg))
6008 result = to_hexstr(result)
6009
6010 if command == "str2intlist":
6011 res = func(carg)
6012 result = "["
6013 for v in res:
6014 result += "%s, " % to_hex(v)
6015 result = result.rstrip(", ") + "]"
6016
6017 msg(result)
6018 return
6019 utils.options = ["int2hexstr", "list2hexstr", "str2intlist"]
6020
6021###########################################################################
6022class pedaGDBCommand(gdb.Command):
6023 """
6024 Wrapper of gdb.Command for master "peda" command
6025 """
6026 def __init__(self, cmdname="peda"):
6027 self.cmdname = cmdname
6028 self.__doc__ = pedacmd._get_helptext()
6029 super(pedaGDBCommand, self).__init__(self.cmdname, gdb.COMMAND_DATA)
6030
6031 def invoke(self, arg_string, from_tty):
6032 # do not repeat command
6033 self.dont_repeat()
6034 arg = peda.string_to_argv(arg_string)
6035 if len(arg) < 1:
6036 pedacmd.help()
6037 else:
6038 cmd = arg[0]
6039 if cmd in pedacmd.commands:
6040 func = getattr(pedacmd, cmd)
6041 try:
6042 # reset memoized cache
6043 reset_cache(sys.modules['__main__'])
6044 func(*arg[1:])
6045 except Exception as e:
6046 if config.Option.get("debug") == "on":
6047 msg("Exception: %s" %e)
6048 traceback.print_exc()
6049 peda.restore_user_command("all")
6050 pedacmd.help(cmd)
6051 else:
6052 msg("Undefined command: %s. Try \"peda help\"" % cmd)
6053 return
6054
6055 def complete(self, text, word):
6056 completion = []
6057 if text != "":
6058 cmd = text.split()[0]
6059 if cmd in pedacmd.commands:
6060 func = getattr(pedacmd, cmd)
6061 for opt in func.options:
6062 if word in opt:
6063 completion += [opt]
6064 else:
6065 for cmd in pedacmd.commands:
6066 if cmd.startswith(text.strip()):
6067 completion += [cmd]
6068 else:
6069 for cmd in pedacmd.commands:
6070 if word in cmd and cmd not in completion:
6071 completion += [cmd]
6072 return completion
6073
6074
6075###########################################################################
6076class Alias(gdb.Command):
6077 """
6078 Generic alias, create short command names
6079 This doc should be changed dynamically
6080 """
6081 def __init__(self, alias, command, shorttext=1):
6082 (cmd, opt) = (command + " ").split(" ", 1)
6083 if cmd == "peda" or cmd == "pead":
6084 cmd = opt.split(" ")[0]
6085 if not shorttext:
6086 self.__doc__ = pedacmd._get_helptext(cmd)
6087 else:
6088 self.__doc__ = green("Alias for '%s'" % command)
6089 self._command = command
6090 self._alias = alias
6091 super(Alias, self).__init__(alias, gdb.COMMAND_NONE)
6092
6093 def invoke(self, args, from_tty):
6094 self.dont_repeat()
6095 gdb.execute("%s %s" %(self._command, args))
6096
6097 def complete(self, text, word):
6098 completion = []
6099 cmd = self._command.split("peda ")[1]
6100 for opt in getattr(pedacmd, cmd).options: # list of command's options
6101 if text in opt and opt not in completion:
6102 completion += [opt]
6103 if completion != []:
6104 return completion
6105 if cmd in ["set", "show"] and text.split()[0] in ["option"]:
6106 opname = [x for x in config.OPTIONS.keys() if x.startswith(word.strip())]
6107 if opname != []:
6108 completion = opname
6109 else:
6110 completion = list(config.OPTIONS.keys())
6111 return completion
6112
6113
6114###########################################################################
6115## INITIALIZATION ##
6116# global instances of PEDA() and PEDACmd()
6117peda = PEDA()
6118pedacmd = PEDACmd()
6119pedacmd.help.__func__.options = pedacmd.commands # XXX HACK
6120
6121# load configuration file
6122pedacmd.load()
6123
6124# register "peda" command in gdb
6125pedaGDBCommand()
6126Alias("pead", "peda") # just for auto correction
6127
6128# create aliases for subcommands
6129for cmd in pedacmd.commands:
6130 func = getattr(pedacmd, cmd)
6131 func.__func__.__doc__ = func.__doc__.replace("MYNAME", cmd)
6132 if cmd not in ["help", "show", "set"]:
6133 Alias(cmd, "peda %s" % cmd, 0)
6134
6135# handle SIGINT / Ctrl-C
6136def sigint_handler(signal, frame):
6137 warning_msg("Got Ctrl+C / SIGINT!")
6138 gdb.execute("set logging off")
6139 peda.restore_user_command("all")
6140 raise KeyboardInterrupt
6141signal.signal(signal.SIGINT, sigint_handler)
6142
6143# custom hooks
6144peda.define_user_command("hook-stop",
6145 "peda context\n"
6146 "session autosave"
6147 )
6148
6149# common used shell commands aliases
6150shellcmds = ["man", "ls", "ps", "grep", "cat", "more", "less", "pkill", "clear", "vi", "nano"]
6151for cmd in shellcmds:
6152 Alias(cmd, "shell %s" % cmd)
6153
6154# custom command aliases, add any alias you want
6155Alias("phelp", "peda help")
6156Alias("psave", "peda save")
6157Alias("pload", "peda load")
6158Alias("pset", "peda set")
6159Alias("pshow", "peda show")
6160Alias("pbreak", "peda pltbreak")
6161Alias("pattc", "peda pattern_create")
6162Alias("patto", "peda pattern_offset")
6163Alias("patta", "peda pattern_arg")
6164Alias("patte", "peda pattern_env")
6165Alias("patts", "peda pattern_search")
6166Alias("find", "peda searchmem") # override gdb find command
6167Alias("ftrace", "peda tracecall")
6168Alias("itrace", "peda traceinst")
6169Alias("jtrace", "peda traceinst j")
6170Alias("stack", "peda telescope $sp")
6171Alias("viewmem", "peda telescope")
6172Alias("reg", "peda xinfo register")
6173
6174# misc gdb settings
6175peda.execute("set confirm off")
6176peda.execute("set verbose off")
6177peda.execute("set output-radix 0x10")
6178peda.execute("set prompt \001%s\002" % red("\002gdb-peda$ \001")) # custom prompt
6179peda.execute("set height 0") # disable paging
6180peda.execute("set history expansion on")
6181peda.execute("set history save on") # enable history saving
6182peda.execute("set disassembly-flavor intel")
6183peda.execute("set follow-fork-mode child")
6184peda.execute("set backtrace past-main on")
6185peda.execute("set step-mode on")
6186peda.execute("set print pretty on")
6187peda.execute("handle SIGALRM print nopass") # ignore SIGALRM
6188peda.execute("handle SIGSEGV stop print nopass") # catch SIGSEGV