├── LICENSE ├── README.md ├── index.html ├── log └── Capability.log ├── sample-input ├── input.json └── setuidAll.json ├── sample-output └── Output_Stat.csv ├── src ├── decap.py ├── libc-callgraphs │ └── glibc.callgraph └── python-utils │ ├── binaryAnalysis.py │ ├── capabilityAnalysis.py │ ├── graph.py │ ├── syscall.py │ ├── syscallToCapabilityMapping.py │ ├── sysfilter.py │ └── util.py └── website ├── about.html ├── images ├── DecapOverview.png └── mapping.png ├── mapping.js ├── stepbystepguide.html ├── style.css ├── syscall-capabilitymapping.html └── syscall-capabilitymappingtable.html /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 MdMehedi Hasan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Decap 2 | Decap is a binary code analysis tool that automatically deprivileges programs by identifying the subset of capabilities they require based on the system calls they may invoke. This is made possible by our systematic effort in deriving a complete mapping between all Linux system calls related to privileged operations and the corresponding capabilities on which they depend. We suggest reading our [paper](https://www3.cs.stonybrook.edu/~mdhasan/papers/decap.raid22.pdf) for more details. 3 | 4 | Decap overview figure 7 | 8 | ### System call - Capability Mapping 9 | We performed a thorough investigation of all available Linux capabilities and the system calls they affect, to derive a detailed and complete mapping between all system calls related to privileged operations and their respective capabilities. The complete mapping can be found [here](https://hasanmdme.github.io/decap/website/syscall-capabilitymappingtable.html), and a visual representation of the mapping is available [here](https://hasanmdme.github.io/decap/website/syscall-capabilitymapping.html). 10 | 11 | Mapping figure 14 | 15 | ### System call Identification and Capability Enforcement 16 | The first step in deprivileging a setuid program is to identify its required system calls. Decap performs static binary code analysis of a target program and its libraries to extract the set of all possible system calls it may invoke, and then to derive and enforce the corresponding set of required capabilities. Decap relies on both [Confine](https://www3.cs.stonybrook.edu/~sghavamnia/papers/confine.raid20.pdf) and [Sysfilter](https://cs.brown.edu/~vpk/papers/sysfilter.raid20.pdf) to identify the system calls required by a given application. Also, CAP_SYS_ADMIN capability is required by multiple system calls but only when invoked with a limited set of specific argument values. Therefore, once the set of required system calls has been extracted, Decap performs argumentlevel analysis for those system calls that conditionally require CAP_SYS_ADMIN, and attempts to extract the concrete values passed to the arguments that determine whether CAP_SYS_ADMIN is required, across all their call sites. Decap identifies the required capabilities for an application based on the system call analysis and the previously generated mapping. Finally, it reduces the privileges by first deprivileging the target application entirely by removing its setuid bit, and then granting only the capabilities that the program actually requires. 17 | 18 | ## Step by Step Guide 19 | This section shows the step by step guide for running Decap to generate capability profile of setuid binaries. [Read more...](https://hasanmdme.github.io/decap/website/stepbystepguide.html) 20 | 21 | ## Academic Publication 22 | Please use the following citation for [Decap](https://www3.cs.stonybrook.edu/~mdhasan/papers/decap.raid22.pdf). 23 | ``` 24 | @inproceedings{decapraid22, 25 | title = {Decap: Deprivileging Programs by Reducing Their Capabilities}, 26 | author = {Hasan, Md Mehedi and Ghavamnia, Seyedhamed and Polychronakis, Michalis}, 27 | booktitle = {Proceedings of the International Conference on Research in Attacks, 28 | Intrusions, and Defenses (RAID)}, 29 | year = {2022} 30 | } 31 | ``` 32 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Decap demo 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 |
18 |

Decap: Deprivileging Programs by Reducing Their Capabilities

19 | 20 |
21 |

About Decap

22 |
23 | 24 |
25 |

While you can find a more complete and thorough description of how Decap works by reading our 26 | paper, 27 | we have summarized some of the most important points in this section. 28 | Read more... 29 |

30 |
31 | 32 |
33 |

Step by Step Guide

34 |
35 | 36 |
37 |

This section shows the step by step guide for running Decap to generate capability profile of setuid binaries. 38 | Read more... 39 |

40 |
41 | 42 |
43 |

Academic Publication

44 |
45 | 46 |
47 |

48 | Please use the following citation for Decap. 49 |

50 |
51 | @inproceedings{decapraid22,
52 | title = {Decap: Deprivileging Programs by Reducing Their Capabilities}, 
53 | author = {Hasan, Md Mehedi and Ghavamnia, Seyedhamed and Polychronakis, Michalis}, 
54 | booktitle = {Proceedings of the International Conference on Research in Attacks,
55 |     Intrusions, and Defenses (RAID)}, 
56 | year = {2022} 
57 | }
58 | 
59 |
60 | 61 |
62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /log/Capability.log: -------------------------------------------------------------------------------- 1 | E 2 | -------------------------------------------------------------------------------- /sample-input/input.json: -------------------------------------------------------------------------------- 1 | { 2 | "nginx": { 3 | "binary-path": "/home/mehedi/test/packageinstall/out/setuidBinary/nginx" 4 | }, 5 | "passwd": { 6 | "binary-path": "/home/mehedi/test/packageinstall/out/setuidBinary/passwd" 7 | }, 8 | "ping": { 9 | "binary-path": "/home/mehedi/test/packageinstall/out/setuidBinary/ping" 10 | }, 11 | "dmcrypt-get-device": { 12 | "binary-path": "/home/mehedi/test/packageinstall/out/setuidBinary/dmcrypt-get-device" 13 | }, 14 | "lpr": { 15 | "binary-path": "/home/mehedi/test/packageinstall/out/setuidBinary/lpr" 16 | }, 17 | "exim4": { 18 | "binary-path": "/home/mehedi/test/packageinstall/out/setuidBinary/exim4" 19 | }, 20 | "unix_chkpwd": { 21 | "binary-path": "/home/mehedi/test/packageinstall/out/setuidBinary/unix_chkpwd" 22 | }, 23 | "tcpdump": { 24 | "binary-path": "/home/mehedi/test/packageinstall/out/setuidBinary/tcpdump" 25 | }, 26 | "httpd": { 27 | "binary-path": "/home/mehedi/test/packageinstall/out/setuidBinary/httpd" 28 | }, 29 | "lighttpd": { 30 | "binary-path": "/home/mehedi/test/packageinstall/out/setuidBinary/lighttpd" 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /sample-input/setuidAll.json: -------------------------------------------------------------------------------- 1 | { 2 | "dbus-daemon-launch-helper": { 3 | "binary-path": "./setuidBinary/dbus-daemon-launch-helper" 4 | }, 5 | "arping": { 6 | "binary-path": "./setuidBinary/arping" 7 | }, 8 | "pkexec": { 9 | "binary-path": "./setuidBinary/pkexec" 10 | }, 11 | "dmcrypt-get-device": { 12 | "binary-path": "./setuidBinary/dmcrypt-get-device" 13 | }, 14 | "Xorg.wrap": { 15 | "binary-path": "./setuidBinary/Xorg.wrap" 16 | }, 17 | "polkit-agent-helper-1": { 18 | "binary-path": "./setuidBinary/polkit-agent-helper-1" 19 | }, 20 | "pppd": { 21 | "binary-path": "./setuidBinary/pppd" 22 | }, 23 | "fusermount": { 24 | "binary-path": "./setuidBinary/fusermount" 25 | }, 26 | "start_kdeinit": { 27 | "binary-path": "./setuidBinary/start_kdeinit" 28 | }, 29 | "sudo": { 30 | "binary-path": "./setuidBinary/sudo" 31 | }, 32 | "at": { 33 | "binary-path": "./setuidBinary/at" 34 | }, 35 | "userhelper": { 36 | "binary-path": "./setuidBinary/userhelper" 37 | }, 38 | "inetutils-traceroute": { 39 | "binary-path": "./setuidBinary/inetutils-traceroute" 40 | }, 41 | "procmail": { 42 | "binary-path": "./setuidBinary/procmail" 43 | }, 44 | "pwauth": { 45 | "binary-path": "./setuidBinary/pwauth" 46 | }, 47 | "mount.nfs": { 48 | "binary-path": "./setuidBinary/mount.nfs" 49 | }, 50 | "ping": { 51 | "binary-path": "./setuidBinary/ping" 52 | }, 53 | "dma-mbox-create": { 54 | "binary-path": "./setuidBinary/dma-mbox-create" 55 | }, 56 | "v4l-conf": { 57 | "binary-path": "./setuidBinary/v4l-conf" 58 | }, 59 | "ping6": { 60 | "binary-path": "./setuidBinary/ping6" 61 | }, 62 | "exim4": { 63 | "binary-path": "./setuidBinary/exim4" 64 | }, 65 | "snap-confine": { 66 | "binary-path": "./setuidBinary/snap-confine" 67 | }, 68 | "spice-client-glib-usb-acl-helper": { 69 | "binary-path": "./setuidBinary/spice-client-glib-usb-acl-helper" 70 | }, 71 | "qmail-queue": { 72 | "binary-path": "./setuidBinary/qmail-queue" 73 | }, 74 | "schroot": { 75 | "binary-path": "./setuidBinary/schroot" 76 | }, 77 | "helper": { 78 | "binary-path": "./setuidBinary/helper" 79 | }, 80 | "pumount": { 81 | "binary-path": "./setuidBinary/pumount" 82 | }, 83 | "pmount": { 84 | "binary-path": "./setuidBinary/pmount" 85 | }, 86 | "start-suid": { 87 | "binary-path": "./setuidBinary/start-suid" 88 | }, 89 | "mount-suid": { 90 | "binary-path": "./setuidBinary/mount-suid" 91 | }, 92 | "action-suid": { 93 | "binary-path": "./setuidBinary/action-suid" 94 | }, 95 | "uux": { 96 | "binary-path": "./setuidBinary/uux" 97 | }, 98 | "uustat": { 99 | "binary-path": "./setuidBinary/uustat" 100 | }, 101 | "uucp": { 102 | "binary-path": "./setuidBinary/uucp" 103 | }, 104 | "uucico": { 105 | "binary-path": "./setuidBinary/uucico" 106 | }, 107 | "uuxqt": { 108 | "binary-path": "./setuidBinary/uuxqt" 109 | }, 110 | "oom-adjust-setuid-helper": { 111 | "binary-path": "./setuidBinary/oom-adjust-setuid-helper" 112 | }, 113 | "vdr-shutdown.wrapper": { 114 | "binary-path": "./setuidBinary/vdr-shutdown.wrapper" 115 | }, 116 | "gtmsecshr": { 117 | "binary-path": "./setuidBinary/gtmsecshr" 118 | }, 119 | "beep": { 120 | "binary-path": "./setuidBinary/beep" 121 | }, 122 | "newgidmap": { 123 | "binary-path": "./setuidBinary/newgidmap" 124 | }, 125 | "newuidmap": { 126 | "binary-path": "./setuidBinary/newuidmap" 127 | }, 128 | "lxc-user-nic": { 129 | "binary-path": "./setuidBinary/lxc-user-nic" 130 | }, 131 | "mount.cifs": { 132 | "binary-path": "./setuidBinary/mount.cifs" 133 | }, 134 | "rsh-redone-rlogin": { 135 | "binary-path": "./setuidBinary/rsh-redone-rlogin" 136 | }, 137 | "rsh-redone-rsh": { 138 | "binary-path": "./setuidBinary/rsh-redone-rsh" 139 | }, 140 | "sensible-mda": { 141 | "binary-path": "./setuidBinary/sensible-mda" 142 | }, 143 | "lprm": { 144 | "binary-path": "./setuidBinary/lprm" 145 | }, 146 | "lpq": { 147 | "binary-path": "./setuidBinary/lpq" 148 | }, 149 | "lpr": { 150 | "binary-path": "./setuidBinary/lpr" 151 | }, 152 | "krb5_child": { 153 | "binary-path": "./setuidBinary/krb5_child" 154 | }, 155 | "ldap_child": { 156 | "binary-path": "./setuidBinary/ldap_child" 157 | }, 158 | "p11_child": { 159 | "binary-path": "./setuidBinary/p11_child" 160 | }, 161 | "proxy_child": { 162 | "binary-path": "./setuidBinary/proxy_child" 163 | }, 164 | "selinux_child": { 165 | "binary-path": "./setuidBinary/selinux_child" 166 | }, 167 | "s-nail-privsep": { 168 | "binary-path": "./setuidBinary/s-nail-privsep" 169 | }, 170 | "ksu": { 171 | "binary-path": "./setuidBinary/ksu" 172 | }, 173 | "oping": { 174 | "binary-path": "./setuidBinary/oping" 175 | }, 176 | "noping": { 177 | "binary-path": "./setuidBinary/noping" 178 | }, 179 | "unix_chkpwd": { 180 | "binary-path": "./setuidBinary/unix_chkpwd" 181 | }, 182 | "pinger": { 183 | "binary-path": "./setuidBinary/pinger" 184 | }, 185 | "xymonping": { 186 | "binary-path": "./setuidBinary/xymonping" 187 | }, 188 | "pam-tmpdir-helper": { 189 | "binary-path": "./setuidBinary/pam-tmpdir-helper" 190 | }, 191 | "super": { 192 | "binary-path": "./setuidBinary/super" 193 | }, 194 | "mm-meter": { 195 | "binary-path": "./setuidBinary/mm-meter" 196 | }, 197 | "mm-webreplay": { 198 | "binary-path": "./setuidBinary/mm-webreplay" 199 | }, 200 | "mm-webrecord": { 201 | "binary-path": "./setuidBinary/mm-webrecord" 202 | }, 203 | "mm-loss": { 204 | "binary-path": "./setuidBinary/mm-loss" 205 | }, 206 | "mm-onoff": { 207 | "binary-path": "./setuidBinary/mm-onoff" 208 | }, 209 | "mm-delay": { 210 | "binary-path": "./setuidBinary/mm-delay" 211 | }, 212 | "mm-link": { 213 | "binary-path": "./setuidBinary/mm-link" 214 | }, 215 | "scamper": { 216 | "binary-path": "./setuidBinary/scamper" 217 | }, 218 | "mgnokiidev": { 219 | "binary-path": "./setuidBinary/mgnokiidev" 220 | }, 221 | "libgtk3-nocsd.so.0": { 222 | "binary-path": "./setuidBinary/libgtk3-nocsd.so.0" 223 | }, 224 | "clockdiff": { 225 | "binary-path": "./setuidBinary/clockdiff" 226 | }, 227 | "chrome-sandbox": { 228 | "binary-path": "./setuidBinary/chrome-sandbox" 229 | }, 230 | "mount.ecryptfs_private": { 231 | "binary-path": "./setuidBinary/mount.ecryptfs_private" 232 | }, 233 | "tcptraceroute.mt": { 234 | "binary-path": "./setuidBinary/tcptraceroute.mt" 235 | }, 236 | "mailq": { 237 | "binary-path": "./setuidBinary/mailq" 238 | }, 239 | "nullmailer-queue": { 240 | "binary-path": "./setuidBinary/nullmailer-queue" 241 | }, 242 | "rnews": { 243 | "binary-path": "./setuidBinary/rnews" 244 | }, 245 | "lbmount": { 246 | "binary-path": "./setuidBinary/lbmount" 247 | }, 248 | "sbox": { 249 | "binary-path": "./setuidBinary/sbox" 250 | }, 251 | "suexec-custom": { 252 | "binary-path": "./setuidBinary/suexec-custom" 253 | }, 254 | "usbauth-npriv": { 255 | "binary-path": "./setuidBinary/usbauth-npriv" 256 | }, 257 | "physlock": { 258 | "binary-path": "./setuidBinary/physlock" 259 | }, 260 | "traceroute6.iputils": { 261 | "binary-path": "./setuidBinary/traceroute6.iputils" 262 | }, 263 | "mtr-packet": { 264 | "binary-path": "./setuidBinary/mtr-packet" 265 | }, 266 | "pppoe": { 267 | "binary-path": "./setuidBinary/pppoe" 268 | }, 269 | "really": { 270 | "binary-path": "./setuidBinary/really" 271 | }, 272 | "uml_net": { 273 | "binary-path": "./setuidBinary/uml_net" 274 | }, 275 | "wrap..exec": { 276 | "binary-path": "./setuidBinary/wrap..exec" 277 | }, 278 | "wims": { 279 | "binary-path": "./setuidBinary/wims" 280 | }, 281 | "enlightenment_sys": { 282 | "binary-path": "./setuidBinary/enlightenment_sys" 283 | }, 284 | "freqset": { 285 | "binary-path": "./setuidBinary/freqset" 286 | }, 287 | "plymouth": { 288 | "binary-path": "./setuidBinary/plymouth" 289 | }, 290 | "usplash": { 291 | "binary-path": "./setuidBinary/usplash" 292 | }, 293 | "askpass-fifo": { 294 | "binary-path": "./setuidBinary/askpass-fifo" 295 | }, 296 | "splashy": { 297 | "binary-path": "./setuidBinary/splashy" 298 | }, 299 | "mandos-client": { 300 | "binary-path": "./setuidBinary/mandos-client" 301 | }, 302 | "kismet_capture": { 303 | "binary-path": "./setuidBinary/kismet_capture" 304 | }, 305 | "suexec-pristine": { 306 | "binary-path": "./setuidBinary/suexec-pristine" 307 | }, 308 | "ifmail": { 309 | "binary-path": "./setuidBinary/ifmail" 310 | }, 311 | "inndstart": { 312 | "binary-path": "./setuidBinary/inndstart" 313 | }, 314 | "runapp": { 315 | "binary-path": "./setuidBinary/runapp" 316 | }, 317 | "amgtar": { 318 | "binary-path": "./setuidBinary/amgtar" 319 | }, 320 | "amstar": { 321 | "binary-path": "./setuidBinary/amstar" 322 | }, 323 | "rundump": { 324 | "binary-path": "./setuidBinary/rundump" 325 | }, 326 | "runtar": { 327 | "binary-path": "./setuidBinary/runtar" 328 | }, 329 | "calcsize": { 330 | "binary-path": "./setuidBinary/calcsize" 331 | }, 332 | "killpgrp": { 333 | "binary-path": "./setuidBinary/killpgrp" 334 | }, 335 | "i810switch": { 336 | "binary-path": "./setuidBinary/i810switch" 337 | }, 338 | "blinklight-fixperm": { 339 | "binary-path": "./setuidBinary/blinklight-fixperm" 340 | }, 341 | "telnetlogin": { 342 | "binary-path": "./setuidBinary/telnetlogin" 343 | }, 344 | "resolvconf-admin": { 345 | "binary-path": "./setuidBinary/resolvconf-admin" 346 | }, 347 | "nvidia-modprobe": { 348 | "binary-path": "./setuidBinary/nvidia-modprobe" 349 | }, 350 | "cryptmount": { 351 | "binary-path": "./setuidBinary/cryptmount" 352 | }, 353 | "ndisc6": { 354 | "binary-path": "./setuidBinary/ndisc6" 355 | }, 356 | "rltraceroute6": { 357 | "binary-path": "./setuidBinary/rltraceroute6" 358 | }, 359 | "rdisc6": { 360 | "binary-path": "./setuidBinary/rdisc6" 361 | }, 362 | "oarresume": { 363 | "binary-path": "./setuidBinary/oarresume" 364 | }, 365 | "oarsh": { 366 | "binary-path": "./setuidBinary/oarsh" 367 | }, 368 | "oarnodes": { 369 | "binary-path": "./setuidBinary/oarnodes" 370 | }, 371 | "oardel": { 372 | "binary-path": "./setuidBinary/oardel" 373 | }, 374 | "oarstat": { 375 | "binary-path": "./setuidBinary/oarstat" 376 | }, 377 | "oarsub": { 378 | "binary-path": "./setuidBinary/oarsub" 379 | }, 380 | "oarhold": { 381 | "binary-path": "./setuidBinary/oarhold" 382 | }, 383 | "oardodo": { 384 | "binary-path": "./setuidBinary/oardodo" 385 | }, 386 | "oarnodesetting": { 387 | "binary-path": "./setuidBinary/oarnodesetting" 388 | }, 389 | "staprun": { 390 | "binary-path": "./setuidBinary/staprun" 391 | }, 392 | "italc_auth_helper": { 393 | "binary-path": "./setuidBinary/italc_auth_helper" 394 | }, 395 | "udevil": { 396 | "binary-path": "./setuidBinary/udevil" 397 | }, 398 | "cockpit-session": { 399 | "binary-path": "./setuidBinary/cockpit-session" 400 | }, 401 | "firejail": { 402 | "binary-path": "./setuidBinary/firejail" 403 | }, 404 | "linux-user-chroot": { 405 | "binary-path": "./setuidBinary/linux-user-chroot" 406 | }, 407 | "innbind": { 408 | "binary-path": "./setuidBinary/innbind" 409 | }, 410 | "ambind": { 411 | "binary-path": "./setuidBinary/ambind" 412 | }, 413 | "idhash": { 414 | "binary-path": "./setuidBinary/idhash" 415 | }, 416 | "senddigest": { 417 | "binary-path": "./setuidBinary/senddigest" 418 | }, 419 | "multigram": { 420 | "binary-path": "./setuidBinary/multigram" 421 | }, 422 | "choplist": { 423 | "binary-path": "./setuidBinary/choplist" 424 | }, 425 | "flist": { 426 | "binary-path": "./setuidBinary/flist" 427 | }, 428 | "index.cgi": { 429 | "binary-path": "./setuidBinary/index.cgi" 430 | }, 431 | "add": { 432 | "binary-path": "./setuidBinary/add" 433 | }, 434 | "hashquery": { 435 | "binary-path": "./setuidBinary/hashquery" 436 | }, 437 | "gpgwww": { 438 | "binary-path": "./setuidBinary/gpgwww" 439 | }, 440 | "lookup": { 441 | "binary-path": "./setuidBinary/lookup" 442 | }, 443 | "faxq-helper": { 444 | "binary-path": "./setuidBinary/faxq-helper" 445 | }, 446 | "switchsh": { 447 | "binary-path": "./setuidBinary/switchsh" 448 | }, 449 | "masqmail": { 450 | "binary-path": "./setuidBinary/masqmail" 451 | }, 452 | "9bind": { 453 | "binary-path": "./setuidBinary/9bind" 454 | }, 455 | "9mount": { 456 | "binary-path": "./setuidBinary/9mount" 457 | }, 458 | "9umount": { 459 | "binary-path": "./setuidBinary/9umount" 460 | }, 461 | "userv": { 462 | "binary-path": "./setuidBinary/userv" 463 | }, 464 | "zapping_setup_fb": { 465 | "binary-path": "./setuidBinary/zapping_setup_fb" 466 | }, 467 | "mesg": { 468 | "binary-path": "./setuidBinary/mesg" 469 | }, 470 | "orville-write": { 471 | "binary-path": "./setuidBinary/orville-write" 472 | }, 473 | "login_duo": { 474 | "binary-path": "./setuidBinary/login_duo" 475 | }, 476 | "lldpcli": { 477 | "binary-path": "./setuidBinary/lldpcli" 478 | }, 479 | "btcli": { 480 | "binary-path": "./setuidBinary/btcli" 481 | }, 482 | "apt-update": { 483 | "binary-path": "./setuidBinary/apt-update" 484 | }, 485 | "ccreds_chkpwd": { 486 | "binary-path": "./setuidBinary/ccreds_chkpwd" 487 | }, 488 | "uncompress.so": { 489 | "binary-path": "./setuidBinary/uncompress.so" 490 | }, 491 | "rush": { 492 | "binary-path": "./setuidBinary/rush" 493 | }, 494 | "jazip": { 495 | "binary-path": "./setuidBinary/jazip" 496 | }, 497 | "vlock-main": { 498 | "binary-path": "./setuidBinary/vlock-main" 499 | }, 500 | "weston-launch": { 501 | "binary-path": "./setuidBinary/weston-launch" 502 | }, 503 | "libsdate.so.0.0.1": { 504 | "binary-path": "./setuidBinary/libsdate.so.0.0.1" 505 | }, 506 | "VirtualBox": { 507 | "binary-path": "./setuidBinary/VirtualBox" 508 | }, 509 | "VBoxSDL": { 510 | "binary-path": "./setuidBinary/VBoxSDL" 511 | }, 512 | "VBoxNetAdpCtl": { 513 | "binary-path": "./setuidBinary/VBoxNetAdpCtl" 514 | }, 515 | "VBoxNetNAT": { 516 | "binary-path": "./setuidBinary/VBoxNetNAT" 517 | }, 518 | "VBoxHeadless": { 519 | "binary-path": "./setuidBinary/VBoxHeadless" 520 | }, 521 | "VBoxNetDHCP": { 522 | "binary-path": "./setuidBinary/VBoxNetDHCP" 523 | }, 524 | "vmware-user-suid-wrapper": { 525 | "binary-path": "./setuidBinary/vmware-user-suid-wrapper" 526 | }, 527 | "netkit-rsh": { 528 | "binary-path": "./setuidBinary/netkit-rsh" 529 | }, 530 | "netkit-rcp": { 531 | "binary-path": "./setuidBinary/netkit-rcp" 532 | }, 533 | "netkit-rlogin": { 534 | "binary-path": "./setuidBinary/netkit-rlogin" 535 | }, 536 | "calife": { 537 | "binary-path": "./setuidBinary/calife" 538 | }, 539 | "oar_resources_init": { 540 | "binary-path": "./setuidBinary/oar_resources_init" 541 | }, 542 | "oarmonitor": { 543 | "binary-path": "./setuidBinary/oarmonitor" 544 | }, 545 | "oaraccounting": { 546 | "binary-path": "./setuidBinary/oaraccounting" 547 | }, 548 | "oar_phoenix": { 549 | "binary-path": "./setuidBinary/oar_phoenix" 550 | }, 551 | "oarremoveresource": { 552 | "binary-path": "./setuidBinary/oarremoveresource" 553 | }, 554 | "Almighty": { 555 | "binary-path": "./setuidBinary/Almighty" 556 | }, 557 | "oarnotify": { 558 | "binary-path": "./setuidBinary/oarnotify" 559 | }, 560 | "oarproperty": { 561 | "binary-path": "./setuidBinary/oarproperty" 562 | }, 563 | "su": { 564 | "binary-path": "./setuidBinary/su" 565 | }, 566 | "chsh": { 567 | "binary-path": "./setuidBinary/chsh" 568 | }, 569 | "chfn": { 570 | "binary-path": "./setuidBinary/chfn" 571 | }, 572 | "ssh-keysign": { 573 | "binary-path": "./setuidBinary/ssh-keysign" 574 | }, 575 | "newgrp": { 576 | "binary-path": "./setuidBinary/newgrp" 577 | }, 578 | "tcpdump": { 579 | "binary-path": "./setuidBinary/tcpdump" 580 | }, 581 | "nginx": { 582 | "binary-path": "./setuidBinary/nginx" 583 | }, 584 | "httpd": { 585 | "binary-path": "./setuidBinary/httpd" 586 | }, 587 | "lighttpd": { 588 | "binary-path": "./setuidBinary/lighttpd" 589 | }, 590 | "mbedtls": { 591 | "binary-path": "./setuidBinary/ssl_server2" 592 | }, 593 | "mariadbd": { 594 | "binary-path": "./setuidBinary/mariadbd" 595 | }, 596 | "mount": { 597 | "binary-path": "./setuidBinary/mount" 598 | }, 599 | "umount": { 600 | "binary-path": "./setuidBinary/umount" 601 | }, 602 | "passwd": { 603 | "binary-path": "./setuidBinary/passwd" 604 | } 605 | } -------------------------------------------------------------------------------- /sample-output/Output_Stat.csv: -------------------------------------------------------------------------------- 1 | ,app_name,syscalls_responsible_for_SYS_ADMIN,SYS_ADMIN removed?,total_capabilities,total_cap_count,number_of_syscalls_from_Confine,number_of_syscalls_from_Sysfilter,number_of_intersected_syscalls 2 | 0,nginx,"['ioctl', 'madvise', 'clone', 'prctl']",Yes,"{'CAP_SETGID', 'CAP_IPC_LOCK', 'CAP_SYS_RESOURCE', 'CAP_LEASE', 'CAP_BLOCK_SUSPEND', 'CAP_SYS_PTRACE', 'CAP_NET_RAW', 'CAP_SYS_NICE', 'CAP_KILL', 'CAP_CHOWN', 'CAP_SYS_TTY_CONFIG', 'CAP_MAC_OVERRIDE', 'CAP_LINUX_IMMUTABLE', 'CAP_SETUID', 'CAP_SETPCAP', 'CAP_NET_BIND_SERVICE', 'CAP_DAC_READ_SEARCH', 'CAP_FSETID', 'CAP_NET_ADMIN', 'CAP_AUDIT_CONTROL', 'CAP_SETFCAP', 'CAP_FOWNER', 'CAP_DAC_OVERRIDE'}",23,110,123,98 3 | 1,passwd,"['ioctl', 'madvise', 'clone', 'prctl']",Yes,"{'CAP_SETGID', 'CAP_IPC_LOCK', 'CAP_SYS_RESOURCE', 'CAP_LEASE', 'CAP_SYS_PTRACE', 'CAP_NET_RAW', 'CAP_SYS_NICE', 'CAP_KILL', 'CAP_CHOWN', 'CAP_SYS_TTY_CONFIG', 'CAP_MAC_OVERRIDE', 'CAP_SETUID', 'CAP_SETPCAP', 'CAP_NET_BIND_SERVICE', 'CAP_SYS_CHROOT', 'CAP_DAC_READ_SEARCH', 'CAP_FSETID', 'CAP_NET_ADMIN', 'CAP_DAC_OVERRIDE', 'CAP_AUDIT_CONTROL', 'CAP_SETFCAP', 'CAP_FOWNER', 'CAP_LINUX_IMMUTABLE'}",23,117,109,87 4 | 2,ping,"['ioctl', 'madvise', 'prctl']",Yes,"{'CAP_SETUID', 'CAP_SETPCAP', 'CAP_DAC_READ_SEARCH', 'CAP_NET_RAW', 'CAP_IPC_LOCK', 'CAP_SYS_RESOURCE', 'CAP_LINUX_IMMUTABLE', 'CAP_KILL', 'CAP_NET_ADMIN', 'CAP_SYS_TTY_CONFIG', 'CAP_LEASE', 'CAP_AUDIT_CONTROL', 'CAP_FOWNER', 'CAP_MAC_OVERRIDE', 'CAP_NET_BIND_SERVICE', 'CAP_DAC_OVERRIDE'}",16,67,60,50 5 | 3,dmcrypt-get-device,"['ioctl', 'madvise', 'clone', 'prctl']",No,"{'CAP_SETGID', 'CAP_IPC_LOCK', 'CAP_SYS_RESOURCE', 'CAP_LEASE', 'CAP_SYS_PTRACE', 'CAP_MKNOD', 'CAP_NET_RAW', 'CAP_SYS_NICE', 'CAP_KILL', 'CAP_CHOWN', 'CAP_SYS_TTY_CONFIG', 'CAP_MAC_OVERRIDE', 'CAP_LINUX_IMMUTABLE', 'CAP_SETUID', 'CAP_SETPCAP', 'CAP_NET_BIND_SERVICE', 'CAP_SYS_ADMIN', 'CAP_DAC_READ_SEARCH', 'CAP_NET_ADMIN', 'CAP_AUDIT_CONTROL', 'CAP_SETFCAP', 'CAP_FOWNER', 'CAP_DAC_OVERRIDE'}",23,115,104,83 6 | 4,lpr,['madvise'],Yes,"{'CAP_SETGID', 'CAP_SETUID', 'CAP_DAC_READ_SEARCH', 'CAP_FSETID', 'CAP_NET_RAW', 'CAP_IPC_LOCK', 'CAP_SYS_RESOURCE', 'CAP_SYS_NICE', 'CAP_KILL', 'CAP_NET_ADMIN', 'CAP_LEASE', 'CAP_DAC_OVERRIDE', 'CAP_AUDIT_CONTROL', 'CAP_FOWNER', 'CAP_MAC_OVERRIDE', 'CAP_NET_BIND_SERVICE', 'CAP_SYS_PTRACE'}",17,63,99,57 7 | 5,exim4,"['ioctl', 'madvise', 'shmctl', 'clone', 'prctl']",No,"{'CAP_SETGID', 'CAP_IPC_LOCK', 'CAP_SYS_RESOURCE', 'CAP_LEASE', 'CAP_SYS_PTRACE', 'CAP_NET_RAW', 'CAP_SYS_NICE', 'CAP_KILL', 'CAP_CHOWN', 'CAP_SYS_TTY_CONFIG', 'CAP_MAC_OVERRIDE', 'CAP_LINUX_IMMUTABLE', 'CAP_SETUID', 'CAP_SETPCAP', 'CAP_NET_BIND_SERVICE', 'CAP_SYS_ADMIN', 'CAP_DAC_READ_SEARCH', 'CAP_FSETID', 'CAP_NET_ADMIN', 'CAP_AUDIT_CONTROL', 'CAP_SETFCAP', 'CAP_FOWNER', 'CAP_IPC_OWNER', 'CAP_DAC_OVERRIDE'}",24,128,0,128 8 | 6,unix_chkpwd,"['ioctl', 'madvise', 'clone', 'prctl']",Yes,"{'CAP_SETGID', 'CAP_IPC_LOCK', 'CAP_SYS_RESOURCE', 'CAP_LEASE', 'CAP_SYS_PTRACE', 'CAP_NET_RAW', 'CAP_SYS_NICE', 'CAP_KILL', 'CAP_SYS_TTY_CONFIG', 'CAP_MAC_OVERRIDE', 'CAP_SETUID', 'CAP_SETPCAP', 'CAP_NET_BIND_SERVICE', 'CAP_DAC_READ_SEARCH', 'CAP_NET_ADMIN', 'CAP_DAC_OVERRIDE', 'CAP_AUDIT_CONTROL', 'CAP_SETFCAP', 'CAP_FOWNER', 'CAP_LINUX_IMMUTABLE'}",20,108,96,73 9 | 7,tcpdump,"['ioctl', 'madvise', 'clone', 'prctl']",No,"{'CAP_SETGID', 'CAP_IPC_LOCK', 'CAP_SYS_RESOURCE', 'CAP_LEASE', 'CAP_SYS_PTRACE', 'CAP_NET_RAW', 'CAP_SYS_NICE', 'CAP_KILL', 'CAP_SYS_TTY_CONFIG', 'CAP_MAC_OVERRIDE', 'CAP_LINUX_IMMUTABLE', 'CAP_SETUID', 'CAP_SETPCAP', 'CAP_NET_BIND_SERVICE', 'CAP_SYS_ADMIN', 'CAP_DAC_READ_SEARCH', 'CAP_FSETID', 'CAP_NET_ADMIN', 'CAP_AUDIT_CONTROL', 'CAP_SETFCAP', 'CAP_FOWNER', 'CAP_DAC_OVERRIDE'}",22,94,120,79 10 | 8,httpd,"['ioctl', 'madvise', 'shmctl', 'clone', 'prctl']",No,"{'CAP_SETGID', 'CAP_IPC_LOCK', 'CAP_SYS_RESOURCE', 'CAP_LEASE', 'CAP_BLOCK_SUSPEND', 'CAP_SYS_PTRACE', 'CAP_NET_RAW', 'CAP_SYS_NICE', 'CAP_KILL', 'CAP_CHOWN', 'CAP_SYS_TTY_CONFIG', 'CAP_MAC_OVERRIDE', 'CAP_LINUX_IMMUTABLE', 'CAP_SETUID', 'CAP_SETPCAP', 'CAP_NET_BIND_SERVICE', 'CAP_SYS_ADMIN', 'CAP_DAC_READ_SEARCH', 'CAP_FSETID', 'CAP_NET_ADMIN', 'CAP_AUDIT_CONTROL', 'CAP_SETFCAP', 'CAP_FOWNER', 'CAP_IPC_OWNER', 'CAP_DAC_OVERRIDE'}",25,124,133,106 11 | 9,lighttpd,"['ioctl', 'madvise', 'clone', 'prctl']",Yes,"{'CAP_SETGID', 'CAP_IPC_LOCK', 'CAP_SYS_RESOURCE', 'CAP_LEASE', 'CAP_BLOCK_SUSPEND', 'CAP_SYS_PTRACE', 'CAP_NET_RAW', 'CAP_SYS_NICE', 'CAP_KILL', 'CAP_SYS_TTY_CONFIG', 'CAP_MAC_OVERRIDE', 'CAP_LINUX_IMMUTABLE', 'CAP_SETUID', 'CAP_SETPCAP', 'CAP_NET_BIND_SERVICE', 'CAP_SYS_CHROOT', 'CAP_DAC_READ_SEARCH', 'CAP_FSETID', 'CAP_NET_ADMIN', 'CAP_AUDIT_CONTROL', 'CAP_SETFCAP', 'CAP_FOWNER', 'CAP_DAC_OVERRIDE'}",23,121,130,96 12 | -------------------------------------------------------------------------------- /src/decap.py: -------------------------------------------------------------------------------- 1 | import os, sys, subprocess, signal 2 | import optparse 3 | import logging 4 | sys.path.insert(0, './python-utils/') 5 | 6 | import capabilityAnalysis 7 | import util 8 | import pandas as pd 9 | import json 10 | import pexpect 11 | 12 | def isValidOpts(opts): 13 | if ( not options.input) or (not options.sysfilterpath): 14 | parser.error("Options --input and --sysfilterpath should be provided") 15 | return False 16 | 17 | return True 18 | 19 | if __name__ == '__main__': 20 | usage = "Usage: %prog --input --sysfilterpath " 21 | 22 | parser = optparse.OptionParser(usage=usage, version="1") 23 | 24 | parser.add_option("-i", "--input", dest="input", default=None, nargs=1, 25 | help="Input file containing list of setuid binaries for capability analysis") 26 | 27 | parser.add_option("-s", "--sysfilterpath", dest="sysfilterpath", 28 | default=None, nargs=1, 29 | help="Path of the sysfilter_extract executable") 30 | 31 | parser.add_option("-d", "--debug", dest="debug", action="store_true", default=False, 32 | help="Debug enabled/disabled") 33 | 34 | (options, args) = parser.parse_args() 35 | if isValidOpts(options): 36 | rootLogger = util.setLogPath("../log/Capability.log", options) 37 | 38 | # 1. parse input JSON 39 | try: 40 | inputJsonFile = open(options.input, 'r') 41 | inputJsonStr = inputJsonFile.read() 42 | inputJson = json.loads(inputJsonStr) 43 | #print(inputJson) 44 | except Exception as e: 45 | rootLogger.error("Trying to load input json from: %s, but doesn't exist: %s", options.input, str(e)) 46 | rootLogger.error("Exiting...") 47 | sys.exit(-1) 48 | 49 | CapStat = pd.DataFrame(columns 50 | =['app_name','syscalls_responsible_for_SYS_ADMIN', 'SYS_ADMIN removed?', 'total_capabilities', 'total_cap_count', 51 | 'number_of_syscalls_from_Confine','number_of_syscalls_from_Sysfilter','number_of_intersected_syscalls']) #final dataframe to get the statistics of capability 52 | 53 | for binaryName, Values in inputJson.items(): 54 | binaryPath = Values.get("binary-path", None) 55 | capAnalysis = capabilityAnalysis.CapabilityAnalysis(binaryName, binaryPath, rootLogger, options.sysfilterpath) 56 | syscallsResponsibleForSysAdmin, isRemovable, capToAdd, numOfConfineSyscalls, numOfSysfilterSyscalls, numofIntersectedSyscalls, success = capAnalysis.runCapabilityAnalysis() 57 | if (not success): 58 | rootLogger.info("Couldn't successfully analyze binary %s!", binaryName) 59 | else: 60 | rootLogger.info("Done capability analysis for %s!", binaryName) 61 | added_cap_count = len(capToAdd) 62 | CapStat = CapStat.append({'app_name' : binaryName, 'syscalls_responsible_for_SYS_ADMIN' : syscallsResponsibleForSysAdmin, 'SYS_ADMIN removed?' : isRemovable, 'total_capabilities' : capToAdd, 'total_cap_count' : added_cap_count, 'number_of_syscalls_from_Confine' : numOfConfineSyscalls, 'number_of_syscalls_from_Sysfilter' : numOfSysfilterSyscalls , 'number_of_intersected_syscalls' : numofIntersectedSyscalls}, ignore_index = True) 63 | rootLogger.info("----------------------------------------------------------------\n") 64 | 65 | 66 | CapStat.to_csv('Output_Stat.csv') 67 | 68 | -------------------------------------------------------------------------------- /src/python-utils/binaryAnalysis.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import util 4 | import re 5 | 6 | class BinaryAnalysis: 7 | """ 8 | This class can be used to extract direct system calls and possibly other information from a binary 9 | """ 10 | def __init__(self, binaryPath, logger): 11 | self.binaryPath = binaryPath 12 | self.logger = logger 13 | 14 | def extractDirectSyscalls(self, SysAdminSyscallNumbers): 15 | objDumpSuccess = True 16 | #Dump binary to tmp file 17 | dumpFileName = self.binaryPath + ".dump" 18 | if ( "/" in dumpFileName ): 19 | dumpFileName = dumpFileName[dumpFileName.rindex("/")+1:] 20 | dumpFilePath = "/tmp/" + dumpFileName 21 | cmd = "objdump -d {} > " + dumpFilePath 22 | if ( os.path.isfile(self.binaryPath) ): 23 | cmd = cmd.format(self.binaryPath) 24 | returncode, out, err = util.runCommand(cmd) 25 | if (returncode != 0 ): 26 | self.logger.error("Couldn't create dump file for: %s with err: %s", self.binaryPath, dumpFilePath) 27 | return (set(), -1, -1, dict(), False) 28 | #Find direct syscalls and arguments 29 | #Specify how many were found successfully and how many were not 30 | syscallSet, successCount, failedCount, syscallToArgumentValMap = self.parseObjdump(dumpFilePath, SysAdminSyscallNumbers) 31 | #Return syscall list along with number of not found syscalls 32 | os.unlink(dumpFilePath) 33 | return (syscallSet, successCount, failedCount, 34 | syscallToArgumentValMap, objDumpSuccess) 35 | else: 36 | self.logger.error("Binary path doesn't exist: %s", self.binaryPath) 37 | return (set(), -1, -1, dict(), False) 38 | 39 | 40 | def binaryDumpPath(self): 41 | #Dump binary to tmp file 42 | dumpFileName = self.binaryPath + ".dump" 43 | if ( "/" in dumpFileName ): 44 | dumpFileName = dumpFileName[dumpFileName.rindex("/")+1:] 45 | dumpFilePath = "/tmp/" + dumpFileName 46 | cmd = "objdump -d {} > " + dumpFilePath 47 | if ( os.path.isfile(self.binaryPath) ): 48 | cmd = cmd.format(self.binaryPath) 49 | returncode, out, err = util.runCommand(cmd) 50 | if ( returncode != 0 ): 51 | #self.logger.error("Couldn't create dump file for: %s with err: %s", self.binaryPath, dumpFilePath) 52 | return (None, -1, -1) 53 | return dumpFilePath 54 | else: 55 | self.logger.error("binary path doesn't exist: %s", self.binaryPath) 56 | return (None, -1, -1) 57 | 58 | 59 | def extractArgumentVal(self, syscallNum, syscallMap): 60 | #Dump binary to tmp file 61 | dumpFileName = self.binaryPath + ".dump" 62 | if ( "/" in dumpFileName ): 63 | dumpFileName = dumpFileName[dumpFileName.rindex("/")+1:] 64 | dumpFilePath = "/tmp/" + dumpFileName 65 | cmd = "objdump -d {} > " + dumpFilePath 66 | if ( os.path.isfile(self.binaryPath) ): 67 | cmd = cmd.format(self.binaryPath) 68 | returncode, out, err = util.runCommand(cmd) 69 | if ( returncode != 0 ): 70 | #self.logger.error("Couldn't create dump file for: %s with err: %s", self.binaryPath, dumpFilePath) 71 | return (None, -1, -1) 72 | #Find argument values of a syscall 73 | argumentVal = self.parseObjdumpForArgument(dumpFilePath, syscallNum, syscallMap) 74 | os.unlink(dumpFilePath) 75 | return (argumentVal) 76 | else: 77 | self.logger.error("binary path doesn't exist: %s", self.binaryPath) 78 | return (None) 79 | 80 | def parentFunctionSearch(self, fnName, wrapper, syscallNum, FnNameBodyMap): 81 | argumentVal = set() 82 | for parentName in FnNameBodyMap: 83 | pBody = FnNameBodyMap[parentName] 84 | for line_no in range(len(pBody)): 85 | lineVal = pBody[line_no] 86 | if (fnName in lineVal and "e8" in lineVal) or (fnName in lineVal and "e9" in lineVal): 87 | tmpY = line_no - 1 88 | sys_flag_val = self.extractFlag(pBody[tmpY], wrapper, syscallNum) 89 | while ( sys_flag_val == -1 and (line_no - tmpY) < 15 and tmpY > 0 ): 90 | tmpY = tmpY - 1 91 | sys_flag_val = self.extractFlag(pBody[tmpY], wrapper, syscallNum) 92 | argumentVal.add(sys_flag_val) 93 | return argumentVal; 94 | 95 | def parseObjdumpForArgument(self, outputFileName, syscallNum, syscallMap): 96 | FnNameBodyMap = {} 97 | FnSysCallMap = {} 98 | f = open(outputFileName) 99 | fnName = "" 100 | for line in f: 101 | if "<" in line and ">:" in line: 102 | namesplit = line.split() 103 | fnName = self.sanitizeFnName(namesplit[1]) 104 | FnNameBodyMap[fnName] = [] 105 | FnSysCallMap[fnName] = [] 106 | continue 107 | if fnName != "": 108 | FnNameBodyMap[fnName].append(line) 109 | f.close() 110 | 111 | argumentVal = set() 112 | for fnName in FnNameBodyMap: 113 | sys_num=-1 114 | body = FnNameBodyMap[fnName] 115 | for i in range(len(body)): 116 | line = body[i] 117 | if (syscallMap[syscallNum]+"@plt" in line and "e8" in 118 | line) or (syscallMap[syscallNum]+"@plt" in line and "e9" in line): 119 | argumentVal = argumentVal | self.iterateFuncBodyForArgumentVal(i, syscallNum, body, 120 | fnName, True, FnNameBodyMap, argumentVal); 121 | 122 | return argumentVal 123 | 124 | def iterateFuncBodyForArgumentVal(self, i, syscallNum, body, fnName, 125 | isLibcWrapper, FnNameBodyMap, argumentVal): 126 | tmpI = i-1 127 | flag_val = self.extractFlag(body[tmpI], isLibcWrapper, syscallNum) 128 | while ( flag_val == -1 and (i - tmpI) < 15 and tmpI > 0 ): 129 | tmpI = tmpI - 1 130 | flag_val = self.extractFlag(body[tmpI], isLibcWrapper, syscallNum) 131 | if (flag_val == -1 and tmpI < 15): 132 | argumentVal = argumentVal | self.parentFunctionSearch(fnName, isLibcWrapper, syscallNum, FnNameBodyMap) 133 | else: 134 | argumentVal.add(flag_val) 135 | return argumentVal 136 | 137 | def extractFlag(self, ins, isLibcWrapper, sys_num): 138 | sys_flag_val = -1 139 | first_arg_edi_rdi_reg = {157, 101, 179, 160} #when wrapper, if no wrapper then esi rsi 140 | second_arg_esi_rsi_reg = {16, 31, 71} #when wrapper, if no wrapper then edx rdx 141 | third_arg_edx_rdx_reg = {56, 28} #when wrapper, if no wrapper then ecx rcx 142 | forth_arg_ecx_rcx_reg = {265} #when wrapper, if no wrapper then r8 143 | split = ins.split() 144 | for i in range(len(split)): 145 | if split[i] == "mov": 146 | # Next token should be src,dest 147 | srcdst = split[i+1].split(",") 148 | src = srcdst[0] 149 | dst = srcdst[1] 150 | if isLibcWrapper: 151 | if (sys_num in second_arg_esi_rsi_reg) and (dst == "%esi" or dst == "%rsi"): 152 | sys_flag_val = src 153 | sys_flag_val = self.sanitizeFlag(sys_flag_val) 154 | elif (sys_num in first_arg_edi_rdi_reg) and (dst == "%edi" or dst == "%rdi"): 155 | sys_flag_val = src 156 | sys_flag_val = self.sanitizeFlag(sys_flag_val) 157 | elif (sys_num in third_arg_edx_rdx_reg) and (dst == "%edx" or dst == "%rdx"): 158 | sys_flag_val = src 159 | sys_flag_val = self.sanitizeFlag(sys_flag_val) 160 | elif (sys_num in forth_arg_ecx_rcx_reg) and (dst == "%ecx" 161 | or dst == "%rcx"): 162 | sys_flag_val = src 163 | sys_flag_val = self.sanitizeFlag(sys_flag_val) 164 | 165 | else: 166 | if (sys_num == 250) and (dst == "%esi" or dst == "%rsi"): 167 | sys_flag_val = src 168 | sys_flag_val = self.sanitizeFlag(sys_flag_val) 169 | elif (sys_num == 317) and (dst == "%esi" or dst == "%rsi"): 170 | sys_flag_val = src 171 | sys_flag_val = self.sanitizeFlag(sys_flag_val) 172 | elif (sys_num == 251) and (dst == "%ecx" or dst == "%rcx"): 173 | sys_flag_val = src 174 | sys_flag_val = self.sanitizeFlag(sys_flag_val) 175 | elif (sys_num in second_arg_esi_rsi_reg) and (dst == "%edx" or dst == "%rdx"): 176 | sys_flag_val = src 177 | sys_flag_val = self.sanitizeFlag(sys_flag_val) 178 | elif (sys_num in first_arg_edi_rdi_reg) and (dst == "%esi" or dst == "%rsi"): 179 | sys_flag_val = src 180 | sys_flag_val = self.sanitizeFlag(sys_flag_val) 181 | elif (sys_num in third_arg_edx_rdx_reg) and (dst == "%ecx" or dst == "%rcx"): 182 | sys_flag_val = src 183 | sys_flag_val = self.sanitizeFlag(sys_flag_val) 184 | elif (sys_num in forth_arg_ecx_rcx_reg) and (dst == "%r8"): 185 | sys_flag_val = src 186 | sys_flag_val = self.sanitizeFlag(sys_flag_val) 187 | 188 | elif split[i] == "xor": 189 | # Next token should be src,dest 190 | srcdst = split[i+1].split(",") 191 | src = srcdst[0] 192 | dst = srcdst[1] 193 | if isLibcWrapper: 194 | if (sys_num in second_arg_esi_rsi_reg) and (dst == "%esi" or dst == "%rsi"): 195 | sys_flag_val = self.xorFlagreturn(dst,src) 196 | elif (sys_num in first_arg_edi_rdi_reg) and (dst == "%edi" or dst == "%rdi"): 197 | sys_flag_val = self.xorFlagreturn(dst,src) 198 | elif (sys_num in third_arg_edx_rdx_reg) and (dst == "%edx" or dst == "%rdx"): 199 | sys_flag_val = self.xorFlagreturn(dst,src) 200 | elif (sys_num in forth_arg_ecx_rcx_reg) and (dst == "%ecx" 201 | or dst == "%rcx"): 202 | sys_flag_val = self.xorFlagreturn(dst,src) 203 | else: 204 | if (sys_num == 250) and (dst == "%esi" or dst == "%rsi"): 205 | sys_flag_val = self.xorFlagreturn(dst,src) 206 | elif (sys_num == 317) and (dst == "%esi" or dst == "%rsi"): 207 | sys_flag_val = self.xorFlagreturn(dst,src) 208 | elif (sys_num == 251) and (dst == "%ecx" or dst == "%rcx"): 209 | sys_flag_val = self.xorFlagreturn(dst,src) 210 | elif (sys_num in second_arg_esi_rsi_reg) and (dst == "%edx" or dst == "%rdx"): 211 | sys_flag_val = self.xorFlagreturn(dst,src) 212 | elif (sys_num in first_arg_edi_rdi_reg) and (dst == "%esi" or dst == "%rsi"): 213 | sys_flag_val = self.xorFlagreturn(dst,src) 214 | elif (sys_num in third_arg_edx_rdx_reg) and (dst == "%ecx" or dst == "%rcx"): 215 | sys_flag_val = self.xorFlagreturn(dst,src) 216 | elif (sys_num in forth_arg_ecx_rcx_reg) and (dst == "%r8"): 217 | sys_flag_val = self.xorFlagreturn(dst,src) 218 | 219 | return sys_flag_val 220 | 221 | def xorFlagreturn(self, dst,src): 222 | if dst == src: 223 | sys_flag_val = '$0x0' 224 | sys_flag_val = self.sanitizeFlag(sys_flag_val) 225 | else: 226 | sys_flag_val = '$0x-1' 227 | sys_flag_val = self.sanitizeFlag(sys_flag_val) 228 | return sys_flag_val 229 | 230 | 231 | def sanitizeFlag(self, sys_flag_val): 232 | if sys_flag_val[0] == "$": 233 | #print(sys_flag_val) 234 | sys_flag_val = sys_flag_val[3:] 235 | return sys_flag_val 236 | 237 | def sanitizeFnName(self, instr): 238 | outstr = "" 239 | for s in instr: 240 | if s == "<": 241 | continue 242 | if s == ">": 243 | continue 244 | if s == ":": 245 | continue 246 | outstr += s 247 | return outstr 248 | 249 | def decimalify(self, token): 250 | number = "" 251 | intnum = -1 252 | #print('$$ ',token) 253 | if token[0] == "$": 254 | number = token[1:] 255 | try: 256 | intnum = int(number, 16) 257 | except ValueError: 258 | #self.logger.debug("can't convert: %s", token) 259 | #print('$$ $$') 260 | pass 261 | return intnum 262 | 263 | def extractNum(self, ins, wrapper): 264 | num = -1 265 | #print(wrapper) 266 | split = ins.split() 267 | for i in range(len(split)): 268 | if split[i] == "mov": 269 | # Next token should be src,dest 270 | srcdst = split[i+1].split(",") 271 | src = srcdst[0] 272 | dst = srcdst[1] 273 | if wrapper and (dst == "%edi" or dst == "%rdi"): 274 | num = self.decimalify(src) 275 | elif (wrapper == False) and (dst == "%rax" or dst == "%eax" or dst == "%rcx" or dst == "%ecx"):# or dst == "%edi" or dst == "%rdi": 276 | #self.logger.debug("src: %s", src) 277 | num = self.decimalify(src) 278 | 279 | return num 280 | 281 | 282 | def parseObjdump(self, outputFileName, SysAdminSyscallNumbers): 283 | FnNameBodyMap = {} 284 | FnSysCallMap = {} 285 | failCount = 0 286 | successCount = 0 287 | f = open(outputFileName) 288 | fnName = "" 289 | for line in f: 290 | if "<" in line and ">:" in line: 291 | # Most likely new function start 292 | namesplit = line.split() 293 | fnName = self.sanitizeFnName(namesplit[1]) 294 | FnNameBodyMap[fnName] = [] 295 | FnSysCallMap[fnName] = [] 296 | continue 297 | if fnName != "": 298 | FnNameBodyMap[fnName].append(line) 299 | f.close() 300 | 301 | # For each function 302 | syscallSet = set() 303 | syscallToArgumentValMap = dict() 304 | for fnName in FnNameBodyMap: 305 | body = FnNameBodyMap[fnName] 306 | wrapper = False 307 | for i in range(len(body)): 308 | line = body[i] 309 | if ("syscall" in line and "0f 05" in line):# ("syscall" in line and "e8" in line) or ("syscall" in line and "e9" in line): 310 | # Check the past three lines for the value of the rax register 311 | tmpI = i-1 312 | num = self.extractNum(body[tmpI], wrapper) 313 | while ( num == -1 and (i - tmpI) < 15 and tmpI > 0 ): 314 | tmpI = tmpI - 1 315 | num = self.extractNum(body[tmpI], wrapper) 316 | if num == -1: 317 | failCount += 1 318 | #self.logger.error("Can't reason about syscall in function: %s in line: %s", fnName, line) 319 | else: 320 | successCount += 1 321 | # Extract argument values for the system calls responsible for CAP_SYS_ADMIN 322 | if (num in SysAdminSyscallNumbers): 323 | argumentVal = set() 324 | argumentVal = self.iterateFuncBodyForArgumentVal(i, num, body, fnName, False, FnNameBodyMap, argumentVal) 325 | if (num in syscallToArgumentValMap): 326 | syscallToArgumentValMap[num] = syscallToArgumentValMap[num] | argumentVal 327 | else: 328 | syscallToArgumentValMap[num] = argumentVal 329 | 330 | syscallSet.add(num) 331 | elif ("syscall" in line and "e8" in line) or ("syscall" in line and "e9" in line): 332 | # Check the past three lines for the value of the rax register 333 | wrapper = True 334 | tmpI = i-1 335 | num = self.extractNum(body[tmpI], wrapper) 336 | while ( num == -1 and (i - tmpI) < 15 and tmpI > 0 ): 337 | tmpI = tmpI - 1 338 | num = self.extractNum(body[tmpI], wrapper) 339 | if num == -1: 340 | failCount += 1 341 | #self.logger.error("Can't reason about syscall in function: %s in line: %s", fnName, line) 342 | else: 343 | successCount += 1 344 | # Extract argument values for the system calls responsible for CAP_SYS_ADMIN 345 | if (num in SysAdminSyscallNumbers): 346 | argumentVal = set() 347 | argumentVal = self.iterateFuncBodyForArgumentVal(i, num, body, fnName, False, FnNameBodyMap, argumentVal) 348 | if (num in syscallToArgumentValMap): 349 | syscallToArgumentValMap[num] = syscallToArgumentValMap[num] | argumentVal 350 | else: 351 | syscallToArgumentValMap[num] = argumentVal 352 | syscallSet.add(num) 353 | 354 | return (syscallSet, successCount, failCount, syscallToArgumentValMap) 355 | -------------------------------------------------------------------------------- /src/python-utils/capabilityAnalysis.py: -------------------------------------------------------------------------------- 1 | import os, sys, subprocess, signal 2 | import logging 3 | 4 | import graph 5 | import syscall 6 | import binaryAnalysis 7 | import syscallToCapabilityMapping 8 | import sysfilter 9 | import util 10 | import pexpect 11 | 12 | class CapabilityAnalysis: 13 | 14 | def __init__(self, binaryName, binaryPath, logger, sysfilterPath): 15 | self.binaryName = binaryName 16 | self.binaryPath = binaryPath 17 | self.sysfilterPath = sysfilterPath 18 | self.logger = logger 19 | 20 | def addCapsToBinary(self, capToAdd): 21 | self.logger.info("Deprivileging setuid binary and enforcing only the required capabilities to the binary") 22 | 23 | # removing setuid privilege from binary 24 | privCmd = "sudo chmod u-s {}" 25 | removePrivilegeCmd = privCmd.format(self.binaryPath) 26 | returncode, out, err = util.runCommand(removePrivilegeCmd) 27 | if ( returncode != 0 ): 28 | logging.error("Error removing setuid bit of binary: %s", err) 29 | return 30 | 31 | # Add the required capabilities to the binary 32 | if (len(capToAdd) == 0): 33 | return 34 | capToAdd = ','.join(capToAdd) 35 | cmd = "sudo setcap '{}=+ep' {}" 36 | finalCmd = cmd.format(capToAdd, self.binaryPath) 37 | self.logger.debug("Modifyng capabilities with the cmd: %s", finalCmd) 38 | returncode, out, err = util.runCommand(finalCmd) 39 | if ( returncode != 0 ): 40 | logging.error("Error modifying capability set of binary: %s", err) 41 | return 42 | 43 | 44 | def ioctlFlags(self): 45 | ioctl_flags = {'40045201','40085203', '5204', '5206', '5207', '5457', 46 | '541d', '540e', '540c', '1269', '125f', '401870c8', '401070c9', 47 | '401070ca', '401870cb', '401070cd', '1261', '125d', '40081271', 48 | '1262', '1264', 'c0185879', '50009403', 'd0009411', 'c0709411', 49 | 'd0009412', '5000940f', '50009402', '40309410', '5000940a', 50 | '5000940b', '5000943a', '40089413', 'c400941b', '941c', 51 | 'c400941d', 'c4089434', 'ca289435', 'c0389424', 'c038943b', 52 | 'c4009420', '40049421', '84009422', 'c0109428', '40189429', 53 | '4010942a', '8030942b', '4040942c', '8040942d', '40309439', 54 | '6611', '8004587d', '6880', 'c0045877', 'c0045878', 'c0185879', 55 | '40106e80', '40086e81', '40786e88', '40086e8b', '40106e8c', 56 | '40186e8d', 'c038586b', '4058587a', '4048587b', 'c0205866', 57 | 'c0205865', 'c0205867', 'c0105872', '80105873', '8004587d', 58 | '40085874', '40085875', '8080583a', 'c0484e41'} 59 | return ioctl_flags 60 | 61 | def quotactlFlags(self): 62 | quotactl_flags = {'800002', '800003', '800006', '800007', '800008', 63 | '5801', '5802', '5803', '5804'} 64 | return quotactl_flags 65 | 66 | def libcSensitiveSysToParentMap(self): 67 | sensitiveMap = {"setrlimit" : "vlimit", "shmctl": "__monstartup"} 68 | return sensitiveMap; 69 | 70 | def hexToBinFlag(self, argVal): 71 | scale = 16 ## equals to hexadecimal 72 | num_of_bits = 32 73 | binFlag = bin(int(argVal, scale))[2:].zfill(num_of_bits) 74 | return binFlag 75 | 76 | 77 | def findCapSysAdminRequirement(self, syscallNum, argVal): 78 | sysAdminRequired = True 79 | binFlag = bin(int('0', 16))[2:].zfill(32) 80 | for items in argVal: 81 | try: 82 | if (items == "removeTrue"): 83 | return True; 84 | if (items == "removeFalse"): 85 | return False; 86 | if (items == -1): 87 | return False 88 | temp = self.hexToBinFlag(str(items)) 89 | #doing bitwise or on string on temp and binFlag 90 | binFlag = ''.join(map(max, binFlag, temp)) 91 | except: 92 | #print('$$not a hex value but a register') 93 | sysAdminRequired = False 94 | 95 | bitList = list(binFlag) 96 | if syscallNum == 56: #clone 97 | sys_admin_flag = {8000000, 40000000, 20000, 20000000, 4000000} 98 | adminFlag = bin(int('0', 16))[2:].zfill(32) 99 | for items in sys_admin_flag: 100 | temp = self.hexToBinFlag(str(items)) 101 | adminFlag = ''.join(map(max, adminFlag, temp)) 102 | adminList = list(adminFlag) 103 | sysAdminRequired = True 104 | for index, val in enumerate(adminList): 105 | if val == 1: 106 | if bitList[index] == 1: 107 | sysAdminRequired = False 108 | elif syscallNum == 157: #prctl 109 | sys_admin_flag1 = '16' #hex value 110 | sys_admin_flag2 = '2' #hex value 111 | if sys_admin_flag1 not in argVal: 112 | sysAdminRequired = True 113 | else: 114 | sysAdminRequired = False 115 | elif syscallNum == 28: #madvise decimal value 116 | advice_flag = '64' #advice flags hex value 117 | if advice_flag in argVal: 118 | sysAdminRequired = False 119 | elif syscallNum == 31: #shmctl decimal value 120 | IPC_SET = '1' #flags hex value 121 | IPC_RMID = '0' 122 | if IPC_SET in argVal or IPC_RMID in argVal: 123 | sysAdminRequired = False 124 | elif syscallNum == 16: #ioctl decimal value 125 | ioctl_flags = self.ioctlFlags() #request flags hex value for sys_admin 126 | for it in argVal: 127 | if it in ioctl_flags: 128 | sysAdminRequired = False 129 | #break 130 | elif syscallNum == 250: #keyctl decimal value 131 | KEYCTL_CHOWN = '4' #flags hex value 132 | KEYCTL_SETPERM = '5' 133 | if KEYCTL_CHOWN in argVal or KEYCTL_SETPERM in argVal: 134 | sysAdminRequired = False 135 | elif syscallNum == 101: #ptrace decimal value 136 | PTRACE_SECCOMP_GET_FILTER = '420c' #flags hex value 137 | PTRACE_SETOPTIONS = '4200' 138 | if PTRACE_SECCOMP_GET_FILTER in argVal or PTRACE_SETOPTIONS in argVal: 139 | sysAdminRequired = False 140 | elif syscallNum == 179: #quotactl decimal value 141 | quotactl_flags = self.quotactlFlags() #cmd flags hex value 142 | for it in argVal: 143 | size = len(it) 144 | # Slice string to remove last 2 characters from it 145 | it = it[:size - 2] 146 | if it in quotactl_flags: 147 | sysAdminRequired = False 148 | elif syscallNum == 71: #msgctl decimal value 149 | IPC_SET = '1' #flags hex value 150 | IPC_RMID = '0' 151 | if IPC_SET in argVal or IPC_RMID in argVal: 152 | sysAdminRequired = False 153 | elif syscallNum == 317: #seccomp decimal value 154 | SECCOMP_SET_MODE_FILTER = '1' #flags hex value 155 | if SECCOMP_SET_MODE_FILTER in argVal: 156 | sysAdminRequired = False 157 | elif syscallNum == 251: #ioprio_set decimal value 158 | IOPRIO_PRIO_CLASS = '1' #flags hex value 159 | if IOPRIO_PRIO_CLASS in argVal: 160 | sysAdminRequired = False 161 | elif syscallNum == 160: #setrlimit decimal value 162 | RLIMIT_NPROC1 = '6' #flags hex value 163 | RLIMIT_NPROC2 = '7' #flags hex value 164 | RLIMIT_NPROC3 = '8' #flags hex value 165 | if RLIMIT_NPROC1 in argVal or RLIMIT_NPROC2 in argVal or RLIMIT_NPROC3 in argVal: 166 | sysAdminRequired = False 167 | 168 | return sysAdminRequired 169 | 170 | 171 | def findArgumentsForImportedFunctions(self, syscallNumber, funcName, 172 | importedFunctionListFromBinary, importedFunctionListFromLibraries, syscallNumberToNameMap, 173 | sysToParentMap, glibcGraph, pathToFunctionMap): 174 | argumentVal = {'0000'}; 175 | 176 | # System call name and imported function name are name 177 | if syscallNumberToNameMap[syscallNumber] == funcName or funcName == '__' + syscallNumberToNameMap[syscallNumber]: 178 | # Check if the imported function is coming from binary 179 | if funcName in importedFunctionListFromBinary: 180 | argumentAnalysis = binaryAnalysis.BinaryAnalysis(self.binaryPath, None) 181 | argumentVal = argumentAnalysis.extractArgumentVal(syscallNumber, syscallNumberToNameMap) 182 | elif funcName in importedFunctionListFromLibraries: 183 | for libPath, fNames in pathToFunctionMap.items(): 184 | if funcName in pathToFunctionMap[libPath] and libPath != '/lib/x86_64-linux-gnu/libc.so.6': 185 | argumentAnalysis = binaryAnalysis.BinaryAnalysis(libPath, None) 186 | argumentVal = argumentVal | argumentAnalysis.extractArgumentVal(syscallNumber, syscallNumberToNameMap) 187 | # System call name is different from imported function name; we get 188 | # system call from another libc function. Ex., clone system call is 189 | # coming from fork libc wrapper 190 | elif syscallNumberToNameMap[syscallNumber] != funcName and funcName != '__' + syscallNumberToNameMap[syscallNumber]: 191 | parent = sysToParentMap[syscallNumber] 192 | if (parent == '__' + syscallNumberToNameMap[syscallNumber]): 193 | parent = glibcGraph.getParent(funcName, parent) 194 | argumentVal = {'removeTrue'} 195 | for syscallName, parentFunc in self.libcSensitiveSysToParentMap().items(): 196 | if syscallName == syscallNumberToNameMap[syscallNumber] and parentFunc == parent: 197 | argumentVal = {'removeFalse'} 198 | return argumentVal 199 | 200 | return argumentVal 201 | 202 | def getDirectSyscalls(self, canBeRemovedSyscallList, cannotBeRemovedSyscallList, syscallNumberToNameMap, SysAdminSyscallNumbers, blackListedLibraries): 203 | # Extract direct system calls from binary 204 | binAnalysisForBinary = binaryAnalysis.BinaryAnalysis(self.binaryPath, self.logger) 205 | directSyscallSetFromBinary, successCount, failCount, syscallToArgumentValMapBinary, success = binAnalysisForBinary.extractDirectSyscalls(SysAdminSyscallNumbers) 206 | 207 | if (not success): 208 | return (set(), success) 209 | 210 | # Analyze argument values for the system calls that are responsible for CAP_SYS_ADMIN 211 | if (syscallToArgumentValMapBinary != None): 212 | for syscallNumber, argumentVal in syscallToArgumentValMapBinary.items(): 213 | sysAdminRequired = self.findCapSysAdminRequirement(syscallNumber, argumentVal) 214 | if sysAdminRequired: 215 | canBeRemovedSyscallList.append(syscallNumber) 216 | else: 217 | cannotBeRemovedSyscallList.append(syscallNumber) 218 | 219 | # Extract direct system calls from libraries 220 | directSyscallSetFromLibraries = set() 221 | libraries = util.readLibrariesWithLdd(self.binaryPath) 222 | for name, libraryPath in libraries.items(): 223 | #direct system calls from shared libraries 224 | if name not in blackListedLibraries: 225 | binAnalysisForLibrary = binaryAnalysis.BinaryAnalysis(libraryPath, None) 226 | directSyscallsSet, successCount, failCount, syscallToArgumentValMapLibraries, success = binAnalysisForLibrary.extractDirectSyscalls(SysAdminSyscallNumbers) 227 | 228 | if (not success): 229 | return (set(), success) 230 | 231 | for syscallNumber, argumentVal in syscallToArgumentValMapLibraries.items(): 232 | sysAdminRequired = self.findCapSysAdminRequirement(syscallNumber, argumentVal) 233 | if sysAdminRequired: 234 | canBeRemovedSyscallList.append(syscallNumber) 235 | else: 236 | cannotBeRemovedSyscallList.append(syscallNumber) 237 | 238 | directSyscallSetFromLibraries = directSyscallSetFromLibraries | directSyscallsSet 239 | 240 | 241 | directSyscallsSet = directSyscallSetFromBinary | directSyscallSetFromLibraries 242 | return (directSyscallsSet, True) 243 | 244 | 245 | def getSyscallsFromImportedFunctions(self, canBeRemovedSyscallList, 246 | cannotBeRemovedSyscallList, syscallNumberToNameMap, 247 | SysAdminSyscallNumbers, blackListedLibraries): 248 | syscallsSetFromImportedFunctions = set() 249 | 250 | # Extract imported functions from binary 251 | importedFunctionListFromBinary = util.extractImportedFunctions(self.binaryPath) 252 | 253 | # Extract imported functions from libraries 254 | libraries = util.readLibrariesWithLdd(self.binaryPath) 255 | importedFunctionListFromLibraries = [] 256 | pathToFunctionMap = {} 257 | 258 | for name, libraryPath in libraries.items(): 259 | if name not in blackListedLibraries: 260 | pathToFunctionMap[libraryPath] = util.extractImportedFunctions(libraryPath) 261 | importedFunctionListFromLibraries = importedFunctionListFromLibraries + pathToFunctionMap[libraryPath] 262 | 263 | allImportedFunctions = set(importedFunctionListFromBinary + importedFunctionListFromLibraries) 264 | 265 | # Extract system calls from imported functions using glibc callgraph 266 | glibcGraph = graph.Graph(self.logger) 267 | glibcGraph.createGraphFromInput("libc-callgraphs/glibc.callgraph", ":") 268 | 269 | for funcName in allImportedFunctions: 270 | syscallsSet, sysToParentMap = glibcGraph.getSyscallFromStartNode(funcName) 271 | for syscallNumber in syscallsSet: 272 | if syscallNumber in SysAdminSyscallNumbers: 273 | # Find argument values for the system calls responsible 274 | # for CAP_SYS_ADMIN 275 | argumentVal = self.findArgumentsForImportedFunctions(syscallNumber, funcName, 276 | importedFunctionListFromBinary, importedFunctionListFromLibraries, syscallNumberToNameMap, 277 | sysToParentMap, glibcGraph, pathToFunctionMap); 278 | # Find if CAP_SYS_ADMIN is required 279 | sysAdminRequired = self.findCapSysAdminRequirement(syscallNumber, argumentVal) 280 | if sysAdminRequired: 281 | canBeRemovedSyscallList.append(syscallNumber) 282 | else: 283 | cannotBeRemovedSyscallList.append(syscallNumber) 284 | syscallsSetFromImportedFunctions = syscallsSetFromImportedFunctions | syscallsSet 285 | 286 | return syscallsSetFromImportedFunctions 287 | 288 | def getSyscallsFromSysfilter(self): 289 | numOfSysfilterSyscalls = 0 290 | sysfilterAnalysis = sysfilter.sysFilter(self.binaryPath, self.sysfilterPath, self.logger) 291 | allSyscallsFromSysfilter = sysfilterAnalysis.getSyscalls(); 292 | 293 | if len(allSyscallsFromSysfilter) == 1 and allSyscallsFromSysfilter[0] == -1: 294 | numOfSysfilterSyscalls = 0 295 | else: 296 | numOfSysfilterSyscalls = len(allSyscallsFromSysfilter) 297 | 298 | return allSyscallsFromSysfilter, numOfSysfilterSyscalls 299 | 300 | def getSyscallNameFromSyscallNumber(self, syscallNumbers, syscallNumberToNameMap): 301 | syscallNameList = [] 302 | i = 0 303 | while i < 400: 304 | if ( i in syscallNumbers and syscallNumberToNameMap.get(i, None)): 305 | syscallNameList.append(syscallNumberToNameMap[i]) 306 | i += 1 307 | #print ('\nExtracted syscall Names intersected: ', syscallNameList, '\n') 308 | return syscallNameList 309 | 310 | def removeCapSysAdmin(self, canBeRemovedSyscallList, cannotBeRemovedSyscallList, 311 | capToAdd, syscallsResponsibleForSysAdmin, syscallNumberToNameMap): 312 | removeSyscallList = set(canBeRemovedSyscallList) - set(cannotBeRemovedSyscallList) 313 | removeSyscallNameList = self.getSyscallNameFromSyscallNumber(removeSyscallList, syscallNumberToNameMap) 314 | 315 | diff = [] 316 | isRemovable = '' 317 | if len(syscallsResponsibleForSysAdmin) != 0: 318 | diff = list(set(syscallsResponsibleForSysAdmin) - set(removeSyscallNameList)) 319 | # remove CAP_SYS_ADMIN only when all of the syscalls responsible 320 | # are removable. Note: we don't remove the syscall itself since a 321 | # syscall can be responsible for other capabilities also. We just 322 | # remove the CAP_SYS_ADMIN capability 323 | if len(diff) == 0: 324 | isRemovable = 'Yes' 325 | capToAdd.remove('CAP_SYS_ADMIN') 326 | self.logger.info("... removing CAP_SYS_ADMIN") 327 | 328 | else: 329 | isRemovable = 'No' 330 | self.logger.info("... CAP_SYS_ADMIN can not be removed") 331 | 332 | return capToAdd, isRemovable 333 | 334 | 335 | def runCapabilityAnalysis(self): 336 | self.logger.info("Starting analysis for binary: %s ...\n", self.binaryName) 337 | 338 | successfullyAnalyzed = True 339 | canBeRemovedSyscallList = [] 340 | cannotBeRemovedSyscallList = [] 341 | syscallMapper = syscall.Syscall(self.logger) 342 | syscallNumberToNameMap = syscallMapper.createMap() 343 | SysAdminSyscallNumbers = {56,16, 28, 157,31,250,101,179, 71, 317, 344 | 251, 160} 345 | blackListedLibraries = ["ld", "libc", "libdl", "libnss_compat", "libnsl", "libnss_files", "libnss_nis", "libm", "libresolv", "librt", "libnss_dns", "gosu"] 346 | 347 | self.logger.info("Starting system call extraction ...") 348 | # Extracting system calls using Confine 349 | self.logger.info("Extracting system calls using Confine and performing argument analysis for the system calls responsible for CAP_SYS_ADMIN ...") 350 | 351 | # Handle direct syscalls 352 | directSyscalls, successfullyAnalyzed = self.getDirectSyscalls(canBeRemovedSyscallList, 353 | cannotBeRemovedSyscallList, syscallNumberToNameMap, SysAdminSyscallNumbers, blackListedLibraries) 354 | 355 | # Handle imported functions 356 | syscallsFromImportedFunctions = self.getSyscallsFromImportedFunctions(canBeRemovedSyscallList, cannotBeRemovedSyscallList, syscallNumberToNameMap, SysAdminSyscallNumbers, blackListedLibraries) 357 | 358 | allSyscallsFromConfine = directSyscalls | syscallsFromImportedFunctions 359 | 360 | allSyscallsFromConfine = list(allSyscallsFromConfine) 361 | allSyscallsFromConfine.sort() 362 | numOfConfineSyscalls = len(allSyscallsFromConfine) 363 | #print('num of syscall confine', numOfConfineSyscalls) 364 | #print ('\nAll syscall Numbers: ', allSyscallsFromConfine, len(allSyscallsFromConfine)) 365 | 366 | # Extracting system calls using Sysfilter 367 | self.logger.info("Extracting system calls using Sysfilter ...") 368 | allSyscallsFromSysfilter, numOfSysfilterSyscalls = self.getSyscallsFromSysfilter() 369 | 370 | 371 | # Take intersection of Confine and Sysfilter 372 | self.logger.info("Generating final system call list ...") 373 | intersectedSyscallSet = allSyscallsFromConfine 374 | if (numOfSysfilterSyscalls != 0): 375 | intersectedSyscallSet = list(set(allSyscallsFromConfine) & set(allSyscallsFromSysfilter)) 376 | intersectedSyscallSet.sort() 377 | 378 | numofIntersectedSyscalls = len(intersectedSyscallSet) 379 | #print('num of syscalls intersected', numofIntersectedSyscalls) 380 | self.logger.info("... System call extraction done!") 381 | self.logger.info("Total number of extracted system calls : %d\n", numofIntersectedSyscalls) 382 | 383 | # Extract names of the syscalls from syscall numbers 384 | syscallNameList = self.getSyscallNameFromSyscallNumber(intersectedSyscallSet, syscallNumberToNameMap) 385 | 386 | 387 | # Extract required capabilities from system calls 388 | self.logger.info("Finding required capabilities for the extracted system calls ...") 389 | capabilities = syscallToCapabilityMapping.Mappings(syscallNameList) 390 | capToAdd, syscallsResponsibleForSysAdmin = capabilities.requiredCapabilities() 391 | 392 | # Remove CAP_SYS_ADMIN using argument analysis 393 | self.logger.info("Checking if CAP_SYS_ADMIN is required based on the argument analysis ...") 394 | capToAdd, isRemovable = self.removeCapSysAdmin(canBeRemovedSyscallList, cannotBeRemovedSyscallList, capToAdd, syscallsResponsibleForSysAdmin, syscallNumberToNameMap); 395 | self.logger.info("Total num of capabilties to add : %d", len(capToAdd)) 396 | 397 | # Add capabilities to binary 398 | self.addCapsToBinary(capToAdd) 399 | 400 | return syscallsResponsibleForSysAdmin, isRemovable, capToAdd, numOfConfineSyscalls, numOfSysfilterSyscalls, numofIntersectedSyscalls, successfullyAnalyzed 401 | -------------------------------------------------------------------------------- /src/python-utils/graph.py: -------------------------------------------------------------------------------- 1 | import os, sys, subprocess, signal 2 | import util 3 | import copy 4 | 5 | class Graph(): 6 | """ 7 | This class can be used to create a graph and run DFS and BFS on it 8 | """ 9 | INITIAL = "white" 10 | VISITED = "green" 11 | 12 | DEFAULT = "orange" #default (direct/indirect/conditional) 13 | CONDITIONAL = "yellow" #conditional 14 | DIRECT = "blue" #direct 15 | INDIRECT = "red" #indirect 16 | EXT = "purple" #external 17 | 18 | def __init__(self, logger): 19 | self.logger = logger 20 | self.adjGraph = dict() 21 | self.reverseAdjGraph = dict() 22 | self.nodeInputs = dict() 23 | self.nodeOutputs = dict() 24 | self.startingNodes = None 25 | self.nodeColor = dict() 26 | self.edgeIdToTuple = dict() #edgeId -> tuple(caller, callee) 27 | self.edgeTupleToId = dict() #tuple(caller, callee) -> edgeId 28 | self.edgeColor = dict() #edgeColor[edgeId] = self.DEFAULT 29 | self.edgeCondition = dict() #edgeCondition[edgeId] = CONDITION(%10==null TRUE) 30 | self.allNodes = set() 31 | self.edgeId = 0 32 | 33 | def deepCopy(self): 34 | copyGraph = Graph(self.logger) 35 | copyGraph.adjGraph = copy.deepcopy(self.adjGraph) 36 | copyGraph.reverseAdjGraph = copy.deepcopy(self.reverseAdjGraph) 37 | copyGraph.nodeInputs = copy.deepcopy(self.nodeInputs) 38 | copyGraph.nodeOutputs = copy.deepcopy(self.nodeOutputs) 39 | copyGraph.startingNodes = copy.deepcopy(self.startingNodes) 40 | copyGraph.nodeColor = copy.deepcopy(self.nodeColor) 41 | copyGraph.edgeIdToTuple = copy.deepcopy(self.edgeIdToTuple) 42 | copyGraph.edgeTupleToId = copy.deepcopy(self.edgeTupleToId) 43 | copyGraph.edgeColor = copy.deepcopy(self.edgeColor) 44 | copyGraph.edgeCondition = copy.deepcopy(self.edgeCondition) 45 | copyGraph.allNodes = copy.deepcopy(self.allNodes) 46 | return copyGraph 47 | 48 | def getAllLeafNodes(self): 49 | leafNodes = set() 50 | for node, outCount in self.nodeOutputs.items(): 51 | if ( outCount == 0 ): 52 | leafNodes.add(node) 53 | return leafNodes 54 | 55 | def getAllNodes(self): 56 | return self.allNodes 57 | 58 | def getNodeCount(self): 59 | return len(self.allNodes) 60 | 61 | def addNode(self, nodeName): 62 | if ( not self.adjGraph.get(nodeName, None) ): 63 | self.adjGraph[nodeName] = list() 64 | count = self.nodeInputs.get(nodeName, 0) 65 | self.nodeInputs[nodeName] = count 66 | count = self.nodeOutputs.get(nodeName, 0) 67 | self.nodeOutputs[nodeName] = count 68 | self.nodeColor[nodeName] = self.INITIAL 69 | self.allNodes.add(nodeName) 70 | 71 | def addEdgeWithType(self, srcNode, dstNode, edgeType): 72 | self.addEdge(srcNode, dstNode) 73 | edgeId = self.edgeTupleToId[(srcNode, dstNode)] 74 | self.edgeColor[edgeId] = edgeType 75 | 76 | def addEdge(self, srcNode, dstNode): 77 | self.allNodes.add(srcNode) 78 | self.allNodes.add(dstNode) 79 | #Add forward edge 80 | currentList = self.adjGraph.get(srcNode, list()) 81 | currentList.append(dstNode) 82 | self.adjGraph[srcNode] = currentList 83 | #self.logger.debug("Adding edge from %s to %s", srcNode, dstNode) 84 | 85 | #Add reverse edge 86 | currentList = self.reverseAdjGraph.get(dstNode, list()) 87 | currentList.append(srcNode) 88 | self.reverseAdjGraph[dstNode] = currentList 89 | 90 | #Update input count: 91 | count = self.nodeInputs.get(srcNode, 0) 92 | self.nodeInputs[srcNode] = count 93 | 94 | if ( srcNode != dstNode ): 95 | count = self.nodeInputs.get(dstNode, 0) 96 | count += 1 97 | self.nodeInputs[dstNode] = count 98 | 99 | #Update output count: 100 | count = self.nodeOutputs.get(dstNode, 0) 101 | self.nodeOutputs[dstNode] = count 102 | 103 | if ( srcNode != dstNode ): 104 | count = self.nodeOutputs.get(srcNode, 0) 105 | count += 1 106 | self.nodeOutputs[srcNode] = count 107 | 108 | self.edgeIdToTuple[self.edgeId] = (srcNode, dstNode) 109 | self.edgeTupleToId[(srcNode, dstNode)] = self.edgeId 110 | self.edgeColor[self.edgeId] = self.DEFAULT 111 | self.edgeId += 1 112 | 113 | def dfs(self, startNode): 114 | visitedNodes = set() 115 | myStack = list() 116 | myStack.append(startNode) 117 | 118 | if ( len(self.adjGraph.get(startNode, list())) == 0 ): 119 | return visitedNodes 120 | 121 | while ( len(myStack) != 0 ): 122 | currentNode = myStack.pop() 123 | if ( currentNode not in visitedNodes): 124 | #self.logger.debug("Visiting node: " + currentNode) 125 | visitedNodes.add(currentNode) 126 | for node in self.adjGraph.get(currentNode, list()): 127 | #self.logger.debug("Adding node: " + node) 128 | myStack.append(node) 129 | 130 | return visitedNodes 131 | 132 | def minimumRemovableEdges(self, conditionalGraphFile, cfgSeparator, start, end, maxDepth): 133 | self.applyConditionalGraph(conditionalGraphFile, cfgSeparator) 134 | clone = self.deepCopy() 135 | depthToEdge = dict() 136 | visitedNodes = set() 137 | clone.reverseDfs(end, start, end, visitedNodes, depthToEdge, 0, maxDepth) 138 | for depth, edgeIdDict in depthToEdge.items(): 139 | for edgeId, status in edgeIdDict.items(): 140 | if ( not status ): 141 | self.logger.info("depth: %d, edgeId: %d, edge: %s", depth, edgeId, self.edgeIdToTuple[edgeId]) 142 | return 143 | 144 | def reverseDfs(self, currentNode, start, end, visitedNodes, depthToEdge, currentDepth, maxDepth): 145 | self.logger.debug("reverseDfs: currentDepth: %d and maxDepth: %d, currentNode: %s, adjList: %s", currentDepth, maxDepth, currentNode, str(self.reverseAdjGraph.get(currentNode, list()))) 146 | if ( currentDepth > maxDepth or currentNode in visitedNodes or len(self.reverseAdjGraph.get(currentNode, list())) == 0 ): 147 | return 148 | accessibleList = self.accessibleFromStartNode(start, [end], list()) 149 | self.logger.debug("%s to %s isReachable? %s", start, end, str(len(accessibleList) != 0)) 150 | if ( len(accessibleList) == 0 ): 151 | self.logger.info("reverseDfs: accessibleList is empty, returning...") 152 | return 153 | 154 | visitedNodes.add(currentNode) 155 | 156 | self.logger.debug("currentNode: %s", currentNode) 157 | adjList = copy.deepcopy(self.reverseAdjGraph.get(currentNode, list())) 158 | for node in adjList: 159 | self.logger.debug("node: %s", node) 160 | conditionalEdgeBool = self.edgeTupleToId.get((node, currentNode), None) and self.edgeColor.get(self.edgeTupleToId[(node, currentNode)], None) and self.edgeColor[self.edgeTupleToId[(node, currentNode)]] == self.CONDITIONAL 161 | if ( conditionalEdgeBool ): 162 | depthEdgeToStatus = depthToEdge.get(currentDepth, dict()) 163 | depthEdgeStatus = depthEdgeToStatus.get(self.edgeTupleToId[node, currentNode], True) 164 | depthEdgeToStatus[self.edgeTupleToId[node, currentNode]] = depthEdgeStatus 165 | #depthEdges.add(self.edgeTupleToId[node, currentNode]) 166 | depthToEdge[currentDepth] = depthEdgeToStatus 167 | self.reverseDfs(node, start, end, visitedNodes, depthToEdge, currentDepth+1, maxDepth) 168 | if ( conditionalEdgeBool ): 169 | clone = self.deepCopy() 170 | clone.deleteEdgeByTuple((node, currentNode)) 171 | isReachable = len(clone.accessibleFromStartNode(start, [end], list())) != 0 172 | if ( not isReachable ): 173 | depthToEdge[currentDepth] = depthEdgeToStatus 174 | depthEdgeToStatus[self.edgeTupleToId[node, currentNode]] = False 175 | depthToEdge[currentDepth] = depthEdgeToStatus 176 | self.logger.info("After deleting edge: %d, tuple: %s, isReachable? %s", self.edgeTupleToId[(node, currentNode)], (node, currentNode), str(isReachable)) 177 | #self.deleteEdgeByTuple((node, currentNode)) 178 | 179 | return 180 | 181 | def bfs(self): 182 | #TODO 183 | return list() 184 | 185 | def deleteEdgeById(self, edgeId): 186 | edgeTuple = self.edgeIdToTuple[edgeId] 187 | return deleteEdgeByTuple(edgeTuple) 188 | 189 | 190 | def deleteEdgeByTuple(self, edgeTuple): 191 | srcNode = edgeTuple[0] 192 | dstNode = edgeTuple[1] 193 | self.logger.debug("Deleting edge from %s to %s", srcNode, dstNode) 194 | #Delete forward edge 195 | currentList = self.adjGraph.get(srcNode, list()) 196 | currentList.remove(dstNode) 197 | self.adjGraph[srcNode] = currentList 198 | 199 | #Add reverse edge 200 | currentList = self.reverseAdjGraph.get(dstNode, list()) 201 | currentList.remove(srcNode) 202 | self.reverseAdjGraph[dstNode] = currentList 203 | 204 | #Update input count: 205 | if ( srcNode != dstNode ): 206 | count = self.nodeInputs.get(dstNode, 0) 207 | count -= 1 208 | self.nodeInputs[dstNode] = count 209 | 210 | #Update output count: 211 | if ( srcNode != dstNode ): 212 | count = self.nodeOutputs.get(srcNode, 0) 213 | count -= 1 214 | self.nodeOutputs[srcNode] = count 215 | 216 | def deleteOutboundEdges(self, node): 217 | dstNodes = copy.deepcopy(self.adjGraph.get(node, list())) 218 | self.logger.debug("dstNodes to be deleted: %s", str(dstNodes)) 219 | for dstNode in dstNodes: 220 | self.deleteEdgeByTuple((node, dstNode)) 221 | 222 | def deleteInboundEdges(self, node, edgeType=None): 223 | srcNodes = copy.deepcopy(self.reverseAdjGraph.get(node, list())) 224 | self.logger.debug("srcNodes to be deleted: %s", str(srcNodes)) 225 | for srcNode in srcNodes: 226 | self.logger.debug("%s->%s edge type: %s", srcNode, node, self.getEdgeType(srcNode, node)) 227 | if ( not edgeType or (edgeType and edgeType == self.getEdgeType(srcNode, node))): 228 | self.deleteEdgeByTuple((srcNode, node)) 229 | 230 | ''' 231 | The difference of this function compared to pruneInaccessibleFunctionPointers is that in this we consider 232 | all functions used as indirect call site targets as our base and not only the function pointer file. 233 | After we reach a conclusion on the correctness of applying this to the main function we can merge these 234 | two funtions. 235 | For now we won't to keep the code which generated the paper results intact 236 | 237 | #BUG: If the function address taken extraction code has a bug (which it does as of 9/5/20) this function 238 | #will remove functions which have their address taken, but we just haven't identified them 239 | #example: httpd: setup_threads_runtime->ap_unixd_accept 240 | #is not extracted by our LLVM pass, if we use the function below we will delete all incoming edges to ap_unixd_accept 241 | #while it is used, the previous function (luckily used in the submitted paper) doesn't have this bug 242 | ''' 243 | def pruneAllFunctionPointersNotAccessibleFromChild(self, startNodes, funcPointerFile, directCfgFile, separator, outputFile): 244 | indirectFunctions = self.extractIndirectFunctions(directCfgFile, separator) 245 | fpFuncToCaller = dict() 246 | 247 | startNodeSet = set() 248 | if ( "," in startNodes ): 249 | startNodeSet = set(startNodes.split(",")) 250 | else: 251 | startNodeSet.add(startNodes) 252 | 253 | fpFile = open(funcPointerFile, 'r') 254 | fpLine = fpFile.readline() 255 | while ( fpLine ): 256 | #Iterate over each fp file line 257 | if ( "->" in fpLine ): 258 | splittedLine = fpLine.split("->") 259 | caller = splittedLine[0].strip() 260 | fpFunc = splittedLine[1].strip() 261 | self.logger.debug("caller: %s, fp: %s", caller, fpFunc) 262 | fpFuncSet = fpFuncToCaller.get(fpFunc, set()) 263 | fpFuncSet.add(caller) 264 | fpFuncToCaller[fpFunc] = fpFuncSet 265 | else: 266 | self.logger.warning("Skipping function pointer line: %s", fpLine) 267 | 268 | fpLine = fpFile.readline() 269 | 270 | for fpFunc in indirectFunctions: 271 | # for fpFunc, callerSet in fpFuncToCaller.items(): 272 | callerSet = fpFuncToCaller.get(fpFunc, set()) 273 | tmpClone = self.deepCopy() 274 | 275 | #Temporarily remove outbound edges from B 276 | tmpClone.deleteOutboundEdges(fpFunc) 277 | reachableSet = set() 278 | for caller in callerSet: 279 | for startNode in startNodeSet: 280 | #Check if caller is reachable from each start node 281 | reachableSet.update(tmpClone.accessibleFromStartNode(startNode, [caller], list())) 282 | self.logger.debug("Reachable Set: %s", str(reachableSet)) 283 | callerReachable = (len(reachableSet) > 0) 284 | self.logger.debug("caller: %s isReachable from child/worker? %s", caller, callerReachable) 285 | #If caller isn't reachable, permanently remove all indirect calls to B 286 | if ( not callerReachable ): 287 | self.deleteInboundEdges(fpFunc.strip(), self.DEFAULT) 288 | #Write final graph to file 289 | self.dumpToFile(outputFile) 290 | 291 | def extractIndirectFunctions(self, directCfgFile, separator): 292 | self.applyDirectGraph(directCfgFile, separator) 293 | indirectFunctions = set() 294 | 295 | for node, callers in self.reverseAdjGraph.items(): 296 | directCallerSet = set() 297 | for caller in callers: 298 | if self.getEdgeType(caller, node) == self.DIRECT: 299 | directCallerSet.add(caller) 300 | if ( len(directCallerSet) != len(set(callers)) ): 301 | indirectFunctions.add(node) 302 | return indirectFunctions 303 | 304 | def pruneInaccessibleFunctionPointers(self, startNodes, funcPointerFile, directCfgFile, separator, outputFile, funcPointerFileWoConditions=None, runtimeExecutedFunctionFilePath=None): 305 | #Apply direct CFG to current graph 306 | self.applyDirectGraph(directCfgFile, separator) 307 | 308 | #3/26/2020 309 | #Do we have to consider all functions only called through indirect call sites 310 | #which don't have their address taken at all? 311 | #Currently we're only removing those which have their address taken in paths unreachable 312 | #from main, but it seems that there could be functions which don't have their address 313 | #taken at all??? 314 | #Won't add to keep this function in accordance with submitted version of paper! 315 | #Could it be that these functions would be removed by dead code elimination of the compiler?? 316 | 317 | #Read function pointer file: 318 | #function (A)->function pointed to by FP (B) 319 | #piped_log_spawn->piped_log_maintenance 320 | 321 | #BUG: We have to consider ALL callers of FP before removing the edges 322 | #We we're previously removing all incoming edges when we identified only 323 | #one caller as unreachable from start 324 | #FIXED 325 | fpFuncToCaller = dict() 326 | 327 | startNodeSet = set() 328 | if ( "," in startNodes ): 329 | startNodeSet = set(startNodes.split(",")) 330 | else: 331 | startNodeSet.add(startNodes) 332 | 333 | fpFile = open(funcPointerFile, 'r') 334 | fpLine = fpFile.readline() 335 | while ( fpLine ): 336 | #Iterate over each fp file line 337 | if ( "->" in fpLine ): 338 | splittedLine = fpLine.split("->") 339 | caller = splittedLine[0].strip() 340 | fpFunc = splittedLine[1].strip() 341 | self.logger.debug("caller: %s, fp: %s", caller, fpFunc) 342 | fpFuncSet = fpFuncToCaller.get(fpFunc, set()) 343 | fpFuncSet.add(caller) 344 | fpFuncToCaller[fpFunc] = fpFuncSet 345 | else: 346 | self.logger.warning("Skipping function pointer line: %s", fpLine) 347 | 348 | fpLine = fpFile.readline() 349 | 350 | fpFile.close() 351 | 352 | if ( funcPointerFileWoConditions ): 353 | fpFile = open(funcPointerFileWoConditions, 'r') 354 | fpLine = fpFile.readline() 355 | while ( fpLine ): 356 | if ( "->" in fpLine ): 357 | splittedLine = fpLine.split("->") 358 | caller = splittedLine[0].strip() 359 | fpFunc = splittedLine[1].strip() 360 | self.logger.debug("caller: %s, fp: %s", caller, fpFunc) 361 | 362 | if ( len(fpFuncToCaller.get(fpFunc, set())) == 0 ): 363 | self.deleteInboundEdges(fpFunc.strip(), self.DEFAULT) 364 | fpLine = fpFile.readline() 365 | fpFile.close() 366 | 367 | #Added to keep track of functions executed at runtime -- for enhanced runtime FP analysis 368 | runtimeExecutedFunctions = set() 369 | if ( runtimeExecutedFunctionFilePath ): 370 | runtimeExecutedFunctionFile = open(runtimeExecutedFunctionFilePath, 'r') 371 | executedFuncLine = runtimeExecutedFunctionFile.readline() 372 | while ( executedFuncLine ): 373 | runtimeExecutedFunctions.add(executedFuncLine.strip()) 374 | executedFuncLine = runtimeExecutedFunctionFile.readline() 375 | runtimeExecutedFunctionFile.close() 376 | 377 | for fpFunc, callerSet in fpFuncToCaller.items(): 378 | tmpClone = self.deepCopy() 379 | self.logger.debug("Starting analysis for fpFunc: %s", fpFunc) 380 | 381 | #Temporarily remove outbound edges from B 382 | self.logger.debug("Temporarily removing outbound edges from: %s", fpFunc) 383 | tmpClone.deleteOutboundEdges(fpFunc) 384 | reachableSet = set() 385 | callerReachable = False 386 | for caller in callerSet: 387 | if ( caller in runtimeExecutedFunctions ): #If the caller is executed at runtime, all fp allocations are reachable 388 | self.logger.debug("caller: %s is in runtimeExecutedFunctions, not removing FP allocation", caller) 389 | callerReachable = True 390 | break 391 | for startNode in startNodeSet: 392 | #Check if caller is reachable from each start node 393 | reachableSet.update(tmpClone.accessibleFromStartNode(startNode, [caller], list())) 394 | self.logger.debug("Reachable Set: %s", str(reachableSet)) 395 | if ( not callerReachable ): 396 | callerReachable = (len(reachableSet) > 0) 397 | self.logger.debug("caller: %s isReachable? %s", caller, callerReachable) 398 | 399 | #If caller isn't reachable, permanently remove all indirect calls to B 400 | if ( not callerReachable ): 401 | self.deleteInboundEdges(fpFunc.strip(), self.DEFAULT) 402 | 403 | #Write final graph to file 404 | self.dumpToFile(outputFile) 405 | 406 | def pruneConditionalTrueEdges(self): 407 | return True 408 | 409 | def isAccessible(self, startNode, targetNode, filterList=list(), exceptList=list()): 410 | results = set() 411 | visitedNodes = set() 412 | myStack = list() 413 | myStack.append(startNode) 414 | self.logger.debug("running isAccessible with startNode: %s, targetNode: %s", startNode, targetNode) 415 | if ( len(self.adjGraph.get(startNode, list())) == 0 ): 416 | self.logger.debug("adjGraph for %s is empty, returning False", startNode) 417 | return False 418 | 419 | while ( len(myStack) != 0 ): 420 | currentNode = myStack.pop() 421 | if ( currentNode not in visitedNodes): 422 | if ( currentNode == targetNode ): 423 | return True 424 | self.logger.debug("Visiting node: " + currentNode) 425 | visitedNodes.add(currentNode) 426 | if ( ( len(filterList) == 0 and len(exceptList) == 0 ) or ( len(filterList) > 0 and currentNode in filterList) or ( len(exceptList) > 0 and currentNode not in exceptList ) ): 427 | results.add(currentNode) 428 | if ( len(self.adjGraph.get(currentNode, list())) != 0 ): 429 | for node in self.adjGraph.get(currentNode, list()): 430 | myStack.append(node) 431 | 432 | return False 433 | 434 | 435 | def extractStartingNodes(self): 436 | self.startingNodes = list() 437 | for nodeName, inputCount in self.nodeInputs.items(): 438 | #self.logger.debug("nodeName: %s, inputCount: %d", nodeName, inputCount) 439 | if ( inputCount == 0 ): 440 | self.startingNodes.append(nodeName) 441 | return self.startingNodes 442 | 443 | def dfsWithDominators(self, nodeName): 444 | #TODO 445 | return None 446 | 447 | def getLeavesFromStartNode(self, nodeName, filterList, exceptList): 448 | results = set() 449 | visitedNodes = set() 450 | myStack = list() 451 | myStack.append(nodeName) 452 | 453 | if ( len(self.adjGraph.get(nodeName, list())) == 0 ): 454 | return results 455 | 456 | while ( len(myStack) != 0 ): 457 | currentNode = myStack.pop() 458 | if ( currentNode not in visitedNodes): 459 | #self.logger.debug("Visiting node: " + currentNode) 460 | visitedNodes.add(currentNode) 461 | if ( len(self.adjGraph.get(currentNode, list())) != 0 ): 462 | for node in self.adjGraph.get(currentNode, list()): 463 | myStack.append(node) 464 | else: 465 | if ( ( len(filterList) == 0 and len(exceptList) == 0 ) or ( len(filterList) > 0 and currentNode in filterList) or ( len(exceptList) > 0 and currentNode not in exceptList ) ): 466 | results.add(currentNode) 467 | 468 | return results 469 | 470 | def accessibleFromStartNode(self, nodeName, filterList, exceptList): 471 | results = set() 472 | visitedNodes = set() 473 | myStack = list() 474 | myStack.append(nodeName) 475 | 476 | if ( nodeName in filterList ): 477 | results.add(nodeName) 478 | 479 | if ( len(self.adjGraph.get(nodeName, list())) == 0 ): 480 | return results 481 | 482 | while ( len(myStack) != 0 ): 483 | currentNode = myStack.pop() 484 | if ( currentNode not in visitedNodes): 485 | #self.logger.debug("Visiting node: " + currentNode) 486 | visitedNodes.add(currentNode) 487 | if ( ( len(filterList) == 0 and len(exceptList) == 0 ) or ( len(filterList) > 0 and currentNode in filterList) or ( len(exceptList) > 0 and currentNode not in exceptList ) ): 488 | results.add(currentNode) 489 | if ( len(self.adjGraph.get(currentNode, list())) != 0 ): 490 | for node in self.adjGraph.get(currentNode, list()): 491 | myStack.append(node) 492 | 493 | return results 494 | 495 | 496 | def getSyscallFromStartNode(self, nodeName): 497 | results = set() 498 | visitedNodes = set() 499 | myStack = list() 500 | myStackParent = list() 501 | sysToParentMap = dict() 502 | myStack.append(nodeName) 503 | myStackParent.append(nodeName) 504 | 505 | if ( len(self.adjGraph.get(nodeName, list())) == 0 ): 506 | return results, sysToParentMap 507 | 508 | while ( len(myStack) != 0 ): 509 | currentNode = myStack.pop() 510 | parentNode = myStackParent.pop() 511 | if ( currentNode not in visitedNodes): 512 | # self.logger.debug("Visiting node: " + currentNode) 513 | visitedNodes.add(currentNode) 514 | if ( len(self.adjGraph.get(currentNode, list())) != 0 ): 515 | for node in self.adjGraph.get(currentNode, list()): 516 | myStack.append(node) 517 | myStackParent.append(currentNode) 518 | else: 519 | if ( currentNode.strip().startswith("syscall") ): 520 | #self.logger.debug("getSyscallFromStartNode: currentNode: %s", currentNode) 521 | currentNode = currentNode.replace("syscall","") 522 | currentNode = currentNode.replace("(","") 523 | currentNode = currentNode.replace(")","") 524 | currentNode = currentNode.strip() 525 | if ( not currentNode.startswith("%") and currentNode != '' ): 526 | results.add(int(currentNode)) 527 | sysToParentMap[int(currentNode)] = parentNode; 528 | #print ("system call number ",int(currentNode)) 529 | #print ("parent function ", parentNode) 530 | #print ("dictionary ", sysToParentMap) 531 | return results, sysToParentMap 532 | 533 | def getParent(self, nodeName, leafName): 534 | visitedNodes = set() 535 | myStack = list() 536 | myStackParent = list() 537 | sysToParentMap = dict() 538 | myStack.append(nodeName) 539 | myStackParent.append(nodeName) 540 | 541 | if ( len(self.adjGraph.get(nodeName, list())) == 0 ): 542 | return "" 543 | 544 | while ( len(myStack) != 0 ): 545 | currentNode = myStack.pop() 546 | parentNode = myStackParent.pop() 547 | if ( currentNode not in visitedNodes): 548 | # self.logger.debug("Visiting node: " + currentNode) 549 | visitedNodes.add(currentNode) 550 | if ( len(self.adjGraph.get(currentNode, list())) != 0 ): 551 | for node in self.adjGraph.get(currentNode, list()): 552 | if (node == leafName): 553 | return currentNode 554 | myStack.append(node) 555 | myStackParent.append(currentNode) 556 | return "" 557 | 558 | def getSyscallFromStartNodeWithVisitedNodes(self, nodeName): 559 | results = set() 560 | visitedNodes = set() 561 | myStack = list() 562 | myStack.append(nodeName) 563 | 564 | if ( len(self.adjGraph.get(nodeName, list())) == 0 ): 565 | return results, visitedNodes 566 | 567 | while ( len(myStack) != 0 ): 568 | currentNode = myStack.pop() 569 | if ( currentNode not in visitedNodes): 570 | # self.logger.debug("Visiting node: " + currentNode) 571 | visitedNodes.add(currentNode) 572 | if ( len(self.adjGraph.get(currentNode, list())) != 0 ): 573 | for node in self.adjGraph.get(currentNode, list()): 574 | myStack.append(node) 575 | else: 576 | if ( currentNode.strip().startswith("syscall") and "(" in currentNode ): 577 | #self.logger.debug("getSyscallFromStartNode: currentNode: %s", currentNode) 578 | currentNode = currentNode.replace("syscall","") 579 | currentNode = currentNode.replace("(","") 580 | currentNode = currentNode.replace(")","") 581 | currentNode = currentNode.strip() 582 | if ( not currentNode.startswith("%") and currentNode != '' ): 583 | results.add(int(currentNode)) 584 | else: 585 | self.logger.warning("getSyscallFromStartNodeWithVisitedNodes skipping currentNode: %s", currentNode) 586 | 587 | return results, visitedNodes 588 | 589 | def createGraphFromInput(self, inputFilePath, separator="->"): 590 | self.logger.debug("Running createGraphFromInput...") 591 | try: 592 | if ( os.path.isfile(inputFilePath) ): 593 | inputFile = open(inputFilePath, 'r') 594 | inputLine = inputFile.readline() 595 | while ( inputLine ): 596 | if ( not inputLine.startswith("#") ): 597 | splittedInput = inputLine.split(separator) 598 | if ( len(splittedInput) == 2 ): 599 | func1 = splittedInput[0].strip() 600 | func2 = splittedInput[1].strip() 601 | if ( func2.startswith("@") ): 602 | func2 = func2[1:] 603 | #self.logger.debug("Adding %s->%s", func1, func2) 604 | self.addEdge(func1, func2) 605 | else: 606 | self.logger.warning("Graph: Skipping line starting with #: %s", inputLine) 607 | inputLine = inputFile.readline() 608 | inputFile.close() 609 | else: 610 | self.logger.error("File doesn't exist: %s", inputFilePath) 611 | return -1 612 | except Exception as e: 613 | self.logger.error("File doesn't exist: %s", inputFilePath) 614 | return -1 615 | return 0 616 | 617 | def createGraphFromInputWithFilter(self, inputFilePath, separator, calleeNameList): 618 | inputFile = open(inputFilePath, 'r') 619 | inputLine = inputFile.readline() 620 | while ( inputLine ): 621 | if ( not inputLine.startswith("#") ): 622 | splittedInput = inputLine.split(separator) 623 | if ( len(splittedInput) == 2 ): 624 | func1 = splittedInput[0].strip() 625 | func2 = splittedInput[1].strip() 626 | if ( func2.startswith("@") ): 627 | func2 = func2[1:] 628 | #self.logger.debug("Adding %s->%s", func1, func2) 629 | if ( func2 not in calleeNameList ): 630 | self.addEdge(func1, func2) 631 | else: 632 | self.logger.warning("Skipping filter: %s->%s", func1, func2) 633 | else: 634 | self.logger.warning("Graph: Skipping line starting with #: %s", inputLine) 635 | inputLine = inputFile.readline() 636 | inputFile.close() 637 | 638 | def convertCcfgToCallGraph(self, startNode="main|0"): 639 | callGraph = Graph(self.logger) 640 | 641 | addedEdges = set() 642 | #visitedFunctions = set() 643 | visitedNodes = set() 644 | 645 | for node, nodeList in self.adjGraph.items(): 646 | if ( node.endswith("|0") ): 647 | myStack = list() 648 | myStack.append(node) 649 | 650 | while ( len(myStack) != 0 ): 651 | currentNode = myStack.pop() 652 | srcFunc = currentNode 653 | if ( '|' in srcFunc ): 654 | srcFunc = srcFunc[:srcFunc.index('|')] 655 | if ( currentNode not in visitedNodes ): 656 | visitedNodes.add(currentNode) 657 | for dstNode in self.adjGraph.get(currentNode, list()): 658 | dstFunc = dstNode 659 | if ( '|' in dstFunc ): 660 | dstFunc = dstFunc[:dstFunc.index('|')] 661 | if ( srcFunc != dstFunc and (srcFunc, dstFunc) not in addedEdges ): 662 | addedEdges.add((srcFunc, dstFunc)) 663 | callGraph.addEdge(srcFunc, dstFunc) 664 | myStack.append(dstNode) 665 | else: 666 | self.logger.debug("Skipping %s because it doesn't end in |0", node) 667 | 668 | #visitedNodes = set() 669 | #addedEdges = set() #This should be handled in the addEdge function (not changing now, due to unknown effects on other code repos 670 | #myStack = list() 671 | #myStack.append(startNode) 672 | 673 | #if ( len(self.adjGraph.get(startNode, list())) == 0 ): 674 | # return callGraph 675 | 676 | #while ( len(myStack) != 0 ): 677 | # currentNode = myStack.pop() 678 | # srcFunc = currentNode 679 | # if ( '|' in srcFunc ): 680 | # srcFunc = srcFunc[:srcFunc.index('|')] 681 | # if ( currentNode not in visitedNodes): 682 | # #self.logger.debug("Visiting node: " + currentNode) 683 | # visitedNodes.add(currentNode) 684 | # for node in self.adjGraph.get(currentNode, list()): 685 | # #self.logger.debug("Adding node: " + node) 686 | # dstFunc = node 687 | # if ( '|' in dstFunc ): 688 | # dstFunc = dstFunc[:dstFunc.index('|')] 689 | # if ( srcFunc != dstFunc and (srcFunc, dstFunc) not in addedEdges ): 690 | # addedEdges.add((srcFunc, dstFunc)) 691 | # callGraph.addEdge(srcFunc, dstFunc) 692 | # myStack.append(node) 693 | 694 | ##return visitedNodes 695 | 696 | 697 | ##for srcNode, nodeList in self.adjGraph.items(): 698 | ## srcFunc = srcNode 699 | ## if ( '|' in srcFunc ): 700 | ## srcFunc = srcFunc[:srcFunc.index('|')] 701 | ## for dstNode in nodeList: 702 | ## dstFunc = dstNode 703 | ## if ( '|' in dstFunc ): 704 | ## dstFunc = dstFunc[:dstFunc.index('|')] 705 | ## if ( srcFunc != dstFunc ): 706 | ## callGraph.addEdge(srcFunc, dstFunc) 707 | return callGraph 708 | 709 | 710 | 711 | def createConditionalControlFlowGraph(self, inputFilePath, keepAllConditionalEdges=True, separatorMap=None, enabledConditionSet=set(), disabledConditionSet=set(), removeIndirectEdges=False, intraproceduralOnly=False): 712 | #separatorMap: ["default":"->", "conditional":"-C->", "directfunc":"-F->", "indirectfunc": "-INDF->", "extfunc": "-ExtF->"] 713 | #In next iterations we might add the specific config option in the -C-> edge type 714 | ''' 715 | currently our file has the following type of lines: 716 | F1|BB1->F1|BB2 717 | F1|BB2-C-T->F1|BB3 718 | F1|BB2-C-F->F1|BB4 719 | F1|BB4->F1|BB3 720 | F1|BB4-INDF->F3|BB1 721 | F1|BB4-F->F4|BB1 722 | F1|BB4-ExtF->strcmp 723 | ''' 724 | #TODOs: 725 | #1. read and parse file and add nodes and edges corresponding to the file 726 | #2. 727 | self.logger.info("Running createConditionalControlFlowGraph function...") 728 | if ( not separatorMap ): 729 | separatorMap = {"CONDITIONAL-TRUE":"-C-T->", "CONDITIONAL-FALSE":"-C-F->", "DIRECT":"-F->", "INDIRECT": "-INDF->", "EXT": "-ExtF->", "DEFAULT":"->"} 730 | try: 731 | if ( os.path.isfile(inputFilePath) ): 732 | inputFile = open(inputFilePath, 'r') 733 | inputLine = inputFile.readline() 734 | while ( inputLine ): 735 | inputLine = inputLine.strip() 736 | #self.logger.debug("adding line: %s", inputLine) 737 | #if ( inputLine.startswith("main") ): 738 | # self.logger.debug("adding line: %s", inputLine) 739 | if ( not inputLine.startswith("#") ): 740 | separatorCount = 0 741 | for separatorName, separator in separatorMap.items(): 742 | if ( separator in inputLine ): 743 | splittedInput = inputLine.split(separator) 744 | callerBB = splittedInput[0] 745 | calleeBB = splittedInput[1] 746 | if ( intraproceduralOnly and (separatorName == "INDIRECT" or separatorName == "DIRECT" or separatorName == "EXT" ) ): 747 | self.logger.debug("Not adding INDIRECT, DIRECT or EXT edge because intraproceduralOnly is TRUE: %s", inputLine) 748 | break 749 | elif ( removeIndirectEdges and separatorName == "INDIRECT" ): 750 | self.logger.debug("Not adding INDIRECT edge: %s", inputLine) 751 | break 752 | elif ( separatorName == "CONDITIONAL-TRUE" or separatorName == "CONDITIONAL-FALSE"): 753 | #if ( keepAllConditionalEdges or callerBB in enabledConditionSet or not callerBB in disabledConditionSet ): 754 | # Changed enabled/disabled conditions to be able to remove both True edge and False edge 755 | separatorSuffix = separatorMap[separatorName] 756 | separatorSuffix = separatorSuffix.replace("->", "") 757 | callerBBWithSep = callerBB + separatorSuffix 758 | if ( keepAllConditionalEdges or callerBBWithSep in enabledConditionSet or not callerBBWithSep in disabledConditionSet ): 759 | self.addEdgeWithType(callerBB, calleeBB, separatorName) 760 | if ( callerBB.startswith("ssl_rand_seed") ): 761 | self.logger.info("adding line: %s", inputLine) 762 | self.logger.debug("Skipping input line since it's probably the TRUE branch:\n%s", inputLine) 763 | else:#if ( inputLine.startswith("ap_run_mpm") ): 764 | self.logger.info("Not adding edge: %s", inputLine) 765 | break 766 | else: 767 | self.addEdgeWithType(callerBB, calleeBB, separatorName) 768 | break 769 | else: 770 | separatorCount += 1 771 | if ( separatorCount >= 6 ): 772 | self.logger.error("Skipping line which doesn't have any separators. Is this expected? %s", inputLine) 773 | inputLine = inputFile.readline() 774 | inputFile.close() 775 | else: 776 | self.logger.error("File doesn't exist: %s", inputFilePath) 777 | return -1 778 | except Exception as e: 779 | self.logger.error("File doesn't exist: %s", inputFilePath) 780 | return -1 781 | 782 | 783 | 784 | 785 | #Deprecated 786 | def applyConditionalGraph(self, inputFilePath, separator): 787 | inputFile = open(inputFilePath, 'r') 788 | inputLine = inputFile.readline() 789 | while ( inputLine ): 790 | if ( not inputLine.startswith("#") ): 791 | splittedInput = inputLine.split(separator) 792 | if ( len(splittedInput) == 2 ): 793 | caller = splittedInput[0].strip() 794 | callee = splittedInput[1].strip() 795 | if ( callee.startswith("@") ): 796 | callee = callee[1:] 797 | if ( not self.edgeTupleToId.get((caller, callee), None) ): 798 | #self.logger.warning("Trying to change color of non-existent edge in initial graph, adding new edge") 799 | self.addEdge(caller, callee) 800 | self.edgeColor[self.edgeTupleToId[(caller, callee)]] = self.DIRECT 801 | elif ( len(splittedInput) == 3 ): 802 | caller = splittedInput[0].strip() 803 | condition = splittedInput[1].strip() 804 | callee = splittedInput[2].strip() 805 | if ( callee.startswith("@") ): 806 | callee = callee[1:] 807 | if ( not self.edgeTupleToId.get((caller, callee), None) ): 808 | #self.logger.warning("Trying to change color of non-existent edge in initial graph, adding new edge") 809 | self.addEdge(caller, callee) 810 | self.edgeColor[self.edgeTupleToId[(caller, callee)]] = self.CONDITIONAL 811 | self.edgeCondition[self.edgeTupleToId[(caller, callee)]] = condition 812 | else: 813 | self.logger.warning("Graph: Skipping line starting with #: %s", inputLine) 814 | inputLine = inputFile.readline() 815 | inputFile.close() 816 | 817 | def applyDirectGraph(self, inputFilePath, separator): 818 | inputFile = open(inputFilePath, 'r') 819 | inputLine = inputFile.readline() 820 | while ( inputLine ): 821 | if ( not inputLine.startswith("#") ): 822 | splittedInput = inputLine.split(separator) 823 | if ( len(splittedInput) == 2 ): 824 | caller = splittedInput[0].strip() 825 | callee = splittedInput[1].strip() 826 | if ( callee.startswith("@") ): 827 | callee = callee[1:] 828 | if ( not self.edgeTupleToId.get((caller, callee), None) ): 829 | #self.logger.warning("Trying to change color of non-existent edge in initial graph, adding new edge") 830 | self.addEdge(caller, callee) 831 | self.edgeColor[self.edgeTupleToId[(caller, callee)]] = self.DIRECT 832 | elif ( len(splittedInput) == 3 ): 833 | caller = splittedInput[0].strip() 834 | condition = splittedInput[1].strip() 835 | callee = splittedInput[2].strip() 836 | if ( callee.startswith("@") ): 837 | callee = callee[1:] 838 | if ( not self.edgeTupleToId.get((caller, callee), None) ): 839 | #self.logger.warning("Trying to change color of non-existent edge in initial graph, adding new edge") 840 | self.addEdge(caller, callee) 841 | self.edgeColor[self.edgeTupleToId[(caller, callee)]] = self.DIRECT 842 | self.edgeCondition[self.edgeTupleToId[(caller, callee)]] = condition 843 | else: 844 | self.logger.warning("Graph: Skipping line starting with #: %s", inputLine) 845 | inputLine = inputFile.readline() 846 | inputFile.close() 847 | 848 | def printAllPaths(self, startNode, endNode, limit=True): 849 | visitedNodes = dict() 850 | allPaths = set() 851 | tmpSet = set() 852 | startNode = startNode.strip() 853 | endNode = endNode.strip() 854 | for node in self.allNodes: 855 | if ( node == endNode ): 856 | self.logger.debug("node == endnode") 857 | node = node.strip() 858 | visitedNodes[node] = False 859 | visitedNodes[startNode] = True 860 | 861 | if ( limit ): 862 | self.printPath(startNode, endNode, "", visitedNodes, tmpSet, allPaths) 863 | self.logger.info("3nd level paths: %s", str(tmpSet)) 864 | else: 865 | self.printPath(startNode, endNode, "", visitedNodes, None, allPaths) 866 | return allPaths 867 | 868 | def printPath(self, startNode, endNode, path, visitedNodes, limitedPaths=None, allPaths=None): 869 | newPath = path + "->" + startNode 870 | tmpStr = newPath 871 | secondDepthIndex = util.findNthOccurence(tmpStr, "->", 3) 872 | if ( limitedPaths != None and tmpStr[:secondDepthIndex] in limitedPaths ): 873 | return 874 | visitedNodes[startNode] = True 875 | currentNodeList = self.adjGraph.get(startNode, list()) 876 | self.logger.debug("%s->", startNode) 877 | for node in currentNodeList: 878 | self.logger.debug(" %s", node) 879 | node = node.strip() 880 | if ( not visitedNodes[node] and node != endNode ): 881 | self.printPath(node, endNode, newPath, visitedNodes, limitedPaths, allPaths) 882 | elif ( node == endNode ): 883 | if ( limitedPaths != None): 884 | tmpStr = newPath + "->" + node 885 | secondDepthIndex = util.findNthOccurence(tmpStr, "->", 3) 886 | limitedPaths.add(tmpStr[:secondDepthIndex]) 887 | self.logger.info("Adding %s to set", tmpStr[:secondDepthIndex]) 888 | print ( newPath + "->" + node ) 889 | allPaths.add(newPath + "->" + node) 890 | else: 891 | print ( newPath + "->" + node ) 892 | allPaths.add(newPath + "->" + node) 893 | 894 | #visitedNodes[startNode] = False 895 | 896 | def toDotCfg(self, outputPath, nodes=None): 897 | dotFileStr = "digraph \"Call Graph\" { label=\"Call Graph\"; \n" 898 | nodeStr = "{} [style=filled,color={},shape=record,shape=circle,label=\"{}\"];\n" 899 | edgeStr = "{} -> {}[color=black];\n" 900 | outputFile = open(outputPath, 'w') 901 | outputFile.write(dotFileStr) 902 | 903 | if ( nodes == None ): 904 | self.extractStartingNodes() 905 | nodes = self.startingNodes 906 | 907 | for node in nodes: 908 | node = node.strip() 909 | if ( node != "" ): 910 | nodeFinalStr = nodeStr.format(node, self.nodeColor.get(node, self.INITIAL), node) 911 | outputFile.write(nodeFinalStr) 912 | for callee in self.adjGraph.get(node, list()): 913 | if ( callee in nodes and self.nodeColor.get(callee, self.INITIAL) == self.VISITED): 914 | edgeFinalStr = edgeStr.format(node, callee) 915 | outputFile.write(edgeFinalStr) 916 | outputFile.write("}\n") 917 | outputFile.close() 918 | 919 | def setNodeColorToVisited(self, node): 920 | self.nodeColor[node] = self.VISITED 921 | 922 | def getNodeColor(self, node): 923 | return self.nodeColor.get(node, "") 924 | 925 | def getEdgeColor(self, caller, callee): 926 | if ( not self.edgeTupleToId.get((caller, callee), None) ): 927 | self.logger.error("Requested edge: %s -> %s doesn't exist!", caller, callee) 928 | return None 929 | return self.getEdgeColorById(self.edgeTupleToId[(caller, callee)]) 930 | 931 | def getEdgeColorById(self, edgeId): 932 | return self.edgeColor[edgeId] 933 | 934 | def getEdgeType(self, caller, callee): 935 | if ( not self.edgeTupleToId.get((caller, callee), None) ): 936 | self.logger.error("Requested edge: %s -> %s doesn't exist!", caller, callee) 937 | return None 938 | return self.getEdgeTypeById(self.edgeTupleToId[(caller, callee)]) 939 | 940 | def getEdgeTypeById(self, edgeId): 941 | return self.edgeColor[edgeId] 942 | 943 | def dumpToFile(self, filePath): 944 | outputFile = open(filePath, 'w') 945 | for srcNode, nodeList in self.adjGraph.items(): 946 | for dstNode in nodeList: 947 | outputFile.write(srcNode + "->" + dstNode + "\n") 948 | outputFile.flush() 949 | outputFile.close() 950 | 951 | -------------------------------------------------------------------------------- /src/python-utils/syscall.py: -------------------------------------------------------------------------------- 1 | import os, sys, subprocess, signal 2 | import util 3 | 4 | class Syscall(): 5 | """ 6 | This class can be used to create a graph and run DFS and BFS on it 7 | """ 8 | def __init__(self, logger): 9 | self.logger = logger 10 | self.syscallMap = dict() #Num->Name 11 | self.syscallInverseMap = dict() #Name->Num 12 | 13 | def createMap(self): 14 | mapCmd = 'awk \'BEGIN { print "#include " } /p_syscall_meta/ { syscall = substr($NF, 19); printf "syscalls[SYS_%s] = \\"%s\\";\\n", syscall, syscall }\' /proc/kallsyms | sort -u | gcc -E -P -' 15 | proc = subprocess.Popen([mapCmd], shell=True, stdout=subprocess.PIPE) 16 | (out, err) = proc.communicate() 17 | splittedOut = out.splitlines() 18 | self.logger.debug("Syscall map count found: %d", len(splittedOut)) 19 | for outLineObj in splittedOut: 20 | # syscalls[137] = "statfs"; 21 | outLine = str(outLineObj.decode("utf-8")) 22 | leftHand = outLine.split(" = ")[0] 23 | syscallNum = leftHand[9:-1] 24 | rightHand = outLine.split(" = ")[1] 25 | syscallName = rightHand[1:-2] 26 | try: 27 | self.syscallMap[int(syscallNum)] = syscallName.strip() 28 | self.syscallInverseMap[syscallName.strip()] = int(syscallNum) 29 | except: 30 | self.logger.debug("Syscall Number isn't integer: %s", syscallNum) 31 | continue 32 | return self.syscallMap 33 | 34 | def getInverseMap(self): 35 | return self.syscallInverseMap 36 | 37 | def createMapWithAuditd(self): 38 | mapCmd = "ausyscall --dump" 39 | returncode, out, err = util.runCommand(mapCmd) 40 | if ( returncode != 0 ): 41 | self.logger.error("Error creating syscall map using ausyscall: %s", err) 42 | return None 43 | splittedOut = out.splitlines() 44 | self.logger.debug("Auditd Syscall map count found: %d", len(splittedOut)) 45 | for outLineObj in splittedOut[1:]: 46 | outLine = str(outLineObj.decode("utf-8")) 47 | syscallNum = outLine.split()[0] 48 | syscallName = outLine.split()[1] 49 | try: 50 | self.syscallMap[int(syscallNum)] = syscallName.strip() 51 | self.syscallInverseMap[syscallName.strip()] = int(syscallNum) 52 | except: 53 | self.logger.debug("Syscall Number isn't integer: %s", syscallNum) 54 | continue 55 | return self.syscallMap 56 | 57 | def findDiff(self, dict1, dict2): 58 | for key, value in dict1.items(): 59 | if ( dict2[key] != value ): 60 | self.logger.debug("Syscall map different for: %d, %s and %s", key, value, dict2[key]) 61 | else: 62 | self.logger.debug("Num to value is the same for %d to %s", key, value) 63 | 64 | #awk 'BEGIN { print "#include " } /p_syscall_meta/ { syscall = substr($NF, 19); printf "syscalls[SYS_%s] = \"%s\";\n", syscall, syscall }' /proc/kallsyms | sort -u | gcc -E -P - 65 | -------------------------------------------------------------------------------- /src/python-utils/syscallToCapabilityMapping.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import util 4 | import re 5 | from collections import ChainMap 6 | 7 | class Mappings: 8 | """ 9 | This class is used to find required capability 10 | """ 11 | def __init__(self, syscallNameList): 12 | self.syscallNameList = syscallNameList 13 | 14 | def requiredCapabilities(self): 15 | capList = set() 16 | syscallResponsibleForSysAdmin = [] 17 | syscallList = self.syscallNameList 18 | for sysCall in syscallList: 19 | caps = self.sysToCapMap(sysCall) 20 | if caps != None: 21 | for capName, value in caps: 22 | if capName == "CAP_SYS_ADMIN": 23 | syscallResponsibleForSysAdmin.append(sysCall) 24 | capList.add(capName) 25 | 26 | #print('\nsyscall responsible for SYS_ADMIN:', syscallResponsibleForSysAdmin) 27 | capListFinal = set() 28 | 29 | for capName in capList: 30 | parentCap = self.capHierarchy(capName) 31 | if parentCap != None: 32 | if parentCap not in capList: 33 | capListFinal.add(capName) 34 | else: 35 | capListFinal.add(capName) 36 | #print('Final-----',capListFinal) 37 | 38 | return capListFinal, syscallResponsibleForSysAdmin 39 | 40 | def sysToCapMap(self, sysCall): 41 | d = {"ioctl": [ 42 | ('CAP_SYS_ADMIN',0), 43 | ('CAP_FOWNER',0), 44 | ('CAP_KILL',0), 45 | ('CAP_LINUX_IMMUTABLE',0), 46 | ('CAP_NET_ADMIN',0), 47 | ('CAP_SYS_RESOURCE',0), 48 | ('CAP_SYS_TTY_CONFIG',0) 49 | ], 50 | "open" : [ 51 | ('CAP_FOWNER',0), 52 | ('CAP_DAC_OVERRIDE',0), 53 | ('CAP_DAC_READ_SEARCH',0) 54 | ], 55 | "openat" : [ 56 | ('CAP_FOWNER',0), 57 | ('CAP_DAC_OVERRIDE',0), 58 | ('CAP_DAC_READ_SEARCH',0) 59 | ], 60 | 61 | "openat2" : [ 62 | ('CAP_FOWNER',0), 63 | ('CAP_DAC_OVERRIDE',0), 64 | ('CAP_DAC_READ_SEARCH',0) 65 | ], 66 | 67 | "open_by_handle_at": [ 68 | ('CAP_DAC_READ_SEARCH',1) 69 | ], 70 | "chmod": [ 71 | ('CAP_FOWNER',0), 72 | ('CAP_FSETID',0) 73 | ], 74 | "fchmod": [ 75 | ('CAP_FOWNER',0), 76 | ('CAP_FSETID',0) 77 | ], 78 | "fchmodat": [ 79 | ('CAP_FOWNER',0), 80 | ('CAP_FSETID',0) 81 | ], 82 | "kill": [ 83 | ('CAP_KILL',1) 84 | ], 85 | "sendto":[ 86 | ('CAP_AUDIT_CONTROL',0),('CAP_SYS_RESOURCE',0), 87 | ('CAP_AUDIT_WRITE',0) 88 | ], 89 | "send":[ 90 | ('CAP_AUDIT_CONTROL',0),('CAP_SYS_RESOURCE',0) 91 | ], 92 | "sendmsg":[ 93 | ('CAP_AUDIT_CONTROL',0),('CAP_SYS_RESOURCE',0) 94 | ], 95 | "recvmsg":[('CAP_AUDIT_CONTROL',0)], 96 | "recv":[('CAP_AUDIT_CONTROL',0)], 97 | "recvfrom":[('CAP_AUDIT_CONTROL',0)], 98 | "epoll_ctl":[('CAP_BLOCK_SUSPEND',0)], 99 | "bpf":[('CAP_SYS_ADMIN',1)], 100 | "clone":[('CAP_SYS_ADMIN',0), ('CAP_SETFCAP',0)], 101 | "perf_event_open":[('CAP_SYS_ADMIN',1)], 102 | "syslog":[('CAP_SYSLOG',1)], 103 | "mount":[('CAP_SYS_ADMIN',1)], 104 | "umount":[('CAP_SYS_ADMIN',1)], 105 | "pivot_root":[('CAP_SYS_ADMIN',1)], 106 | "swapon":[('CAP_SYS_ADMIN',1)], 107 | "swapoff":[('CAP_SYS_ADMIN',1)], 108 | "sethostname":[('CAP_SYS_ADMIN',1)], 109 | "setdomainname":[('CAP_SYS_ADMIN',1)], 110 | "quotactl":[('CAP_SYS_ADMIN',0)], 111 | "vm86":[('CAP_SYS_ADMIN',1)], 112 | "lookup_dcookie":[('CAP_SYS_ADMIN',1)], 113 | "io_submit":[('CAP_SYS_ADMIN',1)], 114 | "msgctl":[('CAP_SYS_ADMIN',0), ('CAP_IPC_OWNER',0),('CAP_SYS_RESOURCE',0)], 115 | "setrlimit":[('CAP_SYS_ADMIN',0),('CAP_SYS_RESOURCE',0)], 116 | "shmctl":[('CAP_SYS_ADMIN',0),('CAP_IPC_OWNER',0)], 117 | "ioprio_set":[('CAP_SYS_ADMIN',0), ('CAP_SYS_NICE',0)], 118 | "setns":[('CAP_SYS_ADMIN',1), ('CAP_SYS_CHROOT',0)], 119 | "fanotify_init":[('CAP_SYS_ADMIN',1)], 120 | "keyctl":[('CAP_SYS_ADMIN',0),('CAP_SETUID',0)], 121 | "madvise":[('CAP_SYS_ADMIN',0)], 122 | "nfsservctl":[('CAP_SYS_ADMIN',1)], 123 | "bdflush":[('CAP_SYS_ADMIN',1)], 124 | "unshare":[('CAP_SYS_ADMIN',1)], 125 | "seccomp":[('CAP_SYS_ADMIN',0)], 126 | "ptrace":[('CAP_SYS_ADMIN',0), ('CAP_SYS_PTRACE',1)], 127 | "chown":[('CAP_CHOWN',1)], 128 | "fchown":[('CAP_CHOWN',1)], 129 | "lchown":[('CAP_CHOWN',1)], 130 | "fchownat":[('CAP_CHOWN',1)], 131 | "linkat":[('CAP_DAC_READ_SEARCH',0)], 132 | "utime":[('CAP_DAC_OVERRIDE',0), ('CAP_FOWNER',0)], 133 | "utimensat":[('CAP_DAC_OVERRIDE',0), ('CAP_FOWNER',0)], 134 | "utimes":[('CAP_DAC_OVERRIDE',0), ('CAP_FOWNER',0)], 135 | "unlink":[('CAP_FOWNER',0)], 136 | "unlinkat":[('CAP_FOWNER',0)], 137 | "fcntl":[('CAP_FOWNER',0), ('CAP_LEASE',0), ('CAP_SYS_RESOURCE',0)], 138 | "rename":[('CAP_FOWNER',0)], 139 | "renameat":[('CAP_FOWNER',0)], 140 | "renameat2":[('CAP_FOWNER',0),('CAP_MKNOD',0)], 141 | "rmdir":[('CAP_FOWNER',0)], 142 | "mlock":[('CAP_IPC_LOCK',1)], 143 | "mlock2":[('CAP_IPC_LOCK',1)], 144 | "mlockall":[('CAP_IPC_LOCK',1)], 145 | "mmap":[('CAP_IPC_LOCK',0)], 146 | "memfd_create":[('CAP_IPC_LOCK',0)], 147 | "msgget":[('CAP_IPC_OWNER',0)], 148 | "msgrcv":[('CAP_IPC_OWNER',0)], 149 | "semop":[('CAP_IPC_OWNER',1)], 150 | "semtimedop":[('CAP_IPC_OWNER',1)], 151 | "shmat":[('CAP_IPC_OWNER',1)], 152 | "shmdt":[('CAP_IPC_OWNER',1)], 153 | "msgsnd":[('CAP_IPC_OWNER',0)], 154 | "mknod":[('CAP_MKNOD',0)], 155 | "mknodat":[('CAP_MKNOD',0)], 156 | "setsockopt":[('CAP_NET_ADMIN',0)], 157 | "bind":[('CAP_NET_BIND_SERVICE',0), ('CAP_AUDIT_READ',0)], 158 | "socket":[('CAP_NET_RAW',0), ('CAP_MAC_OVERRIDE',0)], 159 | "setgroups":[('CAP_SETGID',1)], 160 | "setfsgid":[('CAP_SETGID',1)], 161 | "setgid":[('CAP_SETGID',0)], 162 | "setregid":[('CAP_SETGID',0)], 163 | "setresgid":[('CAP_SETGID',0)], 164 | "prctl":[('CAP_SETPCAP',0), ('CAP_SYS_RESOURCE',0), ('CAP_SYS_ADMIN',0)], 165 | "capset":[('CAP_SETPCAP',1)], 166 | "setuid":[('CAP_SETUID',0)], 167 | "setreuid":[('CAP_SETUID',1)], 168 | "setresuid":[('CAP_SETUID',1)], 169 | "setfsuid":[('CAP_SETUID',1)], 170 | "reboot":[('CAP_SYS_BOOT',1)], 171 | "kexec_load":[('CAP_SYS_BOOT',1)], 172 | "kexec_file_load":[('CAP_SYS_BOOT',1)], 173 | "chroot":[('CAP_SYS_CHROOT',1)], 174 | "nice":[('CAP_SYS_NICE',0)], 175 | "setpriority":[('CAP_SYS_NICE',0)], 176 | "sched_setscheduler":[('CAP_SYS_NICE',1)], 177 | "sched_setparam":[('CAP_SYS_NICE',1)], 178 | "sched_setattr":[('CAP_SYS_NICE',1)], 179 | "sched_setaffinity":[('CAP_SYS_NICE',0)], 180 | "migrate_pages":[('CAP_SYS_NICE',1)], 181 | "move_pages":[('CAP_SYS_NICE',0)], 182 | "spu_create":[('CAP_SYS_NICE',0)], 183 | "mbind":[('CAP_SYS_NICE',0)], 184 | "acct":[('CAP_SYS_PACCT',1)], 185 | "set_robust_list":[('CAP_SYS_PTRACE',1)], 186 | "process_vm_readv":[('CAP_SYS_PTRACE',1)], 187 | "process_vm_writev":[('CAP_SYS_PTRACE',1)], 188 | "userfaultfd":[('CAP_SYS_PTRACE',1)], 189 | "kcmp":[('CAP_SYS_PTRACE',1)], 190 | "iopl":[('CAP_SYS_RAWIO',1)], 191 | "ioperm":[('CAP_SYS_RAWIO',0)], 192 | "prlimit":[('CAP_SYS_RESOURCE',0)], 193 | "mq_open":[('CAP_SYS_RESOURCE',0)], 194 | "settimeofday":[('CAP_SYS_TIME',1)], 195 | "stime":[('CAP_SYS_TIME',1)], 196 | "adjtimex":[('CAP_SYS_TIME',0)], 197 | "clock_adjtime":[('CAP_SYS_TIME',0)], 198 | "ntp_adjtime":[('CAP_SYS_TIME',0)], 199 | "vhangup":[('CAP_SYS_TTY_CONFIG',1)], 200 | "timer_create":[('CAP_WAKE_ALARM',0)], 201 | "timerfd_settime":[('CAP_WAKE_ALARM',0)], 202 | "finit_module":[('CAP_SYS_MODULE'),0], 203 | "init_module":[('CAP_SYS_MODULE',0)], 204 | "create_module":[('CAP_SYS_MODULE',0)], 205 | "delete_module":[('CAP_SYS_MODULE',0)], 206 | "setxattr":[('CAP_MAC_ADMIN',0)], 207 | "lsetxattr":[('CAP_MAC_ADMIN',0)], 208 | "fsetxattr":[('CAP_MAC_ADMIN',0)] 209 | } 210 | 211 | caps = d.get(sysCall) 212 | return caps 213 | 214 | def capHierarchy (self, cap): 215 | CH = {"CAP_AUDIT_READ": "CAP_AUDIT_CONTROL", 216 | "CAP_AUDIT_WRITE":"CAP_AUDIT_CONTROL", 217 | "CAP_BPF":"CAP_SYS_ADMIN", 218 | "CAP_CHECKPOINT_RESTORE":"CAP_SYS_ADMIN", 219 | "CAP_PERFMON":"CAP_SYS_ADMIN", 220 | "CAP_SYSLOG":"CAP_SYS_ADMIN" 221 | } 222 | parent = CH.get(cap) 223 | return parent 224 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /src/python-utils/sysfilter.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import util 4 | import re 5 | import subprocess 6 | 7 | class sysFilter: 8 | def __init__(self, binaryPath, sysfilterPath, logger): 9 | self.binaryPath = binaryPath 10 | self.sysfilterPath = sysfilterPath 11 | self.logger = logger 12 | 13 | def getSyscalls(self): 14 | binPath = self.binaryPath 15 | syscallList = self.runSysfilter(binPath) 16 | return syscallList 17 | 18 | def runSysfilter(self, binPath): 19 | if (not os.path.exists(self.sysfilterPath)): 20 | self.logger.error("Sysfilter path %s doesn't exist", self.sysfilterPath) 21 | cmd = self.sysfilterPath + " " + binPath 22 | returnCode, out, Er = self.runCommand(cmd) 23 | if returnCode!=0: 24 | self.logger.error("Unable to extract system call using sysfilter for binary %s", self.binaryPath) 25 | return [-1] 26 | out = out[1:-2] 27 | out= [int(s) for s in out.split(',') if s.isdigit()] 28 | return out 29 | 30 | def runCommand(self,cmd): 31 | proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 32 | (out, err) = proc.communicate() 33 | outStr = str(out.decode("utf-8")) 34 | errStr = str(err.decode("utf-8")) 35 | return (proc.returncode, outStr, errStr) 36 | -------------------------------------------------------------------------------- /src/python-utils/util.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | import subprocess 3 | import logging 4 | 5 | def runCommand(cmd): 6 | proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 7 | (out, err) = proc.communicate() 8 | outStr = str(out.decode("utf-8")) 9 | errStr = str(err.decode("utf-8")) 10 | return (proc.returncode, outStr, errStr) 11 | 12 | 13 | def readLibrariesWithLdd(elfPath): 14 | cmd = "ldd " + elfPath 15 | (returncode, out, err) = runCommand(cmd) 16 | if ( returncode != 0 ): 17 | logging.critical("ldd error for %s", elfPath) 18 | return dict() 19 | 20 | loadings = dict() 21 | 22 | # Read all imports and exports per each library 23 | for lib in out.split('\n\t'): 24 | # Exclude a virtual dynamically linked shared object(VDSO) and a dynamic loader(DL) 25 | if 'linux-vdso' not in lib and 'ld-linux' not in lib: 26 | try: 27 | libname, libpath = lib.split(" => ") 28 | libname = libname.split(".")[0] # Library name only w/o version 29 | libpath = libpath.split("(")[0].strip() # Discard the address 30 | if libpath!='not found': 31 | loadings[libname] = libpath 32 | 33 | except: 34 | logging.critical("Parsing Error with %s outcome!" % ("ldd")) 35 | 36 | return loadings 37 | 38 | 39 | def extractImportedFunctions(fileName, libcOnly=False): #removed logger from the parameter 40 | if ( libcOnly ): 41 | cmd = "objdump -T " + fileName + " | grep \"UND\" | grep -i libc | awk '{print $5,$6}'" 42 | else: 43 | cmd = "objdump -T " + fileName + " | grep \"UND\" | awk '{print $5,$6}'" 44 | """ 45 | if ( logger ): 46 | logger.debug("Running command: %s", cmd) 47 | """ 48 | returncode, out, err = runCommand(cmd) 49 | if ( returncode != 0 ): 50 | if logger: 51 | logger.error("Error in extracting imported functions: %s", err) 52 | return None 53 | functionList = [] 54 | splittedOut = out.splitlines() 55 | for line in splittedOut: 56 | if ( len(line.split()) > 1 ): 57 | line = line.split()[1] 58 | functionList.append(line.strip()) 59 | #print ("File ",fileName, "\n"); 60 | #print ("Functionlist: ",functionList,"\n"); 61 | return functionList 62 | 63 | def setLogPath(logPath, options): 64 | """ 65 | Set the property of the logger: path, config, and format 66 | :param logPath: 67 | :return: 68 | """ 69 | if os.path.exists(logPath): 70 | os.remove(logPath) 71 | 72 | rootLogger = logging.getLogger("coverage") 73 | if options.debug: 74 | logging.basicConfig(filename=logPath, level=logging.DEBUG) 75 | rootLogger.setLevel(logging.DEBUG) 76 | else: 77 | logging.basicConfig(filename=logPath, level=logging.INFO) 78 | rootLogger.setLevel(logging.INFO) 79 | 80 | consoleHandler = logging.StreamHandler() 81 | rootLogger.addHandler(consoleHandler) 82 | return rootLogger 83 | -------------------------------------------------------------------------------- /website/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | About Decap 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 |
17 |

About Decap

18 | 19 |
20 |

Overview

21 |
22 | 23 |
24 |

Decap is a binary code analysis tool that automatically deprivileges programs by identifying the subset of 25 | capabilities they require based on the system calls they may invoke. This is made possible by our systematic effort in deriving a complete 26 | mapping between all Linux system calls related to privileged operations and the corresponding capabilities on which they depend. We suggest reading our 27 | paper for more details. 28 |

29 | Decap overview figure 32 |
33 |
34 | 35 | 36 | 37 |
38 |

System call - Capability Mapping

39 |
40 | 41 |
42 |

We performed a thorough investigation 43 | of all available Linux capabilities and the system calls they affect, 44 | to derive a detailed and complete mapping between all system calls 45 | related to privileged operations and their respective capabilities. The complete mapping can be found 46 | here, 47 | and a visual representation of the mapping is available 48 | here. 49 |

50 | Mapping figure 53 |
54 |
55 | 56 | 57 |
58 |

System call Identification and Capability Enforcement

59 |
60 | 61 |
62 |

The first step in deprivileging a setuid program is to identify its 63 | required system calls. Decap performs static binary code analysis of a target program and its libraries to extract the set 64 | of all possible system calls it may invoke, and then to derive and enforce the corresponding set of required capabilities. Decap relies on both 65 | Confine 66 | and 67 | Sysfilter 68 | to identify the system calls required by a given application. Also, CAP_SYS_ADMIN capability is 69 | required by multiple system calls but only when invoked with a 70 | limited set of specific argument values. Therefore, once the set of 71 | required system calls has been extracted, Decap performs argumentlevel 72 | analysis for those system calls that conditionally require 73 | CAP_SYS_ADMIN, and attempts to extract the concrete values passed 74 | to the arguments that determine whether CAP_SYS_ADMIN is required, across all their call sites. 75 | Decap identifies the required capabilities for an application based on the system call analysis and the 76 | previously generated mapping. Finally, it reduces the privileges by first deprivileging the target 77 | application entirely by removing its setuid bit, and then granting 78 | only the capabilities that the program actually requires. 79 | 80 |

81 |
82 |
83 | 84 |
85 |

Academic Publication

86 |
87 | 88 |
89 |

90 | Please use the following citation for Decap. 91 |

92 |
 93 | @inproceedings{decapraid22,
 94 | title = {Decap: Deprivileging Programs by Reducing Their Capabilities}, 
 95 | author = {Hasan, Md Mehedi and Ghavamnia, Seyedhamed and Polychronakis, Michalis}, 
 96 | booktitle = {Proceedings of the International Conference on Research in Attacks,
 97 |     Intrusions, and Defenses (RAID)}, 
 98 | year = {2022} 
 99 | }
100 | 
101 |
102 | 103 |
104 | 105 | 106 | -------------------------------------------------------------------------------- /website/images/DecapOverview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasanmdme/decap/6f781a02827035db228d9ad868c15f6ee4667626/website/images/DecapOverview.png -------------------------------------------------------------------------------- /website/images/mapping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasanmdme/decap/6f781a02827035db228d9ad868c15f6ee4667626/website/images/mapping.png -------------------------------------------------------------------------------- /website/mapping.js: -------------------------------------------------------------------------------- 1 | // 2 | // Generated by the Exaile Playlist Analyzer plugin. 3 | // (C) 2014 Dustin Spicuzza 4 | // 5 | // This work is licensed under the Creative Commons Attribution 4.0 6 | // International License. To view a copy of this license, visit 7 | // http://creativecommons.org/licenses/by/4.0/. 8 | // 9 | // Inspired by http://www.findtheconversation.com/concept-map/ 10 | // Loosely based on https://bl.ocks.org/mbostock/4063550 11 | // 12 | var data = []; 13 | data = [['cap_net_broadcast', []], ['cap_syslog', ['syslog']], ['cap_sys_pacct', ['acct']], ['cap_block_suspend', ['epoll_ctl']], ['cap_net_bind_service', ['bind']], ['cap_net_raw', ['socket']], ['cap_linux_immutable', ['ioctl']],['cap_lease', ['fcntl']], ['cap_audit_write', ['sendto']], ['cap_setfcap', ['clone']], ['cap_mac_override', ['socket']], ['cap_audit_read', ['bind']], ['cap_bpf', ['bpf']], ['cap_perfmon', ['perf_event_open']], ['cap_checkpoint_restore', ['clone']], ['cap_kill', ['ioctl', 'kill']], ['cap_setpcap', ['prctl', 'capset']], ['cap_net_admin', ['ioctl', 'setsockopt']], ['cap_sys_rawio', ['iopl', 'ioperm']], ['cap_sys_chroot', ['setns', 'chroot']], ['cap_sys_tty_config', ['ioctl', 'vhangup']], ['cap_wake_alarm', ['timer_create', 'timerfd_settime']], ['cap_fsetid', ['chmod', 'fchmod', 'fchmodat']], ['cap_sys_boot', ['reboot', 'kexec_load', 'kexec_file_load']], ['cap_mknod', ['renameat2', 'mknod', 'mknodat']], ['cap_mac_admin', ['setxattr', 'lsetxattr', 'fsetxattr']], ['cap_chown', ['chown', 'fchown', 'lchown', 'fchownat']], ['cap_sys_module', ['finit_module', 'init_module', 'create_module', 'delete_module']], ['cap_dac_read_search', ['open', 'openat', 'openat2', 'open_by_handle_at', 'linkat']], ['cap_setgid', ['setgroups', 'setfsgid', 'setgid', 'setregid', 'setresgid']], ['cap_setuid', ['keyctl', 'setuid', 'setreuid', 'setresuid', 'setfsuid']], ['cap_ipc_lock', ['mlock', 'mlock2', 'mlockall', 'mmap', 'memfd_create']], ['cap_sys_time', ['settimeofday', 'stime', 'adjtimex', 'clock_adjtime', 'ntp_adjtime']], ['cap_dac_override', ['open', 'openat', 'openat2', 'utime', 'utimensat', 'utimes']], ['cap_sys_ptrace', ['ptrace', 'set_robust_list', 'process_vm_readv', 'process_vm_writev', 'userfaultfd', 'kcmp']], ['cap_audit_control', ['sendto', 'send', 'sendmsg', 'recvmsg', 'recv', 'recvfrom']], ['cap_ipc_owner', ['msgctl', 'shmctl', 'msgget', 'msgrcv', 'semop', 'semtimedop', 'shmat', 'shmdt', 'msgsnd']], ['cap_sys_resource', ['ioctl', 'sendto', 'send', 'sendmsg', 'msgctl', 'setrlimit', 'fcntl', 'prctl', 'prlimit', 'mq_open']], ['cap_sys_nice', ['ioprio_set', 'nice', 'setpriority', 'sched_setscheduler', 'sched_setparam', 'sched_setattr', 'sched_setaffinity', 'migrate_pages', 'move_pages', 'spu_create', 'mbind']], ['cap_fowner', ['ioctl', 'open', 'openat', 'openat2', 'chmod', 'fchmod', 'fchmodat', 'utime', 'utimensat', 'utimes', 'unlink', 'unlinkat', 'fcntl', 'rename', 'renameat', 'renameat2', 'rmdir']], ['cap_sys_admin', ['ioctl', 'bpf', 'clone', 'perf_event_open', 'mount', 'umount', 'pivot_root', 'swapon', 'swapoff', 'sethostname', 'setdomainname', 'quotactl', 'vm86', 'lookup_dcookie', 'io_submit', 'msgctl', 'setrlimit', 'shmctl', 'ioprio_set', 'setns', 'fanotify_init', 'keyctl', 'madvise', 'nfsservctl', 'bdflush', 'unshare', 'seccomp', 'ptrace', 'prctl']]]; 14 | 15 | //data = [[]] 16 | // transform the data into a useful representation 17 | // 1 is inner, 2, is outer 18 | 19 | // need: inner, outer, links 20 | // 21 | // inner: 22 | // links: { inner: outer: } 23 | 24 | 25 | var outer = d3.map(); 26 | console.log('outer =', outer) 27 | var inner = []; 28 | var links = []; 29 | 30 | var outerId = [0]; 31 | //var extent = d3.extent(data, d=>d.value) 32 | //console.log('extent', extent) 33 | data.forEach(function(d){ 34 | //console.log('data d=', d) 35 | if (d == null) 36 | return; 37 | 38 | i = { id: 'i' + inner.length, name: d[0], related_links: [] }; 39 | i.related_nodes = [i.id]; 40 | inner.push(i); 41 | 42 | if (!Array.isArray(d[1])) 43 | d[1] = [d[1]]; 44 | 45 | d[1].forEach(function(d1){ 46 | 47 | o = outer.get(d1); 48 | 49 | if (o == null) 50 | { 51 | o = { name: d1, id: 'o' + outerId[0], related_links: [] }; 52 | o.related_nodes = [o.id]; 53 | outerId[0] = outerId[0] + 1; 54 | 55 | outer.set(d1, o); 56 | } 57 | 58 | // create the links 59 | l = { id: 'l-' + i.id + '-' + o.id, inner: i, outer: o } 60 | links.push(l); 61 | 62 | // and the relationships 63 | i.related_nodes.push(o.id); 64 | i.related_links.push(l.id); 65 | o.related_nodes.push(i.id); 66 | o.related_links.push(l.id); 67 | }); 68 | }); 69 | 70 | 71 | data = { 72 | inner: inner, 73 | outer: outer.values(), 74 | links: links 75 | } 76 | console.log('data = ',data) 77 | // sort the data -- TODO: have multiple sort options 78 | outer = data.outer; 79 | data.outer = Array(outer.length); 80 | //console.log() 81 | var i1 = 0; 82 | var i2 = outer.length - 1; 83 | 84 | for (var i = 0; i < data.outer.length; ++i) 85 | { 86 | if (i % 2 == 1) 87 | data.outer[i2--] = outer[i]; 88 | else 89 | data.outer[i1++] = outer[i]; 90 | } 91 | 92 | console.log(data.outer.reduce(function(a,b) { return a + b.related_links.length; }, 0) / data.outer.length); 93 | /* 94 | var caps = ['cap_sys_admin', 'cap_net_raw', 'cap_setuid'] 95 | // from d3 colorbrewer: 96 | // This product includes color specifications and designs developed by Cynthia Brewer (http://colorbrewer.org/). 97 | var colors = ["#a50026","#d73027","#f46d43","#fdae61","#fee090","#ffffbf","#e0f3f8","#abd9e9","#74add1","#4575b4","#313695"] 98 | var color = d3.scale.linear() 99 | .domain([10, 220]) 100 | //.range([colors.length-1, 0]) 101 | .range(['#ff3399']) 102 | //.clamp(true); 103 | //console.log('color', color) 104 | var color1 = d3.scale.ordinal() 105 | .domain(caps) 106 | .range(['yellow', 'blue', 'green']); 107 | */ 108 | var diameter = 1050; 109 | var rect_width = 140; 110 | var rect_height = 16; 111 | 112 | var link_width = "1px"; 113 | 114 | var il = data.inner.length; 115 | var ol = data.outer.length; 116 | console.log("inner length and outer length", il, ol) 117 | var inner_y = d3.scale.linear() 118 | .domain([0, il]) 119 | .range([-(il * rect_height)/2, (il * rect_height)/2]); 120 | 121 | mid = (data.outer.length/2.0) 122 | console.log("here outer length mid", mid) 123 | var outer_x = d3.scale.linear() 124 | .domain([0, mid, mid, data.outer.length]) 125 | .range([15, 170, 190 ,350]); //mapping in the circle angle in degree..total 360 degree 126 | 127 | var outer_y = d3.scale.linear() 128 | .domain([0, data.outer.length]) 129 | .range([0, diameter / 2 - 120]); 130 | 131 | 132 | // setup positioning 133 | data.outer = data.outer.map(function(d, i) { 134 | d.x = outer_x(i); 135 | d.y = diameter/2.5;//changed from 3 to 2.5 136 | //d.y = outer_y(i) 137 | return d; 138 | }); 139 | 140 | data.inner = data.inner.map(function(d, i) { 141 | d.x = -(rect_width / 2); 142 | d.y = inner_y(i); 143 | return d; 144 | }); 145 | 146 | 147 | function get_color(name) 148 | { 149 | console.log('color name', name) 150 | //name is string value for capability, need to convert this to decimal quivalent then pass it to next 151 | var c = Math.round(color(name)); 152 | if (isNaN(c)) 153 | //return '#dddddd'; // fallback color 154 | return '#1f77b4' 155 | 156 | return colors[c]; 157 | } 158 | /* 159 | function get_color(name) 160 | { 161 | console.log('color name', name) 162 | //name is string value for capability, need to convert this to decimal quivalent then pass it to next 163 | var c = Math.round(color(name)); 164 | if (isNaN(c)) 165 | return '#dddddd'; // fallback color 166 | 167 | return colors[c]; 168 | }*/ 169 | 170 | // Can't just use d3.svg.diagonal because one edge is in normal space, the 171 | // other edge is in radial space. Since we can't just ask d3 to do projection 172 | // of a single point, do it ourselves the same way d3 would do it. 173 | 174 | 175 | function projectX(x) 176 | { 177 | return ((x - 90) / 180 * Math.PI) - (Math.PI/2); 178 | } 179 | 180 | var diagonal = d3.svg.diagonal() 181 | .source(function(d) { return {"x": d.outer.y * Math.cos(projectX(d.outer.x)), 182 | "y": -d.outer.y * Math.sin(projectX(d.outer.x))}; }) 183 | .target(function(d) { return {"x": d.inner.y + rect_height/2, 184 | "y": d.outer.x > 180 ? d.inner.x : d.inner.x + rect_width}; }) 185 | .projection(function(d) { return [d.y, d.x]; }); 186 | 187 | var makefigure_x_pixeltop = 70;//mehedi 188 | var makefigure_x_pixelleft = 0;//mehedi 189 | var svg = d3.select("body").append("svg") 190 | .attr("width", diameter) 191 | .attr("height", diameter) 192 | .append("g") 193 | .attr("transform", "translate(" + (diameter-makefigure_x_pixelleft) / 2 + "," + (diameter-makefigure_x_pixeltop) / 2 + ")"); 194 | 195 | 196 | // links 197 | var link = svg.append('g').attr('class', 'links').selectAll(".link") 198 | .data(data.links) 199 | .enter().append('path') 200 | .attr('class', 'link') 201 | .attr('id', function(d) { return d.id }) 202 | .attr("d", diagonal) 203 | //.attr('stroke', function(d) { return get_color(d.inner.name); }) 204 | .attr('stroke', '#585239') 205 | .attr('stroke-width', link_width); 206 | 207 | // outer nodes 208 | 209 | var onode = svg.append('g').selectAll(".outer_node") 210 | .data(data.outer) 211 | .enter().append("g") 212 | .attr("class", "outer_node") 213 | .attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; }) 214 | .on("mouseover", mouseover) 215 | .on("mouseout", mouseout); 216 | 217 | onode.append("circle") 218 | .attr('id', function(d) { return d.id }) 219 | .style('cursor', "pointer") 220 | .attr("r", 4.5); 221 | 222 | onode.append("circle") 223 | .attr('r', 9) 224 | .style('cursor', "pointer") 225 | .attr('visibility', 'hidden'); 226 | 227 | onode.append("text") 228 | .attr('id', function(d) { return d.id + '-txt'; }) 229 | .attr("dy", ".31em") 230 | .style('cursor', "pointer") 231 | .attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; }) 232 | .attr("transform", function(d) { return d.x < 180 ? "translate(8)" : "rotate(180)translate(-8)"; }) 233 | .text(function(d) { return d.name; }); 234 | //.on("mouseover", mouseover) 235 | //.on("mouseout", mouseout); 236 | 237 | // inner nodes 238 | 239 | var inode = svg.append('g').selectAll(".inner_node") 240 | .data(data.inner) 241 | .enter().append("g") 242 | .attr("class", "inner_node") 243 | .attr("transform", function(d, i) { return "translate(" + d.x + "," + d.y + ")"}) 244 | .on("mouseover", mouseover) 245 | .on("mouseout", mouseout); 246 | 247 | inode.append('rect') 248 | .attr('width', rect_width) 249 | .attr('height', rect_height) 250 | .attr('id', function(d) { return d.id; }); 251 | //.attr('fill', function(d) { return get_color(d.name); }); 252 | 253 | inode.append("text") 254 | .attr('id', function(d) { return d.id + '-txt'; }) 255 | .attr('text-anchor', 'middle') 256 | .style('fill', 'white') 257 | .style('cursor', "pointer") 258 | .attr("transform", "translate(" + rect_width/2 + ", " + rect_height * .75 + ")") 259 | .text(function(d) { return d.name; }); 260 | 261 | // need to specify x/y/etc 262 | 263 | d3.select(self.frameElement).style("height", diameter-150 + "px"); 264 | 265 | function mouseover(d) 266 | { 267 | // bring to front 268 | //console.log('hei',o.related_nodes) 269 | console.log(d.related_nodes); 270 | d3.selectAll('.links .link').sort(function(a, b){ return d.related_links.indexOf(a.id); }); 271 | 272 | for (var i = 0; i < d.related_nodes.length; i++) 273 | { 274 | //console.log('each node',d.related_nodes[i]) 275 | d3.select('#' + d.related_nodes[i]).classed('highlight', true).style('cursor', "pointer"); 276 | if (d.related_nodes[i].includes('o')){ 277 | d3.select('#' + d.related_nodes[i] + '-txt').attr("font-weight", 'bold').style('fill', "#3498db"); 278 | } 279 | } 280 | 281 | for (var i = 0; i < d.related_links.length; i++) 282 | d3.select('#' + d.related_links[i]).attr('stroke-width', '4px').style('stroke', "#3498db");// 283 | //d3.select(this).style('stroke', 'green'); 284 | //d3.select('#' + d.related_links[i]).style('fill', "green"); //mmmmnhnhnhnhnh-------- 285 | } 286 | 287 | function mouseout(d) 288 | { 289 | for (var i = 0; i < d.related_nodes.length; i++) 290 | { 291 | d3.select('#' + d.related_nodes[i]).classed('highlight', false); 292 | if (d.related_nodes[i].includes('o')){ 293 | d3.select('#' + d.related_nodes[i] + '-txt').attr("font-weight", 'normal').style('fill', "black"); 294 | } 295 | } 296 | 297 | for (var i = 0; i < d.related_links.length; i++) 298 | d3.select('#' + d.related_links[i]).attr('stroke-width', link_width).style('stroke', "#585239"); 299 | //d3.select('#' + d.related_links[i]).attr('fill', 'green'); 300 | } 301 | 302 | -------------------------------------------------------------------------------- /website/stepbystepguide.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Decap Tutorial 10 | 11 | 12 | 14 | 15 |
16 |

Decap Step by Step Guide

17 |

18 | In this page we will walk through the steps required to generate a capability 19 | profile for the ping application. 20 |

21 | 22 |
23 |

Prerequisites

24 |
25 | 26 |
27 | 28 | 29 |

Decap is mainly developed in python. You will need to install the python version 3.

30 |
 31 | 	sudo apt update
 32 | 	sudo apt install -y python3
 33 |     sudo pip3 install pandas
 34 | 	
35 | 36 | As Decap uses sysfilter's system call extraction tool, you have to clone and build the sysfilter extraction tool from 37 | here. While running decap, you have to give the path of sysfilter_extract executable as a flag value. 38 | 39 |
40 | 41 |

System Validation

42 |

Now we will check that we are running the correct 43 | kernel version required for completing the hands-on exercise. We tested Decap on Ubuntu 18.04 with kernel v5.4

44 |
uname --kernel-release
45 |

This should print the kernel version which should be as follows:

46 |
5.4.0-48-lowlatency
47 |

It is critical that you see the correct Linux kernel version.

48 |
49 |

We can check the Ubuntu distribution using this command:

50 |
cat /etc/issue
51 |

This should print the Ubuntu version which should be as follows:

52 |
Ubuntu 18.04.5 LTS \n \l
53 |
54 |
55 | 56 |
57 |

Working with Decap

58 |
59 |
60 |

Working with Decap

61 |

1. Check where is binary ping

62 |
whereis -b ping
63 |

This should print the path of the binary like this:

64 |
ping: /bin/ping
65 | 66 |

2. Switch to the `src` directory.

67 |
cd src
68 | 69 |

3. Open a new file, name it as you like. We will use myexample.json in the following examples.

70 |
vim myexample.json
71 | 72 |

4. Copy the following text into the file and update the binary-path value from step 1.

73 |
 74 | {
 75 |     "ping": {
 76 |     	"binary-path": "/bin/ping"
 77 | 	}
 78 | }   	
 79 | 
80 |

5. Now run Decap:

81 |
sudo python3 decap.py --input [full-path-of-myexample.json] --sysfilterpath [full-path-of-sysfilter_extraction-executable]
82 |

The script will now start analyzing the binary for the required capability.

83 |
Starting analysis for binary: ping ...
 84 | 	  
85 | 86 |

System call extraction and argument analysis phase

87 |
Starting system call extraction ...
 88 |           Extracting system calls using Confine and performing argument analysis for the system calls responsible for CAP_SYS_ADMIN ...
 89 | Extracting system calls using Sysfilter ...
 90 | Generating final system call list ...
 91 | ... System call extraction done!
 92 | Total number of extracted system calls : 50
 93 | 	  
94 | 95 |

The script will now start finding the required capability based on extracted system calls.

96 |
Finding required capabilities for the extracted system calls ...
 97 | Checking if CAP_SYS_ADMIN is required based on the argument analysis ...
 98 | ... removing CAP_SYS_ADMIN
 99 | Total num of capabilties to add : 16
100 | Deprivileging setuid binary and enforcing only the required capabilities to the binary
101 | Done capability analysis for ping!
102 | ----------------------------------------------------------------
103 | 	  
104 | 105 |

6. Now the analysis is done and we can check the capabilities of the binary

106 |
getcap /bin/ping
107 |

This should print the capabilities added to the binary like this:

108 |
/bin/ping = cap_dac_override,cap_dac_read_search,cap_fowner,cap_kill,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_sys_resource,cap_sys_tty_config,cap_lease,cap_audit_control,cap_mac_override+ep
109 |
110 | 111 |
112 | 113 |
114 | 115 | 116 | -------------------------------------------------------------------------------- /website/style.css: -------------------------------------------------------------------------------- 1 | body {font-family: Arial;} 2 | 3 | p { 4 | max-width: 75em; 5 | } 6 | 7 | 8 | table, th, td { 9 | border:0.5px solid black; 10 | max-width: 75em; 11 | } 12 | 13 | 14 | 15 | h1 { 16 | text-align: center; 17 | font-weight: bold; 18 | text-decoration: underline; 19 | max-width: 75em; 20 | } 21 | 22 | h3 { 23 | max-width: 75em; 24 | } 25 | 26 | p.thick { 27 | font-weight: bold; 28 | } 29 | .page-wrap{ 30 | max-width: 75em; 31 | margin: 0 auto; 32 | } 33 | 34 | .tryit_output { 35 | background-color: #000000; 36 | color: #FFFFFF; 37 | } 38 | /* Style the tab */ 39 | .tab { 40 | overflow: hidden; 41 | border: 1px solid #ccc; 42 | background-color: #f1f1f1; 43 | } 44 | 45 | /* Style the buttons inside the tab */ 46 | .tab button { 47 | background-color: inherit; 48 | float: left; 49 | border: none; 50 | outline: none; 51 | cursor: pointer; 52 | padding: 14px 16px; 53 | transition: 0.3s; 54 | font-size: 17px; 55 | } 56 | 57 | /* Change background color of buttons on hover */ 58 | .tab button:hover { 59 | background-color: #ddd; 60 | } 61 | 62 | /* Create an active/current tablink class */ 63 | .tab button.active { 64 | background-color: #ccc; 65 | } 66 | 67 | /* Style the tab content */ 68 | .tabcontent { 69 | display: "block"; 70 | padding: 6px 12px; 71 | border: 1px solid #ccc; 72 | border-top: none; 73 | } 74 | -------------------------------------------------------------------------------- /website/syscall-capabilitymapping.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | syscall-capability-mapping 8 | 9 | 92 | 93 | 94 | 95 | 96 | 97 |
98 |

99 | Capability-System call Mapping 100 |

101 |
102 |
103 | 104 | 105 |
106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /website/syscall-capabilitymappingtable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 |
12 |

Capability-System call Mapping

13 | 14 |

As of Linux Kernel v5.17, 41 capabilities have been introduced. Our 15 | mapping includes 126 system calls, which depend on at least one of the 41 16 | available capabilities to perform some privileged operation. Visual 17 | representation of this mapping is also available 18 | here. 19 |

20 | 21 |

The resulting mapping of our effort to identify the system calls that depend on each capability.

22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 |
CapabilitySystem call(s)
CAP_AUDIT_CONTROLsendto, recv, recvfrom, recvmsg
CAP_AUDIT_READbind
CAP_AUDIT_WRITEsendto
CAP_BLOCK_SUSPENDepoll_ctl
CAP_SYS_ADMINbpf, perf_event_open, syslog, mount, umount, pivot_root, swapon, swapoff, setdomainname, vm86, setns, 50 | fanotify_init, unshare, lookup_dcookie, io_submit, prctl, clone, quotactl, msgctl, setrlimit, shmctl, ioprio_set, keyctl, madvise, ioctl, seccomp, ptrace, sethostname
CAP_BPFbpf
CAP_PERFMONperf_event_open
CAP_SYSLOGsyslog
CAP_CHECKPOINT_RESTOREclone
CAP_CHOWNchown, fchown, lchown, fchownat
CAP_DAC_READ_SEARCHopen, openat, openat2, open_by_handle_at, linkat
CAP_DAC_OVERRIDEutime, utimensat, utimes, open, openat, openat2
CAP_FOWNERchmod, fchmod, fchmodat, utime, utimes, utimensat, unlink, unlinkat, open, openat, openat2, fcntl, rename, renameat, renameat2, rmdir, ioctl
CAP_LEASEfcntl
CAP_FSETIDchmod, fchmod, fchmodat
CAP_IPC_LOCKmlock, mlock2, mlockall, mmap, memfd_create
CAP_IPC_OWNERmsgrcv, msgsnd, semop, semtimedop, shmat, shmdt, msgctl, msgget, shmctl
CAP_KILLkill, ioctl
CAP_LINUX_IMMUTABLEioctl
CAP_MAC_ADMINsetxattr, lsetxattr, fsetxattr
CAP_MAC_OVERRIDEsocket
CAP_MKNODmknod, mknodat, renameat2
CAP_NET_ADMINsetsockopt, ioctl
CAP_NET_BIND_SERVICEbind
CAP_NET_BROADCAST
CAP_NET_RAWsocket
CAP_SETGIDsetgroups, setfsgid, setgid, setregid, setresgid
CAP_SETFCAPclone
CAP_SETPCAPcapset, prctl
CAP_SETUIDsetuid, setreuid, setresuid, setfsuid, keyctl
CAP_SYS_BOOTreboot, kexec_file_load, kexec_load
CAP_SYS_CHROOTchroot, setns
CAP_SYS_MODULEfinit_module, init_module, create_module, delete_module
CAP_SYS_NICEsched_setscheduler, sched_setparam, sched_setattr, migrate_pages, setpriority, sched_setaffinity, nice, ioprio_set, move_pages, spu_create, mbind
CAP_SYS_PACCTacct
CAP_SYS_PTRACEptrace, userfaultfd, kcmp, set_robust_list, 206 | process_vm_readv, process_vm_writev
CAP_SYS_RAWIOiopl, ioperm
CAP_SYS_RESOURCEsend, sendto, sendmsg, prctl, msgctl, setrlimit, fcntl, prlimit, mq_open, ioctl
CAP_SYS_TIMEsettimeofday, stime, adjtimex, clock_adjtime, ntp_adjtime
CAP_SYS_TTY_CONFIGvhangup, ioctl
CAP_WAKE_ALARMtimer_create, timerfd_create
234 |
235 |

System calls that conditionally depend on CAP_SYS_ADMIN according to certain values of the highlighted arguments.

236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 245 | 246 | 247 | 248 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 288 | 289 | 290 | 291 | 292 | 293 |
System calls
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, 244 | ... /* pid_t *ptid, struct user_desc *tls, pid_t *ctid */);
int prctl(int option, unsigned long arg2, unsigned long 249 | arg3, unsigned long arg4, unsigned long arg5);
int quotactl(int cmd, const char *special, int id, caddr_t addr);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
int setrlimit(int resource, const struct rlimit *rlim);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
int syscall(SYS_ioprio_set, int which, int who, int ioprio);
long syscall(SYS_keyctl, int operation, unsigned long arg2, 274 | unsigned long arg3, unsigned long arg4, unsigned long arg5);
int madvise(void *addr, size_t length, int advice);
int ioctl(int fd, unsigned long request, ...);
int syscall(SYS_seccomp, unsigned int operation, unsigned 287 | int flags, void *args);
long ptrace(enum __ptrace_request request, pid_t pid,void *addr, void *data);
294 |
295 |
296 |
297 | 298 | 299 | --------------------------------------------------------------------------------