Last active
February 3, 2024 17:29
-
-
Save koute/166f82bfee5e27324077891008fca6eb to your computer and use it in GitHub Desktop.
A script to statically list syscalls used by a given binary
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/ruby | |
| require "shellwords" | |
| require "set" | |
| # Generated from `libc` using the following regex: | |
| # 'pub const SYS_([a-z0-9_]+): ::c_long = (\d+);' | |
| # ' \2 => "\1",' | |
| SYSCALLS = { | |
| 0 => "read", | |
| 1 => "write", | |
| 2 => "open", | |
| 3 => "close", | |
| 4 => "stat", | |
| 5 => "fstat", | |
| 6 => "lstat", | |
| 7 => "poll", | |
| 8 => "lseek", | |
| 9 => "mmap", | |
| 10 => "mprotect", | |
| 11 => "munmap", | |
| 12 => "brk", | |
| 13 => "rt_sigaction", | |
| 14 => "rt_sigprocmask", | |
| 15 => "rt_sigreturn", | |
| 16 => "ioctl", | |
| 17 => "pread64", | |
| 18 => "pwrite64", | |
| 19 => "readv", | |
| 20 => "writev", | |
| 21 => "access", | |
| 22 => "pipe", | |
| 23 => "select", | |
| 24 => "sched_yield", | |
| 25 => "mremap", | |
| 26 => "msync", | |
| 27 => "mincore", | |
| 28 => "madvise", | |
| 29 => "shmget", | |
| 30 => "shmat", | |
| 31 => "shmctl", | |
| 32 => "dup", | |
| 33 => "dup2", | |
| 34 => "pause", | |
| 35 => "nanosleep", | |
| 36 => "getitimer", | |
| 37 => "alarm", | |
| 38 => "setitimer", | |
| 39 => "getpid", | |
| 40 => "sendfile", | |
| 41 => "socket", | |
| 42 => "connect", | |
| 43 => "accept", | |
| 44 => "sendto", | |
| 45 => "recvfrom", | |
| 46 => "sendmsg", | |
| 47 => "recvmsg", | |
| 48 => "shutdown", | |
| 49 => "bind", | |
| 50 => "listen", | |
| 51 => "getsockname", | |
| 52 => "getpeername", | |
| 53 => "socketpair", | |
| 54 => "setsockopt", | |
| 55 => "getsockopt", | |
| 56 => "clone", | |
| 57 => "fork", | |
| 58 => "vfork", | |
| 59 => "execve", | |
| 60 => "exit", | |
| 61 => "wait4", | |
| 62 => "kill", | |
| 63 => "uname", | |
| 64 => "semget", | |
| 65 => "semop", | |
| 66 => "semctl", | |
| 67 => "shmdt", | |
| 68 => "msgget", | |
| 69 => "msgsnd", | |
| 70 => "msgrcv", | |
| 71 => "msgctl", | |
| 72 => "fcntl", | |
| 73 => "flock", | |
| 74 => "fsync", | |
| 75 => "fdatasync", | |
| 76 => "truncate", | |
| 77 => "ftruncate", | |
| 78 => "getdents", | |
| 79 => "getcwd", | |
| 80 => "chdir", | |
| 81 => "fchdir", | |
| 82 => "rename", | |
| 83 => "mkdir", | |
| 84 => "rmdir", | |
| 85 => "creat", | |
| 86 => "link", | |
| 87 => "unlink", | |
| 88 => "symlink", | |
| 89 => "readlink", | |
| 90 => "chmod", | |
| 91 => "fchmod", | |
| 92 => "chown", | |
| 93 => "fchown", | |
| 94 => "lchown", | |
| 95 => "umask", | |
| 96 => "gettimeofday", | |
| 97 => "getrlimit", | |
| 98 => "getrusage", | |
| 99 => "sysinfo", | |
| 100 => "times", | |
| 101 => "ptrace", | |
| 102 => "getuid", | |
| 103 => "syslog", | |
| 104 => "getgid", | |
| 105 => "setuid", | |
| 106 => "setgid", | |
| 107 => "geteuid", | |
| 108 => "getegid", | |
| 109 => "setpgid", | |
| 110 => "getppid", | |
| 111 => "getpgrp", | |
| 112 => "setsid", | |
| 113 => "setreuid", | |
| 114 => "setregid", | |
| 115 => "getgroups", | |
| 116 => "setgroups", | |
| 117 => "setresuid", | |
| 118 => "getresuid", | |
| 119 => "setresgid", | |
| 120 => "getresgid", | |
| 121 => "getpgid", | |
| 122 => "setfsuid", | |
| 123 => "setfsgid", | |
| 124 => "getsid", | |
| 125 => "capget", | |
| 126 => "capset", | |
| 127 => "rt_sigpending", | |
| 128 => "rt_sigtimedwait", | |
| 129 => "rt_sigqueueinfo", | |
| 130 => "rt_sigsuspend", | |
| 131 => "sigaltstack", | |
| 132 => "utime", | |
| 133 => "mknod", | |
| 134 => "uselib", | |
| 135 => "personality", | |
| 136 => "ustat", | |
| 137 => "statfs", | |
| 138 => "fstatfs", | |
| 139 => "sysfs", | |
| 140 => "getpriority", | |
| 141 => "setpriority", | |
| 142 => "sched_setparam", | |
| 143 => "sched_getparam", | |
| 144 => "sched_setscheduler", | |
| 145 => "sched_getscheduler", | |
| 146 => "sched_get_priority_max", | |
| 147 => "sched_get_priority_min", | |
| 148 => "sched_rr_get_interval", | |
| 149 => "mlock", | |
| 150 => "munlock", | |
| 151 => "mlockall", | |
| 152 => "munlockall", | |
| 153 => "vhangup", | |
| 154 => "modify_ldt", | |
| 155 => "pivot_root", | |
| 156 => "_sysctl", | |
| 157 => "prctl", | |
| 158 => "arch_prctl", | |
| 159 => "adjtimex", | |
| 160 => "setrlimit", | |
| 161 => "chroot", | |
| 162 => "sync", | |
| 163 => "acct", | |
| 164 => "settimeofday", | |
| 165 => "mount", | |
| 166 => "umount2", | |
| 167 => "swapon", | |
| 168 => "swapoff", | |
| 169 => "reboot", | |
| 170 => "sethostname", | |
| 171 => "setdomainname", | |
| 172 => "iopl", | |
| 173 => "ioperm", | |
| 174 => "create_module", | |
| 175 => "init_module", | |
| 176 => "delete_module", | |
| 177 => "get_kernel_syms", | |
| 178 => "query_module", | |
| 179 => "quotactl", | |
| 180 => "nfsservctl", | |
| 181 => "getpmsg", | |
| 182 => "putpmsg", | |
| 183 => "afs_syscall", | |
| 184 => "tuxcall", | |
| 185 => "security", | |
| 186 => "gettid", | |
| 187 => "readahead", | |
| 188 => "setxattr", | |
| 189 => "lsetxattr", | |
| 190 => "fsetxattr", | |
| 191 => "getxattr", | |
| 192 => "lgetxattr", | |
| 193 => "fgetxattr", | |
| 194 => "listxattr", | |
| 195 => "llistxattr", | |
| 196 => "flistxattr", | |
| 197 => "removexattr", | |
| 198 => "lremovexattr", | |
| 199 => "fremovexattr", | |
| 200 => "tkill", | |
| 201 => "time", | |
| 202 => "futex", | |
| 203 => "sched_setaffinity", | |
| 204 => "sched_getaffinity", | |
| 205 => "set_thread_area", | |
| 206 => "io_setup", | |
| 207 => "io_destroy", | |
| 208 => "io_getevents", | |
| 209 => "io_submit", | |
| 210 => "io_cancel", | |
| 211 => "get_thread_area", | |
| 212 => "lookup_dcookie", | |
| 213 => "epoll_create", | |
| 214 => "epoll_ctl_old", | |
| 215 => "epoll_wait_old", | |
| 216 => "remap_file_pages", | |
| 217 => "getdents64", | |
| 218 => "set_tid_address", | |
| 219 => "restart_syscall", | |
| 220 => "semtimedop", | |
| 221 => "fadvise64", | |
| 222 => "timer_create", | |
| 223 => "timer_settime", | |
| 224 => "timer_gettime", | |
| 225 => "timer_getoverrun", | |
| 226 => "timer_delete", | |
| 227 => "clock_settime", | |
| 228 => "clock_gettime", | |
| 229 => "clock_getres", | |
| 230 => "clock_nanosleep", | |
| 231 => "exit_group", | |
| 232 => "epoll_wait", | |
| 233 => "epoll_ctl", | |
| 234 => "tgkill", | |
| 235 => "utimes", | |
| 236 => "vserver", | |
| 237 => "mbind", | |
| 238 => "set_mempolicy", | |
| 239 => "get_mempolicy", | |
| 240 => "mq_open", | |
| 241 => "mq_unlink", | |
| 242 => "mq_timedsend", | |
| 243 => "mq_timedreceive", | |
| 244 => "mq_notify", | |
| 245 => "mq_getsetattr", | |
| 246 => "kexec_load", | |
| 247 => "waitid", | |
| 248 => "add_key", | |
| 249 => "request_key", | |
| 250 => "keyctl", | |
| 251 => "ioprio_set", | |
| 252 => "ioprio_get", | |
| 253 => "inotify_init", | |
| 254 => "inotify_add_watch", | |
| 255 => "inotify_rm_watch", | |
| 256 => "migrate_pages", | |
| 257 => "openat", | |
| 258 => "mkdirat", | |
| 259 => "mknodat", | |
| 260 => "fchownat", | |
| 261 => "futimesat", | |
| 262 => "newfstatat", | |
| 263 => "unlinkat", | |
| 264 => "renameat", | |
| 265 => "linkat", | |
| 266 => "symlinkat", | |
| 267 => "readlinkat", | |
| 268 => "fchmodat", | |
| 269 => "faccessat", | |
| 270 => "pselect6", | |
| 271 => "ppoll", | |
| 272 => "unshare", | |
| 273 => "set_robust_list", | |
| 274 => "get_robust_list", | |
| 275 => "splice", | |
| 276 => "tee", | |
| 277 => "sync_file_range", | |
| 278 => "vmsplice", | |
| 279 => "move_pages", | |
| 280 => "utimensat", | |
| 281 => "epoll_pwait", | |
| 282 => "signalfd", | |
| 283 => "timerfd_create", | |
| 284 => "eventfd", | |
| 285 => "fallocate", | |
| 286 => "timerfd_settime", | |
| 287 => "timerfd_gettime", | |
| 288 => "accept4", | |
| 289 => "signalfd4", | |
| 290 => "eventfd2", | |
| 291 => "epoll_create1", | |
| 292 => "dup3", | |
| 293 => "pipe2", | |
| 294 => "inotify_init1", | |
| 295 => "preadv", | |
| 296 => "pwritev", | |
| 297 => "rt_tgsigqueueinfo", | |
| 298 => "perf_event_open", | |
| 299 => "recvmmsg", | |
| 300 => "fanotify_init", | |
| 301 => "fanotify_mark", | |
| 302 => "prlimit64", | |
| 303 => "name_to_handle_at", | |
| 304 => "open_by_handle_at", | |
| 305 => "clock_adjtime", | |
| 306 => "syncfs", | |
| 307 => "sendmmsg", | |
| 308 => "setns", | |
| 309 => "getcpu", | |
| 310 => "process_vm_readv", | |
| 311 => "process_vm_writev", | |
| 312 => "kcmp", | |
| 313 => "finit_module", | |
| 314 => "sched_setattr", | |
| 315 => "sched_getattr", | |
| 316 => "renameat2", | |
| 317 => "seccomp", | |
| 318 => "getrandom", | |
| 319 => "memfd_create", | |
| 320 => "kexec_file_load", | |
| 321 => "bpf", | |
| 322 => "execveat", | |
| 323 => "userfaultfd", | |
| 324 => "membarrier", | |
| 325 => "mlock2", | |
| 326 => "copy_file_range", | |
| 327 => "preadv2", | |
| 328 => "pwritev2", | |
| 329 => "pkey_mprotect", | |
| 330 => "pkey_alloc", | |
| 331 => "pkey_free", | |
| 332 => "statx", | |
| 334 => "rseq", | |
| 424 => "pidfd_send_signal", | |
| 425 => "io_uring_setup", | |
| 426 => "io_uring_enter", | |
| 427 => "io_uring_register", | |
| 428 => "open_tree", | |
| 429 => "move_mount", | |
| 430 => "fsopen", | |
| 431 => "fsconfig", | |
| 432 => "fsmount", | |
| 433 => "fspick", | |
| 434 => "pidfd_open", | |
| 435 => "clone3", | |
| 436 => "close_range", | |
| 437 => "openat2", | |
| 438 => "pidfd_getfd", | |
| 439 => "faccessat2", | |
| 440 => "process_madvise", | |
| 441 => "epoll_pwait2", | |
| 442 => "mount_setattr", | |
| 443 => "quotactl_fd", | |
| 444 => "landlock_create_ruleset", | |
| 445 => "landlock_add_rule", | |
| 446 => "landlock_restrict_self", | |
| 447 => "memfd_secret", | |
| 448 => "process_mrelease", | |
| 449 => "futex_waitv", | |
| 450 => "set_mempolicy_home_node", | |
| }.map { |num, name| [num, "#{num} (#{name})"] }.to_h | |
| REGS_R64 = [ | |
| "rax", | |
| "rbx", | |
| "rcx", | |
| "rdx", | |
| "rsi", | |
| "rdi", | |
| "rsp", | |
| "rbp", | |
| "r8", | |
| "r9", | |
| "r10", | |
| "r11", | |
| "r12", | |
| "r13", | |
| "r14", | |
| "r15" | |
| ] | |
| REGS_R32 = [ | |
| "eax", | |
| "ebx", | |
| "ecx", | |
| "edx", | |
| "esi", | |
| "edi", | |
| "esp", | |
| "ebp", | |
| "r8d", | |
| "r9d", | |
| "r10d", | |
| "r11d", | |
| "r12d", | |
| "r13d", | |
| "r14d", | |
| "r15d" | |
| ] | |
| REGS_R16 = [ | |
| "ax", | |
| "bx", | |
| "cx", | |
| "dx", | |
| "si", | |
| "di", | |
| "sp", | |
| "bp", | |
| "r8w", | |
| "r9w", | |
| "r10w", | |
| "r11w", | |
| "r12w", | |
| "r13w", | |
| "r14w", | |
| "r15w" | |
| ] | |
| REGS_R8 = [ | |
| "al", | |
| "bl", | |
| "cl", | |
| "dl", | |
| "sil", | |
| "dil", | |
| "spl", | |
| "bpl", | |
| "r8b", | |
| "r9b", | |
| "r10b", | |
| "r11b", | |
| "r12b", | |
| "r13b", | |
| "r14b", | |
| "r15b" | |
| ] | |
| REG_MAP = (REGS_R64.map { |r| [r, r] } + REGS_R32.zip(REGS_R64) + REGS_R16.zip(REGS_R64) + REGS_R8.zip(REGS_R64)).to_h | |
| REGS_R = (REGS_R64 + REGS_R32 + REGS_R16 + REGS_R8).join("|") | |
| def is_syscall_jump input | |
| input =~ /\b(call|jmp)\b/ && input =~ /<(syscall|__syscall_cp)(@GLIBC_[^>]+)?>/ | |
| end | |
| raise unless is_syscall_jump "call 4b4ee <__syscall_cp>" | |
| raise unless is_syscall_jump "call QWORD PTR [rip+0x58644d] # c93ca8 <syscall@GLIBC_2.2.5>" | |
| if ARGV.length != 1 | |
| STDERR.puts "Syntex: list-syscalls.rb <binary>" | |
| exit 1 | |
| end | |
| raise "no such file: #{ARGV[0]}" unless File.exist? ARGV[0] | |
| puts "Running objdump..." | |
| dump = `objdump -wd -j .text -M intel #{ARGV[0].shellescape}` | |
| raise "objdump failed" unless $?.exitstatus == 0 | |
| puts "Parsing objdump output..." | |
| current_fn = nil | |
| code_for_fn = {} | |
| fns_with_syscall = Set.new | |
| fns_with_indirect_syscall = Set.new | |
| dump.split("\n").each do |line| | |
| if line =~ /\A[0-9a-f]+ <(.+?)>:/ | |
| current_fn = $1 | |
| next | |
| end | |
| next unless current_fn | |
| next if current_fn == "syscall" || current_fn == "__syscall_cp_c" # These are for indirect syscalls. | |
| code = line.strip.split("\t")[2] | |
| next if code == nil || code == "" | |
| code_for_fn[current_fn] ||= [] | |
| code_for_fn[current_fn] << code.gsub(/[\t ]+/, " ") | |
| fns_with_syscall.add(current_fn) if code == "syscall" | |
| fns_with_indirect_syscall.add(current_fn) if code =~ /<(syscall|__syscall_cp)(@GLIBC_[^>]+)?>/ | |
| end | |
| puts "Found #{fns_with_syscall.length} functions doing direct syscalls" | |
| puts "Found #{fns_with_indirect_syscall.length} functions doing indirect syscalls" | |
| syscalls_for_fn = {} | |
| not_found_count = 0 | |
| (fns_with_syscall + fns_with_indirect_syscall).each do |fn_name| | |
| syscalls_for_fn[fn_name] ||= [] | |
| if fn_name =~ /_ZN11parking_lot9raw_mutex8RawMutex9lock_slow.+/ | |
| # Hardcode 'SYS_futex' as this function produces a really messy assembly. | |
| syscalls_for_fn[fn_name] << 202 | |
| next | |
| end | |
| code = code_for_fn[fn_name] | |
| found = false | |
| regs = {} | |
| code.each do |inst| | |
| if inst =~ /mov (#{REGS_R}),QWORD PTR \[rip\+0x[a-f0-9]+\] # [a-f0-9]+ <syscall/ | |
| reg = $1 | |
| regs[REG_MAP[reg]] = "syscall" | |
| elsif inst =~ /mov (#{REGS_R}),(.+)/ | |
| reg, value = $1, $2 | |
| if value =~ /#{REGS_R}/ | |
| regs[REG_MAP[reg]] = regs[REG_MAP[value]] | |
| elsif value =~ /0x([0-9a-f]+)/ | |
| regs[REG_MAP[reg]] = $1.to_i(16) | |
| else | |
| regs[REG_MAP[reg]] = nil | |
| end | |
| elsif inst =~ /xor (#{REGS_R}),(#{REGS_R})/ | |
| reg_1, reg_2 = $1, $1 | |
| if reg_1 == reg_2 | |
| regs[REG_MAP[reg_1]] = 0 | |
| end | |
| elsif inst =~ /lea (#{REGS_R}),(.+)/ | |
| reg, value = $1, $2 | |
| if value.strip =~ /\[rip\+0x[a-z0-9]+\]\s*#\s*[0-9a-f]+\s*<syscall>/ | |
| regs[REG_MAP[reg]] = "syscall" | |
| else | |
| regs[REG_MAP[reg]] = nil | |
| end | |
| elsif inst =~ /(call|jmp) (#{REGS_R})/ | |
| reg = $2 | |
| if regs[REG_MAP[reg]] == "syscall" | |
| if regs["rdi"] != nil | |
| syscalls_for_fn[fn_name] << regs["rdi"] | |
| found = true | |
| else | |
| found = false | |
| end | |
| end | |
| elsif is_syscall_jump(inst) | |
| if regs["rdi"] != nil | |
| syscalls_for_fn[fn_name] << regs["rdi"] | |
| found = true | |
| else | |
| found = false | |
| end | |
| elsif inst == "syscall" | |
| if regs["rax"] != nil | |
| syscalls_for_fn[fn_name] << regs["rax"] | |
| found = true | |
| else | |
| found = false | |
| end | |
| end | |
| end | |
| unless found | |
| puts "WARN: Function triggers a syscall but couldn't figure out which one: #{fn_name}" | |
| puts " " + code.join("\n ") | |
| puts | |
| not_found_count += 1 | |
| end | |
| end | |
| puts "WARN: Failed to figure out syscall for #{not_found_count} function(s)" if not_found_count > 0 | |
| fns_for_syscall = {} | |
| syscalls_for_fn.each do |fn_name, syscalls| | |
| syscalls.each do |syscall| | |
| fns_for_syscall[syscall] ||= [] | |
| fns_for_syscall[syscall] << fn_name | |
| end | |
| end | |
| puts "Functions per syscall:" | |
| fns_for_syscall.sort_by { |sc, _| sc }.each do |syscall, fn_names| | |
| fn_names = fn_names.sort.uniq | |
| puts " #{SYSCALLS[syscall] || syscall} [#{fn_names.length} functions]" | |
| fn_names.each do |fn_name| | |
| puts " #{fn_name}" | |
| end | |
| end | |
| puts | |
| puts "Used syscalls:" | |
| puts " " + syscalls_for_fn.values.flatten.sort.uniq.map { |sc| SYSCALLS[sc] || sc }.join("\n ") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment