PE - 区分数据与功能导出

时间:2016-10-31 08:26:32

标签: windows debugging portable-executable ida

我试图找到一种方法来确定IDA中哪些出口是数据输出,哪些是实际功能导出。

例如,让我们看看微软的msftedit.dll导出条目: enter image description here

虽然CreateTextServices是一个真正的导出函数: enter image description here

IID_IRichEditOle是一个数据导出,而IDA未能意识到,将数据作为代码进行处理: enter image description here

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

提前致谢。

1 个答案:

答案 0 :(得分:2)

每次出口都没有完全可靠的方法。

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

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

  

如果函数中有ret条指令,导出的条目将被视为有效的导出函数,有超过<min>个有效指令, IDA识别函数的调用约定。

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

误报:内存中可能有一个字符串紧跟着C3C2 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

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

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