我正在重新认证GREM认证(GIAC反向工程恶意软件)。尽管我在几种体系结构(Motorolla,x86,propeller和ARM)中对汇编语言非常满意,但是我的技能却是Windows及其API。在GREM和静态代码分析的背景下,我还有路要走。一个看不见森林的树木问题。我仍然很可能会像上次一样通过该认证,因为我了解其概念中的大多数概念。我的问题是一些总的来说(一直都是)。我开玩笑说一切都太高了,说实话,在大多数情况下,这确实是个玩笑或夸张的极端。但是对我来说,有时候我很难理解一个抽象,它抽象了事物的实际运行方式。对于大多数人来说,只要技术有效,技术如何运作都无关紧要。但是,作为一名黑客,我遇到了技术和信任问题。事情并不总是能正常工作。而且抽象可能不会给您任何提示,说明事情为什么失败,答案会在较低层显示。
等等等等,我离题了。我想着手更详细地学习许多这些Windows API。逆向工程通常会教如何读取代码,但是当我们实际编写代码时,我(可能还有您)的理解就会放大。因此,在这种情况下,我想设置并编写一些非常简单的汇编程序,这些程序将正确的参数放在堆栈上并调用Windows API,这就是调试某些恶意软件时我如何看待这种情况,以及它应该如何工作。作为参考,我正在使用FireEye的FLARE VM设置。它带有魅力,因此我将使用汇编器(我对汇编器确实没有宗教偏爱)。
对于API,Windows方式与Linux方式有些不同。对于Linux,通常,您将所有参数都放在寄存器中,然后执行Int 80(中断Linux)。在Windows中,使用&sdtcall'函数,将所有参数推入堆栈,然后按名称调用Windows API函数(这些函数的相应地址最终被链接到其中)。我并不是真的反对这种方法,它默认情况下允许大量参数,因为它是堆栈,而不是有限的寄存器。
由于我不了解汇编的流行方式,因此我在Internet上查找了一些示例。我想创建一个简单的对话框。我希望看到一个简单的汇编程序,其中包含带有字符串的.data节,然后是带有一些指令的.text(.code)节,这些指令将参数推入堆栈,然后调用API函数。对于我所获得的几乎所有Google搜索结果,我得到的都是方法的抽象版本,并且具有讽刺意味的是:没有组装说明!
在此之前,我会说我最终想出了一种使用源文件中的实际汇编语言进行此操作的方法。这和我预期的一样直接。作为参考,这是源程序的屏幕截图:
请注意,该程序集看起来与源程序非常相似。没错这正是我要去的地方。记住我的目标是尝试了解这些API函数的实际作用,这是实现此目的的最容易理解的方法。您会注意到,所有参数都在堆栈上,并准备在我要调用它们时使用。而且非常清楚它们是如何进入堆栈的(前面的4个压入指令)。
好的。现在,我们来讨论不需要组装的情况。建议写这种方式。因为源代码更易于阅读。因为它是更干净的代码。因为汇编语言很难编写您最好编写不使用汇编指令的汇编程序(然后只使用python放弃并他妈的)。无论如何,这是“清理”的屏幕截图方法:
阅读起来更清晰。如果我的版本中没有评论,则' invoke'版本将在其意图上更加明显。但是现在,这是该调试器中的肮脏性和不可理解性的屏幕截图:
在我开始大声疾呼和批评之前,我得先提个价,并声明我在Internet上找到的示例没有使用.data节,而是在invoke节(更干净的源代码)中插入了字符串。这是造成混乱的真正原因。如果我在此调用命令中使用了.data节:'调用MessageBox,HWND_DESKTOP,message,title1,MB_OKCANCEL'那就太糟糕了。我离题了。因此请注意,即使源代码是干净的,实际组装的是什么(实际上是编译的)仅此而已。您将看到我们即将进行调用时,所有正确的参数都在堆栈中。我看到了我们两个参数(推1和推0)所需的两个原始推。我们还需要另外两个参数。我们需要指向我们的字符串的指针以获取窗口标题和窗口中的消息。这些到底是如何进入堆栈的,这些令人困惑的指令在我们的程序中到底是做什么的。我们真的需要做ARPL,INSB,OUTSD,DAA和IMUL指令吗?好吧,那不是正在发生的事情。我们实际上看到的是字符串的分解表示。请参见我们对syscalls.40201B的第一个调用,它已经跳过了我们的第一个字符串。通常,通过将下一条指令的地址压入堆栈,调用通常知道如何返回原点。不过,在这种情况下,我们的程序根本不打算返回到该地址,它是使用该推入地址作为副作用的,因为该地址确实是我们字符串的第一个字节,它充当了指向该地址的指针,现在可以方便地将其作为参数放在堆栈中。这样,该呼叫使我们跳至执行相同操作的另一个呼叫。它跳过了紧随其后的下一个字符串,从而间接地在堆栈上获得了指向它的指针。因此,第二个调用指令将我们带到了“推0#39;”。在我们对MessageBoxA的API调用之前的指令。这些滥用的CALL指令是我们如何将字符串参数传递到堆栈上的。
最终结果是相同的。作为必须读取或编写汇编源代码的人,使用invoke可能是一种更好的编写和协作方式。但是,关于它的并不是实际的汇编语言,而是将其抽象化了。并非这种行为不常见或无法防御。编译器一直都在做这种事情,即使它们没有做那么多的优化(当它们被优化时,哇)。撇开玩笑,如果您写的东西比较严重,那么使用invoke可能是解决问题的方法,尽管如此,为什么不只使用C呢?写作"汇编"没有实际汇编的快捷方式和宏中的声音听起来很像是高级语言(例如C)。这就是为什么我总是觉得HLA(高级别程序集)如此令人反感的原因。尽管很清楚,但我尊重HLA的作者,他还做了其他非常出色的工作。
关于哪种方法要比哪种方法(在很多方面)要好得多的很多争论都归结为您目前所做的事情。在上一段的用例中,请调用。但是,回到我的用例,我试图通过在汇编中使用不同的参数并对其进行调用,然后看着它们在调试器中执行其动作,来使自己熟悉一些简单的Windows API调用(因为不是所有的API&# 39; s会做一些视觉上的事情;我可能不得不看一下堆栈,寄存器和内存被操纵了)。为该策略使用invoke会使此过程更加混乱。
综上所述,您可能会明白为什么要对Windows二进制文件进行完全逆向工程时,我还有点路要走。不要与有针对性的倒车相混淆。我在研究特定的API并从它们留下的工件以及所有其他作弊者中拉出IOC方面已经足够了。动态形式的分析。但是,如果我想看到更大更完整的图片,我将要开始编写我正在阅读的程序集,并将更大的难题拼凑在一起。至少那是计划。