├── .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 |
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 | 
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 | 
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 | 
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 | 
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 | }
--------------------------------------------------------------------------------