├── HeapView.py
├── README.md
├── pintool
├── makefile
├── makefile.rules
├── obj-ia32
│ └── pintool.so
├── obj-intel64
│ └── pintool.so
└── pintool.cpp
└── tests
├── Makefile
├── test1.c
├── test1.svg
├── test2.c
├── test2.svg
├── test3.c
└── test3.svg
/HeapView.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python2
2 | from copy import deepcopy
3 | import random
4 | import sys
5 |
6 | class Chunk():
7 | def __init__(self,start,end,dstart,dend):
8 | self.start = start
9 | self.end = end
10 | self.dstart = dstart
11 | self.dend = dend
12 | self.free = False
13 | self.color = random_color()
14 | def get_color(self):
15 | if self.free == False:
16 | return self.color
17 | else:
18 | return (240,240,240)
19 | def get_start(self):
20 | return self.start
21 | def get_dstart(self):
22 | return self.dstart
23 | def get_dend(self):
24 | return self.dend
25 | def get_end(self):
26 | return self.end
27 | def set_free(self):
28 | self.free = True
29 | def isFree(self):
30 | return self.free
31 | def __repr__(self):
32 | return "Chunk(%#x,%#x,data(%#x,%#x),free=%d)" % (self.start,self.end,self.dstart,self.dend,self.free)
33 | def overflow(self,addr):
34 | if addr > self.start and addr <= self.dstart:
35 | overflow=addr-self.start
36 | s="s" if overflow > 1 else ""
37 | return "next_chunk (%d byte%s)" % (overflow,s)
38 | if addr > self.dend and addr < self.end:
39 | overflow=addr-self.dend
40 | s="s" if overflow > 1 else ""
41 | return "padding (%d byte%s)" % (overflow,s)
42 | return None
43 |
44 | class MemoryWrite():
45 | def __init__(self,name,start,end):
46 | self.name = name
47 | self.start = start
48 | self.end = end
49 | def get_name(self):
50 | return self.name
51 | def get_start(self):
52 | return self.start
53 | def get_end(self):
54 | return self.end
55 | def __repr__(self):
56 | return "%s(%#x->%#x)" % (self.name,self.start,self.end)
57 |
58 | class State():
59 | def __init__(self,operation,chunks,memory_writes,min_addr,max_addr):
60 | self.operation=operation
61 | self.chunks = chunks
62 | self.memory_writes = memory_writes
63 | self.max_addr=max_addr
64 | self.min_addr=min_addr
65 | def get_chunks(self):
66 | return self.chunks
67 | def get_memoryWrites(self):
68 | return self.memory_writes
69 | def get_name(self):
70 | return self.operation.replace("<","<").replace(">",">")
71 | def get_min_addr(self):
72 | return self.min_addr
73 | def get_max_addr(self):
74 | return self.max_addr
75 |
76 | def svg_header(height):
77 | return '''
78 | '''
85 |
86 | def svg_style():
87 | return ''''''
106 |
107 | def svg_script():
108 | return '''
160 | '''
161 |
162 | def svg_rec(x,y,width,height,rx,ry,colors,opacity=1,boldtext="",text=""):
163 | if width < 4:
164 | width=4
165 | x-=2
166 | svg='''\n''' % (colors[0],colors[1],colors[2],opacity,rx,ry,height,width,y,x,boldtext,text)
172 | return svg
173 |
174 | def svg_dashed_rec(x,y,width,height,rx,ry,colors,opacity=1,boldtext="",text=""):
175 | svg='''\n''' % (colors[0],colors[1],colors[2],opacity,rx,ry,height,width,y,x,boldtext,text)
182 | return svg
183 |
184 | def svg_text(x,y,text,bold="100",color=(0,0,0)):
185 | svg='''%s\n''' % (bold,color[0],color[1],color[2],x,y,text)
186 | return svg
187 |
188 | def svg_info(x,y,text):
189 | return '''
190 |
194 | ''' % (x,y,text)
195 |
196 | def parse_arg(arg):
197 | if arg is None:
198 | return None
199 | if arg == "":
200 | return 0
201 | try:
202 | arg=int(arg,0)
203 | except:
204 | pass
205 | return arg
206 |
207 | def parse_call(call_line):
208 | args = []
209 | name = call_line.split("(")[0]
210 | ret = call_line.split(" = ")[-1]
211 | call_args = call_line.split("(")[1].split(")")[0].split(",")
212 | for arg in [ret,]+call_args:
213 | args.append(parse_arg(arg))
214 | return (name,args)
215 |
216 | def parse_ltrace(data):
217 | chunks = []
218 | states = []
219 | max_addr = 0
220 | min_addr = 0xFFFFFFFFFFFFFFFF
221 |
222 | last_write_function=""
223 | last_write_function_info=""
224 |
225 | lines = data.split("\n")
226 | for line in lines:
227 | memory_writes = None
228 | if line is "":
229 | continue
230 | (call_name,call_args) = parse_call(line)
231 | if call_name == "malloc":
232 | chunk_start = call_args[0]-8
233 | data_start = call_args[0]
234 | data_end = call_args[0]+call_args[1]
235 | size = call_args[1] + 8 + (0x10-1)
236 | size = size - (size%0x10)
237 | if size < 0x20:
238 | size=0x20
239 | chunk_end=chunk_start + size
240 | chunks.append(Chunk(chunk_start,chunk_end,data_start,data_end))
241 | if chunk_end > max_addr:
242 | max_addr=chunk_end
243 | if chunk_start < min_addr:
244 | min_addr=chunk_start
245 | elif call_name == "free":
246 | # find chunk
247 | free_ok=False
248 | for chunk in chunks:
249 | if chunk.get_dstart() == call_args[1] and not chunk.isFree():
250 | chunk.set_free()
251 | free_ok=True
252 | if not free_ok:
253 | continue
254 | elif call_name == "memset":
255 | start=call_args[1]
256 | end=call_args[1]+call_args[3]
257 | for chunk in chunks:
258 | if start >= chunk.get_dstart() and start <= chunk.get_dend():
259 | memory_writes=MemoryWrite(call_name,start,end)
260 | break
261 | elif call_name == "memcpy":
262 | start=call_args[1]
263 | end=call_args[1]+call_args[3]
264 | for chunk in chunks:
265 | if start >= chunk.get_dstart() and start <= chunk.get_dend():
266 | memory_writes=MemoryWrite(call_name,start,end)
267 | break
268 | elif call_name == "memory_write":
269 | start=call_args[2]
270 | end=start+call_args[3]
271 | function = call_args[1].split("+")[0]
272 | if last_write_function == function:
273 | if last_write_size+last_write_addr == start:
274 | last_write_size+=call_args[3]
275 | continue
276 | else:
277 | for chunk in chunks:
278 | if start >= chunk.get_dstart() and start <= chunk.get_dend():
279 | memory_writes=MemoryWrite(last_write_function,last_write_addr,last_write_addr+last_write_size)
280 | break
281 | if last_write_function not in ("_int_malloc","_int_free","malloc_consolidate"):
282 | states.append(State("<"+last_write_function_info+">",deepcopy(chunks),memory_writes,min_addr,max_addr))
283 | last_write_addr = start
284 | last_write_size = call_args[3]
285 | last_write_function=function
286 | last_write_function_info=call_args[1]
287 | else:
288 | if last_write_function != "":
289 | for chunk in chunks:
290 | if start >= chunk.get_dstart() and start <= chunk.get_dend():
291 | memory_writes=MemoryWrite(last_write_function,last_write_addr,last_write_addr+last_write_size)
292 | break
293 | if last_write_function not in ("_int_malloc","_int_free","malloc_consolidate"):
294 | states.append(State("<"+last_write_function_info+">",deepcopy(chunks),memory_writes,min_addr,max_addr))
295 | last_write_addr = start
296 | last_write_size = call_args[3]
297 | last_write_function=function
298 | last_write_function_info=call_args[1]
299 | continue
300 | else:
301 | continue
302 | states.append(State(line,deepcopy(chunks),memory_writes,min_addr,max_addr))
303 |
304 | return states
305 |
306 | def state_to_svg(pos,state,min_addr,max_addr):
307 | svg=""
308 | line_pos = 100*pos + 5*pos
309 | svg+=svg_rec(5, line_pos, 1010, 100, 10, 10, (255,255,255))
310 | svg+=svg_text(10,line_pos+15,state.get_name(),bold="bold")
311 | for chunk_nb,chunk in enumerate(state.get_chunks()):
312 | overlap=0
313 | for nb,check_chunk in enumerate(state.get_chunks()):
314 | if nb >= chunk_nb:
315 | break
316 | if chunk.get_start() >= check_chunk.get_start() and chunk.get_start() < check_chunk.get_end():
317 | overlap+=1;
318 | chunk_size = chunk.get_end() - chunk.get_start()
319 | size = int(((chunk_size * 1.0) / (max_addr - min_addr * 1.0)) * 1000)
320 | start = int(((chunk.get_start() - min_addr * 1.0) / (max_addr - min_addr * 1.0)) * 1000)
321 | chunk_dsize = chunk.get_dend() - chunk.get_dstart()
322 | bold_text = "%#x" % (chunk.get_start())
323 | text = " - %#x (%#x) (%#x -> %#x)" % (chunk_size,chunk_dsize,chunk.get_start(),chunk.get_end())
324 | if chunk.isFree():
325 | svg+=svg_dashed_rec(start+10, line_pos + 20 + overlap*20, size, 20, 5, 5, chunk.get_color(),text=text,boldtext=bold_text)
326 | else:
327 | svg+=svg_rec(start+10, line_pos + 20 + overlap*20, size, 20, 5, 5, chunk.get_color(),text=text,boldtext=bold_text)
328 | write = state.get_memoryWrites()
329 | if write is not None:
330 | size = int(((write.get_end() - write.get_start() * 1.0) / (max_addr - min_addr * 1.0)) * 1000)
331 | start = int(((write.get_start() - min_addr * 1.0) / (max_addr - min_addr * 1.0)) * 1000)
332 | colors=(128,255,128)
333 | text_colors=(0,0,0)
334 | text="%s (%#x -> %#x)" % (write.get_name(),write.get_start(),write.get_end())
335 | tooltip_text=""
336 | overflow=None
337 | for chunk in state.get_chunks():
338 | overflow=chunk.overflow(write.get_end())
339 | if overflow is not None:
340 | colors=(255,0,0)
341 | text_colors=(255,255,255)
342 | tooltip_text="overflow in "+overflow+" "
343 | break
344 | # check if end addr is in chunk
345 | found=False
346 | for chunk in state.get_chunks():
347 | if write.get_end() >= chunk.get_start() and write.get_end() <= chunk.get_end():
348 | found=True
349 | break
350 | if not found:
351 | overflow="write outside chunk"
352 | colors=(255,0,0)
353 | text_colors=(255,255,255)
354 | tooltip_text=overflow
355 |
356 |
357 | overlap=-1
358 | for check_chunk in state.get_chunks():
359 | if write.get_start() > check_chunk.get_start() and write.get_start() <= check_chunk.get_end():
360 | overlap+=1;
361 | if overflow:
362 | svg+=svg_info(start+10+size, line_pos + 24 + overlap*20, tooltip_text)
363 | svg+=svg_rec(start+10, line_pos + 24 + overlap*20, size, 12, 2, 2, colors, opacity=1,text=text)
364 | return svg
365 |
366 |
367 |
368 | def random_color(r=200, g=200, b=125):
369 |
370 | red = (random.randrange(0, 256) + r) / 2
371 | green = (random.randrange(0, 256) + g) / 2
372 | blue = (random.randrange(0, 256) + b) / 2
373 |
374 | return (red, green, blue)
375 |
376 |
377 |
378 | if __name__ == '__main__':
379 | random.seed(100)
380 | chunks=[]
381 | data = open(sys.argv[1],"rb").read()
382 | states = parse_ltrace(data)
383 | min_addr = states[-1].get_min_addr()
384 | max_addr = states[-1].get_max_addr()
385 |
386 | nb_state = len(states)
387 | svg_height=100*nb_state + 5*nb_state + 110
388 | svg=svg_header(svg_height)
389 | svg+=svg_style()
390 | svg+=svg_script()
391 | for pos,state in enumerate(states):
392 | svg+=state_to_svg(pos,state,min_addr,max_addr)
393 |
394 | svg+=svg_footer()
395 | out=open(sys.argv[2],"wb")
396 | out.write(svg)
397 | out.close()
398 |
399 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HeapView
2 | Tool to view heap chunks and memory writes (using pintool)
3 | , strongly inspired by [Villoc](https://github.com/wapiflapi/villoc).
4 |
5 | ```
6 | /opt/pin/pin -t pintool/obj-intel64/pintool.so -- ./vuln
7 | HeapView.py trace vuln.svg
8 | ```
9 |
10 |
11 |
--------------------------------------------------------------------------------
/pintool/makefile:
--------------------------------------------------------------------------------
1 | ##############################################################
2 | #
3 | # DO NOT EDIT THIS FILE!
4 | #
5 | ##############################################################
6 |
7 | # If the tool is built out of the kit, PIN_ROOT must be specified in the make invocation and point to the kit root.
8 | ifdef PIN_ROOT
9 | CONFIG_ROOT := $(PIN_ROOT)/source/tools/Config
10 | else
11 | CONFIG_ROOT := ../Config
12 | endif
13 | include $(CONFIG_ROOT)/makefile.config
14 | include makefile.rules
15 | include $(TOOLS_ROOT)/Config/makefile.default.rules
16 |
17 | TOOL_INCLUDES += ./src/api/c++/
18 |
19 | ##############################################################
20 | #
21 | # DO NOT EDIT THIS FILE!
22 | #
23 | ##############################################################
24 |
--------------------------------------------------------------------------------
/pintool/makefile.rules:
--------------------------------------------------------------------------------
1 | ##############################################################
2 | #
3 | # This file includes all the test targets as well as all the
4 | # non-default build rules and test recipes.
5 | #
6 | ##############################################################
7 |
8 |
9 | ##############################################################
10 | #
11 | # Test targets
12 | #
13 | ##############################################################
14 |
15 | ###### Place all generic definitions here ######
16 |
17 | # This defines tests which run tools of the same name. This is simply for convenience to avoid
18 | # defining the test name twice (once in TOOL_ROOTS and again in TEST_ROOTS).
19 | # Tests defined here should not be defined in TOOL_ROOTS and TEST_ROOTS.
20 | TEST_TOOL_ROOTS := pintool
21 |
22 | # This defines the tests to be run that were not already defined in TEST_TOOL_ROOTS.
23 | TEST_ROOTS :=
24 |
25 | # This defines a list of tests that should run in the "short" sanity. Tests in this list must also
26 | # appear either in the TEST_TOOL_ROOTS or the TEST_ROOTS list.
27 | # If the entire directory should be tested in sanity, assign TEST_TOOL_ROOTS and TEST_ROOTS to the
28 | # SANITY_SUBSET variable in the tests section below (see example in makefile.rules.tmpl).
29 | SANITY_SUBSET :=
30 |
31 | # This defines the tools which will be run during the the tests, and were not already defined in
32 | # TEST_TOOL_ROOTS.
33 | TOOL_ROOTS :=
34 |
35 | # This defines the static analysis tools which will be run during the the tests. They should not
36 | # be defined in TEST_TOOL_ROOTS. If a test with the same name exists, it should be defined in
37 | # TEST_ROOTS.
38 | # Note: Static analysis tools are in fact executables linked with the Pin Static Analysis Library.
39 | # This library provides a subset of the Pin APIs which allows the tool to perform static analysis
40 | # of an application or dll. Pin itself is not used when this tool runs.
41 | SA_TOOL_ROOTS :=
42 |
43 | # This defines all the applications that will be run during the tests.
44 | APP_ROOTS :=
45 |
46 | # This defines any additional object files that need to be compiled.
47 | OBJECT_ROOTS :=
48 |
49 | # This defines any additional dlls (shared objects), other than the pintools, that need to be compiled.
50 | DLL_ROOTS :=
51 |
52 | # This defines any static libraries (archives), that need to be built.
53 | LIB_ROOTS :=
54 |
55 |
56 | ##############################################################
57 | #
58 | # Test recipes
59 | #
60 | ##############################################################
61 |
62 | # This section contains recipes for tests other than the default.
63 | # See makefile.default.rules for the default test rules.
64 | # All tests in this section should adhere to the naming convention: .test
65 |
66 |
67 | ##############################################################
68 | #
69 | # Build rules
70 | #
71 | ##############################################################
72 |
73 | # This section contains the build rules for all binaries that have special build rules.
74 | # See makefile.default.rules for the default build rules.
75 |
--------------------------------------------------------------------------------
/pintool/obj-ia32/pintool.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/polymorf/HeapView/231e4a614f660edf7c51f49e0a0fc2e31a0396c4/pintool/obj-ia32/pintool.so
--------------------------------------------------------------------------------
/pintool/obj-intel64/pintool.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/polymorf/HeapView/231e4a614f660edf7c51f49e0a0fc2e31a0396c4/pintool/obj-intel64/pintool.so
--------------------------------------------------------------------------------
/pintool/pintool.cpp:
--------------------------------------------------------------------------------
1 | #include "pin.H"
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include