-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathdol-responder.py
More file actions
232 lines (193 loc) · 7.1 KB
/
Copy pathdol-responder.py
File metadata and controls
232 lines (193 loc) · 7.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
#!/usr/bin/env python3
import socket
import struct
import fcntl
import os
import time
import threading
DOL_ETHERTYPE = 0x9988
CLIENT_MAC = bytes([0xd0, 0x50, 0x99, 0x60, 0xb2, 0x0d])
SERVER_MAC = bytes([0x74, 0x56, 0x3c, 0xfd, 0xbb, 0xd4])
SERVER_IP = b'10.0.0.29'
TARGET_IQN = b'iqn.2026-07.local.truenas:truenas'
TARGET_PORT = 3260
def get_mac(iface):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', iface[:15].encode()))
return info[18:24]
except:
return SERVER_MAC
def make_response(packet, src_mac):
if len(packet) < 42:
return None
# Parse the request
req_mac = packet[24:30]
# Only respond to our client
if req_mac != CLIENT_MAC:
return None
# Build response based on request structure
# Request observed structure:
# [10 bytes zeros][2 bytes mac_suffix][2 bytes zeros][2 bytes magic=0xa1d5]
# [8 bytes zeros][6 bytes client_mac][2 bytes zeros]
# [2 bytes vendor_id=0x10ec][2 bytes device_id=0x8168][1 byte type=0x01]
# [padding zeros]
# Response attempt: echo back but with type=0x02 and include server info
resp = bytearray(160)
# Copy first 10 bytes (zeros)
resp[0:10] = packet[0:10]
# Copy mac_suffix from request (bytes 10-11)
resp[10:12] = packet[10:12]
# Bytes 12-13: zeros
resp[12:14] = packet[12:14]
# Magic - try different value for response
resp[14:16] = struct.pack('<H', 0xa2d6) # Different magic for response
# Server MAC at offset 16-21 (instead of client)
resp[16:22] = src_mac
# Client MAC at offset 24-29
resp[24:30] = req_mac
# Type/response flag at offset 0x24 (36) - try 0x02
resp[0x24] = 0x02
# Add server IP at offset 0x30 (48)
resp[0x30:0x34] = struct.pack('!I', 0x0a00001d) # 10.0.0.29
# Add target port at offset 0x34 (52)
resp[0x34:0x36] = struct.pack('>H', TARGET_PORT)
# Add target IQN at offset 0x36 (54) - null terminated
iqn_bytes = TARGET_IQN + b'\x00'
resp[0x36:0x36+len(iqn_bytes)] = iqn_bytes
return bytes(resp)
def make_response_v2(packet, src_mac):
"""Alternative response format - mirror the request structure more closely"""
if len(packet) < 42:
return None
req_mac = packet[24:30]
if req_mac != CLIENT_MAC:
return None
resp = bytearray(160)
# Echo first 10 bytes
resp[0:10] = packet[0:10]
# Echo mac_suffix
resp[10:12] = packet[10:12]
# Bytes 12-13: zeros
resp[12:14] = b'\x00\x00'
# Same magic as request
resp[14:16] = packet[14:16]
# 8 bytes zeros
resp[16:24] = b'\x00' * 8
# Client MAC
resp[24:30] = req_mac
# Two bytes zeros
resp[30:32] = b'\x00\x00'
# Vendor/Device IDs
resp[32:36] = packet[32:36]
# Type: use 0x02 for response
resp[36] = 0x02
# Fill server info at offsets 37+
# Server IP 10.0.0.29
resp[37] = 10
resp[38] = 0
resp[39] = 0
resp[40] = 29
# Server port 3260
resp[41] = (3260 >> 8) & 0xff
resp[42] = 3260 & 0xff
# Target IQN string
iqn = TARGET_IQN + b'\x00'
resp[43:43+len(iqn)] = iqn
return bytes(resp)
def make_response_v3(packet, src_mac):
"""Simplest possible echo"""
if len(packet) < 42:
return None
req_mac = packet[24:30]
if req_mac != CLIENT_MAC:
return None
# Just echo back with a flag change and server info at end
resp = bytearray(packet)
resp[36] = 0x02 # Mark as response
# Append server info at end of meaningful data
# Server IP and port and IQN
server_info = b'10.0.0.29:3260:' + TARGET_IQN + b'\x00'
# Put it somewhere in the padding
offset = 64
resp[offset:offset+len(server_info)] = server_info
return bytes(resp)
def listen_and_respond(iface='eth0'):
mac = get_mac(iface)
print(f"Server MAC: {mac.hex(':')}")
print(f"Target client: {CLIENT_MAC.hex(':')}")
print(f"iSCSI target: {TARGET_IQN.decode()}")
print(f"Listening for DOL packets on {iface}...")
# Create raw socket
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(DOL_ETHERTYPE))
s.bind((iface, DOL_ETHERTYPE))
last_response_time = 0
response_count = 0
while True:
try:
packet = s.recv(1600)
if len(packet) < 14:
continue
# Ethernet header: dst(6) + src(6) + type(2)
eth_dst = packet[0:6]
eth_src = packet[6:12]
# Only respond to broadcast or to our server MAC
if eth_dst != b'\xff' * 6:
continue
# Check if this is from our target client
if eth_src != CLIENT_MAC:
continue
payload = packet[14:] if len(packet) > 14 else packet
now = time.time()
if now - last_response_time > 1.0: # Rate limit
resp = make_response(payload, mac)
if resp:
# Send response back to client
ether_header = struct.pack('!6s6sH', eth_src, mac, DOL_ETHERTYPE)
frame = ether_header + resp
s.send(frame)
response_count += 1
last_response_time = now
print(f"[{response_count}] Responded to DOL probe from {eth_src.hex(':')}")
# Also try alternative responses
time.sleep(0.01)
resp2 = make_response_v2(payload, mac)
if resp2:
frame2 = struct.pack('!6s6sH', eth_src, mac, DOL_ETHERTYPE) + resp2
s.send(frame2)
print(f" Sent v2 response")
time.sleep(0.01)
resp3 = make_response_v3(payload, mac)
if resp3:
frame3 = struct.pack('!6s6sH', eth_src, mac, DOL_ETHERTYPE) + resp3
s.send(frame3)
print(f" Sent v3 response")
except KeyboardInterrupt:
break
except Exception as e:
print(f"Error: {e}")
s.close()
def monitor_iscsi():
"""Monitor for iSCSI connection attempts from the client"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('0.0.0.0', 3260))
s.listen(5)
s.settimeout(30)
print("Monitoring iSCSI port 3260 for connections...")
try:
conn, addr = s.accept()
print(f"iSCSI connection from {addr[0]}:{addr[1]}")
conn.close()
except socket.timeout:
print("No iSCSI connection within 30 seconds")
except Exception as e:
print(f"iSCSI monitor error: {e}")
s.close()
if __name__ == '__main__':
import sys
iface = sys.argv[1] if len(sys.argv) > 1 else 'eth0'
# Start monitor thread
t = threading.Thread(target=monitor_iscsi, daemon=True)
t.start()
listen_and_respond(iface)