我试图找到一种方法来确定IDA中哪些出口是数据输出,哪些是实际功能导出。
虽然CreateTextServices
是一个真正的导出函数:
IID_IRichEditOle
是一个数据导出,而IDA未能意识到,将数据作为代码进行处理:
有人知道区分这两者的可靠方法吗?非常感谢帮助。
提前致谢。
答案 0 :(得分:2)
每个导出仅指定可执行文件中的偏移量 - 从逻辑上讲,它可以被任何其他引用它的代码视为代码或数据。
正如您所提到的,在几乎所有情况下,您都可以提出启发式检测导出的类型,但是很容易想出对任何给定启发式都不起作用的反例。例如,你提出的规则是:
如果函数中有
ret
条指令,导出的条目将被视为有效的导出函数,和有超过<min>
个有效指令,和 IDA识别函数的调用约定。
误报:您可能拥有一个使用tail call optimization并以jmp
指令而不是ret
指令结束的功能。任何短期功能也会失败。并且有几种方法可以将IDA混淆为不将代码视为函数。
误报:内存中可能有一个字符串紧跟着C3
或C2
db 'BACKGAMMON0',0,0C3h
,这可能在逻辑上反汇编为有效带有ret
且没有参数的11指令函数。
当您认为导出可以在逻辑上被视为代码和数据时,线条会进一步模糊:想象一下,导出中的字节序列被复制到动态分配的内存中 - 甚至可能在另一个进程中 - 稍后将其作为代码执行。
如果IDA认为它是代码,也许一个合理的建议就是信任IDA并将导出视为代码。 IDA的很大一部分功能是自动猜测数据的逻辑类型,而且它通常都很擅长。如你所示,有时它是错的。但无论如何,你无法获得100%的准确率。你能做的最好的就是假阴性和假阳性之间的平衡。
是否将执行导出作为代码是不可判定的。导出是否将被读取为数据也是不可判定的。由于我们不能保证两者都是真的,因此区分看似模棱两可的案例是不可能的。
证明:假设我们有一个oracle A(P,I,E)
如果程序P
(包括其所有依赖项)执行(或读取)export E
(来自任何加载的DLL),则返回1在P
执行过程中使用&#34;输入&#34; (外部状态)I
。否则,它返回0.
让我们构造一个最小程序Z(P,I,E)
,它执行(或读取)export E
(其加载到地址空间的DLL)当且仅当A(P,I,E)
返回0时
现在考虑Z(Z,I,E)
:
如果Z(Z,I,E)
执行(或从中读取)导出E
,则A(Z,I,E)
将返回1.但Z(Z,I,E)
被定义为不访问权限除非E
返回0,否则导出A(Z,I,E)
。这是一个矛盾。
如果Z(Z,I,E)
未执行(或读取)导出E
,则A(Z,I,E)
将返回0.但Z(Z,I,E)
已定义为将< 在E
返回0时访问导出A(Z,I,E)
。这是一个矛盾。
因此,我们对oracle A(P,I,E)
存在的初步假设被证明是错误的。
根据您尝试解决的确切问题,您可以在运行时确定哪些导出是有效函数。
例如,您可以编写一个应用程序,debugs要分析的程序,并将guard pages放在包含您要挂钩的导出的每个页面上。这意味着,只要页面被访问(执行/读/写),就会引发异常,并且调试程序将获得控制权。
调试器可以检查程序上下文,以查看访问的类型以及是否与导出有关。如果访问是尝试执行导出,则可以在将控制权返回给程序之前执行一些挂钩功能。否则,它可以将控制权返回给程序。
在任何一种情况下,PAGE_GUARD
修饰符都会在每次例外后解除,因此您每次都需要将其恢复。
不出所料,这会使您的程序执行非常慢,因为对包含导出的任何页面的任何R / W / X访问都会导致昂贵的context switch - 这会可能包括执行导出函数中的大多数指令,以及其他几个与它们无关的指令。
您可以采用与其他工具工具类似的方法,例如Pin。
请注意,您可能无法通过检测获得有关每次导出的使用情况的信息。这是因为您可能需要确定使程序访问每个导出所需的输入/外部状态,以了解它是用作代码还是数据(如果有的话)。
另请注意,执行和读取(甚至写入)访问都可能发生在同一个导出中。