PE – 区分数据和function导出

我试图find一种方法来找出在IDA出口是数据出口,哪些是真正的function出口。

例如,让我们来看看Microsoft的msftedit.dll的导出条目: 在这里输入图像说明

CreateTextServices是一个真正的导出函数: 在这里输入图像说明

IID_IRichEditOle是数据导出,IDA没有意识到将数据作为代码进行IID_IRichEditOle在这里输入图像说明

有人知道一个可靠的方法来区分两者吗? 帮助将不胜感激。

提前致谢。

对于每个出口都没有完全可靠的方法来做到这一点。

每个导出只在可执行文件中指定一个偏移量 – 从逻辑上说,它可以被任何其他引用它的代码视为代码或数据。

正如你所提到的,你可以拿出启发式的方法来检测几乎所有情况下的出口类型,但是很容易想出反例,不适合任何给定的启发式方法。 举个例子,你提出的规则是:

如果函数中存在ret指令,则导出的条目将被视为有效的导出函数, 并且存在多于<min>有效指令, 并且 IDA识别该函数的调用约定。

假否定:您可能有一个使用尾部调用优化的函数,以jmp指令而不是ret指令结束。 任何短的功能也将失败。 IDA有几种方式可以混淆为不把代码当作函数来处理。

错误的肯定:内存中可能有一个字符串紧跟着一个C3C2比如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)的类似方法。

请注意,您可能无法通过检测获取有关每个导出的使用情况的信息。 这是因为您可能需要确定导致程序访问每个导出所需的输入/外部状态,以了解它是作为代码还是作为数据(如果有的话)使用。

另请注意,执行和读取(甚至写入)访问可能会在相同的导出上发生。