├── .gitignore ├── .images ├── alloc-init-type-after.png ├── alloc-init-type-before.png ├── memory-management-after.png ├── memory-management-before.png ├── super-init-after.png └── super-init-before.png ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── lib.rs ├── remove_memory_management.rs ├── type_propagation ├── alloc_init.rs ├── mod.rs └── super_init.rs └── util.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .cargo/config.toml 3 | 4 | -------------------------------------------------------------------------------- /.images/alloc-init-type-after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdash/bn-objc-extras/e0420a914a90279239bc24bef12ff982cbfbbd39/.images/alloc-init-type-after.png -------------------------------------------------------------------------------- /.images/alloc-init-type-before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdash/bn-objc-extras/e0420a914a90279239bc24bef12ff982cbfbbd39/.images/alloc-init-type-before.png -------------------------------------------------------------------------------- /.images/memory-management-after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdash/bn-objc-extras/e0420a914a90279239bc24bef12ff982cbfbbd39/.images/memory-management-after.png -------------------------------------------------------------------------------- /.images/memory-management-before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdash/bn-objc-extras/e0420a914a90279239bc24bef12ff982cbfbbd39/.images/memory-management-before.png -------------------------------------------------------------------------------- /.images/super-init-after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdash/bn-objc-extras/e0420a914a90279239bc24bef12ff982cbfbbd39/.images/super-init-after.png -------------------------------------------------------------------------------- /.images/super-init-before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bdash/bn-objc-extras/e0420a914a90279239bc24bef12ff982cbfbbd39/.images/super-init-before.png -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "binaryninja" 16 | version = "0.1.0" 17 | source = "git+https://github.com/Vector35/binaryninja-api.git?branch=dev#8ba8388e12ab32ad937c4514f7a907d2530f1ef8" 18 | dependencies = [ 19 | "binaryninjacore-sys", 20 | "log", 21 | "serde_json", 22 | "thiserror", 23 | ] 24 | 25 | [[package]] 26 | name = "binaryninjacore-sys" 27 | version = "0.1.0" 28 | source = "git+https://github.com/Vector35/binaryninja-api.git?branch=dev#8ba8388e12ab32ad937c4514f7a907d2530f1ef8" 29 | dependencies = [ 30 | "bindgen", 31 | "proc-macro2", 32 | ] 33 | 34 | [[package]] 35 | name = "bindgen" 36 | version = "0.71.1" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" 39 | dependencies = [ 40 | "bitflags", 41 | "cexpr", 42 | "clang-sys", 43 | "itertools", 44 | "log", 45 | "prettyplease", 46 | "proc-macro2", 47 | "quote", 48 | "regex", 49 | "rustc-hash", 50 | "shlex", 51 | "syn", 52 | ] 53 | 54 | [[package]] 55 | name = "bitflags" 56 | version = "2.9.1" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 59 | 60 | [[package]] 61 | name = "bn-bdash-extras" 62 | version = "0.1.0" 63 | source = "git+https://github.com/bdash/bn-bdash-extras?branch=dev#3091d29b37744979d4d00dc8ab8bfe9e37acd379" 64 | dependencies = [ 65 | "binaryninja", 66 | "bn-bdash-extras-macros", 67 | "itertools", 68 | "log", 69 | "serde", 70 | "serde_json", 71 | ] 72 | 73 | [[package]] 74 | name = "bn-bdash-extras-macros" 75 | version = "0.1.0" 76 | source = "git+https://github.com/bdash/bn-bdash-extras?branch=dev#3091d29b37744979d4d00dc8ab8bfe9e37acd379" 77 | dependencies = [ 78 | "proc-macro2", 79 | "quote", 80 | "syn", 81 | ] 82 | 83 | [[package]] 84 | name = "bn-objc-extras" 85 | version = "0.1.0" 86 | dependencies = [ 87 | "binaryninja", 88 | "bn-bdash-extras", 89 | "bstr", 90 | "itertools", 91 | "log", 92 | ] 93 | 94 | [[package]] 95 | name = "bstr" 96 | version = "1.12.0" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" 99 | dependencies = [ 100 | "memchr", 101 | "regex-automata", 102 | "serde", 103 | ] 104 | 105 | [[package]] 106 | name = "cexpr" 107 | version = "0.6.0" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 110 | dependencies = [ 111 | "nom", 112 | ] 113 | 114 | [[package]] 115 | name = "cfg-if" 116 | version = "1.0.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 119 | 120 | [[package]] 121 | name = "clang-sys" 122 | version = "1.8.1" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 125 | dependencies = [ 126 | "glob", 127 | "libc", 128 | "libloading", 129 | ] 130 | 131 | [[package]] 132 | name = "either" 133 | version = "1.15.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 136 | 137 | [[package]] 138 | name = "glob" 139 | version = "0.3.2" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 142 | 143 | [[package]] 144 | name = "itertools" 145 | version = "0.13.0" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 148 | dependencies = [ 149 | "either", 150 | ] 151 | 152 | [[package]] 153 | name = "itoa" 154 | version = "1.0.15" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 157 | 158 | [[package]] 159 | name = "libc" 160 | version = "0.2.172" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 163 | 164 | [[package]] 165 | name = "libloading" 166 | version = "0.8.8" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" 169 | dependencies = [ 170 | "cfg-if", 171 | "windows-targets", 172 | ] 173 | 174 | [[package]] 175 | name = "log" 176 | version = "0.4.27" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 179 | 180 | [[package]] 181 | name = "memchr" 182 | version = "2.7.4" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 185 | 186 | [[package]] 187 | name = "minimal-lexical" 188 | version = "0.2.1" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 191 | 192 | [[package]] 193 | name = "nom" 194 | version = "7.1.3" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 197 | dependencies = [ 198 | "memchr", 199 | "minimal-lexical", 200 | ] 201 | 202 | [[package]] 203 | name = "prettyplease" 204 | version = "0.2.33" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "9dee91521343f4c5c6a63edd65e54f31f5c92fe8978c40a4282f8372194c6a7d" 207 | dependencies = [ 208 | "proc-macro2", 209 | "syn", 210 | ] 211 | 212 | [[package]] 213 | name = "proc-macro2" 214 | version = "1.0.95" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 217 | dependencies = [ 218 | "unicode-ident", 219 | ] 220 | 221 | [[package]] 222 | name = "quote" 223 | version = "1.0.40" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 226 | dependencies = [ 227 | "proc-macro2", 228 | ] 229 | 230 | [[package]] 231 | name = "regex" 232 | version = "1.11.1" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 235 | dependencies = [ 236 | "aho-corasick", 237 | "memchr", 238 | "regex-automata", 239 | "regex-syntax", 240 | ] 241 | 242 | [[package]] 243 | name = "regex-automata" 244 | version = "0.4.9" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 247 | dependencies = [ 248 | "aho-corasick", 249 | "memchr", 250 | "regex-syntax", 251 | ] 252 | 253 | [[package]] 254 | name = "regex-syntax" 255 | version = "0.8.5" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 258 | 259 | [[package]] 260 | name = "rustc-hash" 261 | version = "2.1.1" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 264 | 265 | [[package]] 266 | name = "ryu" 267 | version = "1.0.20" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 270 | 271 | [[package]] 272 | name = "serde" 273 | version = "1.0.219" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 276 | dependencies = [ 277 | "serde_derive", 278 | ] 279 | 280 | [[package]] 281 | name = "serde_derive" 282 | version = "1.0.219" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 285 | dependencies = [ 286 | "proc-macro2", 287 | "quote", 288 | "syn", 289 | ] 290 | 291 | [[package]] 292 | name = "serde_json" 293 | version = "1.0.140" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 296 | dependencies = [ 297 | "itoa", 298 | "memchr", 299 | "ryu", 300 | "serde", 301 | ] 302 | 303 | [[package]] 304 | name = "shlex" 305 | version = "1.3.0" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 308 | 309 | [[package]] 310 | name = "syn" 311 | version = "2.0.101" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 314 | dependencies = [ 315 | "proc-macro2", 316 | "quote", 317 | "unicode-ident", 318 | ] 319 | 320 | [[package]] 321 | name = "thiserror" 322 | version = "2.0.12" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 325 | dependencies = [ 326 | "thiserror-impl", 327 | ] 328 | 329 | [[package]] 330 | name = "thiserror-impl" 331 | version = "2.0.12" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 334 | dependencies = [ 335 | "proc-macro2", 336 | "quote", 337 | "syn", 338 | ] 339 | 340 | [[package]] 341 | name = "unicode-ident" 342 | version = "1.0.18" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 345 | 346 | [[package]] 347 | name = "windows-targets" 348 | version = "0.53.0" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" 351 | dependencies = [ 352 | "windows_aarch64_gnullvm", 353 | "windows_aarch64_msvc", 354 | "windows_i686_gnu", 355 | "windows_i686_gnullvm", 356 | "windows_i686_msvc", 357 | "windows_x86_64_gnu", 358 | "windows_x86_64_gnullvm", 359 | "windows_x86_64_msvc", 360 | ] 361 | 362 | [[package]] 363 | name = "windows_aarch64_gnullvm" 364 | version = "0.53.0" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" 367 | 368 | [[package]] 369 | name = "windows_aarch64_msvc" 370 | version = "0.53.0" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" 373 | 374 | [[package]] 375 | name = "windows_i686_gnu" 376 | version = "0.53.0" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" 379 | 380 | [[package]] 381 | name = "windows_i686_gnullvm" 382 | version = "0.53.0" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" 385 | 386 | [[package]] 387 | name = "windows_i686_msvc" 388 | version = "0.53.0" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" 391 | 392 | [[package]] 393 | name = "windows_x86_64_gnu" 394 | version = "0.53.0" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" 397 | 398 | [[package]] 399 | name = "windows_x86_64_gnullvm" 400 | version = "0.53.0" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" 403 | 404 | [[package]] 405 | name = "windows_x86_64_msvc" 406 | version = "0.53.0" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" 409 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bn-objc-extras" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | binaryninja = { git = "https://github.com/Vector35/binaryninja-api.git", branch = "dev" } 8 | log = "0.4" 9 | itertools = "0.13" 10 | bn-bdash-extras = { git = "https://github.com/bdash/bn-bdash-extras", branch = "dev" } 11 | bstr = "1.12.0" 12 | 13 | [lib] 14 | crate-type = ["cdylib"] 15 | 16 | [profile.dev] 17 | panic = "abort" 18 | build-override.debug = true 19 | 20 | [profile.release] 21 | panic = "abort" 22 | 23 | [lints.clippy] 24 | pedantic = { level = "warn", priority = -1 } 25 | cast_sign_loss = "allow" 26 | items_after_statements = "allow" 27 | too_many_lines = "allow" 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Mark Rowe 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 | # Experimental improvements to Objective-C analysis for Binary Ninja 2 | 3 | This Binary Ninja plug-in adds several additional types of analysis related to Objective-C runtime functions. 4 | They are experimental and have some rough edges. The goal is to move each of the analysis types upstream 5 | into Binary Ninja once they mature. 6 | 7 | The additional analysis that is currently available is: 8 | 1. Removal of Objective-C runtime calls that deal purely with reference counting. 9 | 2. Propagation of type information for the return values of Objective-C runtime functions where it can be 10 | inferred based on function arguments. 11 | 12 | More will be added as time permits. 13 | 14 | ## Supported Binary Ninja versions 15 | 16 | Only recent versions of Binary Ninja 5.1-dev are supported. 17 | 18 | ## Installation 19 | 20 | ``` 21 | git clone https://github.com/bdash/bn-objc-extras.git 22 | cd bn-objc-extras 23 | cargo build --release 24 | ln -sf $PWD/target/release/libbn_objc_extras.dylib ~/Library/Application\ Support/Binary\ Ninja/plugins/ 25 | ``` 26 | 27 | ## Configuration 28 | 29 | As these analysis passes have rough edges they do not run by default. They can be applied on a per-function 30 | basis via the `Function Analysis` context menu. If you're brave, you can enable them by default via the relevant 31 | `bdash.objc` setting in Binary Ninja's settings. 32 | 33 | ## Details 34 | 35 | ### Remove Objective-C Reference Counting Calls 36 | 37 | This pass detects and removes calls to Objective-C runtime functions that deal purely with the reference count 38 | of an object. Detected calls include: 39 | * `objc_autorelease` 40 | * `objc_autoreleaseReturnValue` 41 | * `objc_release` 42 | * `objc_retain` 43 | * `objc_retainAutorelease` 44 | * `objc_retainAutoreleasedReturnValue` 45 | 46 | Removing these calls can make it much easier to follow the logic of a function. 47 | 48 | | Before | After | 49 | |--------|-------| 50 | | ![Before](.images/memory-management-before.png "Before") | ![After](.images/memory-management-after.png "After") | 51 | 52 | #### Limitations 53 | 54 | In some cases eliminating memory management calls can lead to Binary Ninja's High Level IL view showing apparently dead code. 55 | Temporarily disabling the analysis pass via the `Function Analysis` context menu is a quick way to understand what is going 56 | on in these cases. 57 | 58 | One common case of this is when an instance variable is loaded from an object and them immediately passed to `objc_release` 59 | without otherwise being used. The only use of the variable is removed, but Binary Ninja preserves and displays the load 60 | of the instance variable. 61 | 62 | ### Propagate Types from `[super init…]` 63 | 64 | This pass detects calls to `[super init…]` and overrides the return type of each call to the static type of `self` that is 65 | encoded in the `objc_super` struct that is passed to `objc_msgSendSuper2`. 66 | 67 | When combined with removing Objective-C reference counting, this can significantly clean up the bodies of `-init` methods. 68 | 69 | | Before | After | 70 | |--------|-------| 71 | | ![Before](.images/super-init-before.png "Before") | ![After](.images/super-init-after.png "After") | 72 | 73 | ### Propagate Types from Objective-C Runtime Calls 74 | 75 | This analysis pass detects calls to Objective-C runtime functions such as `objc_alloc` / `objc_alloc_init` 76 | and overrides the return type of each call to the type corresponding to the class object passed to the 77 | function[^1]. This can help with resolving accesses to instance variables and propagating object types 78 | further on in the function. 79 | 80 | | Before | After | 81 | |--------|-------| 82 | | ![Before](.images/alloc-init-type-before.png "Before") | ![After](.images/alloc-init-type-after.png "After") | 83 | 84 | Note: due to [limitations in Binary Ninja's handling of call type adjustments](https://github.com/Vector35/binaryninja-api/issues/6737), 85 | this pass is not available in images loaded from the dyld shared cache. 86 | 87 | [^1]: It is not guaranteed that `-[Foo init]` will return an instance of `Foo`, but it does in the 88 | overwhelmingly majority of cases. If you're analyzing some code that violates this assumption you 89 | can either disable this analysis pass for the function in question, or manually override the type 90 | at the callsite in question. 91 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use binaryninja::{ 2 | binary_view::BinaryViewExt as _, 3 | logger::Logger, 4 | rc::Ref, 5 | workflow::{Activity, Workflow}, 6 | }; 7 | use log::LevelFilter; 8 | 9 | use bn_bdash_extras::activity; 10 | 11 | mod remove_memory_management; 12 | mod type_propagation; 13 | mod util; 14 | 15 | const OBJC_REMOVE_MEMORY_MANAGMENT_ACTIVITY_NAME: &str = "bdash.objc-remove-memory-management"; 16 | const OBJC_TYPES_ALLOC_INIT_ACTIVITY_NAME: &str = "bdash.objc-types.alloc-init"; 17 | const OBJC_TYPES_SUPER_INIT_ACTIVITY_NAME: &str = "bdash.objc-types.super-init"; 18 | 19 | fn tag_type_for_view( 20 | view: &binaryninja::binary_view::BinaryView, 21 | ) -> Ref { 22 | view.tag_type_by_name("Objective-C Extras") 23 | .unwrap_or_else(|| view.create_tag_type("Objective-C Extras", "OC")) 24 | } 25 | 26 | fn register_activities( 27 | memory_management: &Activity, 28 | types_alloc_init: &Activity, 29 | types_super_init: &Activity, 30 | workflow: &Workflow, 31 | ) { 32 | if !workflow.registered() { 33 | log::debug!( 34 | "Skipping activity registration for workflow {} as it is not registered", 35 | workflow.name() 36 | ); 37 | return; 38 | } 39 | 40 | let workflow = workflow.clone_to(&workflow.name()); 41 | workflow.register_activity(memory_management).unwrap(); 42 | workflow.register_activity(types_alloc_init).unwrap(); 43 | workflow.register_activity(types_super_init).unwrap(); 44 | 45 | workflow.insert( 46 | "core.function.generateMediumLevelIL", 47 | [memory_management.name()], 48 | ); 49 | workflow.insert_after( 50 | "core.function.analyzeConstantReferences", 51 | [types_alloc_init.name()], 52 | ); 53 | workflow.insert_after( 54 | "core.function.analyzeConstantReferences", 55 | [types_super_init.name()], 56 | ); 57 | 58 | workflow.register().unwrap(); 59 | } 60 | 61 | #[unsafe(no_mangle)] 62 | pub extern "C" fn CorePluginDependencies() { 63 | use binaryninja::add_optional_plugin_dependency; 64 | add_optional_plugin_dependency("workflow_objc"); 65 | add_optional_plugin_dependency("sharedcache"); 66 | } 67 | 68 | #[unsafe(no_mangle)] 69 | #[allow(non_snake_case)] 70 | pub extern "C" fn CorePluginInit() -> bool { 71 | Logger::new("Obj-C Extras") 72 | .with_level(LevelFilter::Debug) 73 | .init(); 74 | 75 | let memory_management_config = activity::Config::action( 76 | OBJC_REMOVE_MEMORY_MANAGMENT_ACTIVITY_NAME, 77 | "Obj-C: Remove reference counting calls", 78 | "Remove calls to objc_retain / objc_release / objc_autorelease to simplify the resulting higher-level ILs", 79 | ) 80 | .with_eligibility(activity::Eligibility::auto_with_default(false)); 81 | 82 | let types_alloc_init_config = activity::Config::action( 83 | OBJC_TYPES_ALLOC_INIT_ACTIVITY_NAME, 84 | "Obj-C: Propagate return type from objc_alloc_init", 85 | "Adjust the return type of calls to objc_alloc / objc_alloc_init when a fixed type is passed as an argument.", 86 | ) 87 | .with_eligibility( 88 | // Currently disabled in DSCView due to https://github.com/Vector35/binaryninja-api/issues/6737 89 | activity::Eligibility::auto_with_default(false) 90 | .with_predicate(activity::ViewType::NotIn(&["DSCView"])), 91 | ); 92 | 93 | let types_super_init_config = activity::Config::action( 94 | OBJC_TYPES_SUPER_INIT_ACTIVITY_NAME, 95 | "Obj-C: Propagate return type from [super init…]", 96 | "Adjust the return type of calls to objc_msgSendSuper2 where the selector is in the init family.", 97 | ) 98 | .with_eligibility(activity::Eligibility::auto()); 99 | 100 | let memory_management_activity = Activity::new_with_action( 101 | &memory_management_config.to_string(), 102 | remove_memory_management::action, 103 | ); 104 | let types_alloc_init_activity = Activity::new_with_action( 105 | &types_alloc_init_config.to_string(), 106 | type_propagation::alloc_init::action, 107 | ); 108 | let types_super_init_activity = Activity::new_with_action( 109 | &types_super_init_config.to_string(), 110 | type_propagation::super_init::action, 111 | ); 112 | 113 | register_activities( 114 | &memory_management_activity, 115 | &types_alloc_init_activity, 116 | &types_super_init_activity, 117 | &Workflow::instance("core.function.metaAnalysis"), 118 | ); 119 | register_activities( 120 | &memory_management_activity, 121 | &types_alloc_init_activity, 122 | &types_super_init_activity, 123 | &Workflow::instance("core.function.objectiveC"), 124 | ); 125 | register_activities( 126 | &memory_management_activity, 127 | &types_alloc_init_activity, 128 | &types_super_init_activity, 129 | &Workflow::instance("core.function.sharedCache"), 130 | ); 131 | 132 | true 133 | } 134 | -------------------------------------------------------------------------------- /src/remove_memory_management.rs: -------------------------------------------------------------------------------- 1 | use binaryninja::{ 2 | architecture::{Architecture, Register as _, RegisterInfo as _}, 3 | binary_view::BinaryViewExt as _, 4 | low_level_il::{ 5 | LowLevelILRegisterKind, 6 | expression::{ExpressionHandler, LowLevelILExpression, ValueExpr}, 7 | function::{FunctionForm, FunctionMutability}, 8 | instruction::{InstructionHandler, LowLevelILInstruction, LowLevelInstructionIndex}, 9 | lifting::LowLevelILLabel, 10 | }, 11 | workflow::AnalysisContext, 12 | }; 13 | 14 | use bn_bdash_extras::llil::match_instr; 15 | 16 | // j_ prefixes are for stub functions in the dyld shared cache. 17 | // The prefix is added by Binary Ninja's shared cache workflow. 18 | const IGNORABLE_MEMORY_MANAGEMENT_FUNCTIONS: &[&[u8]] = &[ 19 | b"_objc_autorelease", 20 | b"_objc_autoreleaseReturnValue", 21 | b"_objc_release", 22 | b"_objc_retain", 23 | b"_objc_retainAutorelease", 24 | b"_objc_retainAutoreleaseReturnValue", 25 | b"_objc_retainAutoreleasedReturnValue", 26 | b"_objc_retainBlock", 27 | b"_objc_unsafeClaimAutoreleasedReturnValue", 28 | b"j__objc_autorelease", 29 | b"j__objc_autoreleaseReturnValue", 30 | b"j__objc_release", 31 | b"j__objc_retain", 32 | b"j__objc_retainAutorelease", 33 | b"j__objc_retainAutoreleaseReturnValue", 34 | b"j__objc_retainAutoreleasedReturnValue", 35 | b"j__objc_retainBlock", 36 | b"j__objc_unsafeClaimAutoreleasedReturnValue", 37 | ]; 38 | 39 | fn is_call_to_ignorable_memory_management_function<'func, M, F>( 40 | view: &binaryninja::binary_view::BinaryView, 41 | instr: &'func LowLevelILInstruction<'func, M, F>, 42 | ) -> bool 43 | where 44 | M: FunctionMutability + std::fmt::Debug, 45 | F: FunctionForm + std::fmt::Debug, 46 | LowLevelILInstruction<'func, M, F>: InstructionHandler<'func, M, F>, 47 | LowLevelILExpression<'func, M, F, ValueExpr>: ExpressionHandler<'func, M, F>, 48 | { 49 | let target = match_instr! { 50 | instr, 51 | Call(ConstPtr(address)) | TailCall(ConstPtr(address)) => address, 52 | Goto(target) => target.address(), 53 | _ => return false, 54 | }; 55 | let Some(symbol) = view.symbol_by_address(target) else { 56 | return false; 57 | }; 58 | IGNORABLE_MEMORY_MANAGEMENT_FUNCTIONS.contains(&symbol.full_name().to_bytes()) 59 | } 60 | 61 | pub(crate) fn action(analysis_context: &AnalysisContext) { 62 | let Some(llil) = (unsafe { analysis_context.llil_function() }) else { 63 | return; 64 | }; 65 | 66 | let func = analysis_context.function(); 67 | 68 | let Some(link_register) = func.arch().link_reg() else { 69 | return; 70 | }; 71 | let link_register_size = link_register.info().size(); 72 | let link_register = LowLevelILRegisterKind::Arch(link_register); 73 | 74 | let mut did_replace = false; 75 | for idx in 0..=llil.instruction_count() { 76 | let Some(instr) = llil.instruction_from_index(LowLevelInstructionIndex(idx)) else { 77 | continue; 78 | }; 79 | 80 | // TODO: Detect calls to `objc_release` that are immediately after a load of a struct field. 81 | // It might be preferable to leave those in place since otherwise the load is left behind. 82 | if !is_call_to_ignorable_memory_management_function(&analysis_context.view(), &instr) { 83 | continue; 84 | } 85 | 86 | match_instr! { 87 | instr, 88 | TailCall(_) => unsafe { 89 | llil.set_current_address(instr.address()); 90 | llil.replace_expression( 91 | instr.expr_idx(), 92 | llil.ret(llil.reg(link_register_size, link_register)), 93 | ); 94 | }, 95 | Call(_) => unsafe { 96 | llil.set_current_address(instr.address()); 97 | llil.replace_expression(instr.expr_idx(), llil.nop()); 98 | }, 99 | Goto(_) if idx == 0 => unsafe { 100 | // If the `objc_retain` is the first instruction in the function, this function 101 | // must only contain the call to the memory management function since when the 102 | // memory management function returns, it will return to this function's caller. 103 | llil.set_current_address(instr.address()); 104 | llil.replace_expression( 105 | instr.expr_idx(), 106 | llil.ret(llil.reg(link_register_size, link_register)), 107 | ); 108 | }, 109 | Goto(_) => { 110 | // The shared cache workflow inlines calls to stub functions, which causes them 111 | // to show up as a `lr = ; goto ;` sequence. 112 | // We need to remove the load of `lr` and update the `goto` to jump to the next instruction. 113 | 114 | let Some(prev) = 115 | llil.instruction_from_index(LowLevelInstructionIndex(idx - 1_usize)) 116 | else { 117 | continue; 118 | }; 119 | 120 | let target = match_instr!{ 121 | prev, 122 | SetReg(reg, ConstPtr(target)) if *reg == link_register => target, 123 | _ => continue, 124 | }; 125 | 126 | let Some(LowLevelInstructionIndex(target_idx)) = llil.instruction_index_at(target) else { 127 | continue; 128 | }; 129 | 130 | let mut label = LowLevelILLabel::new(); 131 | label.operand = target_idx; 132 | 133 | unsafe { 134 | llil.set_current_address(prev.address()); 135 | llil.replace_expression(prev.expr_idx(), llil.nop()); 136 | llil.set_current_address(instr.address()); 137 | llil.replace_expression(instr.expr_idx(), llil.goto(&mut label)); 138 | } 139 | } 140 | _ => {} 141 | } 142 | 143 | func.add_tag( 144 | &crate::tag_type_for_view(&analysis_context.view()), 145 | "Removed memory management call", 146 | Some(instr.address()), 147 | false, 148 | None, 149 | ); 150 | did_replace = true; 151 | } 152 | 153 | if did_replace { 154 | llil.generate_ssa_form(); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/type_propagation/alloc_init.rs: -------------------------------------------------------------------------------- 1 | use binaryninja::{ 2 | binary_view::{BinaryView, BinaryViewExt as _}, 3 | medium_level_il::MediumLevelILLiftedInstruction, 4 | rc::Ref, 5 | types::Type, 6 | workflow::AnalysisContext, 7 | }; 8 | use bstr::ByteSlice; 9 | 10 | use crate::util; 11 | 12 | // j_ prefixes are for stub functions in the dyld shared cache. 13 | // The prefix is added by Binary Ninja's shared cache workflow. 14 | const ALLOC_INIT_FUNCTIONS: &[&[u8]] = &[ 15 | b"_objc_alloc_init", 16 | b"_objc_alloc_initWithZone", 17 | b"_objc_alloc", 18 | b"_objc_allocWithZone", 19 | b"_objc_opt_new", 20 | b"j__objc_alloc_init", 21 | b"j__objc_alloc_initWithZone", 22 | b"j__objc_alloc", 23 | b"j__objc_allocWithZone", 24 | b"j__objc_opt_new", 25 | ]; 26 | 27 | fn return_type_for_alloc_call(call: &util::Call<'_>, view: &BinaryView) -> Option> { 28 | if call.call.params.len() != 1 { 29 | return None; 30 | } 31 | 32 | let class_addr = 33 | util::match_constant_pointer_or_load_of_constant_pointer(&call.call.params[0])?; 34 | let class_symbol_name = view.symbol_by_address(class_addr)?.full_name(); 35 | let class_name = util::class_name_from_symbol_name(class_symbol_name.to_bytes().as_bstr())?; 36 | 37 | let class_type = view.type_by_name(class_name.to_str().ok()?)?; 38 | Some(Type::pointer(&call.target.arch(), &class_type)) 39 | } 40 | 41 | fn process_instruction(instr: &MediumLevelILLiftedInstruction, view: &BinaryView) -> Option<()> { 42 | let call = util::match_call_to_function_named(instr, view, ALLOC_INIT_FUNCTIONS)?; 43 | 44 | util::adjust_return_type_of_call( 45 | &call, 46 | return_type_for_alloc_call(&call, view)?.as_ref(), 47 | view, 48 | "Adjusted return type of alloc / init call", 49 | ); 50 | 51 | Some(()) 52 | } 53 | 54 | pub(crate) fn action(analysis_context: &AnalysisContext) { 55 | let Some(mlil) = analysis_context.mlil_function() else { 56 | return; 57 | }; 58 | 59 | let mlil_ssa = mlil.ssa_form(); 60 | let view = analysis_context.view(); 61 | 62 | for basic_block in &mlil_ssa.basic_blocks() { 63 | for instr in basic_block.iter() { 64 | process_instruction(&instr.lift(), &view); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/type_propagation/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod alloc_init; 2 | pub(crate) mod super_init; 3 | -------------------------------------------------------------------------------- /src/type_propagation/super_init.rs: -------------------------------------------------------------------------------- 1 | use binaryninja::{ 2 | binary_view::{BinaryView, BinaryViewExt as _}, 3 | medium_level_il::{ 4 | MediumLevelILLiftedInstruction, MediumLevelILLiftedInstructionKind, 5 | operation::{Constant, LiftedSetVarSsa, LiftedSetVarSsaField, LiftedVarPhi, Var, VarSsa}, 6 | }, 7 | rc::Ref, 8 | types::Type, 9 | workflow::AnalysisContext, 10 | }; 11 | use bstr::ByteSlice as _; 12 | 13 | use crate::util; 14 | 15 | // j_ prefixes are for stub functions in the dyld shared cache. 16 | // The prefix is added by Binary Ninja's shared cache workflow. 17 | const OBJC_MSG_SEND_SUPER_FUNCTIONS: &[&[u8]] = &[ 18 | b"_objc_msgSendSuper", 19 | b"_objc_msgSendSuper2", 20 | b"j__objc_msgSendSuper", 21 | b"j__objc_msgSendSuper2", 22 | ]; 23 | 24 | fn return_type_for_super_call(call: &util::Call, view: &BinaryView) -> Option> { 25 | // Expecting to see at least `objc_super` and a selector. 26 | if call.call.params.len() < 2 { 27 | return None; 28 | } 29 | 30 | let selector_addr = 31 | util::match_constant_pointer_or_load_of_constant_pointer(&call.call.params[1])?; 32 | let selector_symbol_name = view.symbol_by_address(selector_addr)?.full_name(); 33 | let selector_name = 34 | util::selector_name_from_symbol_name(selector_symbol_name.to_bytes().as_bstr())?; 35 | 36 | if !selector_name.starts_with(b"init") { 37 | return None; 38 | } 39 | 40 | let super_param = &call.call.params[0]; 41 | let MediumLevelILLiftedInstructionKind::VarSsa(VarSsa { 42 | src: super_param_var, 43 | }) = super_param.kind 44 | else { 45 | log::debug!( 46 | "Unhandled super paramater format at {:#0x} {:?}", 47 | super_param.address, 48 | super_param 49 | ); 50 | return None; 51 | }; 52 | 53 | // Parameter is an SSA variable. Find its definitions to find when it was assigned. 54 | // From there we can determine the values it was assigned. 55 | let Some(super_param_def) = call 56 | .instr 57 | .function 58 | .ssa_variable_definition(&super_param_var) 59 | else { 60 | log::debug!(" could not find definition of variable?"); 61 | return None; 62 | }; 63 | 64 | let src = match super_param_def.lift().kind { 65 | MediumLevelILLiftedInstructionKind::SetVarSsa(LiftedSetVarSsa { src, .. }) => src, 66 | MediumLevelILLiftedInstructionKind::VarPhi(LiftedVarPhi { .. }) => { 67 | // The Swift compiler generates code that conditionally assigns to the receiver field of `objc_super`. 68 | // TODO: Recognize that pattern and handle it. 69 | log::debug!( 70 | " found phi node for definition of `objc_super` variable at {:#0x} {:?}", 71 | super_param_def.address, 72 | super_param_def 73 | ); 74 | return None; 75 | } 76 | _ => { 77 | log::error!( 78 | "Unexpected variable definition kind at {:#0x} {:#x?}", 79 | super_param_def.address, 80 | super_param_def 81 | ); 82 | return None; 83 | } 84 | }; 85 | 86 | let src_var = match src.kind { 87 | MediumLevelILLiftedInstructionKind::AddressOf(Var { src: src_var }) => src_var, 88 | MediumLevelILLiftedInstructionKind::VarSsa(_) 89 | | MediumLevelILLiftedInstructionKind::Sub(_) => { 90 | // The Swift compiler generates code that initializes the `objc_super` variable in more varied ways. 91 | log::debug!( 92 | " found non-address-of variable definition of `objc_super` variable at {:#0x} {:?}", 93 | super_param_def.address, 94 | super_param_def 95 | ); 96 | return None; 97 | } 98 | _ => { 99 | log::error!( 100 | "Unexpected source of variable definition at {:#0x} {:x?}", 101 | super_param_def.address, 102 | super_param_def 103 | ); 104 | return None; 105 | } 106 | }; 107 | 108 | // `src_var` is a `struct objc_super`. Find constant values assigned to the `super_class` field (offset 8). 109 | let super_class_constants: Vec<_> = call 110 | .instr 111 | .function 112 | .var_definitions(&src_var) 113 | .into_iter() 114 | .filter_map(|def| { 115 | let def = def.lift(); 116 | let MediumLevelILLiftedInstructionKind::SetVarAliasedField(LiftedSetVarSsaField { 117 | src, 118 | offset: 8, 119 | .. 120 | }) = def.kind 121 | else { 122 | return None; 123 | }; 124 | 125 | let MediumLevelILLiftedInstructionKind::ConstPtr(Constant { constant }) = src.kind 126 | else { 127 | return None; 128 | }; 129 | Some(constant) 130 | }) 131 | .collect(); 132 | 133 | // In the common case there are either zero or one assignments to the `super_class` field. 134 | // If there are zero, that likely means the assigned value was not a constant. Handling 135 | // that is above my pay grade. 136 | let &[super_class_ptr] = &super_class_constants[..] else { 137 | log::debug!( 138 | "Unexpected number of assignments to super class found for {:#0x}: {:#0x?}", 139 | src.address, 140 | super_class_constants 141 | ); 142 | return None; 143 | }; 144 | 145 | let Some(super_class_symbol) = view.symbol_by_address(super_class_ptr) else { 146 | log::debug!("No symbol found for super class at {super_class_ptr:#0x}"); 147 | return None; 148 | }; 149 | 150 | let super_class_symbol_name = super_class_symbol.full_name(); 151 | let Some(class_name) = 152 | util::class_name_from_symbol_name(super_class_symbol_name.to_bytes().as_bstr()) 153 | else { 154 | log::debug!("Unable to extract class name from symbol name: {super_class_symbol_name:?}"); 155 | return None; 156 | }; 157 | 158 | let Some(class_type) = view.type_by_name(class_name.to_str_lossy()) else { 159 | log::debug!("No type found for class named {class_name:?}"); 160 | return None; 161 | }; 162 | 163 | Some(Type::pointer(&call.target.arch(), &class_type)) 164 | } 165 | 166 | fn process_instruction(instr: &MediumLevelILLiftedInstruction, view: &BinaryView) -> Option<()> { 167 | let call = util::match_call_to_function_named(instr, view, OBJC_MSG_SEND_SUPER_FUNCTIONS)?; 168 | 169 | util::adjust_return_type_of_call( 170 | &call, 171 | return_type_for_super_call(&call, view)?.as_ref(), 172 | view, 173 | "Adjusted return type of super init call", 174 | ); 175 | 176 | Some(()) 177 | } 178 | 179 | pub(crate) fn action(analysis_context: &AnalysisContext) { 180 | let Some(mlil) = analysis_context.mlil_function() else { 181 | return; 182 | }; 183 | 184 | let mlil_ssa = mlil.ssa_form(); 185 | let view = analysis_context.view(); 186 | 187 | for basic_block in &mlil_ssa.basic_blocks() { 188 | for instr in basic_block.iter() { 189 | process_instruction(&instr.lift(), &view); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use binaryninja::{ 2 | binary_view::{BinaryView, BinaryViewExt as _}, 3 | confidence::Conf, 4 | function::Function, 5 | medium_level_il::{ 6 | MediumLevelILFunction, MediumLevelILLiftedInstruction, MediumLevelILLiftedInstructionKind, 7 | operation::{Constant, LiftedCallSsa, LiftedLoadSsa}, 8 | }, 9 | rc::Ref, 10 | types::Type, 11 | variable::{RegisterValueType, SSAVariable}, 12 | }; 13 | use bstr::BStr; 14 | 15 | pub(crate) fn class_name_from_symbol_name(symbol_name: &BStr) -> Option<&BStr> { 16 | // The symbol name for the `objc_class_t` can have different names depending 17 | // on factors such as being local or external, and whether the reference 18 | // is from the shared cache or a standalone Mach-O file. 19 | Some(if symbol_name.starts_with(b"cls_") { 20 | &symbol_name[4..] 21 | } else if symbol_name.starts_with(b"clsRef_") { 22 | &symbol_name[7..] 23 | } else if symbol_name.starts_with(b"_OBJC_CLASS_$_") { 24 | &symbol_name[14..] 25 | } else { 26 | return None; 27 | }) 28 | } 29 | 30 | pub(crate) fn selector_name_from_symbol_name(symbol_name: &BStr) -> Option<&BStr> { 31 | Some(if symbol_name.starts_with(b"sel_") { 32 | &symbol_name[4..] 33 | } else { 34 | return None; 35 | }) 36 | } 37 | 38 | #[allow(clippy::struct_field_names)] 39 | pub(crate) struct Call<'a> { 40 | pub instr: &'a MediumLevelILLiftedInstruction, 41 | pub call: &'a LiftedCallSsa, 42 | pub target: Ref, 43 | } 44 | 45 | /// Returns a `Call` if `instr` is a call or tail call to a function whose name appears in `function_names` 46 | pub(crate) fn match_call_to_function_named<'a>( 47 | instr: &'a MediumLevelILLiftedInstruction, 48 | view: &'a BinaryView, 49 | function_names: &'a [&[u8]], 50 | ) -> Option> { 51 | let (MediumLevelILLiftedInstructionKind::TailcallSsa(ref call) 52 | | MediumLevelILLiftedInstructionKind::CallSsa(ref call)) = instr.kind 53 | else { 54 | return None; 55 | }; 56 | 57 | let MediumLevelILLiftedInstructionKind::ConstPtr(Constant { 58 | constant: call_target, 59 | }) = call.dest.kind 60 | else { 61 | return None; 62 | }; 63 | 64 | let target_function = view.function_at(&instr.function.function().platform(), call_target)?; 65 | let function_name = target_function.symbol().full_name(); 66 | if !function_names.contains(&function_name.to_bytes()) { 67 | return None; 68 | } 69 | 70 | Some(Call { 71 | instr, 72 | call, 73 | target: target_function, 74 | }) 75 | } 76 | 77 | /// Adjust the return type of the call represented by `call` 78 | /// 79 | /// A tag is added at the call instruction with the given description. 80 | pub(crate) fn adjust_return_type_of_call( 81 | call: &Call<'_>, 82 | return_type: &Type, 83 | view: &BinaryView, 84 | tag_description: &str, 85 | ) { 86 | let function = call.instr.function.function(); 87 | let target_function_type = if let Some(existing_call_type_adjustment) = 88 | function.call_type_adjustment(call.instr.address, None) 89 | { 90 | existing_call_type_adjustment.contents 91 | } else { 92 | call.target.function_type() 93 | }; 94 | 95 | let adjusted_call_type = Type::function( 96 | return_type, 97 | target_function_type.parameters().unwrap(), 98 | target_function_type.has_variable_arguments().contents, 99 | ); 100 | 101 | function.set_auto_call_type_adjustment( 102 | call.instr.address, 103 | Conf::new(&*adjusted_call_type, 192), 104 | None, 105 | ); 106 | function.add_tag( 107 | &crate::tag_type_for_view(view), 108 | tag_description, 109 | Some(call.instr.address), 110 | false, 111 | None, 112 | ); 113 | } 114 | 115 | fn ssa_variable_value_or_load_of_constant_pointer( 116 | function: &MediumLevelILFunction, 117 | var: &SSAVariable, 118 | ) -> Option { 119 | let value = function.ssa_variable_value(var); 120 | match value.state { 121 | RegisterValueType::ConstantPointerValue => return Some(value.value as u64), 122 | RegisterValueType::UndeterminedValue => {} 123 | _ => return None, 124 | } 125 | 126 | let def = function.ssa_variable_definition(var)?; 127 | let MediumLevelILLiftedInstructionKind::SetVarSsa(set_var) = def.lift().kind else { 128 | return None; 129 | }; 130 | 131 | let MediumLevelILLiftedInstructionKind::LoadSsa(LiftedLoadSsa { src, .. }) = set_var.src.kind 132 | else { 133 | return None; 134 | }; 135 | 136 | match src.kind { 137 | MediumLevelILLiftedInstructionKind::ConstPtr(Constant { constant }) => Some(constant), 138 | _ => None, 139 | } 140 | } 141 | 142 | /// If `instr` is a constant pointer or is a variable whose value is loaded from a constant pointer, 143 | /// return that pointer address. 144 | pub(crate) fn match_constant_pointer_or_load_of_constant_pointer( 145 | instr: &MediumLevelILLiftedInstruction, 146 | ) -> Option { 147 | match instr.kind { 148 | MediumLevelILLiftedInstructionKind::ConstPtr(Constant { constant }) => Some(constant), 149 | MediumLevelILLiftedInstructionKind::VarSsa(var) => { 150 | ssa_variable_value_or_load_of_constant_pointer(&instr.function, &var.src) 151 | } 152 | _ => None, 153 | } 154 | } 155 | --------------------------------------------------------------------------------