Created
April 7, 2021 12:28
-
-
Save logost/cb93df41dd2432454324449b390403c4 to your computer and use it in GitHub Desktop.
Send ABORT over iSCSI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# -*- coding: UTF-8 -*- | |
# Author : [email protected] <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