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