mandiant / Ghidrathon

The FLARE team's open-source extension to add Python 3 scripting to Ghidra.
Apache License 2.0
696 stars 54 forks source link

isinstance() not working for Ghidra types #13

Closed pieceofsummer closed 2 years ago

pieceofsummer commented 2 years ago

Seems like isinstance() is broken for Ghidra types:


   _____ _     _     _           _   _                 
  / ____| |   (_)   | |         | | | |                
 | |  __| |__  _  __| |_ __ __ _| |_| |__   ___  _ __  
 | | |_ | '_ \| |/ _` | '__/ _` | __| '_ \ / _ \| '_ \ 
 | |__| | | | | | (_| | | | (_| | |_| | | | (_) | | | |
  \_____|_| |_|_|\__,_|_|  \__,_|\__|_| |_|\___/|_| |_|

Python 3.10.6 Interpreter for Ghidra. Developed by FLARE.

>>> from ghidra.program.database.data import *
>>> dm = currentProgram.getDataTypeManager()
>>> dt = next(x for x in dm.getAllDataTypes() if x.getName() == 'CreateProcessW')
>>> print(type(dt))
<class 'ghidra.program.database.data.FunctionDefinitionDB'>
>>> print(FunctionDefinitionDB)
class ghidra.program.database.data.FunctionDefinitionDB
>>> isinstance(dt, FunctionDefinitionDB)
Traceback (most recent call last):
  File "/Users/pieceofsummer/.ghidra/.ghidra_10.1.5_PUBLIC/Extensions/Ghidrathon-1.0.0/data/python/jepeval.py", line 66, in jepeval
    more_input_needed = _jepeval(line)
  File "/Users/pieceofsummer/.ghidra/.ghidra_10.1.5_PUBLIC/Extensions/Ghidrathon-1.0.0/data/python/jepeval.py", line 49, in _jepeval
    exec(compile(line, "<string>", "single"), globals(), globals())
  File "<string>", line 1, in <module>
TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union
mike-hunhoff commented 2 years ago

Thank you for the report @pieceofsummer ! We will do some digging here to see if we can pinpoint the issue, or upstream if Jep-specific. In the meantime, the following workaround should resolve the exception:

isinstance(dt, type(FunctionDefinitionDB))
pieceofsummer commented 2 years ago

Hi.

Unfortunately, the proposed workaround won't work. Of course, it doesn't throw errors anymore, but the result is absolutely wrong:

>>> dm = currentProgram.getDataTypeManager()
>>> dt = next(x for x in dm.getAllDataTypes() if x.getName() == 'CreateProcessW')
>>> type(dt)
<class 'ghidra.program.database.data.FunctionDefinitionDB'>
>>> isinstance(dt, type(FunctionDefinitionDB))
False
>>> type(FunctionDefinitionDB)
<class 'jep.PyJClass'>

The only workaround I've found for this, is 'FunctionDefinitionDB' in str(type(dt)). Obviously something one won't expect to see in good code, but at least it works )

mike-hunhoff commented 2 years ago

Nice catch - apologies for that. I did some more digging and Jep exposes the underlying Python type through __pytype__ e.g.:

isinstance(dt, FunctionDefinitionDB.__pytype__)

that in my testing worked with isinstance:


   _____ _     _     _           _   _                 
  / ____| |   (_)   | |         | | | |                
 | |  __| |__  _  __| |_ __ __ _| |_| |__   ___  _ __  
 | | |_ | '_ \| |/ _` | '__/ _` | __| '_ \ / _ \| '_ \ 
 | |__| | | | | | (_| | | | (_| | |_| | | | (_) | | | |
  \_____|_| |_|_|\__,_|_|  \__,_|\__|_| |_|\___/|_| |_|

Python 3.11.0 Interpreter for Ghidra. Developed by FLARE.

>>> from ghidra.program.model.data import *
>>> dt = next(x for x in currentProgram.getDataTypeManager().getAllDataTypes() if x.getName() == "uint")
>>> dt
<ghidra.program.model.data.UnsignedIntegerDataType object at 0x000001BFD2CAB500>
>>> type(UnsignedIntegerDataType)
<class 'jep.PyJClass'>
>>> isinstance(dt, UnsignedIntegerDataType.__pytype__)
True
>>> isinstance(int, UnsignedIntegerDataType.__pytype__)
False

We want to see isinstance used without a workaround and we'll work to improve this.

pieceofsummer commented 2 years ago

Yep, that works. Thanks for your effort!

mike-hunhoff commented 2 years ago

@pieceofsummer I sent this issue upstream and the Jep developers came up with a fix (see https://github.com/ninia/jep/pull/440). Unfortunately it may be some time before the fix makes it in a Jep release. For now, we've added a workaround (see https://github.com/mandiant/Ghidrathon/commit/fdf6c4590ba346f884e0c61087205ea532cce013) that will be included in the Ghidrathon 2.0.0 release, likely dropping later today. I'll open a separate issue to remind us to remove the workaround when the Jep fix is released.


   _____ _     _     _           _   _                 
  / ____| |   (_)   | |         | | | |                
 | |  __| |__  _  __| |_ __ __ _| |_| |__   ___  _ __  
 | | |_ | '_ \| |/ _` | '__/ _` | __| '_ \ / _ \| '_ \ 
 | |__| | | | | | (_| | | | (_| | |_| | | | (_) | | | |
  \_____|_| |_|_|\__,_|_|  \__,_|\__|_| |_|\___/|_| |_|

Python 3.11.0 Interpreter for Ghidra 10.2. Developed by FLARE.

>>> from ghidra.program.database import ProgramDB
>>> type(currentProgram)
<class 'ghidra.program.database.ProgramDB'>
>>> type(ProgramDB)
<class 'jep.PyJClass'>
>>> isinstance(currentProgram, ProgramDB)
True
>>> isinstance(currentProgram, str)
False