GithubHelp home page GithubHelp logo

Comments (20)

Harding-Stardust avatar Harding-Stardust commented on July 17, 2024 1

I tried it and I got the following trace log:

Jupyter QtConsole 5.4.0
Python 3.8.10 (tags/v3.8.10:3d8993a, May  3 2021, 11:48:03) [MSC v.1928 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.9.0 -- An enhanced Interactive Python. Type '?' for help.

database.functions.list()
---------------------------------------------------------------------------
DisassemblerError                         Traceback (most recent call last)
Cell In[1], line 1
----> 1 database.functions.list()

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:694, in database.list(*arguments, **keywords)
    691 # Now we have a matching callable for the user's parameters, and we just need
    692 # to unpack our individual parameters and dispatch to the callable with them.
    693 parameters, wild_parameters, keyword_parameters = result_parameters
--> 694 return result_callable(*itertools.chain(parameters, wild_parameters), **keyword_parameters)

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\database.py:598, in functions.list(cls, **type)
    596     '''List all of the functions in the database with a glob that matches `string`.'''
    597     return cls.list(like=string)
--> 598 @utils.multicase()
    599 @classmethod
    600 @utils.string.decorate_arguments('name', 'like', 'regex')
    601 def list(cls, **type):
    602     '''List all of the functions in the database that match the keyword specified by `type`.'''
    603     listable = []

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:1994, in transform.<locals>.wrapper(F, *rargs, **rkwds)
   1992         cls = E.__class__
   1993         raise cls("{!s}: Exception raised while transforming parameter `{:s}` with value {!r}".format('.'.join([f.__module__, f.__name__]), argname, kwds[argname]))
-> 1994 return F(*res, **kwds)

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\database.py:633, in functions.list(cls, **type)
    631     refs = max(len(xref.up(ea)), refs)
    632     lvars = max(Fcount_lvars(func) if idaapi.get_frame(ea) else 0, lvars)
--> 633     avars = max(Fcount_avars(func), avars)
    635     listable.append(ea)
    637 # Collect the number of digits for everything from the first pass

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:56, in <lambda>(*a)
     54 first, second, third, last = operator.itemgetter(0), operator.itemgetter(1), operator.itemgetter(2), operator.itemgetter(-1)
     55 # return a closure that executes a list of functions one after another from left-to-right.
---> 56 fcompose = lambda *Fa: functools.reduce(lambda F1, F2: lambda *a: F1(F2(*a)), builtins.reversed(Fa))
     57 # return a closure that executes function `F` whilst discarding any arguments passed to it.
     58 fdiscard = lambda F, *a, **k: lambda *ap, **kp: F(*a, **k)

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:56, in <lambda>(*a)
     54 first, second, third, last = operator.itemgetter(0), operator.itemgetter(1), operator.itemgetter(2), operator.itemgetter(-1)
     55 # return a closure that executes a list of functions one after another from left-to-right.
---> 56 fcompose = lambda *Fa: functools.reduce(lambda F1, F2: lambda *a: F1(F2(*a)), builtins.reversed(Fa))
     57 # return a closure that executes function `F` whilst discarding any arguments passed to it.
     58 fdiscard = lambda F, *a, **k: lambda *ap, **kp: F(*a, **k)

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:56, in <lambda>(*a)
     54 first, second, third, last = operator.itemgetter(0), operator.itemgetter(1), operator.itemgetter(2), operator.itemgetter(-1)
     55 # return a closure that executes a list of functions one after another from left-to-right.
---> 56 fcompose = lambda *Fa: functools.reduce(lambda F1, F2: lambda *a: F1(F2(*a)), builtins.reversed(Fa))
     57 # return a closure that executes function `F` whilst discarding any arguments passed to it.
     58 fdiscard = lambda F, *a, **k: lambda *ap, **kp: F(*a, **k)

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\function.py:2650, in frame.arguments.iterate(cls, func)
   2646     return
   2648 # If we got here, then we have type information that we can grab out
   2649 # of the given address. Once we have it, rip the details out o it.
-> 2650 tinfo = type(ea)
   2651 _, ftd = interface.tinfo.function_details(ea, tinfo)
   2653 # Now we just need to iterate through our parameters collecting the
   2654 # raw location information for all of them. We preserve the type
   2655 # information in case we're unable to find the argument in a member.

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:694, in function.__new__(*arguments, **keywords)
    691 # Now we have a matching callable for the user's parameters, and we just need
    692 # to unpack our individual parameters and dispatch to the callable with them.
    693 parameters, wild_parameters, keyword_parameters = result_parameters
--> 694 return result_callable(*itertools.chain(parameters, wild_parameters), **keyword_parameters)

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\function.py:3232, in type.__new__(cls, func)
   3229 # If we weren't able to create the missing type, then we need to abort. This shouldn't
   3230 # be possible at all whatsoever, but perhaps some database state is preventing us.
   3231 if not missing:
-> 3232     raise E.DisassemblerError(u"{:s}({:#x}) : Unable to create a dummy function type to return for the for the specified function ({:#x}).".format('.'.join([__name__, cls.__name__]), ea, ea))
   3234 logging.warning(u"{:s}({:#x}) : Unable to guess the missing type for the function at {:#x} due to error ({:d}) which will result in an empty function type (\"{:s}\") being returned.".format('.'.join([__name__, cls.__name__]), ea, ea, idaapi.GUESS_FUNC_FAILED, utils.string.escape("{!s}".format(missing), '"')))
   3235 return missing

DisassemblerError: function.type(0x401029) : Unable to create a dummy function type to return for the for the specified function (0x401029).

from ida-minsc.

Harding-Stardust avatar Harding-Stardust commented on July 17, 2024 1

I found the problem and it was in one of my scripts (that I didn't knew was loaded). It was this line:

setattr(ida_typeinf.tinfo_t, '__len__', lambda self: self.get_size() if self.get_size() != ida_typeinf.BADSIZE else 0)

That is messing up the if-statment in

        # Then we'll return each type that we fetched while prioritizing the one that is
        # stored in NSUP_, then the guessed one, and then falling back to the missing one.
        if nsupped or guessed:
            return nsupped or guessed # nsupped is OK here but the __len__() returns 0 and that makes the    if nsupped evaluate to false.

tinfo_t with function prototypes have a size of 0 and when there is no bool it checks for len and if that returns 0 then the if statement fails.

Sorry to take up your time!

Now is the database.functions.list() also working as expected <3

from ida-minsc.

Harding-Stardust avatar Harding-Stardust commented on July 17, 2024 1

I added

setattr(ida_typeinf.tinfo_t, '__bool__', lambda self: self.is_well_defined())

to my code and now it all works well.

from ida-minsc.

arizvisa avatar arizvisa commented on July 17, 2024 1

Lol. And that, hands-down, is why Python is a terrible programming language.

from ida-minsc.

arizvisa avatar arizvisa commented on July 17, 2024 1

I closed the PR, and will close this issue. If you feel that is in error, let me know and I'll re-open.

Btw, you should consider the "persistence-refactor" branch if you believe the things in this plugin are actually useful to you. It's development is still ongoing, but it significantly improves structure arithmetic, all the matchers allow iterable types, tagging and searches are improved, operand references can be used for referencing specific operands, all references now bundle their access ('rwx'), etc. There's quite a lot. The "master" branch is about a year behind, and is before i decided to go "all-in" on some of the original features.

As an example, since you're looking at database.functions.list, you can do.

db.functions.list(ea=[addr1, addr2, addr3])

# now capture the results
addrs = db.functions(ea=[addr1, addr2, addr3])

# feed them back into the matchers
refs = [func.up(ea) for ea in addrs]
db.functions.list(ea=itertools.chain(*refs), like='CCmdTarget::*')

# consolidate into one line, and just list all functions that reference a function that references your target function
db.functions.list(ea=itertools.chain(*map(func.up, db.functions(ea=[addr1, addr2, addr2]))))

Or if you want to distinguish calls in the current function that dereference an address.

for ref in func.calls():
    if '&' in ref: print(db.disasm(ref))

# something with registers being written to
for ref in func.registers(ins.reg.eax):
    if 'w' in ref: print(db.disasm(ref))

If you have any questions, feel free to ask in the discussions.

from ida-minsc.

arizvisa avatar arizvisa commented on July 17, 2024

What version of IDA are you using, and can you include the full backtrace? I'm thinking that it's happening at line 683 or 633, but the line number you provided (line 595) doesn't show which callable in the function module is actually failing (the one that uses internal.interface.tinfo.function_details).

def list(cls, string):

Specifically to reproduce your issue, you should be able to use db.functions.list(ea=0x401029) which should exclude this line number from your backtrace and result in the same problem.

So... I'm guessing here, but there's a chance that the type of your function at 0x401029 is missing.. This is actually strange, because on IDA 7.7SP1 (on Windows), when a type is not applied to a function...it should always be guessing the type. This is the same on all of the instances of IDA that I currently have available. However, I'm sure the full backtrace of the exception that gets raised would give me a better idea of what is happening anyways.

Example of what it should look like on 7.7 SP1 (windows)

.text:0000000180007170                       ; =============== S U B R O U T I N E =======================================
.text:0000000180007170
.text:0000000180007170
.text:0000000180007170                       sub_180007170   proc near               ; DATA XREF: .data:00000001800D3D08↓o
.text:0000000180007170                                                               ; .pdata:00000001800DB210↓o
.text:0000000180007170
.text:0000000180007170                       var_88          = dword ptr -88h
.text:0000000180007170                       var_80          = dword ptr -80h
.text:0000000180007170                       var_78          = dword ptr -78h

Python>func.t()
__int64 __fastcall()

Same thing on 8.3 (non-windows)

.text:10001B80                                   ; =============== S U B R O U T I N E =======================================
.text:10001B80
.text:10001B80
.text:10001B80                                   sub_10001B80    proc near               ; CODE XREF: sub_10001000+BC↑p
.text:10001B80                                                                           ; sub_10001A60+3C↑p
.text:10001B80
.text:10001B80                                   var_10          = dword ptr -10h

Python>func.t()
int __cdecl(int, int)

So, because of this guess...can you print the type of the function in your database that is raising the exception with the following? I might have more questions after this once I can see exactly which line is causing the issue, but I'm theorizing that the following line is returning None in your environment for some reason.

print(function.type(0x401029))

Also, if you're able to transfer me your database (or just a database with that function in it), that would allow me to identify what specifically about that function is causing the issue...but I understand if that's not something that you're able to do.

from ida-minsc.

Harding-Stardust avatar Harding-Stardust commented on July 17, 2024

So I cannot send the IDA database but I can give you a full trace log and I can send the file that I used to test it on. It's an old 32-bit PE file that is a very easy crackme (not mine)

Jupyter QtConsole 5.4.0
Python 3.8.10 (tags/v3.8.10:3d8993a, May  3 2021, 11:48:03) [MSC v.1928 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.9.0 -- An enhanced Interactive Python. Type '?' for help.

database.functions.list()
---------------------------------------------------------------------------
InvalidTypeOrValueError                   Traceback (most recent call last)
Cell In[1], line 1
----> 1 database.functions.list()

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:694, in database.list(*arguments, **keywords)
    691 # Now we have a matching callable for the user's parameters, and we just need
    692 # to unpack our individual parameters and dispatch to the callable with them.
    693 parameters, wild_parameters, keyword_parameters = result_parameters
--> 694 return result_callable(*itertools.chain(parameters, wild_parameters), **keyword_parameters)

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\database.py:598, in functions.list(cls, **type)
    596     '''List all of the functions in the database with a glob that matches `string`.'''
    597     return cls.list(like=string)
--> 598 @utils.multicase()
    599 @classmethod
    600 @utils.string.decorate_arguments('name', 'like', 'regex')
    601 def list(cls, **type):
    602     '''List all of the functions in the database that match the keyword specified by `type`.'''
    603     listable = []

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:1994, in transform.<locals>.wrapper(F, *rargs, **rkwds)
   1992         cls = E.__class__
   1993         raise cls("{!s}: Exception raised while transforming parameter `{:s}` with value {!r}".format('.'.join([f.__module__, f.__name__]), argname, kwds[argname]))
-> 1994 return F(*res, **kwds)

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\database.py:633, in functions.list(cls, **type)
    631     refs = max(len(xref.up(ea)), refs)
    632     lvars = max(Fcount_lvars(func) if idaapi.get_frame(ea) else 0, lvars)
--> 633     avars = max(Fcount_avars(func), avars)
    635     listable.append(ea)
    637 # Collect the number of digits for everything from the first pass

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:56, in <lambda>(*a)
     54 first, second, third, last = operator.itemgetter(0), operator.itemgetter(1), operator.itemgetter(2), operator.itemgetter(-1)
     55 # return a closure that executes a list of functions one after another from left-to-right.
---> 56 fcompose = lambda *Fa: functools.reduce(lambda F1, F2: lambda *a: F1(F2(*a)), builtins.reversed(Fa))
     57 # return a closure that executes function `F` whilst discarding any arguments passed to it.
     58 fdiscard = lambda F, *a, **k: lambda *ap, **kp: F(*a, **k)

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:56, in <lambda>(*a)
     54 first, second, third, last = operator.itemgetter(0), operator.itemgetter(1), operator.itemgetter(2), operator.itemgetter(-1)
     55 # return a closure that executes a list of functions one after another from left-to-right.
---> 56 fcompose = lambda *Fa: functools.reduce(lambda F1, F2: lambda *a: F1(F2(*a)), builtins.reversed(Fa))
     57 # return a closure that executes function `F` whilst discarding any arguments passed to it.
     58 fdiscard = lambda F, *a, **k: lambda *ap, **kp: F(*a, **k)

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:56, in <lambda>(*a)
     54 first, second, third, last = operator.itemgetter(0), operator.itemgetter(1), operator.itemgetter(2), operator.itemgetter(-1)
     55 # return a closure that executes a list of functions one after another from left-to-right.
---> 56 fcompose = lambda *Fa: functools.reduce(lambda F1, F2: lambda *a: F1(F2(*a)), builtins.reversed(Fa))
     57 # return a closure that executes function `F` whilst discarding any arguments passed to it.
     58 fdiscard = lambda F, *a, **k: lambda *ap, **kp: F(*a, **k)

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\function.py:2651, in frame.arguments.iterate(cls, func)
   2648 # If we got here, then we have type information that we can grab out
   2649 # of the given address. Once we have it, rip the details out o it.
   2650 tinfo = type(ea)
-> 2651 _, ftd = interface.tinfo.function_details(ea, tinfo)
   2653 # Now we just need to iterate through our parameters collecting the
   2654 # raw location information for all of them. We preserve the type
   2655 # information in case we're unable to find the argument in a member.
   2656 items = []

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_interface.py:2797, in tinfo.function_details(cls, func, ti)
   2793     tinfo = ti
   2795 # Anything else is a type error that we need to raise to the user.
   2796 else:
-> 2797     raise internal.exceptions.InvalidTypeOrValueError(u"{:s}.function_details({:#x}, {!r}) : The type that was received ({!r}) for the specified function ({:#x}) was not a function type.".format('.'.join([__name__, cls.__name__]), ea, "{!s}".format(ti), "{!s}".format(ti), ea))
   2799 # Now we can check to see if the type has details that we can grab the
   2800 # argument type out of. If there are no details, then we raise an
   2801 # exception informing the user.
   2802 if not tinfo.has_details():

InvalidTypeOrValueError: internal.interface.tinfo.function_details(0x401029, '') : The type that was received ('') for the specified function (0x401029) was not a function type.
database.functions.list(ea=0x401029)
---------------------------------------------------------------------------
InvalidTypeOrValueError                   Traceback (most recent call last)
Cell In[2], line 1
----> 1 database.functions.list(ea=0x401029)

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:694, in database.list(*arguments, **keywords)
    691 # Now we have a matching callable for the user's parameters, and we just need
    692 # to unpack our individual parameters and dispatch to the callable with them.
    693 parameters, wild_parameters, keyword_parameters = result_parameters
--> 694 return result_callable(*itertools.chain(parameters, wild_parameters), **keyword_parameters)

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\database.py:598, in functions.list(cls, **type)
    596     '''List all of the functions in the database with a glob that matches `string`.'''
    597     return cls.list(like=string)
--> 598 @utils.multicase()
    599 @classmethod
    600 @utils.string.decorate_arguments('name', 'like', 'regex')
    601 def list(cls, **type):
    602     '''List all of the functions in the database that match the keyword specified by `type`.'''
    603     listable = []

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:1994, in transform.<locals>.wrapper(F, *rargs, **rkwds)
   1992         cls = E.__class__
   1993         raise cls("{!s}: Exception raised while transforming parameter `{:s}` with value {!r}".format('.'.join([f.__module__, f.__name__]), argname, kwds[argname]))
-> 1994 return F(*res, **kwds)

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\database.py:633, in functions.list(cls, **type)
    631     refs = max(len(xref.up(ea)), refs)
    632     lvars = max(Fcount_lvars(func) if idaapi.get_frame(ea) else 0, lvars)
--> 633     avars = max(Fcount_avars(func), avars)
    635     listable.append(ea)
    637 # Collect the number of digits for everything from the first pass

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:56, in <lambda>(*a)
     54 first, second, third, last = operator.itemgetter(0), operator.itemgetter(1), operator.itemgetter(2), operator.itemgetter(-1)
     55 # return a closure that executes a list of functions one after another from left-to-right.
---> 56 fcompose = lambda *Fa: functools.reduce(lambda F1, F2: lambda *a: F1(F2(*a)), builtins.reversed(Fa))
     57 # return a closure that executes function `F` whilst discarding any arguments passed to it.
     58 fdiscard = lambda F, *a, **k: lambda *ap, **kp: F(*a, **k)

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:56, in <lambda>(*a)
     54 first, second, third, last = operator.itemgetter(0), operator.itemgetter(1), operator.itemgetter(2), operator.itemgetter(-1)
     55 # return a closure that executes a list of functions one after another from left-to-right.
---> 56 fcompose = lambda *Fa: functools.reduce(lambda F1, F2: lambda *a: F1(F2(*a)), builtins.reversed(Fa))
     57 # return a closure that executes function `F` whilst discarding any arguments passed to it.
     58 fdiscard = lambda F, *a, **k: lambda *ap, **kp: F(*a, **k)

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:56, in <lambda>(*a)
     54 first, second, third, last = operator.itemgetter(0), operator.itemgetter(1), operator.itemgetter(2), operator.itemgetter(-1)
     55 # return a closure that executes a list of functions one after another from left-to-right.
---> 56 fcompose = lambda *Fa: functools.reduce(lambda F1, F2: lambda *a: F1(F2(*a)), builtins.reversed(Fa))
     57 # return a closure that executes function `F` whilst discarding any arguments passed to it.
     58 fdiscard = lambda F, *a, **k: lambda *ap, **kp: F(*a, **k)

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\function.py:2651, in frame.arguments.iterate(cls, func)
   2648 # If we got here, then we have type information that we can grab out
   2649 # of the given address. Once we have it, rip the details out o it.
   2650 tinfo = type(ea)
-> 2651 _, ftd = interface.tinfo.function_details(ea, tinfo)
   2653 # Now we just need to iterate through our parameters collecting the
   2654 # raw location information for all of them. We preserve the type
   2655 # information in case we're unable to find the argument in a member.
   2656 items = []

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_interface.py:2797, in tinfo.function_details(cls, func, ti)
   2793     tinfo = ti
   2795 # Anything else is a type error that we need to raise to the user.
   2796 else:
-> 2797     raise internal.exceptions.InvalidTypeOrValueError(u"{:s}.function_details({:#x}, {!r}) : The type that was received ({!r}) for the specified function ({:#x}) was not a function type.".format('.'.join([__name__, cls.__name__]), ea, "{!s}".format(ti), "{!s}".format(ti), ea))
   2799 # Now we can check to see if the type has details that we can grab the
   2800 # argument type out of. If there are no details, then we raise an
   2801 # exception informing the user.
   2802 if not tinfo.has_details():

InvalidTypeOrValueError: internal.interface.tinfo.function_details(0x401029, '') : The type that was received ('') for the specified function (0x401029) was not a function type.
print(database.function.type(0x401029))
<< note from Harding, there is nothing here. >>

database.function.type(0x401029)
Out[4]: <class 'ida_typeinf.tinfo_t'> which looks like:
<< note from Harding, this an empty tinfo_t >>

The crackme can be found here: https://hardingonline.se/vault.zip

from ida-minsc.

Harding-Stardust avatar Harding-Stardust commented on July 17, 2024

Using version 8.3.230608 Windows x64 (64-bit address size)

I just opened the file in IDA64 (even if it is a 32-bit since that is the new work flow in IDA)
and I am using ipyida.

I'm getting the same error without ipyida.

from ida-minsc.

Harding-Stardust avatar Harding-Stardust commented on July 17, 2024

I get the correct tinfo_t with the following code:

type_at_addr = ida_typeinf.print_type(0x401029, ida_typeinf.PRTYPE_1LINE | ida_typeinf.PRTYPE_SEMI)
print(type_at_addr)

INT_PTR __userpurge DialogFunc@<eax>(INT_PTR@<eax>, HWND hDlg, UINT, WPARAM, LPARAM);

from ida-minsc.

arizvisa avatar arizvisa commented on July 17, 2024

Hmm. Trying to build a fresh database from the binary results in the following type being applied by default. For some reason, that also returns a valid type using your unit-test. :-/

.text:00401029
.text:00401029                                   ; Attributes: bp-based frame
.text:00401029
.text:00401029                                   ; INT_PTR __stdcall DialogFunc(HWND, UINT, WPARAM, LPARAM)
.text:00401029                                   DialogFunc      proc near               ; DATA XREF: start+E↑o
.text:00401029
.text:00401029                                   hDlg            = dword ptr  8
.text:00401029                                   arg_4           = dword ptr  0Ch
.text:00401029                                   arg_8           = dword ptr  10h

Python>function.type(0x401029)
INT_PTR __stdcall(HWND, UINT, WPARAM, LPARAM)

Applying the __userpurge type directly and trying again still returns the same....

.text:00401029                                   ; Attributes: bp-based frame
.text:00401029
.text:00401029                                   ; INT_PTR __userpurge DialogFunc@<eax>(INT_PTR@<eax>, HWND hDlg, UINT, WPARAM, LPARAM)
.text:00401029                                   DialogFunc      proc near               ; DATA XREF: start+E↑o
.text:00401029
.text:00401029                                   hDlg            = dword ptr  8

Python>function.type(0x401029)
INT_PTR __userpurge@<eax>(INT_PTR@<eax>, HWND hDlg, UINT, WPARAM, LPARAM)

However, thanks to the backtrace that you gave me, that should be enough to identify the issue. Appreciate it.

Also, this isn't that important, but it's worth keeping in mind that database.function.type only works because the function module is being imported into the database with its full name. It's not always guaranteed to have that name, and the correct way to ensure it always exists is to use the function module or its alias, func. So if that module ends up getting a function namespace added to it, your code might break. Hence it's recommended to use function.type, function.t, or func.t in the future.

print(database.function.type(0x401029))
<< note from Harding, there is nothing here. >>

database.function.type(0x401029)
Out[4]: <class 'ida_typeinf.tinfo_t'> which looks like:
<< note from Harding, this an empty tinfo_t >>

from ida-minsc.

arizvisa avatar arizvisa commented on July 17, 2024

Okay, so the issue has got to be that IDAPython's idaapi.guess_tinfo(ti, ea) is failing resulting in the type not being populated (https://github.com/arizvisa/ida-minsc/blob/master/base/function.py#L3209). It actually debug logs and returns an empty type. The sanity checks inside internal.interface.tinfo.function_details that verify you don't give it bunk data are raising the exception.

Anyways, am currently working on a fix for it which ensures that function.type cannot possibly fail.

from ida-minsc.

arizvisa avatar arizvisa commented on July 17, 2024

Okay, I think this does a better job at guarding against your situation. To be fair, I'm actually not sure what causes your situation because the logic that is happening is to literally use idaapi.get_tinfo to fetch the type for an address, and if that fails for some reason to then ask the disassembler to guess the function type with idaapi.guess_tinfo. If for some reason the disassembly cannot guess the function type, then database.type demangles the function name and then asks the disassembler to attempt parsing it for the type.

This means that in your situation, both idaapi.get_tinfo and idaapi.guess_tinfo are failing. In terms of reproducing your issue, the only thing I can think of is that perhaps the AFL_TI flag is clear or NSUP_TINFO for that function is missing. I took a 5-minute look at the implementation of print_type, but it actually uses get_opinfo followed by a call to calc_c_cpp_name for rendering it to a string. So, I'm not sure why the database.type function is failing on your system since it's doing practically the exact same thing for that address. :-/

Anyways, try out PR #187 and let me know if that remedies it for you in the "master" branch.

from ida-minsc.

arizvisa avatar arizvisa commented on July 17, 2024

Wait, what!? There is absolutely no reason that should fail? It seems the code for creating a function type within your type library doesn't work?

+        missing, ftd = idaapi.tinfo_t(), idaapi.func_type_data_t()
+        ftd.rettype = idaapi.tinfo_t(idaapi.BT_VOID if fn and fn.flags & idaapi.FUNC_NORET else idaapi.BT_INT)
+        ftd.cc = idaapi.CM_CC_UNKNOWN | idaapi.BFA_NORET if fn and fn.flags & idaapi.FUNC_NORET else idaapi.CM_CC_UNKNOWN
+        missing = missing if missing.create_func(ftd) else None

Perhaps if you modify base/function.py at line number 3211 and change idaapi.CM_CC_UNKNOWN to idaapi.CM_CC_CDECL? I'm shooting in the dark here...

The logic that should be happening is literally the following:

ti = idaapi.tinfo_t()
if idaapi.get_tinfo(ti, ea):
    return ti

ti = idaapi.tinfo_t()
error = idaapi.guess_tinfo(ti, ea)
if error != idaapi.GUESS_FUNC_FAILED:
    return ti

# the following works without a database being loaded.
missing, ftd = idaapi.tinfo_t(), idaapi.func_type_data_t()
ftd.rettype = idaapi.tinfo_t(idaapi.BT_INT)
ftd.cc = idaapi.CM_CC_UNKNOWN 
if not missing.create_func(ftd):
    raise Exception
return missing

I do not have any ideas why the last thing should fail, because it doesn't depend on anything within a database. Actually, you can run that entire last section without a database being open and it should work.

from ida-minsc.

Harding-Stardust avatar Harding-Stardust commented on July 17, 2024

I was stepping your code and you are correct that it is idaapi.guess_type() that fails. I tried some other database and I got some very strange results that the guess_tinfo was wrong even if the function prototype was set. I don't know what to make out of it.

guess_tinfo

from ida-minsc.

Harding-Stardust avatar Harding-Stardust commented on July 17, 2024

From my VERY limited testing, it seems that the guess_tinfo() ignores what the type is set to and only use "what IDA thinks".
I used the following code and did it on some different functions that had no types, type from the decompiler, manually set type (in the disassembly window)

import ida_typeinf
def get_type(arg_ea: int) -> ida_typeinf.tinfo_t:
    type_as_str = ida_typeinf.print_type(arg_ea, ida_typeinf.PRTYPE_1LINE | ida_typeinf.PRTYPE_SEMI)
    res = ida_typeinf.tinfo_t()
    ida_typeinf.parse_decl(res, None, type_as_str, ida_typeinf.PT_SIL) # PT_SIL == SILENT, meaning no popup that there was any problems
    return res

def guess_type(arg_ea: int) -> ida_typeinf.tinfo_t:
    res = ida_typeinf.tinfo_t()
    idaapi.guess_tinfo(res, arg_ea)
    return res

from ida-minsc.

arizvisa avatar arizvisa commented on July 17, 2024

From my VERY limited testing, it seems that the guess_tinfo() ignores what the type is set to and only use "what IDA thinks". I used the following code and did it on some different functions that had no types, type from the decompiler, manually set type (in the disassembly window)

Yeah, it's okay if guess_tinfo returns the wrong type. This is due to it only being a fallback in case get_tinfo fails to get the type the user specified. The get_tinfo(ti, 0x401029) call shouldn't fail to begin with, but that's the reason we are here.

So this following code, (done by database.type.__new__), simply fetches the type from an address. This should be the type that you manually apply, or that the disassembler/decompiler has applied. If it's successful, it should return a function prototype, otherwise it falls through to the next conditional.

ti = idaapi.tinfo_t()
if idaapi.get_tinfo(ti, ea):
    # assert(ti.is_func()), 'should be a func prototype'
    return ti

This next code is essentially the guess (it is used by function.type.__new__). This is a failure case for when there wasn't a type attached to a function. Its goal is just to return anything that looks like a function type. So, to clarify, it's ok if the type returned from guess_tinfo is wrong because our previous idaapi.get_tinfo told us there wasn't a type at the address to begin with.

ti = idaapi.tinfo_t()
error = idaapi.guess_tinfo(ti, ea)
if error != idaapi.GUESS_FUNC_FAILED:
    # assert(ti.is_func()), 'should be a func prototype'
    return ti

Then there is the final snippet, this is the one that raised your latest exception after I created the PR. This is an emergency case for when both get_tinfo and guess_tinfo fails. Essentially, if we couldn't get the type for an address or guess the type for the address, then this creates a dummy type for when everything is missing.

# the following works without a database being loaded.
missing, ftd = idaapi.tinfo_t(), idaapi.func_type_data_t()
ftd.rettype = idaapi.tinfo_t(idaapi.BT_INT)
ftd.cc = idaapi.CM_CC_UNKNOWN 
if not missing.create_func(ftd):
    raise Exception
assert(missing.is_func()), 'should be a func prototype'
return missing

What this is doing is creating a func_type_data_t where the result is an integer and there are no parameters (the prototype should look like int missing()). This is intended to always work. I'm explicitly checking the return value as a sanity check, and yet on your system the exception being raised suggests that it's unable to create this emergency type using tinfo_t.create_func(ftd).

On my systems, that last snippet always succeeds...regardless of there being an open database. If you even google-search, tinfo_t.create_func, nobody is checking its result because they also assume that it always works. The condition on your system is essentially hitting every single failure path. So that means that idaapi.get_tinfo can't get the type, then idaapi.guess_tinfo is unable to guess the type, then tinfo_t.create_func is unable to create the function type.

Since the intent of function.type is literally to return a function type at all costs, and apparently tinfo_t.create_func cannot create a function type, I'm unsure how to proceed other than reverse-engineer IDA, or ask support@ about the conditions that can result in the symptoms that are occurring on your system.

I guess the final thing I can do is to hardcode the type by deserializing it with something like:

ALWAYS_BE_A_FUNCTION_TYPE_PLZ = (b'\x0c\x10\x07\x01', None, None)
ti = idaapi.tinfo_t()
if not ti.deserialize(idaapi.get_idati(), *ALWAYS_BE_A_FUNCTION_TYPE_PLZ):
    raise Exception

from ida-minsc.

arizvisa avatar arizvisa commented on July 17, 2024

Can you confirm that the following doesn't work. Maybe add it to the base/database.py module, in a function of your choosing. This way you can call it as ti = database.blah().

def blah():
    missing, ftd = idaapi.tinfo_t(), idaapi.func_type_data_t()
    ftd.rettype = idaapi.tinfo_t(idaapi.BT_INT)
    ftd.cc = idaapi.CM_CC_UNKNOWN 
    if not missing.create_func(ftd):
        raise Exception("{!s}".format(missing))
    assert(missing.is_func())
    return missing

Then do something similar with the following function. Insert it into base/database.py, and then try to call it as ti = database.blah2().

def blah2():
    ALWAYS_BE_A_FUNCTION_TYPE_PLZ = (b'\x0c\x10\x07\x01', None, None)
    ti = idaapi.tinfo_t()
    if not ti.deserialize(idaapi.get_idati(), *ALWAYS_BE_A_FUNCTION_TYPE_PLZ):
        raise Exception("{!s}".format(ti))
    assert(ti.is_func())
    return ti

If the first blah() fails, then that's the symptom you're encountering and we are actually expecting that. If both of them inside base/database.py fail, then go ahead and define both of those functions in your IPyIDA instance or whatever you're using (you can also use shift+f2), and then try calling both of them from there. If they still fail, then something is going on with your instance of IDAPython. If they succeed, then something else entirely is going on...

from ida-minsc.

Harding-Stardust avatar Harding-Stardust commented on July 17, 2024

I have to go to bed now but I tried database.type(0x00401029) and that gave the correct tinfo_t:

database.type(0x00401029)
Out[29]: 
<class 'ida_typeinf.tinfo_t'> which looks like:
INT_PTR __stdcall(HWND, UINT, WPARAM, LPARAM)

while database.function.type(0x00401029) failed.

database.function.type(0x00401029)
---------------------------------------------------------------------------
DisassemblerError                         Traceback (most recent call last)
Cell In[35], line 1
----> 1 database.function.type(0x00401029)

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\_utils.py:694, in function.__new__(*arguments, **keywords)
    691 # Now we have a matching callable for the user's parameters, and we just need
    692 # to unpack our individual parameters and dispatch to the callable with them.
    693 parameters, wild_parameters, keyword_parameters = result_parameters
--> 694 return result_callable(*itertools.chain(parameters, wild_parameters), **keyword_parameters)

File ~\AppData\Roaming\Hex-Rays\IDA Pro\base\function.py:3232, in type.__new__(cls, func)
   3229 # If we weren't able to create the missing type, then we need to abort. This shouldn't
   3230 # be possible at all whatsoever, but perhaps some database state is preventing us.
   3231 if not missing:
-> 3232     raise E.DisassemblerError(u"{:s}({:#x}) : Unable to create a dummy function type to return for the for the specified function ({:#x}).".format('.'.join([__name__, cls.__name__]), ea, ea))
   3234 logging.warning(u"{:s}({:#x}) : Unable to guess the missing type for the function at {:#x} due to error ({:d}) which will result in an empty function type (\"{:s}\") being returned.".format('.'.join([__name__, cls.__name__]), ea, ea, idaapi.GUESS_FUNC_FAILED, utils.string.escape("{!s}".format(missing), '"')))
   3235 return missing

DisassemblerError: function.type(0x401029) : Unable to create a dummy function type to return for the for the specified function (0x401029).

This worked:

In [25]: ALWAYS_BE_A_FUNCTION_TYPE_PLZ = (b'\x0c\x10\x07\x01', None, None)
ti = idaapi.tinfo_t()
if not ti.deserialize(idaapi.get_idati(), *ALWAYS_BE_A_FUNCTION_TYPE_PLZ):
    raise Exception

Out[26]: 
<class 'ida_typeinf.tinfo_t'> which looks like:
int()

Your blah() function worked also:

def blah():
    missing, ftd = idaapi.tinfo_t(), idaapi.func_type_data_t()
    ftd.rettype = idaapi.tinfo_t(idaapi.BT_INT)
    ftd.cc = idaapi.CM_CC_UNKNOWN 
    if not missing.create_func(ftd):
        raise Exception("{!s}".format(missing))
    assert(missing.is_func())
    return missing
    

blah()
Out[28]: 
<class 'ida_typeinf.tinfo_t'> which looks like:
int()

from ida-minsc.

Harding-Stardust avatar Harding-Stardust commented on July 17, 2024

Hold on, something else strange is going on. I have to do a fresh install in a virtual machine and see if there might be some other scripts that are interfering. Don't spend more time on this right now. <3

I REALLY gotta sleep now tho so that's gotta be done tomorrow.

from ida-minsc.

arizvisa avatar arizvisa commented on July 17, 2024

Closing this issue. If you feel this is in error, feel free to let me know and I'll re-open.

from ida-minsc.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.