PE - 区分数据和函数导出

PE - Distinguish data from function export

我正在尝试找到一种方法来在 IDA 中找出哪些导出是数据导出,哪些是实际函数导出。

例如,让我们看一下微软msftedit.dll的导出条目:

CreateTextServices 是一个真正的导出函数:

IID_IRichEditOle 是数据导出,IDA 没有意识到这一点,将数据作为代码插入:

有人知道区分两者的可靠方法吗?非常感谢您的帮助。

提前致谢。

对于每次导出都没有完全可靠的方法。

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

正如您提到的,您可以想出启发式方法来检测几乎所有情况下的导出类型,但很容易想出对任何给定启发式方法都不起作用的反例。以您提出的规则为例:

The exported entry will be considered a valid exported function if there is a ret instruction in the function, and there are more than <min> valid instructions, and IDA recognizes the function's calling convention.

漏报: 您可能有一个使用 tail call optimization 并以 jmp 指令而不是 ret 指令结尾的函数。任何短函数也会失败。有几种方法可以让 IDA 混淆为不将代码视为函数。

误报: 内存中可能有一个字符串紧跟 C3C2,例如 db 'BACKGAMMON0',0,0C3h——这可能逻辑反汇编为有效的 11 指令函数,带有 ret 且没有参数。

当您认为导出可以在逻辑上被视为代码 数据时,线条更加模糊:想象一下导出的字节序列被复制到动态分配的内存——甚至可能在另一个进程中——稍后作为代码执行。

也许合理的建议是只信任 IDA,如果 IDA 认为导出是代码,则将导出视为代码。 IDA 的很大一部分功能是自动猜测数据的逻辑类型,而且它通常非常擅长。正如您所展示的,有时它是错误的。但无论如何你都无法获得 100% 的准确率。你能做的最好的事情就是在假阴性和假阳性之间取得平衡。


这个问题不可判定性的证明:

导出是否作为代码执行是不确定的。导出是否将被读取为数据也是不确定的。由于我们不能保证两者都是正确的,因此无法区分看似模棱两可的情况。

证明:假设我们有一个 oracle A(P,I,E) 如果程序 P(包括它的所有依赖项)执行(或读取)导出 returns 1 E(来自 P 执行过程中加载的任何 DLL)和 "input"(外部状态)I。否则,它 returns 0.

让我们构建一个最小程序Z(P,I,E),当且仅当A(P,I,E) returns 0.

现在考虑Z(Z,I,E)的结果:

如果 Z(Z,I,E) 执行(或读取)导出 E,那么 A(Z,I,E) 将 return 1。但是 Z(Z,I,E) 被定义为 not access export E unless A(Z,I,E) returns 0。这是矛盾的。

如果 Z(Z,I,E) 不执行(或读取)导出 E,那么 A(Z,I,E) 将 return 0。但是 Z(Z,I,E) 被定义为当 A(Z,I,E) return 为 0 时,它 访问导出 E。这是矛盾的。

因此,我们最初假设 oracle A(P,I,E) 存在的假设被证明是错误的。


但是您可以通过检测做得更好...

根据您要解决的具体问题,您或许能够确定哪些导出在运行时是有效函数。

例如,您可以编写一个应用程序,该应用程序 debugs the program you which to analyze and places guard pages 在包含您希望挂钩的导出的每个页面上。这意味着,无论何时访问(executed/read/written 到)页面,都会引发异常,并且调试器程序会获得控制权。

调试器可以检查程序上下文以查看进行了何种类型的访问以及它是否与导出有任何关系。如果访问是尝试执行导出,它可以在 return 对程序进行控制之前执行一些挂钩功能。否则,它可能只是 return 控制程序。

在任何一种情况下,PAGE_GUARD 修饰符都会在每次异常后解除,因此您每次都需要将其放回原处。

不出所料,这会使您的程序执行 非常慢 ,因为任何 R/W/X 访问任何包含导出的页面都会导致昂贵的 context switch -- 这可能包括作为导出函数的一部分的大多数指令的执行,以及与它们无关的其他一些指令。

您可以对其他检测工具采取类似的方法,例如 Pin

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

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