Skip to content

Commit 6bc9f09

Browse files
committed
initial commit
0 parents  commit 6bc9f09

27 files changed

+1857
-0
lines changed

.gitignore

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# created by virtualenv automatically
2+
bin/
3+
lib/
4+
pwntools-doc/
5+
pyvenv.cfg
6+
examples/gdb_script.log
7+
examples/trace_example.py
8+
examples/.gdb_history
9+
.gdb_history

dbgtools/__init__.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from dbgtools.main import *
2+
from dbgtools.breakpoints import *
3+
from dbgtools import logger
4+
from dbgtools import gdbapi
5+
from dbgtools import commands

dbgtools/breakpoints.py

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import gdb
2+
from typing import Sequence, Callable, Optional
3+
from dbgtools.main import get_pie_base
4+
from dbgtools.logger import Logger
5+
6+
7+
def get_all_breakpoints() -> Sequence[gdb.Breakpoint]:
8+
return gdb.breakpoints()
9+
10+
11+
def convert_to_gdb_bp_str(ptr: Optional[int] = None,
12+
func_name: Optional[str] = None,
13+
offset: Optional[int] = None) -> str:
14+
if ptr is not None:
15+
assert func_name is None and offset is None
16+
return f"*{hex(ptr)}"
17+
elif func_name is not None and offset is not None:
18+
return f"*({func_name}+{hex(offset)})"
19+
else:
20+
msg = "Breakpoint string has to consist of either <pointer> or " \
21+
+ "<func_name, offset>"
22+
raise ValueError(msg)
23+
24+
25+
class CustomBreakpoint(gdb.Breakpoint):
26+
_action_funcs: list[Callable[[], None]]
27+
_explicit_stop: bool
28+
29+
def __init__(self,
30+
bp_str: str,
31+
action_funcs: Optional[list[Callable[[], None]]] = None,
32+
enabled: bool = True,
33+
explicit_stop: bool = False,
34+
temporary: bool = False):
35+
super().__init__(bp_str, temporary=temporary)
36+
if action_funcs is None:
37+
action_funcs = []
38+
self._make_unique()
39+
self._bp_str = bp_str
40+
self.enabled = enabled
41+
self._explicit_stop = explicit_stop
42+
self._action_funcs = action_funcs
43+
self._cond_func = None
44+
45+
@classmethod
46+
def create_pt_bp(cls, ptr, *args, **kwargs):
47+
return cls(convert_to_gdb_bp_str(ptr=ptr), *args, **kwargs)
48+
49+
@classmethod
50+
def create_pie_bp(cls, ptr, *args, **kwargs):
51+
return cls(convert_to_gdb_bp_str(ptr=get_pie_base() + ptr),
52+
*args, **kwargs)
53+
54+
@classmethod
55+
def create_func_off_bp(cls, func_name, offset, *args, **kwargs):
56+
return cls(convert_to_gdb_bp_str(func_name=func_name, offset=offset),
57+
*args, **kwargs)
58+
59+
def _make_unique(self):
60+
for bp in get_all_breakpoints():
61+
if bp.number != self.number:
62+
bp.delete()
63+
64+
def set_condition_func(self, cond_func):
65+
self._cond_func = cond_func
66+
67+
def reset_condition_func(self):
68+
self._cond_func = None
69+
70+
def stop(self):
71+
for bp_stop_func in self._action_funcs:
72+
bp_stop_func()
73+
if self._explicit_stop:
74+
return True
75+
if self._cond_func is not None:
76+
return self._cond_func()
77+
return False
78+
79+
80+
class LogBreakpoint(CustomBreakpoint):
81+
logger_func: Callable[[], bytes]
82+
83+
def __init__(self,
84+
bp_str: str,
85+
logger_func: Callable[[], bytes],
86+
action_funcs: Optional[list[Callable[[], None]]] =None,
87+
enabled_default: bool = True,
88+
explicit_stop: bool = False,
89+
temporary: bool = False):
90+
super().__init__(bp_str,
91+
action_funcs,
92+
enabled_default,
93+
explicit_stop,
94+
temporary)
95+
self._logger_func = logger_func
96+
97+
def stop(self):
98+
logger = Logger.get_instance()
99+
log = self._logger_func()
100+
if len(log) != 0:
101+
logger.log_line(log)
102+
return super().stop()
103+
104+
105+
class ActionBreakpoint(CustomBreakpoint):
106+
def __init__(self,
107+
bp_str: str,
108+
action_funcs: Optional[list[Callable[[], None]]] ,
109+
explicit_stop: bool = False):
110+
super().__init__(bp_str,
111+
action_funcs,
112+
enabled_default=True,
113+
explicit_stop=explicit_stop,
114+
temporary=False)

dbgtools/commands/breaknew.py

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import gdb
2+
from dbgtools.main import delete_all_breakpoints
3+
from dbgtools.gdbapi import execute_commands
4+
5+
6+
class BreakNewCmd(gdb.Command):
7+
"""Creates a breakpoint and delete all previous ones"""
8+
def __init__(self):
9+
super(BreakNewCmd, self).__init__("bn", gdb.COMMAND_USER)
10+
11+
def help(self):
12+
print("Usage: bn <bp>")
13+
14+
def invoke(self, argument, from_tty):
15+
argument = argument.split()
16+
if len(argument) != 1:
17+
self.help()
18+
else:
19+
delete_all_breakpoints()
20+
execute_commands([f"b {argument[0]}"])

dbgtools/commands/breakpie.py

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import gdb
2+
from dbgtools.main import force_load_pie_base, get_pie_base, \
3+
set_manual_breakpoint
4+
from dbgtools.commands.utils import parse_tint
5+
6+
7+
class BreakPIECmd(gdb.Command):
8+
"""
9+
Creates a breakpoint relative to the current PIE base.
10+
11+
While this is supported by pwndbg, we want to keep it because it works
12+
before program start.
13+
"""
14+
def __init__(self):
15+
super(BreakPIECmd, self).__init__("bpie", gdb.COMMAND_USER)
16+
17+
def help(self):
18+
print("Usage: bpie <relative bp offset>")
19+
20+
def invoke(self, argument, from_tty):
21+
argument = argument.split()
22+
if len(argument) != 1:
23+
self.help()
24+
else:
25+
piebase = get_pie_base()
26+
if piebase is None:
27+
# program not running probably
28+
print("Current PIE base could not be found.\n" +
29+
"Do you want to try and force PIE base loading (program will be executed!)")
30+
choice = input("[y/n] > ")
31+
if len(choice) >= 1 and choice[0].lower() == "y":
32+
piebase = force_load_pie_base()
33+
if piebase is None:
34+
print("Could not force load PIE base")
35+
return
36+
else:
37+
return
38+
39+
bp_off = parse_tint(argument[0])
40+
set_manual_breakpoint(piebase + bp_off)

dbgtools/commands/commands.py

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from dbgtools.commands.domalloc import DoMallocCmd
2+
from dbgtools.commands.dofree import DoFreeCmd
3+
from dbgtools.commands.libcbase import LibcBaseCmd
4+
from dbgtools.commands.heapbase import HeapBaseCmd
5+
from dbgtools.commands.libcsym import LibcSymCmd
6+
from dbgtools.commands.pwndump import PwnDumpCmd
7+
from dbgtools.commands.traceheap import TraceHeapCmd
8+
from dbgtools.commands.heaplookup import HeapPtrLookup
9+
from dbgtools.commands.tracer import TracerCmd
10+
from dbgtools.commands.breaknew import BreakNewCmd
11+
from dbgtools.commands.breakpie import BreakPIECmd
12+
from dbgtools.commands.mmap import MmapCmd
13+
from dbgtools.commands.mprotect import MprotectCmd
14+
15+
16+
17+
DoMallocCmd()
18+
DoFreeCmd()
19+
LibcBaseCmd()
20+
HeapBaseCmd()
21+
LibcSymCmd()
22+
PwnDumpCmd()
23+
TraceHeapCmd()
24+
HeapPtrLookup()
25+
TracerCmd()
26+
BreakNewCmd()
27+
BreakPIECmd()
28+
MmapCmd()
29+
MprotectCmd()

dbgtools/commands/dofree.py

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import gdb
2+
from dbgtools.commands.utils import SupressedOutput, parse_tint
3+
from dbgtools.main import *
4+
5+
6+
class DoFreeCmd(gdb.Command):
7+
"""Performs free(ptr)"""
8+
def __init__(self):
9+
super(DoFreeCmd, self).__init__("dofree", gdb.COMMAND_USER)
10+
11+
def invoke(self, argument, from_tty):
12+
argument = argument.split()
13+
if len(argument) != 1:
14+
self.help()
15+
else:
16+
ptr = parse_tint(argument[0])
17+
try:
18+
self._do_free(ptr)
19+
except ValueError:
20+
print("Address of free could not be found. Please specify it"
21+
+ " manually!")
22+
23+
def _do_free(self, ptr: int):
24+
with SupressedOutput():
25+
free_addr = get_free_addr()
26+
call_func1(free_addr, ptr)
27+
gdb.execute(f"x/4gx {hex(ptr)}")
28+
29+
def help(self):
30+
print("Usage: dofree <ptr> [<ptr to free function>]")

dbgtools/commands/domalloc.py

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import gdb
2+
from dbgtools.commands.utils import SupressedOutput, parse_tint
3+
from dbgtools.main import get_malloc_addr, call_func1
4+
5+
class DoMallocCmd(gdb.Command):
6+
"""Performs malloc(size)"""
7+
def __init__(self):
8+
super(DoMallocCmd, self).__init__("domalloc", gdb.COMMAND_USER)
9+
10+
def invoke(self, argument, from_tty):
11+
argument = argument.split()
12+
if len(argument) != 1:
13+
self.help()
14+
else:
15+
size = parse_tint(argument[0])
16+
try:
17+
self._do_malloc(size)
18+
except ValueError:
19+
print("Address of malloc could not be found. Please specify it manually!")
20+
21+
def _do_malloc(self, size: int):
22+
with SupressedOutput():
23+
malloc_addr = get_malloc_addr()
24+
malloc_chunk = call_func1(malloc_addr, size)
25+
gdb.execute(f"x/{(size//8)+1}gx {hex(malloc_chunk)}")
26+
print(f"malloc({hex(size)}) -> {hex(malloc_chunk)}")
27+
28+
def help(self):
29+
print("Usage: domalloc <size> [<ptr to malloc function>]")

dbgtools/commands/heapbase.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import gdb
2+
from dbgtools.commands.utils import parse_tint
3+
from dbgtools.main import get_heap_base
4+
5+
6+
class HeapBaseCmd(gdb.Command):
7+
"""Looks up the base address of all existing heaps"""
8+
def __init__(self):
9+
super(HeapBaseCmd, self).__init__("heapbase", gdb.COMMAND_USER)
10+
11+
def invoke(self, argument, from_tty):
12+
argument = argument.split()
13+
if len(argument) != 1 and len(argument) != 0:
14+
self.help()
15+
else:
16+
heap_addresses = get_heap_base()
17+
if heap_addresses is None:
18+
print("no heap found")
19+
elif len(heap_addresses) == 1 and heap_addresses[0] != -1:
20+
heap_ptr = heap_addresses[0]
21+
if len(argument) == 1:
22+
ptr = parse_tint(argument[0])
23+
print(f"heap @ {hex(heap_ptr)} | off: {hex(ptr - heap_ptr)}")
24+
else:
25+
print(f"heap @ {hex(heap_ptr)}")
26+
elif len(heap_addresses) >= 2:
27+
print("Found multiple heaps")
28+
for hp in heap_addresses:
29+
print(f"heap @ {hex(hp)}")
30+
31+
def help(self):
32+
print("Usage: heapbase [<ptr>]")

0 commit comments

Comments
 (0)