memcheck-gdb.py (24772B)
1 import os 2 import re 3 import subprocess 4 import difflib 5 from elftools.elf import elffile 6 from enum import IntEnum 7 8 v_off_g = 0 9 file_g = None 10 11 class RkLoadSymbols(gdb.Command): 12 """Determine the KASLR-Offset and map the symbols.""" 13 14 v_off = 0 15 symbol = "native_safe_halt" 16 17 def __init__(self): 18 super(RkLoadSymbols, self).__init__("rk-load-symbols", gdb.COMMAND_USER, gdb.COMMAND_DATA) 19 20 21 def invoke(self, arg, from_tty): 22 if not arg: 23 print("Please provide an argument") 24 return None 25 26 self.get_v_off(arg) 27 self.load_sym(arg) 28 29 def load_sym(self, arg): 30 v_off = hex(self.v_off) 31 32 print(f"attempting to load symbols from \"{arg}\" with offset {v_off}") 33 try: 34 gdb.execute(f"add-symbol-file {arg} -o {self.v_off}") 35 except: 36 print("error loading symbol file, does it exist?") 37 return None 38 39 def get_v_off(self, arg): 40 global file_g 41 global v_off_g 42 43 sym_addr = get_symbol_address(arg, self.symbol) 44 45 if sym_addr is None: 46 return None 47 48 file_g = arg 49 50 #minimal assumption: user is at login prompt 51 try: 52 real = gdb.execute("where", to_string=True).split(" ")[2] 53 except: 54 print("error executing where, is the VM running?") 55 return None 56 57 real_addr = int(real, 16) 58 self.v_off = ((real_addr - sym_addr) & (~0xf)) 59 v_off_g = self.v_off 60 61 RkLoadSymbols() 62 63 64 65 66 class RkKaslrOffset(gdb.Command): 67 """Output the calculated physical and virtual KASLR offset.""" 68 69 symbol = "native_safe_halt" 70 obj_addr = None 71 72 def __init__(self): 73 super(RkKaslrOffset, self).__init__("rk-kaslr-offset", gdb.COMMAND_USER, gdb.COMMAND_DATA) 74 75 # assuming rk-load-symbols has already been run 76 def invoke(self, arg, from_tty): 77 global file_g 78 79 if file_g is None: 80 print("no object file has been read in to calculate offsets, please run `rk-load-symbols` first") 81 return None 82 83 self.obj_addr = get_symbol_address(file_g, self.symbol) 84 obj_addr = hex(self.obj_addr) 85 86 print(f"address for symbol `{self.symbol}` inside object file \"{file_g}\" is {obj_addr}") 87 print(f"looking up addresses for symbol `{self.symbol}`") 88 89 v_addr = self.get_v_addr() 90 91 if v_addr is None: 92 print(f"could not retrieve virtual address address for symbol `{self.symbol}`") 93 return None 94 95 p_addr = self.get_p_addr(v_addr) 96 97 if p_addr is None: 98 print(f"could not retrieve physical address address for symbol `{self.symbol}`") 99 return None 100 101 print(f"found virtual address {v_addr} with associated physical address {p_addr}") 102 103 v_addr = v_addr.strip() 104 p_addr = p_addr.strip() 105 106 v_bytes = gdb.execute(f"x/8xb {v_addr}", to_string=True).split()[-7:] 107 p_bytes = gdb.execute(f"monitor xp/8xb {p_addr}", to_string=True).split()[-7:] 108 109 print(f"8 bytes of memory read starting from virtual address {v_addr}: {v_bytes}") 110 print(f"8 bytes of memory read starting from physical address {p_addr}: {p_bytes}") 111 112 print(f"bytes read are {'equal' if v_bytes == p_bytes else 'different'}") 113 114 print() 115 116 print(f"calculating offsets relating to object file address {obj_addr}") 117 118 v_off = self.get_off(v_addr) 119 p_off = self.get_off(p_addr) 120 121 print(f"virtual KASLR offset: {v_off}") 122 print(f"physical KASLR offset: {p_off}") 123 124 125 def get_v_addr(self): 126 try: 127 return gdb.execute(f"p {self.symbol}", to_string=True).split(" ")[-2] 128 except: 129 print("error executing `where`, is the VM running?") 130 return None 131 132 133 def get_p_addr(self, v_addr): 134 try: 135 return gdb.execute(f"monitor gva2gpa {v_addr}", to_string=True).split(" ")[-1] 136 except: 137 print("error interacting with monitor, is the VM running?") 138 return None 139 140 141 def get_off(self, addr): 142 global file_g 143 144 if self.obj_addr is None: 145 return None 146 147 real_addr = int(addr, 16) 148 149 return hex((real_addr - self.obj_addr) & (~0xf)) 150 151 152 RkKaslrOffset() 153 154 155 156 157 class RKSyscallCheck(gdb.Command): 158 """Check the integrity of the syscall table. Run rk-load-symbols first.""" 159 160 symbol = "sys_call_table" 161 sys_call_table = 0 162 163 def __init__(self): 164 super(RKSyscallCheck, self).__init__("rk-syscall-check", gdb.COMMAND_USER, gdb.COMMAND_DATA) 165 166 167 def invoke(self, arg, from_tty): 168 global v_off_g 169 global file_g 170 171 if v_off_g == 0: 172 print("KASLR offset is 0 - run `rk-load-symbols` first") 173 print("if KASLR is enabled, just run `add-symbol-file <file>`") 174 return None 175 176 print("this might take a while") 177 print("exits silently when no tampering has been detected") 178 179 self.load_syscall_table() 180 self.check_syscall_table() 181 182 def load_syscall_table(self): 183 global file_g 184 185 ret = get_symbol_address(file_g, self.symbol) 186 if ret is None: 187 return None 188 189 self.sys_call_table = ret + v_off_g 190 191 def check_syscall_table(self): 192 global syscalls 193 194 for i, l in enumerate(syscalls): 195 if l == "sys_ni_syscall": 196 continue 197 198 cur = gdb.execute(f"x *({self.sys_call_table} + ({i} * 8))", to_string = True) 199 addr = re.search(r"(0x\w+)", cur) 200 201 if addr is None: 202 print("error parsing gdb x output") 203 continue 204 205 addr = int(addr.group(1), 16) 206 207 self.check_integrity(l, addr) 208 209 def check_integrity(self, symbol, addr): 210 global file_g 211 global v_off_g 212 213 should = get_symbol_address(file_g, symbol) 214 215 if should is None: 216 return None 217 218 should += v_off_g 219 220 if should != addr: 221 print(f"syscall table compromised for {symbol}!") 222 print(f"expected: {hex(should)}, table points to: {hex(addr)}") 223 224 225 226 RKSyscallCheck() 227 228 229 230 231 # return address of symbol from file through nm 232 def get_symbol_address(file, symbol): 233 stream = os.popen(f"nm {file} | grep -w \"\\b{symbol}\\b$\" | awk \'{{print $1}}\'") 234 sym = stream.read() 235 stream.close() 236 237 # symbol address _before_ randomization 238 try: 239 sym_addr = int(sym, 16) 240 return sym_addr 241 except: 242 print(f"error retrieving address from '{file}', did you specify a file?") 243 return None 244 245 246 syscalls = [ 247 '__x64_sys_read', 248 '__x64_sys_write', 249 '__x64_sys_open', 250 '__x64_sys_close', 251 '__x64_sys_newstat', 252 '__x64_sys_newfstat', 253 '__x64_sys_newlstat', 254 '__x64_sys_poll', 255 '__x64_sys_lseek', 256 '__x64_sys_mmap', 257 '__x64_sys_mprotect', 258 '__x64_sys_munmap', 259 '__x64_sys_brk', 260 '__x64_sys_rt_sigaction', 261 '__x64_sys_rt_sigprocmask', 262 '__x64_sys_rt_sigreturn', 263 '__x64_sys_ioctl', 264 '__x64_sys_pread64', 265 '__x64_sys_pwrite64', 266 '__x64_sys_readv', 267 '__x64_sys_writev', 268 '__x64_sys_access', 269 '__x64_sys_pipe', 270 '__x64_sys_select', 271 '__x64_sys_sched_yield', 272 '__x64_sys_mremap', 273 '__x64_sys_msync', 274 '__x64_sys_mincore', 275 '__x64_sys_madvise', 276 '__x64_sys_shmget', 277 '__x64_sys_shmat', 278 '__x64_sys_shmctl', 279 '__x64_sys_dup', 280 '__x64_sys_dup2', 281 '__x64_sys_pause', 282 '__x64_sys_nanosleep', 283 '__x64_sys_getitimer', 284 '__x64_sys_alarm', 285 '__x64_sys_setitimer', 286 '__x64_sys_getpid', 287 '__x64_sys_sendfile64', 288 '__x64_sys_socket', 289 '__x64_sys_connect', 290 '__x64_sys_accept', 291 '__x64_sys_sendto', 292 '__x64_sys_recvfrom', 293 '__x64_sys_sendmsg', 294 '__x64_sys_recvmsg', 295 '__x64_sys_shutdown', 296 '__x64_sys_bind', 297 '__x64_sys_listen', 298 '__x64_sys_getsockname', 299 '__x64_sys_getpeername', 300 '__x64_sys_socketpair', 301 '__x64_sys_setsockopt', 302 '__x64_sys_getsockopt', 303 '__x64_sys_clone', 304 '__x64_sys_fork', 305 '__x64_sys_vfork', 306 '__x64_sys_execve', 307 '__x64_sys_exit', 308 '__x64_sys_wait4', 309 '__x64_sys_kill', 310 '__x64_sys_newuname', 311 '__x64_sys_semget', 312 '__x64_sys_semop', 313 '__x64_sys_semctl', 314 '__x64_sys_shmdt', 315 '__x64_sys_msgget', 316 '__x64_sys_msgsnd', 317 '__x64_sys_msgrcv', 318 '__x64_sys_msgctl', 319 '__x64_sys_fcntl', 320 '__x64_sys_flock', 321 '__x64_sys_fsync', 322 '__x64_sys_fdatasync', 323 '__x64_sys_truncate', 324 '__x64_sys_ftruncate', 325 '__x64_sys_getdents', 326 '__x64_sys_getcwd', 327 '__x64_sys_chdir', 328 '__x64_sys_fchdir', 329 '__x64_sys_rename', 330 '__x64_sys_mkdir', 331 '__x64_sys_rmdir', 332 '__x64_sys_creat', 333 '__x64_sys_link', 334 '__x64_sys_unlink', 335 '__x64_sys_symlink', 336 '__x64_sys_readlink', 337 '__x64_sys_chmod', 338 '__x64_sys_fchmod', 339 '__x64_sys_chown', 340 '__x64_sys_fchown', 341 '__x64_sys_lchown', 342 '__x64_sys_umask', 343 '__x64_sys_gettimeofday', 344 '__x64_sys_getrlimit', 345 '__x64_sys_getrusage', 346 '__x64_sys_sysinfo', 347 '__x64_sys_times', 348 '__x64_sys_ptrace', 349 '__x64_sys_getuid', 350 '__x64_sys_syslog', 351 '__x64_sys_getgid', 352 '__x64_sys_setuid', 353 '__x64_sys_setgid', 354 '__x64_sys_geteuid', 355 '__x64_sys_getegid', 356 '__x64_sys_setpgid', 357 '__x64_sys_getppid', 358 '__x64_sys_getpgrp', 359 '__x64_sys_setsid', 360 '__x64_sys_setreuid', 361 '__x64_sys_setregid', 362 '__x64_sys_getgroups', 363 '__x64_sys_setgroups', 364 '__x64_sys_setresuid', 365 '__x64_sys_getresuid', 366 '__x64_sys_setresgid', 367 '__x64_sys_getresgid', 368 '__x64_sys_getpgid', 369 '__x64_sys_setfsuid', 370 '__x64_sys_setfsgid', 371 '__x64_sys_getsid', 372 '__x64_sys_capget', 373 '__x64_sys_capset', 374 '__x64_sys_rt_sigpending', 375 '__x64_sys_rt_sigtimedwait', 376 '__x64_sys_rt_sigqueueinfo', 377 '__x64_sys_rt_sigsuspend', 378 '__x64_sys_sigaltstack', 379 '__x64_sys_utime', 380 '__x64_sys_mknod', 381 'sys_ni_syscall', 382 '__x64_sys_personality', 383 '__x64_sys_ustat', 384 '__x64_sys_statfs', 385 '__x64_sys_fstatfs', 386 '__x64_sys_sysfs', 387 '__x64_sys_getpriority', 388 '__x64_sys_setpriority', 389 '__x64_sys_sched_setparam', 390 '__x64_sys_sched_getparam', 391 '__x64_sys_sched_setscheduler', 392 '__x64_sys_sched_getscheduler', 393 '__x64_sys_sched_get_priority_max', 394 '__x64_sys_sched_get_priority_min', 395 '__x64_sys_sched_rr_get_interval', 396 '__x64_sys_mlock', 397 '__x64_sys_munlock', 398 '__x64_sys_mlockall', 399 '__x64_sys_munlockall', 400 '__x64_sys_vhangup', 401 '__x64_sys_modify_ldt', 402 '__x64_sys_pivot_root', 403 '__x64_sys_sysctl', 404 '__x64_sys_prctl', 405 '__x64_sys_arch_prctl', 406 '__x64_sys_adjtimex', 407 '__x64_sys_setrlimit', 408 '__x64_sys_chroot', 409 '__x64_sys_sync', 410 '__x64_sys_acct', 411 '__x64_sys_settimeofday', 412 '__x64_sys_mount', 413 '__x64_sys_umount', 414 '__x64_sys_swapon', 415 '__x64_sys_swapoff', 416 '__x64_sys_reboot', 417 '__x64_sys_sethostname', 418 '__x64_sys_setdomainname', 419 '__x64_sys_iopl', 420 '__x64_sys_ioperm', 421 'sys_ni_syscall', 422 '__x64_sys_init_module', 423 '__x64_sys_delete_module', 424 'sys_ni_syscall', 425 'sys_ni_syscall', 426 '__x64_sys_quotactl', 427 'sys_ni_syscall', 428 'sys_ni_syscall', 429 'sys_ni_syscall', 430 'sys_ni_syscall', 431 'sys_ni_syscall', 432 'sys_ni_syscall', 433 '__x64_sys_gettid', 434 '__x64_sys_readahead', 435 '__x64_sys_setxattr', 436 '__x64_sys_lsetxattr', 437 '__x64_sys_fsetxattr', 438 '__x64_sys_getxattr', 439 '__x64_sys_lgetxattr', 440 '__x64_sys_fgetxattr', 441 '__x64_sys_listxattr', 442 '__x64_sys_llistxattr', 443 '__x64_sys_flistxattr', 444 '__x64_sys_removexattr', 445 '__x64_sys_lremovexattr', 446 '__x64_sys_fremovexattr', 447 '__x64_sys_tkill', 448 '__x64_sys_time', 449 '__x64_sys_futex', 450 '__x64_sys_sched_setaffinity', 451 '__x64_sys_sched_getaffinity', 452 'sys_ni_syscall', 453 '__x64_sys_io_setup', 454 '__x64_sys_io_destroy', 455 '__x64_sys_io_getevents', 456 '__x64_sys_io_submit', 457 '__x64_sys_io_cancel', 458 'sys_ni_syscall', 459 '__x64_sys_lookup_dcookie', 460 '__x64_sys_epoll_create', 461 'sys_ni_syscall', 462 'sys_ni_syscall', 463 '__x64_sys_remap_file_pages', 464 '__x64_sys_getdents64', 465 '__x64_sys_set_tid_address', 466 '__x64_sys_restart_syscall', 467 '__x64_sys_semtimedop', 468 '__x64_sys_fadvise64', 469 '__x64_sys_timer_create', 470 '__x64_sys_timer_settime', 471 '__x64_sys_timer_gettime', 472 '__x64_sys_timer_getoverrun', 473 '__x64_sys_timer_delete', 474 '__x64_sys_clock_settime', 475 '__x64_sys_clock_gettime', 476 '__x64_sys_clock_getres', 477 '__x64_sys_clock_nanosleep', 478 '__x64_sys_exit_group', 479 '__x64_sys_epoll_wait', 480 '__x64_sys_epoll_ctl', 481 '__x64_sys_tgkill', 482 '__x64_sys_utimes', 483 'sys_ni_syscall', 484 '__x64_sys_mbind', 485 '__x64_sys_set_mempolicy', 486 '__x64_sys_get_mempolicy', 487 '__x64_sys_mq_open', 488 '__x64_sys_mq_unlink', 489 '__x64_sys_mq_timedsend', 490 '__x64_sys_mq_timedreceive', 491 '__x64_sys_mq_notify', 492 '__x64_sys_mq_getsetattr', 493 '__x64_sys_kexec_load', 494 '__x64_sys_waitid', 495 '__x64_sys_add_key', 496 '__x64_sys_request_key', 497 '__x64_sys_keyctl', 498 '__x64_sys_ioprio_set', 499 '__x64_sys_ioprio_get', 500 '__x64_sys_inotify_init', 501 '__x64_sys_inotify_add_watch', 502 '__x64_sys_inotify_rm_watch', 503 '__x64_sys_migrate_pages', 504 '__x64_sys_openat', 505 '__x64_sys_mkdirat', 506 '__x64_sys_mknodat', 507 '__x64_sys_fchownat', 508 '__x64_sys_futimesat', 509 '__x64_sys_newfstatat', 510 '__x64_sys_unlinkat', 511 '__x64_sys_renameat', 512 '__x64_sys_linkat', 513 '__x64_sys_symlinkat', 514 '__x64_sys_readlinkat', 515 '__x64_sys_fchmodat', 516 '__x64_sys_faccessat', 517 '__x64_sys_pselect6', 518 '__x64_sys_ppoll', 519 '__x64_sys_unshare', 520 '__x64_sys_set_robust_list', 521 '__x64_sys_get_robust_list', 522 '__x64_sys_splice', 523 '__x64_sys_tee', 524 '__x64_sys_sync_file_range', 525 '__x64_sys_vmsplice', 526 '__x64_sys_move_pages', 527 '__x64_sys_utimensat', 528 '__x64_sys_epoll_pwait', 529 '__x64_sys_signalfd', 530 '__x64_sys_timerfd_create', 531 '__x64_sys_eventfd', 532 '__x64_sys_fallocate', 533 '__x64_sys_timerfd_settime', 534 '__x64_sys_timerfd_gettime', 535 '__x64_sys_accept4', 536 '__x64_sys_signalfd4', 537 '__x64_sys_eventfd2', 538 '__x64_sys_epoll_create1', 539 '__x64_sys_dup3', 540 '__x64_sys_pipe2', 541 '__x64_sys_inotify_init1', 542 '__x64_sys_preadv', 543 '__x64_sys_pwritev', 544 '__x64_sys_rt_tgsigqueueinfo', 545 '__x64_sys_perf_event_open', 546 '__x64_sys_recvmmsg', 547 '__x64_sys_fanotify_init', 548 '__x64_sys_fanotify_mark', 549 '__x64_sys_prlimit64', 550 '__x64_sys_name_to_handle_at', 551 '__x64_sys_open_by_handle_at', 552 '__x64_sys_clock_adjtime', 553 '__x64_sys_syncfs', 554 '__x64_sys_sendmmsg', 555 '__x64_sys_setns', 556 '__x64_sys_getcpu', 557 '__x64_sys_process_vm_readv', 558 '__x64_sys_process_vm_writev', 559 '__x64_sys_kcmp', 560 '__x64_sys_finit_module', 561 '__x64_sys_sched_setattr', 562 '__x64_sys_sched_getattr', 563 '__x64_sys_renameat2', 564 '__x64_sys_seccomp', 565 '__x64_sys_getrandom', 566 '__x64_sys_memfd_create', 567 '__x64_sys_kexec_file_load', 568 '__x64_sys_bpf', 569 '__x64_sys_execveat', 570 '__x64_sys_userfaultfd', 571 '__x64_sys_membarrier', 572 '__x64_sys_mlock2', 573 '__x64_sys_copy_file_range', 574 '__x64_sys_preadv2', 575 '__x64_sys_pwritev2', 576 '__x64_sys_pkey_mprotect', 577 '__x64_sys_pkey_alloc', 578 '__x64_sys_pkey_free', 579 '__x64_sys_statx', 580 '__x64_sys_io_pgetevents', 581 '__x64_sys_rseq' 582 ] 583 584 class Relocs(IntEnum): 585 R_X86_64_64 = 1 586 R_X86_64_PC32 = 2 587 R_X86_64_PLT32 = 4 588 R_X86_64_32 = 10 589 R_X86_64_32S = 11 590 591 class RkCheckFunctions(gdb.Command): 592 """Check the integrity of the functions in the kernel.""" 593 594 f = None 595 s = None 596 597 symbols = None 598 headers = None 599 600 use_memoization = False 601 602 same_count = 0 603 diff_count = 0 604 skip_count = 0 605 606 #Key: symbol, value: tuple (size, code bytes from ELF file) 607 code_dict = {} 608 609 #Key: symbol, value: list of ranges for exclude bytes (relative to function entry!) 610 altinstr_dict = {} 611 paravirt_dict = {} 612 613 #Key: address to bytes, value: (rel_type, addend, value) 614 relatext_dict = {} 615 616 def __init__(self): 617 super(RkCheckFunctions, self).__init__("rk-check-functions", gdb.COMMAND_USER, gdb.COMMAND_DATA) 618 619 # assuming rk-load-symbols has already been run 620 def invoke(self, arg, from_tty): 621 global file_g 622 623 if file_g is None: 624 print("no object file has been read in to calculate offsets, please run `rk-load-symbols` first") 625 return None 626 627 self.f = elffile.ELFFile(open(file_g, "rb")) 628 self.s = self.f.get_section_by_name(".symtab") 629 630 print("this will take a while") 631 print("populating dictionaries...", end='', flush=True) 632 self.fill_code_dict() 633 self.fill_altinstr_dict() 634 self.fill_paravirt_dict() 635 print(" done!") 636 637 print("comparing functions...") 638 print("listing functions that differ", flush=True) 639 self.compare_functions() 640 print("...done!") 641 642 print(f"{self.diff_count} functions differ, {self.same_count} are equal, {self.skip_count} symbols skipped") 643 644 645 def fill_code_dict(self): 646 global file_g 647 global v_off_g 648 649 tmp = ".tmpvml" 650 651 s = subprocess.run(f"objcopy --change-addresses={v_off_g} {file_g} {tmp}", shell=True) 652 653 if s.returncode != 0: 654 print("Error running objcopy!") 655 return None 656 657 # TODO just grab inferior id from add-inferior.. 658 # -no-connection is _very_ important, otherwise we read in the live bytes from the vm again 659 gdb.execute(f"add-inferior -exec {tmp} -no-connection") 660 gdb.execute("inferior 2") 661 662 for symbol in self.s.iter_symbols(): 663 if symbol.entry["st_info"]["type"] == "STT_FUNC": 664 name = symbol.name 665 size = symbol.entry["st_size"] 666 667 try: 668 a = gdb.execute(f"x {name} + {v_off_g}", to_string=True).split(" ")[0] 669 except: 670 self.skip_count += 1 671 continue 672 673 try: 674 addr = int(a, 16) 675 elf = gdb.selected_inferior().read_memory(addr, size) 676 except: 677 self.skip_count += 1 678 continue 679 680 self.code_dict[(name, addr)] = (size, bytes(elf).hex()) 681 682 gdb.execute("inferior 1") 683 684 685 def fill_altinstr_dict(self): 686 global file_g 687 global v_off_g 688 689 # alt_instr layout (read from elf section .altinstructions, size: 13 bytes): 690 # .long offset <-- Adress to instructions we ignore: addr = (__alt_instructions + cur (offset into .altinstructions)) + offset + v_off_g 691 # .long repl_offset 692 # .word cpuid 693 # .byte instrlen 694 # .byte replacementlen <-- How many instructions we skip 695 # .byte padlen 696 697 sec = self.f.get_section_by_name(".altinstructions") 698 data = sec.data() 699 700 alt_instr_sz = 13 701 replacementlen_off = 11 702 703 i = 0 704 while i < sec["sh_size"]: 705 addr = (sec["sh_addr"] + i) + int.from_bytes(data[i:(i + 4)], byteorder="little", signed=True) + v_off_g 706 replacementlen = int.from_bytes(data[(i + replacementlen_off):(i + replacementlen_off + 1)], byteorder="little", signed=False) 707 708 info = gdb.execute(f"info symbol {addr}", to_string=True).split(" ") 709 710 key = info[0] 711 712 if info[1] == "+": 713 t = int(info[2]) 714 value = range(t, 2 * (t + replacementlen)) 715 else: 716 value = range(2 * replacementlen) 717 718 if key in self.altinstr_dict: 719 self.altinstr_dict[key].append(value) 720 else: 721 self.altinstr_dict[key] = [value] 722 723 i += alt_instr_sz 724 725 def fill_paravirt_dict(self): 726 global file_g 727 global v_off_g 728 729 # paravirt_patch_site layout (read from elf section .parainstructions, size with padding: 16 bytes): 730 # .quad instr <-- Adress to instruction = instr + v_off_g 731 # .byte instrtype 732 # .byte len 733 # .short clobbers 734 # 4 byte padding 735 736 # struct paravirt_patch_site { 737 # u8 *instr; /* original instructions */ 738 # u8 instrtype; /* type of this instruction */ 739 # u8 len; /* length of original instruction */ 740 # u16 clobbers; /* what registers you may clobber */ 741 #}; 742 743 sec = self.f.get_section_by_name(".parainstructions") 744 data = sec.data() 745 746 paravirt_patch_site_sz = 16 747 len_off = 9 748 749 i = 0 750 while i < sec["sh_size"]: 751 addr = int.from_bytes(data[i:(i + 8)], byteorder="little", signed=False) + v_off_g 752 len_ = int.from_bytes(data[(i + len_off):(i + len_off + 1)], byteorder="little", signed=False) 753 754 info = gdb.execute(f"info symbol {addr}", to_string=True).split(" ") 755 756 key = info[0] 757 758 if info[1] == "+": 759 t = int(info[2]) 760 value = range(t, 2 * (t + len_)) 761 else: 762 value = range(2 * len_) 763 764 if key in self.paravirt_dict: 765 self.paravirt_dict[key].append(value) 766 else: 767 self.paravirt_dict[key] = [value] 768 769 i += paravirt_patch_site_sz 770 771 772 773 def compare_functions(self): 774 global v_off_g 775 776 for (name, addr), (size, elf) in self.code_dict.items(): 777 try: 778 live = gdb.selected_inferior().read_memory(addr, size) 779 live = bytes(live).hex() 780 except: 781 self.skip_count += 1 782 continue 783 784 to_exclude = [] 785 if len(live) > 1 and live[0:2] == "0f": 786 to_exclude += list(range(10)) 787 788 # https://lore.kernel.org/patchwork/patch/391755/ 789 # performance optimization: only check entire function if first byte matches 790 if len(live) > 1 and live[0:2] == "cc": 791 int3_chain = ''.join('c' * len(live)) 792 if live == int3_chain: 793 self.skip_count += 1 794 continue 795 796 if len(live) > 1 and live[0:2] == "00": 797 null_chain = ''.join('0' * len(live)) 798 if live == null_chain: 799 self.skip_count += 1 800 continue 801 802 to_exclude_paravirt = [l for r in self.paravirt_dict[name] 803 for l in list(r)] if name in self.paravirt_dict else [] 804 805 to_exclude_altinstr = [l for r in self.altinstr_dict[name] 806 for l in list(r)] if name in self.altinstr_dict else [] 807 808 to_exclude += to_exclude_paravirt + to_exclude_altinstr 809 810 if to_exclude: 811 elf = "".join([elf_byte for i, elf_byte in enumerate(elf) 812 if i not in to_exclude]) 813 814 live = "".join([live_byte for i, live_byte in enumerate(live) 815 if i not in to_exclude]) 816 817 i = 0 818 off = v_off_g >> 16 819 max_len = len(live) 820 resolved = True 821 822 # loop over bytes character-by-character 823 while i < max_len: 824 if live[i] != elf[i]: 825 elf_base = int("0x" + elf[i+2:i+4] + elf[i:i+2], 16) 826 live_base = int("0x" + live[i+2:i+4] + live[i:i+2], 16) 827 828 # KASLR offset has not yet been applied 829 if elf_base + off == live_base: 830 i += 4 831 continue 832 833 # KASLR offset has been unnecessarily applied 834 if live_base + off == elf_base: 835 i += 4 836 continue 837 838 # account for the LOCK prefix 839 # https://stackoverflow.com/a/8891781/11069175 840 if elf[i:i+2] == "f0" or live[i:i+2] == "f0": 841 i += 2 842 continue 843 844 # pattern: nop -> jmp 845 if elf[i:i+4] == "0f1f" and live[i:i+2] == "e9": 846 i += 10 847 continue 848 849 # pattern: call -> nop 850 if elf[i:i+2] == "e8" and live[i:i+4] == "0f1f": 851 i += 10 852 continue 853 854 resolved = False 855 break 856 else: 857 i += 1 858 859 if resolved: 860 self.same_count += 1 861 else: 862 print(name) 863 self.diff_count += 1 864 865 def get_v_addr(self, symbol): 866 try: 867 return gdb.execute(f"x {symbol}", to_string=True).split(" ")[0] 868 except: 869 return None 870 871 RkCheckFunctions()