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