Skip to content

Instantly share code, notes, and snippets.

@koute
Last active February 3, 2024 17:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save koute/166f82bfee5e27324077891008fca6eb to your computer and use it in GitHub Desktop.
Save koute/166f82bfee5e27324077891008fca6eb to your computer and use it in GitHub Desktop.
A script to statically list syscalls used by a given binary
#!/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