DEFCON Conference 18.使用数学的Trollim逆向工程

我将要讨论数学巨魔。 这些不是一些时髦的黑客把戏,而是一种艺术表达,一种有趣的智能技术,使人们认为您是白痴。 现在,我将检查我的报告是否准备好在屏幕上显示。 一切似乎都很好,所以我可以自我介绍。



我的名字叫弗兰克·杜(Frank Tu),在Twitter上的拼写是frank ^ 2和@franksquared,因为Twitter也有某种垃圾邮件发送者,名为“ frank 2”。 我试图对他们应用社会工程学,以便他们删除他的帐户,因为从技术上讲,这是垃圾邮件,并且我有权利摆脱它作为我的克隆。 但是显然,如果您诚实地对待它们,它们将不会兑现,因为尽管我请求删除垃圾邮件发送者帐户,但他们对此却无能为力,所以我将这该死的Twitter发送到地狱。

许多人凭我的帽子认出我。 我在DefCon DC949和DC310区域小组工作。 我也与Rapid7合作,但是如果不使用粗俗的语言我就不能在这里谈论它,我的经理也不想让我发誓。 因此,尽管这是一个相当复杂的主题,但我已经为DefCon准备了此演示文稿,并且要在15分钟的截止日期之前完成。 这本质上是一个标准演示,着重于逆向工程和相关有趣的事情。

在Twitter上讨论此主题时,形成了两个阵营。 一个人说:“我不知道这该死的坦率^ 2在说什么,但是太棒了!” 来自Reddit的第二个人看到了我的幻灯片,并因为链接到与该主题无关的事物而感到沮丧,我为这样一个严肃的主题没有被完全覆盖而感到生气,所以我希望我的演示文稿“内容更多,垃圾更少”。



因此,我想重点谈谈这句话。 Reddit的家伙没什么私人的-我说这不仅是为了防止他出现在这个房间里,还因为这是公正的批评。 因为没有包含足够有用内容的对话是空话。

我的谈话主题是黑客的例行公事,但在我看来,事实上,演讲者通常不会以娱乐性的方式展示其信息,即使可能的话,也倾向于干脆,含糊的结论。 “这里是IP,这里是ESP,这是您执行漏洞利用的方式,这是我的“零日”,现在鼓掌!” -每个人都鼓掌。

感谢您的掌声,谢谢! 在我看来,我的资料中有很多有趣的观点,因此应该以一种有趣的方式加以说明,我将尽力做到这一点。

您将看到对计算机科学的异常肤浅态度和完全幼稚的幽默,所以希望您对我将在这里展示的内容表示赞赏。 如果您来这里寻求认真的对话,对不起。

在幻灯片上,您可以看到我对上一份报告的科学分析,比较了严格科学方法的份额和提供计算机安全性的“药物”的份额。



您会发现还有更多的“药品”,但您不必担心,现在科学的份额已略有增加。



因此,前段时间,我的朋友Merlin坐在最前列,他基于IRC Python脚本编写了一个惊人的机器人,该脚本只占用一行。

这是学习函数式编程的一个非常棒的练习,很有趣。 您可以简单地将一个功能添加到另一个功能中,并获得各种不同功能的组合,并且所有这些功能都以彩虹波的形式绘制在屏幕上,通常,这是您可以做的最愚蠢的事情之一。



我以为如果将此原理应用于二进制文件? 我不知道这个想法是从哪里来的,但是结果真棒! 但是,我想澄清一些基本概念。

您的数学老师介绍的这些功能可能比实际复杂得多。



因此,公式f(x)的含义很简单,它的作用类似于普通函数。 您有X,有输入,然后得到X 7倍,这等于您的值。 在Python中,您可以创建一个函数(lambda x:x * 7)。 如果您想使用Java,很抱歉,希望您永远不要这样做-那么您可以执行以下操作:

public static int multiplyBySevenAndReturn(Integer x) { return x * 7; } 

您知道,数学函数甚至可能更加复杂,但这就是我们目前所需要了解的。

如果看代码汇编,您会发现JMP和CALL指令没有绑定到特定的值;它们使用偏移量。 如果使用调试器,则可以看到JMP00401000更像是跳转到字节转发指令,而不是跳转到5或10字节的特定指令。 除了将一堆东西压入堆栈外,CALL函数也是如此。 当您将地址“粘贴”到寄存器时,即访问特定地址时,情况例外。 一切都以完全不同的方式发生在这里。 将地址连接到寄存器并执行CALL EAX之类的操作后,该函数将访问EAX中的特定值。 CALL [EAX]或JMP [EAX]也是一样-它只是取消引用EAX并转到该地址。 使用调试器时,您可能无法确定CALL正在访问哪个特定地址。 这可能是一个问题,因此您应该意识到这一点。
让我们看一下JMP SHORT的短跳转功能。 这是x86体系结构中的一条特殊指令,它允许您使用1字节的偏移量而不是4字节的偏移量,从而减少了已用的内存空间。 以后对于单独的指令将要进行的所有操作,这将很重要。 重要的是要记住,JMP SHORT的范围是256个字节。 但是,没有所谓的CALL SHORT。



现在考虑计算机科学的巫术。 在创建这些幻灯片的过程中,我意识到实际上您可以将程序集定义为零空间,也就是说,从技术上讲,每条指令之间都存在零空间。 如果查看单个指令,您会发现每个指令都在另一条指令之后执行。 从技术上讲,这可以解释为无条件跳转到下一条指令。 这为我们在每个汇编指令之间提供了一个空间,而每个指令都与无条件跳转相对应。

顺便说一下,如果您看一下这个汇编示例,这些都是非常简单的事情,我建议您使用ASCII进行解码,因此,这只是一组常规指令。



位于每条指令之间的JMP 0是您通常看不到的无条件跳转。 他们在每条指令之后彼此跟随。 因此,当且仅当每个单元指令伴随无条件跳转到下一条指令时,才可以将每个单独的汇编指令放置在任意存储位置中。 因为如果您转移程序集并且需要使用与以前相同的代码,则必须将无条件跳转附加到每条指令上。
让我们进一步看。 从技术上讲,一维数组可以解释为二维数组,它只需要一些数学,行或类似的东西,我不能肯定地说,但这并不难。 这使我们有机会以晶格(x,y)的形式解释内存中的位置。 结合将指令之间的空白解释为可以相互关联的无条件跳转,我们可以从字面上绘制指令。 太棒了!

为了在实践中实现此目的,您需要执行以下步骤:

  • 分解每条指令以找出代码是什么;
  • 在内存中分配的空间远大于指令集的大小。 我通常保留的内存是代码大小的10倍;
  • 对于每条指令,确定f(x);
  • 将每条指令设置到内存中的相应(x,y)位置;
  • 将无条件跳转附加到指令上;
  • 将内存标记为可执行文件并运行代码。

不幸的是,这里出现了许多问题。 就像重力一样,它仅在理论上起作用,但是在实践中,我们看到了完全不同的事情。 因为实际上x86将您的JMP指令,CALL指令发送到地狱,从而使您的自引用代码,使用迭代的自修改代码失真。



让我们从JMP指令开始。 由于JMP指令是有偏见的,因此当放置在任意位置时,它们不再指向您认为应该指向的位置。 简短的JMP处于相似的位置。 由函数(x,y)偶然放置的它们不会指示您所依赖的内容。 但是与长JMP不同,短JMP更易于修复,尤其是在处理一维数组时。 简短的JMP易于转换为常规JMP,但是随后您必须弄清楚新的偏移量已经变成了什么。

使用基于寄存器的JMP仍然令人头疼,并且由于它们需要紧密的偏移量并且可以在运行时进行计算,因此没有简单的方法来找出它们的去向。 要自动检测每个寄存器,您需要使用汇编理论中的大量知识。 在运行时,可能有函数指针,类指针等。 的确,如果您不想为了完成所有这些而做额外的工作,那么您将无法做到。 f(x)函数在实际代码中工作的效果不如在纸上优雅。 如果要正确执行操作,则需要做很多工作。

要定义类指针和类似的东西,您需要结合使用C和C ++。 在保存之前,请在拆卸过程中将SHORT JMP转换为常规JMP,因为您必须处理偏差,这非常简单。

试图计算实际位移非常麻烦。 您发现的所有指令都有偏移量,这些偏移量将在代码移动时移动,因此应重新计算。 这意味着您需要遵循说明以及它们作为目标的移动方向。 我很难在幻灯片上向您解释,但是如何实现此目标的一个示例是CD以及本次会议的资料。

放置所有指令后,用新的偏移量替换旧的偏移量。 如果您没有损坏位移,那么一切都会解决。 现在您已经准备好了,这是一个真正的机会,可以在最高层次上实施该想法。 为此,您需要:

  • 拆卸说明;
  • 准备一个内存缓冲区;
  • 初始化可用常数f(x);
  • 遍历f(x)和某些数据指针,根据它们将在跟踪他妈的指令时编写代码;
  • 将指令分配给相应的已创建索引;
  • 解决所有有条件的跳跃;
  • 将新的内存分区标记为可执行;
  • 执行代码。

如果将事情放在正确的位置,那么我们会得到奇怪的事情-一切都弄乱了,指令跳入了晦涩的内存位置,所有这些看起来都很迷人。



这一切是否具有实际意义,或者仅仅是马戏表演? 这种转换的应用价值如下。 隔离汇编指令和一些计算f(x)的步骤,使我们可以将这些汇编指令放置在缓冲区中的任何位置,而无需任何用户交互。 为了混淆代码执行路径,您要做的就是在某种汇编器中以数学方式编写函数和指针,并随机选择它们。

这大大简化了多态编码技术。 您可以编写一系列函数来随机确定代码的位置,然后选择这些函数作为随机变量,而不是每次都以某种方式操纵代码而编写代码。

防反向功能不如防调试技术那么酷和新鲜。

反版本并不是让无法使用IDA带来多少乐趣,不是因为GNAA Last Measure图片可以使Reverser的计算机损坏多少,尽管这确实很有趣。 反复归意味着只不过是个混蛋,因为如果您像最后一个混蛋一样,得到一个Reverser,一个破坏不同系统保护的家伙,他会生气,将这个恶意程序发送到地狱然后离开。

同时,您将能够将所有机器人出售给俄罗斯商业网络,因为有了您的软件,您就可以“降低”所有与逆向工程有关的机器人。 每个人都知道如何在Google上找到反调试技术,但他们找不到解决在那里出现的创造性问题的解决方案。 最具创造力的防左轮手枪可使还原器的手指脱离键盘,并在墙壁上留下拳头大小的孔。 反向器会生气,因为他们的代码弄乱了一切,所以他们不明白您的所作所为。

这是一种神经游戏,一种心理上的事情,如果您在这件事上有创造力并创造出真正令人惊叹的反反转,那么您可以为此感到自豪。 但是您知道,实际上,您只是在尝试将其从代码中删除。

那我该怎么办? 我将采用混淆功能并对它们进行混淆。 然后,我将使用纠缠函数混淆的第二个版本并再次应用混淆。 因此,让我们拉一下代码。 这是一个数学巨魔的例子,我以它为例。



因此,我在打开的窗口中输入“按公式混淆”命令。



接下来,您将看到完成其工作的汇编说明。 请注意,我在这里使用了C ++,尽管我丝毫没有尝试避免这种情况。



这里突出显示了活动功能CALL EAX,然后是要应用的跳转指令,您在缓冲区中看到了各种不同的东西,所有这些都是通过每个单独的指令完成的。





现在,我将程序倒带到最后,您将看到结果。 因此,代码看起来仍然很棒,这里编译了许多JMP指令,看起来很混乱,实际上却很混乱。



下一张幻灯片显示了堆栈外观的图形表示。



每次发生这种情况时,我都会生成一个任意形状的随机正弦波公式,您会在这里看到许多不同的形状,这很酷。 我认为代码从左上角的某个地方开始,但我记不清楚了。 因此,他扭曲了一切,不仅可以制作正弦曲线,还可以扭曲螺旋线。



在这里,只有两个公式起作用,这些公式已包含在源代码中。 基于此,您可以根据需要执行任意数量的创意工作,从开始缓冲区到结束缓冲区,本质上只是DIFF。

问题在于此代码示例使用无条件跳转,这实际上是不好的,因为该代码应与以前完全相同,即无条件跳转仅遵循一个方向。 因此,您需要以相同的方式从入口点到末尾,摆脱跳转指令,然后就完成了-您已经获得了代码! 怎么办 有必要将无条件跳转变成有条件跳转。 有条件的跳转是在两个方向上进行的,它要好得多,可以说它好了50%。

在这里,我们有一个有趣的难题:如果我们需要条件跳转,那么我们仍然需要使用无条件跳转……到底是什么? 那我们该怎么办? 不透明谓词将拯救我们! 对于那些不知道的人,不透明谓词本质上是布尔语句,始终针对特定版本执行该布尔语句,而不管任何事情。
因此,让我们看一下我前面提到的零空间的扩展。 如果您有一组指令,并且它们具有无条件的跳转(即每条指令之间的转换),那么可以直接在一条指令之前或之后执行一系列不直接影响我们所需指令的汇编指令。
例如,如果您编写了非常具体的指令,这些指令不会改变您要混淆的主程序集,也就是说,只要您维护每条汇编程序指令的状态,就不要尝试不参与寄存器。 这更令人惊奇。
您可以考虑每条汇编指令,这些指令可能会混淆,例如前言,汇编数据和后记。 序言是汇编指令之前的序言,而后记是汇编指令之后的序言。 序言通常被使用或可以用于两件事:

  • 纠正前导词不透明谓词的后果;
  • 代码片段反调试。

但是序言本质上是有限的,因为您不能做太多。
后记很有趣。 它可以用于:

  • 不透明的谓词和复杂的跳转到以下代码部分;
  • 对整个代码执行进行反调试和模糊处理;
  • 程序本身中各种代码片段的加密和解密。

现在,我正在研究加密和解密每条指令的可能性,以便在执行每条指令时,它会解密下一部分,下一部分,下一个等等。 下一张幻灯片显示了一个示例。



序言行和调试器调用以绿色突出显示。 该调用所做的只是检查我们是否有调试器,然后转到代码的任意部分。

下面我们有一个非常简单的不透明谓词。 如果您在上指令的后记中保持Eax值,然后遵循XOR运算符,那么您的JZ会认为:“好吧,我显然可以向左或向右走,我认为最好向右走,因为那里是0”。 然后执行POP EAX,您的EAX返回,然后处理下一条指令,依此类推。

显然,这会产生比我们的基本策略更大的问题,例如残留影响和生成不同指令集的复杂性。 因此,将很难确定一条指令如何影响另一条指令。 您可以向我扔拖鞋,因为我尚未完成这个惊人的程序,但是您可以在我的博客中关注开发进度。



我注意到我们的公式f(x)不必迭代计算,例如f(1),f(2),... f(n)。 没有什么可以阻止对它们进行随机计算。 如果您很聪明,可以确定需要多少条指令,然后分配例如f(27),f(54),f(9),这将是您的指令随机放置的地方。 在执行此操作时,根据编写代码的方式,您可以提前将其停止,该代码仍将随机绑定您的指令。

如果您的代码是根据可预测的公式生成的,那么入口点也是可预测的,因此在完成接收代码之前,您可以再选一个级别,并将入口点或多或少地造成混淆。 例如,从单个入口获取300条汇编指令。

现在让我们谈谈这些缺点。



此方法需要仔细编译代码,主要使用GCC或上帝禁止使用C ++。 C ++实际上是一种很酷的语言,这有几个原因,但是您知道所有编译器都糟透了。 因此,这件事情的主要内容是一份有能力的手工制作的汇编,因为如果您试图混淆自己的组件,将导致发明Conficker蠕虫的帮派的认可,那么你就搞砸了。

您将需要大量的内存。 记住正弦波的图片。 红色是代码,蓝色是其工作所需的内存,它足以使所有内容正常工作。

完成代码后,您可能将要处理巨大的数据集。 如果您想混淆多个功能,它将大大增加。

函数指针的行为无法预测-有时正确,有时则无法。 这取决于您在做什么,并且肯定会出现问题,因为您无法预测函数指针在程序集中触发的位置和时间。

生成混淆并在序言和后记中操纵程序集的技巧越复杂,修复和调试就越困难。 因此,编写这样的代码就像在“好吧,我会在这里仔细插入一个或两个JMP”和“如何在短时间内解决所有问题”之间取得平衡之间的平衡? 因此,您只需要插入说明,然后找出几个月来所做的事情即可。

希望您今天学到了一些有用的东西。 我认为我真的喝醉了,因此我不太了解现在发生了什么。 下一张幻灯片显示了我的Twitter联系人,我的博客和网站,因此请访问我或写信。



仅此而已,谢谢您的光临!



感谢您与我们在一起。 你喜欢我们的文章吗? 想看更多有趣的资料吗? 通过下订单或将其推荐给您的朋友来支持我们,为我们为您开发的入门级​​服务器的独特模拟,为Habr用户提供30%的折扣: 关于VPS(KVM)E5-2650 v4(6核)的全部真相10GB DDR4 240GB SSD 1Gbps从$ 20还是如何划分服务器? (RAID1和RAID10提供选件,最多24个内核和最大40GB DDR4)。

VPS(KVM)E5-2650 v4(6核)10GB DDR4 240GB SSD 1Gbps至1月,直到 6个月的付款期免费 ,您可以在此处订购。

戴尔R730xd便宜2倍?在荷兰和美国,我们有2台Intel Dodeca-Core Xeon E5-2650v4 128GB DDR4 6x480GB SSD 1Gbps 100电视(249美元起) 阅读有关如何构建基础架构大厦的信息。 使用价格为9000欧元的Dell R730xd E5-2650 v4服务器的上等课程?

Source: https://habr.com/ru/post/zh-CN433280/


All Articles