转载

IE安全系列:脚本先锋(III)--网马中的Shellcode

本文V.1,V.2两节也将尽量只从脚本的角度来解释部分内容,第三部分将从实例中简单总结一下通用SHELLCODE的实现方法。 下一章脚本先锋IV中,将介绍简单的shellcode分析方式。至于与其他系统安全机制结合起来的内容,“脚本先锋”系列中暂时就不提了,而将留在后续章节中介绍。

V.1 何为Shellcode,调试工具简单介绍(OD、Windbg)

shellcode本是指获得一个shell的代码,也或者是达到特定目的的代码,网马中利用IE漏洞的“Shellcode”一词,大多数就是指这样的代码集合

IE安全系列:脚本先锋(III)--网马中的Shellcode

图:PE文件中字节和对应的反汇编语句

诚如所见,如果要通过机器码实行一段有意义的操作,代码中很可能会包含非可打印字符、扩展字符。其中,特别是扩展字符可能因为用户机器语言环境不同产生歧义。如下图,如果采用明文的方式,4个字节的内容在用户机器上长度会被判断为3(中文系统默认设置为例),这会导致在铺设shellcode时长度无法准确计算,在利用漏洞时会产生大量的不便。

IE安全系列:脚本先锋(III)--网马中的Shellcode IE安全系列:脚本先锋(III)--网马中的Shellcode

因此在布置shellcode时通常都会将其escape编码。escape很简单,也即将字符重新改为字符的ASCII值的十六进制形式,并加上百分号。

Unescape能解开的数据有多种形式,常见的为:

%XX,XX为字符的ASCII值;

%uAABB,AABB为unicode字符的ASCII值,如果要把这个结果当成多字节数据来处理时,此时相当于%BB%AA;

以下并不算严格的“解开”,但是也算是一种编码格式,因此也包含进去了:

/OO、/xHH,最普通的字符串转义形式,即类C语法中的/转义符号(最常见的比如/n、/r、/0);

/uAABB,同%u。

escape不会对字母和数字进行编码,也不会对* @ - _ + . / 这些字符编码。其他所有的字符都会被转义序列替换。

unesacpe则会对所有的符合上述“能解开”的内容进行解码。

例如字符”|”,其ASCII值为124 (0x7c),经过ESCAPE编码之后则为%7C。

在网马中还会出现一个名词,这里单独介绍一下:

NOP sled(或者slide,slice): 指不会对代码执行产生太大影响的内容。或者至少是对要执行的shellcode不会影响太大的内容。

例如:

·nop (0x90,但是喷射起来可能不是多方便,毕竟如果是想要覆盖某些对象的虚表,那么0x90909090这个地址必然是个不可能完成的任务,因为这个已经是内核态地址了,如果只是普通的缓冲区溢出使用这个也未尝不可)

·or al,0c(0x0c0c, 2字节的sled,比较方便也极为常见,即使一路喷过去,最理想情况所需内存也不过160M而已,虽然实际肯定会大一些)

·or eax,0d0d0d0d(0x0d 0x0d0d0d0d,5字节,可能导致对齐问题,但是由于不常见也不一定会被内存检测工具检测到),等等。

通过内存喷射覆盖某个已经释放了的对象后,该对象的内存看起来会像:

IE安全系列:脚本先锋(III)--网马中的Shellcode

图:变量0x35fb03是某个class,free后该class的内存重新被nop sled占据,当该对象内的成员函数被重新使用时,EIP将变为0x0c0c0c0c+offset。

IE安全系列:脚本先锋(III)--网马中的Shellcode

而0c0c0c0c处,则安排着有我们的shellcode,当然上面这个只是演示,所以放了一堆A在里面。

由于0x0c0c只是会操作eax,一般不会产生什么大影响,所以就可以任由它这么覆盖下去,而由于它是2个字节的,所以相比非主流的0x0d对齐时“性价比”比较好。

IE安全系列:脚本先锋(III)--网马中的Shellcode

图:在Visual Studio 2008中模拟堆喷覆盖一个已释放的类的虚表

如果你也要做类似简单的试验,建议不要在2011之后的Visual Studio中去做,在这里面会变得比较麻烦,2011中delete操作删除一个对象后,该对象的地址会被置为0x8123。导致难以复现上述现象。

至于0x8123这个值也许之后可能大家会在分析其他软件的时候发现,简单介绍一下微软的做法:

微软在VS11 Beta中引入了这个功能,使用0x8123来解决UAF的问题,它的处理方案是相当于将原来的:

delete p;

一句话给扩展为:

delete p;

(void*)p = (void*)0x00008123;

两句。

而通常,程序员所写的正确的释放过程应当为

delete p; p = NULL;

经过插入后变为了

delete p; (void*)p = (void*)0x00008123; p = NULL;

三句。

在编译器眼里,这三句中,由于后两句都是给同一个变量赋常量值,因而又会被自动优化为两句

delete p; p=NULL;

看到了吧,如果正确释放,VS插入的这句不会影响最终生成结果,而如果程序员忘记了p=NULL一句,最终结果将变为:

delete p;  (void*)p = (void*)0x00008123;

而0x00008123位于Zero Pages(第0~15页,地址范围0x0~ 0x0000FFFF)中,因而如果被访问到会导致程序触发存取违例,因而这个地址可以被视为安全的。相对于更加频繁出现的访问0x00000000造成的空指针引用崩溃,如果程序员看到程序是访问了0x00008123崩溃了,那么立马就应该知道是发生了释放后引用的问题。这里的内容具体可参考(1]。

[1]  [2] [3] [4] [5] [6] [7]  下一页

正文到此结束
Loading...