├── src ├── data_user │ └── .gitkeep ├── __init__.py ├── mlil_var_analysis │ ├── __init__.py │ └── mlil_var_origins.py ├── data │ └── default_printf_like_functions.data └── format_string_finder.py ├── tests ├── __init__.py ├── data │ ├── fs_test.bin │ ├── fs_test.o │ ├── fs_test_32.bin │ ├── Makefile │ ├── zzz_test_uninitialized_mem.c │ └── fs_test.c └── test.py ├── images └── example.gif ├── .gitignore ├── LICENSE ├── README.md ├── plugin.json └── __init__.py /src/data_user/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | from .test import run_fs_tests -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | from .format_string_finder import * -------------------------------------------------------------------------------- /src/mlil_var_analysis/__init__.py: -------------------------------------------------------------------------------- 1 | from .mlil_var_origins import * -------------------------------------------------------------------------------- /images/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vasco-jofra/format-string-finder-binja/HEAD/images/example.gif -------------------------------------------------------------------------------- /tests/data/fs_test.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vasco-jofra/format-string-finder-binja/HEAD/tests/data/fs_test.bin -------------------------------------------------------------------------------- /tests/data/fs_test.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vasco-jofra/format-string-finder-binja/HEAD/tests/data/fs_test.o -------------------------------------------------------------------------------- /tests/data/fs_test_32.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vasco-jofra/format-string-finder-binja/HEAD/tests/data/fs_test_32.bin -------------------------------------------------------------------------------- /tests/data/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | gcc -Wformat-security fs_test.c -fno-stack-protector -o fs_test.bin 3 | gcc -Wformat-security fs_test.c -fno-stack-protector -m32 -o fs_test_32.bin -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/data_user/* 2 | !**/data_user/.gitkeep 3 | 4 | **/other_tests/* 5 | 6 | .vscode/* 7 | 8 | # Byte-compiled / optimized / DLL files 9 | __pycache__/ 10 | *.py[cod] 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 jofra 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /tests/data/zzz_test_uninitialized_mem.c: -------------------------------------------------------------------------------- 1 | // ==================== 2 | // utils 3 | void putint(uint64_t a) { 4 | printf("%lu\n", a); 5 | } 6 | 7 | void test(uint64_t a, uint64_t b) { 8 | if (a > b) { 9 | putint(a); 10 | } else { 11 | putint(b); 12 | } 13 | } 14 | 15 | // ==================== 16 | // uninitialized mem tests 17 | void uninit_mem_bad_local() { 18 | uint64_t a; 19 | putint(a); 20 | } 21 | 22 | void uninit_mem_good_local() { 23 | uint64_t a = 0xdeadbeef; 24 | uint64_t b = a + 23; 25 | putint(b); 26 | } 27 | 28 | uint64_t uninit_global; 29 | void uninit_mem_good_global() { 30 | putint(uninit_global); 31 | } 32 | 33 | 34 | void uninit_mem_good_parameter(uint64_t a) { 35 | putint(a); 36 | } 37 | 38 | // ========================= 39 | // tests 40 | void test_phi_2_deep(uint64_t b, uint64_t b2, uint64_t n) { 41 | uint64_t res; 42 | if (b) { 43 | if (b2) { 44 | res = n + b; 45 | } else { 46 | res = n + b + 3; 47 | } 48 | printf("%d", res); 49 | } else { 50 | res = b2 + 18; 51 | } 52 | putint(res); 53 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Format String Finder 2 | Author: **jofra** 3 | 4 | _Finds format string vulnerabilities_ 5 | 6 | ## Description: 7 | This plugin will detect format string vulnerabilities and printf-like functions. 8 | 9 | ## Example 10 | ![](https://raw.githubusercontent.com/Vasco-jofra/format-string-finder-binja/master/images/example.gif) 11 | 12 | ## How it works 13 | 1. Loads [known functions](https://raw.githubusercontent.com/Vasco-jofra/format-string-finder-binja/master/src/data/default_printf_like_functions.data) that receive a format parameter. 14 | 2. For each xref of these functions find where the fmt parameter comes from: 15 | 1. If it comes from an **argument** we mark it as a **printf-like function** and test its xrefs 16 | 2. If it is a **constant** value located in a **read-only** area we mark it as **safe** 17 | 3. If it comes from a known **'safe' function call result** (functions from the `dgettext` family) we mark it as **safe** 18 | 4. Otherwise we mark it as **vulnerable** 19 | 3. Prints a markdown report 20 | 21 | ## Settings 22 | - `format_string_finder.should_highlight_variable_trace`: 23 | - Highlight instructions that are used in the trace of the format parameter origin. 24 | - `format_string_finder.should_enable_tests_plugin` 25 | - Enable the tests plugin. Only for development. 26 | -------------------------------------------------------------------------------- /tests/data/fs_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // ==================== 8 | // printf like functions 9 | void PRINTF_LIKE_1(char *fmt, ...) { 10 | va_list args; 11 | va_start(args, fmt); 12 | 13 | printf (fmt, args); 14 | 15 | va_end(args); 16 | } 17 | 18 | void PRINTF_LIKE_2(int n, char *fmt, ...) { 19 | va_list args; 20 | va_start(args, fmt); 21 | 22 | printf("%d", n); 23 | printf (fmt, args); 24 | 25 | va_end(args); 26 | } 27 | 28 | // ==================== 29 | // safe printf usage 30 | void SAFE_fs() { 31 | printf("%d\n", 0xdeadbeef); 32 | } 33 | 34 | void SAFE_phi(int b) { 35 | char *fmt; 36 | if (b) { 37 | fmt = "Path 1"; 38 | } else { 39 | fmt = "Path 2"; 40 | } 41 | 42 | printf(fmt); 43 | } 44 | 45 | void SAFE_phi_2_deep(int b, int b2) { 46 | char *fmt; 47 | if (b) { 48 | if (b2) { 49 | fmt = "Path 1.1"; 50 | } else { 51 | fmt = "Path 1.2"; 52 | } 53 | printf(fmt); 54 | } else { 55 | fmt = "Path 2"; 56 | } 57 | char *fmt2 = fmt; 58 | printf(fmt2); 59 | } 60 | 61 | void SAFE_fs_second_order_arg_0() { 62 | PRINTF_LIKE_1("I'm so safe\n"); 63 | } 64 | 65 | void SAFE_fs_second_order_arg_1() { 66 | PRINTF_LIKE_2(10, "I'm so very safe\n"); 67 | } 68 | 69 | // ==================== 70 | // vulnerable printf usage 71 | void VULN_fs_local() { 72 | char c[64] = {0}; 73 | read(0, c, 64); 74 | 75 | printf(c); 76 | } 77 | 78 | char g_fs[64] = "Hello"; 79 | void VULN_fs_global() { 80 | printf(g_fs); 81 | } 82 | 83 | void VULN_fs_heap() { 84 | char *f = (char *) calloc(64, 1); 85 | printf(f); 86 | } 87 | 88 | void VULN_fs_second_order_arg_0() { 89 | char c[64] = {0}; 90 | read(0, c, 64); 91 | 92 | PRINTF_LIKE_1(c); 93 | } 94 | 95 | void VULN_fs_second_order_arg_1() { 96 | char c[64] = {0}; 97 | read(0, c, 64); 98 | 99 | PRINTF_LIKE_2(10, c); 100 | } 101 | 102 | void VULN_phi(int b) { 103 | char *fmt; 104 | 105 | if (b) { 106 | read(0, fmt, 64); 107 | } else { 108 | fmt = "Path 2"; 109 | } 110 | 111 | printf(fmt); 112 | } 113 | 114 | int main() { 115 | return 0; 116 | } -------------------------------------------------------------------------------- /tests/test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from binaryninja import BinaryViewType, log_info, log_error 4 | 5 | from ..src import FormatStringFinder 6 | 7 | __all__ = ["run_fs_tests"] 8 | 9 | 10 | def run_fs_test(fpath): 11 | bv = BinaryViewType.get_view_of_file(fpath) 12 | bv.update_analysis_and_wait() 13 | 14 | fs_finder = FormatStringFinder(bv, False) 15 | fs_finder.find_format_strings() 16 | 17 | # ==================== 18 | # Prepare what should be found 19 | success = True 20 | printf_like_funcs = [x for x in bv.symbols if x.startswith("PRINTF_LIKE")] 21 | vuln_funcs = [x for x in bv.symbols if x.startswith("VULN")] 22 | safe_funcs = [x for x in bv.symbols if x.startswith("SAFE")] 23 | 24 | def test_helper(should_be_found, found, msg_type): 25 | nonlocal success 26 | 27 | for i in should_be_found: 28 | if i not in found: 29 | log_error(f"'{i}' was not detected as a {msg_type}.") 30 | success = False 31 | else: 32 | log_info(f"'{i}' successfully detected as a {msg_type}.") 33 | 34 | # ==================== 35 | # Check that every printf like function was found 36 | found_printf_like_funcs = [x.name for x in fs_finder.new_printf_like_funcs] 37 | test_helper(printf_like_funcs, found_printf_like_funcs, "printf like function") 38 | 39 | # ==================== 40 | # Check that all safe/vuln results are in safe/vuln functions 41 | found_vuln_funcs = [x.ref.function.name for x in fs_finder.results if x.is_vuln] 42 | found_safe_funcs = [x.ref.function.name for x in fs_finder.results if not x.is_vuln] 43 | test_helper(vuln_funcs, found_vuln_funcs, "vuln function") 44 | test_helper(safe_funcs, found_safe_funcs, "safe function") 45 | 46 | bv.file.close() 47 | return success 48 | 49 | 50 | def run_fs_tests(_=None): 51 | bins_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data") 52 | 53 | all_tests_succeeded = True 54 | for fname in os.listdir(bins_dir): 55 | if not fname.endswith(".bin"): 56 | continue 57 | 58 | fpath = os.path.join(bins_dir, fname) 59 | log_info("\n" + "=" * 40) 60 | log_info("Running test on binary %s" % (fpath)) 61 | 62 | success = run_fs_test(fpath) 63 | if success: 64 | log_info(f"Test passed!") 65 | else: 66 | all_tests_succeeded = False 67 | log_error(f"Test failed!") 68 | 69 | if all_tests_succeeded: 70 | log_info(f"All tests passed!") 71 | else: 72 | log_error(f"Some tests failed!") 73 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginmetadataversion": 2, 3 | "name": "Format String Finder", 4 | "author": "jofra", 5 | "type": [ 6 | "helper" 7 | ], 8 | "api": [ 9 | "python3" 10 | ], 11 | "description": "Finds format string vulnerabilities", 12 | "longdescription": "This plugin will detect format string vulnerabilities and 'format string like' functions.\n\n## Example\n![](https://raw.githubusercontent.com/Vasco-jofra/format-string-finder-binja/master/images/example.gif)\n\n## How it work\n 1. Loads [known functions](https://raw.githubusercontent.com/Vasco-jofra/format-string-finder-binja/master/src/data/default_printf_like_functions.data) that receive a format parameter.\n 2. For each xref of these functions find where the fmt parameter comes from:\n 1. If it comes from an **argument** we mark it as a **'format string like'** function and test its xrefs\n 2. If it is a **constant** value located in a **read-only** area we mark it as **safe**\n 3. If it comes from a known **'safe' function call result** (functions from the `dgettext` family) we mark it as **safe**\n 4. Otherwise we mark it as **vulnerable**\n 3. Prints a markdown report\n\n## Settings\n - `format_string_finder.should_highlight_variable_trace`:\n - Highlight instructions that are used in the trace of the format parameter origin.\n - `format_string_finder.should_enable_tests_plugin`\n - Enable the tests plugin. Only for development.", 13 | "license": { 14 | "name": "MIT", 15 | "text": "Copyright 2019 jofra\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." 16 | }, 17 | "platforms": [ 18 | "Darwin", 19 | "Windows", 20 | "Linux" 21 | ], 22 | "installinstructions": { 23 | "Darwin": "", 24 | "Windows": "", 25 | "Linux": "" 26 | }, 27 | "version": "1.0", 28 | "minimumbinaryninjaversion": 1689 29 | } -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | 4 | from binaryninja import PluginCommand, BackgroundTaskThread, BinaryViewType, Settings, log_info 5 | 6 | from .src import FormatStringFinder 7 | from .tests import run_fs_tests 8 | 9 | __all__ = [] 10 | 11 | # ==================== 12 | # Settings 13 | # Register group 14 | group_id = "format_string_finder" 15 | Settings().register_group(group_id, "Format String Finder") 16 | 17 | # Register setting 1 18 | setting_1_id = "should_highlight_variable_trace" 19 | setting_1_should_highlight_variable_trace = group_id + "." + setting_1_id 20 | setting_1_properties = { 21 | "description": 22 | "Highlight instructions that are used in the trace of the format parameter origin.", 23 | "title": "Should Highlight Variable Trace", 24 | "default": False, 25 | "type": "boolean", 26 | "id": setting_1_id 27 | } 28 | Settings().register_setting(setting_1_should_highlight_variable_trace, json.dumps(setting_1_properties)) 29 | 30 | # Register setting 2 31 | setting_2_id = "should_enable_tests_plugin" 32 | setting_2_should_enable_tests_plugin = group_id + "." + setting_2_id 33 | setting_2_properties = { 34 | "description": "Enable the tests plugin. Only for development.", 35 | "title": "Should Enable Tests Plugin", 36 | "default": False, 37 | "type": "boolean", 38 | "id": setting_2_id 39 | } 40 | Settings().register_setting(setting_2_should_enable_tests_plugin, json.dumps(setting_2_properties)) 41 | 42 | 43 | # ==================== 44 | # Plugins 45 | class RunPluginInBackground(BackgroundTaskThread): 46 | def __init__(self, func, *args): 47 | BackgroundTaskThread.__init__(self, f"Running {func.__name__}", can_cancel=True) 48 | self.func = func 49 | self.args = args 50 | 51 | def run(self): 52 | self.func(*self.args) 53 | 54 | 55 | # ==================== 56 | # find format strings plugin 57 | def plugin_fs_finder(bv): 58 | # Find format strings 59 | fs_finder = FormatStringFinder(bv, Settings().get_bool(setting_1_should_highlight_variable_trace)) 60 | fs_finder.find_format_strings() 61 | 62 | # Get results and print them in the logs view and in a markdown report 63 | md = fs_finder.get_results_string() 64 | log_info(md) 65 | 66 | md = '(use the \'Log View\' for clickable addresses)\n' + md 67 | title = f"FormatStringFinder results for {os.path.basename(bv.file.filename)}" 68 | bv.show_markdown_report(title=title, contents=md) 69 | 70 | 71 | def run_plugin_fs_finder(*args): 72 | t = RunPluginInBackground(plugin_fs_finder, *args) 73 | t.start() 74 | 75 | 76 | PluginCommand.register("Format String Finder", "Finds format string vulnerabilities", run_plugin_fs_finder) 77 | 78 | 79 | # ==================== 80 | # find format strings **test** plugin 81 | def run_fs_tests_in_background(*args): 82 | t = RunPluginInBackground(run_fs_tests, *args) 83 | t.start() 84 | 85 | 86 | # Only enable the **tests** plugin if the setting is enabled 87 | if Settings().get_bool(setting_2_should_enable_tests_plugin): 88 | PluginCommand.register( 89 | "Format String Finder: Run Tests", "Test format-string-finder", run_fs_tests_in_background 90 | ) 91 | -------------------------------------------------------------------------------- /src/data/default_printf_like_functions.data: -------------------------------------------------------------------------------- 1 | # int printf(const char *format, ...); 2 | # int fprintf(FILE *stream, const char *format, ...); 3 | # int dprintf(int fd, const char *format, ...); 4 | # int sprintf(char *str, const char *format, ...); 5 | # int snprintf(char *str, size_t size, const char *format, ...); 6 | printf 0 7 | fprintf 1 8 | dprintf 1 9 | sprintf 1 10 | snprintf 2 11 | 12 | # int vprintf(const char *format, va_list ap); 13 | # int vfprintf(FILE *stream, const char *format, va_list ap); 14 | # int vdprintf(int fd, const char *format, va_list ap); 15 | # int vsprintf(char *str, const char *format, va_list ap); 16 | # int vsnprintf(char *str, size_t size, const char *format, va_list ap); 17 | vprintf 0 18 | vfprintf 1 19 | vdprintf 1 20 | vsprintf 1 21 | vsnprintf 2 22 | 23 | # wprintf(const wchar_t *format, ...); 24 | # fwprintf(FILE *stream, const wchar_t *format, ...); 25 | # swprintf(wchar_t *wcs, size_t maxlen, const wchar_t *format, ...); 26 | # vwprintf(const wchar_t *format, va_list args); 27 | # vfwprintf(FILE *stream, const wchar_t *format, va_list args); 28 | # vswprintf(wchar_t *wcs, size_t maxlen, const wchar_t *format, va_list args); 29 | wprintf 0 30 | fwprintf 1 31 | swprintf 2 32 | vwprintf 1 33 | vfwprintf 1 34 | vswprintf 2 35 | 36 | # int asprintf(char **strp, const char *fmt, ...); 37 | # int vasprintf(char **strp, const char *fmt, va_list ap); 38 | asprintf 1 39 | vasprintf 1 40 | 41 | # int pmprintf(const char *fmt, ... /*args*/); 42 | # int pmsprintf(char *str, size_t size, const char *fmt, ... /*args*/); 43 | pmprintf 0 44 | pmsprintf 2 45 | 46 | # obstack_printf (struct obstack *obs, const char *format, ...) 47 | # obstack_vprintf (struct obstack *obs, const char *format, va_list args) 48 | obstack_printf 1 49 | obstack_vprintf 1 50 | 51 | #### __chk functions 52 | # __asprintf_chk (char **result_ptr, int flags, const char *format, ...) 53 | # __dprintf_chk (int d, int flags, const char *format, ...) 54 | # ___fprintf_chk (FILE *fp, int flag, const char *format, ...) 55 | # __fwprintf_chk (FILE *fp, int flag, const wchar_t *format, ...) 56 | # __obstack_vprintf_chk (struct obstack *obstack, int flags, const char *format, va_list args) 57 | # __obstack_printf_chk (struct obstack *obstack, int flags, const char *format, ...) 58 | # ___printf_chk (int flag, const char *format, ...) 59 | # ___snprintf_chk (char *s, size_t maxlen, int flags, size_t slen, const char *format, ...) 60 | # ___sprintf_chk (char *s, int flags, size_t slen, const char *format, ...) 61 | # __swprintf_chk (wchar_t *s, size_t n, int flag, size_t s_len, const wchar_t *format, ...) 62 | # __vasprintf_chk (char **result_ptr, int flags, const char *format, va_list args) 63 | # __vdprintf_chk (int d, int flags, const char *format, va_list arg) 64 | # ___vfprintf_chk (FILE *fp, int flag, const char *format, va_list ap) 65 | # __vfwprintf_chk (FILE *fp, int flag, const wchar_t *format, va_list ap) 66 | # ___vprintf_chk (int flag, const char *format, va_list ap) 67 | # ___vsnprintf_chk (char *s, size_t maxlen, int flags, size_t slen, const char *format, va_list args) 68 | # ___vsprintf_chk (char *s, int flags, size_t slen, const char *format, va_list args) 69 | # __vwprintf_chk (int flag, const wchar_t *format, va_list ap) 70 | # __vswprintf_chk (wchar_t *s, size_t maxlen, int flags, size_t slen, const wchar_t *format, va_list args) 71 | # __wprintf_chk (int flag, const wchar_t *format, ...) 72 | __asprintf_chk 2 73 | __dprintf_chk 2 74 | ___fprintf_chk 2 75 | __fwprintf_chk 2 76 | __obstack_vprintf_chk 2 77 | __obstack_printf_chk 2 78 | ___printf_chk 1 79 | ___snprintf_chk 4 80 | ___sprintf_chk 3 81 | __swprintf_chk 4 82 | __vasprintf_chk 2 83 | __vdprintf_chk 2 84 | ___vfprintf_chk 2 85 | __vfwprintf_chk 2 86 | ___vprintf_chk 1 87 | ___vsnprintf_chk 4 88 | ___vsprintf_chk 3 89 | __vwprintf_chk 1 90 | __vswprintf_chk 4 91 | __wprintf_chk 1 92 | 93 | #### __chk aliases 94 | # __fprintf_chk (FILE *fp, int flag, const char *format, ...) 95 | # __printf_chk (int flag, const char *format, ...) 96 | # __snprintf_chk (char *s, size_t maxlen, int flags, size_t slen, const char *format, ...) 97 | # __sprintf_chk (char *s, int flags, size_t slen, const char *format, ...) 98 | # __vfprintf_chk (FILE *fp, int flag, const char *format, va_list ap) 99 | # __vprintf_chk (int flag, const char *format, va_list ap) 100 | # __vsnprintf_chk (char *s, size_t maxlen, int flags, size_t slen, const char *format, va_list args) 101 | # __vsprintf_chk (char *s, int flags, size_t slen, const char *format, va_list args) 102 | __fprintf_chk 2 103 | __printf_chk 1 104 | __snprintf_chk 4 105 | __sprintf_chk 3 106 | __vfprintf_chk 2 107 | __vprintf_chk 1 108 | __vsnprintf_chk 4 109 | __vsprintf_chk 3 -------------------------------------------------------------------------------- /src/mlil_var_analysis/mlil_var_origins.py: -------------------------------------------------------------------------------- 1 | from binaryninja import log_info, log_warn, log_error 2 | from binaryninja import MediumLevelILFunction, MediumLevelILInstruction, SSAVariable, HighlightStandardColor, BinaryView 3 | from binaryninja import MediumLevelILOperation as MLILOperation 4 | 5 | __all__ = [ 6 | "MLILSSAVarAnalysisOrigins", "VarOriginParameter", "VarOriginConst", "VarOriginAddressOf", 7 | "VarOriginCallResult", "VarOriginUnknown", "VarOriginLoad" 8 | ] 9 | 10 | 11 | class VarOriginParameter: 12 | def __init__(self, parameter_idx): 13 | self.parameter_idx = parameter_idx 14 | 15 | def __str__(self): 16 | return f"" 17 | 18 | 19 | class VarOriginConst: 20 | def __init__(self, const): 21 | self.const = const 22 | 23 | def get_string(self, bv: BinaryView): 24 | s = bv.get_string_at(self.const, partial=True) 25 | 26 | if s: 27 | s = s.value 28 | else: 29 | # Get string with size of 3 or less (get_string_at does not return these) 30 | s = str(bv.read(self.const, 4)) 31 | if "\x00" not in s: 32 | s = None 33 | else: 34 | s = s.split("\x00", 1)[0] 35 | s = str(s) 36 | 37 | return s 38 | 39 | def __str__(self): 40 | return f"" 41 | 42 | 43 | class VarOriginAddressOf: 44 | def __init__(self, var): 45 | self.var = var 46 | 47 | def __str__(self): 48 | return f"" 49 | 50 | 51 | class VarOriginLoad: 52 | def __str__(self): 53 | return f"" 54 | 55 | 56 | class VarOriginCallResult: 57 | def __init__(self, func_name): 58 | self.func_name = func_name 59 | 60 | def __str__(self): 61 | return f"" 62 | 63 | 64 | class VarOriginUnknown: 65 | def __init__(self, msg): 66 | self.msg = msg 67 | 68 | def __str__(self): 69 | return f"" 70 | 71 | 72 | class MLILSSAVarAnalysisOrigins: 73 | def __init__(self, bv: BinaryView, mlil_ssa_func: MediumLevelILFunction): 74 | self.bv = bv 75 | self.mlil_ssa_func = mlil_ssa_func 76 | 77 | self.func = mlil_ssa_func.source_function 78 | 79 | def run(self, ssa_var: SSAVariable, should_highlight, visited=None): 80 | origins = [] 81 | if visited is None: 82 | visited = set() 83 | 84 | while True: 85 | if ssa_var in visited: 86 | # Seen a case where we had (in '/bin/dash'): 87 | # - r13_1#4 = ϕ(r13_1#3, r13_1#6) 88 | # - r13_1#6 = ϕ(r13_1#4, r13_1#5) 89 | msg = f"Found phi vars (including {ssa_var}) that depend on each other in function {ssa_var.var.function.start}. I've only seen this happen a couple of times." 90 | log_error(msg) 91 | origins.append(VarOriginUnknown(msg)) 92 | return origins 93 | visited.add(ssa_var) 94 | 95 | # Step 1: If we reach an ssa_var with version 0, it will have no more definitions 96 | if ssa_var.version == 0: 97 | is_parameter, parameter_idx = self.is_ssa_var_a_parameter(ssa_var) 98 | if is_parameter: 99 | origins.append(VarOriginParameter(parameter_idx)) 100 | else: 101 | # Var is version 0 but not a function parameter. Sometimes these are stack addrs. 102 | origins.append(VarOriginUnknown("Var is version 0 but not a function parameter")) 103 | return origins 104 | 105 | # Step 2: Get the next definition 106 | var_def_instr: MediumLevelILInstruction = self.mlil_ssa_func.get_ssa_var_definition(ssa_var) 107 | if var_def_instr is None: 108 | msg = f"{ssa_var} has no definition in function {hex(ssa_var.var.function.start)} (Not sure how this is possible)" 109 | log_error(msg) 110 | origins.append(VarOriginUnknown(msg)) 111 | return origins 112 | 113 | if should_highlight: 114 | self.func.set_user_instr_highlight( 115 | var_def_instr.address, HighlightStandardColor.OrangeHighlightColor 116 | ) 117 | 118 | # log_info(str(var_def_instr.operation) + ": " + str(var_def_instr)) 119 | 120 | # Step 3: Get the next var/vars to check 121 | if var_def_instr.operation in ( 122 | MLILOperation.MLIL_SET_VAR_SSA, MLILOperation.MLIL_SET_VAR_ALIASED 123 | ): 124 | src = var_def_instr.src 125 | if src.operation == MLILOperation.MLIL_VAR_SSA: 126 | # Keep propagating backwards 127 | ssa_var = src.src 128 | continue 129 | 130 | if src.operation in (MLILOperation.MLIL_CONST, MLILOperation.MLIL_CONST_PTR): 131 | # Found a constant 132 | origins.append(VarOriginConst(src.constant)) 133 | elif src.operation == MLILOperation.MLIL_ADDRESS_OF: 134 | origins.append(VarOriginAddressOf(src.src)) 135 | elif src.operation == MLILOperation.MLIL_LOAD_SSA: 136 | origins.append(VarOriginLoad()) 137 | else: 138 | # We are NOT interested in things like adds/subs because we are looking for either arguments or constants 139 | msg = f"{src.operation.name} for a MLIL_SET_VAR_SSA src, so we stopped propagating the chain." 140 | origins.append(VarOriginUnknown(msg)) 141 | log_warn(msg) 142 | 143 | elif var_def_instr.operation == MLILOperation.MLIL_VAR_PHI: 144 | # Find the origins of each PHI 145 | for phi_var in var_def_instr.src: 146 | origins += self.run(phi_var, should_highlight=should_highlight, visited=visited) 147 | 148 | elif var_def_instr.operation == MLILOperation.MLIL_CALL_SSA: 149 | # Found a var defined as the result of a function call 150 | func_addr = var_def_instr.dest.value.value 151 | func = self.bv.get_function_at(func_addr) 152 | if func is None: 153 | # A function call from an address that has no function? 154 | msg = f"Couldn't get function at {hex(func_addr)} (from MLIL_CALL_SSA at {var_def_instr.address})." 155 | origins.append(VarOriginUnknown(msg)) 156 | log_error(msg) 157 | else: 158 | func_name = self.bv.get_function_at(var_def_instr.dest.value.value).name 159 | origins.append(VarOriginCallResult(func_name)) 160 | 161 | else: 162 | # What is this?? 163 | msg = f"{var_def_instr.operation.name} not supported at {hex(var_def_instr.address)}" 164 | origins.append(VarOriginUnknown(msg)) 165 | log_error(msg) 166 | 167 | return origins 168 | 169 | # ==================== 170 | def is_var_a_parameter(self, var): 171 | for i, it in enumerate(self.func.parameter_vars): 172 | if var == it: 173 | return True, i 174 | return False, None 175 | 176 | def is_ssa_var_a_parameter(self, ssa_var): 177 | return self.is_var_a_parameter(ssa_var.var) 178 | 179 | 180 | # For testing 181 | if __name__ == "__console__": 182 | log_info("\n====================\nOrigins") 183 | analysis = MLILSSAVarAnalysisOrigins(bv, current_mlil.ssa_form) # pylint: disable=undefined-variable 184 | origs = analysis.run(current_mlil.ssa_form[26].operands[2][0].src, True) # pylint: disable=undefined-variable 185 | log_info(str([str(i) for i in origs])) -------------------------------------------------------------------------------- /src/format_string_finder.py: -------------------------------------------------------------------------------- 1 | import os 2 | import subprocess 3 | 4 | from binaryninja import log_debug, log_error, log_info, log_warn 5 | from binaryninja import BinaryView, SymbolType, SymbolBinding, HighlightStandardColor 6 | from binaryninja import MediumLevelILOperation as MLILOperation 7 | 8 | from .mlil_var_analysis import MLILSSAVarAnalysisOrigins, VarOriginConst, VarOriginParameter, VarOriginCallResult 9 | 10 | __all__ = ["FormatStringFinder"] 11 | 12 | 13 | class PrintfLikeFunction: 14 | BASE_DIR = os.path.dirname(os.path.realpath(__file__)) 15 | DATA_DIR = os.path.join(BASE_DIR, "data") 16 | USER_DATA_DIR = os.path.join(BASE_DIR, "data_user") 17 | 18 | def __init__(self, name, parameter_index): 19 | self.name = name 20 | self.parameter_index = parameter_index 21 | 22 | def __hash__(self): 23 | return hash((self.name, self.parameter_index)) 24 | 25 | def __eq__(self, other): 26 | return self.name == other.name and self.parameter_index == other.parameter_index 27 | 28 | def __str__(self): 29 | return f"" 30 | 31 | @staticmethod 32 | def load_file(fpath): 33 | if not os.path.isfile(fpath): 34 | raise FileNotFoundError(f"Path {fpath} not found") 35 | 36 | res = [] 37 | with open(fpath, "r") as f: 38 | for l in f: 39 | l = l.strip() 40 | if l == "" or l.startswith("#"): 41 | continue 42 | name, param_idx = l.split(" ") 43 | res.append(PrintfLikeFunction(name.strip(), int(param_idx))) 44 | return res 45 | 46 | @staticmethod 47 | def load_all(): 48 | """ 49 | Load all printf like functions from the '/data' and '/data_user' dirs. 50 | """ 51 | res = [] 52 | dirs_to_load = [PrintfLikeFunction.DATA_DIR, PrintfLikeFunction.USER_DATA_DIR] 53 | 54 | for d in dirs_to_load: 55 | for fname in os.listdir(d): 56 | fpath = os.path.join(d, fname) 57 | res += PrintfLikeFunction.load_file(fpath) 58 | 59 | return res 60 | 61 | @staticmethod 62 | def save_to_user_data(fname, printf_like_funcs): 63 | fpath = os.path.join(PrintfLikeFunction.USER_DATA_DIR, fname) 64 | with open(fpath, "w") as f: 65 | for func in printf_like_funcs: 66 | f.write(f"{func.name} {func.parameter_index}\n") 67 | 68 | 69 | class FormatStringFinderResult: 70 | def __init__(self, bv: BinaryView, ref): 71 | self.bv = bv 72 | self.ref = ref 73 | 74 | # If we never actually set the result because of some error 75 | self.failed = True 76 | 77 | # The result content 78 | self.is_vuln = None 79 | self.safe_origins = None 80 | self.vuln_origins = None 81 | 82 | def set_result(self, safe_origins, vuln_origins): 83 | self.failed = False 84 | 85 | self.safe_origins = safe_origins 86 | self.vuln_origins = vuln_origins 87 | self.is_vuln = (len(vuln_origins) != 0) 88 | 89 | def __str__(self): 90 | msg = f"{hex(self.ref.address)} : " 91 | 92 | if self.failed: 93 | msg += "failed analysis" 94 | else: 95 | if self.is_vuln: 96 | msg += "VULN " 97 | msg += str([str(i) for i in self.vuln_origins]) 98 | else: 99 | msg += "SAFE " 100 | 101 | safe_origins_str = [] 102 | for orig in self.safe_origins: 103 | if isinstance(orig, VarOriginConst): 104 | format_str = orig.get_string(self.bv) 105 | if format_str is None: 106 | format_str = str(orig) 107 | else: 108 | format_str = repr(format_str) 109 | 110 | safe_origins_str.append(format_str) 111 | else: 112 | msg += str(orig) 113 | msg += ", ".join(safe_origins_str) 114 | 115 | return msg 116 | 117 | 118 | class FormatStringFinder: 119 | def __init__(self, bv: BinaryView, should_highlight): 120 | self.bv = bv 121 | 122 | self.safe_functions = [ 123 | "gettext", 124 | "dgettext", 125 | "dcgettext", 126 | "ngettext", 127 | "dngettext", 128 | "dcngettext", 129 | ] 130 | self.should_highlight = should_highlight 131 | 132 | self.results = [] 133 | self.new_printf_like_funcs = set() 134 | self.heuristic_vul_function_ptr_calls = set() 135 | 136 | def find_format_strings(self, CHECK_WITH_READELF=False): 137 | visited = set() 138 | to_visit = [] 139 | 140 | # ==================== 141 | # Step 0: Get all hardcoded known printf_like functions 142 | to_visit = PrintfLikeFunction.load_all() 143 | # @@TODO: We could look for refs of strings with '%s', '%d'... and if they are the parameter of an external function, add those as 'printf like' 144 | 145 | while to_visit: 146 | printf_like_func = to_visit.pop(0) 147 | 148 | # Sometimes, due to saving printf_like_funcs in a file to later reload we get repeated entries 149 | if printf_like_func in visited: 150 | log_debug("Skipping analysis of duplicate printf_like_func ' %s '" % printf_like_func.name) 151 | continue 152 | visited.add(printf_like_func) 153 | 154 | syms = self.get_symbols_by_raw_name(printf_like_func.name) 155 | if not syms: 156 | if printf_like_func.name.startswith("sub_"): 157 | log_error(f"No symbol found for function '{printf_like_func.name}'") 158 | continue 159 | 160 | log_debug(f"\n===== {printf_like_func} =====") 161 | log_debug(f" syms: {syms} =====") 162 | 163 | # @@TODO: Add arg name 'format' and type 'char*' to the format var (Tried before but arg and var get disconnected sometimes. Likely a bug.) 164 | 165 | # Get every ref for this symbol(s) 166 | refs = [] 167 | for sym in syms: 168 | it_refs = self.bv.get_code_refs(sym.address) 169 | 170 | # readelf check to get a second opinion 171 | if CHECK_WITH_READELF and sym.type == SymbolType.ExternalSymbol: 172 | self.check_relocations_with_readelf(sym, syms, it_refs) 173 | 174 | refs += it_refs 175 | 176 | # ==================== 177 | # Step 1: Check each xref for vulns 178 | for ref in refs: 179 | log_debug(f"Analyzing xref {hex(ref.address)}") 180 | ref_result = FormatStringFinderResult(self.bv, ref) 181 | self.results.append(ref_result) 182 | 183 | # ==================== 184 | # Step 1.0: Sanity checks 185 | mlil_instr = self.get_mlil_instr(ref.function, ref.address) 186 | if not mlil_instr: 187 | continue 188 | 189 | # Check for known unhandled operations 190 | if mlil_instr.operation in ( 191 | MLILOperation.MLIL_CALL_UNTYPED, MLILOperation.MLIL_TAILCALL_UNTYPED 192 | ): 193 | log_debug("@@TODO: How to handle MLIL_CALL_UNTYPED and MLIL_TAILCALL_UNTYPED?") 194 | continue 195 | elif mlil_instr.operation in (MLILOperation.MLIL_SET_VAR, MLILOperation.MLIL_STORE): 196 | # Our xref is being used to set a var and not in a call. 197 | # @@TODO: Maybe we could try to find if it is called close by and use that as an xref 198 | continue 199 | 200 | # If it wasn't one of the above, it must be one of these 201 | if mlil_instr.operation not in (MLILOperation.MLIL_CALL, MLILOperation.MLIL_TAILCALL): 202 | assert False, f"mlil operation '{mlil_instr.operation.name}' is unsupported @ {hex(ref.address)}" 203 | 204 | # @@TODO: Can we force it to have the necessary arguments? Looking at the calling convention? 205 | if printf_like_func.parameter_index >= len(mlil_instr.params): 206 | log_error( 207 | f"{hex(ref.address)} : parameter nr {printf_like_func.parameter_index} for " 208 | f"function call of '{printf_like_func.name}' is not available" 209 | ) 210 | continue 211 | 212 | if self.should_highlight: 213 | ref.function.set_user_instr_highlight( 214 | ref.address, HighlightStandardColor.RedHighlightColor 215 | ) 216 | # ==================== 217 | # Step 1.1: Find the origins of the format parameter for this xref 218 | fmt_param = mlil_instr.ssa_form.params[printf_like_func.parameter_index] 219 | 220 | if fmt_param.operation in (MLILOperation.MLIL_CONST, MLILOperation.MLIL_CONST_PTR): 221 | # Handle immediate constants 222 | var_origins = [VarOriginConst(fmt_param.constant)] 223 | elif fmt_param.operation in (MLILOperation.MLIL_VAR_SSA, MLILOperation.MLIL_VAR_ALIASED): 224 | # @@TODO: What is the meaning of 'MLILOperation.MLIL_VAR_ALIASED' ? 225 | # Find the origins of the variable 226 | fmt_ssa = fmt_param.src 227 | mlil_ssa = ref.function.medium_level_il.ssa_form 228 | 229 | # Get the var origins. Can be a parameter, a const, an address of another var... 230 | var_origins = MLILSSAVarAnalysisOrigins(self.bv, 231 | mlil_ssa).run(fmt_ssa, self.should_highlight) 232 | else: 233 | assert False, f"ERROR: fmt_param.operation is {fmt_param.operation.name} @ {hex(ref.address)}" 234 | 235 | if var_origins is None: 236 | log_warn(f"{hex(ref.address)} : Failed to get origins of the format parameter") 237 | continue 238 | 239 | # ==================== 240 | # Step 1.2: Determine if the origins are safe or vulnerable 241 | # Case 1: If any origin is an argument -> PRINTF_LIKE 242 | # Case 2: If any is NOT a read-only constant or a parameter -> VULN 243 | # Case 3: If all are an arg or a const -> SAFE 244 | vuln_origins = [] 245 | safe_origins = [] 246 | 247 | for orig in var_origins: 248 | if isinstance(orig, VarOriginParameter): 249 | safe_origins.append(orig) 250 | 251 | # Add as a printf like function 252 | new_printf_like = PrintfLikeFunction(ref.function.name, orig.parameter_idx) # pylint: disable=no-member 253 | to_visit.append(new_printf_like) 254 | self.new_printf_like_funcs.add(new_printf_like) 255 | 256 | # Create a symbol for the new printf like function if it does not exist 257 | if not self.bv.get_symbols_by_name(ref.function.name): 258 | ref.function.name = ref.function.name 259 | 260 | elif isinstance(orig, VarOriginConst) and self.is_addr_read_only(orig.const): 261 | safe_origins.append(orig) 262 | 263 | elif isinstance(orig, VarOriginCallResult) and orig.func_name in self.safe_functions: # pylint: disable=no-member 264 | # We accept that 'dcgettext' is safe because you need root to control the translation 265 | safe_origins.append(orig) 266 | 267 | else: 268 | vuln_origins.append(orig) 269 | 270 | ref_result.set_result(safe_origins, vuln_origins) 271 | log_debug(str(ref_result)) 272 | 273 | # ==================== 274 | # Step 2: Heuristic to find function pointer calls that might me vulnerable 275 | self.heuristic_look_for_vul_function_ptr_calls(mlil_instr, var_origins) 276 | 277 | # ==================== 278 | # Step 3: Save the exported functions to a file so other files that import them know they are printf like 279 | exported_printf_like_funcs = [] 280 | for func in self.new_printf_like_funcs: 281 | syms = self.bv.get_symbols_by_name(func.name) 282 | if not syms: 283 | continue 284 | 285 | for s in syms: 286 | if s.type == SymbolType.FunctionSymbol and s.binding == SymbolBinding.GlobalBinding: 287 | log_info(f"Saving exported function '{func.name}' to user_data") 288 | exported_printf_like_funcs.append(func) 289 | break 290 | 291 | if exported_printf_like_funcs: 292 | fname = os.path.basename(self.bv.file.filename) 293 | PrintfLikeFunction.save_to_user_data(fname, exported_printf_like_funcs) 294 | 295 | def heuristic_look_for_vul_function_ptr_calls(self, mlil_instr, var_origins): 296 | """ 297 | Looks for things like: 298 | ```C 299 | printf("%s", input); 300 | this->debug_func(input); 301 | ``` 302 | We don't know that `this->debug_func` receives a format string (and so our analysis fails) This simple heuristic finds these cases and we can then check by hand. 303 | """ 304 | if len(var_origins) != 1: 305 | return 306 | 307 | orig = var_origins[0] 308 | if not isinstance(orig, VarOriginConst) or not self.is_addr_read_only(orig.const): 309 | return 310 | 311 | orig_str = orig.get_string(self.bv) 312 | if not orig_str: 313 | return 314 | 315 | # ==================== 316 | # Restrict based on the string content 317 | if "%" not in orig_str: 318 | return 319 | 320 | # Ensure the string contains %s and that is no larger than 5 chars 321 | # This tries to include things like '%s\n' and '%s.\n' 322 | if len(orig_str) > 5 or "%s" not in orig_str: 323 | return 324 | 325 | # ==================== 326 | # Look for calls using registers in the current and next few basic blocks 327 | # If we find one it might be a vulnerable call 328 | # @@TODO: We could check if the orig of one of its params is the same as the param after the fmt string in the original ref 329 | main_bb = mlil_instr.il_basic_block 330 | bbs_to_check = [main_bb] + [x.target for x in main_bb.outgoing_edges] 331 | 332 | for bb in bbs_to_check: 333 | for instr in bb: 334 | if instr.operation == MLILOperation.MLIL_CALL and instr.dest.operation == MLILOperation.MLIL_VAR: 335 | log_debug(f"Heuristic finding {hex(instr.address)}") 336 | self.heuristic_vul_function_ptr_calls.add(instr.address) 337 | 338 | def get_results_string(self): 339 | failed_results = [x for x in self.results if x.is_vuln is None] 340 | vuln_results = [x for x in self.results if x.is_vuln is True] 341 | safe_results = [x for x in self.results if x.is_vuln is False] 342 | 343 | md = "" 344 | md += f"# Format String Finder results for '{self.bv.file.filename}'\n" 345 | 346 | # Summary 347 | md += f" - Found {len(vuln_results)} vuln calls\n" 348 | md += f" - Found {len(safe_results)} safe calls\n" 349 | md += f" - Found {len(failed_results)} failed analysis\n" 350 | md += "\n" 351 | 352 | # Print the printf like functions we found 353 | md += "## Printf like functions\n" 354 | for i in self.new_printf_like_funcs: 355 | if i.name.startswith("sub_"): 356 | addr = int(i.name.replace("sub_", ""), 16) 357 | else: 358 | addr = self.bv.get_symbol_by_raw_name(i.name).address 359 | md += f" - {i.name} ( {hex(addr)} )\n" 360 | md += "\n" 361 | 362 | # Vulnerable results 363 | md += "## Vulnerable calls\n" 364 | for res in vuln_results: 365 | md += f" - {res}\n" 366 | md += "\n" 367 | 368 | # Heuristic findings 369 | if self.heuristic_vul_function_ptr_calls: 370 | md += "## Heuristic findings\n" 371 | for addr in self.heuristic_vul_function_ptr_calls: 372 | md += f" - {hex(addr)}\n" 373 | md += "\n" 374 | 375 | # Failed results 376 | if failed_results: 377 | md += "## Failed analysis\n" 378 | for res in failed_results: 379 | md += f" - {res}\n" 380 | md += "\n" 381 | 382 | # Safe results 383 | md += "## Safe calls\n" 384 | for res in safe_results: 385 | md += f" - {res}\n" 386 | md += "\n" 387 | 388 | # Then print every result per function analyzed 389 | return md 390 | 391 | # ==================== 392 | # Helpers 393 | def is_addr_read_only(self, addr): 394 | return self.bv.is_offset_readable(addr) and not self.bv.is_offset_writable(addr) 395 | 396 | def get_symbols_by_raw_name(self, name): 397 | """ 398 | API only has 'get_symbol_by_raw_name' and 'get_symbols_by_name'. The later demangles names (we don't want this here) 399 | """ 400 | syms = self.bv.symbols.get(name) 401 | if not syms: # pylint: disable=no-else-return 402 | return [] 403 | elif not isinstance(syms, list): 404 | return [syms] 405 | else: 406 | return syms 407 | 408 | def get_mlil_instr(self, func, addr): 409 | llil_instr = func.get_low_level_il_at(addr) 410 | if not llil_instr: 411 | log_error( 412 | f"Couldn't get llil_instr at {hex(addr)}" 413 | f" (Could be because there are WRONG repeated xrefs for overlaping funcs)" 414 | f" (last time this happened it was (issue #1196) (should be fixed now)" 415 | ) 416 | return None 417 | 418 | mlil_instr = llil_instr.medium_level_il 419 | if not mlil_instr: 420 | log_warn(f"Couldn't get mlil_instr from llil_instr at {hex(addr)} (probably was not a call)") 421 | return None 422 | 423 | return mlil_instr 424 | 425 | def check_relocations_with_readelf(self, syms, sym, refs): 426 | cmd = 'readelf -r "%s" | grep -e " %s"' % (self.bv.file.filename, sym.name[:22]) 427 | output = subprocess.check_output(cmd, shell=True).strip() 428 | readelf_reloc_addrs = {x.address for x in refs} 429 | for i in output.split("\n"): 430 | readelf_reloc_addrs.add(int(i.split(" ", 1)[0], 16) - 1) 431 | readelf_nr_relocs = len(readelf_reloc_addrs) 432 | 433 | # Ensure we only account once for each xref address (because of overlaping funcs) 434 | binja_reloc_addrs = {x.address for x in refs} 435 | binja_nr_relocs = len(binja_reloc_addrs) 436 | 437 | # Because there is a xref to the GOT which binja removes (I think) 438 | if len(syms) > 1: 439 | binja_nr_relocs += 1 440 | 441 | msg = "External symbol: relocs amount for '%s': Readelf=%-2d; Binja=%-2d" % ( 442 | sym.name, readelf_nr_relocs, binja_nr_relocs 443 | ) 444 | if readelf_nr_relocs != binja_nr_relocs: 445 | log_error(msg) 446 | log_error( 447 | "Different is: %s" % 448 | " ".join([hex(x) for x in readelf_reloc_addrs.difference(binja_reloc_addrs)]) 449 | ) 450 | 451 | if not (binja_nr_relocs <= readelf_nr_relocs): 452 | log_warn("Such ninja") --------------------------------------------------------------------------------