├── .gitignore ├── LICENSE ├── README.md ├── ida2py.py ├── img ├── angr_symbolic.png ├── arr.png ├── heap.png ├── pair.png └── sshd.png └── tests ├── angr_symbolic.py ├── basic_test.py ├── binaries ├── angr_symbolic ├── basic_test ├── flareon_sshd_shellcode └── rc4 ├── flareon_sshd_shellcode.py ├── rc4.py └── src ├── angr_symbolic.c ├── basic_test.cpp └── rc4.c /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.til 3 | *.id* 4 | *.nam 5 | *.i64 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Lam Jun Rong 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ida2py 2 | 3 | An intuitive query API for IDA Pro 4 | 5 | ## Features 6 | 7 | All global variables with defined names and types are now accessible directly in IDAPython. 8 | 9 | Accessing a global array of integers 10 | 11 | 12 | ida2py supports [most](#the-ida2py-type-system) IDA data types, including arrays, structs, pointers or a combination of the above. To convert a variable to a (primitive) Python object, simply call `.pyval()`. 13 | 14 | ![Exporting a binary heap](img/heap.png) 15 | 16 | User defined types are available in Python as well. 17 | 18 | You can either use `Type @ address` or `Type(address)`. 19 | 20 | `Type()` is a shorthand for `Type @ idc.here()`. You can use the `*` operator to create array types. 21 | 22 | ![Casting to a new type with Python](img/pair.png) 23 | 24 | If your variable/type name is not a valid Python identifier, you can use the `_ida(name)` function to obtain a reference to it instead. For example, `_ida("Node") == Node`. 25 | 26 | ### angr integration 27 | 28 | Using the `angr_exec` context manager, you can emulate functions by directly calling them. 29 | 30 | ![Emulating flareon 11's sshd decryption](./img/sshd.png) 31 | 32 | You will need to have `angr` installed. The Unicorn engine will be used where possible. If you need more customization, you can pass `proj` and `state` arguments to `angr_exec`. 33 | 34 | `__usercall` calling convention is supported, but Golang CC has not been tested and probably doesn't work. Passing floats as arguments is not currently supported. 35 | 36 | Use the `AngrExecutor.buf` function to convert a static string or `claripy.BVS` into memory that is mapped into `angr` 37 | ![angr symbolic execution](./img/angr_symbolic.png) 38 | 39 | Some functions, such as `srand`, `time` and `rand`, will not be emulated. You will need to implement these functions via angr hooks if required. 40 | 41 | For more detailed/advanced usage, refer to the [tests](./tests/). 42 | 43 | ## Installation 44 | 45 | Simply copy `ida2py.py` to your IDA plugin directory. 46 | 47 | You don't need to install `angr` to use ida2py's base features. 48 | 49 | 50 | ### angr integration 51 | ida2py has been tested to work with angr `9.2.133`, the latest version as of writing. 52 | 53 | You can install it by running 54 | ```shell 55 | python3 -m pip install angr==9.2.133 56 | ``` 57 | 58 | ## How it works 59 | 60 | ## The ida2py type system 61 | 62 | ida2py supports 6 basic types: 63 | 1. Integers 64 | 2. Strings (this might be removed in the future) 65 | 3. Arrays 66 | 4. Structs 67 | 5. Pointers 68 | 6. Functions (more features coming here) 69 | 70 | If ida2py cannot determine the type of a variable, or if it does not fit within the type system, the `Unknown` type will be returned. 71 | 72 | Non-integral numeric types, such as floats and doubles, are not currently supported. 73 | 74 | It is likely that some types will not be accurately represented, and the type system is quite complicated, so bug reports are always welcome. 75 | 76 | ### Global scope modification 77 | The `ctypes` module is used to modify the `PyObject` of the current scope's `globals()` dictionary. By installing a fake `globals` class with a custom `__getitem__` method, we can intercept variable accesses before they cause a `NameError` and instead return an appropriate object derived from IDA. 78 | 79 | This is similar to, and builds upon, my previous ['C Types for Python'](https://gist.github.com/junron/9e203a745095e793f92922c4e208e9ff) project. 80 | 81 | Note that this is not official or intended Python behavior, so usage of this library may crash IDAPython or your entire IDA Pro application. -------------------------------------------------------------------------------- /ida2py.py: -------------------------------------------------------------------------------- 1 | import __main__ 2 | 3 | from ctypes import * 4 | import builtins 5 | import functools 6 | import inspect 7 | 8 | import idc 9 | import ida_typeinf 10 | import ida_bytes 11 | import idaapi 12 | import ida_nalt 13 | import ida_lines 14 | import ida_funcs 15 | import ida_idp 16 | import idautils 17 | import ida_hexrays 18 | 19 | import typing 20 | from copy import copy 21 | 22 | if typing.TYPE_CHECKING: 23 | import angr 24 | 25 | state = None 26 | 27 | class context: 28 | print_style = 'dec' 29 | auto_deref = False 30 | indent_width = 2 31 | max_line_width = 80 32 | max_display_count = 16 33 | executor: typing.Optional["Executor"] = None 34 | 35 | context.print_style = 'dec' 36 | 37 | class IntWrapperMeta(type): 38 | def __new__(cls, name, bases, namespace): 39 | # List of special methods that return integers and should return IntWrapper instead 40 | int_returning_methods = [ 41 | '__add__', '__radd__', '__sub__', '__rsub__', 42 | '__mul__', '__rmul__', '__floordiv__', '__rfloordiv__', 43 | '__mod__', '__rmod__', '__pow__', '__rpow__', 44 | '__lshift__', '__rlshift__', '__rshift__', '__rrshift__', 45 | '__and__', '__rand__', '__or__', '__ror__', 46 | '__xor__', '__rxor__', '__neg__', '__pos__', 47 | '__abs__' 48 | ] 49 | 50 | # For each method that returns an int, create a wrapped version 51 | for method_name in int_returning_methods: 52 | if method_name not in namespace: # Only if not already defined 53 | def make_wrapped_method(method_name): 54 | def wrapped_method(self, *args, **kwargs): 55 | # Get the original method from int class 56 | original_method = getattr(int, method_name) 57 | # Call it and wrap the result 58 | args = [x.value if isinstance(x, Wrapper) else x for x in args] 59 | result = original_method(self.value, *args, **kwargs) 60 | return IntWrapper(self.signed, self.bits, self.byte_order, result) 61 | return wrapped_method 62 | 63 | namespace[method_name] = make_wrapped_method(method_name) 64 | 65 | return super().__new__(cls, name, bases, namespace) 66 | 67 | def auto_indent(func): 68 | @functools.wraps(func) 69 | def wrapper(*args, **kwargs): 70 | depth = kwargs.get('depth', 0) 71 | result = str(func(*args, **kwargs)) 72 | indent = ' ' * depth 73 | return indent + result.replace('\n', '\n' + indent) 74 | wrapper.__signature__ = inspect.signature(func) 75 | return wrapper 76 | 77 | @functools.total_ordering 78 | class Wrapper: 79 | address: int|None = None 80 | _value: typing.Any|None = None 81 | 82 | @property 83 | def value(self): 84 | if self._value is not None: 85 | return self._value 86 | self._value = self._get_value() 87 | return self._value 88 | 89 | def _get_value(self): 90 | raise NotImplementedError() 91 | 92 | def type_name(self): 93 | raise NotImplementedError() 94 | 95 | @auto_indent 96 | def array_repr(self, depth=0): 97 | raise NotImplementedError() 98 | 99 | def _addr_repr(self): 100 | return f' @ {hex(self.address)}' if self.address is not None else '' 101 | 102 | def pyval(self): 103 | def _pyval(v): 104 | if isinstance(v, StructWrapper): 105 | v = {k:_pyval(val[1]) for k,val in v.value.items()} 106 | if isinstance(v, Wrapper): 107 | v = _pyval(v.value) 108 | if isinstance(v, list): 109 | v = [_pyval(x) for x in v] 110 | return v 111 | return _pyval(self) 112 | 113 | def __repr__(self): 114 | raise NotImplementedError() 115 | 116 | def __bytes__() -> bytes: 117 | raise NotImplementedError() 118 | 119 | def __sizeof__(self) -> int: 120 | raise NotImplementedError() 121 | 122 | def __copy__(self) -> "Wrapper": 123 | raise NotImplementedError() 124 | 125 | def __str__(self): 126 | return repr(self) 127 | 128 | def __eq__(self, other): 129 | if isinstance(other, type(self)): 130 | return self.value == other.value 131 | return self.value == other 132 | 133 | def __lt__(self, other): 134 | if isinstance(other, type(self)): 135 | return self.value < other.value 136 | return self.value < other 137 | 138 | def __bool__(self): 139 | return bool(self.value) 140 | 141 | def __dir__(self): 142 | # Make private properties actually "private" 143 | prefixes = [] 144 | s = self.__class__ 145 | while s != object: 146 | prefixes.append(f"_{s.__name__}__") 147 | s = s.__base__ 148 | hide_names = ["_addr_repr", "_get_value", "_value"] 149 | return [name for name in super().__dir__() if not (any(name.startswith(prefix) for prefix in prefixes) or name in hide_names)] 150 | 151 | class _Invalid: 152 | def __init__(self): 153 | pass 154 | 155 | def __repr__(self): 156 | return "Invalid" 157 | 158 | Invalid = _Invalid() 159 | 160 | class IntWrapper(Wrapper, metaclass=IntWrapperMeta): 161 | def __init__(self, signed=True, bits=32, byte_order='little', value=None, address = None): 162 | self.signed = signed 163 | self.bits = bits 164 | self.byte_order = byte_order 165 | self._value = value 166 | self.address = address 167 | 168 | def _get_value(self): 169 | try: 170 | return int.from_bytes(idc.get_bytes(int(self.address), self.bits//8), self.byte_order, signed=self.signed) 171 | except TypeError: 172 | return Invalid 173 | 174 | def type_name(self): 175 | return f"{'' if self.signed else 'U'}Int{self.bits}" 176 | 177 | @auto_indent 178 | def array_repr(self, depth=0): 179 | if context.print_style == 'dec': 180 | return str(self.value) 181 | else: 182 | return hex(self.value) 183 | 184 | def __repr__(self): 185 | if self.address is None and self._value is None: 186 | return self.type_name() 187 | return f"{self.type_name()}({self.array_repr()})" + self._addr_repr() 188 | 189 | def __bytes__(self): 190 | return int.to_bytes(self.value, self.bits//8, self.byte_order, signed=self.signed) 191 | 192 | def __sizeof__(self): 193 | return self.bits // 8 194 | 195 | def __copy__(self): 196 | return IntWrapper(self.signed, self.bits, self.byte_order, self._value, self.address) 197 | 198 | def __int__(self): 199 | return self.value 200 | 201 | def __index__(self): 202 | return self.value 203 | 204 | def __call__(self, val): 205 | assert self.address is None and self._value is None 206 | t = copy(self) 207 | t.address = None 208 | if isinstance(val, int): 209 | t._value = val 210 | return t 211 | if isinstance(val, Wrapper): 212 | val = bytes(val) 213 | t.address = val.address 214 | if type(val) != bytes: 215 | raise TypeError(f"Cannot convert from {val} to {self.type_name()}") 216 | if context.allow_excess: 217 | val = val[:self.bits//8] 218 | assert len(val) == self.bits//8, f"Input size {len(val)} != integer size {self.bits//8}" 219 | t._value = int.from_bytes(val, self.byte_order, signed=self.signed) 220 | return t 221 | 222 | class ArrayWrapper(Wrapper): 223 | __t: Wrapper 224 | __length: int 225 | 226 | def __init__(self, t: Wrapper, length): 227 | self.__t = t 228 | self.__length = length 229 | self.address = t.address 230 | 231 | def __getitem__(self, index): 232 | if isinstance(index, slice): 233 | return self.value.__getitem__(index) 234 | if index < 0: 235 | index += self.__length 236 | if index >= self.__length: 237 | raise IndexError(f"Array index {index} out of range for array of size {self.__length}") 238 | if index < 0: 239 | raise IndexError(f"Array index {index} cannot be negative") 240 | elem = copy(self.__t) 241 | elem._value = None 242 | elem.address = self.address + index * elem.__sizeof__() 243 | return elem 244 | 245 | def __len__(self): 246 | return self.__length 247 | 248 | def __iter__(self): 249 | for i in range(self.__length): 250 | yield self[i] 251 | 252 | def _get_value(self): 253 | out = [] 254 | for i in range(self.__length): 255 | out.append(self[i]) 256 | return out 257 | 258 | def __sizeof__(self): 259 | return self.__t.__sizeof__() * self.__length 260 | 261 | def type_name(self): 262 | return self.__t.type_name() + f"[{self.__length}]" 263 | 264 | @auto_indent 265 | def array_repr(self, depth=0): 266 | if self.address is None: 267 | return "" 268 | if self.__length > context.max_display_count: 269 | if self.value: 270 | items = self.value[:context.max_display_count] 271 | else: 272 | items = [self[i] for i in range(context.max_display_count)] 273 | else: 274 | items = self.value 275 | if len(items) == 0: 276 | return "{}" 277 | parts = [x.array_repr(depth=depth+1) for x in items] 278 | multiline = parts[0].count("\n") > 0 279 | line_width = sum(len(part.strip()) + 2 for part in parts) 280 | if multiline or line_width > context.max_line_width: 281 | inner = "\n" + ",\n".join(parts) + (", ..." if self.__length > context.max_display_count else '') + "\n" 282 | else: 283 | inner = ", ".join(part.strip() for part in parts) + (", ..." if self.__length > context.max_display_count else '') 284 | return "{" + inner + "}" 285 | 286 | def __bytes__(self): 287 | out = b"" 288 | for v in self.value: 289 | out += bytes(v) 290 | return out 291 | 292 | def __copy__(self) -> "Wrapper": 293 | return ArrayWrapper(self.__t, self.__length) 294 | 295 | def __repr__(self) -> str: 296 | return self.type_name() + self.array_repr() + self._addr_repr() 297 | 298 | 299 | class StringWrapper(Wrapper): 300 | __length: int 301 | __str_type: str 302 | def __init__(self, length, str_type, address): 303 | self.__length = length 304 | self.address = address 305 | if str_type is None: 306 | str_type = "char" 307 | self.__str_type = str_type 308 | 309 | def __getitem__(self, index): 310 | return self.value.__getitem__(index) 311 | 312 | def __len__(self): 313 | if self.__length: 314 | return self.__length 315 | return len(self.value) 316 | 317 | def __iter__(self): 318 | return self.value.__iter__() 319 | 320 | def __repr__(self): 321 | ret = self.array_repr() 322 | if self.__str_type == 'wchar_t': 323 | ret = "L"+ret[1:] 324 | return ret + self._addr_repr() 325 | 326 | def __str__(self): 327 | return repr(self) 328 | 329 | def _get_value(self): 330 | res = idc.get_strlit_contents(self.address, strtype=ida_nalt.STRTYPE_C_16 if self.__str_type == "wchar_t" else ida_nalt.STRTYPE_C) 331 | if res: 332 | return res 333 | try: 334 | return idc.get_bytes(self.address, self.__length) 335 | except TypeError: 336 | raise ValueError(f"Could not get bytes at {hex(self.address)}") 337 | 338 | def type_name(self): 339 | if self.__length is None: 340 | return f"{self.__str_type}[]" 341 | return f"{self.__str_type}[{self.__length}]" 342 | 343 | @auto_indent 344 | def array_repr(self, depth=0): 345 | if self.__length is not None and self.__length > 256: 346 | return bytes.__str__(idc.get_bytes(self.address, 256)) + f" and {self.__length - 256} more bytes" 347 | return bytes.__str__(self.value) 348 | 349 | def __bytes__(self) -> bytes: 350 | return self.value 351 | 352 | def __sizeof__(self) -> int: 353 | return len(self) * (2 if self.__str_type == "wchar_t" else 1) 354 | 355 | def __copy__(self) -> "Wrapper": 356 | return StringWrapper(self.__length, self.__str_type, self.address) 357 | 358 | 359 | class PointerWrapper(Wrapper): 360 | tinfo_hint: ida_typeinf.tinfo_t 361 | def __init__(self, is_string, tinfo_hint, address): 362 | self.address = address 363 | self.__is_string = is_string 364 | self.tinfo_hint = tinfo_hint 365 | 366 | def _get_value(self): 367 | pointed_addr = read_pointer(self.address) 368 | if pointed_addr is Invalid: 369 | return pointed_addr 370 | if pointed_addr == 0: 371 | return None 372 | return self.__deref_at_address(pointed_addr, True) 373 | 374 | 375 | def __deref_at_address(self, pointed_addr, get_whole=False): 376 | if self.__is_string and get_whole: 377 | tinfo = ida_typeinf.tinfo_t() 378 | ida_typeinf.parse_decl(tinfo, None, f"char[];", ida_typeinf.PT_SIL) 379 | else: 380 | tinfo = None 381 | if self.tinfo_hint is None: 382 | tinfo = get_type_at_address(pointed_addr) 383 | if tinfo is None: 384 | tinfo = self.tinfo_hint 385 | 386 | if tinfo is None: 387 | print(f"Failed to get type at {hex(pointed_addr)}") 388 | return UnknownWrapper(pointed_addr) 389 | return ida2py(tinfo, pointed_addr) 390 | 391 | def type_name(self): 392 | if self.address is None: 393 | if self.tinfo_hint is None: 394 | return "Unknown*" 395 | new_tinfo = ida_typeinf.tinfo_t() 396 | new_tinfo.create_ptr(self.tinfo_hint) 397 | return new_tinfo.dstr() 398 | if self.__is_string: 399 | return self.value.type_name() 400 | return f"{self.value.type_name()}*" 401 | 402 | @auto_indent 403 | def array_repr(self, depth=0): 404 | if read_pointer(self.address) == 0: 405 | return "NULL" 406 | # Strings and functions within structs/arrays can be understood to be pointers 407 | if self.__is_string or (self.tinfo_hint and self.tinfo_hint.is_func()): 408 | # Do not increase depth for pointers 409 | return self.value.array_repr(depth=depth) 410 | if read_pointer(self.address) is Invalid: 411 | return "Invalid" 412 | if context.auto_deref: 413 | return self.value.array_repr(depth=depth).strip() 414 | return "{" + self.type_name() + "} " + hex(read_pointer(self.address)) 415 | 416 | def __repr__(self): 417 | if self.value is Invalid: 418 | return f"Pointer to Invalid" 419 | if self.value is None: 420 | return "NULL" 421 | return f"Pointer to ({self.value})" + self._addr_repr() 422 | 423 | def __bytes__(self) -> bytes: 424 | return int.to_bytes(read_pointer(self.address), get_address_size(), get_byteorder()) 425 | 426 | def __sizeof__(self) -> int: 427 | return get_address_size() 428 | 429 | def __copy__(self) -> "Wrapper": 430 | return PointerWrapper(self.__is_string, self.tinfo_hint, self.address) 431 | 432 | def __getitem__(self, index): 433 | if index == 0 and self.address is None: 434 | return self.value 435 | if isinstance(index, slice): 436 | if index.stop is None: 437 | raise ValueError("Slice must have an end index") 438 | start, stop, stride = index.indices(index.stop) 439 | out = [] 440 | for i in range(start, stop, stride): 441 | out.append(self[i]) 442 | if self.__is_string: 443 | return bytes(out) 444 | return out 445 | pointed_addr = read_pointer(self.address) 446 | if pointed_addr is Invalid: 447 | return pointed_addr 448 | if self.tinfo_hint is not None: 449 | tif = self.tinfo_hint 450 | if tif.is_array() and tif.get_size() == 0: 451 | tif.remove_ptr_or_array() 452 | elem_size = tif.get_size() 453 | else: 454 | elem_size = self.value.__sizeof__() 455 | pointed_addr += index * elem_size 456 | return self.__deref_at_address(pointed_addr) 457 | 458 | def __call__(self, val): 459 | assert self.address is None 460 | t = copy(self) 461 | t.address = None 462 | if isinstance(val, Wrapper): 463 | t.address = val 464 | return t 465 | 466 | def __getattr__(self, key): 467 | try: 468 | return object.__getattribute__(self, key) 469 | except AttributeError: 470 | if isinstance(self.value, StructWrapper): 471 | return self.value.__getattr__(key) 472 | raise AttributeError(f"PointerWrapper object has no attribute {key}") 473 | 474 | def __dir__(self): 475 | if isinstance(self.value, StructWrapper): 476 | return self.value.__dir__() 477 | return super().__dir__() 478 | 479 | 480 | class StructWrapper(Wrapper): 481 | __tif: ida_typeinf.tinfo_t 482 | __name: str 483 | __members: dict[str, Wrapper] 484 | def __init__(self, tif: ida_typeinf.tinfo_t, address): 485 | self.__tif = tif 486 | self.__name = tif.get_type_name() 487 | self.__members = {} 488 | udt = ida_typeinf.udt_type_data_t() 489 | self.address = address 490 | if tif.get_udt_details(udt): 491 | for udm in udt: 492 | udm_type: ida_typeinf.tinfo_t = ida_typeinf.tinfo_t(udm.type) 493 | offset = udm.offset//8 494 | res = ida2py(udm_type, None) 495 | self.__members[udm.name] = (offset, res) 496 | 497 | def _get_value(self): 498 | assert self.address is not None 499 | out = {} 500 | for key in self.__members: 501 | offset, t = self.__members[key] 502 | t = copy(t) 503 | t._value = None 504 | t.address = self.address + offset 505 | out[key] = (offset, t) 506 | return out 507 | 508 | def type_name(self): 509 | return "struct "+self.__name 510 | 511 | @auto_indent 512 | def array_repr(self, depth=0, force_multiline=False): 513 | parts = [] 514 | members = sorted(self.value.items(), key=lambda k: k[1][0]) 515 | for name, (offset, type) in members: 516 | parts.append(f"{name} = {type.array_repr(depth=depth+1).strip()}") 517 | line_width = sum(len(part) + 2 for part in parts) + 2 518 | if line_width > context.max_line_width or force_multiline: 519 | return "{\n" + ",\n".join(" " * context.indent_width + part for part in parts) + "\n}" 520 | return "{" + ", ".join(parts) + "}" 521 | 522 | def __repr__(self): 523 | return self.type_name() + " " + self.array_repr(force_multiline=True) + self._addr_repr() 524 | 525 | def __bytes__(self) -> bytes: 526 | out = b"" 527 | members = sorted(self.value.items(), key=lambda k: k[1][0]) 528 | for name, (offset, type) in members: 529 | if len(out) != offset: 530 | out += b"\0" * (offset - len(out)) 531 | assert len(out) == offset 532 | out += bytes(type) 533 | out += (b"\0" * (self.__sizeof__() - len(out))) 534 | assert len(out) == self.__sizeof__(), (len(out), self.__sizeof__()) 535 | return out 536 | 537 | def __sizeof__(self) -> int: 538 | return self.__tif.get_size() 539 | 540 | def __copy__(self) -> "Wrapper": 541 | return StructWrapper(self.__tif, self.address) 542 | 543 | def __getattr__(self, key): 544 | if key in self.value: 545 | offset, t = self.value[key] 546 | if self.address is None: 547 | return t 548 | if t.address is None: 549 | t = copy(t) 550 | t.address = self.address + offset 551 | return t 552 | raise AttributeError(f"struct '{self.__name}' has no member {key}") 553 | 554 | def __dir__(self): 555 | return super().__dir__() + list(self.__members.keys()) 556 | 557 | class FunctionWrapper(Wrapper): 558 | tif: ida_typeinf.tinfo_t 559 | func: ida_funcs.func_t 560 | func_data: ida_typeinf.func_type_data_t 561 | def __init__(self, tif, func, address): 562 | assert tif.is_func(), f"{tif} is not a function type" 563 | if idc.get_segm_attr(address, idc.SEGATTR_TYPE) == 1: 564 | # extern 565 | ea = next(idautils.CodeRefsTo(address, 0), None) 566 | func = ida_funcs.get_func(ea) 567 | if func is not None: 568 | address = next(func.addresses()) 569 | self.tif = tif 570 | self.func = func 571 | self.func_data = ida_typeinf.func_type_data_t() 572 | assert tif.get_func_details(self.func_data) 573 | self.address = address 574 | 575 | @property 576 | def name(self): 577 | return idc.get_name(self.func.start_ea) 578 | 579 | @property 580 | def offset_str(self): 581 | return (f' + {hex(self.address - self.func.start_ea)}' if self.address != self.func.start_ea else '') 582 | 583 | def _get_value(self): 584 | return f"Function {self.name} at {hex(self.address)}" 585 | 586 | def type_name(self): 587 | return ida_typeinf.print_tinfo("", 0, 0, 0, self.tif, self.name, "") 588 | 589 | @auto_indent 590 | def array_repr(self, depth=0): 591 | if self.offset_str: 592 | return self.name + self.offset_str 593 | return self.name 594 | 595 | def __repr__(self): 596 | return self.type_name() + self.offset_str + self._addr_repr() 597 | 598 | def __bytes__(self) -> bytes: 599 | raise NotImplementedError("Cannot convert function to bytes") 600 | 601 | def __sizeof__(self) -> int: 602 | return self.func.size() 603 | 604 | def __copy__(self) -> "Wrapper": 605 | return FunctionWrapper(self.tif, self.func, self.address) 606 | 607 | def __call__(self, *args): 608 | assert context.executor is not None, "Cannot call function without executor" 609 | return context.executor.call(self, args) 610 | 611 | class Executor: 612 | def __init__(self): 613 | self.old = None 614 | 615 | def __enter__(self) -> "Executor": 616 | self.old = context.executor 617 | context.executor = self 618 | return self 619 | 620 | def __exit__(self, exc_type, exc_value, traceback): 621 | context.executor = self.old 622 | 623 | def alloc(self, size): 624 | raise NotImplementedError() 625 | 626 | def buf(self, b: bytes): 627 | raise NotImplementedError() 628 | 629 | def call(self, func: FunctionWrapper, args): 630 | raise NotImplementedError() 631 | 632 | class AngrExecutor(Executor): 633 | proj: "angr.Project" 634 | state: "angr.SimState" 635 | 636 | def __init__(self, proj, state): 637 | super().__init__() 638 | self.proj = proj 639 | self.state = state 640 | 641 | def alloc(self, size: int) -> "AngrPointer": 642 | result = self.state.heap.allocate(size) 643 | pointed_type = IntWrapper(signed=False, bits=8) 644 | return AngrPointer(result, pointed_type) 645 | 646 | def buf(self, b): 647 | length = len(b) 648 | if hasattr(b, 'concrete') and not b.concrete: 649 | length //= 8 650 | buf = self.alloc(length) 651 | buf.set_bytes(b) 652 | return buf 653 | 654 | @staticmethod 655 | def argloc_to_simarg(argloc, type): 656 | import angr 657 | size = type.get_size() 658 | if size == idc.BADADDR: 659 | return 660 | if not argloc.is_fragmented(): 661 | if argloc.is_reg1(): 662 | return angr.calling_conventions.SimRegArg( 663 | ida_idp.get_reg_name(argloc.reg1(), size), 664 | size, 665 | reg_offset=argloc.regoff() 666 | ) 667 | elif argloc.in_stack(): 668 | return angr.calling_conventions.SimRegArg(argloc.stkoff(), size) 669 | raise NotImplementedError("Cannot convert to simarg") 670 | 671 | def get_cc(self, func: FunctionWrapper): 672 | import angr 673 | 674 | 675 | class UsercallArgSession: 676 | """ 677 | An argsession for use with SimCCUsercall 678 | """ 679 | 680 | __slots__ = ( 681 | "cc", 682 | "real_args", 683 | ) 684 | 685 | def __init__(self, cc): 686 | self.cc = cc 687 | # The actual UsercallArgSession has a bug here 688 | self.real_args = angr.calling_conventions.SerializableListIterator(self.cc.args) 689 | 690 | def getstate(self): 691 | return self.real_args.getstate() 692 | 693 | def setstate(self, state): 694 | self.real_args.setstate(state) 695 | 696 | class SimCCUsercall(angr.calling_conventions.SimCC): 697 | def __init__(self, arch, args, ret_loc): 698 | super().__init__(arch) 699 | self.args = args 700 | self.ret_loc = ret_loc 701 | 702 | ArgSession = UsercallArgSession 703 | 704 | def next_arg(self, session, arg_type): 705 | return next(session.real_args) 706 | 707 | def return_val(self, ty, **kwargs): 708 | return self.ret_loc 709 | return SimCCUsercall( 710 | arch=self.proj.arch, 711 | args=[AngrExecutor.argloc_to_simarg(arg.argloc, arg.type) for arg in func.func_data], 712 | ret_loc=AngrExecutor.argloc_to_simarg(func.func_data.retloc, func.func_data.rettype) 713 | ) 714 | 715 | def call(self, func: FunctionWrapper, args): 716 | import claripy 717 | def convert_for_angr(val): 718 | if isinstance(val, AngrPointer): 719 | return val.pointed_address 720 | if isinstance(val, StringWrapper) or isinstance(val, IntWrapper): 721 | val = val.pyval() 722 | elif isinstance(val, str): 723 | val = val.encode() 724 | if isinstance(val, bytes): 725 | return self.buf(val).pointed_address 726 | return val 727 | args = [convert_for_angr(x) for x in args] 728 | addr = func.address 729 | if addr < self.proj.loader.main_object.min_addr: 730 | addr += self.proj.loader.main_object.min_addr 731 | 732 | if self.proj.arch.is_thumb(self.proj.entry): 733 | addr |= 1 734 | 735 | angr_func = self.proj.factory.callable(addr, base_state=self.state) 736 | 737 | # potentially support usercall, unless is vararg 738 | if not func.func_data.is_vararg_cc(): 739 | cc = self.get_cc(func) 740 | cc.RETURN_ADDR = angr_func._cc.return_addr 741 | if len(cc.args) != len(args): 742 | raise ValueError(f"Function should be called with {len(cc.args)} arguments, but {len(args)} provided") 743 | angr_func = self.proj.factory.callable(addr, base_state=self.state, cc=cc) 744 | args = [claripy.BVV(arg, simarg.size * 8) for arg, simarg in zip(args, cc.args)] 745 | result = angr_func(*args) 746 | stdout = angr_func.result_state.posix.dumps(1) 747 | # Clean up stdout 748 | angr_func.result_state.posix.stdout.content = [] 749 | angr_func.result_state.posix.stdout.pos = 0 750 | self.state = angr_func.result_state 751 | try: 752 | print(stdout.decode(),end="") 753 | except UnicodeDecodeError: 754 | print(stdout) 755 | if not func.func.does_return(): 756 | print(f"Function does not return") 757 | return UnknownWrapper(None) 758 | if func.func_data.rettype.is_void(): 759 | return 760 | if not result.concrete: 761 | return result 762 | rettype = ida2py(func.func_data.rettype) 763 | if rettype is None: 764 | print(f"Could not determine return type") 765 | return result 766 | if isinstance(rettype, PointerWrapper): 767 | pointed_type = ida2py(rettype.tinfo_hint) 768 | if pointed_type is None or isinstance(pointed_type, UnknownWrapper): 769 | pointed_type = IntWrapper(signed=False, bits=8) 770 | return AngrPointer(result.concrete_value, pointed_type) 771 | else: 772 | rettype._value = result.concrete_value 773 | return rettype 774 | 775 | class AngrPointer: 776 | def __init__(self, pointed_address: int, type: Wrapper): 777 | self.pointed_address = pointed_address 778 | self.type = type 779 | 780 | def _get_mem_info(self, idx): 781 | addr = self.pointed_address + idx * self.type.__sizeof__() 782 | signed = isinstance(self.type, IntWrapper) and self.type.signed 783 | bits = (isinstance(self.type, IntWrapper) and self.type.bits) or get_address_size()*8 784 | type_str = f"{'' if signed else 'u'}int{bits}_t" 785 | return addr, type_str 786 | 787 | def __getitem__(self, idx: int|slice) -> int|list: 788 | mem = context.executor.state.mem 789 | if isinstance(idx, slice): 790 | if idx.stop is None: 791 | raise ValueError("Slice must have an end index") 792 | start, stop, stride = idx.indices(idx.stop) 793 | out = [] 794 | for i in range(start, stop, stride): 795 | out.append(self[i]) 796 | return out 797 | addr, type_str = self._get_mem_info(idx) 798 | res = getattr(mem[addr], type_str) 799 | if res.concrete: 800 | return res.concrete 801 | return res 802 | 803 | def __setitem__(self, idx: int|slice, val: int|list): 804 | mem = context.executor.state.mem 805 | if isinstance(idx, slice): 806 | if idx.stop is None: 807 | raise ValueError("Slice must have an end index") 808 | start, stop, stride = idx.indices(idx.stop) 809 | if not isinstance(val, (list, tuple)): 810 | raise TypeError("Can only assign an iterable to slice") 811 | val = list(val) # Convert to list to check length 812 | if len(val) != len(range(start, stop, stride)): 813 | raise ValueError("Iterable length does not match slice length") 814 | for i, v in zip(range(start, stop, stride), val): 815 | self[i] = v 816 | return 817 | addr, type_str = self._get_mem_info(idx) 818 | setattr(mem[addr], type_str, val) 819 | 820 | def bytes(self, n=None): 821 | executor: AngrExecutor = context.executor 822 | if n is None: 823 | try: 824 | return executor.state.mem[self.pointed_address].string.concrete 825 | except ValueError as e: 826 | print("ValueError:", e) 827 | return b"" 828 | res = executor.state.memory.load(self.pointed_address, n) 829 | if res.concrete: 830 | return bytes(res.concrete) 831 | return res 832 | 833 | def set_bytes(self, b): 834 | executor: AngrExecutor = context.executor 835 | executor.state.memory.store(self.pointed_address, b) 836 | 837 | def __repr__(self): 838 | return f"Pointer to {self.type.type_name()} @ Angr[{hex(self.pointed_address)}]" 839 | 840 | def __eq__(self, value): 841 | if isinstance(value, AngrPointer): 842 | return self.pointed_address == value.pointed_address 843 | if isinstance(value, Wrapper): 844 | value = bytes(value) 845 | return self.bytes(len(value)) == value 846 | 847 | 848 | def angr_exec(filepath=None, proj=None, state=None, log_level="WARNING", unicorn=True): 849 | print("Initializing angr...") 850 | import angr 851 | import logging 852 | logging.getLogger('angr').setLevel(log_level) 853 | 854 | if proj is None: 855 | proj = angr.Project(filepath or idaapi.get_input_file_path(), auto_load_libs=False) 856 | 857 | if state is None: 858 | state = proj.factory.blank_state(add_options=( 859 | { 860 | angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY, 861 | angr.options.ZERO_FILL_UNCONSTRAINED_REGISTERS, 862 | } | (angr.options.unicorn if unicorn else set()) 863 | )) 864 | print("Angr initialized successfully") 865 | return AngrExecutor(proj, state) 866 | 867 | class UnknownWrapper(Wrapper): 868 | def __init__(self, address): 869 | self.address = address 870 | 871 | def _get_value(self): 872 | raise ValueError("Cannot get value of unknown") 873 | 874 | def type_name(self): 875 | return "Unknown" 876 | 877 | def array_repr(self): 878 | return "Unknown" 879 | 880 | def __repr__(self): 881 | return "Unknown" + self._addr_repr() 882 | 883 | def __bytes__(self) -> bytes: 884 | raise ValueError("Cannot convert unknown to bytes") 885 | 886 | def __sizeof__(self) -> int: 887 | raise ValueError("Cannot get size of unknown") 888 | 889 | def __copy__(self) -> "Wrapper": 890 | return UnknownWrapper(self.address) 891 | 892 | 893 | def get_byteorder(): 894 | return 'big' if idaapi.inf_is_be() else 'little' 895 | 896 | def get_address_size(): 897 | address_size = 8 898 | if idaapi.inf_is_32bit_exactly(): 899 | address_size = 4 900 | elif idaapi.inf_is_16bit(): 901 | address_size = 2 902 | return address_size 903 | 904 | def read_pointer(ea): 905 | try: 906 | return int.from_bytes(idc.get_bytes(ea, get_address_size()), get_byteorder()) 907 | except TypeError: 908 | return Invalid 909 | 910 | def get_type_at_address(ea) -> ida_typeinf.tinfo_t | None: 911 | tinfo = ida_typeinf.tinfo_t() 912 | if ida_nalt.get_tinfo(tinfo, ea): 913 | return tinfo 914 | flags = ida_bytes.get_flags(ea) 915 | if not ida_bytes.is_loaded(ea): 916 | return None 917 | if ida_bytes.is_code(flags): 918 | func = idaapi.get_func(ea) 919 | if ea == func.start_ea: 920 | decomp = ida_hexrays.decompile(ea, flags=ida_hexrays.DECOMP_WARNINGS) 921 | if decomp: 922 | decl = ida_lines.tag_remove(decomp.print_dcl()) +";" 923 | else: 924 | decl = "void x();" 925 | result = ida_typeinf.parse_decl(tinfo, None, decl, ida_typeinf.PT_SIL) 926 | if result is not None: 927 | return tinfo 928 | return get_type_at_address(func.start_ea) 929 | elif ida_bytes.is_off0(flags): 930 | pointed_addr = read_pointer(ea) 931 | if pointed_addr is Invalid: 932 | return 933 | inner_type = get_type_at_address(pointed_addr) 934 | if inner_type is None: 935 | ida_typeinf.parse_decl(tinfo, None, f"void*;", ida_typeinf.PT_SIL) 936 | return tinfo 937 | tinfo.create_ptr(inner_type) 938 | return tinfo 939 | elif ida_bytes.is_strlit(flags): 940 | string_type = 'wchar_t' if ida_nalt.get_str_type(ea) & ida_nalt.STRTYPE_C_16 else 'char' 941 | result = ida_typeinf.parse_decl(tinfo, None, f"{string_type}[];", ida_typeinf.PT_SIL) 942 | if result is not None: 943 | return tinfo 944 | elif ida_bytes.is_data(flags): 945 | size = ida_bytes.get_item_size(ea) 946 | result = ida_typeinf.parse_decl(tinfo, None, f"__int{size*8};", ida_typeinf.PT_SIL) 947 | if result is not None: 948 | return tinfo 949 | 950 | def ida2py(tif: ida_typeinf.tinfo_t, addr: int|None = None) -> Wrapper|None: 951 | tif = tif.copy() 952 | size = tif.get_size() 953 | if tif.is_array(): 954 | tif.remove_ptr_or_array() 955 | elem_size = tif.get_size() 956 | tif.clr_const() 957 | tif.clr_volatile() 958 | if tif.get_type_name() in ["char", "wchar_t"] or tif.is_char(): 959 | return StringWrapper(size//elem_size if size > 0 else None, tif.get_type_name(), addr) 960 | assert size % elem_size == 0, (size, elem_size) 961 | return ArrayWrapper(ida2py(tif, addr), size//elem_size) 962 | elif tif.is_ptr_or_array(): 963 | tif.remove_ptr_or_array() 964 | tif.clr_const() 965 | tif.clr_volatile() 966 | return PointerWrapper(tif.get_type_name() in ["char", "wchar_t"] or tif.is_char(), tif, addr) 967 | 968 | if tif.is_struct(): 969 | return StructWrapper(tif, address=addr) 970 | 971 | if tif.is_integral(): 972 | signed = tif.is_signed() 973 | size = tif.get_size() 974 | return IntWrapper(signed, size * 8, get_byteorder(), address=addr) 975 | 976 | if tif.is_func(): 977 | func = idaapi.get_func(addr) 978 | # Decompile function to get latest type information 979 | ida_hexrays.decompile(addr, flags=ida_hexrays.DECOMP_WARNINGS) 980 | tif = get_type_at_address(addr) 981 | return FunctionWrapper(tif, func, addr) 982 | 983 | if hasattr(tif, "is_typedef") and tif.is_typedef(): 984 | typename = tif.get_final_type_name() 985 | tif2 = ida_typeinf.tinfo_t() 986 | ida_typeinf.parse_decl(tif2, None, f"{typename};", ida_typeinf.PT_SIL) 987 | return ida2py(tif2, addr) 988 | 989 | return UnknownWrapper(addr) 990 | 991 | class TypeConstructor: 992 | __tinfo: ida_typeinf.tinfo_t 993 | __wrapper_type: Wrapper 994 | 995 | def __init__(self, tinfo: ida_typeinf.tinfo_t): 996 | self.__tinfo = tinfo 997 | self.__wrapper_type = ida2py(tinfo) 998 | 999 | def ptr(self) -> "TypeConstructor": 1000 | new_tinfo = ida_typeinf.tinfo_t() 1001 | new_tinfo.create_ptr(self.__tinfo) 1002 | return TypeConstructor(new_tinfo) 1003 | 1004 | def __mul__(self, length: int) -> "TypeConstructor": 1005 | if not isinstance(length, int): 1006 | raise TypeError("Array length must be an integer") 1007 | if length <= 0: 1008 | raise ValueError("Cannot multiply type by non-positive length") 1009 | new_tinfo = ida_typeinf.tinfo_t() 1010 | new_tinfo.create_array(self.__tinfo, length) 1011 | return TypeConstructor(new_tinfo) 1012 | 1013 | def __call__(self, addr: int|None=None) -> Wrapper|None: 1014 | if addr is None: 1015 | addr = idc.here() 1016 | return ida2py(self.__tinfo, addr) 1017 | 1018 | def __matmul__(self, addr: int) -> Wrapper|None: 1019 | if addr is None: 1020 | raise ValueError("Address cannot be None when using @ operator") 1021 | return self.__call__(addr) 1022 | 1023 | def __repr__(self): 1024 | return self.__wrapper_type.type_name() 1025 | 1026 | class SearchResult(str): 1027 | value: int 1028 | accesses: int 1029 | address: int 1030 | tif: ida_typeinf.tinfo_t 1031 | 1032 | def __new__(cls, name, address=None, tif=None, accesses=0): 1033 | instance = super().__new__(cls, name) 1034 | instance.accesses = accesses 1035 | # Deprioritize names with leading underscores 1036 | instance.__base_value = -(len(name) - len(name.lstrip("_"))) 1037 | # We probably don't care about stuff in the loader segment? 1038 | if idaapi.getseg(address).is_header_segm(): 1039 | instance.__base_value -= 1 1040 | instance.address = address 1041 | instance.tif = tif 1042 | return instance 1043 | 1044 | # Higher, the better 1045 | @property 1046 | def value(self): 1047 | # Prioritize names with more accesses 1048 | return self.__base_value + self.accesses 1049 | 1050 | def __lt__(self, other): 1051 | if not isinstance(other, SearchResult) or self.value == other.value: 1052 | return str(self) < str(other) 1053 | return self.value > other.value 1054 | 1055 | class Ida2py: 1056 | __names: dict[str, SearchResult] = {} 1057 | def __dir__(self): 1058 | results = [] 1059 | for address, name in idautils.Names(): 1060 | if not ida_bytes.has_user_name(ida_bytes.get_flags(address)): 1061 | continue 1062 | if any(not (c.isalnum() or c == "_") for c in name): 1063 | continue 1064 | 1065 | tinfo = get_type_at_address(address) 1066 | if tinfo is not None: 1067 | res = SearchResult(name, address, tinfo) 1068 | if name in self.__names: 1069 | res.accesses = self.__names[name].accesses 1070 | results.append(res) 1071 | self.__names[name] = res 1072 | return results 1073 | 1074 | def __getattr__(self, name): 1075 | caller_name = inspect.currentframe().f_back.f_code.co_name 1076 | if name not in self.__names: 1077 | raise AttributeError(f"name '{name}' is not defined") 1078 | 1079 | # These functions are used in IDA's autocomplete 1080 | if caller_name not in ["build_hints", "maybe_extend_syntactically"]: 1081 | # Return the real value when we actually access it 1082 | self.__names[name].accesses += 1 1083 | address = self.__names[name].address 1084 | return ida2py(get_type_at_address(address), address) 1085 | else: 1086 | # Return a fake object with the correct type name 1087 | X = type('X', (object,), {}) 1088 | X.__name__ = self.__names[name].tif.dstr() 1089 | return X() 1090 | 1091 | def __call__(self, key): 1092 | if type(key) is int: 1093 | addr = key 1094 | else: 1095 | addr = idc.get_name_ea_simple(key) 1096 | if addr == idc.BADADDR: 1097 | tinfo = ida_typeinf.tinfo_t() 1098 | result = ida_typeinf.parse_decl(tinfo, None, f"{key} x;", ida_typeinf.PT_SIL) 1099 | if result is not None: 1100 | return TypeConstructor(tinfo) 1101 | if key.startswith("u"): 1102 | result = ida_typeinf.parse_decl(tinfo, None, f"unsigned {key[1:]} x;", ida_typeinf.PT_SIL) 1103 | if result is not None: 1104 | return TypeConstructor(tinfo) 1105 | raise NameError(f"name '{key}' is not defined") 1106 | tinfo = get_type_at_address(addr) 1107 | if tinfo is not None: 1108 | ret = ida2py(tinfo, addr) 1109 | if ret is not None: 1110 | return ret 1111 | return UnknownWrapper(addr) 1112 | 1113 | # In case global hooking doesn't work 1114 | def hook(g): 1115 | obase = py_object.from_address(id(g) + 8) 1116 | class fglobals(dict): 1117 | __slots__ = () 1118 | def __getitem__(self, key, dict=dict, obase=obase): 1119 | try: 1120 | obase.value = dict 1121 | if key in self: 1122 | return self[key] 1123 | if hasattr(builtins, key): 1124 | return getattr(builtins, key) 1125 | return _ida(key) 1126 | finally: 1127 | obase.value = __class__ 1128 | obase.value = fglobals 1129 | g["_ida"] = _ida 1130 | g["angr_exec"] = angr_exec 1131 | 1132 | _ida = Ida2py() 1133 | 1134 | if __name__.startswith("__plugins__"): 1135 | obase = py_object.from_address(id(__main__.__dict__) + 8) 1136 | class fglobals(dict): 1137 | __slots__ = () 1138 | def __getitem__(self, key, dict=dict, obase=obase): 1139 | try: 1140 | obase.value = dict 1141 | if key in self: 1142 | return self[key] 1143 | if hasattr(builtins, key): 1144 | return getattr(builtins, key) 1145 | return _ida(key) 1146 | finally: 1147 | obase.value = __class__ 1148 | 1149 | obase.value = fglobals 1150 | __main__.__dict__["_ida"] = _ida 1151 | __main__.__dict__["angr_exec"] = angr_exec 1152 | -------------------------------------------------------------------------------- /img/angr_symbolic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junron/ida2py/ee57d989d779c079a4a0b9a6de1a035762773801/img/angr_symbolic.png -------------------------------------------------------------------------------- /img/arr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junron/ida2py/ee57d989d779c079a4a0b9a6de1a035762773801/img/arr.png -------------------------------------------------------------------------------- /img/heap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junron/ida2py/ee57d989d779c079a4a0b9a6de1a035762773801/img/heap.png -------------------------------------------------------------------------------- /img/pair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junron/ida2py/ee57d989d779c079a4a0b9a6de1a035762773801/img/pair.png -------------------------------------------------------------------------------- /img/sshd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junron/ida2py/ee57d989d779c079a4a0b9a6de1a035762773801/img/sshd.png -------------------------------------------------------------------------------- /tests/angr_symbolic.py: -------------------------------------------------------------------------------- 1 | import idapro 2 | import os 3 | import sys 4 | import angr 5 | import claripy 6 | import idc 7 | 8 | cur_dir = os.path.dirname(__file__) 9 | 10 | sys.path.append(f"{cur_dir}/..") 11 | idapro.open_database(f"{cur_dir}/binaries/angr_symbolic", True) 12 | 13 | try: 14 | import ida2py 15 | # angr doesn't like being global hooked in a test environment 16 | # But it works in normal IDA 17 | 18 | # ida2py.hook(globals()) 19 | idc.SetType(0x4040, "char ciphertext[126];") 20 | ct = ida2py._ida("ciphertext") 21 | encrypt = ida2py._ida("encrypt") 22 | l = len(ct) 23 | with ida2py.angr_exec() as e: 24 | msg = claripy.BVS("msg", 8 * l) 25 | buf = e.buf(msg) 26 | encrypt(buf, l) 27 | e.state.add_constraints(buf == ct) 28 | res = e.state.solver.eval(msg, cast_to=bytes) 29 | 30 | print(res.decode()) 31 | 32 | assert res == b"Happy New Year!!!\n\nYou climbed a mountain, are you satisfied?\nAs you stand at the top\nYou already wanna do this\nOne more time\n" 33 | finally: 34 | idapro.close_database(False) -------------------------------------------------------------------------------- /tests/basic_test.py: -------------------------------------------------------------------------------- 1 | import idapro 2 | import os 3 | import sys 4 | 5 | cur_dir = os.path.dirname(__file__) 6 | 7 | sys.path.append(f"{cur_dir}/..") 8 | idapro.open_database(f"{cur_dir}/binaries/basic_test", True) 9 | 10 | try: 11 | import ida2py 12 | 13 | ida2py.hook(globals()) 14 | 15 | print("arr_ptr", arr_ptr) 16 | 17 | print("names", names) 18 | assert type(names) is ida2py.PointerWrapper 19 | assert names.pyval() == b"hell" 20 | 21 | # Variable types will update when set in ida` 22 | import idc 23 | idc.SetType(names.address, "char * names[4];") 24 | print("names2", names) 25 | assert type(names) is ida2py.ArrayWrapper 26 | assert len(names) == 4 27 | assert names[0].pyval() == b"hell" 28 | assert names.pyval() == [b'hell', b'ow', b'world', b'dl'] 29 | assert names[1][0] == ord('o') 30 | assert names[-1].pyval() == b"dl" 31 | 32 | array = (uint * 5) @ arr.address 33 | print("array", array) 34 | assert type(array) is ida2py.ArrayWrapper 35 | assert type(array[0]) is ida2py.IntWrapper 36 | assert array[0] == 1 37 | assert array[1] + 3 == 5 38 | assert array[array[0]] == 2, array[array[0]] 39 | 40 | 41 | idc.SetType(arr_ptr.address, "int* arr_ptr;") 42 | assert arr_ptr[0] == 1 43 | assert arr_ptr[1] == 2 44 | 45 | 46 | arr_ptr2 = (ulong * 2).ptr() @ arr_ptr.address 47 | 48 | print(arr_ptr2) 49 | assert arr_ptr2.pyval() == [8589934593, 17179869187] 50 | 51 | idc.parse_decls(""" 52 | struct Node{ 53 | unsigned long val; 54 | Node* left; 55 | Node* right; 56 | }; 57 | """, 0) 58 | idc.SetType(n1.address, "Node") 59 | 60 | print("n1", n1) 61 | assert n1.val == 90 62 | assert n1.pyval() == {'val': 0x5a, 'left': {'val': 0x56, 'left': {'val': 0x1, 'left': None, 'right': None}, 'right': None}, 'right': {'val': 0x41, 'left': None, 'right': None}} 63 | 64 | # Autocomplete tests 65 | print(dir(_ida)) 66 | assert all(x in dir(_ida) for x in ["n1", "names", "arr_ptr", "main"]) 67 | # TODO: Will be fixed in next version with type detection 68 | # assert "arr" in dir(_ida) 69 | def build_hints(): 70 | assert _ida.n1.__class__.__name__ == "Node", _ida.n1.__class__.__name__ 71 | build_hints() 72 | _ida.n1 73 | _ida.n1 74 | # Test adaptive ordering 75 | assert dir(_ida)[0] == "n1", dir(_ida) 76 | finally: 77 | idapro.close_database(False) -------------------------------------------------------------------------------- /tests/binaries/angr_symbolic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junron/ida2py/ee57d989d779c079a4a0b9a6de1a035762773801/tests/binaries/angr_symbolic -------------------------------------------------------------------------------- /tests/binaries/basic_test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junron/ida2py/ee57d989d779c079a4a0b9a6de1a035762773801/tests/binaries/basic_test -------------------------------------------------------------------------------- /tests/binaries/flareon_sshd_shellcode: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junron/ida2py/ee57d989d779c079a4a0b9a6de1a035762773801/tests/binaries/flareon_sshd_shellcode -------------------------------------------------------------------------------- /tests/binaries/rc4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junron/ida2py/ee57d989d779c079a4a0b9a6de1a035762773801/tests/binaries/rc4 -------------------------------------------------------------------------------- /tests/flareon_sshd_shellcode.py: -------------------------------------------------------------------------------- 1 | import idapro 2 | import os 3 | import sys 4 | 5 | cur_dir = os.path.dirname(__file__) 6 | 7 | sys.path.append(f"{cur_dir}/..") 8 | idapro.open_database(f"{cur_dir}/binaries/flareon_sshd_shellcode", True) 9 | 10 | try: 11 | import ida2py 12 | # angr doesn't like being global hooked in a test environment 13 | # But it works in normal IDA 14 | 15 | # ida2py.hook(globals()) 16 | 17 | import idc 18 | 19 | idc.set_name(0x401CD2, "setup") 20 | idc.SetType(0x401CD2, "void __usercall setup(__int64 a1@, __int64 a2@, __int64 a3@, __int64 a4@)") 21 | setup = ida2py._ida("setup") 22 | 23 | idc.set_name(0x401D49, "decrypt") 24 | idc.SetType(0x401D49, "void __usercall decrypt(__int64 a1@, __int64 a2@, __int64 a3@)") 25 | decrypt = ida2py._ida("decrypt") 26 | 27 | key = bytes.fromhex("8d ec 91 12 eb 76 0e da 7c 7d 87 a4 43 27 1c 35 d9 e0 cb 87 89 93 b4 d9 04 ae f9 34 fa 21 66 d7") 28 | nonce = bytes.fromhex("11 11 11 11 11 11 11 11 11 11 11 11") 29 | ct = bytes.fromhex("A9 F6 34 08 42 2A 9E 1C 0C 03 A8 08 94 70 BB 8D AA DC 6D 7B 24 FF 7F 24 7C DA 83 9E 92 F7 07 1D 02 63 90 2E C1 58") 30 | 31 | with ida2py.angr_exec() as executor: 32 | state = executor.alloc(0x100) 33 | buf = executor.buf(ct) 34 | 35 | setup(state, key, nonce, 0) 36 | decrypt(state, buf, len(ct)) 37 | 38 | print("output:", buf.bytes()) 39 | assert b'supp1y_cha1n_sund4y@flare-on.com' in buf.bytes() 40 | finally: 41 | idapro.close_database(False) -------------------------------------------------------------------------------- /tests/rc4.py: -------------------------------------------------------------------------------- 1 | import idapro 2 | import os 3 | import sys 4 | 5 | cur_dir = os.path.dirname(__file__) 6 | 7 | sys.path.append(f"{cur_dir}/..") 8 | idapro.open_database(f"{cur_dir}/binaries/rc4", True) 9 | 10 | try: 11 | import ida2py 12 | # angr doesn't like being global hooked in a test environment 13 | # But it works in normal IDA 14 | 15 | # ida2py.hook(globals()) 16 | key = "SecretKey123" 17 | ct = b"\x01\x83\xb8#\xba\x8d^\xb6L\xd0}Jx\xc9\xe8" 18 | with ida2py.angr_exec(): 19 | malloc = ida2py._ida("malloc") 20 | rc4_init = ida2py._ida("rc4_init") 21 | rc4_process = ida2py._ida("rc4_process") 22 | print_bytes = ida2py._ida("print_bytes") 23 | printf = ida2py._ida("printf") 24 | 25 | state = malloc(0x200) # little bit extra to be safe 26 | rc4_init(state, key, len(key)) 27 | 28 | out = malloc(len(ct)) 29 | rc4_process(state, ct, out, len(ct)) 30 | print_bytes(out, len(ct)) 31 | 32 | assert out.bytes() == b"ida2py RC4 demo", out.bytes() 33 | 34 | printf("output: %s\n", out) 35 | finally: 36 | idapro.close_database(False) -------------------------------------------------------------------------------- /tests/src/angr_symbolic.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // Generated by Claude 5 | void encrypt(unsigned char *data, int length) { 6 | for (int i = 0; i < length; i++) { 7 | unsigned char c = data[i]; 8 | c = (c << 2) | (c >> 6); // Rotate left by 2 9 | c ^= 0xAA; // XOR with pattern 10 | c += i % 5; // Position-dependent addition 11 | data[i] = c; 12 | } 13 | } 14 | 15 | unsigned char ciphertext[] = {139, 48, 109, 110, 83, 42, 148, 65, 122, 46, 207, 64, 49, 102, 50, 46, 47, 132, 133, 211, 23, 128, 44, 42, 31, 15, 32, 37, 66, 63, 42, 48, 44, 34, 27, 127, 20, 125, 50, 19, 19, 27, 44, 50, 103, 63, 43, 81, 26, 131, 42, 104, 49, 126, 19, 103, 52, 17, 66, 63, 86, 131, 177, 106, 46, 79, 24, 129, 45, 107, 123, 48, 21, 62, 46, 47, 124, 44, 126, 15, 63, 43, 125, 26, 111, 130, 208, 25, 130, 46, 47, 28, 101, 66, 51, 59, 80, 44, 122, 51, 19, 20, 49, 45, 63, 23, 43, 125, 14, 19, 103, 131, 153, 22, 67, 42, 32, 25, 102, 67, 42, 124, 17, 34, 67, 130, 0}; 16 | 17 | int main(){ 18 | char buf[0x100]; 19 | int len = strlen(ciphertext); 20 | for(int i=0;i 2 | #include 3 | 4 | const char* names[4] = { "hell", "ow", "world", "dl" }; 5 | 6 | int arr[5] = { 1,2,3,4,5 }; 7 | int* arr_ptr = arr; 8 | 9 | int main(int argc, char* argv[]); 10 | 11 | 12 | struct Node { 13 | int val; 14 | struct Node* left; 15 | struct Node* right; 16 | }; 17 | 18 | struct Node n2 { 19 | 65, NULL, NULL 20 | }; 21 | struct Node n3 { 22 | 1, NULL, NULL 23 | }; 24 | struct Node n4 { 25 | 86, &n3, NULL 26 | }; 27 | 28 | struct Node n1 { 29 | 90, &n4, &n2 30 | }; 31 | 32 | int main(int argc, char* argv[]) 33 | { 34 | 35 | std::cout << "Hello, world!" << std::endl; 36 | std::cout << names[0] << std::endl; 37 | std::cout << arr_ptr[1] << std::endl; 38 | std::cout << n1.left->left->val << std::endl; 39 | } -------------------------------------------------------------------------------- /tests/src/rc4.c: -------------------------------------------------------------------------------- 1 | // Contents generated by claude 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | // RC4 State Structure 8 | typedef struct { 9 | unsigned char S[256]; 10 | int i, j; 11 | } RC4_state; 12 | 13 | // Initialize RC4 state with key 14 | void rc4_init(RC4_state *state, const unsigned char *key, int key_length) { 15 | // Initialize state array 16 | for (int i = 0; i < 256; i++) { 17 | state->S[i] = i; 18 | } 19 | 20 | // Key-scheduling algorithm (KSA) 21 | int j = 0; 22 | for (int i = 0; i < 256; i++) { 23 | j = (j + state->S[i] + key[i % key_length]) % 256; 24 | 25 | // Swap elements 26 | unsigned char temp = state->S[i]; 27 | state->S[i] = state->S[j]; 28 | state->S[j] = temp; 29 | } 30 | 31 | // Reset index values 32 | state->i = 0; 33 | state->j = 0; 34 | } 35 | 36 | // Generate pseudo-random byte (Pseudo-random generation algorithm - PRGA) 37 | unsigned char rc4_generate_byte(RC4_state *state) { 38 | state->i = (state->i + 1) % 256; 39 | state->j = (state->j + state->S[state->i]) % 256; 40 | 41 | // Swap elements 42 | unsigned char temp = state->S[state->i]; 43 | state->S[state->i] = state->S[state->j]; 44 | state->S[state->j] = temp; 45 | 46 | // Return pseudo-random byte 47 | int k = (state->S[state->i] + state->S[state->j]) % 256; 48 | return state->S[k]; 49 | } 50 | 51 | // Encrypt or decrypt data (RC4 is symmetric) 52 | void rc4_process(RC4_state *state, 53 | const unsigned char *input, 54 | unsigned char *output, 55 | int length) { 56 | for (int n = 0; n < length; n++) { 57 | // XOR input byte with generated pseudo-random byte 58 | output[n] = input[n] ^ rc4_generate_byte(state); 59 | } 60 | } 61 | 62 | // Utility function to print byte array 63 | void print_bytes(const unsigned char *data, int length) { 64 | for (int i = 0; i < length; i++) { 65 | printf("%02x ", data[i]); 66 | } 67 | printf("\n"); 68 | } 69 | 70 | int main() { 71 | // Example usage 72 | const unsigned char key[] = "SecretKey123"; 73 | const char* plaintext = "Hello, RC4 Encryption!"; 74 | 75 | // Allocate buffers 76 | int text_length = strlen(plaintext); 77 | unsigned char *encrypted = malloc(text_length); 78 | unsigned char *decrypted = malloc(text_length); 79 | 80 | // Initialize RC4 state 81 | RC4_state state; 82 | rc4_init(&state, key, strlen(key)); 83 | 84 | // Encrypt 85 | printf("Original: %s\n", plaintext); 86 | printf("Original Bytes: "); 87 | print_bytes((unsigned char*)plaintext, text_length); 88 | 89 | rc4_process(&state, (unsigned char*)plaintext, encrypted, text_length); 90 | printf("Encrypted Bytes: "); 91 | print_bytes(encrypted, text_length); 92 | 93 | // Reset state for decryption 94 | rc4_init(&state, key, strlen(key)); 95 | 96 | // Decrypt 97 | rc4_process(&state, encrypted, decrypted, text_length); 98 | decrypted[text_length] = '\0'; // Null-terminate 99 | printf("Decrypted: %s\n", decrypted); 100 | 101 | // Free memory 102 | free(encrypted); 103 | free(decrypted); 104 | 105 | return 0; 106 | } --------------------------------------------------------------------------------