ndrewh / pyda

Write dynamic binary analysis tools in Python
Other
7 stars 0 forks source link

feat: "native" calls #2

Closed ndrewh closed 1 month ago

ndrewh commented 4 months ago

I think in theory you can just cast an int to a function pointer using ctypes.cast and call it, but I'm not sure. It would be cool to make it easier to do make these calls e.g:

e = ELF(...)
crc32_result = p.call[e.symbols["crc32"]](0, b"AAAA", 4)

It would also be nice to do this with some guardrails. For example:

These guardrails would be quite complicated to implement, so some analysis of use-cases is warranted...

ndrewh commented 2 months ago

I've realized there's a decent chance directly calling into the executable won't work as intended, depending on how much indirection is imposed by dynamorio in the first place. I don't think dynamorio actually ever jumps directly to app code; it always translates blocks and then jumps to the translation in the code cache. Depending on whether it also translates certain data accesses, we might not find the library in the state we intended.

Instead, I think it would be useful to have a primitive to "run code from X to Y safely". This has several applications, including making it easier to implement a call primitive. The basic flow needs to look something like this:

  1. Assert that the application is stopped (e.g. after a run_until. note: being in a hook does not qualify)
  2. Save the current mcontext (so we can restore it upon return from the call)
  3. Set up the new mcontext for the call (e.g. put args in registers), update rip
  4. p.run_until(end_addr)
  5. Restore mcontext
ndrewh commented 2 months ago

I've updated the previous comment now that run_until is implemented.

I think all we need to implement this is a push/pop operation for register state. We can leave the notion of "safety" or "rollback" to another issue (#31).