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