]>
Commit | Line | Data |
---|---|---|
1 | from xnu import * | |
2 | from utils import * | |
3 | from core.configuration import * | |
4 | ||
5 | shift = None | |
6 | ||
7 | shadow_strings = { | |
8 | 0x00: 'VALID', | |
9 | 0x01: 'PARTIAL1', | |
10 | 0x02: 'PARTIAL2', | |
11 | 0x03: 'PARTIAL3', | |
12 | 0x04: 'PARTIAL4', | |
13 | 0x05: 'PARTIAL5', | |
14 | 0x06: 'PARTIAL6', | |
15 | 0x07: 'PARTIAL7', | |
16 | 0xac: 'ARRAY_COOKIE', | |
17 | 0xf0: 'STACK_RZ', | |
18 | 0xf1: 'STACK_LEFT_RZ', | |
19 | 0xf2: 'STACK_MID_RZ', | |
20 | 0xf3: 'STACK_RIGHT_RZ', | |
21 | 0xf5: 'STACK_FREED', | |
22 | 0xf8: 'STACK_OOSCOPE', | |
23 | 0xf9: 'GLOBAL_RZ', | |
24 | 0xe9: 'HEAP_RZ', | |
25 | 0xfa: 'HEAP_LEFT_RZ', | |
26 | 0xfb: 'HEAP_RIGHT_RZ', | |
27 | 0xfd: 'HEAP_FREED' | |
28 | } | |
29 | ||
30 | def is_kasan_build(): | |
31 | try: | |
32 | enable = kern.globals.kasan_enabled | |
33 | return True | |
34 | except ValueError, e: | |
35 | return False | |
36 | ||
37 | def shadow_for_address(addr, shift): | |
38 | return ((addr >> 3) + shift) | |
39 | ||
40 | def address_for_shadow(addr, shift): | |
41 | return ((addr - shift) << 3) | |
42 | ||
43 | def get_shadow_byte(shadow_addr): | |
44 | return unsigned(kern.GetValueFromAddress(shadow_addr, 'uint8_t *')[0]) | |
45 | ||
46 | def print_legend(): | |
47 | for (k,v) in shadow_strings.iteritems(): | |
48 | print " {:02x}: {}".format(k,v) | |
49 | ||
50 | def print_shadow_context(addr, context): | |
51 | addr = shadow_for_address(addr, shift) | |
52 | base = (addr & ~0xf) - 16 * context | |
53 | shadow = kern.GetValueFromAddress(unsigned(base), "uint8_t *") | |
54 | ||
55 | print " "*17 + " 0 1 2 3 4 5 6 7 8 9 a b c d e f" | |
56 | for x in range(0, 2*context+1): | |
57 | vals = "" | |
58 | l = " " | |
59 | for y in xrange(x*16, (x+1)*16): | |
60 | r = " " | |
61 | if base+y == addr: | |
62 | l = "[" | |
63 | r = "]" | |
64 | elif base+y+1 == addr: | |
65 | r = "" | |
66 | sh = shadow[y] | |
67 | vals += "{}{:02x}{}".format(l, sh, r) | |
68 | l = "" | |
69 | print("{:x}:{}".format(base + 16*x, vals)) | |
70 | ||
71 | kasan_guard_size = 16 | |
72 | def print_alloc_free_entry(addr, orig_ptr): | |
73 | h = kern.GetValueFromAddress(addr, 'struct freelist_entry *') | |
74 | asz = unsigned(h.size) | |
75 | usz = unsigned(h.user_size) | |
76 | pgsz = unsigned(kern.globals.page_size) | |
77 | ||
78 | if h.zone: | |
79 | zone = h.zone | |
80 | if str(zone.zone_name).startswith("fakestack"): | |
81 | alloc_type = "fakestack" | |
82 | leftrz = 16 | |
83 | else: | |
84 | alloc_type = "zone" | |
85 | leftrz = unsigned(zone.kasan_redzone) | |
86 | else: | |
87 | alloc_type = "kalloc" | |
88 | if asz - usz >= 2*pgsz: | |
89 | leftrz = pgsz | |
90 | else: | |
91 | leftrz = kasan_guard_size | |
92 | ||
93 | rightrz = asz - usz - leftrz | |
94 | ||
95 | print "Freed {} object".format(alloc_type) | |
96 | print "Valid range: 0x{:x} -- 0x{:x} ({} bytes)".format(addr + leftrz, addr + leftrz + usz - 1, usz) | |
97 | print "Total range: 0x{:x} -- 0x{:x} ({} bytes)".format(addr, addr + asz - 1, asz) | |
98 | print "Offset: {} bytes".format(orig_ptr - addr - leftrz) | |
99 | print "Redzone: {} / {} bytes".format(leftrz, rightrz) | |
100 | if h.zone: | |
101 | print "Zone: 0x{:x} <{:s}>".format(unsigned(zone), zone.zone_name) | |
102 | ||
103 | btframes = unsigned(h.frames) | |
104 | if btframes > 0: | |
105 | print "", | |
106 | print "Free site backtrace ({} frames):".format(btframes) | |
107 | for i in xrange(0, btframes): | |
108 | fr = unsigned(kern.globals.vm_kernel_slid_base) + unsigned(h.backtrace[i]) | |
109 | print " #{:}: {}".format(btframes-i-1, GetSourceInformationForAddress(fr)) | |
110 | ||
111 | print "", | |
112 | print_hexdump(addr, asz, 0) | |
113 | ||
114 | alloc_header_sz = 16 | |
115 | ||
116 | def print_alloc_info(_addr): | |
117 | addr = (_addr & ~0x7) | |
118 | ||
119 | _shp = shadow_for_address(_addr, shift) | |
120 | _shbyte = get_shadow_byte(_shp) | |
121 | _shstr = shadow_byte_to_string(_shbyte) | |
122 | ||
123 | # If we're in a left redzone, scan to the start of the real allocation, where | |
124 | # the header should live | |
125 | shbyte = _shbyte | |
126 | while shbyte == 0xfa: | |
127 | addr += 8 | |
128 | shbyte = get_shadow_byte(shadow_for_address(addr, shift)) | |
129 | ||
130 | # Search backwards for an allocation | |
131 | searchbytes = 0 | |
132 | while searchbytes < 8*4096: | |
133 | ||
134 | shp = shadow_for_address(addr, shift) | |
135 | shbyte = get_shadow_byte(shp) | |
136 | shstr = shadow_byte_to_string(shbyte) | |
137 | ||
138 | headerp = addr - alloc_header_sz | |
139 | liveh = kern.GetValueFromAddress(headerp, 'struct kasan_alloc_header *') | |
140 | freeh = kern.GetValueFromAddress(addr, 'struct freelist_entry *') | |
141 | ||
142 | # heap allocations should only ever have these shadow values | |
143 | if shbyte not in (0,1,2,3,4,5,6,7, 0xfa, 0xfb, 0xfd, 0xf5): | |
144 | print "No allocation found at 0x{:x} (found shadow {:x})".format(_addr, shbyte) | |
145 | return | |
146 | ||
147 | live_magic = (addr & 0xffffffff) ^ 0xA110C8ED | |
148 | free_magic = (addr & 0xffffffff) ^ 0xF23333D | |
149 | ||
150 | if live_magic == unsigned(liveh.magic): | |
151 | usz = unsigned(liveh.user_size) | |
152 | asz = unsigned(liveh.alloc_size) | |
153 | leftrz = unsigned(liveh.left_rz) | |
154 | base = headerp + alloc_header_sz - leftrz | |
155 | ||
156 | if _addr >= base and _addr < base + asz: | |
157 | footer = kern.GetValueFromAddress(addr + usz, 'struct kasan_alloc_footer *') | |
158 | rightrz = asz - usz - leftrz | |
159 | ||
160 | print "Live heap object" | |
161 | print "Valid range: 0x{:x} -- 0x{:x} ({} bytes)".format(addr, addr + usz - 1, usz) | |
162 | print "Total range: 0x{:x} -- 0x{:x} ({} bytes)".format(base, base + asz - 1, asz) | |
163 | print "Offset: {} bytes (shadow: 0x{:02x} {})".format(_addr - addr, _shbyte, _shstr) | |
164 | print "Redzone: {} / {} bytes".format(leftrz, rightrz) | |
165 | ||
166 | btframes = unsigned(liveh.frames) | |
167 | print "", | |
168 | print "Alloc site backtrace ({} frames):".format(btframes) | |
169 | for i in xrange(0, btframes): | |
170 | fr = unsigned(kern.globals.vm_kernel_slid_base) + unsigned(footer.backtrace[i]) | |
171 | print " #{:}: {}".format(btframes-i-1, GetSourceInformationForAddress(fr)) | |
172 | ||
173 | print "", | |
174 | print_hexdump(base, asz, 0) | |
175 | return | |
176 | ||
177 | elif free_magic == unsigned(freeh.magic): | |
178 | asz = unsigned(freeh.size) | |
179 | if _addr >= addr and _addr < addr + asz: | |
180 | print_alloc_free_entry(addr, _addr) | |
181 | return | |
182 | ||
183 | searchbytes += 8 | |
184 | addr -= 8 | |
185 | ||
186 | print "No allocation found at 0x{:x}".format(_addr) | |
187 | ||
188 | def shadow_byte_to_string(sb): | |
189 | return shadow_strings.get(sb, '??') | |
190 | ||
191 | def print_whatis(_addr, ctx): | |
192 | addr = _addr & ~0x7 | |
193 | total_size = 0 | |
194 | base = None | |
195 | leftrz = None | |
196 | rightrz = None | |
197 | extra = "Live" | |
198 | ||
199 | shbyte = get_shadow_byte(shadow_for_address(addr, shift)) | |
200 | maxsearch = 4096 * 2 | |
201 | ||
202 | if shbyte in [0xfa, 0xfb, 0xfd, 0xf5]: | |
203 | print_alloc_info(_addr) | |
204 | return | |
205 | ||
206 | if shbyte not in [0,1,2,3,4,5,6,7,0xf8]: | |
207 | print "Poisoned memory, shadow {:x} [{}]".format(shbyte, shadow_byte_to_string(shbyte)) | |
208 | return | |
209 | ||
210 | if shbyte is 0xf8: | |
211 | extra = "Out-of-scope" | |
212 | ||
213 | # look for the base of the object | |
214 | while shbyte in [0,1,2,3,4,5,6,7,0xf8]: | |
215 | sz = 8 - shbyte | |
216 | if shbyte is 0xf8: | |
217 | sz = 8 | |
218 | total_size += sz | |
219 | addr -= 8 | |
220 | shbyte = get_shadow_byte(shadow_for_address(addr, shift)) | |
221 | maxsearch -= 8 | |
222 | if maxsearch <= 0: | |
223 | print "No object found" | |
224 | return | |
225 | base = addr + 8 | |
226 | leftrz = shbyte | |
227 | ||
228 | # If we did not find a left/mid redzone, we aren't in an object | |
229 | if leftrz not in [0xf1, 0xf2, 0xfa, 0xf9]: | |
230 | print "No object found" | |
231 | return | |
232 | ||
233 | # now size the object | |
234 | addr = (_addr & ~0x7) + 8 | |
235 | shbyte = get_shadow_byte(shadow_for_address(addr, shift)) | |
236 | while shbyte in [0,1,2,3,4,5,6,7,0xf8]: | |
237 | sz = 8 - shbyte | |
238 | if shbyte is 0xf8: | |
239 | sz = 8 | |
240 | total_size += sz | |
241 | addr += 8 | |
242 | shbyte = get_shadow_byte(shadow_for_address(addr, shift)) | |
243 | maxsearch -= 8 | |
244 | if maxsearch <= 0: | |
245 | print "No object found" | |
246 | return | |
247 | rightrz = shbyte | |
248 | ||
249 | # work out the type of the object from its redzone | |
250 | objtype = "Unknown" | |
251 | if leftrz == 0xf1 or leftrz == 0xf2: | |
252 | objtype = "stack" | |
253 | elif leftrz == 0xf9 and rightrz == 0xf9: | |
254 | objtype = "global" | |
255 | elif leftrz == 0xfa and rightrz == 0xfb: | |
256 | print_alloc_info(_addr) | |
257 | return | |
258 | ||
259 | print "{} {} object".format(extra, objtype) | |
260 | print "Valid range: 0x{:x} -- 0x{:x} ({} bytes)".format(base, base+total_size-1, total_size) | |
261 | print "Offset: {} bytes".format(_addr - base) | |
262 | print "", | |
263 | print_hexdump(base, total_size, 0) | |
264 | ||
265 | def print_hexdump(base, size, ctx): | |
266 | start = base - 16*ctx | |
267 | size += size % 16 | |
268 | size = min(size + 16*2*ctx, 256) | |
269 | ||
270 | try: | |
271 | data_array = kern.GetValueFromAddress(start, "uint8_t *") | |
272 | print_hex_data(data_array[0:size], start, "Hexdump") | |
273 | except: | |
274 | pass | |
275 | ||
276 | def kasan_subcommand(cmd, args, opts): | |
277 | addr = None | |
278 | if len(args) > 0: | |
279 | addr = long(args[0], 0) | |
280 | ||
281 | if cmd in ['a2s', 'toshadow', 'fromaddr', 'fromaddress']: | |
282 | print "0x{:016x}".format(shadow_for_address(addr, shift)) | |
283 | elif cmd in ['s2a', 'toaddr', 'toaddress', 'fromshadow']: | |
284 | print "0x{:016x}".format(address_for_shadow(addr, shift)) | |
285 | elif cmd == 'shadow': | |
286 | shadow = shadow_for_address(addr, shift) | |
287 | sb = get_shadow_byte(shadow) | |
288 | print("0x{:02x} @ 0x{:016x} [{}]\n\n".format(sb, shadow, shadow_byte_to_string(sb))) | |
289 | ctx = long(opts.get("-C", 5)) | |
290 | print_shadow_context(addr, ctx) | |
291 | elif cmd == 'legend': | |
292 | print_legend() | |
293 | elif cmd == 'info': | |
294 | pages_used = unsigned(kern.globals.shadow_pages_used) | |
295 | pages_total = unsigned(kern.globals.shadow_pages_total) | |
296 | nkexts = unsigned(kern.globals.kexts_loaded) | |
297 | print "Offset: 0x{:016x}".format(shift) | |
298 | print "Shadow used: {} / {} ({:.1f}%)".format(pages_used, pages_total, 100.0*pages_used/pages_total) | |
299 | print "Kexts loaded: {}".format(nkexts) | |
300 | elif cmd == 'whatis': | |
301 | ctx = long(opts.get("-C", 1)) | |
302 | print_whatis(addr, ctx) | |
303 | elif cmd == 'alloc' or cmd == 'heap': | |
304 | print_alloc_info(addr) | |
305 | ||
306 | @lldb_command('kasan', 'C:') | |
307 | def Kasan(cmd_args=None, cmd_options={}): | |
308 | """kasan <cmd> [opts..] | |
309 | ||
310 | Commands: | |
311 | ||
312 | info basic KASan information | |
313 | shadow <addr> print shadow around 'addr' | |
314 | heap <addr> show info about heap object at 'addr' | |
315 | whatis <addr> print whatever KASan knows about address | |
316 | toshadow <addr> convert address to shadow pointer | |
317 | toaddr <shdw> convert shadow pointer to address | |
318 | legend print a shadow byte table | |
319 | ||
320 | -C <num> : num lines of context to show""" | |
321 | ||
322 | if not is_kasan_build(): | |
323 | print "KASan not enabled in build" | |
324 | return | |
325 | ||
326 | if len(cmd_args) == 0: | |
327 | print Kasan.__doc__ | |
328 | return | |
329 | ||
330 | global shift | |
331 | shift = unsigned(kern.globals.__asan_shadow_memory_dynamic_address) | |
332 | ||
333 | # Since the VM is not aware of the KASan shadow mapping, accesses to it will | |
334 | # fail. Setting kdp_read_io=1 avoids this check. | |
335 | if GetConnectionProtocol() == "kdp" and unsigned(kern.globals.kdp_read_io) == 0: | |
336 | print "Setting kdp_read_io=1 to allow KASan shadow reads" | |
337 | if sizeof(kern.globals.kdp_read_io) == 4: | |
338 | WriteInt32ToMemoryAddress(1, addressof(kern.globals.kdp_read_io)) | |
339 | elif sizeof(kern.globals.kdp_read_io) == 8: | |
340 | WriteInt64ToMemoryAddress(1, addressof(kern.globals.kdp_read_io)) | |
341 | readio = unsigned(kern.globals.kdp_read_io) | |
342 | assert readio == 1 | |
343 | ||
344 | return kasan_subcommand(cmd_args[0], cmd_args[1:], cmd_options) | |
345 |