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