1. OllyDbg 2.x Plugin Writing - Creating the OLLYDBG.LIB file

    While the current version of OllyDbg (2.01e as of writing) is still in development, it now has full plugin API support.
    Compiling a plugin requires linking with an ollydbg.lib file describing the imports defined in the plugin.h header file.
    For Borland compilers this is a straight forward procedure and the LIB file should be fully compatible with the plugin declarations.
    For MSVC compilers (Visual Studio, Visual C++) the situation is a bit more complicated.
    I will describe what worked for me and how to go about producing an MSVC compatible ollydbg.lib file.

    For MSVC you first need to create a DEF file in the proper format and then create the LIB file with the LIB /DEF: command. In OllyDbg 2.x the plugin exports are a mixture of _cdecl and _stdcall.

    Cdecl functions can be declared in the DEF file as FunctionName @Ordinal.

    Stdcall functions must be declared in the DEF file in the decorated format FunctionName@<number of parameter bytes> @Ordinal.

    The attachment contains an MSVC suitable ollydbg.lib file (tested in VC6++ and VS 2010), the DEF file, and the python script I wrote to extract the number of parameter bytes from each _stdcall function in plugin.h and create the required DEF file format. Since the plugin.h function declarations may change in future versions as OllyDbg 2.x is developed, the python script may come in useful to create an updated ollydbg.lib file.

    Stop reading now if you don't need the details.

    For Borland compilers one can simply use IMPLIB.exe to create a LIB file in the following manner, and you're ready to go.

    implib ollydbg.lib ollydbg.exe

    For MSVC, well, here's the MS definition of the steps:

    Given a .DLL (in our case OLLYDBG.EXE) with functions exported via a C interface, you can create an import library by following these steps:

    1. Use DUMPBIN /EXPORTS <.DLL file name> to obtain the list of exported symbols for the .DLL in question. The symbols appear in the "name" column of the table whose headings are "ordinal hint name."
    2. Create a .DEF file that contains an EXPORTS section with the names of the functions listed in the "name" column of the DUMPBIN output.
    3. For _cdecl functions, the symbol appears just as it would when used in the calling program. Just place this symbol in the EXPORTS section of the .DEF file.
    4. Use LIB /DEF:<.DEF file name> to generate the import library and exports file. The base name of the import library will be the base name of the .DEF file. Use /OUT: to control the output library name.


    For OllyDbg 1.x all export functions were in _cdecl format and the above instructions were suitable. Modified PDK's and explanations were provided in the following links:


    OllyDbg 2.x now has a mixture of _stdcall and _cdecl exported functions in plugin.h. If you try to link with an ollydbg.lib produced in the previous way you'll get compiler errors such as this when trying to compile the bookmark.c plugin example:

    error LNK2019: unresolved external symbol _Commentaddress@16 referenced in function _Bookmarkdraw

    I found that is was required to declare the stdcall function in the DEF file as follows:

    Commentaddress@16 @278

    Here is a useful article which sums up some of the differences in calling conventions for _stdcall and _cdecl for various compilers:


    I guess the rest of the details are in the python script below. For those who are interested in that kind of thing you might read it easier from the zip file copy with syntax highlighting.

    There is one further modification of plugin.h for MSVC, two of the exported functions (Heapsort/Heapsortex) use the Borland _USERENTRY type in their parameter list which generates a compiler error. The solution is provided by the Borland defs.h file:

    _USERENTRY Specifies the calling convention the RTL expects user
    compiled functions to use (for callbacks)

    #define _USERENTRY __cdecl
    You simply need to add #define _USERENTRY __cdecl to the plugin.h file.

    All that's left is for the RE community to start updating old 1.x version plugins or creating new ones and adding them to the CRCETL here:



    # makedef.py
    # Generates a correct EXPORTS .DEF file for OllyDbg 2.x in MSVC format in order to
    # create the OLLYDBG.LIB file required for writing OllyDbg plugins
    # Instructions:
    # 1. Create a DUMPBIN.EXE /EXPORTS file for Ollydbg.exe, i.e.:
    #       C:\Program Files\Microsoft Visual Studio 10.0\VC>dumpbin /exports ollydbg.exe > olly_exports.txt
    # 2. Create a DEF file by running this python script on the file, 
    #    making sure the Ollydbg plugin.h file is in the same directory
    #       > python makedef.py olly_exports.txt
    # 3. Create a LIB file by running LIB.EXE /DEF on the output file from this script (ollydbg.def) 
    #       C:\Program Files\Microsoft Visual Studio 10.0\VC>lib /def:ollydbg.def /OUT:ollydbg.lib
    # 4. Compile your Ollydbg plugin linked to the lib file
    # In OllyDbg 2.x the plugin exports are a mixture of _cdecl and _stdcall. 
    # Cdecl functions can be declared in the DEF file as
    #       FunctionName @Ordinal
    # Stdcall functions must be declared in the DEF file in the decorated format
    #       FunctionName@<number of parameter bytes> @Ordinal
    # There is one further modification of plugin.h for MSVC, two of the exported functions (Heapsort/Heapsortex)
    # use the Borland _USERENTRY type in their parameter list which generates a compiler error.
    # The solution is provided by the Borland defs.h file:
    #   _USERENTRY      Specifies the calling convention the RTL expects user
    #                   compiled functions to use (for callbacks)
    #   #define _USERENTRY __cdecl
    # You simply need to add #define _USERENTRY __cdecl to the plugin.h file.
    import string, re, sys, os
    def main():    
        if len(sys.argv) < 2:
            sys.stderr.write("USAGE: %s <DUMPBIN /EXPORTS output filename>" % (sys.argv[0],))
            return 1
        if not os.path.exists(sys.argv[1]):
            sys.stderr.write("ERROR: File %r was not found!" % (sys.argv[1],))
            return 1        
        if not os.path.exists("plugin.h"):
            sys.stderr.write("ERROR: Ollydbg plugin.h file must be in same directory!")
            return 1 
        # PART 1: 
        # Extract all __stdcall ("stdapi") functions from plugin.h, determine the number of parameters for each,
        #   and create a list of them in the decorated format 
        #       FunctionName@<number of parameter bytes>, i.e. Absolutizepath@4
        #   This list will be merged later with the rest of the undecorated __cdecl functions
        #   and exported variables to create a DEF file
        # Parse plugin.h to isolate STDAPI functions
        # List to hold original complete function declaration string
        list_fx = []
        # List to hold decorated 'function@parameters' export name string for __stdcall functions
        list_decorated = []    
        # Regex pattern to match Type portion of function string for removal, i.e. "stdapi (int)  "     
        # pattern_stdapi = re.compile(r"(?i)^stdapi.+?\)\s*")
        # RegexBuddy is my buddy ;)    
        # (?i)^stdapi.+?\)\s*
        # Options: case insensitive; ^ and $ match at line breaks
        pattern_stdapi = re.compile(r"""
            (?i)                    # Match the remainder of the regex with the options: case insensitive (i) <?i>
            ^                       # Assert position at the beginning of a line (at beginning of the string or after a line break character) <^>
            stdapi                  # Match the characters "stdapi" literally <stdapi>
            .+?                     # Match any single character that is not a line break character <.+?>
                                    #   Between one and unlimited times, as few times as possible, expanding as needed (lazy) <+?>
            \)                      # Match the character ")" literally <\)>
            \s*                     # Match a single character that is a "whitespace character" (spaces, tabs, line breaks, etc.) <\s*>
                                    #   Between zero
    Attached Thumbnails Attached Files
  2. Using nt!_MiSystemVaType to navigate dynamic kernel address space in Windows7

    32-bit Windows Vista and later use a feature known as Dynamic Kernel Address Space. To quote from a technical article - the Memory Manager dynamically manages the kernel's address space, allocating and deallocating space to various uses to meet the needs of the system. As a result, the amount of virtual memory being used for internal components, device drivers, the file system cache, kernel stacks, system PTE's, per-session code data structures as well as paged and nonpaged pool memory will grow and shrink based on system activity.

    The key to keeping track of all this dynamic memory lies in the unexported pointer nt!_MiSystemVaType, a mapped array of byte values that describes both the type of memory allocation, and by virtue of the indexed position within the array, the location and size of the memory block. Each time there is a new memory allocation, the MiSystemVaType array is updated.

    In this code project I will try to show how to use MiSystemVaType to navigate the dynamic kernel address space to get a complete mapping of the various allocation types. In addition, I'll give an example of how to use it to find and identify loaded drivers, as well as discuss how it might be used to conduct efficient memory pool searches.

    Here are a few background articles on the subject at hand:

    Understanding the kernel address space on 32-bit Windows Vista

    Inside the Windows Vista Kernel: Part 2

    Windows® Internals, Fifth Edition
    9.5.7 Dynamic System Virtual Address Space Management


    nt!_MiSystemVaType is a pointer to an array of byte values of enum type MI_SYSTEM_VA_TYPE. Each byte in the array describes a single Large Page and maps, in sequential order, the entire upper 2GB of logical address space from 0x80000000 (MmSystemRangeStart) - 0xFFFFFFFF. The size of the byte array is either 0x400 when PAE is enabled, where the default size of a Large Page is 2MB, or 0x200 in non-PAE mode, which uses a Large Page size of 4MB.

    The enum type values can be listed with WinDbg/LiveKd:

    kd> dt nt!_MI_SYSTEM_VA_TYPE
       MiVaUnused = 0n0
       MiVaSessionSpace = 0n1
       MiVaProcessSpace = 0n2
       MiVaBootLoaded = 0n3
       MiVaPfnDatabase = 0n4
       MiVaNonPagedPool = 0n5
       MiVaPagedPool = 0n6
       MiVaSpecialPoolPaged = 0n7
       MiVaSystemCache = 0n8
       MiVaSystemPtes = 0n9
       MiVaHal = 0n10
       MiVaSessionGlobalSpace = 0n11
       MiVaDriverImages = 0n12
       MiVaSpecialPoolNonPaged = 0n13
       MiVaMaximumType = 0n14

    PAE mode:

    The Physical Address Extension (PAE) processor feature enables use of 64-bit page table entries for physical addresses that are wider than 32 bits. If PAE is enabled, the size of page table entries (PTEs) are increased from 32 to 64 bits (4 to 8 bytes). Consequently, the size of a Large Page is reduced from 4MB to 2MB in PAE mode. One can determine the size of the PTE data structure, nt!_MMPTE, (and hence if PAE is enabled or not) with the command:

    kd> dt -v nt!_MMPTE
    struct _MMPTE, 1 elements, 0x8 bytes
    To determine if PAE is enabled programmatically we can read the ProcessorFeatures field of KUSER_SHARED_DATA, a shared memory structure mapped to all processes and located at 0x7FFE0000 in usermode. This is equivalent to what the IsProcessorFeaturePresent API does.

    KUSER_SHARED_DATA is duplicated at 0xFFDF0000 in kernelmode. Fortunately ntddk.h gives us a handy macro with which to work with it. The snippet below will give us (by inference) the size of nt!_MMPTE, from which we can derive the size of a large page and the size of the MiSystemVaType array.

    PHP Code:
    #define KI_USER_SHARED_DATA         0xffdf0000
    #define SharedUserData  ((KUSER_SHARED_DATA * const)   KI_USER_SHARED_DATA)

    // Determine if PAE is enabled from  KI_USER_SHARED_DATA.ProcessorFeatures

    DbgPrint ("PAE enabled\n");

    sizeof_MMPTE 8;

    } else {

    DbgPrint ("PAE not enabled\n");

    sizeof_MMPTE 4;

    In the registry the PAE status can be read from
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PhysicalAddressExtension

    Here is a summary of the differences between PAE and non-PAE mode which are relevant to our code:

    (PAGE_SIZE = 0x1000)
    PAE enabled:
        nt kernel version:
             ntkrnlpa.exe: 1 CPU, PAE
             ntkpamp.exe:  n CPU, SMP, PAE
        sizeof_MMPTE = 8
        LARGE_PAGE_SIZE = PAGE_SIZE * PAGE_SIZE / sizeof_MMPTE = 0x200000  (2MB)
        sizeof MiSystemVaType array = (0xFFFFFFFF+1 -  (ULONG)MmSystemRangeStart) / LARGE_PAGE_SIZE = 0x400
        0x400 * 0x200000 = 0x80000000 = (0x80000000 / 1024 /1024 /1024) =  2GB
    PAE disabled:
        nt kernel version:
            ntoskrnl.exe: 1 CPU
            ntkrnlmp.exe: n CPU, SMP
        sizeof_MMPTE = 4
        LARGE_PAGE_SIZE = PAGE_SIZE * PAGE_SIZE / sizeof_MMPTE = 0x400000  (4MB)
        sizeof MiSystemVaType array = (0xFFFFFFFF+1 -  (ULONG)MmSystemRangeStart) / LARGE_PAGE_SIZE = 0x200
        0x200 * 0x400000 = 0x80000000 = 2GB
    PAE is enabled by default in Windows 7, if you wish to test the included code in non-PAE mode use BCDEdit as follows:

    If DEP is enabled, PAE cannot be disabled. Use the following BCDEdit /set commands to disable both DEP and PAE:

    bcdedit /set nx AlwaysOff
    bcdedit /set pae ForceDisable

    To restore:

    bcdedit /set nx Optout (or one of [Optin |OptOut | AlwaysOn])
    bcdedit /set pae ForceEnable


    Finding the unexported pointer nt!_MiSystemVaType:

    We need to programmatically find the offset to nt!_MiSystemVaType. Since this is an unexported pointer we'll have to parse a known kernel function which makes use of the variable. Uh Oh. Production code need not apply . Oh well, this is an RCE forum, right? At least that's better than using a hard-coded value, not as good as using symbols.

    Rather than using a classic byte-pattern search that is often used to find unexported variables, I made use of a clever idea Zairon mentioned to me, that of looking for cross references between code and data sections in order to pick up instances of data variable usage. In essence, derive XREFS similar to IDA.

    I really like Zairon's idea of using XREF analysis over a byte-pattern search method because it's simple, highly adaptable, and is less susceptible to changing byte patterns between different OS kernel versions.

    The function I chose to parse for the offset of MiSystemVaType was the exported MmIsNonPagedSystemAddressValid procedure. The simple algorithm logic I used was: "Scan for the first data XREF to the section called '.data''"

    See the source code for the specific algorithm I implemented, plus a few suggestions for creating a more rigorous algorithm if desired, such as using a length disassembly engine (LDE) to avoid the possibility a false XREF could occur across instructions.

    The simple logic above should be valid for all current 32-bit nt* kernel versions in Windows 7 / Vista / Server 2008. Even better, MmIsNonPagedSystemAddressValid has been deemed to be obsolete and is exported to support existing drivers only, so it's more unlikely to change anytime soon.

    _MmIsNonPagedSystemAddressValid@4 proc
    8B FF                             mov     edi, edi
    55                                push    ebp
    8B EC                             mov     ebp, esp
    53                                push    ebx
    56                                push    esi
    8B 35 18 57 97 82                 mov     esi, ds:_MmSystemRangeStart //  xref to ALMOSTRO
    57                                push    edi
    8B 7D 08                          mov     edi, [ebp+VirtualAddress]
    BB F8 3F 00 00                    mov     ebx, 3FF8h
    3B FE                             cmp     edi, esi
    72 25                             jb      short loc_828F17A8
    8B C6                             mov     eax, esi
    C1 E8 12                          shr     eax, 12h
    8B CF                             mov     ecx, edi
    C1 E9 12                          shr     ecx, 12h
    23 C3                             and     eax, ebx
    23 CB                             and     ecx, ebx
    2B C8                             sub     ecx, eax
    C1 F9 03                          sar     ecx, 3
    8A 81 60 51 95 82                 mov     al, _MiSystemVaType[ecx]  // xref to .data

    Making sense of MiSystemVaType:

    Now that we've got the pointer to nt!_MiSystemVaType, what do we do with it? The first obvious thing is just to list everything out.

    Let's take a look at the first 0x10 bytes of the MiSystemVaType array. Each byte maps a logical address block of LARGE_PAGE_SIZE, beginning at ...
    Attached Thumbnails Attached Files
  3. IceProbe - SoftIce Command Tracer

    IceProbe is a utility that allows live tracing and analysis of SoftIce commands using the full capability of SoftIce itself. It is a tool strictly for code exploration, designed to be able to trace running Softice code in order to augment IDA analysis. It is debugging a debugger, in order to answer the question "How does Softice work?"

    There is much that can be learned about system internals by studying Softice code. This utility will give a live hands-on method of tracing and exploring the code for the first time. It can also act as a GUI front-end for Softice, as bizarre as that might sound.


    Any SoftIce command typed into the command line window is stored in a global string buffer. The command string consists of the command name and any arguments. The buffer is passed to the individual function where it is parsed, and the command is executed.

    We can selectively replace instances of this global buffer pointer with one of our own and call Softice commands directly from a GUI interface. An (optional) embedded breakpoint which will pop-up Softice is written into our driver code immediately before calling the command, which allows us to start tracing the Softice command.

    While live tracing you have full use of all other Softice commands at your disposal, including the ability to set breakpoints in Softice code itself. There is an additional modification which will force the "Idt" command to expose the addresses of the Softice IDT hooks so you can also locate and analyse those various handlers as well.


    Iceprobe is simple to use, select Initialize/Reinitialize from the menu and the driver will return a listview listing of all the Softice commands and their addresses. A log window will monitor the driver. Double click on one of the entries and you will be presented with a dialog box to add any usual arguments to the command. When you select OK, Softice will popup at the start of the command, and you're ready to start tracing with F8.


    Disable Manual Tracing Mode
    We embed an INT 3 in our code and programatically enable "I3HERE DRV" in order to make Softice popup at the start of each command. Set this option if you don't want Softice to popup. The command will still be executed and output to the Softice window as normal.

    Make "Idt" show real addresses
    Expose the addresses of the Softice IDT hooks in the listing from the "Idt" command.

    Disable extra Softice self address space checks
    These are somewhat experimental patching of locations where Softice tests if an offset is within its own address space. Specifically, they occur in the "Search" command, in a portion of breakpoint handling code where MSR LastBranch and MSR LastException information is printed, and in the Int0D handler. You may or may not see any effect.

    Include Undocumented Commands
    There is only one command here, BPTE - Breakpoint on Thread Execution was its probable purpose. Code exists to be traced, but the command appears non-functional and was never documented. If selected, the BPTE command will be added to the listview where you can run it with test arguments.

    Increase Recursive Disassembly Level (Calls nested 4 deep)
    We must find every occurence of the Softice global command buffer used in each command, in order to replace them with a pointer to our own buffer. A recursive disassembly is therefore needed in order to trace through all nested subcalls within a command.

    A simple recursive method is used - trace each call until a RET/RETN is reached. It was found that this was sufficient with a default value of 3 nested levels of disassembly to find all instances of the global buffer for each command. A value of 4 will find further instances, but most seem to be false positives and not part of the command execution path. This is due to how Softice code is laid out (code chunks, use of jmps, etc), and the simplified method of recursive disassembly.

    Output Recursive Call Pattern for Xref with IDA (DbgPrint) - Shows the nested recursive disassembly of all Calls and SubCalls for a command, as determined by the Increase Recursive Disassembly Level option. The pattern can be matched to what you find during the IDA analysis. It makes it easier to keep track of where you are while jumping back and forth between IDA and the Softice/Softice tracing of a command.

    Output Developmental Notes (DbgPrint)
    Prints a bunch of output about the Softice driver and internal offsets, mostly used during development.

    All these options can be "toggled" on or off by setting them and selecting Initialize/Reinitialize from the menu again.

    IDA Analysis:

    This tool is meant to work side by side with an IDA analysis of the ntice.sys driver. Iceprobe should run without problem with Driver Studio 2.7, 3.1 or 3.2. It is designed to work with the final official DS3.2.1 patch version of the Softice driver which was publically available on their ftp site. This offical patch is available here:


    This would be incomplete without an explanation of how to set up IDA properly, which fortunately I discussed previously:

    Setting up IDA for analysing Softice functions

    Briefly, Softice keeps its command names and offsets in indexed tables. The very first step is to run the following idc script. The CmdTable offsets are for the DS3.2.1 patch version. If you happen to be using a different version change the offsets accordingly, the above thread describes how to find them.

    PHP Code:
    #include <idc.idc>

    // with idc command
    // CmdTable(0x15EAAD, 0x15E7AD);

    static CmdTable(NameTableCommandTable) {
    auto ij;
    auto CmdIndexCmdName ;
        while ( 
    Word(i) != 0) {
           while ( 
    Byte(j) != 0j++;
    CmdName "c_" substr(Name(i),1,j-i);      
    CmdIndex Byte(j) * 4;
    MakeNameDword(CommandTable+CmdIndex), CmdName);

    Your IDA disassembly will now identify all of the Softice commands by name. I would then strongly suggest to look at the IDB analysis and Softice headers produced by The Owl while developing Icedump, and use them to start naming some of the internal variables already defined. The article by +Spath is old but indispensable as well.

    NTICE and WINICE IDB Files by the_owl (IDB)

    SOFTICE INTERNALS revision 2 by +Spath

    Now you can start filling in the blanks in your IDA analysis with live tracing of any command using Iceprobe. The ideal situation is to have Softice running under VMWare and have IDA on your desktop. Iceprobe is stable, but you ARE live tracing Softice, so running under VMWare, etc. is desirable.

    To further enhance the experience, you can create progressive NMS symbol files of your IDA analysis and have Softice load its own symbol file into itself using its Symbol Loader. Produce the symbol file with Mostek's Ida2Sice


    Any command can be traced, while at the same time being able to issue any other Softice command. However, if you execute the same command as you are tracing it will only rerun it with the same parameters that were initially set in the GUI, since we've overwritten the global buffer for that command with our own pointer.

    For tracing the BPX command, you can set a breakpoint with a double click, or use BPM. You can even trace the HBOOT command and watch your VM reboot! (I put a protection in the GUI so you can't inadvertently click the HBOOT command).

    WTF is this?:

    I wrote this a few years back, partly as a way of tracing Softice code, but mostly as a way of exploring system internals and how Softice made use of various system structures, variables, hardware and registers. Sort of kernel spelunking through the eyes of a ring 0 debugger.

    IceProbe was first integrated as a KDExtension driver to take advantage of the internal Softice disassembly engine available through the WINDBG_EXTENSION_APIS interface. Further Softice internal details can be found in the thread

    Guide to creating a Softice Kernel Debugger Extension (KDExtension)

    This version uses a standalone driver and the disassembler is an integration of a module I created from the Ndisasm NASM disassembler for use in drivers. The disasm module is also available separately here:

    Sysdasm: Full-Text Disassembler DLL Export Module for Kernel Mode

    Full VC6++ source is included for those interested in looking at an old friend with new eyes


    Attached Thumbnails Attached Files
  4. IDC scripting a Win32.Virut variant - Part 2

    This post continues Part 1, which should be read first for reference.

    Some points of interest about the viral code itself:

    • There is a small RDTSC timing check at the start. If the difference between 2 RDTSC calls is greater than 0x100 then it's determined that you're in a VM or single step tracing the code (or have only 4K of memory maybe!). In that case the virus doesn't infect but instead returns to the default address of 0x40101D and only tries to connect to the irc server.

      Doing a little test I found that 2 RDTSC calls would return a difference of between 1000-1500 ticks under VMWare, but only 50 ticks under a real system. Not a sophisticated VM test, but it seems to work.

    • There is also apparently a Redpill type VM check which I found crashes under VMWare.

      :0040323A RedPill:      ; CODE XREF: Main+2
      :0040323A       push    eax
      :0040323B       sidt    qword ptr [esp-2]
      :00403240       pop     eax
      :00403241       mov     eax, [eax+6]    ; invalid pointer, crashes under VMWare
      :00403241                               ; buggy code?!
      :00403244       shl     eax, 10h
      :00403247       jns     short Force_RDTSC_test_to_fail
    • The virus is Win9x/Me compatible. If the OS is Win2K or greater it hooks NtCreateFile / NtOpenFile. If Win9x, it hooks VWIN32_Int21Dispatch and monitors for the MS-DOS LFN (long file name) service 0x716C, which is used for open/create. The Win9x related stuff is "old school" and has been documented before. If interested see:

      The VxDCall backdoor http://vx.netlux.org/lib/vgy06.html
      VIRUS ANALYSIS 2 http://peterszor.com/hps.pdf

    • The virus copies itself to the last section of a file it infects, but the initial seed value for the Xor decryption routine is different for every infected file (uses an RDTSC randomization), showing its polymorphic nature. After copying itself from running memory into the file, the main Decrypt function is called once again, but this time it's encrypting itself in the file, the XOR routine being reversible.

    • After determining the system offsets for the Nt calls it hooks, the virus creates a section, maps itself into it, then immediately continues execution from this high memory mapping address. All further execution is run from this memory mapped image. This makes it a little more difficult when live tracing and comparing addresses to the disassembly, but the jump to high memory makes a great breakpoint when you get deeper into the live analysis.

      .data:004035FE lea eax, dword_403606
      .data:00403604 jmp eax
      .data:00403606 ... ; this is now running from high mem

    • One interesting (though not original) thing the virus does is how it disables Windows File Protection (WFP) so it can infect any file in any directory without being subverted.

      Immediately after beginning execution in the high memory mapped section that I just mentioned, it calls NtAdjustPrivilegesToken with SeDebugPrivilege privilege so it is able to call CreateRemoteThread() in the Winlogon process.

      How to obtain a handle to any process with SeDebugPrivilege

      Using a CreateToolhelp32Snapshot/Process32Next loop, it scans for the 4th process returned, or Winlogon. Then it hooks the 4 Nt calls within the winlogon address context by overwriting the Service Id of the relevant Zw ntdll KiFastSystemCall with a detour to its own hook routine (which is mapped into winlogon memory space).

      Once this is done it calls CreateRemoteThread followed by a short Sleep routine to let the remote thread execute within winlogon context. After the Sleep nap, the CreateToolhelp32Snapshot loop continues and hooks the 4 Nt calls in every other active process.

      The aforementioned CreateRemoteThread winlogon thread does a number of things besides disabling WFP. It sets up the Win9x/Me VxDCall file hook if applicable, writes the irc sites IP address to the Windows hosts file, accesses the registry, creates some Events, creates a couple more threads, one of which maps out ntoskrnl and the KeServiceDescriptorTable, tries to connect to the internet, etc.

      WFP is disabled by patching in a call to ExitThread immediately after a NtWaitForMultipleObjects call in an sfc.os.dll function called SfcWatchProtectedDirectoriesWorkerThread. The function name pretty much describes what it does, it's a worker thread which continually monitors for file changes from a protected dll list. After patching SfcWatchProtectedDirectoriesWorkerThread the virus calls SfcTerminateWatcherThread. (IDA + symbols is a wonderful thing )

      Here is what the function looks like in code:

      // The search pattern in sfc.os.dll
      :00403CE4          Pattern_SFC_OS_DLL:    ; DATA XREF: Disable_WFP+F
      :00403CE4 6A 01       push    1
      :00403CE6 6A 01       push    1
      :00403CE8 FF 33       push    dword ptr [ebx]
      :00403CEA FF 73 04    push    dword ptr [ebx+4]
      :00403CEA          ; -----------------------------------------------
                            // part of call ds:NtWaitForMultipleObjects
      :00403CED FF 15       dw 15FFh   
      :00403CEF Disable_WFP     proc near
      // EAX = base address of sfc.os.dll returned from GetModuleHandleA
      :00403CEF       test    eax, eax
      :00403CF1       jz      short locret_403CE3
      :00403CF3       push    0Bh
      :00403CF8       mov     edx, eax
      :00403CFA       pop     ebx
      :00403CFB       add     edx, [eax+IMAGE_DOS_HEADER.e_lfanew]
      :00403CFE       lea     esi, Pattern_SFC_OS_DLL
                              // SectionTable.PointerToRawData .text section
      :00403D04       mov     edi, [edx+10Ch]
                              // SectionTable.SizeOfRawData .text section
      :00403D0A       mov     ecx, [edx+108h]
      :00403D10       add     edi, eax
      :00403D12       sub     ecx, ebx
      :00403D14 Find_Pattern:           ; CODE XREF: Disable_WFP+2E
      :00403D14       pusha
      :00403D15       mov     ecx, ebx
      :00403D17       repe cmpsb
      :00403D19       popa
      :00403D1A       jz      short loc_403D21
      :00403D1C       inc     edi
      :00403D1D       loop    Find_Pattern
      :00403D1F       jmp     short locret_403CE3
      :00403D21 ; ----------------------------------------------------
      :00403D21 loc_403D21:             ; CODE XREF: Disable_WFP+2B
      :00403D21       add     edi, 0Fh
      :00403D24       push    edi
      :00403D25       mov     edx, esp
      :00403D27       push    ebx
      :00403D28       mov     ecx, esp
      :00403D2A       push    eax
      :00403D2B       push    esp             ; OldProtect
      :00403D2C       push    40h             ; Protect
      :00403D2E       push    ecx             ; RegionSize
      :00403D2F       push    edx             ; BaseAddress
      :00403D30       push    0FFFFFFFFh      ; hProcess
      :00403D32       call    NtProtectVirtualMemory
      :00403D38       add     esp, 0Ch
      :00403D3B       mov     edx, ExitThread
      :00403D41       sub     edx, edi
      :00403D43       sub     edx, 7
      // Patch in ExitThread call immediately after
      // NtWaitForMultipleObjects in sfc.os function
      // SfcWatchProtectedDirectoriesWorkerThread
      // patch in push eax opcodes (6A00) + Call near, relative opcode (E8)
      :00403D46       mov     dword ptr [edi], 0E8006Ah  
      // patch in ExitThread displacement
      :00403D4C       mov     [edi+3], edx
      :00403D4F       retn
      :00403D4F Disable_WFP     endp ;
      Once I figured out what the code was doing I was able to do a search and found that the entire routine for disabling WFP was ripped from here:


    • And finally, and congratulations if you're still reading this, there is a mysterious Int 0x2C interrupt call at the start of the program. This call is the main reason this virus held my interest for so long and is ultimately the genesis of this article. I was hoping a full analysis of the program would point out its purpose. Alas, I'm still as clueless as when I started.

      Therefore I open up to speculation (or reason or knowledge), the true function of this Int2C call, if there is one, to you Gentle Reader (may Asimov forgive me).

      Here is the start of the code once again showing the important bits. I've tried to outline the "normal" path of infection by highlighting in Blue. Under any condition that I can see, Int2C will always return STATUS_NO_EVENT_PAIR, so that particular path of execution is fixed.

      The question is then. is this just quirky code to confuse us, or can an Int2C call have a real purpose in Ring3 under certain conditions?

      :00403200 start           proc near 
      :00403200                 cld
      :00403201                 call    Main
      :00403201 start           endp
      :0040321F popebp_ret:
      :0040321F                 pop     ebp
      :00403220                 retn
      :00403221 Force_RDTSC_test_to_fail:
      :00403221                 push    ebp
      :00403222                 mov     eax, 8000h
      :00403227                 xor     ecx, ecx
      :00403229                 jmp     short loc_403255
  5. IDC scripting a Win32.Virut variant - Part 1

    I had started live tracing this piece of malware when I realized it was a prime candidate for some IDA idc scripting. There are a few things about the program which makes static analysis difficult.

    1. It's encrypted. It's a rather simple encryption however which is easily scripted out.
    2. The code is primarily executed in the .data section and there are many inline character strings and non-standard code instructions which prevent IDA from getting an accurate disassembly.
    3. Variable pointers and import calls are referenced as EBP offsets, so IDA can't recognize absolute addresses to create the proper Xrefs, autogeneration and all the other wonderful analysis it normally performs.
    4. Imports are determined dynamically through GetProcAddress, so until we define them IDA can't recognize them.

    Let's address each of these problems through a bit of idc scripting and static analysis to supplement our live tracing. In Part 2 I'll mention a few points about the viral code itself.

    I've included several files in the attachment, the idc scripts, a header file prototyping some functions and structures not included in the internal IDA definitions. As well there is as an IDB file in IDA 4.9 freeware version format which is fully commented with all functions and variables defined (or at least named, this is meant to be a "working" disassembly for further analysis, not necessarily a definitive treatise). The IDB file can be opened in any IDA version 4.9 and above.

    Also included is of course the virus, or else what fun would this be? The win32_virut.exe file has been renamed with a .VXE extension and zip password protected with the password malware. It is quite an infectious file, but it readily detects a normal virtual machine sandbox and won't infect under those conditions. You actually have to force the code to decrypt its payload under a VM. Also, the remote site it tries to connect to has been closed for Terms Of Agreement violations (ya think?), so no live connection is ever made.

    You may find it easier to read the idc scripts by downloading the originals or by reading this post in the Blogs Forum (follow the link under 'Post or View Comments' at the bottom of this blog).

    A brief description of the virus family from

    This virus is a polymorphic, memory-resident file-infector, with backdoor behaviour. Once executed, it injects itself into WINLOGON, creates a new thread in that process, and passes the execution control to the host file.

    It also hooks the following functions in each running process (in NTDLL module):
    NtCreateFile, NtOpenFile, NtCreateProcess, NtCreateProcessEx
    so that every time an infected process calls one of these functions, the execution is passed to the virus, which infects the accessed file, and then returns the control to the original function.

    It infects EXE and SCR files, using different infection techniques:
    Appending to the last section of the victim, and setting the Entry Point directly to the viral code. (our variant)

    The virus is able to avoid emulators and virtual machines. To ensure there's only one instance of it running in the system, it creates an event with one of the following names:
    VT_3, VT_4, VevT, Vx_4

    It tries to connect to some IRC server, and join a certain channel. Once it joins the channel, it waits for commands that instruct it to download several files from Internet, and then execute them. The IRC server can be:
    proxim.ntkrnlpa.info (our variant, site no longer active)
    Much of what is written above can be figured out by live tracing the malware and eventually letting it infect our sandbox. But let's see what damage we can do to it before it does damage to us..

    Step 1: Decrypt

    Here is the Entry Point of the virus, which is in the .data section:
    :00403200                 cld
    :00403201                 call    loc_40322E
    :00403206                 push    ebx
    Take note that the return address of the Call pushed onto the stack will be 0x403206. Trace into the Call and after a bit of preliminary code we reach here:

    :00403257  mov     ebp, [esp+4]
        // call return of 0x403206 placed in ebp
    :0040325B  sub     dword ptr [esp+4], 21E9h
        // new return address of (0x403206 - 0x21E9) = 0x40101D placed on stack
    :0040326A  sub     ebp, 301006h
    // ebp offset becomes (0x403206 - 0x301006) = 0x102200
    :00403270  lea     eax, [ebp+301082h]
    // eax = (0x102200 + 0x301082) = 0x403282
    // this is the starting address of the encrypted code
    :00403276  mov     dx, [eax-65h]
    // word pointer at 0x40321D is a decryption seed value:  db 8Eh, 0C8h
    :0040327D  call    sub_403206      // Decryption routine
    // the code from here on down is all encrypted
    :00403282  db      65h
    :00403282  enter   0BDDh, 0C1h
    :00403287  push    ss
    The fact that none of the code from address 0x403282 onwards doesn't make much sense indicates that Call sub_403206 is a decryption routine. Let's take a look at that call:

    :00403206 Decrypt         proc near               ; CODE XREF: :0040327D
    :00403206                 push    ebx
    :00403207                 mov     ecx, 0DA5h
    :0040320C                 mov     ebx, edx
    :0040320E loc_40320E:                             ; CODE XREF: Decrypt+13
    :0040320E                 xor     [eax], dx
    :00403211                 lea     eax, [eax+2]
    :00403214                 xchg    dl, dh
    :00403216                 lea     edx, [ebx+edx]
    :00403219                 loop    loc_40320E
    :0040321B                 pop     ebx
    :0040321C                 retn
    :0040321C Decrypt         endp
    :0040321C ; ---------------------------------------------------------------
    :0040321D Initial_Decrypt_Seed db 8Eh, 0C8h
    :0040321F; ----------------------------------------------------------------
    A simple XOR loop decryption where the xor value is modified on each iteration by the XCHG instruction. ECX is a counter decremented by the LOOP opcode. The initial decryption seed value is the db 8Eh, 0C8h we discovered above.

    Armed with this small bit of analysis we can create the following idc script for decrypting.

    PHP Code:
    #include <idc.idc>
    // Step 1: idc to decrypt section between .data:0x403282 and .data:0x404DCC    
    // performs the equivalent asm function (xchg dl, dh)
    #define    bswap16(x)                    \
    ((((x) & 0xff00) >> 8) |        \
    x) & 0x00ff) << 8))
    auto startdecryptsizeenddecryptseedeadecryptwordx;
    // starting values determined from decrypt function
    startdecrypt 0x403282;
    size 0x0DA5 2;                          // word size replacement
    enddecrypt = (startdecrypt size);         // = 0x404DCC
    seed 0xC88E;
    ea startdecrypt;
    decryptword seed;
    Message("\nDecrypting... \n");
        while (
    ea enddecrypt)
    // (xor [eax], dx)
    Word(ea);                               // fetch the word
    = (decryptword);                      // decrypt it
    PatchWord(eax);                           // put it back            
    decryptword bswap16(decryptword);     // xchg dl, dh
    decryptword decryptword seed;       // lea edx, [ebx+edx]
    ea ea 2;    
    // Let's try to get IDA to reanalyze the code
    MakeUnknown (startdecryptsize1);
    AnalyzeArea (startdecryptenddecrypt);
    Message("...Done \n");

    After running this script you MUST go through the decrypted section and manually resolve the embedded string pointers with the IDA A(scii) command and any unrecognized or incorrect disassembly with the C(ode) command. This is a necessary step for the subsequent IDC scripts to work properly!

    You will find a lot of things like the following, which you need to make sure is correctly resolved. By itself IDA won't properly disassemble the code.

    :004032D8 E8 0D     call    loc_4032EA
    :004032D8                   ; --------------------------------------------
    :004032DD 47 65+ aGetlasterror   db 'GetLastError',0 // LPCSTR lpProcName
    :004032EA                   ; --------------------------------------------
    :004032EA        loc_4032EA:       ; CODE XREF: :004032D8
    :004032EA 03 F3     add     esi, ebx
    :004032EC 53        push    ebx     // HMODULE hModule
    :004032ED FF D6     call    esi     // GetProcAddress
    Notice the neat little trick in the above code of how the second parameter of GetProcAddress is automatically pushed onto the stack by effectively being the return address of Call loc_4032EA, which jumps over the string. This type of thing is repeated throughout the program.

    Chances are you won't get every bit of disassembly and ascii string identified correctly the first time through a manual fixup, but after applying ...
    Attached Thumbnails Attached Files