但是要调用它们,我需要有关参数和返回类型的信息。
是否有一种简便的方法可以在反汇编中对其进行识别?
是否存在可以提取这些原型的工具或对我有帮助的工具?
#1 楼
注意:我假设Windows上的32位x86,不幸的是您的问题并不确定。但是由于它是Windows,并且您没有明确提到x64,所以这是我能做的最合理的假设。首先,尝试使用搜索引擎搜索函数名称。不要只满足于一个搜索引擎。如果失败,请检查DLL附带的包装。是否包括进口LIB?如果是这样,请使用它们提供线索(可能有用,也可能无效)。
否则...
大多数反汇编程序(阅读标签维基工具)将很容易向您显示已导出职能。因此,找到它们根本不是问题。通常还会显示它们的导出名称。
在屏幕快照显示的输出中,似乎名称没有被修饰/修饰。这表明-但不是确定性的证据-这些函数使用
stdcall
调用约定(最好还是由主持人之一的Ange来阅读此约定)。现在我不知道您知道多少,但是由于您尝试RCE,所以您可能精通调用约定。如果不是这样,我们可以这样总结一下:调用约定控制参数(顺序,对齐方式)以及将参数(寄存器,堆栈)传递给函数的方式。我们待会儿再讲。如果您使用的是x64 Windows,并且DLL也为64bit,则可以依赖Microsoft x64调用约定(请阅读本文)。现在有两条主要途径可以采用
途径1-使用DLL分析程序
如果碰巧有一个使用该DLL的程序,则可以使用调试器或反汇编器来查找调用约定和传递的参数数量。只需查找引用导出的DLL函数的
call
指令,然后在前面找到mov
或push
指令。如果碰巧遇到cdecl
函数,则将在esp
之后再次调整堆栈指针(call
)。可能是这种情况(请参见下面的示例),但是与各种编译器特定的fastcall
变体一样不太可能,因为stdcall
提供了尽可能广泛的兼容性。还将详细解释此处介绍的一些概念。途径2-分析DLL本身
如果您碰巧拥有IDA并分析了32位DLL,则可能IDA已经使用其启发式方法确定了参数数量和调用约定。让我演示一下(使用
sqlite3.dll
)。在“导出”选项卡中,找到您感兴趣的功能,然后双击它。这将带您到函数开始的地址(此处为sqlite3_open
)。在SQLite3文档中验证此发现)。但是,这里还有另一件事。在call sqlite3_open_v2_0
之后,我们可以看到将堆栈指针调整了10h(= 16),从而清除了四个参数。查看push
之前的call
指令,我们可以看到确实通过堆栈传递了四个32位(即DWORD)参数。由于对函数
sqlite3_open
本身的一部分没有进一步的清除,因此现在很明显,它很可能也遵循C调用约定(cdecl
)。同样,我们可以通过查看文档来验证发现(您将无法获得的好处)。确实,由于没有给出任何明确的调用约定,您最终将默认使用cdecl
。单个retn
(某些反汇编程序将显示ret
),即return
,也不会清理堆栈,因为否则它看起来像retn 8
或类似的东西。这是一个相当小的函数,但即使借助周围的信息,我们可以得出很多有关的信息。
现在,请问
stdcall
的问题,您很可能会遇到如前所述的情况。为什么不去购买Windows 7中的著名产品,例如kernel32.dll
?同样,我将采用琐碎的功能,因为更好地展示了这些要点。请注意,我告诉IDA不要使用Microsoft的调试符号,而是跳过使用FLIRT签名。这意味着默认情况下会显示一些好的东西,这些东西被抑制以显示如何确定发生了什么。外观:绿线对于我们的案例没有意思,但是您会遇到很多。通常在几个编译器中都可以找到它,并且
ebp
通常被称为“帧指针”(位于堆栈帧中的帧),基本上是偏移量,可基于此偏移量访问堆栈变量。您可以在push [ebp+arg_0]
行中看到典型的用法。 IDA弄清楚了这一点并向我们显示了Attributes: bp-based frame
。我们在
call sub_77E29B80
之后看不到堆栈指针的调整,因此(内部)函数看起来也遵循stdcall
调用约定。但是,ret 4
暗示被调用方(在这种情况下为功能AddAtomA
)用于清理堆栈,这意味着我们可以排除cdecl
。它是四个字节,因为这是32位系统上的“自然”大小。您还可以从我的嵌入式注释中看到,这些参数是反向传递到堆栈上的。但是您在参与RCE之前无论如何都应该了解这些事情,否则请阅读以上链接的文章以及此处的一些书中的内容。在这种特殊情况下,我们可以敢于作另一个假设,但是它会咬我们。假设这是Microsoft的
fastcall
约定(请记住它们因编译器而异),然后将使用寄存器ecx
和edx
,然后在堆栈上传递参数。这意味着在我们的情况下,我们可能要假设情况并非如此,因为在调用sub_77E29B80
之前未保存这些寄存器。对于像这样的机器生成的代码,这是一个很好的论据。但是,如果是手动优化的代码,则程序员可以依靠有关调用约定的知识,并跳过在call
之前/之后保存/恢复寄存器。尽管如此,在这种情况下,手工优化的代码不太可能(或不太可能)使用帧指针。严格不需要这三个指令来完成这项工作。这样的争论-即使没有先验知识-现在我们也可以着手使用原型编写一个小程序:int __stdcall AddAtomA(void* unknown)
并使用调试器查看传递的内容。通常这是一个乏味的过程,但是很多过程(尤其是查找参数的数量)可能都可以编写脚本。另外,一旦找出一个函数,整个DLL的调用约定就可能是相同的(当然存在例外)。只要确保您分析的函数至少带有一个参数,否则就无法从环境数据中区分出
stdcall
和cdecl
。路线X-一个丑陋的
您也可以简单地使用
dumpbin
或类似的工具来编写测试程序的脚本。然后,该测试程序将调用该函数,在前后检查堆栈指针,从而可以区分stdcall
和cdecl
。您还可以玩一些技巧,例如在堆栈上传递20个参数(如果您要假设stdcall
用于实验),并查看清理了多少被调用者。有很多可能性可以尝试而不是分析。但是,使用前两种方法,您将获得更好(更可靠)的结果。堆栈溢出。它显示了如何仅从DLL构建导入LIB。结论
方法与其他反汇编程序的差别不会太大,我只需要在您可以复制它们的方式,这就是我选择IDA的原因。 IDA的免费软件版本可能已经足够了(32bit,PE,x86)-但是请记住,它不允许用于商业用途。
屏幕快照摘自IDA 6.4。
评论
可以肯定的是,最好的办法是转储模块导出的所有符号名称,然后使用蜘蛛网Google搜寻,生成头文件并查看不会引起段错误的第一个签名。否则,您将不得不假设在汇编程序级检查符号是否看起来像cdecl / stdcall函数,然后对其进行分析。这通常会起作用,但是并非所有函数都返回给调用者(它们可以跳到继续执行的助手,并且可能返回给调用者),并且并非所有函数都说出它们期望作为输入(它们可以采用指向难以确定的结构的指针) 。