forked from OpenRCE/paimei
-
Notifications
You must be signed in to change notification settings - Fork 1
/
heap_trace.py
255 lines (170 loc) · 6.5 KB
/
heap_trace.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
#!c:\python\python.exe
# $Id: heap_trace.py 231 2008-07-21 22:43:36Z pedram.amini $
# TODO - need to add race condition testing and hook de-activation testing.
from pydbg import *
from pydbg.defines import *
import pgraph
import utils
import sys
import getopt
USAGE = "USAGE: heap_trace.py <-p|--pid PID> | <-l|--load filename>" \
"\n [-g|--graph] enable graphing" \
"\n [-m|--monitor] enabe heap integrity checking" \
"\n [-h|--host udraw host] udraw host (for graphing), def:127.0.0.1" \
"\n [-o|--port udraw port] udraw port (for graphing), def:2542"
ERROR = lambda msg: sys.stderr.write("ERROR> " + msg + "\n") or sys.exit(1)
class __alloc:
call_stack = []
size = 0
def access_violation (dbg):
crash_bin = utils.crash_binning.crash_binning()
crash_bin.record_crash(dbg)
print "***** process access violated *****"
print crash_bin.crash_synopsis()
dbg.terminate_process()
def dll_load_handler (dbg):
global hooks
try:
last_dll = dbg.get_system_dll(-1)
except:
return
if last_dll.name.lower() == "ntdll.dll":
addrRtlAllocateHeap = dbg.func_resolve_debuggee("ntdll", "RtlAllocateHeap")
addrRtlFreeHeap = dbg.func_resolve_debuggee("ntdll", "RtlFreeHeap")
addrRtlReAllocateHeap = dbg.func_resolve_debuggee("ntdll", "RtlReAllocateHeap")
hooks.add(dbg, addrRtlAllocateHeap, 3, None, RtlAllocateHeap)
hooks.add(dbg, addrRtlFreeHeap, 3, None, RtlFreeHeap)
hooks.add(dbg, addrRtlReAllocateHeap, 4, None, RtlReAllocateHeap)
print "rtl heap manipulation routines successfully hooked"
return DBG_CONTINUE
def graph_connect (dbg, buff_addr, size, realloc=False):
global count, graph
count += 1
eip = dbg.context.Eip
allocator = pgraph.node(eip)
allocated = pgraph.node(buff_addr)
allocator.label = "%08x" % eip
allocated.label = "%d" % size
allocated.size = size
allocator.color = 0xFFAC59
if realloc:
allocated.color = 0x46FF46
else:
allocated.color = 0x59ACFF
graph.add_node(allocator)
graph.add_node(allocated)
edge = pgraph.edge(allocator.id, allocated.id)
edge.label = "%d" % count
graph.add_edge(edge)
def graph_update (id, focus_first=False):
global graph, udraw
if udraw:
if focus_first:
udraw.focus_node(id)
udraw.graph_new(graph)
if not focus_first:
udraw.focus_node(id)
def monitor_add (dbg, address, size):
global monitor, allocs
if not monitor:
return
alloc = __alloc()
alloc.size = size
alloc.call_stack = dbg.stack_unwind()
allocs[address] = alloc
dbg.bp_set_mem(address+size+1, 1, handler=monitor_bp)
def monitor_bp (dbg):
global allocs
print "heap bound exceeded at %08x by %08x" % (dbg.violation_address, dbg.exception_address)
for call in dbg.stack_unwind():
print "\t%08x" % call
# determine which chunk was violated.
for addr, alloc in allocs.iteritems():
if addr + alloc.size < dbg.violation_address < addr + alloc.size + 4:
violated_chunk = addr
break
print "violated chunk:"
print "0x%08x: %d" % (violated_chunk, allocs[violated_chunk].size)
for call in allocs[violated_chunk].call_stack:
print "\t%08x" % call
raw_input("")
# XXX - add check for Rtl addresses in call stack and ignore
def monitor_print ():
for addr, alloc in allocs.iteritems():
print "0x%08x: %d" % (addr, alloc.size)
for call in alloc.call_stack:
print "\t%08x" % call
def monitor_remove (dbg, address):
global monitor, allocs
if not monitor:
return
del allocs[address]
monitor_print()
def outstanding_bytes ():
outstanding = 0
for node in graph.nodes.values():
if hasattr(node, "size"):
outstanding += node.size
return outstanding
def RtlAllocateHeap (dbg, args, ret):
global graph
# heap id, flags, size
print "[%04d] %08x: RtlAllocateHeap(%08x, %08x, %d) == %08x" % (len(graph.nodes), dbg.context.Eip, args[0], args[1], args[2], ret)
monitor_add(dbg, ret, args[2])
graph_connect(dbg, ret, args[2])
graph_update(dbg.context.Eip)
def RtlFreeHeap (dbg, args, ret):
global graph
# heap id, flags, address
print "[%04d] %08x: RtlFreeHeap(%08x, %08x, %08x) == %08x" % (len(graph.nodes), dbg.context.Eip, args[0], args[1], args[2], ret)
print "%d bytes outstanding" % outstanding_bytes()
monitor_remove(dbg, args[2])
for edge in graph.edges_to(args[2]):
graph.del_edge(edge.id)
graph.del_node(args[2])
graph_update(args[2], True)
def RtlReAllocateHeap (dbg, args, ret):
global graph
# heap id, flags, address, new size
print "[%04d] %08x: RtlReAllocateHeap(%08x, %08x, %08x, %d) == %08x" % (len(graph.nodes), dbg.context.Eip, args[0], args[1], args[2], args[3], ret)
monitor_remove(dbg, args[2])
monitor_add(dbg, ret, args[3])
graph.del_node(args[2])
graph_connect(dbg, ret, args[3], realloc=True)
graph_update(dbg.context.Eip)
# parse command line options.
try:
opts, args = getopt.getopt(sys.argv[1:], "gh:o:l:mp:", ["graph", "host=", "monitor", "port=", "pid="])
except getopt.GetoptError:
ERROR(USAGE)
count = 0
udraw = False
host = "127.0.0.1"
port = 2542
filename = None
pid = None
udraw = None
graph = pgraph.graph()
hooks = utils.hook_container()
monitor = False
allocs = {}
for opt, arg in opts:
if opt in ("-g", "--graph"): udraw = True
if opt in ("-h", "--host"): host = arg
if opt in ("-o", "--port"): port = int(arg)
if opt in ("-l", "--load"): filename = arg
if opt in ("-p", "--pid"): pid = int(arg)
if opt in ("-m", "--monitor"): monitor = True
if not pid and not filename:
ERROR(USAGE)
if udraw:
udraw = utils.udraw_connector(host, port)
print "connection to udraw established..."
dbg = pydbg()
if pid:
dbg.attach(pid)
else:
dbg.load(filename)
dbg.set_callback(EXCEPTION_ACCESS_VIOLATION, access_violation)
dbg.set_callback(LOAD_DLL_DEBUG_EVENT, dll_load_handler)
dbg.run()