1# This file is part of Scapy
2# See http://www.secdev.org/projects/scapy for more information
3# Copyright (C) Philippe Biondi <phil@secdev.org>
4# Copyright (C) Gabriel Potter <gabriel@potter.fr>
5# This program is published under a GPLv2 license
6
7# flake8: noqa E266
8# (We keep comment boxes, it's then one-line comments)
9
10"""
11C API calls to Windows DLLs
12"""
13
14import ctypes
15import ctypes.wintypes
16from ctypes import Structure, POINTER, byref, create_string_buffer, WINFUNCTYPE
17
18from scapy.config import conf
19from scapy.consts import WINDOWS_XP
20
21ANY_SIZE = 65500  # FIXME quite inefficient :/
22AF_UNSPEC = 0
23NO_ERROR = 0x0
24
25CHAR = ctypes.c_char
26DWORD = ctypes.wintypes.DWORD
27BOOL = ctypes.wintypes.BOOL
28BOOLEAN = ctypes.wintypes.BOOLEAN
29ULONG = ctypes.wintypes.ULONG
30ULONGLONG = ctypes.c_ulonglong
31HANDLE = ctypes.wintypes.HANDLE
32LPWSTR = ctypes.wintypes.LPWSTR
33VOID = ctypes.c_void_p
34INT = ctypes.c_int
35UINT = ctypes.wintypes.UINT
36UINT8 = ctypes.c_uint8
37UINT16 = ctypes.c_uint16
38UINT32 = ctypes.c_uint32
39UINT64 = ctypes.c_uint64
40BYTE = ctypes.c_byte
41UCHAR = UBYTE = ctypes.c_ubyte
42SHORT = ctypes.c_short
43USHORT = ctypes.c_ushort
44
45
46# UTILS
47
48
49def _resolve_list(list_obj):
50    current = list_obj
51    _list = []
52    while current and hasattr(current, "contents"):
53        _list.append(_struct_to_dict(current.contents))
54        current = current.contents.next
55    return _list
56
57
58def _struct_to_dict(struct_obj):
59    results = {}
60    for fname, ctype in struct_obj.__class__._fields_:
61        val = getattr(struct_obj, fname)
62        if fname == "next":
63            # Already covered by the trick below
64            continue
65        if issubclass(ctype, (Structure, ctypes.Union)):
66            results[fname] = _struct_to_dict(val)
67        elif val and hasattr(val, "contents"):
68            # Let's resolve recursive pointers
69            if hasattr(val.contents, "next"):
70                results[fname] = _resolve_list(val)
71            else:
72                results[fname] = val
73        else:
74            results[fname] = val
75    return results
76
77##############################
78####### WinAPI handles #######
79##############################
80
81_winapi_SetConsoleTitle = ctypes.windll.kernel32.SetConsoleTitleW
82_winapi_SetConsoleTitle.restype = BOOL
83_winapi_SetConsoleTitle.argtypes = [LPWSTR]
84
85def _windows_title(title=None):
86    """Updates the terminal title with the default one or with `title`
87    if provided."""
88    if conf.interactive:
89        _winapi_SetConsoleTitle(title or "Scapy v{}".format(conf.version))
90
91
92SC_HANDLE = HANDLE
93
94class SERVICE_STATUS(Structure):
95    """https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-_service_status"""  # noqa: E501
96    _fields_ = [("dwServiceType", DWORD),
97                ("dwCurrentState", DWORD),
98                ("dwControlsAccepted", DWORD),
99                ("dwWin32ExitCode", DWORD),
100                ("dwServiceSpecificExitCode", DWORD),
101                ("dwCheckPoint", DWORD),
102                ("dwWaitHint", DWORD)]
103
104
105OpenServiceW = ctypes.windll.Advapi32.OpenServiceW
106OpenServiceW.restype = SC_HANDLE
107OpenServiceW.argtypes = [SC_HANDLE, LPWSTR, DWORD]
108
109CloseServiceHandle = ctypes.windll.Advapi32.CloseServiceHandle
110CloseServiceHandle.restype = BOOL
111CloseServiceHandle.argtypes = [SC_HANDLE]
112
113OpenSCManagerW = ctypes.windll.Advapi32.OpenSCManagerW
114OpenSCManagerW.restype = SC_HANDLE
115OpenSCManagerW.argtypes = [LPWSTR, LPWSTR, DWORD]
116
117QueryServiceStatus = ctypes.windll.Advapi32.QueryServiceStatus
118QueryServiceStatus.restype = BOOL
119QueryServiceStatus.argtypes = [SC_HANDLE, POINTER(SERVICE_STATUS)]
120
121def get_service_status(service):
122    """Returns content of QueryServiceStatus for a service"""
123    SERVICE_QUERY_STATUS = 0x0004
124    schSCManager = OpenSCManagerW(
125        None,  # Local machine
126        None,  # SERVICES_ACTIVE_DATABASE
127        SERVICE_QUERY_STATUS
128    )
129    service = OpenServiceW(
130        schSCManager,
131        service,
132        SERVICE_QUERY_STATUS
133    )
134    status = SERVICE_STATUS()
135    QueryServiceStatus(
136        service,
137        status
138    )
139    result = _struct_to_dict(status)
140    CloseServiceHandle(service)
141    CloseServiceHandle(schSCManager)
142    return result
143
144
145##############################
146###### Define IPHLPAPI  ######
147##############################
148
149
150iphlpapi = ctypes.windll.iphlpapi
151
152##############################
153########### Common ###########
154##############################
155
156
157class in_addr(Structure):
158    _fields_ = [("byte", UBYTE * 4)]
159
160
161class in6_addr(Structure):
162    _fields_ = [("byte", UBYTE * 16)]
163
164
165class sockaddr_in(Structure):
166    _fields_ = [("sin_family", SHORT),
167                ("sin_port", USHORT),
168                ("sin_addr", in_addr),
169                ("sin_zero", 8 * CHAR)]
170
171
172class sockaddr_in6(Structure):
173    _fields_ = [("sin6_family", SHORT),
174                ("sin6_port", USHORT),
175                ("sin6_flowinfo", ULONG),
176                ("sin6_addr", in6_addr),
177                ("sin6_scope_id", ULONG)]
178
179
180class SOCKADDR_INET(ctypes.Union):
181    _fields_ = [("Ipv4", sockaddr_in),
182                ("Ipv6", sockaddr_in6),
183                ("si_family", USHORT)]
184
185##############################
186######### ICMP stats #########
187##############################
188
189
190class MIBICMPSTATS(Structure):
191    _fields_ = [("dwMsgs", DWORD),
192                ("dwErrors", DWORD),
193                ("dwDestUnreachs", DWORD),
194                ("dwTimeExcds", DWORD),
195                ("dwParmProbs", DWORD),
196                ("dwSrcQuenchs", DWORD),
197                ("dwRedirects", DWORD),
198                ("dwEchos", DWORD),
199                ("dwEchoReps", DWORD),
200                ("dwTimestamps", DWORD),
201                ("dwTimestampReps", DWORD),
202                ("dwAddrMasks", DWORD),
203                ("dwAddrMaskReps", DWORD)]
204
205
206class MIBICMPINFO(Structure):
207    _fields_ = [("icmpInStats", MIBICMPSTATS),
208                ("icmpOutStats", MIBICMPSTATS)]
209
210
211class MIB_ICMP(Structure):
212    _fields_ = [("stats", MIBICMPINFO)]
213
214
215PMIB_ICMP = POINTER(MIB_ICMP)
216
217# Func
218
219_GetIcmpStatistics = WINFUNCTYPE(ULONG, PMIB_ICMP)(
220    ('GetIcmpStatistics', iphlpapi))
221
222
223def GetIcmpStatistics():
224    """Return all Windows ICMP stats from iphlpapi"""
225    statistics = MIB_ICMP()
226    _GetIcmpStatistics(byref(statistics))
227    results = _struct_to_dict(statistics)
228    del(statistics)
229    return results
230
231##############################
232##### Adapters Addresses #####
233##############################
234
235
236# Our GetAdaptersAddresses implementation is inspired by
237# @sphaero 's gist: https://gist.github.com/sphaero/f9da6ebb9a7a6f679157
238# published under a MPL 2.0 License (GPLv2 compatible)
239
240# from iptypes.h
241MAX_ADAPTER_ADDRESS_LENGTH = 8
242MAX_DHCPV6_DUID_LENGTH = 130
243
244GAA_FLAG_INCLUDE_PREFIX = 0x0010
245GAA_FLAG_INCLUDE_ALL_INTERFACES = 0x0100
246# for now, just use void * for pointers to unused structures
247PIP_ADAPTER_WINS_SERVER_ADDRESS_LH = VOID
248PIP_ADAPTER_GATEWAY_ADDRESS_LH = VOID
249PIP_ADAPTER_DNS_SUFFIX = VOID
250
251IF_OPER_STATUS = UINT
252IF_LUID = UINT64
253
254NET_IF_COMPARTMENT_ID = UINT32
255GUID = BYTE * 16
256NET_IF_NETWORK_GUID = GUID
257NET_IF_CONNECTION_TYPE = UINT  # enum
258TUNNEL_TYPE = UINT  # enum
259
260
261class SOCKET_ADDRESS(ctypes.Structure):
262    _fields_ = [('address', POINTER(SOCKADDR_INET)),
263                ('length', INT)]
264
265
266class _IP_ADAPTER_ADDRESSES_METRIC(Structure):
267    _fields_ = [('length', ULONG),
268                ('interface_index', DWORD)]
269
270
271class IP_ADAPTER_UNICAST_ADDRESS(Structure):
272    pass
273
274
275PIP_ADAPTER_UNICAST_ADDRESS = POINTER(IP_ADAPTER_UNICAST_ADDRESS)
276if WINDOWS_XP:
277    IP_ADAPTER_UNICAST_ADDRESS._fields_ = [
278        ("length", ULONG),
279        ("flags", DWORD),
280        ("next", PIP_ADAPTER_UNICAST_ADDRESS),
281        ("address", SOCKET_ADDRESS),
282        ("prefix_origin", INT),
283        ("suffix_origin", INT),
284        ("dad_state", INT),
285        ("valid_lifetime", ULONG),
286        ("preferred_lifetime", ULONG),
287        ("lease_lifetime", ULONG),
288    ]
289else:
290    IP_ADAPTER_UNICAST_ADDRESS._fields_ = [
291        ("length", ULONG),
292        ("flags", DWORD),
293        ("next", PIP_ADAPTER_UNICAST_ADDRESS),
294        ("address", SOCKET_ADDRESS),
295        ("prefix_origin", INT),
296        ("suffix_origin", INT),
297        ("dad_state", INT),
298        ("valid_lifetime", ULONG),
299        ("preferred_lifetime", ULONG),
300        ("lease_lifetime", ULONG),
301        ("on_link_prefix_length", UBYTE)
302    ]
303
304
305class IP_ADAPTER_ANYCAST_ADDRESS(Structure):
306    pass
307
308
309PIP_ADAPTER_ANYCAST_ADDRESS = POINTER(IP_ADAPTER_ANYCAST_ADDRESS)
310IP_ADAPTER_ANYCAST_ADDRESS._fields_ = [
311    ("length", ULONG),
312    ("flags", DWORD),
313    ("next", PIP_ADAPTER_ANYCAST_ADDRESS),
314    ("address", SOCKET_ADDRESS),
315]
316
317
318class IP_ADAPTER_MULTICAST_ADDRESS(Structure):
319    pass
320
321
322PIP_ADAPTER_MULTICAST_ADDRESS = POINTER(IP_ADAPTER_MULTICAST_ADDRESS)
323IP_ADAPTER_MULTICAST_ADDRESS._fields_ = [
324    ("length", ULONG),
325    ("flags", DWORD),
326    ("next", PIP_ADAPTER_MULTICAST_ADDRESS),
327    ("address", SOCKET_ADDRESS),
328]
329
330
331class IP_ADAPTER_DNS_SERVER_ADDRESS(Structure):
332    pass
333
334
335PIP_ADAPTER_DNS_SERVER_ADDRESS = POINTER(IP_ADAPTER_DNS_SERVER_ADDRESS)
336IP_ADAPTER_DNS_SERVER_ADDRESS._fields_ = [
337    ("length", ULONG),
338    ("flags", DWORD),
339    ("next", PIP_ADAPTER_DNS_SERVER_ADDRESS),
340    ("address", SOCKET_ADDRESS),
341]
342
343
344class IP_ADAPTER_PREFIX(Structure):
345    pass
346
347
348PIP_ADAPTER_PREFIX = ctypes.POINTER(IP_ADAPTER_PREFIX)
349IP_ADAPTER_PREFIX._fields_ = [
350    ("alignment", ULONGLONG),
351    ("next", PIP_ADAPTER_PREFIX),
352    ("address", SOCKET_ADDRESS),
353    ("prefix_length", ULONG)
354]
355
356
357class IP_ADAPTER_ADDRESSES(Structure):
358    pass
359
360
361LP_IP_ADAPTER_ADDRESSES = POINTER(IP_ADAPTER_ADDRESSES)
362
363if WINDOWS_XP:
364    IP_ADAPTER_ADDRESSES._fields_ = [
365        ('length', ULONG),
366        ('interface_index', DWORD),
367        ('next', LP_IP_ADAPTER_ADDRESSES),
368        ('adapter_name', ctypes.c_char_p),
369        ('first_unicast_address', PIP_ADAPTER_UNICAST_ADDRESS),
370        ('first_anycast_address', PIP_ADAPTER_ANYCAST_ADDRESS),
371        ('first_multicast_address', PIP_ADAPTER_MULTICAST_ADDRESS),
372        ('first_dns_server_address', PIP_ADAPTER_DNS_SERVER_ADDRESS),
373        ('dns_suffix', ctypes.c_wchar_p),
374        ('description', ctypes.c_wchar_p),
375        ('friendly_name', ctypes.c_wchar_p),
376        ('physical_address', BYTE * MAX_ADAPTER_ADDRESS_LENGTH),
377        ('physical_address_length', ULONG),
378        ('flags', ULONG),
379        ('mtu', ULONG),
380        ('interface_type', DWORD),
381        ('oper_status', IF_OPER_STATUS),
382        ('ipv6_interface_index', DWORD),
383        ('zone_indices', ULONG * 16),
384        ('first_prefix', PIP_ADAPTER_PREFIX),
385    ]
386else:
387    IP_ADAPTER_ADDRESSES._fields_ = [
388        ('length', ULONG),
389        ('interface_index', DWORD),
390        ('next', LP_IP_ADAPTER_ADDRESSES),
391        ('adapter_name', ctypes.c_char_p),
392        ('first_unicast_address', PIP_ADAPTER_UNICAST_ADDRESS),
393        ('first_anycast_address', PIP_ADAPTER_ANYCAST_ADDRESS),
394        ('first_multicast_address', PIP_ADAPTER_MULTICAST_ADDRESS),
395        ('first_dns_server_address', PIP_ADAPTER_DNS_SERVER_ADDRESS),
396        ('dns_suffix', ctypes.c_wchar_p),
397        ('description', ctypes.c_wchar_p),
398        ('friendly_name', ctypes.c_wchar_p),
399        ('physical_address', BYTE * MAX_ADAPTER_ADDRESS_LENGTH),
400        ('physical_address_length', ULONG),
401        ('flags', ULONG),
402        ('mtu', ULONG),
403        ('interface_type', DWORD),
404        ('oper_status', IF_OPER_STATUS),
405        ('ipv6_interface_index', DWORD),
406        ('zone_indices', ULONG * 16),
407        ('first_prefix', PIP_ADAPTER_PREFIX),
408        ('transmit_link_speed', ULONGLONG),
409        ('receive_link_speed', ULONGLONG),
410        ('first_wins_server_address', PIP_ADAPTER_WINS_SERVER_ADDRESS_LH),
411        ('first_gateway_address', PIP_ADAPTER_GATEWAY_ADDRESS_LH),
412        ('ipv4_metric', ULONG),
413        ('ipv6_metric', ULONG),
414        ('luid', IF_LUID),
415        ('dhcpv4_server', SOCKET_ADDRESS),
416        ('compartment_id', NET_IF_COMPARTMENT_ID),
417        ('network_guid', NET_IF_NETWORK_GUID),
418        ('connection_type', NET_IF_CONNECTION_TYPE),
419        ('tunnel_type', TUNNEL_TYPE),
420        ('dhcpv6_server', SOCKET_ADDRESS),
421        ('dhcpv6_client_duid', BYTE * MAX_DHCPV6_DUID_LENGTH),
422        ('dhcpv6_client_duid_length', ULONG),
423        ('dhcpv6_iaid', ULONG),
424        ('first_dns_suffix', PIP_ADAPTER_DNS_SUFFIX)]
425
426# Func
427
428_GetAdaptersAddresses = WINFUNCTYPE(ULONG, ULONG, ULONG,
429                                    POINTER(VOID),
430                                    LP_IP_ADAPTER_ADDRESSES,
431                                    POINTER(ULONG))(
432                                        ('GetAdaptersAddresses', iphlpapi))
433
434
435def GetAdaptersAddresses(AF=AF_UNSPEC):
436    """Return all Windows Adapters addresses from iphlpapi"""
437    # We get the size first
438    size = ULONG()
439    flags = ULONG(GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_INCLUDE_ALL_INTERFACES)
440    res = _GetAdaptersAddresses(AF, flags,
441                                None, None,
442                                byref(size))
443    if res != 0x6f:  # BUFFER OVERFLOW -> populate size
444        raise RuntimeError("Error getting structure length (%d)" % res)
445    # Now let's build our buffer
446    pointer_type = POINTER(IP_ADAPTER_ADDRESSES)
447    buffer = create_string_buffer(size.value)
448    AdapterAddresses = ctypes.cast(buffer, pointer_type)
449    # And call GetAdaptersAddresses
450    res = _GetAdaptersAddresses(AF, flags,
451                                None, AdapterAddresses,
452                                byref(size))
453    if res != NO_ERROR:
454        raise RuntimeError("Error retrieving table (%d)" % res)
455    results = _resolve_list(AdapterAddresses)
456    del(AdapterAddresses)
457    return results
458
459##############################
460####### Routing tables #######
461##############################
462
463### V1 ###
464
465
466class MIB_IPFORWARDROW(Structure):
467    _fields_ = [('ForwardDest', DWORD),
468                ('ForwardMask', DWORD),
469                ('ForwardPolicy', DWORD),
470                ('ForwardNextHop', DWORD),
471                ('ForwardIfIndex', DWORD),
472                ('ForwardType', DWORD),
473                ('ForwardProto', DWORD),
474                ('ForwardAge', DWORD),
475                ('ForwardNextHopAS', DWORD),
476                ('ForwardMetric1', DWORD),
477                ('ForwardMetric2', DWORD),
478                ('ForwardMetric3', DWORD),
479                ('ForwardMetric4', DWORD),
480                ('ForwardMetric5', DWORD)]
481
482
483class MIB_IPFORWARDTABLE(Structure):
484    _fields_ = [('NumEntries', DWORD),
485                ('Table', MIB_IPFORWARDROW * ANY_SIZE)]
486
487
488PMIB_IPFORWARDTABLE = POINTER(MIB_IPFORWARDTABLE)
489
490# Func
491
492_GetIpForwardTable = WINFUNCTYPE(DWORD,
493                                 PMIB_IPFORWARDTABLE, POINTER(ULONG), BOOL)(
494                                     ('GetIpForwardTable', iphlpapi))
495
496
497def GetIpForwardTable():
498    """Return all Windows routes (IPv4 only) from iphlpapi"""
499    # We get the size first
500    size = ULONG()
501    res = _GetIpForwardTable(None, byref(size), False)
502    if res != 0x7a:  # ERROR_INSUFFICIENT_BUFFER -> populate size
503        raise RuntimeError("Error getting structure length (%d)" % res)
504    # Now let's build our buffer
505    pointer_type = PMIB_IPFORWARDTABLE
506    buffer = create_string_buffer(size.value)
507    pIpForwardTable = ctypes.cast(buffer, pointer_type)
508    # And call GetAdaptersAddresses
509    res = _GetIpForwardTable(pIpForwardTable, byref(size), True)
510    if res != NO_ERROR:
511        raise RuntimeError("Error retrieving table (%d)" % res)
512    results = []
513    for i in range(pIpForwardTable.contents.NumEntries):
514        results.append(_struct_to_dict(pIpForwardTable.contents.Table[i]))
515    del(pIpForwardTable)
516    return results
517
518### V2 ###
519
520
521NET_IFINDEX = ULONG
522NL_ROUTE_PROTOCOL = INT
523NL_ROUTE_ORIGIN = INT
524
525
526class NET_LUID(Structure):
527    _fields_ = [("Value", ULONGLONG)]
528
529
530class IP_ADDRESS_PREFIX(Structure):
531    _fields_ = [("Prefix", SOCKADDR_INET),
532                ("PrefixLength", UINT8)]
533
534
535class MIB_IPFORWARD_ROW2(Structure):
536    _fields_ = [("InterfaceLuid", NET_LUID),
537                ("InterfaceIndex", NET_IFINDEX),
538                ("DestinationPrefix", IP_ADDRESS_PREFIX),
539                ("NextHop", SOCKADDR_INET),
540                ("SitePrefixLength", UCHAR),
541                ("ValidLifetime", ULONG),
542                ("PreferredLifetime", ULONG),
543                ("Metric", ULONG),
544                ("Protocol", NL_ROUTE_PROTOCOL),
545                ("Loopback", BOOLEAN),
546                ("AutoconfigureAddress", BOOLEAN),
547                ("Publish", BOOLEAN),
548                ("Immortal", BOOLEAN),
549                ("Age", ULONG),
550                ("Origin", NL_ROUTE_ORIGIN)]
551
552
553class MIB_IPFORWARD_TABLE2(Structure):
554    _fields_ = [("NumEntries", ULONG),
555                ("Table", MIB_IPFORWARD_ROW2 * ANY_SIZE)]
556
557
558PMIB_IPFORWARD_TABLE2 = POINTER(MIB_IPFORWARD_TABLE2)
559
560# Func
561
562if not WINDOWS_XP:
563    # GetIpForwardTable2 does not exist under Windows XP
564    _GetIpForwardTable2 = WINFUNCTYPE(
565        ULONG, USHORT,
566        POINTER(PMIB_IPFORWARD_TABLE2))(
567            ('GetIpForwardTable2', iphlpapi)
568    )
569    _FreeMibTable = WINFUNCTYPE(None, PMIB_IPFORWARD_TABLE2)(
570        ('FreeMibTable', iphlpapi)
571    )
572
573
574def GetIpForwardTable2(AF=AF_UNSPEC):
575    """Return all Windows routes (IPv4/IPv6) from iphlpapi"""
576    if WINDOWS_XP:
577        raise OSError("Not available on Windows XP !")
578    table = PMIB_IPFORWARD_TABLE2()
579    res = _GetIpForwardTable2(AF, byref(table))
580    if res != NO_ERROR:
581        raise RuntimeError("Error retrieving table (%d)" % res)
582    results = []
583    for i in range(table.contents.NumEntries):
584        results.append(_struct_to_dict(table.contents.Table[i]))
585    _FreeMibTable(table)
586    return results
587