简而言之,有一天我很无聊,请我的朋友Maximus Hackerman给我一些要做的事情。迅速,他通过一个简单的可执行文件发送,称为“ reververeme.exe”(virustotal链接),简单地评论了“在您自己的应用程序中打印隐藏的消息”。自然,没有提供CPP标头文件。运行可执行文件后,它只是作为基于控制台的应用程序打开的,打印“发送了所有魔术数据包,很快就会退出,蜂鸣器!”然后几乎立即退出;我想“很快”是主观的。
幸运的是,似乎没有反欺骗,所以我想哈克曼当天感觉很好。
连接windbg和一个拆卸器后,我们可以看到应用程序抛出的,最初似乎是divide by zero 。
但是,经过仔细检查,我们发现在应用程序打印其唯一可见消息之前就抛出了此例外。重要的是要注意,在此之前,有两个VEH(矢量范围的异常处理程序)略微注册,因此此例外很可能是故意的,并用于扭动控制流。便宜的技巧真的要试图把我们赶下去!
忘记车辆目前,可以肯定地假设,如果应用程序正在“发送”数据包,那么它是使用winsock send函数来执行此操作的(尽管该应用程序清楚地说它们是“魔术”数据包,所以我们会看到)。
正如预期的那样,事实证明,您无法通过魔术发送数据包,因此Winsock send功能确实已导入。
通过在发送函数上放置断点,我们可以查看发送时是否有任何相关数据,我们可以在发送时抽象。可悲的是,当缓冲区加载到功能中时,数据已被加密。但是,我们可以确定缓冲区的长度始终为3个字节,插座始终绑定为0x69 ( NICE )的硬编码值,并且发送函数断点总计达到14次。
有几种方法可以解决上述加密。一种是将其完全逆转,这可能是很大的努力,另一个是在加密之前找到所需的数据并在加密之前将其抽象。后者比前者要容易得多,因此我们要做。不过,请随时扭转加密,我简要介绍了,这并不令人恐惧。另外,如果您觉得很花哨,则可以始终覆盖套接字处理值并与之达成接收申请。
可悲的是,除了最初的消息外,只读数据段中没有有用的字符串,因此从那个方面没有有用的指针!
回到车辆,我们可以看到两个注册的处理程序都用于将一些未知对象复制到分配的内存缓冲区中。这似乎是通过使用memcpy来完成的,将函数用作第二个参数,进而使用硬编码整数作为其第二个参数。值得注意的是,这些硬编码值在任何时候都不超过14。我只在屏幕截图中包含了其中一辆车,因为除了硬编码值外,它们基本相同。
通过在一个memcpy函数之一上进行断点并检查第二个参数中的子函数,我们可以看到硬编码整数(以下示例中的13 / 0DH)设置为A1参数的第一个字节,而A1+1包含一个字符,就在指针被指出后。
如果我们检查其他14个对此功能的调用中的一些,则可以找到相同的行为重复;将字符从1到14安排,使用相应的数字作为订单的指标,我们可以看到它们开始拼写一些清晰的单词。现在,一旦我们知道它是什么,我们可能会变得超级懒惰,只是制作一个控制台应用来打印出消息,但这确实感觉就像作弊。另外,消息可能在某个时候发生变化。因此,让我们编写一个代码洞穴以在加密之前拦截它们。
抱歉,但是我们正在使用C ++!如果您希望使用C#,请随时通过整个平台调用970万个功能,然后完成后回到这里。无论如何,首先,我们需要决定在哪里编码洞穴。幸运的是,我们已经知道!通过分析上述函数,无论多么简短,我们就知道,通过突出显示的点, RDX包含索引, RDX+1包含相应的字符。以下是讨论功能的装配代码。
现在,从逻辑上讲,从mov [rsp+arg_8], rdx最佳的跳跃位置是我们并不真正在乎第三个参数,但是确实想拦截RDX寄存器和RDX+1 。要做这个孩子,我们将需要一些字节:10个字节用于MOV指令,将代码洞穴的地址移至寄存器(我们将在10点新闻中使用RAX ,更多地使用此内容),而JMP指令中的2个字节为2个字节,以跳到寄存器。对于那些拥有进一步数学的PTSD的人,总计12个字节。现在,在我们去所有贝西姨妈并用writeProcessMemory上划出该代码之前,我们需要考虑到上面的组件中没有理想的位置可以替换12个字节。如果我们想从偏移0x2905 ( mov [rsp+arg_8], rdx )跳跃,并且我们需要12个字节才能这样做,那么这需要我们来抵消0x2917 ,这是两个MOV指令之间的Smack Bang。不幸的是,如果我们只是简单地将我们的字节写入那里,它将完全堵塞组装,并可能引起一些,呃,“有趣”的副作用。结果,要添加一些单字节说明以填充填充物,然后将其四舍五入到指令结束时,这将变得更容易(也许更刺眼,对不起)。欢迎在0x90。
无论如何,现在我们知道了我们的计划是什么,所以让我们编写一些代码:提示Intense Hacker Man Music !
下面是一旦字节写入应用程序的组件,最初的跳到我们的代码洞穴的跳跃将看起来像,并带有自己的NOP幻灯片。
但是,在我们实际编写和替换任何组件之前,我们需要以暂停状态启动该应用程序;这将在早期阶段停止应用程序运行时,以便可以在应用程序进入我们感兴趣的指令之前进行内存更改。下面的代码提取物显示了此过程,我不会在此回购的源文件中查看它,并且它大多对其进行说明。
现在,该过程已经产生,我们需要获取该过程的基本地址。通常,您可以为此使用EnumProcessModules,但是由于我们立即暂停了主处理线程,PEB不包含填充的PEB_LDR_DATA结构,特别是InMemoryOrderModuleList ,因此我们目前无法获得基础地址。顺便说一句,这似乎并未在MSDN上的任何地方进行记录。幸运的是,这相对容易绕过。通过很快恢复过程,查询模块,然后重悬于过程中,我们可以获得所需的信息,而无需进步太大。与Windows中的大多数内容一样,Microsoft喜欢重申其操作系统是优越的,而不是再次记录所需的功能: NtSuspendProcess和NtResumeProcess 。很方便地,我父亲是比尔·盖茨(Bill Gates)的朋友,他告诉我这些功能在ntdll.dll中耦合在一起,因此我们可以使用我之前完成的以下课程来获取它们:
现在,我们拥有所需的两个函数,我们可以恢复过程,查询模块并重新悬浮该过程:
您可能想知道,为什么WARY循环等待两个模块发现而不是一个模块?好吧,微软希望通过也没有提到EnumProcessModules找到的第一个模块是Ntdll.dll,而是在无证意大利面条网的背面获得肉丸的成绩,第二个模块将是可执行的。虽然这听起来很合理,但是一旦找到可执行文件,它将与ntdll.dll交换索引。这是一个例子:
仅查询第一个模块后的结果:
查询两个模块后的结果:
在进行进一步之前,我们需要编写将初始跳跃到代码洞穴的汇编代码,而代码洞穴本身。从本质上讲,我们将从偏移0x2905开始覆盖一些内存,跳跃,进行代码洞穴间谍活动,然后跳回0x2911 ,以继续正常的程序流。最初,我们将地址转移到RAX寄存器为MOV RAX, 0x0 ,因为我们会跳到的地址是动态的,我们还不知道它是什么。 RAX是一个安全使用的登记册,因为它的挥发性寄存器,并且无论如何在不久之后都被覆盖。有趣的事实,这就是任天堂对原始Mario Bros平台游戏编程的方式,并有很多跳跃(告诉我我很有趣)!以下是编译器中代码的外观;它可以以其他方式创建,但是我选择将所需的汇编指令分为字节码。如果您想在家中这样做,请使用此网站。
实际代码洞穴的代码更为复杂,并且在此文件中也注释了它的逻辑,但这是粗略的过程:
R10RDX的下部移至R11BRDX的第二个字节移入R11B+12 )到R10的地址(是0x2911 )R10我们还需要将覆盖(NOP幻灯片)覆盖的汇编代码重写到我们的代码洞穴中,以保留堆栈等。该代码在“ Predetermined Assembly ”区域中引用,不包括NOP。以下是丑陋的荣耀中的代码洞穴:大部分都是困难的部分。
在这一点上,我们实质上有我们需要将隐藏消息从内存中删除的一切,我们只需要实现它即可。回顾一下,我们有一个悬挂过程的句柄,代表汇编逻辑的2个字节数组,悬挂过程的基础地址,存储在modules[0]中,以及我们需要编写跳跃逻辑的位置的偏移。下面的代码段创建要跳起来的地址,代码洞穴的地址(跳至),我们的3字节内存存储的地址,将地址写入汇编代码,然后在恢复暂停过程之前将地址写入汇编代码:
codeCaveStorageAddr的神奇的哈利·波特风格铸造是将地址转换为字节,并且循环中的硬编码值是用于汇编数组中的动态地址位置。当然,这可以以更干净的方式完成(不要写魔术数字孩子),我不得不手动写出所有这些字节后就懒了。最后几个要写的位是要使用ReadProcessMemory读取的循环,存储器,将字符和索引存储到有序的字节数组中,发信号字节,使用WriteProcessMemory ,当当前内存的读取完成并打印隐藏的消息时。我们知道,发送功能仅发生14次,因此一旦填充字节数组,我们就退出了while循环;可以通过更多的内存编辑来更改此过程,以向我们的应用程序发出信号,表明该过程正在“退出”,并且我们的循环可以停止,而不是使用14个硬编码值,但对于此示例而言。
结果?我们的隐藏消息印在我们自己的控制台应用程序中!如果有人好奇,NPT是对其他软件最大hacker-im-iall-iall-Him写道的引用。