linux-rootkit

Feature-rich interactive rootkit that targets Linux kernel 4.19, accompanied by a dynamic kernel memory analysis GDB plugin for in vivo introspection (e.g. using QEMU)
git clone git://git.deurzen.net/linux-rootkit
Log | Files | Refs

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()