-
Notifications
You must be signed in to change notification settings - Fork 358
/
windows.py
335 lines (256 loc) · 11.3 KB
/
windows.py
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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# Diverter for Windows implemented using WinDivert library
import logging
from pydivert.windivert import *
import socket
import os
import dpkt
from . import fnpacket
import time
import threading
import platform
from winutil import *
from diverterbase import *
import subprocess
class WindowsPacketCtx(fnpacket.PacketCtx):
def __init__(self, lbl, wdpkt):
self.wdpkt = wdpkt
raw = wdpkt.raw.tobytes()
super(WindowsPacketCtx, self).__init__(lbl, raw)
# Packet mangling properties are extended here to also write the data to
# the pydivert.Packet object. This is because there appears to be no way to
# populate the pydivert.Packet object with plain octets unless you can also
# provide @interface and @direction arguments which do not appear at a
# glance to be directly available as attributes of pydivert.Packet,
# according to https://ffalcinelli.github.io/pydivert/
#
# Perhaps we can get these from wd_addr?
# src_ip overrides
@property
def src_ip(self):
return self._src_ip
@src_ip.setter
def src_ip(self, new_srcip):
super(self.__class__, self.__class__).src_ip.fset(self, new_srcip)
self.wdpkt.src_addr = new_srcip
# dst_ip overrides
@property
def dst_ip(self):
return self._dst_ip
@dst_ip.setter
def dst_ip(self, new_dstip):
super(self.__class__, self.__class__).dst_ip.fset(self, new_dstip)
self.wdpkt.dst_addr = new_dstip
# sport overrides
@property
def sport(self):
return self._sport
@sport.setter
def sport(self, new_sport):
super(self.__class__, self.__class__).sport.fset(self, new_sport)
if self.proto:
self.wdpkt.src_port = new_sport
# dport overrides
@property
def dport(self):
return self._dport
@dport.setter
def dport(self, new_dport):
super(self.__class__, self.__class__).dport.fset(self, new_dport)
if self.proto:
self.wdpkt.dst_port = new_dport
class Diverter(DiverterBase, WinUtilMixin):
def __init__(self, diverter_config, listeners_config, ip_addrs,
logging_level=logging.INFO):
# Populated by winutil and used to restore modified Interfaces back to
# DHCP
self.adapters_dhcp_restore = list()
self.adapters_dns_restore = list()
super(Diverter, self).__init__(diverter_config, listeners_config,
ip_addrs, logging_level)
self.running_on_windows = True
if not self.single_host_mode:
self.logger.error('Windows diverter currently only supports '
'SingleHost mode')
sys.exit(1)
# Used (by winutil) for caching of DNS server names prior to changing
self.adapters_dns_server_backup = dict()
# Configure external and loopback IP addresses
self.external_ip = self.get_best_ipaddress()
if not self.external_ip:
self.external_ip = self.get_ip_with_gateway()
if not self.external_ip:
self.external_ip = socket.gethostbyname(socket.gethostname())
self.logger.info('External IP: %s Loopback IP: %s' %
(self.external_ip, self.loopback_ip))
#######################################################################
# Initialize filter and WinDivert driver
# Interpose on all IP datagrams so they appear in the pcap, let
# DiverterBase decide whether they're actually forwarded etc.
self.filter = 'outbound and ip'
# Initialize WinDivert
try:
self.handle = WinDivert(filter=self.filter)
self.handle.open()
except WindowsError, e:
if e.winerror == 5:
self.logger.error('ERROR: Insufficient privileges to run '
'windows diverter.')
self.logger.error(' Please restart with Administrator '
'privileges.')
sys.exit(1)
elif e.winerror == 3:
self.logger.error('ERROR: Could not locate WinDivert DLL or '
'one of its components.')
self.logger.error(' Please make sure you have copied '
'FakeNet-NG to the C: drive.')
sys.exit(1)
else:
self.logger.error('ERROR: Failed to open a handle to the '
'WinDivert driver: %s', e)
sys.exit(1)
###########################################################################
# Diverter controller functions
def startCallback(self):
# Set local DNS server IP address
if self.is_set('modifylocaldns'):
self.set_dns_server(self.external_ip)
# Stop DNS service
if self.is_set('stopdnsservice'):
self.stop_service_helper('Dnscache')
self.logger.info('Diverting ports: ')
self.flush_dns()
self.diverter_thread = threading.Thread(target=self.divert_thread)
self.diverter_thread.daemon = True
self.diverter_thread.start()
return True
def divert_thread(self):
try:
while True:
wdpkt = self.handle.recv()
if wdpkt is None:
self.logger.error('ERROR: Can\'t handle packet.')
continue
pkt = WindowsPacketCtx('divert_thread', wdpkt)
cb3 = [
self.check_log_icmp,
self.redirIcmpIpUnconditionally
]
cb4 = [
self.maybe_redir_port,
self.maybe_fixup_sport,
self.maybe_redir_ip,
self.maybe_fixup_srcip,
]
self.handle_pkt(pkt, cb3, cb4)
# Attempt to send the processed packet
self.setLastErrorNull() # WinDivert/LastError workaround
try:
self.handle.send(pkt.wdpkt)
except Exception, e:
protocol = 'Unknown'
if pkt.proto:
protocol = pkt.proto
elif pkt.is_icmp:
protocol = 'ICMP'
self.logger.error('ERROR: Failed to send %s %s %s packet',
self.pktDirectionStr(pkt),
self.pktInterfaceStr(pkt), protocol)
self.logger.error(' %s' % (pkt.hdrToStr()))
self.logger.error(' %s', e)
except WindowsError as e:
if e.winerror in [4, 6, 995]:
return
else:
raise
def stopCallback(self):
if self.pcap:
self.pcap.close()
self.handle.close()
# Restore DHCP adapter settings
for interface_name in self.adapters_dhcp_restore:
cmd_set_dhcp = ('netsh interface ip set address name="%s" dhcp' %
interface_name)
# Restore DHCP on interface
try:
subprocess.check_call(cmd_set_dhcp, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
except subprocess.CalledProcessError, e:
self.logger.error('Failed to restore DHCP on interface %s.' %
interface_name)
else:
self.logger.info('Restored DHCP on interface %s' %
interface_name)
# Restore DHCP adapter settings
for interface_name in self.adapters_dns_restore:
cmd_del_dns = ('netsh interface ip delete dns name="%s" all' %
interface_name)
cmd_set_dns_dhcp = ('netsh interface ip set dns "%s" dhcp' %
interface_name)
# Restore DNS on interface
try:
subprocess.check_call(cmd_del_dns, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
subprocess.check_call(cmd_set_dns_dhcp, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
except subprocess.CalledProcessError, e:
self.logger.error("Failed to restore DNS on interface %s." %
interface_name)
else:
self.logger.info("Restored DNS on interface %s" %
interface_name)
# Restore DNS server
if self.is_set('modifylocaldns'):
self.restore_dns_server()
# Restart DNS service
if self.is_set('stopdnsservice'):
self.start_service_helper('Dnscache')
self.flush_dns()
return True
def pktInterfaceStr(self, pkt):
"""WinDivert provides is_loopback which Windows Diverter uses to
display information about the disposition of packets it is
processing during error and other cases.
"""
return 'loopback' if pkt.wdpkt.is_loopback else 'external'
def pktDirectionStr(self, pkt):
"""WinDivert provides is_inbound which Windows Diverter uses to
display information about the disposition of packets it is
processing during error and other cases.
"""
return 'inbound' if pkt.wdpkt.is_inbound else 'outbound'
def redirIcmpIpUnconditionally(self, crit, pkt):
"""Redirect ICMP to loopback or external IP if necessary.
On Windows, we can't conveniently use an iptables REDIRECT rule to get
ICMP packets sent back home for free, so here is some code.
"""
if (pkt.is_icmp and
pkt.dst_ip not in [self.loopback_ip, self.external_ip]):
self.logger.info('Modifying ICMP packet (type %d, code %d):' %
(pkt.icmp_type, pkt.icmp_code))
self.logger.info(' from: %s' % (pkt.hdrToStr()))
pkt.dst_ip = self.getNewDestinationIp(pkt.src_ip)
self.logger.info(' to: %s' % (pkt.hdrToStr()))
return pkt
def main():
diverter_config = {'redirectalltraffic': 'no',
'defaultlistener': 'DefaultListener',
'dumppackets': 'no'}
listeners_config = {'DefaultListener': {'port': '1337', 'protocol': 'TCP'}}
diverter = Diverter(diverter_config, listeners_config)
diverter.start()
###########################################################################
# Run processing
import time
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
diverter.stop()
###########################################################################
# Run tests
# TODO
if __name__ == '__main__':
main()