1from xnu import * 2from utils import * 3from core.configuration import * 4 5shift = None 6 7shadow_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 30def is_kasan_build(): 31 try: 32 enable = kern.globals.kasan_enabled 33 return True 34 except ValueError, e: 35 return False 36 37def shadow_for_address(addr, shift): 38 return ((addr >> 3) + shift) 39 40def address_for_shadow(addr, shift): 41 return ((addr - shift) << 3) 42 43def get_shadow_byte(shadow_addr): 44 return unsigned(kern.GetValueFromAddress(shadow_addr, 'uint8_t *')[0]) 45 46def print_legend(): 47 for (k,v) in shadow_strings.iteritems(): 48 print " {:02x}: {}".format(k,v) 49 50def 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 71kasan_guard_size = 16 72def 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.z_name).startswith("fakestack"): 81 alloc_type = "fakestack" 82 leftrz = 16 83 else: 84 alloc_type = "zone" 85 leftrz = unsigned(zone.z_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.z_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, 1) 113 114alloc_header_sz = 16 115 116def magic_for_addr(addr, xor): 117 magic = addr & 0xffff 118 magic ^= (addr >> 16) & 0xffff 119 magic ^= (addr >> 32) & 0xffff 120 magic ^= (addr >> 48) & 0xffff 121 magic ^= xor 122 return magic 123 124def print_alloc_info(_addr): 125 addr = (_addr & ~0x7) 126 127 _shp = shadow_for_address(_addr, shift) 128 _shbyte = get_shadow_byte(_shp) 129 _shstr = shadow_byte_to_string(_shbyte) 130 131 # If we're in a left redzone, scan to the start of the real allocation, where 132 # the header should live 133 shbyte = _shbyte 134 while shbyte == 0xfa: 135 addr += 8 136 shbyte = get_shadow_byte(shadow_for_address(addr, shift)) 137 138 # Search backwards for an allocation 139 searchbytes = 0 140 while searchbytes < 8*4096: 141 142 shp = shadow_for_address(addr, shift) 143 shbyte = get_shadow_byte(shp) 144 shstr = shadow_byte_to_string(shbyte) 145 146 headerp = addr - alloc_header_sz 147 liveh = kern.GetValueFromAddress(headerp, 'struct kasan_alloc_header *') 148 freeh = kern.GetValueFromAddress(addr, 'struct freelist_entry *') 149 150 # heap allocations should only ever have these shadow values 151 if shbyte not in (0,1,2,3,4,5,6,7, 0xfa, 0xfb, 0xfd, 0xf5): 152 print "No allocation found at 0x{:x} (found shadow {:x})".format(_addr, shbyte) 153 return 154 155 if magic_for_addr(addr, 0x3a65) == unsigned(liveh.magic): 156 usz = unsigned(liveh.user_size) 157 asz = unsigned(liveh.alloc_size) 158 leftrz = unsigned(liveh.left_rz) 159 base = headerp + alloc_header_sz - leftrz 160 161 if _addr >= base and _addr < base + asz: 162 footer = kern.GetValueFromAddress(addr + usz, 'struct kasan_alloc_footer *') 163 rightrz = asz - usz - leftrz 164 offset = _addr - addr 165 166 print "Live heap object" 167 print "Valid range: 0x{:x} -- 0x{:x} ({} bytes)".format(addr, addr + usz - 1, usz) 168 print "Total range: 0x{:x} -- 0x{:x} ({} bytes)".format(base, base + asz - 1, asz) 169 print "Offset: {} bytes (shadow: 0x{:02x} {}, remaining: {} bytes)".format(offset, _shbyte, _shstr, usz - offset) 170 print "Redzone: {} / {} bytes".format(leftrz, rightrz) 171 172 btframes = unsigned(liveh.frames) 173 print "", 174 print "Alloc site backtrace ({} frames):".format(btframes) 175 for i in xrange(0, btframes): 176 fr = unsigned(kern.globals.vm_kernel_slid_base) + unsigned(footer.backtrace[i]) 177 print " #{:}: {}".format(btframes-i-1, GetSourceInformationForAddress(fr)) 178 179 print "", 180 print_hexdump(base, asz, 1) 181 return 182 183 elif magic_for_addr(addr, 0xf233) == unsigned(freeh.magic): 184 asz = unsigned(freeh.size) 185 if _addr >= addr and _addr < addr + asz: 186 print_alloc_free_entry(addr, _addr) 187 return 188 189 searchbytes += 8 190 addr -= 8 191 192 print "No allocation found at 0x{:x}".format(_addr) 193 194def shadow_byte_to_string(sb): 195 return shadow_strings.get(sb, '??') 196 197def print_whatis(_addr, ctx): 198 addr = _addr & ~0x7 199 total_size = 0 200 base = None 201 leftrz = None 202 rightrz = None 203 extra = "Live" 204 205 shaddr = shadow_for_address(addr, shift) 206 try: 207 shbyte = get_shadow_byte(shaddr) 208 except: 209 print "Unmapped shadow 0x{:x} for address 0x{:x}".format(shaddr, addr) 210 return 211 212 maxsearch = 8*4096 213 214 if shbyte in [0xfa, 0xfb, 0xfd, 0xf5]: 215 print_alloc_info(_addr) 216 return 217 218 if shbyte not in [0,1,2,3,4,5,6,7,0xf8]: 219 print "Poisoned memory, shadow {:x} [{}]".format(shbyte, shadow_byte_to_string(shbyte)) 220 return 221 222 if shbyte is 0xf8: 223 extra = "Out-of-scope" 224 225 # look for the base of the object 226 while shbyte in [0,1,2,3,4,5,6,7,0xf8]: 227 sz = 8 - shbyte 228 if shbyte is 0xf8: 229 sz = 8 230 total_size += sz 231 addr -= 8 232 shbyte = get_shadow_byte(shadow_for_address(addr, shift)) 233 maxsearch -= 8 234 if maxsearch <= 0: 235 print "No object found" 236 return 237 base = addr + 8 238 leftrz = shbyte 239 240 # If we did not find a left/mid redzone, we aren't in an object 241 if leftrz not in [0xf1, 0xf2, 0xfa, 0xf9]: 242 print "No object found" 243 return 244 245 # now size the object 246 addr = (_addr & ~0x7) + 8 247 shbyte = get_shadow_byte(shadow_for_address(addr, shift)) 248 while shbyte in [0,1,2,3,4,5,6,7,0xf8]: 249 sz = 8 - shbyte 250 if shbyte is 0xf8: 251 sz = 8 252 total_size += sz 253 addr += 8 254 shbyte = get_shadow_byte(shadow_for_address(addr, shift)) 255 maxsearch -= 8 256 if maxsearch <= 0: 257 print "No object found" 258 return 259 rightrz = shbyte 260 261 # work out the type of the object from its redzone 262 objtype = "Unknown" 263 if leftrz == 0xf1 or leftrz == 0xf2: 264 objtype = "stack" 265 elif leftrz == 0xf9 and rightrz == 0xf9: 266 objtype = "global" 267 elif leftrz == 0xfa and rightrz == 0xfb: 268 print_alloc_info(_addr) 269 return 270 271 print "{} {} object".format(extra, objtype) 272 print "Valid range: 0x{:x} -- 0x{:x} ({} bytes)".format(base, base+total_size-1, total_size) 273 print "Offset: {} bytes".format(_addr - base) 274 print "", 275 print_hexdump(base, total_size, 1) 276 277def print_hexdump(base, size, ctx): 278 if size < 16: 279 size = 16 280 base -= base % 16 281 start = base - 16*ctx 282 size += size % 16 283 size = min(size + 16*2*ctx, 256) 284 285 try: 286 data_array = kern.GetValueFromAddress(start, "uint8_t *") 287 print_hex_data(data_array[0:size], start, "Hexdump") 288 except: 289 pass 290 291def kasan_subcommand(cmd, args, opts): 292 addr = None 293 if len(args) > 0: 294 addr = long(args[0], 0) 295 296 if cmd in ['a2s', 'toshadow', 'fromaddr', 'fromaddress']: 297 print "0x{:016x}".format(shadow_for_address(addr, shift)) 298 elif cmd in ['s2a', 'toaddr', 'toaddress', 'fromshadow']: 299 print "0x{:016x}".format(address_for_shadow(addr, shift)) 300 elif cmd == 'shadow': 301 shadow = shadow_for_address(addr, shift) 302 sb = get_shadow_byte(shadow) 303 print("0x{:02x} @ 0x{:016x} [{}]\n\n".format(sb, shadow, shadow_byte_to_string(sb))) 304 ctx = long(opts.get("-C", 5)) 305 print_shadow_context(addr, ctx) 306 elif cmd == 'key' or cmd == 'legend': 307 print_legend() 308 elif cmd == 'info': 309 pages_used = unsigned(kern.globals.shadow_pages_used) 310 pages_total = unsigned(kern.globals.shadow_pages_total) 311 nkexts = unsigned(kern.globals.kexts_loaded) 312 print "Offset: 0x{:016x}".format(shift) 313 print "Shadow used: {} / {} ({:.1f}%)".format(pages_used, pages_total, 100.0*pages_used/pages_total) 314 print "Kexts loaded: {}".format(nkexts) 315 elif cmd == 'whatis': 316 ctx = long(opts.get("-C", 1)) 317 print_whatis(addr, ctx) 318 elif cmd == 'alloc' or cmd == 'heap': 319 print_alloc_info(addr) 320 else: 321 print "Unknown subcommand: `{}'".format(cmd) 322 323@lldb_command('kasan', 'C:') 324def Kasan(cmd_args=None, cmd_options={}): 325 """kasan <cmd> [opts..] 326 327 Commands: 328 329 info basic KASan information 330 shadow <addr> print shadow around 'addr' 331 heap <addr> show info about heap object at 'addr' 332 whatis <addr> print whatever KASan knows about address 333 toshadow <addr> convert address to shadow pointer 334 toaddr <shdw> convert shadow pointer to address 335 legend print a shadow byte table 336 337 -C <num> : num lines of context to show""" 338 339 if not is_kasan_build(): 340 print "KASan not enabled in build" 341 return 342 343 if len(cmd_args) == 0: 344 print Kasan.__doc__ 345 return 346 347 global shift 348 shift = unsigned(kern.globals.__asan_shadow_memory_dynamic_address) 349 350 # Since the VM is not aware of the KASan shadow mapping, accesses to it will 351 # fail. Setting kdp_read_io=1 avoids this check. 352 if GetConnectionProtocol() == "kdp" and unsigned(kern.globals.kdp_read_io) == 0: 353 print "Setting kdp_read_io=1 to allow KASan shadow reads" 354 if sizeof(kern.globals.kdp_read_io) == 4: 355 WriteInt32ToMemoryAddress(1, addressof(kern.globals.kdp_read_io)) 356 elif sizeof(kern.globals.kdp_read_io) == 8: 357 WriteInt64ToMemoryAddress(1, addressof(kern.globals.kdp_read_io)) 358 readio = unsigned(kern.globals.kdp_read_io) 359 assert readio == 1 360 361 return kasan_subcommand(cmd_args[0], cmd_args[1:], cmd_options) 362 363