├── Cutter ├── __init__.py └── get_rust_libs_cutter.py ├── requirements-ida.txt ├── requirements-rizin.txt ├── config.ini ├── IDA └── get_rust_libs_ida.py ├── Rizin └── get_rust_libs_rizin.py ├── .gitignore ├── README.md └── libs2sigs.py /Cutter/__init__.py: -------------------------------------------------------------------------------- 1 | from .get_rust_libs_cutter import create_cutter_plugin -------------------------------------------------------------------------------- /requirements-ida.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4==4.11.1 2 | requests==2.28.1 3 | rust_demangler==1.0 4 | -------------------------------------------------------------------------------- /requirements-rizin.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4==4.11.1 2 | requests==2.28.1 3 | rust_demangler==1.0 4 | rzpipe==0.4.0 -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | [Project] 2 | name = rust_codes 3 | path = path/to/project 4 | [Generator] 5 | pat = path/to/generator 6 | sigmake = path/to/sigmake 7 | 8 | [Target] 9 | arch = x86_64-unknown-linux-gnu -------------------------------------------------------------------------------- /IDA/get_rust_libs_ida.py: -------------------------------------------------------------------------------- 1 | import idautils 2 | import re 3 | import libs2sigs 4 | 5 | sc = idautils.Strings() 6 | 7 | pattern = re.compile(r'([\w\d\-_]+)-(\d\.\d+\.\d+)') 8 | libs = set(re.findall(pattern, ''.join(map(str, sc)))) 9 | 10 | print('Found %d libraries!' % len(libs)) 11 | 12 | for lib, version in libs: 13 | print('%s = "%s"' % (lib, version)) 14 | 15 | libs2sigs.rlib_to_sig(libs) -------------------------------------------------------------------------------- /Rizin/get_rust_libs_rizin.py: -------------------------------------------------------------------------------- 1 | import re 2 | import rzpipe 3 | 4 | import libs2sigs 5 | 6 | binary_path = './lib.so' 7 | pattern = re.compile(r'([\w\d\-_]+)-(\d+\.\d+\.\d+)') 8 | 9 | rz = rzpipe.open(binary_path) 10 | rz.cmd('aa') 11 | 12 | libs = set(re.findall(pattern, rz.cmd('izQ'))) 13 | 14 | print('Found %d libraries!' % len(libs)) 15 | 16 | for lib, version in libs: 17 | print('%s = "%s"' % (lib, version)) 18 | 19 | libs2sigs.rlib_to_sig(libs, 'rizin') -------------------------------------------------------------------------------- /Cutter/get_rust_libs_cutter.py: -------------------------------------------------------------------------------- 1 | import cutter 2 | import libs2sigs 3 | import re 4 | 5 | from PySide2.QtCore import QObject, SIGNAL 6 | from PySide2.QtWidgets import QAction 7 | 8 | 9 | class DockWidget(cutter.CutterDockWidget): 10 | def __init__(self, parent, action): 11 | super(DockWidget, self).__init__(parent, action) 12 | QObject.connect(cutter.core(), SIGNAL( 13 | "seekChanged(RVA)"), self.update_contents) 14 | 15 | def update_contents(self): 16 | pattern = re.compile(r'([\w\d\-_]+)-(\d\.\d+\.\d+)') 17 | libs = set(re.findall(pattern, cutter.cmd('izQ'))) 18 | 19 | libs2sigs.rlib_to_sig(libs, 'rizin') 20 | 21 | 22 | class GetRlibsPlugin(cutter.CutterPlugin): 23 | name = "Rlibs2Sigs Plugin" 24 | description = "This plugin creates signatures for binary's rust libs" 25 | version = "1.0" 26 | author = "R3v0LT" 27 | 28 | def setupPlugin(self): 29 | pass 30 | 31 | def setupInterface(self, main): 32 | action = QAction("My Plugin", main) 33 | action.setCheckable(True) 34 | widget = DockWidget(main, action) 35 | main.addPluginDockWidget(widget, action) 36 | 37 | def terminate(self): 38 | pass 39 | 40 | 41 | def create_cutter_plugin(): 42 | return GetRlibsPlugin() 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rlibs2sigs 2 | 3 | Plugin for IDA / Cutter / Rizin. Generate signatures for compiled binaries written in Rust using strings containing the names and versions of the used libraries 4 | 5 | ## Requirements 6 | 7 | For IDA plugin: 8 | 9 | ```bash 10 | pip install -r requirements-ida.txt 11 | ``` 12 | 13 | For Rizin / Cutter plugins: 14 | 15 | ```bash 16 | pip install -r requirements-rizin.txt 17 | ``` 18 | 19 | Before running each script, you need to set the necessary parameters in `config.ini`. The `arch` field can be left empty. The `pat` and `sigmake` fields are needed when starting in IDA Pro. They specifies the paths from the generator of the `.pat` file and `sigmake` binary. In other cases, you don't need to set them 20 | 21 | In `libs2sigs.py` you must specify path to `config.ini` in `rlib_to_sig` function 22 | 23 | ## Installion and usage 24 | 25 | ### IDA Pro 26 | 27 | Run script (alt+f7) `get_rust_libs_ida.py` from IDA Pro after its analyze of necessary binary 28 | 29 | ### Rizin 30 | 31 | Run `get_rust_libs_rizin.py` script: 32 | 33 | ```bash 34 | python get_rust_libs_rizin.py 35 | ``` 36 | 37 | ### Cutter 38 | 39 | 1. Add to startup options of `Cutter.exe` flag `--no-output-redirect` or use it when running from console 40 | 2. Copy `libs2sigs.py` into `get_rust_libs_cutter` directory 41 | 3. Move it to directory for Cutter Python plugins (`path/to/Cutter/plugins/python` by default) 42 | 4. Script will run automatically after starting `Cutter` 43 | 44 | --- 45 | 46 | Плагин для IDA / Cutter / Rizin. Создает сигнатуры для собранных бинарных файлов на Rust на основе строк, содержащих названия и версии используемых библиотек 47 | 48 | ## Подготовка 49 | 50 | Для использования с IDA: 51 | 52 | ```bash 53 | pip install -r requirements-ida.txt 54 | ``` 55 | 56 | Для использования с Rizin / Cutter: 57 | 58 | ```bash 59 | pip install -r requirements-rizin.txt 60 | ``` 61 | 62 | Перед запуском каждого скрипта нужно указать нужные параметры в `config.ini`. В этом файле поле `arch` можно оставлять пустым. Поля `pat` и `sigmake` нужны при запуске в IDA Pro. Там указываются пути по генератора `.pat` файла и до `sigmake`. В остальных случаях можно не указывать 63 | 64 | В файле `libs2sigs.py` необходимо указать путь до `config.ini` в функции `rlib_to_sig` 65 | 66 | ## Установка и использование 67 | 68 | ### IDA Pro 69 | 70 | Вызвать скрипт (alt+f7) `get_rust_libs_ida.py` из IDA Pro, открыв нужный файл для анализа 71 | 72 | ### Rizin 73 | 74 | Вызвать скрипт `get_rust_libs_rizin.py`: 75 | 76 | ```bash 77 | python get_rust_libs_rizin.py 78 | ``` 79 | 80 | ### Cutter 81 | 82 | 1. Добавить в параметры запуска `Cutter` или запускать через консоль с флагом `--no-output-redirect` 83 | 2. Скопировать `libs2sigs.py` в папку `get_rust_libs_cutter` 84 | 3. Перенести эту папку в директорию для Python плагинов (обычно `path/to/Cutter/plugins/python`) 85 | 4. Скрипт запустится автоматически 86 | -------------------------------------------------------------------------------- /libs2sigs.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | import os 3 | import re 4 | import subprocess 5 | import textwrap 6 | 7 | import requests 8 | from bs4 import BeautifulSoup 9 | from rust_demangler import demangle 10 | 11 | EXTERNS = set() 12 | EXAMPLE_FUNCS = list() 13 | LIB_FUNCS = list() 14 | USINGS = dict() 15 | RUST_PROJ_NAME = '' 16 | RUST_PROJ_PATH = '' 17 | PAT_GENERATOR_PATH = '' 18 | SIGMAKE_PATH = '' 19 | ARCH = '' 20 | 21 | VARIABLES = {'bool': 'boolean', 22 | 'u8': 'ubyte', 'u16': 'uword', 'u32': 'udword', 'u64': 'uqword', 23 | 'i8': 'byte', 'i16': 'word', 'i32': 'dword', 'i64': 'qword', 24 | 'f32': 'float', 'f64': 'double', 25 | 'char': 'character', '&str': 'string1', 'String': 'string2', 26 | '&[u8]': 'ref_arr_u8', '&[u16]': 'ref_arr_u16', 27 | '&[u32]': 'ref_arr_u32', '&[u64]': 'ref_arr_u64', 28 | '&[i8]': 'ref_arr_i8', '&[i16]': 'ref_arr_i16', 29 | '&[i32]': 'ref_arr_i32', '&[i64]': 'ref_arr_i64', 30 | '&mut [u8]': 'mut_u8', '&mut [u16]': 'mut_u16', 31 | '&mut [u32]': 'mut_u32', '&mut [u64]': 'mut_u64', 32 | '&mut [i8]': 'mut_i8', '&mut [i16]': 'mut_i16', 33 | '&mut [i32]': 'mut_i32', '&mut [i64]': 'mut_i64', 34 | '&mut str': 'mut_str', '&mut String': 'mut_str2', '&char': 'mut_char', 35 | 'usize': 'arch_uint', 'isize': 'arch_int', 36 | '&usize': 'arch_ref_uint', '&isize': 'arch_ref_int', 37 | '&mut usize': 'arch_ref_mut_uint', '&mut isize': 'arch_ref_mut_int'} 38 | 39 | DEFINES = ( 40 | 'let mut mut_str = String::from("123").as_mut();', 41 | 'let mut mut_str2 = &mut String::from("123");', 42 | 'let boolean = true;', 43 | 'let ubyte = 1u8;', 44 | 'let uword = 1u16;', 45 | 'let udword = 1u32;', 46 | 'let uqword = 1u64;', 47 | 'let byte = 1i8;', 48 | 'let word = 1i16;', 49 | 'let dword = 1i32;', 50 | 'let qword = 1i64;', 51 | 'let float = 1f32;', 52 | 'let double = 1f64;', 53 | "let character = '1';", 54 | 'let string1 = "123";', 55 | 'let string2 = String::from("123");', 56 | 'let ref_arr_u8 = &[1u8, 2u8];', 57 | 'let ref_arr_u16 = &[1u16, 2u16];', 58 | 'let ref_arr_u32 = &[1u32, 2u32];', 59 | 'let ref_arr_u64 = &[1u64, 2u64];', 60 | 'let ref_arr_i8 = &[1i8, 2i8];', 61 | 'let ref_arr_i16 = &[1i16, 2i16];', 62 | 'let ref_arr_i32 = &[1i32, 2i32];', 63 | 'let ref_arr_i64 = &[1i64, 2i64];', 64 | 'let mut mut_u8 = &mut [1u8, 2u8];', 65 | 'let mut mut_u16 = &mut [1u16, 2u16];', 66 | 'let mut mut_u32 = &mut [1u32, 2u32];', 67 | 'let mut mut_u64 = &mut [1u64, 2u64];', 68 | 'let mut mut_i8 = &mut [1i8, 2i8];', 69 | 'let mut mut_i16 = &mut [1i16, 2i16];', 70 | 'let mut mut_i32 = &mut [1i32, 2i32];', 71 | 'let mut mut_i64 = &mut [1i64, 2i64];', 72 | "let mut mut_char = 'b';", 73 | 'let arch_uint: usize = 8;', 74 | 'let arch_int: isize = 8;', 75 | 'let arch_ref_uint: &usize = &8;', 76 | 'let arch_ref_int: &isize = &8;', 77 | 'let mut arch_ref_mut_uint: &mut usize = &mut 8;', 78 | 'let mut arch_ref_mut_int: &mut isize = &mut 8;', 79 | ) 80 | 81 | 82 | def get_lib_funcs(lib, ver): 83 | '''Get all functions html path''' 84 | 85 | url = 'https://docs.rs/{}/{}' 86 | 87 | r = requests.get(url.format(lib, ver) + '/#functions') 88 | 89 | soup = BeautifulSoup(r.text, 'html.parser') 90 | 91 | result = [] 92 | for link in soup.find_all('a', attrs={'class': 'fn'}): 93 | result.append(link.get('href')) 94 | 95 | return result 96 | 97 | 98 | def get_lib_funcs_code(funcs, lib, ver): 99 | '''Get all fuctions code from html page''' 100 | 101 | full_code = [] 102 | 103 | for func in funcs: 104 | func_code = get_func_code(lib, ver, func) 105 | 106 | if func_code: 107 | new_code = update_func(lib, func_code, func) 108 | full_code.append(new_code) 109 | 110 | if len(full_code) == 0: 111 | func_code = get_func_code(lib, ver) 112 | if func_code: 113 | new_code = update_func(lib, func_code, 'fn.main.html') 114 | full_code.append(new_code) 115 | 116 | return full_code 117 | 118 | 119 | def update_func(lib, func_code, func): 120 | '''Change some function code to be called later in lib.rs''' 121 | 122 | for i, code in enumerate(func_code): 123 | replace_name = f'{lib.replace("-", "_")}_{func[3:-5]}_example_{i}' 124 | EXAMPLE_FUNCS.append(replace_name) 125 | 126 | code = code.replace('Box', 'Box') \ 127 | .replace('struct', 'pub struct') \ 128 | .replace(';Run', ';') 129 | 130 | if 'main' in code: 131 | code = code.replace('fn main', f'pub fn {replace_name}') 132 | 133 | return strip_externs(code) 134 | 135 | func_name = re.search(r'^fn ([\w\d_]+\()', code, re.MULTILINE) 136 | pub_func_name = re.search(r'^pub fn ([\w\d_]+\()', code, re.MULTILINE) 137 | 138 | if pub_func_name: 139 | code = code.replace( 140 | pub_func_name.group(1), f'{replace_name}(') 141 | 142 | elif func_name: 143 | code = code.replace( 144 | func_name.group(0), f'pub fn {replace_name}(') 145 | 146 | else: 147 | code = f'pub fn {replace_name}() {{\n' + code + '\n}' 148 | 149 | return strip_externs(code) 150 | 151 | 152 | def get_func_code(lib: str, ver: str, func=''): 153 | '''Get single function code from it's html page''' 154 | 155 | url = f'https://docs.rs/{lib}/{ver}/{lib.replace("-", "_")}/{func}' 156 | 157 | soup, examples = get_example(url) 158 | 159 | check_template(soup, lib) 160 | 161 | if examples: 162 | return map(lambda x: x.text, examples) 163 | 164 | 165 | return None 166 | 167 | def get_example(url): 168 | r = requests.get(url) 169 | soup = BeautifulSoup(r.text, 'html.parser') 170 | 171 | examples = soup.find_all( 172 | 'pre', attrs={'class': 'rust rust-example-rendered'}) 173 | 174 | return soup, examples 175 | 176 | def check_template(soup: BeautifulSoup, lib): 177 | fn_template = soup.find_all('pre', attrs={'class': 'rust fn'}) 178 | 179 | fn_regex = re.compile(r'([\w\d]|>)\((.*)\)( ->|\n|$)') 180 | type_regex = re.compile(r': ([&\[\]\w\d \'<>]+)') 181 | fn_name_regex = re.compile(r'fn ([\w\d_]+)|([\w\d_])+<.*>\(') 182 | fn_name = str() 183 | types = list() 184 | 185 | for tmpl in fn_template: 186 | args = re.search(fn_regex, tmpl.text) 187 | fn_name = re.search(fn_name_regex, tmpl.text).group(1) 188 | 189 | if fn_name == '': 190 | return False 191 | 192 | if args.group(2): 193 | types = re.findall(type_regex, args.group(2)) 194 | else: 195 | types = [] 196 | 197 | args = [] 198 | for t in types: 199 | lifetime = re.findall(r'\'\w+ ', t) 200 | if lifetime: 201 | t = t.replace(lifetime[0], '') 202 | 203 | if t in VARIABLES.keys(): 204 | args.append(VARIABLES[t]) 205 | else: 206 | print(f'Unexpected type: {t}') 207 | return False 208 | 209 | if fn_name != '': 210 | LIB_FUNCS.append(f"{lib.replace('-', '_')}::{fn_name}({', '.join(args)});") 211 | USINGS[lib].append(fn_name) 212 | 213 | return True 214 | 215 | 216 | def strip_externs(code): 217 | '''Strip externs from modules and save them for use in lib.rs''' 218 | 219 | lines = code.split('\n') 220 | 221 | for line in code.split('\n'): 222 | if 'extern' in line: 223 | crate = re.search(r'extern crate (.*);', 224 | lines.pop(lines.index(line))) 225 | 226 | EXTERNS.add(crate.group(1)) 227 | 228 | elif '#[macro_use]' in line: 229 | lines.pop(lines.index(line)) 230 | 231 | return '\n'.join(lines) 232 | 233 | 234 | def check_compile(): 235 | '''Check every function for compilation. If succeed, add it to lib.rs''' 236 | 237 | head = '#![allow(unused_imports)]\n#![allow(dead_code)]\n\n' 238 | head += '\n'.join(map(lambda x: f'#[macro_use]\nextern crate {x.replace("-", "_")};\n', 239 | set(EXTERNS))) 240 | head += '\n' 241 | 242 | mods = '' 243 | candidates = '' 244 | usings = '' 245 | variables = '' 246 | 247 | for define in DEFINES: 248 | variables += f' {define}\n' 249 | 250 | for i in range(len(LIB_FUNCS)): 251 | candidate = f' {LIB_FUNCS[i]}\n' 252 | 253 | full_code = head + '\npub mod smth {\n' + \ 254 | '\n #[no_mangle]\n pub extern "C" fn main() {\n' + \ 255 | variables + candidate + ' }\n' + '}' 256 | 257 | if cargo_check(full_code, LIB_FUNCS[i]): 258 | candidates += candidate 259 | 260 | for i in range(len(EXAMPLE_FUNCS)): 261 | mod = f'mod func{i};\n' 262 | candidate = f' {EXAMPLE_FUNCS[i]}();\n' 263 | use = f' use crate::func{i}::{EXAMPLE_FUNCS[i]};\n' 264 | 265 | full_code = head + mod + '\npub mod smth {\n' + use + \ 266 | '\n #[no_mangle]\n pub extern "C" fn main() {\n' + \ 267 | variables + candidate + ' }\n' + '}' 268 | 269 | if cargo_check(full_code, EXAMPLE_FUNCS[i]): 270 | candidates += candidate 271 | mods += mod 272 | usings += use 273 | 274 | lib_code = head + mods + '\npub mod smth {\n' + usings + \ 275 | '\n #[no_mangle]\n pub extern "C" fn main() {\n' + \ 276 | variables + candidates + ' }\n' + '}' 277 | 278 | with open(f'{RUST_PROJ_PATH}/{RUST_PROJ_NAME}/src/lib.rs', 'w') as rust_lib: 279 | rust_lib.write(lib_code) 280 | 281 | 282 | def cargo_check(code, func): 283 | 284 | with open(f'{RUST_PROJ_PATH}/{RUST_PROJ_NAME}/src/lib.rs', 'w') as rust_lib: 285 | rust_lib.write(code) 286 | 287 | if ARCH: 288 | proc = subprocess.run(['cargo', 'check', '--release', 289 | f'--manifest-path={RUST_PROJ_PATH}/{RUST_PROJ_NAME}/Cargo.toml', 290 | f'--target={ARCH}'], 291 | capture_output=True) 292 | else: 293 | proc = subprocess.run(['cargo', 'check', '--release', 294 | f'--manifest-path={RUST_PROJ_PATH}/{RUST_PROJ_NAME}/Cargo.toml'], 295 | capture_output=True) 296 | 297 | if b'error[' in proc.stderr or b'error: could not compile' in proc.stderr: 298 | print(f'Error on compile function: {func}. Removing it!') 299 | return False 300 | 301 | return True 302 | 303 | 304 | def create_mods(mods): 305 | '''Create modules from each example function codes''' 306 | 307 | for i in range(len(mods)): 308 | with open(f'{RUST_PROJ_PATH}/{RUST_PROJ_NAME}/src/func{i}.rs', 'wb') as code: 309 | code.write(mods[i].encode(errors='replace')) 310 | 311 | 312 | def cargo_build(): 313 | '''Compile code with cargo''' 314 | 315 | if ARCH: 316 | os.system('cargo build --release ' 317 | f'--manifest-path={RUST_PROJ_PATH}/{RUST_PROJ_NAME}/Cargo.toml ' 318 | f'--target={ARCH}') 319 | else: 320 | os.system('cargo build --release ' 321 | f'--manifest-path={RUST_PROJ_PATH}/{RUST_PROJ_NAME}/Cargo.toml') 322 | 323 | 324 | def get_latest_version(lib): 325 | '''Get the latest version of specified library''' 326 | 327 | url = f'https://docs.rs/{lib}/latest/{lib}/' 328 | r = requests.get(url) 329 | 330 | soup = BeautifulSoup(r.text, 'html.parser') 331 | 332 | li_ver = soup.find('li', attrs={'class': 'version'}) 333 | if li_ver == None: 334 | div_ver = soup.find('div', attrs={'class': 'version'}).text[8:] 335 | return div_ver 336 | 337 | return li_ver.text[8:] 338 | 339 | 340 | def gen_cargo_toml(libs): 341 | '''Generate valid Cargo.toml''' 342 | 343 | cargo_template = textwrap.dedent( 344 | ''' 345 | [package] 346 | name = "rust_codes" 347 | version = "0.1.0" 348 | edition = "2021" 349 | 350 | [profile.release] 351 | debug = true 352 | strip = false 353 | 354 | [lib] 355 | crate-type = ["staticlib", "cdylib"] 356 | 357 | [dependencies] 358 | %s 359 | ''') 360 | 361 | toml_path = f'{RUST_PROJ_PATH}/{RUST_PROJ_NAME}/Cargo.toml' 362 | 363 | deps = '' 364 | for lib, version in libs: 365 | deps += '%s = "%s"\n' % (lib, version) 366 | 367 | for ext in EXTERNS: 368 | if not f'{ext} =' in deps and not f'{ext.replace("_", "-")} =' in deps: 369 | version = get_latest_version(ext) 370 | 371 | deps += '%s = "%s"\n' % (ext, version) 372 | 373 | with open(toml_path, 'w') as toml: 374 | toml.write(cargo_template % deps) 375 | 376 | 377 | def cargo_new(): 378 | if os.path.isdir(f'{RUST_PROJ_PATH}/{RUST_PROJ_NAME}'): 379 | return 380 | 381 | os.chdir(RUST_PROJ_PATH) 382 | os.system(f'cargo new {RUST_PROJ_NAME} --lib --vcs none') 383 | 384 | 385 | def create_sig_ida(): 386 | target = f'{RUST_PROJ_PATH}/{RUST_PROJ_NAME}/target/{ARCH}/release' 387 | 388 | if os.name == 'nt': 389 | libname = f'{RUST_PROJ_NAME}.lib' 390 | else: 391 | libname = f'lib{RUST_PROJ_NAME}.a' 392 | 393 | os.system(f'{PAT_GENERATOR_PATH} {target}/{libname} {target}/{libname}.pat') 394 | 395 | rust_demangle(f'{target}/{libname}.pat') 396 | 397 | cmd = f'{SIGMAKE_PATH} {target}/{libname}.pat {target}/{libname}.sig' 398 | proc = subprocess.run(cmd.split(), capture_output=True) 399 | 400 | if b'COLLISIONS' in proc.stderr: 401 | exc = open(f'{target}/{libname}.exc').readlines()[4:] 402 | 403 | with open(f'{target}/{libname}.exc', 'w') as sigexc: 404 | sigexc.write('\n'.join(exc)) 405 | subprocess.run(cmd.split()) 406 | 407 | def create_sig_rizin(): 408 | target = f'{RUST_PROJ_PATH}/{RUST_PROJ_NAME}/target/{ARCH}/release' 409 | 410 | if os.name == 'nt': 411 | libname = f'{RUST_PROJ_NAME}.dll' 412 | else: 413 | libname = f'lib{RUST_PROJ_NAME}.so' 414 | 415 | # cmd = f'rz-sign -a -o {target}/{libname}.sig {target}/{libname}' 416 | cmd = f'rizin -A -qc "zfc {target}/{libname}.sig" {target}/{libname}' 417 | os.system(cmd) 418 | 419 | def rust_demangle(target): 420 | mangled_regex = re.compile(r'_ZN[\w\d_$\.]+E'.encode()) 421 | 422 | with open(target, 'rb') as mangled_file: 423 | mangled_content = mangled_file.read() 424 | mangled_names = re.findall(mangled_regex, mangled_content) 425 | 426 | for name in mangled_names: 427 | mangled_content = mangled_content.replace( 428 | name, demangle(name.decode()).encode().replace(b' ', b'')) 429 | 430 | with open(target, 'wb') as demangled_file: 431 | demangled_file.write(mangled_content) 432 | 433 | def parse_config(conf_path): 434 | config = configparser.ConfigParser() 435 | config.read(conf_path) 436 | 437 | global RUST_PROJ_NAME 438 | global RUST_PROJ_PATH 439 | global PAT_GENERATOR_PATH 440 | global SIGMAKE_PATH 441 | global ARCH 442 | 443 | RUST_PROJ_NAME = config['Project']['name'] 444 | RUST_PROJ_PATH = config['Project']['path'] 445 | 446 | try: 447 | PAT_GENERATOR_PATH = config['Generator']['pat'] 448 | SIGMAKE_PATH = config['Generator']['sigmake'] 449 | 450 | except: 451 | pass 452 | 453 | ARCH = config['Target']['arch'] 454 | 455 | def rlib_to_sig(libs, target='ida'): 456 | parse_config(r'config.ini') 457 | 458 | all_funcs = list() 459 | all_modules = list() 460 | 461 | for lib, ver in libs: 462 | USINGS[lib] = [] 463 | EXTERNS.add(lib.replace('-', '_')) 464 | 465 | lib_funcs = get_lib_funcs(lib, ver) 466 | all_modules.extend(get_lib_funcs_code(lib_funcs, lib, ver)) 467 | all_funcs.extend(lib_funcs) 468 | 469 | cargo_new() 470 | 471 | gen_cargo_toml(libs) 472 | 473 | create_mods(all_modules) 474 | check_compile() 475 | 476 | cargo_build() 477 | 478 | if target == 'ida': 479 | create_sig_ida() 480 | 481 | elif target == 'rizin': 482 | create_sig_rizin() 483 | 484 | 485 | # if __name__ == '__main__': 486 | # libs = set([('cesu8', '1.1.0'), ('memchr', '2.4.1'), ('proc-maps', '0.2.0'), ('log', '0.4.1'), ('lazy_static', '1.4.0'), ('rustc-demangle', '0.1.2'), 487 | # ('serde_json', '1.0.7'), ('aho-corasick', '0.7.1'), ('regex', '1.5.4'), ('regex-syntax', '0.6.2'), ('miniz_oxide', '0.4.0'), ('rand', '0.8.4')]) 488 | 489 | # rlib_to_sig(libs) 490 | --------------------------------------------------------------------------------