metasm 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.hgtags +3 -0
  4. data/Gemfile +1 -0
  5. data/INSTALL +61 -0
  6. data/LICENCE +458 -0
  7. data/README +29 -21
  8. data/Rakefile +10 -0
  9. data/TODO +10 -12
  10. data/doc/code_organisation.txt +2 -0
  11. data/doc/core/DynLdr.txt +247 -0
  12. data/doc/core/ExeFormat.txt +43 -0
  13. data/doc/core/Expression.txt +220 -0
  14. data/doc/core/GNUExports.txt +27 -0
  15. data/doc/core/Ia32.txt +236 -0
  16. data/doc/core/SerialStruct.txt +108 -0
  17. data/doc/core/VirtualString.txt +145 -0
  18. data/doc/core/WindowsExports.txt +61 -0
  19. data/doc/core/index.txt +1 -0
  20. data/doc/style.css +6 -3
  21. data/doc/usage/debugger.txt +327 -0
  22. data/doc/usage/index.txt +1 -0
  23. data/doc/use_cases.txt +2 -2
  24. data/metasm.gemspec +22 -0
  25. data/{lib/metasm.rb → metasm.rb} +11 -3
  26. data/{lib/metasm → metasm}/compile_c.rb +13 -7
  27. data/metasm/cpu/arc.rb +8 -0
  28. data/metasm/cpu/arc/decode.rb +425 -0
  29. data/metasm/cpu/arc/main.rb +191 -0
  30. data/metasm/cpu/arc/opcodes.rb +588 -0
  31. data/{lib/metasm → metasm/cpu}/arm.rb +7 -5
  32. data/{lib/metasm → metasm/cpu}/arm/debug.rb +2 -2
  33. data/{lib/metasm → metasm/cpu}/arm/decode.rb +13 -12
  34. data/{lib/metasm → metasm/cpu}/arm/encode.rb +23 -8
  35. data/{lib/metasm → metasm/cpu}/arm/main.rb +0 -3
  36. data/metasm/cpu/arm/opcodes.rb +324 -0
  37. data/{lib/metasm → metasm/cpu}/arm/parse.rb +25 -13
  38. data/{lib/metasm → metasm/cpu}/arm/render.rb +2 -2
  39. data/metasm/cpu/arm64.rb +15 -0
  40. data/metasm/cpu/arm64/debug.rb +38 -0
  41. data/metasm/cpu/arm64/decode.rb +289 -0
  42. data/metasm/cpu/arm64/encode.rb +41 -0
  43. data/metasm/cpu/arm64/main.rb +105 -0
  44. data/metasm/cpu/arm64/opcodes.rb +232 -0
  45. data/metasm/cpu/arm64/parse.rb +20 -0
  46. data/metasm/cpu/arm64/render.rb +95 -0
  47. data/{lib/metasm/ppc.rb → metasm/cpu/bpf.rb} +2 -4
  48. data/metasm/cpu/bpf/decode.rb +142 -0
  49. data/metasm/cpu/bpf/main.rb +60 -0
  50. data/metasm/cpu/bpf/opcodes.rb +81 -0
  51. data/metasm/cpu/bpf/render.rb +41 -0
  52. data/metasm/cpu/cy16.rb +9 -0
  53. data/metasm/cpu/cy16/decode.rb +253 -0
  54. data/metasm/cpu/cy16/main.rb +63 -0
  55. data/metasm/cpu/cy16/opcodes.rb +78 -0
  56. data/metasm/cpu/cy16/render.rb +41 -0
  57. data/metasm/cpu/dalvik.rb +11 -0
  58. data/{lib/metasm → metasm/cpu}/dalvik/decode.rb +35 -13
  59. data/{lib/metasm → metasm/cpu}/dalvik/main.rb +51 -2
  60. data/{lib/metasm → metasm/cpu}/dalvik/opcodes.rb +19 -11
  61. data/metasm/cpu/ia32.rb +17 -0
  62. data/{lib/metasm → metasm/cpu}/ia32/compile_c.rb +5 -7
  63. data/{lib/metasm → metasm/cpu}/ia32/debug.rb +5 -5
  64. data/{lib/metasm → metasm/cpu}/ia32/decode.rb +246 -59
  65. data/{lib/metasm → metasm/cpu}/ia32/decompile.rb +7 -7
  66. data/{lib/metasm → metasm/cpu}/ia32/encode.rb +19 -13
  67. data/{lib/metasm → metasm/cpu}/ia32/main.rb +51 -8
  68. data/metasm/cpu/ia32/opcodes.rb +1424 -0
  69. data/{lib/metasm → metasm/cpu}/ia32/parse.rb +47 -16
  70. data/{lib/metasm → metasm/cpu}/ia32/render.rb +31 -4
  71. data/metasm/cpu/mips.rb +14 -0
  72. data/{lib/metasm → metasm/cpu}/mips/compile_c.rb +1 -1
  73. data/metasm/cpu/mips/debug.rb +42 -0
  74. data/{lib/metasm → metasm/cpu}/mips/decode.rb +46 -16
  75. data/{lib/metasm → metasm/cpu}/mips/encode.rb +4 -3
  76. data/{lib/metasm → metasm/cpu}/mips/main.rb +11 -4
  77. data/{lib/metasm → metasm/cpu}/mips/opcodes.rb +86 -17
  78. data/{lib/metasm → metasm/cpu}/mips/parse.rb +1 -1
  79. data/{lib/metasm → metasm/cpu}/mips/render.rb +1 -1
  80. data/{lib/metasm/dalvik.rb → metasm/cpu/msp430.rb} +1 -1
  81. data/metasm/cpu/msp430/decode.rb +247 -0
  82. data/metasm/cpu/msp430/main.rb +62 -0
  83. data/metasm/cpu/msp430/opcodes.rb +101 -0
  84. data/{lib/metasm → metasm/cpu}/pic16c/decode.rb +6 -7
  85. data/{lib/metasm → metasm/cpu}/pic16c/main.rb +0 -0
  86. data/{lib/metasm → metasm/cpu}/pic16c/opcodes.rb +1 -1
  87. data/{lib/metasm/mips.rb → metasm/cpu/ppc.rb} +4 -4
  88. data/{lib/metasm → metasm/cpu}/ppc/decode.rb +18 -12
  89. data/{lib/metasm → metasm/cpu}/ppc/decompile.rb +3 -3
  90. data/{lib/metasm → metasm/cpu}/ppc/encode.rb +2 -2
  91. data/{lib/metasm → metasm/cpu}/ppc/main.rb +17 -12
  92. data/{lib/metasm → metasm/cpu}/ppc/opcodes.rb +11 -5
  93. data/metasm/cpu/ppc/parse.rb +55 -0
  94. data/metasm/cpu/python.rb +8 -0
  95. data/metasm/cpu/python/decode.rb +136 -0
  96. data/metasm/cpu/python/main.rb +36 -0
  97. data/metasm/cpu/python/opcodes.rb +180 -0
  98. data/{lib/metasm → metasm/cpu}/sh4.rb +1 -1
  99. data/{lib/metasm → metasm/cpu}/sh4/decode.rb +48 -17
  100. data/{lib/metasm → metasm/cpu}/sh4/main.rb +13 -4
  101. data/{lib/metasm → metasm/cpu}/sh4/opcodes.rb +7 -8
  102. data/metasm/cpu/x86_64.rb +15 -0
  103. data/{lib/metasm → metasm/cpu}/x86_64/compile_c.rb +28 -17
  104. data/{lib/metasm → metasm/cpu}/x86_64/debug.rb +4 -4
  105. data/{lib/metasm → metasm/cpu}/x86_64/decode.rb +57 -15
  106. data/{lib/metasm → metasm/cpu}/x86_64/encode.rb +55 -26
  107. data/{lib/metasm → metasm/cpu}/x86_64/main.rb +14 -6
  108. data/metasm/cpu/x86_64/opcodes.rb +136 -0
  109. data/{lib/metasm → metasm/cpu}/x86_64/parse.rb +10 -2
  110. data/metasm/cpu/x86_64/render.rb +35 -0
  111. data/metasm/cpu/z80.rb +9 -0
  112. data/metasm/cpu/z80/decode.rb +313 -0
  113. data/metasm/cpu/z80/main.rb +67 -0
  114. data/metasm/cpu/z80/opcodes.rb +224 -0
  115. data/metasm/cpu/z80/render.rb +59 -0
  116. data/{lib/metasm/os/main.rb → metasm/debug.rb} +160 -401
  117. data/{lib/metasm → metasm}/decode.rb +35 -4
  118. data/{lib/metasm → metasm}/decompile.rb +15 -16
  119. data/{lib/metasm → metasm}/disassemble.rb +201 -45
  120. data/{lib/metasm → metasm}/disassemble_api.rb +651 -87
  121. data/{lib/metasm → metasm}/dynldr.rb +220 -133
  122. data/{lib/metasm → metasm}/encode.rb +10 -1
  123. data/{lib/metasm → metasm}/exe_format/a_out.rb +9 -6
  124. data/{lib/metasm → metasm}/exe_format/autoexe.rb +1 -0
  125. data/{lib/metasm → metasm}/exe_format/bflt.rb +57 -27
  126. data/{lib/metasm → metasm}/exe_format/coff.rb +11 -3
  127. data/{lib/metasm → metasm}/exe_format/coff_decode.rb +53 -20
  128. data/{lib/metasm → metasm}/exe_format/coff_encode.rb +11 -13
  129. data/{lib/metasm → metasm}/exe_format/dex.rb +13 -5
  130. data/{lib/metasm → metasm}/exe_format/dol.rb +1 -0
  131. data/{lib/metasm → metasm}/exe_format/elf.rb +93 -57
  132. data/{lib/metasm → metasm}/exe_format/elf_decode.rb +143 -34
  133. data/{lib/metasm → metasm}/exe_format/elf_encode.rb +122 -31
  134. data/metasm/exe_format/gb.rb +65 -0
  135. data/metasm/exe_format/javaclass.rb +424 -0
  136. data/{lib/metasm → metasm}/exe_format/macho.rb +204 -16
  137. data/{lib/metasm → metasm}/exe_format/main.rb +26 -3
  138. data/{lib/metasm → metasm}/exe_format/mz.rb +1 -0
  139. data/{lib/metasm → metasm}/exe_format/nds.rb +7 -4
  140. data/{lib/metasm → metasm}/exe_format/pe.rb +71 -8
  141. data/metasm/exe_format/pyc.rb +167 -0
  142. data/{lib/metasm → metasm}/exe_format/serialstruct.rb +67 -14
  143. data/{lib/metasm → metasm}/exe_format/shellcode.rb +7 -3
  144. data/metasm/exe_format/shellcode_rwx.rb +114 -0
  145. data/metasm/exe_format/swf.rb +205 -0
  146. data/{lib/metasm → metasm}/exe_format/xcoff.rb +7 -7
  147. data/metasm/exe_format/zip.rb +335 -0
  148. data/metasm/gui.rb +13 -0
  149. data/{lib/metasm → metasm}/gui/cstruct.rb +35 -41
  150. data/{lib/metasm → metasm}/gui/dasm_coverage.rb +11 -11
  151. data/{lib/metasm → metasm}/gui/dasm_decomp.rb +7 -20
  152. data/{lib/metasm → metasm}/gui/dasm_funcgraph.rb +0 -0
  153. data/metasm/gui/dasm_graph.rb +1695 -0
  154. data/{lib/metasm → metasm}/gui/dasm_hex.rb +12 -8
  155. data/{lib/metasm → metasm}/gui/dasm_listing.rb +43 -28
  156. data/{lib/metasm → metasm}/gui/dasm_main.rb +310 -53
  157. data/{lib/metasm → metasm}/gui/dasm_opcodes.rb +5 -19
  158. data/{lib/metasm → metasm}/gui/debug.rb +93 -27
  159. data/{lib/metasm → metasm}/gui/gtk.rb +162 -40
  160. data/{lib/metasm → metasm}/gui/qt.rb +12 -2
  161. data/{lib/metasm → metasm}/gui/win32.rb +179 -42
  162. data/{lib/metasm → metasm}/gui/x11.rb +59 -59
  163. data/{lib/metasm → metasm}/main.rb +389 -264
  164. data/{lib/metasm/os/remote.rb → metasm/os/gdbremote.rb} +146 -54
  165. data/{lib/metasm → metasm}/os/gnu_exports.rb +1 -1
  166. data/{lib/metasm → metasm}/os/linux.rb +628 -151
  167. data/metasm/os/main.rb +330 -0
  168. data/{lib/metasm → metasm}/os/windows.rb +132 -42
  169. data/{lib/metasm → metasm}/os/windows_exports.rb +141 -0
  170. data/{lib/metasm → metasm}/parse.rb +26 -24
  171. data/{lib/metasm → metasm}/parse_c.rb +221 -116
  172. data/{lib/metasm → metasm}/preprocessor.rb +55 -40
  173. data/{lib/metasm → metasm}/render.rb +14 -38
  174. data/misc/hexdump.rb +2 -1
  175. data/misc/lint.rb +58 -0
  176. data/misc/txt2html.rb +9 -7
  177. data/samples/bindiff.rb +3 -4
  178. data/samples/dasm-plugins/bindiff.rb +15 -0
  179. data/samples/dasm-plugins/bookmark.rb +133 -0
  180. data/samples/dasm-plugins/c_constants.rb +57 -0
  181. data/samples/dasm-plugins/colortheme_solarized.rb +125 -0
  182. data/samples/dasm-plugins/cppobj_funcall.rb +60 -0
  183. data/samples/dasm-plugins/dasm_all.rb +70 -0
  184. data/samples/dasm-plugins/demangle_cpp.rb +31 -0
  185. data/samples/dasm-plugins/deobfuscate.rb +251 -0
  186. data/samples/dasm-plugins/dump_text.rb +35 -0
  187. data/samples/dasm-plugins/export_graph_svg.rb +86 -0
  188. data/samples/dasm-plugins/findgadget.rb +75 -0
  189. data/samples/dasm-plugins/hl_opcode.rb +32 -0
  190. data/samples/dasm-plugins/hotfix_gtk_dbg.rb +19 -0
  191. data/samples/dasm-plugins/imm2off.rb +34 -0
  192. data/samples/dasm-plugins/match_libsigs.rb +93 -0
  193. data/samples/dasm-plugins/patch_file.rb +95 -0
  194. data/samples/dasm-plugins/scanfuncstart.rb +36 -0
  195. data/samples/dasm-plugins/scanxrefs.rb +26 -0
  196. data/samples/dasm-plugins/selfmodify.rb +197 -0
  197. data/samples/dasm-plugins/stringsxrefs.rb +28 -0
  198. data/samples/dasmnavig.rb +1 -1
  199. data/samples/dbg-apihook.rb +24 -9
  200. data/samples/dbg-plugins/heapscan.rb +283 -0
  201. data/samples/dbg-plugins/heapscan/compiled_heapscan_lin.c +155 -0
  202. data/samples/dbg-plugins/heapscan/compiled_heapscan_win.c +128 -0
  203. data/samples/dbg-plugins/heapscan/graphheap.rb +616 -0
  204. data/samples/dbg-plugins/heapscan/heapscan.rb +709 -0
  205. data/samples/dbg-plugins/heapscan/winheap.h +174 -0
  206. data/samples/dbg-plugins/heapscan/winheap7.h +307 -0
  207. data/samples/dbg-plugins/trace_func.rb +214 -0
  208. data/samples/disassemble-gui.rb +35 -5
  209. data/samples/disassemble.rb +31 -6
  210. data/samples/dump_upx.rb +24 -12
  211. data/samples/dynamic_ruby.rb +12 -3
  212. data/samples/exeencode.rb +6 -5
  213. data/samples/factorize-headers-peimports.rb +1 -1
  214. data/samples/lindebug.rb +175 -381
  215. data/samples/metasm-shell.rb +1 -2
  216. data/samples/peldr.rb +2 -2
  217. data/tests/all.rb +1 -1
  218. data/tests/arc.rb +26 -0
  219. data/tests/dynldr.rb +22 -4
  220. data/tests/expression.rb +55 -0
  221. data/tests/graph_layout.rb +285 -0
  222. data/tests/ia32.rb +79 -26
  223. data/tests/mips.rb +9 -2
  224. data/tests/x86_64.rb +66 -18
  225. metadata +330 -218
  226. data/lib/metasm/arm/opcodes.rb +0 -177
  227. data/lib/metasm/gui.rb +0 -23
  228. data/lib/metasm/gui/dasm_graph.rb +0 -1354
  229. data/lib/metasm/ia32.rb +0 -14
  230. data/lib/metasm/ia32/opcodes.rb +0 -873
  231. data/lib/metasm/ppc/parse.rb +0 -52
  232. data/lib/metasm/x86_64.rb +0 -12
  233. data/lib/metasm/x86_64/opcodes.rb +0 -118
  234. data/samples/gdbclient.rb +0 -583
  235. data/samples/rubstop.rb +0 -399
@@ -0,0 +1,709 @@
1
+ # This file is part of Metasm, the Ruby assembly manipulation suite
2
+ # Copyright (C) 2006-2009 Yoann GUILLOT
3
+ #
4
+ # Licence is LGPL, see LICENCE in the top-level directory
5
+
6
+
7
+ module Metasm
8
+ class Heap
9
+ attr_accessor :vm, :range, :ptsz
10
+ attr_accessor :cp
11
+ # hash chunk userdata pointer -> chunk userdata size
12
+ attr_accessor :chunks
13
+ # hash chunk user pointer -> C::Struct
14
+ attr_accessor :chunk_struct
15
+ # the chunk graph: chunk pointer -> [array of chunks addrs pointed]
16
+ attr_accessor :xrchunksto, :xrchunksfrom
17
+
18
+ def initialize(dbg)
19
+ @dbg = dbg
20
+ @dbg.pid_stuff_list << :heap
21
+ @dbg.heap = self
22
+ @range = {}
23
+ @dwcache = {}
24
+ # userdata_ptr => len
25
+ @chunks = {}
26
+ @xrchunksto = {}
27
+ @xrchunksfrom = {}
28
+ @ptsz = dbg.cpu.size/8
29
+ # ptr => C::Struct
30
+ @chunk_struct = {}
31
+ end
32
+
33
+ def pagecache(base, len)
34
+ @dbg.read_mapped_range(base, len)
35
+ end
36
+
37
+ def dwcache(base, len)
38
+ @dwcache[[base, len]] ||= pagecache(base, len).unpack(@ptsz == 4 ? 'L*' : 'Q*')
39
+ end
40
+
41
+ # return the array of dwords in the chunk
42
+ def chunkdw(ptr, len=@chunks[ptr])
43
+ if base = find_range(ptr)
44
+ dwcache(base, @range[base])[(ptr-base)/@ptsz, len/@ptsz]
45
+ end
46
+ end
47
+
48
+ # returns the list of chunks, sorted
49
+ def chunklist
50
+ @chunklist ||= @chunks.keys.sort
51
+ end
52
+
53
+ # dichotomic search of the chunk containing ptr
54
+ # len = hash ptr => length
55
+ # list = list of hash keys sorted
56
+ def find_elem(ptr, len, list=nil)
57
+ return ptr if len[ptr]
58
+
59
+ list ||= len.keys.sort
60
+
61
+ if list.length < 16
62
+ return list.find { |p| p <= ptr and p + len[p] > ptr }
63
+ end
64
+
65
+ window = list
66
+ while window and not window.empty?
67
+ i = window.length/2
68
+ wi = window[i]
69
+ if ptr < wi
70
+ window = window[0, i]
71
+ elsif ptr < wi + len[wi]
72
+ return wi
73
+ else
74
+ window = window[i+1, i]
75
+ end
76
+ end
77
+ end
78
+
79
+ # find the chunk encompassing ptr
80
+ def find_chunk(ptr)
81
+ find_elem(ptr, @chunks, chunklist)
82
+ end
83
+
84
+ def find_range(ptr)
85
+ @range_keys ||= @range.keys.sort
86
+ find_elem(ptr, @range, @range_keys)
87
+ end
88
+
89
+ # { chunk size => [list of chunk addrs] }
90
+ attr_accessor :buckets
91
+ def bucketize
92
+ @buckets = {}
93
+ chunklist.each { |a|
94
+ (@buckets[@chunks[a]] ||= []) << a
95
+ }
96
+ end
97
+
98
+ # find the kernels of the graph (strongly connected components)
99
+ # must be called after scan_xr
100
+ # also find the graph diameter
101
+ attr_accessor :kernels, :maxpath
102
+ def find_kernels(adj = @xrchunksto)
103
+ @maxpath = []
104
+ kernels = {}
105
+ adj.keys.sort.each { |ptr|
106
+ next if kernels[ptr]
107
+ paths = [[ptr]]
108
+ while path = paths.pop
109
+ next if not l = @xrchunksfrom[path.first]
110
+ l.each { |pl|
111
+ next if kernels[pl]
112
+ next if not adj[pl]
113
+ if path.include?(pl)
114
+ kernels[pl] = true
115
+ else
116
+ paths << [pl, *path]
117
+ end
118
+ }
119
+ @maxpath = paths.last if paths.last and paths.last.length > @maxpath.length
120
+ end
121
+ }
122
+ if @maxpath.first and np = (adj[@maxpath.last] - @maxpath).first
123
+ @maxpath << np
124
+ end
125
+ @kernels = []
126
+ while k = kernels.index(true)
127
+ curk = reachfrom(k, adj).find_all { |ok|
128
+ true == reachfrom(ok, adj) { |tk|
129
+ break true if tk == k
130
+ }
131
+ }
132
+ @kernels << curk
133
+ curk.each { |ka| kernels.delete ka }
134
+ end
135
+ end
136
+
137
+ attr_accessor :roots
138
+ # find the root nodes that allow acces to most other nodes
139
+ # { root => [reachable nodes] }
140
+ # does not include single nodes (@chunks.keys - @xrchunksfrom.keys)
141
+ def find_roots(adj=@xrchunksto)
142
+ @roots = {}
143
+ adj.keys.sort.each { |r|
144
+ if not @roots[r]
145
+ l = reachfrom(r, adj, @roots)
146
+ l.each { |t| @roots[t] = true if adj[t] } # may include r !, also dont mark leaves
147
+ @roots[r] = l
148
+ end
149
+ }
150
+ @roots.delete_if { |k, v| v == true }
151
+ end
152
+
153
+ def reachfrom(p, adj = @xrchunksto, roots={})
154
+ return roots[p] if roots[p].kind_of? Array
155
+ hash = {}
156
+ todo = [p]
157
+ while p = todo.pop
158
+ if to = roots[p] || adj[p] and to.kind_of? Array
159
+ to.each { |tk|
160
+ if not hash[tk]
161
+ hash[tk] = true
162
+ todo << tk
163
+ yield tk if block_given?
164
+ end
165
+ }
166
+ end
167
+ end
168
+ hash.keys
169
+ end
170
+
171
+ # create a subset of xrchunksto from one point
172
+ def graph_from(p, adj = @xrchunksto)
173
+ hash = {}
174
+ todo = [p]
175
+ while p = todo.pop
176
+ next if hash[p]
177
+ if adj[p]
178
+ hash[p] = adj[p]
179
+ todo.concat hash[p]
180
+ end
181
+ end
182
+ hash
183
+ end
184
+
185
+ # dump the whole graph in a dot file
186
+ def dump_graph(fname='graph.gv', graph=@xrchunksto)
187
+ File.open(fname, 'w') { |fd|
188
+ fd.puts "digraph foo {"
189
+ graph.each { |b, l|
190
+ fd.puts l.map { |e| '"%x" -> "%x";' % [b, e] }
191
+ }
192
+ fd.puts "}"
193
+ }
194
+ end
195
+
196
+ # chunk ptr => { dwindex => [list of ptrs] }
197
+ attr_accessor :linkedlists, :alllists
198
+ def find_linkedlists
199
+ @linkedlists = {}
200
+ @alllists = []
201
+ @buckets.sort.each { |sz, lst|
202
+ #puts "sz #{sz} #{lst.length}"
203
+ lst.each { |ptr|
204
+ next if not l = @xrchunksto[ptr]
205
+ next if not l.find { |tg| @chunks[tg] == sz }
206
+ dw = chunkdw(ptr)
207
+ dw.length.times { |dwoff|
208
+ next if @linkedlists[ptr] and @linkedlists[ptr][dwoff]
209
+ tg = dw[dwoff]
210
+ next if @chunks[tg] != sz
211
+ check_linkedlist(ptr, dwoff)
212
+ }
213
+ }
214
+ }
215
+ end
216
+
217
+ def check_linkedlist(ptr, dwoff)
218
+ psz = @chunks[ptr]
219
+ fwd = ptr
220
+ lst = [fwd]
221
+ base = find_range(fwd)
222
+ loop do
223
+ if not base or base > fwd or base + @range[base] <= fwd
224
+ base = find_range(fwd)
225
+ end
226
+ break if not base
227
+ fwd = dwcache(base, @range[base])[(fwd-base)/@ptsz + dwoff]
228
+ break if fwd == 0
229
+ return if not cl = @chunks[fwd] # XXX root/tail may be in .data
230
+ return if cl != psz
231
+ break if lst.include? fwd
232
+ lst << fwd
233
+ end
234
+ fwd = ptr
235
+ while pv = @xrchunksfrom[fwd]
236
+ fwd = pv.find { |p|
237
+ next if @chunks[p] != psz
238
+ if not base or base > p or base + @range[base] <= p
239
+ base = find_range(fwd)
240
+ end
241
+ dwcache(base, @range[base])[(p-base)/@ptsz + dwoff] == fwd
242
+ }
243
+ break if not fwd
244
+ break if lst.include? fwd
245
+ lst.unshift fwd
246
+ end
247
+ if lst.length > 3
248
+ lst.each { |p| (@linkedlists[p] ||= {})[dwoff] = lst }
249
+ @alllists << lst
250
+ end
251
+ end
252
+
253
+ # { chunkinarray => { rootptr => [chunks] } }
254
+ attr_accessor :arrays, :allarrays
255
+ def find_arrays
256
+ @arrays = {}
257
+ @allarrays = []
258
+ @buckets.sort.each { |sz, lst|
259
+ next if sz < @ptsz*6
260
+ lst.each { |ptr|
261
+ next if not to = @xrchunksto[ptr]
262
+ # a table must have at least half its storage space filled with ptrs
263
+ next if to.length <= sz/@ptsz/2
264
+ # also, ptrs must point to same-size stuff
265
+ lsz = Hash.new(0)
266
+ to.each { |t| lsz[@chunks[t]] += 1 }
267
+ cnt = lsz.values.max
268
+ next if cnt <= sz/@ptsz/2
269
+ tgsz = lsz.index(cnt)
270
+ ar = to.find_all { |t| @chunks[t] == tgsz }.uniq
271
+ next if ar.length <= sz/@ptsz/2
272
+ ar.each { |p| (@arrays[p] ||= {})[ptr] = ar }
273
+ @allarrays << ar
274
+ }
275
+ }
276
+ end
277
+ end
278
+
279
+ class LinuxHeap < Heap
280
+ # find all chunks in the memory address space
281
+ attr_accessor :mmaps
282
+
283
+ def scan_chunks
284
+ @chunks = {}
285
+ each_heap { |a, l, ar|
286
+ scan_heap(a, l, ar)
287
+ }
288
+ @mmapchunks = []
289
+ @mmaps.each { |a, l|
290
+ ll = scan_mmap(a, l) || 4096
291
+ a += ll
292
+ l -= ll
293
+ }
294
+ end
295
+
296
+ # scan all chunks for cross-references (one chunk contaning a pointer to some other chunk)
297
+ def scan_chunks_xr
298
+ @xrchunksto = {}
299
+ @xrchunksfrom = {}
300
+ each_heap { |a, l, ar|
301
+ scan_heap_xr(a, l)
302
+ }
303
+ @mmapchunks.each { |a|
304
+ scan_mmap_xr(a, @chunks[a])
305
+ }
306
+ end
307
+
308
+ # scan chunks from a heap base addr
309
+ def scan_heap(base, len, ar)
310
+ dw = dwcache(base, len)
311
+ ptr = 0
312
+
313
+ psz = dw[ptr]
314
+ sz = dw[ptr+1]
315
+ base += 2*@ptsz # user pointer
316
+ raise "bad heap base %x %x %x %x" % [psz, sz, base, len] if psz != 0 or sz & 1 == 0
317
+
318
+ loop do
319
+ clen = sz & -8 # chunk size
320
+ ptr += clen/@ptsz # start of next chk
321
+ break if ptr >= dw.length or clen == 0
322
+ sz = dw[ptr+1]
323
+ if sz & 1 > 0 # pv_inuse
324
+ # user data length up to chucksize-4 (over next psz)
325
+ #puts "used #{'%x' % base} #{clen-@ptsz}" if $VERBOSE
326
+ @chunks[base] = clen-@ptsz
327
+ else
328
+ #puts "free #{'%x' % base} #{clen-@ptsz}" if $VERBOSE
329
+ end
330
+ base += clen
331
+ end
332
+
333
+ del_fastbin(ar)
334
+ end
335
+
336
+ def scan_heap_xr(base, len)
337
+ dw = dwcache(base, len)
338
+ @chunks.each_key { |p|
339
+ i = (p-base) / @ptsz
340
+ if i >= 0 and i < dw.length
341
+ lst = dw[i, @chunks[p]/@ptsz].find_all { |pp| @chunks[pp] }
342
+ @xrchunksto[p] = lst if not lst.empty?
343
+ lst.each { |pp| (@xrchunksfrom[pp] ||= []) << p }
344
+ end
345
+ }
346
+ end
347
+
348
+ # scan chunks from a mmap base addr
349
+ # big chunks are allocated on anonymous-mmap areas
350
+ # for mmap chunks, pv_sz=0 pv_inuse=0, mmap=1, data starts at 8, mmapsz = userlen+12 [roundup 4096]
351
+ # one entry in /proc/pid/maps may point to multiple consecutive mmap chunks
352
+ # scans for a mmap chunk header, returns the chunk size if pattern match or nil
353
+ def scan_mmap(base, len)
354
+ foo = chunkdata(base)
355
+ clen = foo[1] & ~0xfff
356
+ if foo[0] == 0 and foo[1] & 0xfff == 2 and clen > 0 and clen <= len
357
+ @chunks[base + foo.length] = clen-4*@ptsz
358
+ @mmapchunks << (base + foo.length)
359
+ clen
360
+ end
361
+ end
362
+
363
+ def scan_mmap_xr(base, len)
364
+ dw = dwcache(base, len)
365
+ lst = dw[2..-1].find_all { |pp| @chunks[pp] }
366
+ @xrchunksto[base] = lst if not lst.empty?
367
+ lst.each { |pp| (@xrchunksfrom[pp] ||= []) << base }
368
+ end
369
+
370
+ attr_accessor :main_arena_ptr
371
+
372
+ # we need to find the main_arena from the libc
373
+ # we do this by analysing 'malloc_trim'
374
+ def scan_libc(addr)
375
+ raise 'no libc' if not addr
376
+
377
+ return if @main_arena_ptr = @dbg.symbols.index('main_arena')
378
+
379
+ unless trim = @dbg.symbols.index('malloc_trim') || @dbg.symbols.index('weak_malloc_trim')
380
+ @dbg.loadsyms 'libc[.-]'
381
+ trim = @dbg.symbols.index('malloc_trim') || @dbg.symbols.index('weak_malloc_trim')
382
+ end
383
+ raise 'cant find malloc_trim' if not trim
384
+
385
+ d = @dbg.disassembler
386
+
387
+ d.disassemble_fast(trim) if not d.di_at(trim)
388
+ if d.block_at(trim).list.last.opcode.name == 'call'
389
+ # x86 getip, need to dasm to have func_binding (cross fingers)
390
+ d.disassemble d.block_at(trim).to_normal.first
391
+ end
392
+ d.each_function_block(trim) { |b|
393
+ # mutex_lock(&main_arena.mutex) gives us the addr
394
+ next if not cmpxchg = d.block_at(b).list.find { |di| di.kind_of? DecodedInstruction and di.opcode.name == 'cmpxchg' }
395
+ @main_arena_ptr = d.backtrace(cmpxchg.instruction.args.first.symbolic.pointer, cmpxchg.address)
396
+ if @main_arena_ptr.length == 1
397
+ @main_arena_ptr = @main_arena_ptr[0].reduce
398
+ break
399
+ end
400
+ }
401
+ raise "cant find mainarena" if not @main_arena_ptr.kind_of? Integer
402
+ @dbg.symbols[@main_arena_ptr] = 'main_arena'
403
+ end
404
+
405
+ def chunkdata(ptr)
406
+ @cp.decode_c_ary('uintptr_t', 2, @dbg.memory, ptr).to_array
407
+ end
408
+
409
+ def each_heap
410
+ if not @cp.toplevel.struct['malloc_state']
411
+ @cp.parse <<EOS
412
+ // TODO autotune these 2 defines..
413
+ #define THREAD_STATS 0
414
+ //#define PER_THREAD
415
+
416
+ #define NFASTBINS 10
417
+ #define NBINS 128
418
+ #define BINMAPSIZE (NBINS/32)
419
+
420
+ struct malloc_state {
421
+ int mutex;
422
+ int flags;
423
+ #if THREAD_STATS
424
+ long stat_lock_direct, stat_lock_loop, stat_lock_wait;
425
+ #endif
426
+ void *fastbinsY[NFASTBINS];
427
+ void *top;
428
+ void *last_remainder;
429
+ void *bins[NBINS * 2 - 2]; // *2: double-linked list
430
+ unsigned int binmap[BINMAPSIZE];
431
+ struct malloc_state *next;
432
+ #ifdef PER_THREAD
433
+ struct malloc_state *next_free;
434
+ #endif
435
+ uintptr_t system_mem; // XXX int32?
436
+ uintptr_t max_system_mem;
437
+ };
438
+
439
+ struct heap_info {
440
+ struct malloc_state *ar_ptr; // Arena for this heap.
441
+ struct _heap_info *prev; // Previous heap.
442
+ uintptr_t size; // Current size in bytes. XXX int32?
443
+ uintptr_t mprotect_size; // Size in bytes that has been mprotected
444
+ };
445
+ EOS
446
+ end
447
+
448
+ ptr = @main_arena_ptr
449
+ loop do
450
+ ar = @cp.decode_c_struct('malloc_state', @dbg.memory, ptr)
451
+ if ptr == @main_arena_ptr
452
+ # main arena: find start from top.end - system_mem
453
+ toplen = chunkdata(ar.top)[1] & -8
454
+ yield ar.top + toplen - ar.system_mem, ar.system_mem, ar
455
+ else
456
+ # non-main arena: find heap_info for top, follow list
457
+ iptr = ar.top & -0x10_0000 # XXX
458
+ while iptr
459
+ hi = @cp.decode_c_struct('heap_info', @dbg.memory, iptr)
460
+ off = hi.sizeof
461
+ off += ar.sizeof if iptr+off == hi.ar_ptr
462
+ yield iptr+off, hi.size-off, ar
463
+
464
+ iptr = hi.prev
465
+ end
466
+ end
467
+
468
+ ptr = ar.next
469
+ break if ptr == @main_arena_ptr
470
+ end
471
+ end
472
+
473
+ def del_fastbin(ar)
474
+ nfastbins = 10
475
+ nfastbins.times { |i|
476
+ ptr = ar.fastbinsy[i]
477
+ while ptr
478
+ @chunks.delete ptr+2*@ptsz
479
+ ptr = @cp.decode_c_ary('void *', 3, @dbg.memory, ptr)[2]
480
+ end
481
+ }
482
+ end
483
+ end
484
+
485
+
486
+ class WindowsHeap < Heap
487
+ attr_accessor :heaps
488
+
489
+ def scan_chunks
490
+ @hsz = @cp.sizeof(@cp.find_c_struct('_HEAP_ENTRY'))
491
+ @chunks = {}
492
+ each_heap { |ar| scan_heap(ar) }
493
+ end
494
+
495
+ # scan all chunks for cross-references (one chunk containing a pointer to some other chunk)
496
+ def scan_chunks_xr
497
+ @xrchunksto = {}
498
+ @xrchunksfrom = {}
499
+ each_heap { |ar| scan_heap_xr(ar) }
500
+ end
501
+
502
+ # scan chunks from a heap
503
+ def scan_heap(ar)
504
+ each_heap_segment(ar) { |p, l|
505
+ scan_heap_segment(p, l)
506
+ }
507
+ scan_frontend(ar)
508
+ scan_valloc(ar)
509
+ end
510
+
511
+ def scan_frontend(ar)
512
+ if ar.frontendheaptype == 1
513
+ laptr = ar.frontendheap
514
+ @chunks.delete laptr # not a real (user) chunk
515
+ 128.times {
516
+ la = @cp.decode_c_struct('_HEAP_LOOKASIDE', @dbg.memory, laptr)
517
+ free = la.listhead.flink
518
+ while free
519
+ @chunks.delete free
520
+ free = @cp.decode_c_struct('_LIST_ENTRY', @dbg.memory, free).flink
521
+ end
522
+ laptr += la.sizeof
523
+ }
524
+ end
525
+ end
526
+
527
+ def scan_valloc(ar)
528
+ each_listentry(ar.virtualallocdblocks, '_HEAP_VIRTUAL_ALLOC_ENTRY') { |va|
529
+ # Unusedbyte count stored in the BusyBlock.Size field
530
+ @chunks[va.stroff + va.sizeof] = va.CommitSize - va.Size
531
+ }
532
+ end
533
+
534
+ def scan_heap_segment(first, len)
535
+ off = 0
536
+ heapcpy = pagecache(first, len)
537
+ while off < len
538
+ he = @cp.decode_c_struct('_HEAP_ENTRY', heapcpy, off)
539
+ sz = he.Size*8
540
+ if he.Flags & 1 == 1
541
+ @chunks[first+off+@hsz] = sz - he.UnusedBytes
542
+ end
543
+ off += sz
544
+ end
545
+ end
546
+
547
+ def scan_heap_xr(ar)
548
+ each_heap_segment(ar) { |p, l|
549
+ scan_heap_segment_xr(p, l)
550
+ }
551
+ end
552
+
553
+ def scan_heap_segment_xr(first, len)
554
+ off = 0
555
+ heapcpy = pagecache(first, len)
556
+ while off < len
557
+ he = @cp.decode_c_struct('_HEAP_ENTRY', heapcpy, off)
558
+ sz = he.Size*8
559
+ ptr = first + off + @hsz
560
+ if he.Flags & 1 == 1 and csz = @chunks[ptr] and csz > 0
561
+ heapcpy[off + @hsz, csz].unpack('L*').each { |p|
562
+ if @chunks[p]
563
+ (@xrchunksto[ptr] ||= []) << p
564
+ (@xrchunksfrom[p] ||= []) << ptr
565
+ end
566
+ }
567
+ end
568
+ off += sz
569
+ end
570
+ end
571
+
572
+ # yields the _HEAP structure for all heaps
573
+ def each_heap
574
+ heaps.each { |a, l|
575
+ ar = @cp.decode_c_struct('_HEAP', @dbg.memory, a)
576
+ yield ar
577
+ }
578
+ end
579
+
580
+ # yields all [ptr, len] for allocated segments of a _HEAP
581
+ # this maps to the _HEAP_SEGMENT further subdivised to skip
582
+ # the _HEAP_UNCOMMMTTED_RANGE areas
583
+ # for the last chunk of the _HEAP_SEGMENT, only yield up to chunk_header
584
+ def each_heap_segment(ar)
585
+ ar.segments.to_array.compact.each { |a|
586
+ sg = @cp.decode_c_struct('_HEAP_SEGMENT', @dbg.memory, a)
587
+ skiplist = []
588
+ ptr = sg.uncommittedranges
589
+ while ptr
590
+ ucr = @cp.decode_c_struct('_HEAP_UNCOMMMTTED_RANGE', @dbg.memory, ptr)
591
+ skiplist << [ucr.Address, ucr.Size]
592
+ ptr = ucr.Next
593
+ end
594
+ ptr = sg.firstentry
595
+ # XXX lastentryinsegment == firstentry ???
596
+ # lastvalidentry = address of the end of the segment (may point to unmapped space)
597
+ ptrend = sg.lastvalidentry
598
+ skiplist.delete_if { |sa, sl| sa < ptr or sa + sl > ptrend }
599
+ skiplist << [ptrend, 1]
600
+ skiplist.sort.each { |sa, sl|
601
+ yield(ptr, sa-ptr)
602
+ ptr = sa + sl
603
+ }
604
+ }
605
+ end
606
+
607
+ # call with a LIST_ENTRY allocstruct, the target structure and LE offset in this structure
608
+ def each_listentry(le, st, off=0)
609
+ ptr0 = le.stroff
610
+ ptr = le.flink
611
+ while ptr != ptr0
612
+ yield @cp.decode_c_struct(st, @dbg.memory, ptr-off)
613
+ ptr = @cp.decode_c_struct('_LIST_ENTRY', @dbg.memory, ptr).flink
614
+ end
615
+ end
616
+ end
617
+
618
+ class Windows7Heap < WindowsHeap
619
+ # 4-byte xor key to decrypt the chunk headers
620
+ attr_accessor :chunkkey_size, :chunkkey_flags, :chunkkey_unusedbytes
621
+ def each_heap_segment(ar)
622
+ if ar.encodeflagmask != 0
623
+ @chunkkey_size = ar.encoding.size
624
+ @chunkkey_flags = ar.encoding.flags
625
+ @chunkkey_unusedbytes = ar.encoding.unusedbytes
626
+ else
627
+ @chunkkey_size = 0
628
+ @chunkkey_flags = 0
629
+ @chunkkey_unusedbytes = 0
630
+ end
631
+
632
+ each_listentry(ar.segmentlist, '_HEAP_SEGMENT', 0x10) { |sg|
633
+ skiplist = []
634
+ each_listentry(sg.ucrsegmentlist, '_HEAP_UCR_DESCRIPTOR', 8) { |ucr|
635
+ skiplist << [ucr.address, ucr.size]
636
+ }
637
+
638
+ ptr = sg.firstentry
639
+ ptrend = sg.lastvalidentry + @hsz
640
+ skiplist.delete_if { |sa, sl| sa < ptr or sa + sl > ptrend }
641
+ skiplist << [ptrend, 1]
642
+ skiplist.sort.each { |sa, sl|
643
+ yield(ptr, sa-ptr)
644
+ ptr = sa + sl
645
+ }
646
+ }
647
+ end
648
+
649
+ def scan_heap_segment(first, len)
650
+ off = 0
651
+ heapcpy = pagecache(first, len)
652
+ while off < len
653
+ he = @cp.decode_c_struct('_HEAP_ENTRY', heapcpy, off)
654
+ sz = (he.Size ^ @chunkkey_size)*8
655
+ if (he.Flags ^ @chunkkey_flags) & 1 == 1
656
+ @chunks[first+off+@hsz] = sz - (he.UnusedBytes ^ @chunkkey_unusedbytes)
657
+ end
658
+ off += sz
659
+ end
660
+ end
661
+
662
+ def scan_frontend(ar)
663
+ return if ar.frontendheaptype != 2
664
+ lfh = @cp.decode_c_struct('_LFH_HEAP', @dbg.memory, ar.frontendheap)
665
+ lfh.localdata[0].segmentinfo.to_array.each { |sinfo|
666
+ sinfo.cacheditems.to_array.each { |ssp|
667
+ next if not ssp
668
+ subseg = @cp.decode_c_struct('_HEAP_SUBSEGMENT', @dbg.memory, ssp)
669
+ scan_lfh_ss(subseg)
670
+ }
671
+ }
672
+ end
673
+
674
+ def scan_lfh_ss(subseg)
675
+ up = subseg.userblocks
676
+ return if not up
677
+ bs = subseg.blocksize
678
+ bc = subseg.blockcount
679
+ list = Array.new(bc) { |i| up + 0x10 + bs*8*i }
680
+
681
+ free = []
682
+ ptr = subseg.freeentryoffset
683
+ subseg.depth.times {
684
+ free << (up + 8*ptr)
685
+ ptr = @dbg.memory[up + 8*ptr + 8, 2].unpack('v')[0]
686
+ }
687
+ @foo ||= 0
688
+ @foo += 1
689
+ p @foo if @foo % 10 == 0
690
+
691
+ up += 0x10
692
+ list -= free
693
+ list.each { |p| @chunks[p+8] = bs*8 - (@cp.decode_c_struct('_HEAP_ENTRY', @dbg.memory, p).unusedbytes & 0x7f) }
694
+ end
695
+
696
+ def scan_chunks_xr
697
+ @xrchunksto = {}
698
+ @xrchunksfrom = {}
699
+ @chunks.each { |a, l|
700
+ pagecache(a, l).unpack('L*').each { |p|
701
+ if @chunks[p]
702
+ (@xrchunksto[a] ||= []) << p
703
+ (@xrchunksfrom[p] ||= []) << a
704
+ end
705
+ }
706
+ }
707
+ end
708
+ end
709
+ end