Skip to content

Instantly share code, notes, and snippets.

@logost
Created April 7, 2021 12:28
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save logost/cb93df41dd2432454324449b390403c4 to your computer and use it in GitHub Desktop.
Save logost/cb93df41dd2432454324449b390403c4 to your computer and use it in GitHub Desktop.
Send ABORT over iSCSI
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
# Author : tintinweb@oststrom.com <github.com/tintinweb>
'''
A simple TCP three-way handshake example
#> python scapy_tcp_handshake.py
DEBUG:__main__:init: ('oststrom.com', 80)
DEBUG:__main__:start
DEBUG:__main__:SND: SYN
DEBUG:__main__:RCV: SYN+ACK
DEBUG:__main__:SND: SYN+ACK -> ACK
DEBUG:__main__:RCV: None
DEBUG:__main__:RCV: None
None
DEBUG:__main__:SND: FIN
DEBUG:__main__:RCV: None
Note: linux might send an RST for forged SYN packets. Disable it by executing:
#> iptables -A OUTPUT -p tcp --tcp-flags RST RST -s <src_ip> -j DROP
'''
from scapy.all import *
import time
import sys
import logging
logger = logging.getLogger(__name__)
login_header = [ 0x43, 0x87, 0x00, 0x00, 0x00, 0x00, 0x01, 0xb8,
0x80, 0x25, 0x18, 0xf4, 0x00, 0x00, 0x00, 0x00,
0x42, 0xdd, 0x1b, 0x50, 0x00, 0x00, 0x00, 0x00,
0x64, 0x6d, 0x06, 0x51, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]
login_text = "SessionType=Normal\0HeaderDigest=None,CRC32C\0DataDigest=None\0InitialR2T=No\0ImmediateData=Yes\0MaxBurstLength=262144\0FirstBurstLength=262144\0DefaultTime2Wait=2\0DefaultTime2Retain=0\0MaxOutstandingR2T=1\0ErrorRecoveryLevel=0\0IFMarker=No\0OFMarker=No\0MaxConnections=1\0MaxRecvDataSegmentLength=262144\0DataPDUInOrder=Yes\0DataSequenceInOrder=Yes\0"
abort_bytes = [
0x42, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x42, 0xdd, 0x1b, 0x53, 0x42, 0xdd, 0x1b, 0x51,
0x64, 0x6d, 0x06, 0x53, 0x00, 0x00, 0x00, 0x01,
0x64, 0x6d, 0x06, 0x51, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]
test_lun_bytes = [
0x01, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x42, 0xdd, 0x1b, 0x51, 0x00, 0x00, 0x00, 0x00,
0x64, 0x6d, 0x06, 0x51, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]
def change_u32_at(cmd, value, offset):
cmd[offset + 3] = value & 0xff
value = value >> 8
cmd[offset + 2] = value & 0xff
value = value >> 8
cmd[offset + 1] = value & 0xff
value = value >> 8
cmd[offset + 0] = value & 0xff
return cmd
def get_u32_at(cmd, offset):
value = cmd[offset + 0] << 24
value |= cmd[offset + 1] << 16
value |= cmd[offset + 2] << 8
value |= cmd[offset + 3]
return value
def abort_cmd(statsn):
cmd = abort_bytes.copy()
change_u32_at(cmd, statsn, 28)
return bytes(cmd)
def test_lun_cmd(statsn):
cmd = test_lun_bytes.copy()
change_u32_at(cmd, statsn, 28)
return bytes(cmd)
def login_cmd(initiator, target):
cmd = login_header.copy()
cmd += [ord(v) for v in list("InitiatorName=%s\0"% initiator)]
cmd += [ord(v) for v in list("TargetName=%s\0"% target) ]
cmd += [ord(v) for v in login_text]
return bytes(cmd)
class TcpHandshake(object):
def __init__(self, target):
self.seq = 0
self.seq_next = 0
self.target = target
self.dst = target[0]
self.dport = target[1]
self.sport = random.randrange(0,2**16)
self.l4 = IP(dst=target[0])/TCP(sport=self.sport, dport=self.dport, flags=0,
seq=random.randrange(0,2**32))
self.src = self.l4.src
self.swin = self.l4[TCP].window
self.dwin= 1
self.acked = 0
self.iscsi_statsn = 0
logger.debug("init: %s"%repr(target))
def start(self):
logger.debug("start")
return self.send_syn()
def match_packet(self, pkt):
if pkt.haslayer(IP) and pkt[IP].dst == self.l4[IP].src \
and pkt.haslayer(TCP) and pkt[TCP].dport == self.sport \
and pkt[TCP].ack == self.seq_next:
return True
return False
def _sr1(self, pkt):
send(pkt)
ans = sniff(filter="tcp port %s"%self.target[1],lfilter=self.match_packet,count=1,timeout=1)
return ans[0] if ans else None
def r1(self):
ans = sniff(filter="tcp port %s"%self.target[1],lfilter=self.match_packet,count=2,timeout=1)
return ans[0] if ans else None
def handle_recv(self, pkt):
if pkt and pkt.haslayer(IP) and pkt.haslayer(TCP):
if pkt[TCP].flags & 0x3f == 0x12: # SYN+ACK
logger.debug("RCV: SYN+ACK")
return self.send_synack_ack(pkt)
elif pkt[TCP].flags & 4 != 0: # RST
logger.debug("RCV: RST")
raise Exception("RST")
elif pkt[TCP].flags & 0x1 == 1: # FIN
logger.debug("RCV: FIN")
return self.send_finack(pkt)
elif pkt[TCP].flags & 0x3f == 0x10: # ACK
logger.debug("RCV: ACK")
return None
elif pkt[TCP].flags & 0x8 == 0x8: # PUSH
self.acked = len(pkt[TCP]) - 20 # tcp header len
if (len(pkt[TCP]) - 20 > 24):
self.iscsi_statsn = get_u32_at(pkt[TCP].payload.load, 24)
logger.debug("RCV: PUSH (%d) statsn 0x%x" % (self.acked, self.iscsi_statsn))
return self.send_ack(pkt)
logger.debug("RCV: %s"%repr(pkt))
return None
def recv1(self):
return self.handle_recv(self.r1())
def send_syn(self):
logger.debug("SND: SYN")
self.l4[TCP].flags = "S"
self.seq_next = self.l4[TCP].seq + 1
response = self._sr1(self.l4)
self.l4[TCP].seq += 1
return self.handle_recv(response)
def send_synack_ack(self, pkt):
logger.debug("SND: SYN+ACK -> ACK")
self.l4[TCP].ack = pkt[TCP].seq+1
self.l4[TCP].flags = "A"
self.seq_next = self.l4[TCP].seq
response = self._sr1(self.l4)
return self.handle_recv(response)
def send_data(self, d):
logger.debug("SND: DATA")
self.l4[TCP].flags = "PA"
response = self._sr1(self.l4/d)
self.seq_next = self.l4[TCP].seq + len(d)
self.l4[TCP].seq += len(d)
return self.handle_recv(response)
def send_fin(self):
logger.debug("SND: FIN")
self.l4[TCP].flags = "F"
self.seq_next = self.l4[TCP].seq + 1
response = self._sr1(self.l4)
self.l4[TCP].seq += 1
return self.handle_recv(response)
def send_finack(self, pkt):
logger.debug("SND: FIN+ACK")
self.l4[TCP].flags = "FA"
self.l4[TCP].ack = pkt[TCP].seq+1
self.seq_next = self.l4[TCP].seq + 1
response = send(self.l4)
self.l4[TCP].seq += 1
raise Exception("FIN+ACK")
def send_ack(self, pkt):
logger.debug("SND: ACK")
self.l4[TCP].flags = "A"
self.l4[TCP].ack = pkt[TCP].seq + self.acked
self.seq_next = self.l4[TCP].seq + self.acked
response = self._sr1(self.l4)
#self.l4[TCP].seq += 1
def send_test_lun(self):
self.send_data(test_lun_cmd(self.iscsi_statsn + 1))
def send_abort(self):
self.send_data(abort_cmd(self.iscsi_statsn + 1))
if __name__=='__main__':
logging.basicConfig(level=logging.DEBUG)
logger.setLevel(logging.DEBUG)
if (len(sys.argv) < 4):
print("%s IP iqn.target iqn.initiator" % sys.argv[0])
exit(1)
else:
sp_host = sys.argv[1]
target = sys.argv[2]
initiator = sys.argv[3]
conf.verb = 0
tcp_hs = TcpHandshake((sp_host, 3260))
tcp_hs.start()
tcp_hs.send_data(login_cmd(initiator, target))
tcp_hs.recv1() # receive login response
tcp_hs.send_test_lun()
tcp_hs.recv1() # receive test_lun response
tcp_hs.send_abort()
print("go remove the backend device\n")
time.sleep(10) # Sleep for 10 seconds
tcp_hs.recv1() # receive abort response
tcp_hs.send_abort() # send some cmd to release previous abort
tcp_hs.recv1() # receive abort response
tcp_hs.send_fin()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment