Results 1 to 29 of 29

Thread: [VB6] Loader, shellcode, without runtime...

  1. #1

    Thread Starter
    PowerPoster
    Join Date
    Feb 2015
    Posts
    2,671

    [VB6] Loader, shellcode, without runtime...

    Hello everyone! Today i want to show you the quite interesting things. One day i was investigating the PE (portable executable) file format especially EXE. I decided to create a simple loader of the executable files specially for VB6-compiled applications. This loader should load an VB6-compiled exe from the memory without file. THIS IS JUST FOR THE EXPERIMENTAL PURPOSES IN ORDER TO CHECK POSSIBILITIES OF VB6. Due to that the VB6-compiled applications don't used most of the PE features it was quite simple objective. Most of programers says that a VB6-apllication is linked with the VB6 runtime (MSVBVM), a VB6 application doesn't work without the runtime and the runtime is quite slow. Today i'll prove that it is possible to write an application that absolutely doesn't use runtime (although i was already doing that in the driver). These projects i had written quite a long time ago, but these were in the russian language. I think it could be quite interesting for someone who wants to examine the basic principles of work with the PE files.
    Before we begin i want to say couple words about the projects. These projects were not tested well enough therefore it can cause problems. The loader doesn't support most of the features of PE files therefore some executables may not work.
    So...
    This overview consists three projects:

    1. Compiler - it is the biggest project of all. It creates an installation based on the loader, user files, commands and manifest;
    2. Loader - it is the simple loader that performs commands, unpacks files and runs an executable from memory;
    3. Patcher - it is the small utility that removes the runtime import from an executable file.

    I call an exe that contains the commands, files and executable file the installation. The main idea is to put the information about an installation to the resources of the loader. When the loader is being loaded it reads the information and performs the commands from resources. I decided to use an special storage to save the files and exe, and other storage for commands.
    The first storage stores all the files that will be unpacked, and the main executable that will be launched. The second storage stores the commands that will be passed to the ShellExecuteEx function after unpacking process will have been completed. The loader supports the following wildcards (for path):

    1. <app> - application installed path;
    2. <win> - system windows directory;
    3. <sys> - System32 directory;
    4. <drv> - system drive;
    5. <tmp> - temporary directory;
    6. <dtp> - user desktop.

    Compiler.


    Name:  Compiler.png
Views: 8949
Size:  20.5 KB

    This is the application that forms the installation information and puts it to the loader resource. All the information is stored in a project. You can save and load a project from file. The clsProject class in VB project represents the compiler-project. This compiler has 3 sections: storage, execute, manifest.
    The 'storage' section allows to add the files that will be copied when the application is being launched. Each item in the list has flags: 'replace if exists', 'main executable', 'ignore error'. If you select 'replace if exists' flag a file will be copied even if one exists. The 'main executable' flag can be set only for the single executable file. It means that this file will be launched when all the operations have been performed. The 'ignore error' flag makes ignore any errors respectively. The order in the list corresponds the order of extracting the files except the main executable. The main executable is not extracted and is launched after all the operations. The storage section is represented as clsStorage class in the VB project. This class implements the standard collection of the clsStorageItem objects and adds some additional methods.The MainExecutable property determines the index of main executable file in the storage. When this parameter equal -1 executable file is not presented. The clsStoragaItem class represent the single item in the storage list. It has some properties that determine the behavior of item. This section is helpful if you want to copy files to disk before execution of the application.
    The next section is the 'execute'. This section allows execute any commands. This commands just pass to ShellExecuteEx function. Thus you can register libraries or do something else. Each item in the execution list has two properties: the executable path and parameters. Both the path and the parameters is passed to ShellExecuteEx function. It is worth noting that all the operations is performed synchronously in the order that set in the list. It also has the 'ignore error' flag that prevents appearance any messages if an error occurs. The execute section is represented as two classes: clsExecute and clsExecuteItem. These classes are similar to the storage classes.
    The last section is 'manifest'. It is just the manifest text file that you can add to the final executable. You should check the checkbox 'include manifest' in the 'manifest' tab if you wan to add manifest. It can be helpful for Free-Reg COM components or for visual styles.
    All the classes refer to the project object (clsProject) that manages them. Each class that refers to project can be saved or loaded to the PropertyBag object. When a project is being saved it alternately saves each entity to the property bag, same during loading. It looks like a IPersistStream interface behavior. All the links to the storage items in the project is stored with relative paths (like a VB6 .vbp file) hence you can move project folder without issues. In order to translate from/to relative/absolute path i used PathRelativePathTo and PathCanonicalize functions.
    So... This was basic information about compiler project. Now i want to talk about compilation procedure. As i said all the information about extracting/executing/launching is stored to the loader resources. At first we should define the format of the data. This information is represented in the following structures:
    Code:
    ' // Storage list item
    Private Type BinStorageListItem
        ofstFileName        As Long            ' // Offset of file name
        ofstDestPath        As Long            ' // Offset of file path
        dwSizeOfFile        As Long            ' // Size of file
        ofstBeginOfData     As Long            ' // Offset of beginning data
        dwFlags             As FileFlags       ' // Flags
    End Type
    
    ' // Execute list item
    Private Type BinExecListItem
        ofstFileName        As Long            ' // Offset of file name
        ofstParameters      As Long            ' // Offset of parameters
        dwFlags             As ExeFlags        ' // Flags
    End Type
    
    ' // Storage descriptor
    Private Type BinStorageList
        dwSizeOfStructure   As Long            ' // Size of structure
        iExecutableIndex    As Long            ' // Index of main executable
        dwSizeOfItem        As Long            ' // Size of BinaryStorageItem structure
        dwNumberOfItems     As Long            ' // Number of files in storage
    End Type
    
    ' // Execute list descriptor
    Private Type BinExecList
        dwSizeOfStructure   As Long            ' // Size of structure
        dwSizeOfItem        As Long            ' // Size of BinaryExecuteItem structure
        dwNumberOfItems     As Long            ' // Number of items
    End Type
    
    ' // Base information about project
    Private Type BinProject
        dwSizeOfStructure   As Long            ' // Size of structure
        storageDescriptor   As BinStorageList  ' // Storage descriptor
        execListDescriptor  As BinExecList     ' // Command descriptor
        dwStringsTableLen   As Long            ' // Size of strings table
        dwFileTableLen      As Long            ' // Size of data table
    End Type
    The 'BinProject' structure is located at beginning of resource entry. Notice that project is stored as RT_RCDATA item with 'PROJECT' name. The dwSizeOfStructure field defines the size of the BinProject structure, storageDescriptor and execListDescriptor represent the storage and execute descriptors respectively. The dwStringsTableLen field shows the size of strings table. The strings table contains all the names and commands in the unicode format. The dwFileTableLen field shows the size of all data in the storage. Both storage (BinStorageList) and execute list (BinExecList) have dwSizeOfItem and dwSizeOfStructure fields that define the size of a descriptor structure and the size of a list item. These structures also have dwNumberOfItems field that shows how many items is contained in the list. The 'iExecutableIndex' field contains the index of executable file that will be launched. The common structure of a project in the resources is shown in this figure:
    Name:  BinProject.png
Views: 8226
Size:  60.3 KB
    An item can refers to the strings table and file table for this purpose it uses the offset from beginning of a table. All the items is located one by one. Okay, you have explored the internal project format now i tell how can you build the loader that contains these data. As i said we store data to resources of the loader. I will tell about the loader a little bit later now i want to note one issue. When you put the project data to resources it doesn't affect to exe information. For example if you launch this exe the information contained in the resources of the internal exe won't be loaded. Same with icons and version information. You should copy the resources from the internal exe to loader in order to avoid this troubles. WinAPI provides the set of the functions for replacing resources. In order to obtain the list of resources you should parse the exe file and extract data. I wrote the 'LoadResources' function that extract all the resources of specified exe data to array.

  2. #2

    Thread Starter
    PowerPoster
    Join Date
    Feb 2015
    Posts
    2,671

    Re: [VB6] Loader, shellcode, without runtime...

    PE format.

    In order to obtain resources from an exe file, run EXE from memory and well know the stucture of an exe file we should examine the PE (portable executable) format. The PE format has the quite complex structure. When loader launches a PE file (exe or dll) it does quite many work. Each PE file begins with special structure IMAGE_DOS_HEADER aka. dos stub. Because both DOS and WINDOWS applications have exe extension you can launch an exe file in DOS, but if you try to do it DOS launches this dos stub. Usually it show the message: "This program cannot be run in DOS mode", but you can write any program:
    Name:  DOs.png
Views: 6438
Size:  1.4 KB
    Code:
    Type IMAGE_DOS_HEADER
        e_magic                     As Integer
        e_cblp                      As Integer
        e_cp                        As Integer
        e_crlc                      As Integer
        e_cparhdr                   As Integer
        e_minalloc                  As Integer
        e_maxalloc                  As Integer
        e_ss                        As Integer
        e_sp                        As Integer
        e_csum                      As Integer
        e_ip                        As Integer
        e_cs                        As Integer
        e_lfarlc                    As Integer
        e_ovno                      As Integer
        e_res(0 To 3)               As Integer
        e_oemid                     As Integer
        e_oeminfo                   As Integer
        e_res2(0 To 9)              As Integer
        e_lfanew                    As Long
    End Type
    Since we don't write a dos program it doesn't matter. We wonder only two fields of this structure: e_magic and e_lfanew. The first field should contains the 'MZ' signature aka. IMAGE_DOS_SIGNATURE and e_lfanew offset to very crucial structure IMAGE_NT_HEADERS described as:
    Code:
    Type IMAGE_NT_HEADERS
        Signature                       As Long
        FileHeader                      As IMAGE_FILE_HEADER
        OptionalHeader                  As IMAGE_OPTIONAL_HEADER
    End Type
    The first filed of this structure contains the signature 'PE' (aka. IMAGE_NT_SIGNATURE). The next field describes the executable file:
    Code:
    Type IMAGE_FILE_HEADER
        Machine                         As Integer
        NumberOfSections                As Integer
        TimeDateStamp                   As Long
        PointerToSymbolTable            As Long
        NumberOfSymbols                 As Long
        SizeOfOptionalHeader            As Integer
        Characteristics                 As Integer
    End Type
    The 'Machine' field defines the processor architecture and should have the IMAGE_FILE_MACHINE_I386 value in our case. The NumberOfSections filed determines the count of sections contained in the exe file.
    An exe file contains the sections anyway. Each section takes a place in the address space and optionally in file. A section can contain the code or data (initialized or not), has the name as well. The most common names: .text, .data, .rsrc. Usually, the .text section contains the code, the .data section initialized data and .rsrc - resources. You can change this behavior using the linker directives. Each section have address called virtual address. Generally there are several types of the addresses. The first is relative virtual address (RVA). Because of a PE file can be loaded to any address all the references inside the PE file use the relative addressing. RVA is the offset from beginning of the base address (the address of the first byte of the PE module in the memory). The sum of the RVA and the base address is the VA (the virtual address). Also, there is the raw offset addressing that shows the location of data in the file relative the RVA. Notice that RVA <> raw offset. When a module is being loaded each secion is placed to its address. For example a module could have the section that has no-initialized data. This section wouldn't take a place in the exe file but would occupy the address space. It is the very crucial aspect because we will work with the raw exe file.
    The 'TimeDateStamp' field contains the creation date of the PE module in UTC format. The 'PointerToSymbolTable' and 'NumberOfSymbols' contain the information about symbols in the PE file. Generally this fields contains zero. This fields is always used in object files (*.OBJ, *.LIB) in order to resolve links during linking as well as debugging information for PE modules. The next field 'SizeOfOptionalHeader' contains the size of structure following after IMAGE_FILE_HEADER named IMAGE_OPTIONAL_HEADER that always is presented in PE files (although may be missing in OBJ files). This structure is very essential for loading a PE file to memory. Notice that this structure is different in x64 and x86 executable files. Eventually, the 'Characteristics' field contains the PE attributtes.
    The IMAGE_OPTIONAL_HEADER structure has the following format:
    Code:
    Type IMAGE_OPTIONAL_HEADER
        Magic                           As Integer
        MajorLinkerVersion              As Byte
        MinorLinkerVersion              As Byte
        SizeOfCode                      As Long
        SizeOfInitializedData           As Long
        SizeOfUnitializedData           As Long
        AddressOfEntryPoint             As Long
        BaseOfCode                      As Long
        BaseOfData                      As Long
        ImageBase                       As Long
        SectionAlignment                As Long
        FileAlignment                   As Long
        MajorOperatingSystemVersion     As Integer
        MinorOperatingSystemVersion     As Integer
        MajorImageVersion               As Integer
        MinorImageVersion               As Integer
        MajorSubsystemVersion           As Integer
        MinorSubsystemVersion           As Integer
        W32VersionValue                 As Long
        SizeOfImage                     As Long
        SizeOfHeaders                   As Long
        CheckSum                        As Long
        SubSystem                       As Integer
        DllCharacteristics              As Integer
        SizeOfStackReserve              As Long
        SizeOfStackCommit               As Long
        SizeOfHeapReserve               As Long
        SizeOfHeapCommit                As Long
        LoaderFlags                     As Long
        NumberOfRvaAndSizes             As Long
        DataDirectory(15)               As IMAGE_DATA_DIRECTORY
    End Type
    The first field contains the type of the image (x86, x64 or ROM image). We consider only IMAGE_NT_OPTIONAL_HDR32_MAGIC that represents a common 32-it application. The next two fields is not important (they were used in the old systems) and contain 4. The next group of fields contains the sizes of all the code, initialized data and uninitialized data. These values should be a multiple of 'SectionAlignment' of the structure (see later). The 'AddressOfEntryPoint' is very important RVA-value that sets the start point of a program (look like Sub Main). We will use this field when we have already loaded the PE-image to memory and it is necessary to run the program. The next crucial filds is 'ImageBase' that sets the prefered address of loading the module. When a loader is loading a module it tries to load it to thr prefered address (set in the 'ImageBase' filed). If this address is occupied then the loader checks the 'Characteristics' field in the 'IMAGE_FILE_HEADER' structure. If this field contains the IMAGE_FILE_RELOCS_STRIPPED it means that the module can't be loaded. In order to load such module we should add the reloaction information to PE that allows to set up the addresses if the module can't be loaded to prefered base address. We will use this field in the shellcode with the 'SizeOfImage' fields to reserve a memory region for the unpacked PE. The 'SectionAlignment' and 'FileAlignment' set the align in memory and file respectevely. By changing the file alignment we can reduce the PE file size but a system can not be possible to load this modified PE. The section alignment sets the align of a section (usually it equals to page size). The 'SizeOfHeaders' value sets the size of all headers (DOS header, NT headers, sections headers) aligned to the 'FileAlignment' value. The 'SizeOfStackReserve' and 'SizeOfStackCommit' determine the total and initial stack size. It's the same for 'SizeOfHeapReserve' and 'SizeOfHeapCommit' fields but for the heap. The 'NumberOfRvaAndSizes' fields contains the number of items in 'DataDirectory' array. This field always set to 16. The 'DataDirectory' array is very important because it contains the several data catalogs that contain essential information about import, export, resources, relocations, etc. We use only few items from this catalog that is used by VB6 compiler. I'll say about catalogs little bit later let's look what is behind the catalogs. There is list of the section headers. The number of section, if you remember, we can obtain from the IMAGE_FILE_HEADER structure. Let's consider the section header structure:
    Code:
    Type IMAGE_SECTION_HEADER
        SectionName(7)              As Byte
        VirtualSize                 As Long
        VirtualAddress              As Long
        SizeOfRawData               As Long
        PointerToRawData            As Long
        PointerToRelocations        As Long
        PointerToLinenumbers        As Long
        NumberOfRelocations         As Integer
        NumberOfLinenumbers         As Integer
        Characteristics             As Long
    End Type
    The first field contains the name of the section in the UTF-8 format with the NULL-terminated characters. This name is limited by 8 symbols (if a section has the size of the name equals 8 the NULL terminated character is skipped). A COFF file may have the name greater than 8 characters in this case the name begins with '/' symbol followed by the ASCII decimal representation offset in the string table (IMAGE_FILE_HEADER field). A PE file doesn't support the long names. The 'VirtualSize' and 'VirtualAddress' fields contain the size of section in memory (the address is set as RVA). The 'SizeOfRawData' and 'PointerToRawData' contain the data location in the file (if section has an initialized data). This is the key moment because we can calculate the raw offset by the virtual address using the information from the section headers. I wrote function for it that translate a RVA address to the RAW offset in the file:
    Code:
    ' // RVA to RAW
    Function RVA2RAW( _
                     ByVal rva As Long, _
                     ByRef sec() As IMAGE_SECTION_HEADER) As Long
        Dim index As Long
        
        For index = 0 To UBound(sec)
            
            If rva >= sec(index).VirtualAddress And _
               rva < sec(index).VirtualAddress + sec(index).VirtualSize Then
                RVA2RAW = sec(index).PointerToRawData + (rva - sec(index).VirtualAddress)
                Exit Function
            End If
            
        Next
        
        RVA2RAW = rva
        
    End Function
    This function enumerates all the sections and checks if the passed address is within section. The next 5 fields are used in only COFF file and don't matter in a PE file. The 'Characteristics' field contains the attributes of section such as memory permissions and managment. This field is important as well. We will use this information for the memory protection of an exe file in the loader.

  3. #3

    Thread Starter
    PowerPoster
    Join Date
    Feb 2015
    Posts
    2,671

    Re: [VB6] Loader, shellcode, without runtime...

    Okay, let's return to the data directories. As we saw there is 16 items in this catalog. Usually, a PE file doesn't use all of them. Let's consider the structure of single item:
    Code:
    Private Type IMAGE_DATA_DIRECTORY
        VirtualAddress                  As Long
        Size                            As Long
    End Type
    This structure consist two fields. The first fields contains the offset (RVA) to the data of the catalog, second - size. When an item of data catalog is not required it contains zero in the both fields. An VB6-compiled application generally contains only 4 items: import table , resource table , bound import table and import address table (IAT). Now we consider the resource table that has the IMAGE_DIRECTORY_ENTRY_RESOURCE index, because we work with this imformation in the compiler application.
    All the resources in the exe file are represented as the tree with triple depth. The first level defines the resource type (RT_BITMAP, RT_MANIFEST, RT_RCDATA, etc.), the next level defines the resource identifier, the third level defines language. In the standard resource editor you can change the first two levels. All the resources placed in the resources table located in a '.rsrc' section of the exe file. Regarding the resources structure we even can change it after compilation. In oredr to access to resources we should read the IMAGE_DIRECTORY_ENTRY_RESOURCE catalog from the optional header. The 'VirtualAddress' field of this structure contains the RVA of the resource table which has the following structure:
    Code:
    Type IMAGE_RESOURCE_DIRECTORY
        Characteristics             As Long
        TimeDateStamp               As Long
        MajorVersion                As Integer
        MinorVersion                As Integer
        NumberOfNamedEntries        As Integer
        NumberOfIdEntries           As Integer
    End Type
    This structure describes all the resources in PE file. The first four fields are not important, the 'NumberOfNamedEntries' and 'NumberOfIdEntries' contain the number of named items and the items with the numerical id respectively. For instance, when you add an image in 'VB Resource Editor' it'll add a numerical item with id = 2 (RT_BITMAP). The items are after this structure and have the following form:
    Code:
    Type IMAGE_RESOURCE_DIRECTORY_ENTRY
        NameId                      As Long
        OffsetToData                As Long
    End Type
    The first field of this structure defines either an item name or an item id depending on the most significant bit. If this bit is set the remained bits show the offset from beginning of resources to IMAGE_RESOURCE_DIR_STRING_U structure that has the following format:
    Code:
    Type IMAGE_RESOURCE_DIR_STRING_U
        Length                      As Integer
        NameString                  As String
    End Type
    Note that this is not the proper VB structure is shown for descriptive reasons. The first two bytes is the unsigned short (the closest analog is Integer) that show the length of the unicode string (in characters) that follows them. Thus, in order to obtain a string we should read the first two bytes to an integer, allocate memory for the string with the read value size, and read the remaining data to the string variable. Conversely, if the most significant bit of the 'NameId' field is cleared the field containts an identifier (RT_BITMAP in the previous case). The 'OffsetToData' field has two interpretations too. If the MSB is set it is the offset (from beginnig of resources) to the next level of tree i.e. to an IMAGE_RESOURCE_DIRECTORY structure. Otherwise, if the MSB is cleared this is the offset (from beginning of resources too) to a "leave" of the tree, i.e. to structure IMAGE_RESOURCE_DATA_ENTRY:
    Code:
    Type IMAGE_RESOURCE_DATA_ENTRY
        OffsetToData                As Long
        Size                        As Long
        CodePage                    As Long
        Reserved                    As Long
    End Type
    The most important fields of this structure are 'OffsetToData' and 'Size' that contain the RVA and Size of the raw data of the resource. Now we can get all the resources from a PE file.

    Compilation.

    So... When you start the compilation it calls the Compile function. Firstly it packs all the storage items and execute items to binary format (BinProject, BinStorageListItem, etc.) and forms the string table and the files table. The string table is saves as the unicode strings with the null-terminating characters. I use special class clsStream for safe working with binary data. This class allows to read/write any data or streams into the binary buffer, compress buffer. I use RtlCompressBuffer function for compression stream that uses LZ-compression method. After packing and compression it check output project format. It is supports two formats: bin (raw project data), and exe (loader). The binary format is not interesting right now, we will consider the exe format. Firstly, it extracts all the resources from the main executable to the three-level catalog. This operation is performed by ExtractResources function. An identifier name is saved as the "#" symbol with the appended string that represents the resource id in the decimal format. Afterwards it clones the loader template to the resulting file, then begins to modify the resources in this file using BeginUpdateResource api. Then it alternately copies all the extracted resources (UpdateResource), binary project and mainfest (if needed) to the resulting file and applies changes with EndUpdateResource function. Again, the binary project is saved with "PROJECT" name and RT_DATA type. Basically that's it.

  4. #4

    Thread Starter
    PowerPoster
    Join Date
    Feb 2015
    Posts
    2,671

    Re: [VB6] Loader, shellcode, without runtime...


    Loader.
    So... I think it the most interesting part. So, we must avoid the usage of the runtime. How to do it? I give some rules:
    1. Set an user function as startup;
    2. Avoid any objects in project;
    3. Avoid immediate arrays. The fixed arrays in a type is not forbidden;
    4. Avoid string variables as well Variant/Object. In some cases Currency/Date;
    5. Avoid the API functions with Declare statement.
    6. Avoid VarPtr and some other standard functions.
    7. ....

    It isn't the complete list of restrictions and during the shellcode execution it adds additional restrinctions.
    So, begin. In order to avoid the usage of a string variable i keep all the string variables as Long pointer to the string. There is an issue with loading of a string because we can't access to any string to load it. I decide to use resources as the storage of strings and load it by ID. Thus, we can save the pointer to a string into a Long variable without references to runtime. I used a TLB (type library) for all the API functions without usesgetlasterror attribute in order to avoid the Declare statement. In order to set a startup function i use the linker options. The startup function in the loader is 'Main'. Note, if in the IDE you select the startup function 'Main' actually this function is not startup because any EXE application written in VB6 begins with ThunRTMain function, that loads project and initialize runtime and thread. Generally, the loader conist three modules:
    1. modMain - startup function and working with storage/execute items;
    2. modConstants - working with string constants;
    3. modLoader - loader of EXE files.


    When loader has been launched it starts the Main function.
    Code:
    ' // Startup subroutine
    Sub Main()
    
        ' // Load constants
        If Not LoadConstants Then
            MessageBox 0, GetString(MID_ERRORLOADINGCONST), 0, MB_ICONERROR Or MB_SYSTEMMODAL
            GoTo EndOfProcess
        End If
        
        ' // Load project
        If Not ReadProject Then
            MessageBox 0, GetString(MID_ERRORREADINGPROJECT), 0, MB_ICONERROR Or MB_SYSTEMMODAL
            GoTo EndOfProcess
        End If
        
        ' // Copying from storage
        If Not CopyProcess Then GoTo EndOfProcess
        
        ' // Execution process
        If Not ExecuteProcess Then GoTo EndOfProcess
        
        ' // If main executable is not presented exit
        If ProjectDesc.storageDescriptor.iExecutableIndex = -1 Then GoTo EndOfProcess
        
        ' // Run exe from memory
        If Not RunProcess Then
            ' // Error occrurs
            MessageBox 0, GetString(MID_ERRORSTARTUPEXE), 0, MB_ICONERROR Or MB_SYSTEMMODAL
        End If
        
    EndOfProcess:
        
        If pProjectData Then
            HeapFree GetProcessHeap(), HEAP_NO_SERIALIZE, pProjectData
        End If
        
        ExitProcess 0
        
    End Sub
    Firstly, it function call LoadConstants function to load all the needed constants from resources:
    Code:
    ' // modConstants.bas - main module for loading constants
    ' // © Krivous Anatoly Anatolevich (The trick), 2016
    
    Option Explicit
    
    Public Enum MessagesID
        MID_ERRORLOADINGCONST = 100     ' // Errors
        MID_ERRORREADINGPROJECT = 101   '
        MID_ERRORCOPYINGFILE = 102      '
        MID_ERRORWIN32 = 103            '
        MID_ERROREXECUTELINE = 104      '
        MID_ERRORSTARTUPEXE = 105       '
        PROJECT = 200                   ' // Project resource ID
        API_LIB_KERNEL32 = 300          ' // Library names
        API_LIB_NTDLL = 350             '
        API_LIB_USER32 = 400            '
        MSG_LOADER_ERROR = 500
    End Enum
    
    ' // Paths
    
    Public pAppPath  As Long            ' // Path to application
    Public pSysPath  As Long            ' // Path to System32
    Public pTmpPath  As Long            ' // Path to Temp
    Public pWinPath  As Long            ' // Path to Windows
    Public pDrvPath  As Long            ' // Path to system drive
    Public pDtpPath  As Long            ' // Path to desktop
    
    ' // Substitution constants
    
    Public pAppRepl  As Long
    Public pSysRepl  As Long
    Public pTmpRepl  As Long
    Public pWinRepl  As Long
    Public pDrvRepl  As Long
    Public pDtpRepl  As Long
    Public pStrNull  As Long            ' // \0
    
    Public hInstance    As Long         ' // Base address
    Public lpCmdLine    As Long         ' // Command line
    Public SI           As STARTUPINFO  ' // Startup parameters
    Public LCID         As Long         ' // LCID
    
    ' // Load constants
    Function LoadConstants() As Boolean
        Dim lSize   As Long
        Dim pBuf    As Long
        Dim index   As Long
        Dim ctl     As tagINITCOMMONCONTROLSEX
        
        ' // Load windows classes
        ctl.dwSize = Len(ctl)
        ctl.dwICC = &H3FFF&
        InitCommonControlsEx ctl
        
        ' // Get startup parameters
        GetStartupInfo SI
        
        ' // Get command line
        lpCmdLine = GetCommandLine()
        
        ' // Get base address
        hInstance = GetModuleHandle(ByVal 0&)
        
        ' // Get LCID
        LCID = GetUserDefaultLCID()
        
        ' // Alloc memory for strings
        pBuf = SysAllocStringLen(0, MAX_PATH)
        If pBuf = 0 Then Exit Function
        
        ' // Get path to process file name
        If GetModuleFileName(hInstance, pBuf, MAX_PATH) = 0 Then GoTo CleanUp
        
        ' // Leave only directory
        PathRemoveFileSpec pBuf
        
        ' // Save path
        pAppPath = SysAllocString(pBuf)
        
        ' // Get Windows folder
        If GetWindowsDirectory(pBuf, MAX_PATH) = 0 Then GoTo CleanUp
        pWinPath = SysAllocString(pBuf)
        
        ' // Get System32 folder
        If GetSystemDirectory(pBuf, MAX_PATH) = 0 Then GoTo CleanUp
        pSysPath = SysAllocString(pBuf)
        
        ' // Get Temp directory
        If GetTempPath(MAX_PATH, pBuf) = 0 Then GoTo CleanUp
        pTmpPath = SysAllocString(pBuf)
        
        ' // Get system drive
        PathStripToRoot pBuf
        pDrvPath = SysAllocString(pBuf)
        
        ' // Get desktop path
        If SHGetFolderPath(0, CSIDL_DESKTOPDIRECTORY, 0, SHGFP_TYPE_CURRENT, pBuf) Then GoTo CleanUp
        pDtpPath = SysAllocString(pBuf)
        
        ' // Load wildcards
        For index = 1 To 6
            If LoadString(hInstance, index, pBuf, MAX_PATH) = 0 Then GoTo CleanUp
            Select Case index
            Case 1: pAppRepl = SysAllocString(pBuf)
            Case 2: pSysRepl = SysAllocString(pBuf)
            Case 3: pTmpRepl = SysAllocString(pBuf)
            Case 4: pWinRepl = SysAllocString(pBuf)
            Case 5: pDrvRepl = SysAllocString(pBuf)
            Case 6: pDtpRepl = SysAllocString(pBuf)
            End Select
        Next
        
        ' // vbNullChar
        pStrNull = SysAllocStringLen(0, 0)
    
        ' // Success
        LoadConstants = True
        
    CleanUp:
        
        If pBuf Then SysFreeString pBuf
        
    End Function
    
    ' // Obtain string from resource (it should be less or equal MAX_PATH)
    Public Function GetString( _
                    ByVal ID As MessagesID) As Long
                    
        GetString = SysAllocStringLen(0, MAX_PATH)
        
        If GetString Then
        
            If LoadString(hInstance, ID, GetString, MAX_PATH) = 0 Then SysFreeString GetString: GetString = 0: Exit Function
            If SysReAllocString(GetString, GetString) = 0 Then SysFreeString GetString: GetString = 0: Exit Function
            
        End If
        
    End Function
    The 'LoadConstants' function loads all the needed variables and string (hInstance, LCID, command line, wildcards, default paths, etc.). All the strings is stored in the BSTR unicode format. The 'GetString' function loads a string from resource by number. The 'MessagesID' contains some string identifiers needed in program (error messages, libraries names, etc.). When all the constants are loaded it calls the ReadProject function that loads the binary project:
    Code:
    ' // Load project
    Function ReadProject() As Boolean
        Dim hResource       As Long:                Dim hMememory       As Long
        Dim lResSize        As Long:                Dim pRawData        As Long
        Dim status          As Long:                Dim pUncompressed   As Long
        Dim lUncompressSize As Long:                Dim lResultSize     As Long
        Dim tmpStorageItem  As BinStorageListItem:  Dim tmpExecuteItem  As BinExecListItem
        Dim pLocalBuffer    As Long
        
        ' // Load resource
        hResource = FindResource(hInstance, GetString(PROJECT), RT_RCDATA)
        If hResource = 0 Then GoTo CleanUp
        
        hMememory = LoadResource(hInstance, hResource)
        If hMememory = 0 Then GoTo CleanUp
        
        lResSize = SizeofResource(hInstance, hResource)
        If lResSize = 0 Then GoTo CleanUp
        
        pRawData = LockResource(hMememory)
        If pRawData = 0 Then GoTo CleanUp
        
        pLocalBuffer = HeapAlloc(GetProcessHeap(), HEAP_NO_SERIALIZE, lResSize)
        If pLocalBuffer = 0 Then GoTo CleanUp
        
        ' // Copy to local buffer
        CopyMemory ByVal pLocalBuffer, ByVal pRawData, lResSize
        
        ' // Set default size
        lUncompressSize = lResSize * 2
        
        ' // Do decompress...
        Do
            
            If pUncompressed Then
                pUncompressed = HeapReAlloc(GetProcessHeap(), HEAP_NO_SERIALIZE, ByVal pUncompressed, lUncompressSize)
            Else
                pUncompressed = HeapAlloc(GetProcessHeap(), HEAP_NO_SERIALIZE, lUncompressSize)
            End If
            
            status = RtlDecompressBuffer(COMPRESSION_FORMAT_LZNT1, _
                                         ByVal pUncompressed, lUncompressSize, _
                                         ByVal pLocalBuffer, lResSize, lResultSize)
            
            lUncompressSize = lUncompressSize * 2
            
        Loop While status = STATUS_BAD_COMPRESSION_BUFFER
        
        pProjectData = pUncompressed
        
        If status Then GoTo CleanUp
    
        ' // Validation check
        If lResultSize < LenB(ProjectDesc) Then GoTo CleanUp
        
        ' // Copy descriptor
        CopyMemory ProjectDesc, ByVal pProjectData, LenB(ProjectDesc)
        
        ' // Check all members
        If ProjectDesc.dwSizeOfStructure <> Len(ProjectDesc) Then GoTo CleanUp
        If ProjectDesc.storageDescriptor.dwSizeOfStructure <> Len(ProjectDesc.storageDescriptor) Then GoTo CleanUp
        If ProjectDesc.storageDescriptor.dwSizeOfItem <> Len(tmpStorageItem) Then GoTo CleanUp
        If ProjectDesc.execListDescriptor.dwSizeOfStructure <> Len(ProjectDesc.execListDescriptor) Then GoTo CleanUp
        If ProjectDesc.execListDescriptor.dwSizeOfItem <> Len(tmpExecuteItem) Then GoTo CleanUp
        
        ' // Initialize pointers
        pStoragesTable = pProjectData + ProjectDesc.dwSizeOfStructure
        pExecutesTable = pStoragesTable + ProjectDesc.storageDescriptor.dwSizeOfItem * ProjectDesc.storageDescriptor.dwNumberOfItems
        pFilesTable = pExecutesTable + ProjectDesc.execListDescriptor.dwSizeOfItem * ProjectDesc.execListDescriptor.dwNumberOfItems
        pStringsTable = pFilesTable + ProjectDesc.dwFileTableLen
        
        ' // Check size
        If (pStringsTable + ProjectDesc.dwStringsTableLen - pProjectData) <> lResultSize Then GoTo CleanUp
        
        ' // Success
        ReadProject = True
        
    CleanUp:
        
        If pLocalBuffer Then HeapFree GetProcessHeap(), HEAP_NO_SERIALIZE, pLocalBuffer
        
        If Not ReadProject And pProjectData Then
            HeapFree GetProcessHeap(), HEAP_NO_SERIALIZE, pProjectData
        End If
        
    End Function
    As you can see i use the heap memory instead arrays. Firstly, it loads the 'PROJECT' resource and copies one to heap memory then tries to decompres using the RtlDecompressBuffer function. This function is not returns the sufficient output buffer size therefore we try to do decompress of the buffer increasing the output buffer size. Afterwards it checks all parameters and initializes the global project pointers.

  5. #5

    Thread Starter
    PowerPoster
    Join Date
    Feb 2015
    Posts
    2,671

    Re: [VB6] Loader, shellcode, without runtime...

    If it is succeeded then it launches the 'CopyProcess' procedure that unpacks all the storage items according project data:
    Code:
    ' // Copying process
    Function CopyProcess() As Boolean
        Dim bItem       As BinStorageListItem:  Dim index       As Long
        Dim pPath       As Long:                Dim dwWritten   As Long
        Dim msg         As Long:                Dim lStep       As Long
        Dim isError     As Boolean:             Dim pItem       As Long
        Dim pErrMsg     As Long:                Dim pTempString As Long
        
        ' // Set pointer
        pItem = pStoragesTable
        
        ' // Go thru file list
        For index = 0 To ProjectDesc.storageDescriptor.dwNumberOfItems - 1
    
            ' // Copy file descriptor
            CopyMemory bItem, ByVal pItem, Len(bItem)
            
            ' // Next item
            pItem = pItem + ProjectDesc.storageDescriptor.dwSizeOfItem
            
            ' // If it is not main executable
            If index <> ProjectDesc.storageDescriptor.iExecutableIndex Then
            
                ' // Normalize path
                pPath = NormalizePath(pStringsTable + bItem.ofstDestPath, pStringsTable + bItem.ofstFileName)
                
                ' // Error occurs
                If pPath = 0 Then
                
                    pErrMsg = GetString(MID_ERRORWIN32)
                    MessageBox 0, pErrMsg, 0, MB_ICONERROR Or MB_SYSTEMMODAL
                    GoTo CleanUp
                    
                Else
                    Dim hFile   As Long
                    Dim disp    As CREATIONDISPOSITION
                    
                    ' // Set overwrite flags
                    If bItem.dwFlags And FF_REPLACEONEXIST Then disp = CREATE_ALWAYS Else disp = CREATE_NEW
                    
                    ' // Set number of subroutine
                    lStep = 0
                    
                    ' // Run subroutines
                    Do
                        ' // Disable error flag
                        isError = False
                        
                        ' // Free string
                        If pErrMsg Then SysFreeString pErrMsg: pErrMsg = 0
                        
                        ' // Choose subroutine
                        Select Case lStep
                        Case 0  ' // 0. Create folder
                        
                            If Not CreateSubdirectories(pPath) Then isError = True
                            
                        Case 1  ' // 1. Create file
                        
                            hFile = CreateFile(pPath, FILE_GENERIC_WRITE, 0, ByVal 0&, disp, FILE_ATTRIBUTE_NORMAL, 0)
                            If hFile = INVALID_HANDLE_VALUE Then
                                If GetLastError = ERROR_FILE_EXISTS Then Exit Do
                                isError = True
                            End If
                            
                        Case 2  ' // 2. Copy data to file
                        
                            If WriteFile(hFile, ByVal pFilesTable + bItem.ofstBeginOfData, _
                                         bItem.dwSizeOfFile, dwWritten, ByVal 0&) = 0 Then isError = True
                                         
                            If dwWritten <> bItem.dwSizeOfFile Then
                                isError = True
                            Else
                                CloseHandle hFile: hFile = INVALID_HANDLE_VALUE
                            End If
                            
                        End Select
                        
                        ' // If error occurs show notification (retry, abort, ignore)
                        If isError Then
                        
                            ' // Ignore error
                            If bItem.dwFlags And FF_IGNOREERROR Then Exit Do
    
                            pTempString = GetString(MID_ERRORCOPYINGFILE)
                            pErrMsg = StrCat(pTempString, pPath)
                            
                            ' // Cleaning
                            SysFreeString pTempString: pTempString = 0
                            
                            Select Case MessageBox(0, pErrMsg, 0, MB_ICONERROR Or MB_SYSTEMMODAL Or MB_CANCELTRYCONTINUE)
                            Case MESSAGEBOXRETURN.IDCONTINUE: Exit Do
                            Case MESSAGEBOXRETURN.IDTRYAGAIN
                            Case Else:  GoTo CleanUp
                            End Select
                            
                        Else: lStep = lStep + 1
                        End If
                        
                    Loop While lStep <= 2
                            
                    If hFile <> INVALID_HANDLE_VALUE Then
                        CloseHandle hFile: hFile = INVALID_HANDLE_VALUE
                    End If
                    
                    ' // Cleaning
                    SysFreeString pPath: pPath = 0
                    
                End If
                
            End If
            
        Next
        
        ' // Success
        CopyProcess = True
        
    CleanUp:
        
        If pTempString Then SysFreeString pTempString
        If pErrMsg Then SysFreeString pErrMsg
        If pPath Then SysFreeString pPath
        
        If hFile <> INVALID_HANDLE_VALUE Then
            CloseHandle hFile
            hFile = INVALID_HANDLE_VALUE
        End If
        
    End Function
    This procedure goes through all the storage items and unpacks all the items one by one except the main executable file. The 'NormalizePath' function replace the wildcards in the path to the real strings path. There is the 'CreateSubdirectories' function that creates the intermediate directories (if needs) for specified path. Then it calls the CreateFile function and copy data to it through WriteFile. If an error occurs it shows the message box with the standard suggestions: Retry, Abort, Ignore.
    Code:
    ' // Create all subdirectories by path
    Function CreateSubdirectories( _
                    ByVal pPath As Long) As Boolean
        Dim pComponent As Long
        Dim tChar      As Integer
        
        ' // Pointer to first char
        pComponent = pPath
        
        ' // Go thru path components
        Do
        
            ' // Get next component
            pComponent = PathFindNextComponent(pComponent)
            
            ' // Check if end of line
            CopyMemory tChar, ByVal pComponent, 2
            If tChar = 0 Then Exit Do
            
            ' // Write null-terminator
            CopyMemory ByVal pComponent - 2, 0, 2
            
            ' // Check if path exists
            If PathIsDirectory(pPath) = 0 Then
            
                ' // Create folder
                If CreateDirectory(pPath, ByVal 0&) = 0 Then
                    ' // Error
                    CopyMemory ByVal pComponent - 2, &H5C, 2
                    Exit Function
                End If
                
            End If
            
            ' // Restore path delimiter
            CopyMemory ByVal pComponent - 2, &H5C, 2
            
        Loop
        
        ' // Success
        CreateSubdirectories = True
        
    End Function
    
    ' // Get normalize path (replace wildcards, append file name)
    Function NormalizePath( _
                    ByVal pPath As Long, _
                    ByVal pTitle As Long) As Long
        Dim lPathLen    As Long:    Dim lRelacerLen As Long
        Dim lTitleLen   As Long:    Dim pRelacer    As Long
        Dim lTotalLen   As Long:    Dim lPtr        As Long
        Dim pTempString As Long:    Dim pRetString  As Long
        
        ' // Determine wildcard
        Select Case True
        Case IntlStrEqWorker(0, pPath, pAppRepl, 5): pRelacer = pAppPath
        Case IntlStrEqWorker(0, pPath, pSysRepl, 5): pRelacer = pSysPath
        Case IntlStrEqWorker(0, pPath, pTmpRepl, 5): pRelacer = pTmpPath
        Case IntlStrEqWorker(0, pPath, pWinRepl, 5): pRelacer = pWinPath
        Case IntlStrEqWorker(0, pPath, pDrvRepl, 5): pRelacer = pDrvPath
        Case IntlStrEqWorker(0, pPath, pDtpRepl, 5): pRelacer = pDtpPath
        Case Else: pRelacer = pStrNull
        End Select
        
        ' // Get string size
        lPathLen = lstrlen(ByVal pPath)
        lRelacerLen = lstrlen(ByVal pRelacer)
        
        ' // Skip wildcard
        If lRelacerLen Then
            pPath = pPath + 5 * 2
            lPathLen = lPathLen - 5
        End If
        
        If pTitle Then lTitleLen = lstrlen(ByVal pTitle)
        
        ' // Get length all strings
        lTotalLen = lPathLen + lRelacerLen + lTitleLen
        
        ' // Check overflow (it should be les or equal MAX_PATH)
        If lTotalLen > MAX_PATH Then Exit Function
        
        ' // Create string
        pTempString = SysAllocStringLen(0, MAX_PATH)
        If pTempString = 0 Then Exit Function
        
        ' // Copy
        lstrcpyn ByVal pTempString, ByVal pRelacer, lRelacerLen + 1
        lstrcat ByVal pTempString, ByVal pPath
    
        ' // If title is presented append
        If pTitle Then
    
            ' // Error
            If PathAddBackslash(pTempString) = 0 Then GoTo CleanUp
    
            ' // Copy file name
            lstrcat ByVal pTempString, ByVal pTitle
            
        End If
        
        ' // Alloc memory for translation relative path to absolute
        pRetString = SysAllocStringLen(0, MAX_PATH)
        If pRetString = 0 Then GoTo CleanUp
        
        ' // Normalize
        If PathCanonicalize(pRetString, pTempString) = 0 Then GoTo CleanUp
        
        NormalizePath = pRetString
        
    CleanUp:
        
        If pTempString Then SysFreeString pTempString
        If pRetString <> 0 And NormalizePath = 0 Then SysFreeString pRetString
        
    End Function
    
    ' // Concatenation strings
    Function StrCat( _
                    ByVal pStringDest As Long, _
                    ByVal pStringAppended As Long) As Long
        Dim l1 As Long, l2 As Long
        
        l1 = lstrlen(ByVal pStringDest): l2 = lstrlen(ByVal pStringAppended)
        StrCat = SysAllocStringLen(0, l1 + l2)
        
        If StrCat = 0 Then Exit Function
        
        lstrcpyn ByVal StrCat, ByVal pStringDest, l1 + 1
        lstrcat ByVal StrCat, ByVal pStringAppended
        
    End Function
    After unpacking it is call the 'ExecuteProcess' function that launches all the commands using ShellExecuteEx function:
    Code:
    ' // Execution command process
    Function ExecuteProcess() As Boolean
        Dim index       As Long:                Dim bItem       As BinExecListItem
        Dim pPath       As Long:                Dim pErrMsg     As Long
        Dim shInfo      As SHELLEXECUTEINFO:    Dim pTempString As Long
        Dim pItem       As Long:                Dim status      As Long
    
        ' // Set pointer and size
        shInfo.cbSize = Len(shInfo)
        pItem = pExecutesTable
        
        ' // Go thru all items
        For index = 0 To ProjectDesc.execListDescriptor.dwNumberOfItems - 1
        
            ' // Copy item
            CopyMemory bItem, ByVal pItem, ProjectDesc.execListDescriptor.dwSizeOfItem
            
            ' // Set pointer to next item
            pItem = pItem + ProjectDesc.execListDescriptor.dwSizeOfItem
            
            ' // Normalize path
            pPath = NormalizePath(pStringsTable + bItem.ofstFileName, 0)
            
            ' // Fill SHELLEXECUTEINFO
            shInfo.lpFile = pPath
            shInfo.lpParameters = pStringsTable + bItem.ofstParameters
            shInfo.fMask = SEE_MASK_NOCLOSEPROCESS Or SEE_MASK_FLAG_NO_UI
            shInfo.nShow = SW_SHOWDEFAULT
            
            ' // Performing...
            status = ShellExecuteEx(shInfo)
            
            ' // If error occurs show notification (retry, abort, ignore)
            Do Until status
                
                If pErrMsg Then SysFreeString pErrMsg: pErrMsg = 0
                
                ' // Ignore error
                If bItem.dwFlags And EF_IGNOREERROR Then
                    Exit Do
                End If
                            
                pTempString = GetString(MID_ERROREXECUTELINE)
                pErrMsg = StrCat(pTempString, pPath)
                
                SysFreeString pTempString: pTempString = 0
                
                Select Case MessageBox(0, pErrMsg, 0, MB_ICONERROR Or MB_SYSTEMMODAL Or MB_CANCELTRYCONTINUE)
                Case MESSAGEBOXRETURN.IDCONTINUE: Exit Do
                Case MESSAGEBOXRETURN.IDTRYAGAIN
                Case Else: GoTo CleanUp
                End Select
    
                status = ShellExecuteEx(shInfo)
                
            Loop
            
            ' // Wait for process terminaton
            WaitForSingleObject shInfo.hProcess, INFINITE
            CloseHandle shInfo.hProcess
            
        Next
        
        ' // Success
        ExecuteProcess = True
        
    CleanUp:
    
        If pTempString Then SysFreeString pTempString
        If pErrMsg Then SysFreeString pErrMsg
        If pPath Then SysFreeString pPath
        
    End Function
    As you can see it's like the previous procedure. Here is the same it only uses ShellExecuteEx instead unpacking. Note that each operation performs synchronously, i.e. each calling of ShellExecuteEx waits for operation will have been done.

  6. #6

    Thread Starter
    PowerPoster
    Join Date
    Feb 2015
    Posts
    2,671

    Re: [VB6] Loader, shellcode, without runtime...

    If it is succeeded then it is call the 'RunProcess' function that prepares the data for executing main executable from memory.
    Code:
    ' // Run exe from project in memory
    Function RunProcess() As Boolean
        Dim bItem       As BinStorageListItem:  Dim Length      As Long
        Dim pFileData   As Long
        
        ' // Get descriptor of executable file
        CopyMemory bItem, ByVal pStoragesTable + ProjectDesc.storageDescriptor.dwSizeOfItem * _
                          ProjectDesc.storageDescriptor.iExecutableIndex, Len(bItem)
        
    
        ' // Alloc memory within top memory addresses
        pFileData = VirtualAlloc(ByVal 0&, bItem.dwSizeOfFile, MEM_TOP_DOWN Or MEM_COMMIT, PAGE_READWRITE)
        If pFileData = 0 Then Exit Function
        
        ' // Copy raw exe file to this memory
        CopyMemory ByVal pFileData, ByVal pFilesTable + bItem.ofstBeginOfData, bItem.dwSizeOfFile
        
        ' // Free decompressed project data
        HeapFree GetProcessHeap(), HEAP_NO_SERIALIZE, pProjectData
        pProjectData = 0
        
        ' // Run exe from memory
        RunExeFromMemory pFileData, bItem.dwFlags And FF_IGNOREERROR
        
        ' ----------------------------------------------------
        ' // An error occurs
        ' // Clean memory
        
        VirtualFree ByVal pFileData, 0, MEM_RELEASE
        
        ' // If ignore error then success
        If bItem.dwFlags And FF_IGNOREERROR Then RunProcess = True
        
    End Function
    It allocates the memory in the top area of virtual addresses because the most of exe files is loaded to quite low addresses (usually 0x00400000). Afterwards it free the memory of the project data because if the process is launched this memory won't release, then it call the 'RunExeFromMemory' function that does next step of loading an exe. If for any reasons loading of an exe file wouldn't done it frees the allocated memory and return the control to the 'Main' function. So, in order to load an exe file we should release the loader memory, i.e. unload us loader. We should leave small piece of code that will load an exe and run it. I decide to use the shellcode, although it is possible to use a dll. The shellcode is the small base-independent code (this code doesn't refer to external data) that allows to do the usefull stuff. Anyway we should ensure the access to API functions from the shellcode. You can't call an api function directly from the shellcode because the main exe is unloaded and any reference to the import table of main exe occurs crash. The second restriction is the 'CALL' instruction can use relative offset (it is most frequently case). Therefore we should initialize some "springboard" that will jump an api function. I decide to do it using splicing method. I just replace the first 5 bytes of a stub function to JMP assembler instruction that refers to the needed API:
    Code:
    ' // Run EXE file by memory address
    Function RunExeFromMemory( _
                    ByVal pExeData As Long, _
                    ByVal IgnoreError As Boolean) As Boolean
        Dim Length  As Long:    Dim pCode       As Long
        Dim pszMsg  As Long:    Dim pMsgTable   As Long
        Dim index   As Long:    Dim pCurMsg     As Long
        
        ' // Get size of shellcode
        Length = GetAddr(AddressOf ENDSHELLLOADER) - GetAddr(AddressOf BEGINSHELLLOADER)
        
        ' // Alloc memory within top addresses
        pCode = VirtualAlloc(ByVal 0&, Length, MEM_TOP_DOWN Or MEM_COMMIT, PAGE_EXECUTE_READWRITE)
        
        ' // Copy shellcode to allocated memory
        CopyMemory ByVal pCode, ByVal GetAddr(AddressOf BEGINSHELLLOADER), Length
        
        ' // Initialization of shellcode
        If Not InitShellLoader(pCode) Then GoTo CleanUp
        
        ' // Splice CallLoader function in order to call shellcode
        Splice AddressOf CallLoader, pCode + GetAddr(AddressOf LoadExeFromMemory) - GetAddr(AddressOf BEGINSHELLLOADER)
        
        ' // Check ignore errors
        If Not IgnoreError Then
            
            ' // Alloc memory for messages table
            pMsgTable = VirtualAlloc(ByVal 0&, 1024, MEM_TOP_DOWN Or MEM_COMMIT, PAGE_READWRITE)
            If pMsgTable = 0 Then GoTo CleanUp
            
            ' // Skip pointers
            pCurMsg = pMsgTable + EM_END * 4
            
            For index = 0 To EM_END - 1
            
                ' // Load message string
                pszMsg = GetString(MSG_LOADER_ERROR + index)
                If pszMsg = 0 Then GoTo CleanUp
                
                Length = SysStringLen(pszMsg)
    
                lstrcpyn ByVal pCurMsg, ByVal pszMsg, Length + 1
                
                ' // Store pointer
                CopyMemory ByVal pMsgTable + index * 4, pCurMsg, Len(pCurMsg)
                
                ' // Next message offset
                pCurMsg = pCurMsg + (Length + 1) * 2
                
                SysFreeString pszMsg
                
            Next
            
        End If
        
        ' // Call shellcode
        CallLoader pExeData, pCode, pMsgTable
        
    CleanUp:
        
        If pMsgTable Then
            VirtualFree ByVal pMsgTable, 0, MEM_RELEASE
        End If
        
        If pCode Then
            VirtualFree ByVal pCode, 0, MEM_RELEASE
        End If
        
    End Function
    As you can see it calculates the size of shellcode using the difference between the extreme functions ENDSHELLLOADER and BEGINSHELLLOADER. These functions should surround the shellcode and have the different prototypes because VB6 compiler can union identical functions. Then it allocates the memory for the shellcode and copies the shellcode to there. Afterwards it calls the 'InitShellLoader' function, that splaces all the function in shellcode:
    Code:
    ' // Shellcode initialization
    Function InitShellLoader( _
                     ByVal pShellCode As Long) As Boolean
        Dim hLib    As Long:        Dim sName   As Long
        Dim sFunc   As Long:        Dim lpAddr  As Long
        Dim libIdx  As Long:        Dim fncIdx  As Long
        Dim libName As MessagesID:  Dim fncName As MessagesID
        Dim fncSpc  As Long:        Dim splAddr As Long
        
        ' // +----------------------------------------------------------------+
        ' // |                  Fixing of API addresses                       |
        ' // +----------------------------------------------------------------+
        ' // | In order to call api function from shellcode i use splicing of |
        ' // |    our VB functions and redirect call to corresponding api.    |
        ' // |     I did same in the code that injects to other process.      |
        ' // +----------------------------------------------------------------+
        
        splAddr = GetAddr(AddressOf tVirtualAlloc) - GetAddr(AddressOf BEGINSHELLLOADER) + pShellCode
        
        ' // Get size in bytes between stub functions
        fncSpc = GetAddr(AddressOf tVirtualProtect) - GetAddr(AddressOf tVirtualAlloc)
    
        ' // Use 3 library: kernel32, ntdll и user32
        For libIdx = 0 To 2
        
            ' // Get number of imported functions depending on library
            Select Case libIdx
            Case 0: libName = API_LIB_KERNEL32: fncIdx = 13
            Case 1: libName = API_LIB_NTDLL:    fncIdx = 1
            Case 2: libName = API_LIB_USER32:   fncIdx = 1
            End Select
            
            ' // Get library name from resources
            sName = GetString(libName): If sName = 0 Then Exit Function
            
            ' // Get module handle
            hLib = GetModuleHandle(ByVal sName): If hLib = 0 Then Exit Function
            SysFreeString sName
            
            ' // Go thru functions
            Do While fncIdx
            
                libName = libName + 1
                ' // Get function name
                sName = GetString(libName): If sName = 0 Then Exit Function
                
                ' // Because of GetProcAddress works with ANSI string translate it to ANSI
                sFunc = ToAnsi(sName): If sFunc = 0 Then Exit Function
                
                ' // Get function address
                lpAddr = GetProcAddress(hLib, sFunc)
                SysFreeString sName: SysFreeString sFunc
                
                ' // Error
                If lpAddr = 0 Then Exit Function
                
                ' // Splice stub
                Splice splAddr, lpAddr
                
                ' // Next stub
                splAddr = splAddr + fncSpc
                fncIdx = fncIdx - 1
                
            Loop
            
        Next
        
        ' // Modify CallByPointer
        lpAddr = GetAddr(AddressOf CallByPointer) - GetAddr(AddressOf BEGINSHELLLOADER) + pShellCode
        
        ' // pop eax    - 0x58
        ' // pop ecx    - 0x59
        ' // push eax   - 0x50
        ' // jmp ecx    - 0xFFE1
        
        CopyMemory ByVal lpAddr, &HFF505958, 4
        CopyMemory ByVal lpAddr + 4, &HE1, 1
    
        ' // Success
        InitShellLoader = True
        
    End Function
    
    ' // Splice function
    Sub Splice( _
                ByVal Func As Long, _
                ByVal NewAddr As Long)
        ' // Set memory permissions
        VirtualProtect ByVal Func, 5, PAGE_EXECUTE_READWRITE, 0
        CopyMemory ByVal Func, &HE9, 1                      ' // JMP
        CopyMemory ByVal Func + 1, NewAddr - Func - 5, 4    ' // Relative address
    End Sub
    Firstly it calculates the offset of the first "springboard" function (in this case it is tVirtualAlloc function) from beginning of the shellcode and calculates the distance in bytes between "springboard" functions. When VB6-compiler compiles an module it puts all the functions in the same order as in the code. The needed condition is to ensure the unique returned value from each function. Then it goes through all the needed libraries (kernel32, ntdll, user32 - in this order) and their functions. The first item in the resource strings is the library name followed by the functions names in this library. When an item is obtained it translates the function name to ANSI format and calls GetProcAddress function. Afterwards it calls the Splice function that makes up the "springboard" to the needed function from the shellcode. Eventually, it modified the CallByPointer function in order to ensure the jump from the shellcode to the loaded exe. Okay, further the RunExeFromMemory function splices the CallLoader in order to ensure the jump to shellcode from the main executable. After this operation is done the function begins to form the error message table (if needs) that is just the set of pointers to the messages strings. Eventually it call the spliced CallLoader function that jumps to the LoadExeFromMemory shellcode function that has already not been placed in main exe.

  7. #7

    Thread Starter
    PowerPoster
    Join Date
    Feb 2015
    Posts
    2,671

    Re: [VB6] Loader, shellcode, without runtime...


    Inside shellcode.

    So, i made the several function inside the shellcode:
    1. LoadExeFromMemory - is the main function of the shellcode;
    2. GetImageNtHeaders - returns the IMAGE_NT_HEADERS structure and its address by the passed base address;
    3. GetDataDirectory - returns the IMAGE_DATA_DIRECTORY structure and its address by the passed base address and catalog index;
    4. EndProcess - shows the error message (if any) and ends of the process;
    5. ProcessSectionsAndHeaders - allocates the memory for all headers (DOS, NT, sections) and all the sections. Copies all data to the sections;
    6. ReserveMemory - reserves the sufficient memory for EXE;
    7. ProcessRelocations - adjusts the addresses if an exe has not been loaded to base address;
    8. ProcessImportTable - scans the import table of an exe file, loads the needed libraries and fills the import address table;
    9. SetMemoryPermissions - adjusts the memory permissions for each section;
    10. UpdateNewBaseAddress - refresh the new base address in the system structures PEB and LDR.

    Due to the fact i can't use the VarPtr function, i made the similar function using the lstrcpyn function - IntPtr. So, the 'LoadExeFromMemory' function obtain firstly the NT headers and checks the processor architecture, whether the PE file is executable and whether the PE file is 32 bit application. If it is succeeded then the shellcode unload the main exe file from memory using the ZwUnmapViewOfSection function. If function has been succeeded the main exe file isn't in the memory anymore and the memory occupied by exe has been released. Henceforth we can't directly use API function, we should use our "springboards":
    Code:
    ' // Parse exe in memory
    Function LoadExeFromMemory( _
                     ByVal pRawData As Long, _
                     ByVal pMyBaseAddress As Long, _
                     ByVal pErrMsgTable As Long) As Boolean
        Dim NtHdr   As IMAGE_NT_HEADERS
        Dim pBase   As Long
        Dim index   As Long
        Dim iError  As ERROR_MESSAGES
        Dim pszMsg  As Long
        
        ' // Get IMAGE_NT_HEADERS
        If GetImageNtHeaders(pRawData, NtHdr) = 0 Then
            iError = EM_UNABLE_TO_GET_NT_HEADERS
            EndProcess pErrMsgTable, iError
            Exit Function
        End If
        
        ' // Check flags
        If NtHdr.FileHeader.Machine <> IMAGE_FILE_MACHINE_I386 Or _
           (NtHdr.FileHeader.Characteristics And IMAGE_FILE_EXECUTABLE_IMAGE) = 0 Or _
           (NtHdr.FileHeader.Characteristics And IMAGE_FILE_32BIT_MACHINE) = 0 Then Exit Function
    
        ' // Release main EXE memory. After that main exe is unloaded from memory.
        ZwUnmapViewOfSection GetCurrentProcess(), GetModuleHandle(ByVal 0&)
    
        ' // Reserve memory for EXE
        iError = ReserveMemory(pRawData, pBase)
        If iError Then
            EndProcess pErrMsgTable, iError
            Exit Function
        End If
        
        ' // Place data
        iError = ProcessSectionsAndHeaders(pRawData, pBase)
        If iError Then
            EndProcess pErrMsgTable, iError
            Exit Function
        End If
        
        ' // Update new base address
        iError = UpdateNewBaseAddress(pBase)
        If iError Then
            EndProcess pErrMsgTable, iError
            Exit Function
        End If
        
        ' // Import table processing
        iError = ProcessImportTable(pBase)
        If iError Then
            EndProcess pErrMsgTable, iError
            Exit Function
        End If
        
        ' // Relocations processing
        iError = ProcessRelocations(pBase)
        If iError Then
            EndProcess pErrMsgTable, iError
            Exit Function
        End If
        
        ' // Set the memory attributes
        iError = SetMemoryPermissions(pBase)
        If iError Then
            EndProcess pErrMsgTable, iError
            Exit Function
        End If
        
        ' // Release error message table
        If pErrMsgTable Then
            tVirtualFree pErrMsgTable, 0, MEM_RELEASE
        End If
        
        ' // Call entry point
        CallByPointer NtHdr.OptionalHeader.AddressOfEntryPoint + pBase
        
        ' // End process
        EndProcess
        
    End Function
    Then shellcode calls the ReserveMemory function shown below. This function extracts the NT header from the loadable exe and tries to reserve the memory at 'ImageBase' address with the 'SizeOfImage' size. If it isn't succeeded the function checks if the exe file contains the relocation information. If so, it tries to reserve memory at any address. The relocation information allows to load an PE file to any address other than 'ImageBase'. It contains all the places where an exe uses the absolute addressing. You can adjust these places using the difference between the real base address and the 'ImageBase' field:
    Code:
    ' // Reserve memory for EXE
    Function ReserveMemory( _
                     ByVal pRawExeData As Long, _
                     ByRef pBase As Long) As ERROR_MESSAGES
        Dim NtHdr       As IMAGE_NT_HEADERS
        Dim pLocBase    As Long
        
        If GetImageNtHeaders(pRawExeData, NtHdr) = 0 Then
            ReserveMemory = EM_UNABLE_TO_GET_NT_HEADERS
            Exit Function
        End If
        
        ' // Reserve memory for EXE
        pLocBase = tVirtualAlloc(ByVal NtHdr.OptionalHeader.ImageBase, _
                              NtHdr.OptionalHeader.SizeOfImage, _
                              MEM_RESERVE, PAGE_EXECUTE_READWRITE)
        If pLocBase = 0 Then
            
            ' // If relocation information not found error
            If NtHdr.FileHeader.Characteristics And IMAGE_FILE_RELOCS_STRIPPED Then
            
                ReserveMemory = EM_UNABLE_TO_ALLOCATE_MEMORY
                Exit Function
                
            Else
                ' // Reserve memory in other region
                pLocBase = tVirtualAlloc(ByVal 0&, NtHdr.OptionalHeader.SizeOfImage, _
                                     MEM_RESERVE, PAGE_EXECUTE_READWRITE)
                
                If pLocBase = 0 Then
                
                    ReserveMemory = EM_UNABLE_TO_ALLOCATE_MEMORY
                    Exit Function
                    
                End If
    
            End If
            
        End If
        
        pBase = pLocBase
        
    End Function
    Okay, if memory reserving failed it shows the message with error and ends the application. Otherwise it calls the ProcessSectionsAndHeaders function. This function places all the headers to the allocated memory, extracts the information about all the sections and copies all the data to sections. If an section has the uninitialized data it fills this region with zero:
    Code:
    ' // Allocate memory for sections and copy them data to there
    Function ProcessSectionsAndHeaders( _
                     ByVal pRawExeData As Long, _
                     ByVal pBase As Long) As ERROR_MESSAGES
    
        Dim iSec    As Long
        Dim pNtHdr  As Long
        Dim NtHdr   As IMAGE_NT_HEADERS
        Dim sec     As IMAGE_SECTION_HEADER
        Dim lpSec   As Long
        Dim pData   As Long
        
        pNtHdr = GetImageNtHeaders(pRawExeData, NtHdr)
        If pNtHdr = 0 Then
            ProcessSectionsAndHeaders = EM_UNABLE_TO_GET_NT_HEADERS
            Exit Function
        End If
        
        ' // Alloc memory for headers
        pData = tVirtualAlloc(ByVal pBase, NtHdr.OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE)
        If pData = 0 Then
            ProcessSectionsAndHeaders = EM_UNABLE_TO_ALLOCATE_MEMORY
            Exit Function
        End If
        
        ' // Copy headers
        tCopyMemory pData, pRawExeData, NtHdr.OptionalHeader.SizeOfHeaders
        
        ' // Get address of beginnig of sections headers
        pData = pNtHdr + Len(NtHdr.Signature) + Len(NtHdr.FileHeader) + NtHdr.FileHeader.SizeOfOptionalHeader
        
        ' // Go thru sections
        For iSec = 0 To NtHdr.FileHeader.NumberOfSections - 1
        
            ' // Copy section descriptor
            tCopyMemory IntPtr(sec.SectionName(0)), pData, Len(sec)
            
            ' // Alloc memory for section
            lpSec = tVirtualAlloc(sec.VirtualAddress + pBase, sec.VirtualSize, MEM_COMMIT, PAGE_READWRITE)
            If lpSec = 0 Then
                ProcessSectionsAndHeaders = EM_UNABLE_TO_ALLOCATE_MEMORY
                Exit Function
            End If
            
            ' If there is initialized data
            If sec.SizeOfRawData Then
            
                ' // Take into account  file alignment
                If sec.SizeOfRawData > sec.VirtualSize Then sec.SizeOfRawData = sec.VirtualSize
                
                ' // Copy initialized data to section
                tCopyMemory lpSec, pRawExeData + sec.PointerToRawData, sec.SizeOfRawData
                lpSec = lpSec + sec.SizeOfRawData
                sec.VirtualSize = sec.VirtualSize - sec.SizeOfRawData
                
            End If
    
            ' // Fill remain part with zero
            tFillMemory lpSec, sec.VirtualSize, 0
            
            ' // Next section
            pData = pData + Len(sec)
            
        Next
        
    End Function
    Then the LoadExeFromMemory function calls the UpdateNewBaseAddress function that update the new base address in the user-mode system structures. Windows creates the special stucture named PEB (Process Environment Block) for each process. This is the very usefull structure that allows to obtain the very many information about the process. Many API functions gets information from this structure. For example GetModuleHandle(NULL) takes the returned value from the PEB.ImageBaseAddress or GetModuleHandle("MyExename") takes the returned value from the PEB.Ldr list of the loaded modules. We should update this information according the new base address in order to API functions retrieve the correct values. The small part of PEB structure is shown below:
    Code:
    Type PEB
        NotUsed                         As Long
        Mutant                          As Long
        ImageBaseAddress                As Long
        LoaderData                      As Long ' // Pointer to PEB_LDR_DATA
        ProcessParameters               As Long
        ' // ....
    End Type
    We are interested only the 'ImageBaseAddress' and 'LoaderData' fields. The first field contains the base address of an exe file. The second field contains the pointer to the PEB_LDR_DATA structure that describes all the loaded modules in the process:
    Code:
    Type PEB_LDR_DATA
        Length                          As Long
        Initialized                     As Long
        SsHandle                        As Long
        InLoadOrderModuleList           As LIST_ENTRY
        InMemoryOrderModuleList         As LIST_ENTRY
        InInitializationOrderModuleList As LIST_ENTRY
    End Type
    This structure contains the three doubly-linked lists that describe each module. The 'InLoadOrderModuleList' list contains the links to items in the loading oreder item, i.e. the items in this list is placed in loading order (the first module is at beginning). The 'InMemoryOrderModuleList' is same only in order of placing in memory, 'InInitializationOrderModuleList' in initialization order. We should get the first element of 'InLoadOrderModuleList' list that is the pointer to structure LDR_MODULE:
    Code:
    Type LDR_MODULE
        InLoadOrderModuleList           As LIST_ENTRY
        InMemoryOrderModuleList         As LIST_ENTRY
        InInitOrderModuleList           As LIST_ENTRY
        BaseAddress                     As Long
        EntryPoint                      As Long
        SizeOfImage                     As Long
        FullDllName                     As UNICODE_STRING
        BaseDllName                     As UNICODE_STRING
        Flags                           As Long
        LoadCount                       As Integer
        TlsIndex                        As Integer
        HashTableEntry                  As LIST_ENTRY
        TimeDateStamp                   As Long
    End Type
    This structure describes an module. The first element of 'InLoadOrderModuleList' is the main exe module descriptor. We should change the 'BaseAddress' field to new value and save changes. So, in order to obtain the PEB structure we can use the universal function NtQueryInformationProcess that extract the many useful information about process (read more in 'Windows NT/2000 Native API Reference' by Gary Nebbett). The PEB structure can be obtained from the PROCESS_BASIC_INFORMATION structure that describes the basic information about the process:
    Code:
    Type PROCESS_BASIC_INFORMATION
        ExitStatus                      As Long
        PebBaseAddress                  As Long
        AffinityMask                    As Long
        BasePriority                    As Long
        UniqueProcessId                 As Long
        InheritedFromUniqueProcessId    As Long
    End Type
    The 'PebBaseAddress' field contains the address of the PEB structure.

  8. #8

    Thread Starter
    PowerPoster
    Join Date
    Feb 2015
    Posts
    2,671

    Re: [VB6] Loader, shellcode, without runtime...

    In order to obtain the PROCESS_BASIC_INFORMATION structure we should pass the ProcessBasicInformation as the class information to NtQueryInformationProcess function. Because of structure size may change in various versions of Windows i use the heap memory for extracting the PROCESS_BASIC_INFORMATION structure. If the size doesn't suit it increases the size and repeats again:
    Code:
    Function UpdateNewBaseAddress( _
                     ByVal pBase As Long) As ERROR_MESSAGES
        Dim pPBI    As Long:                        Dim PBIlen  As Long
        Dim PBI     As PROCESS_BASIC_INFORMATION:   Dim cPEB    As PEB
        Dim ntstat  As Long
        Dim ldrData As PEB_LDR_DATA
        Dim ldrMod  As LDR_MODULE
        
        ntstat = tNtQueryInformationProcess(tGetCurrentProcess(), ProcessBasicInformation, IntPtr(PBI.ExitStatus), Len(PBI), PBIlen)
        
        Do While ntstat = STATUS_INFO_LENGTH_MISMATCH
            
            PBIlen = PBIlen * 2
            
            If pPBI Then
                tHeapFree tGetProcessHeap(), HEAP_NO_SERIALIZE, pPBI
            End If
            
            pPBI = tHeapAlloc(tGetProcessHeap(), HEAP_NO_SERIALIZE, PBIlen)
            ntstat = tNtQueryInformationProcess(tGetCurrentProcess(), ProcessBasicInformation, pPBI, PBIlen, PBIlen)
            
        Loop
        
        If ntstat <> STATUS_SUCCESS Then
            UpdateNewBaseAddress = EM_PROCESS_INFORMATION_NOT_FOUND
            GoTo CleanUp
        End If
        
        If pPBI Then
            ' // Copy to PROCESS_BASIC_INFORMATION
            tCopyMemory IntPtr(PBI.ExitStatus), pPBI, Len(PBI)
        End If
    
        ' // Get PEB
        tCopyMemory IntPtr(cPEB.NotUsed), PBI.PebBaseAddress, Len(cPEB)
        
        ' // Modify image base
        cPEB.ImageBaseAddress = pBase
        
        ' // Restore PEB
        tCopyMemory PBI.PebBaseAddress, IntPtr(cPEB.NotUsed), Len(cPEB)
        
        ' // Fix base address in PEB_LDR_DATA list
        tCopyMemory IntPtr(ldrData.Length), cPEB.LoaderData, Len(ldrData)
        
        ' // Get first element
        tCopyMemory IntPtr(ldrMod.InLoadOrderModuleList.Flink), ldrData.InLoadOrderModuleList.Flink, Len(ldrMod)
        
        ' // Fix base
        ldrMod.BaseAddress = pBase
        
        ' // Restore
        tCopyMemory ldrData.InLoadOrderModuleList.Flink, IntPtr(ldrMod.InLoadOrderModuleList.Flink), Len(ldrMod)
        
    CleanUp:
        
        ' // Free memory
        If pPBI Then
            tHeapFree tGetProcessHeap(), HEAP_NO_SERIALIZE, pPBI
        End If
        
    End Function
    After updating of the base address in the system structures the shellcode calls the ProcessImportTable function that loads the needed libraryes for exe file. Firstly it gets the IMAGE_DIRECTORY_ENTRY_IMPORT directory that contains the RVA of the array of the IMAGE_IMPORT_DESCRIPTOR structures:
    Code:
    Type IMAGE_IMPORT_DESCRIPTOR
        Characteristics                 As Long
        TimeDateStamp                   As Long
        ForwarderChain                  As Long
        pName                           As Long
        FirstThunk                      As Long
    End Type
    Each structure describes the single DLL. The 'pName' field contains the RVA to the ASCIIZ library name. The 'Characteristics' field contains the RVA to the table of the imported function names and 'FirstThunk' contains the RVA of the import addresses table. The names table is the array of IMAGE_THUNK_DATA structures that is the 32 bit Long value. If the most significant bit is set the remaining bits represents the ordinal of the function (import by ordinal). Otherwise the remaining bits contains the RVA of the function name prenexed by 'Hint' value. If the IMAGE_THUNK_DATA structure contains zero it means that no more names. If all the fields of the IMAGE_IMPORT_DESCRIPTOR equal zero it means that list of structureas is ended.
    Code:
    ' // Process import table
    Function ProcessImportTable( _
                     ByVal pBase As Long) As ERROR_MESSAGES
        Dim NtHdr           As IMAGE_NT_HEADERS:        Dim datDirectory    As IMAGE_DATA_DIRECTORY
        Dim dsc             As IMAGE_IMPORT_DESCRIPTOR: Dim hLib            As Long
        Dim thnk            As Long:                    Dim Addr            As Long
        Dim fnc             As Long:                    Dim pData           As Long
            
        If GetImageNtHeaders(pBase, NtHdr) = 0 Then
            ProcessImportTable = EM_UNABLE_TO_GET_NT_HEADERS
            Exit Function
        End If
        
        ' // Import table processing
        If NtHdr.OptionalHeader.NumberOfRvaAndSizes > 1 Then
            
            If GetDataDirectory(pBase, IMAGE_DIRECTORY_ENTRY_IMPORT, datDirectory) = 0 Then
                ProcessImportTable = EM_INVALID_DATA_DIRECTORY
                Exit Function
            End If
    
            ' // If import table exists
            If datDirectory.Size > 0 And datDirectory.VirtualAddress > 0 Then
            
                ' // Copy import descriptor
                pData = datDirectory.VirtualAddress + pBase
                tCopyMemory IntPtr(dsc.Characteristics), pData, Len(dsc)
                
                ' // Go thru all descriptors
                Do Until dsc.Characteristics = 0 And _
                         dsc.FirstThunk = 0 And _
                         dsc.ForwarderChain = 0 And _
                         dsc.pName = 0 And _
                         dsc.TimeDateStamp = 0
                    
                    If dsc.pName > 0 Then
                    
                        ' // Load needed library
                        hLib = tLoadLibrary(dsc.pName + pBase)
                        
                        If hLib = 0 Then
                            ProcessImportTable = EM_LOADLIBRARY_FAILED
                            Exit Function
                        End If
    
                        If dsc.Characteristics Then fnc = dsc.Characteristics + pBase Else fnc = dsc.FirstThunk + pBase
                        
                        ' // Go to names table
                        tCopyMemory IntPtr(thnk), fnc, 4
                        
                        ' // Go thru names table
                        Do While thnk
                        
                            ' // Check import type
                            If thnk < 0 Then
                                ' // By ordinal
                                Addr = tGetProcAddress(hLib, thnk And &HFFFF&)
                            Else
                                ' // By name
                                Addr = tGetProcAddress(hLib, thnk + 2 + pBase)
                            End If
                            
                            ' // Next function
                            fnc = fnc + 4
                            tCopyMemory IntPtr(thnk), fnc, 4
                            tCopyMemory dsc.FirstThunk + pBase, IntPtr(Addr), 4
                            dsc.FirstThunk = dsc.FirstThunk + 4
                            
                        Loop
                        
                    End If
                    
                    ' // Next descriptor
                    pData = pData + Len(dsc)
                    tCopyMemory IntPtr(dsc.Characteristics), pData, Len(dsc)
                    
                Loop
                
            End If
            
        End If
                     
    End Function
    The ProcessRelocation function is called then. This functions adjust all the absolute references (if any). It obtains the IMAGE_DIRECTORY_ENTRY_BASERELOC catalog that contains the RVA to the array of IMAGE_BASE_RELOCATION structures. Each item in this list contains the settings within 4KB relative 'VirtualAddress' fields:
    Code:
    Type IMAGE_BASE_RELOCATION
        VirtualAddress                  As Long
        SizeOfBlock                     As Long
    End Type
    The 'SizeOfBlock' contains the size of item in bytes. The array of 16 bits numbers is placed after the each IMAGE_BASE_RELOCATION structure. You can calculate number of this strucuture as (SizeOfBlock - Len(IMAGE_BASE_RELOCATION)) \ Len(Integer). Each element of the array of the descriptors has the following structure:
    Name:  REloc.png
Views: 6442
Size:  8.8 KB
    The high four bits contains the type of relocation. We are interested the IMAGE_REL_BASED_HIGHLOW type that means we should add the difference (RealBaseAddress - ImageBaseAddress) to a Long that is at the address 'VirtualAddress' + 12 least bits of descriptors. Array of IMAGE_BASE_RELOCATION structures is ended with stucture where all fields is zero:
    Code:
    ' // Process relocations
    Function ProcessRelocations( _
                     ByVal pBase As Long) As ERROR_MESSAGES
        Dim NtHdr           As IMAGE_NT_HEADERS:        Dim datDirectory    As IMAGE_DATA_DIRECTORY
        Dim relBase         As IMAGE_BASE_RELOCATION:   Dim entriesCount    As Long
        Dim relType         As Long:                    Dim dwAddress       As Long
        Dim dwOrig          As Long:                    Dim pRelBase        As Long
        Dim delta           As Long:                    Dim pData           As Long
        
        ' // Check if module has not been loaded to image base value
        If GetImageNtHeaders(pBase, NtHdr) = 0 Then
            ProcessRelocations = EM_UNABLE_TO_GET_NT_HEADERS
            Exit Function
        End If
        
        delta = pBase - NtHdr.OptionalHeader.ImageBase
        
        ' // Process relocations
        If delta Then
            
            ' // Get address of relocation table
            If GetDataDirectory(pBase, IMAGE_DIRECTORY_ENTRY_BASERELOC, datDirectory) = 0 Then
                ProcessRelocations = EM_INVALID_DATA_DIRECTORY
                Exit Function
            End If
            
            If datDirectory.Size > 0 And datDirectory.VirtualAddress > 0 Then
            
                ' // Copy relocation base
                pRelBase = datDirectory.VirtualAddress + pBase
                tCopyMemory IntPtr(relBase.VirtualAddress), pRelBase, Len(relBase)
                
                Do While relBase.VirtualAddress
                
                    ' // To first reloc chunk
                    pData = pRelBase + Len(relBase)
                    
                    entriesCount = (relBase.SizeOfBlock - Len(relBase)) \ 2
                    
                    Do While entriesCount > 0
                        
                        tCopyMemory IntPtr(relType), pData, 2
                        
                        Select Case (relType \ 4096) And &HF
                        Case IMAGE_REL_BASED_HIGHLOW
                            
                            ' // Calculate address
                            dwAddress = relBase.VirtualAddress + (relType And &HFFF&) + pBase
                            
                            ' // Get original address
                            tCopyMemory IntPtr(dwOrig), dwAddress, Len(dwOrig)
                            
                            ' // Add delta
                            dwOrig = dwOrig + delta
                            
                            ' // Save
                            tCopyMemory dwAddress, IntPtr(dwOrig), Len(dwOrig)
                            
                        End Select
                        
                        pData = pData + 2
                        entriesCount = entriesCount - 1
                        
                    Loop
                    
                    ' // Next relocation base
                    pRelBase = pRelBase + relBase.SizeOfBlock
                    tCopyMemory IntPtr(relBase.VirtualAddress), pRelBase, Len(relBase)
                    
                Loop
                
            End If
            
        End If
    
    End Function

  9. #9

    Thread Starter
    PowerPoster
    Join Date
    Feb 2015
    Posts
    2,671

    Re: [VB6] Loader, shellcode, without runtime...

    After relocations processing shellcode calls the function SetMemoryPermissions that adjusts the memory protection for each section according to the 'Characteristics' field of IMAGE_SECTION_HEADER structure. It just calls the VirtualProtect function with the certain memory attributes:
    Code:
    ' // Set memory permissions
    Private Function SetMemoryPermissions( _
                     ByVal pBase As Long) As ERROR_MESSAGES
        Dim iSec    As Long:                    Dim pNtHdr  As Long
        Dim NtHdr   As IMAGE_NT_HEADERS:        Dim sec     As IMAGE_SECTION_HEADER
        Dim Attr    As MEMPROTECT:              Dim pSec    As Long
        Dim ret     As Long
        
        pNtHdr = GetImageNtHeaders(pBase, NtHdr)
        If pNtHdr = 0 Then
            SetMemoryPermissions = EM_UNABLE_TO_GET_NT_HEADERS
            Exit Function
        End If
    
        ' // Get address of first section header
        pSec = pNtHdr + 4 + Len(NtHdr.FileHeader) + NtHdr.FileHeader.SizeOfOptionalHeader
        
        ' // Go thru section headers
        For iSec = 0 To NtHdr.FileHeader.NumberOfSections - 1
        
            ' // Copy section descriptor
            tCopyMemory IntPtr(sec.SectionName(0)), pSec, Len(sec)
            
            ' // Get type
            If sec.Characteristics And IMAGE_SCN_MEM_EXECUTE Then
                If sec.Characteristics And IMAGE_SCN_MEM_READ Then
                    If sec.Characteristics And IMAGE_SCN_MEM_WRITE Then
                        Attr = PAGE_EXECUTE_READWRITE
                    Else
                        Attr = PAGE_EXECUTE_READ
                    End If
                Else
                    If sec.Characteristics And IMAGE_SCN_MEM_WRITE Then
                        Attr = PAGE_EXECUTE_WRITECOPY
                    Else
                        Attr = PAGE_EXECUTE
                    End If
                End If
            Else
                If sec.Characteristics And IMAGE_SCN_MEM_READ Then
                    If sec.Characteristics And IMAGE_SCN_MEM_WRITE Then
                        Attr = PAGE_READWRITE
                    Else
                        Attr = PAGE_READONLY
                    End If
                Else
                    If sec.Characteristics And IMAGE_SCN_MEM_WRITE Then
                        Attr = PAGE_WRITECOPY
                    Else
                        Attr = PAGE_NOACCESS
                    End If
                End If
            End If
            
            ' // Set memory permissions
            If tVirtualProtect(sec.VirtualAddress + pBase, sec.VirtualSize, Attr, IntPtr(ret)) = 0 Then
                SetMemoryPermissions = EM_UNABLE_TO_PROTECT_MEMORY
                Exit Function
            End If
            
            ' // Next section
            pSec = pSec + Len(sec)
            
        Next
        
    End Function
    Eventually it frees the message table (if any) and calls the entry point of the loaded exe. In the previous version of the loader i unloaded the shellcode too but some exe doesn't call ExitProcess therefore it can causes the crash. The loader has been done.
    Although we write the loader without runtime usage the VB6 compiler adds it because all the OBJ files has references to MSVBVM60 during compilation. We have to remove the runtime from the import table of the loader manually. I made the special utility - Patcher that searches runtime in the import table and the bound import table and removes it. This utility is helpful for the VB kernel drivers too. I won't describe the its work because it uses same concepts of the PE format that we already examined. Overall we get the working exe that doesn't use MSVBVM60 runtime on the target machine.

    In order to use the loader you should compile it then you should run the patcher and patch compiled loader. Afterwards you can use the compiler.

    I hope you enjoyed it. Thank for attention!
    Regards,
    Кривоус Анатолий (The trick).
    Attached Files Attached Files

  10. #10
    PowerPoster Elroy's Avatar
    Join Date
    Jun 2014
    Location
    Near Nashville TN
    Posts
    9,853

    Re: [VB6] Loader, shellcode, without runtime...

    Absolutely incredible. I'm still a ways from digesting all of this, but it's just an amazing exposé of the guts of an executable.

    Many Thanks,
    Elroy
    Any software I post in these forums written by me is provided "AS IS" without warranty of any kind, expressed or implied, and permission is hereby granted, free of charge and without restriction, to any person obtaining a copy. To all, peace and happiness.

  11. #11
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    5,651

    Re: [VB6] Loader, shellcode, without runtime...

    Wow, amazing work. You've managed to raise the bar for VB6 capabilities once again.

  12. #12
    Fanatic Member
    Join Date
    Aug 2016
    Posts
    673

    Re: [VB6] Loader, shellcode, without runtime...

    Code:
    VERSION 1.0 CLASS
    BEGIN
      MultiUse = -1  'True
      Persistable = 0  'NotPersistable
      DataBindingBehavior = 0  'vbNone
      DataSourceBehavior  = 0  'vbNone
      MTSTransactionMode  = 0  'NotAnMTSObject
    END
    Attribute VB_Name = "clsStream"
    Attribute VB_GlobalNameSpace = False
    Attribute VB_Creatable = True
    Attribute VB_PredeclaredId = False
    Attribute VB_Exposed = False
    ' // clsStream.cls - binary stream class
    ' // ?Krivous Anatoly Anatolevich (The trick), 2016
    
    Option Explicit
    
    Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
    
    Private Declare Function ReadFile _
                    Lib "kernel32" (ByVal hFile As Long, _
                                    ByRef lpBuffer As Any, _
                                    ByVal nNumberOfBytesToRead As Long, _
                                    ByRef lpNumberOfBytesRead As Long, _
                                    ByRef lpOverlapped As Any) As Long
    
    Private Declare Function GetFileSizeEx _
                    Lib "kernel32" (ByVal hFile As Long, _
                                    ByRef lpFileSize As LARGE_INTEGER) As Long
    
    Private Declare Function CreateFile _
                    Lib "kernel32" _
                    Alias "CreateFileW" (ByVal lpFileName As Long, _
                                         ByVal dwDesiredAccess As Long, _
                                         ByVal dwShareMode As Long, _
                                         ByRef lpSecurityAttributes As Any, _
                                         ByVal dwCreationDisposition As Long, _
                                         ByVal dwFlagsAndAttributes As Long, _
                                         ByVal hTemplateFile As Long) As Long
    
    Private Declare Function WriteFile _
                    Lib "kernel32" (ByVal hFile As Long, _
                                    ByRef lpBuffer As Any, _
                                    ByVal nNumberOfBytesToWrite As Long, _
                                    ByRef lpNumberOfBytesWritten As Long, _
                                    ByRef lpOverlapped As Any) As Long
    
    Private Declare Sub ZeroMemory _
                    Lib "kernel32" _
                    Alias "RtlZeroMemory" (ByRef dest As Any, _
                                           ByVal numBytes As Long)
    
    Private Declare Sub CopyMemory _
                    Lib "kernel32" _
                    Alias "RtlMoveMemory" (ByRef Destination As Any, _
                                           ByRef Source As Any, _
                                           ByVal Length As Long)
    
    Private Declare Function RtlCompressBuffer _
                    Lib "ntdll" (ByVal CompressionFormatAndEngine As Integer, _
                                 ByRef UncompressedBuffer As Any, _
                                 ByVal UncompressedBufferSize As Long, _
                                 ByRef CompressedBuffer As Any, _
                                 ByVal CompressedBufferSize As Long, _
                                 ByVal UncompressedChunkSize As Long, _
                                 ByRef FinalCompressedSize As Long, _
                                 ByRef WorkSpace As Any) As Long
    
    Private Declare Function IsBadWritePtr _
                    Lib "kernel32" (ByRef lp As Any, _
                                    ByVal ucb As Long) As Long
    
    Private Declare Function RtlGetCompressionWorkSpaceSize _
                    Lib "ntdll" (ByVal CompressionFormatAndEngine As Integer, _
                                 ByRef CompressBufferWorkSpaceSize As Long, _
                                 ByRef CompressFragmentWorkSpaceSize As Long) As Long
    
    Private Declare Function IsBadReadPtr _
                    Lib "kernel32" (ByRef lp As Any, _
                                    ByVal ucb As Long) As Long
    
    Private Const GRANULARITY              As Long = &H400  ' // Glanularity of memory allocation
    
    Private Const COMPRESSION_FORMAT_LZNT1 As Long = 2
    
    Private mBuffer()                      As Byte ' // Local buffer
    
    Private mSizeOfBuffer                  As Long ' // Size of buffer
    
    Const MAX_PATH                         As Long = 260
    
    Const RT_RCDATA                        As Long = 10&
    
    Private Const GENERIC_WRITE            As Long = &H40000000
    
    Const INVALID_HANDLE_VALUE             As Long = -1
    
    Private Const GENERIC_READ             As Long = &H80000000
    
    Private Const CREATE_ALWAYS            As Long = 2
    
    Private Const OPEN_EXISTING            As Long = 3
    
    Private Const FILE_SHARE_READ          As Long = &H1
    
    Private Const FILE_ATTRIBUTE_NORMAL    As Long = &H80
    
    Private mSizeOfData                    As Long ' // Size of data
    
    Private Type LARGE_INTEGER
    
        lowpart                     As Long
        highpart                    As Long
    
    End Type
    
    Private mCurIndex As Long ' // Current pointer
    
    Private Declare Function GetProcessHeap Lib "kernel32" () As Long
    
    Private Declare Function HeapAlloc _
                    Lib "kernel32" (ByVal hHeap As Long, _
                                    ByVal dwFlags As Long, _
                                    ByVal dwBytes As Long) As Long
    
    Private Declare Function HeapReAlloc _
                    Lib "kernel32" (ByVal hHeap As Long, _
                                    ByVal dwFlags As Long, _
                                    lpMem As Any, _
                                    ByVal dwBytes As Long) As Long
    
    Private Declare Function RtlDecompressBuffer _
            Lib "ntdll.dll" (ByVal CompressionFormat As Integer, _
                             ByVal ptrDestBuffer As Any, _
                             ByVal DestBufferSize As Long, _
                             ByVal ptrSrceBuffer As Any, _
                             ByVal SceBufferSize As Long, _
                             ByRef pDestinationSize As Long) As Long
    Private Declare Function HeapFree Lib "kernel32" (ByVal hHeap As Long, ByVal dwFlags As Long, lpMem As Any) As Long
    
    ' // Size of data
    Public Property Get Size() As Long
        Size = mSizeOfData
    
    End Property
    
    ' // Current pointer
    Public Property Get BufferPointer() As Long
        BufferPointer = mCurIndex
    
    End Property
    
    Public Property Let BufferPointer(ByVal Value As Long)
    
        If Value < 0 Then
            Err.Raise 5
            Exit Property
    
        End If
    
        mCurIndex = Value
        
    End Property
    
    ' // Compress stream
    Public Sub CompressStream()
    
        Dim szWorkSpace  As Long
    
        Dim WorkSpace()  As Byte
    
        Dim output()     As Byte
    
        Dim outputSize   As Long
    
        Dim returnedSize As Long
            
        If mSizeOfData = 0 Then
            Err.Raise 7
    
        End If
        
        If RtlGetCompressionWorkSpaceSize(COMPRESSION_FORMAT_LZNT1, szWorkSpace, 0) Then
            Err.Raise 7
    
        End If
        
        outputSize = mSizeOfData * 2
        ReDim WorkSpace(szWorkSpace - 1)
        ReDim output(outputSize - 1)
        
        If RtlCompressBuffer(COMPRESSION_FORMAT_LZNT1, mBuffer(0), mSizeOfData, output(0), outputSize, 4096, returnedSize, WorkSpace(0)) Then
            Err.Raise 7
    
        End If
        
        ReDim mBuffer(returnedSize - 1)
        
        CopyMemory mBuffer(0), output(0), returnedSize
        
        mSizeOfData = returnedSize
        
    End Sub
    Attached Images Attached Images    
    Last edited by xxdoc123; Mar 8th, 2017 at 01:09 AM.

  13. #13

  14. #14
    New Member
    Join Date
    Dec 2017
    Posts
    2

    Re: [VB6] Loader, shellcode, without runtime...

    Quote Originally Posted by The trick View Post
    This is the function features and compression algorithm. For example it has 2 bytes header.

    You should look at the launcher solution, there is the code for decompression data.
    is there any means of contacting you directly ? via skype or email ?

  15. #15

  16. #16
    New Member
    Join Date
    Dec 2017
    Posts
    2

    Re: [VB6] Loader, shellcode, without runtime...

    Quote Originally Posted by The trick View Post
    Write me in PM.
    okay , sent

  17. #17
    New Member
    Join Date
    Nov 2016
    Location
    Bali, Indonesia
    Posts
    14

    Re: [VB6] Loader, shellcode, without runtime...

    i wish i can understand what are you doing but sadly my vb knowledge still far far away from enough to understand all of this. Your contribution for the vb6 community need more appreciation. you are awesome bro


    edit: anyway your compiler output results detected as worm/trojan by antivirus
    Last edited by putu.oka; Dec 3rd, 2017 at 12:38 PM.

  18. #18
    Member Dragokas's Avatar
    Join Date
    Aug 2015
    Location
    Ukraine
    Posts
    740

    Re: [VB6] Loader, shellcode, without runtime...

    edit: anyway your compiler output results detected as worm/trojan by antivirus
    It is highly depends on software you run by loader.
    What AV vendor do you have problem with?
    What is your software purpose and what kind of activity is it produces after run?
    Malware analyst, VirusNet developer, HiJackThis+ author || my CodeBank works

  19. #19
    Member Dragokas's Avatar
    Join Date
    Aug 2015
    Location
    Ukraine
    Posts
    740

    Re: [VB6] Loader, shellcode, without runtime...

    Here is a fix for clsStream.cls.
    - Caused crash in second call to .ReadData method when first call set incorrect cursor position after requesting the length that exceeded EOF.
    Attached Files Attached Files

  20. #20

  21. #21
    Hyperactive Member
    Join Date
    Apr 2015
    Posts
    356

    Re: [VB6] Loader, shellcode, without runtime...

    Dear Trick,

    I had taken a standard exe application with default form1 and compiled to exe.Using your patcher I created new exe without runtime.After that I am unable to run this new exe created without runtime.How to use either the other two apps in launcher or compiler to run this new exe.
    1.Please explain with example.
    2.Can the same way of running an application without runtime can be used in multithreaded application.Please clarify.


    regards,
    JSVenu

  22. #22
    Hyperactive Member
    Join Date
    Jan 2015
    Posts
    323

    Re: [VB6] Loader, shellcode, without runtime...

    i noticed there is a function "RunExeFromMemory"
    can you add a optional parameter argsForExe?

  23. #23

  24. #24
    Hyperactive Member
    Join Date
    Jan 2015
    Posts
    323

    Re: [VB6] Loader, shellcode, without runtime...

    i want to embed some exe in type of res file, in another exe
    and call it from memory, for not expose the embed res exe file.
    i m only a new comer, have no ability to write malware at all.

  25. #25

  26. #26
    PowerPoster Arnoutdv's Avatar
    Join Date
    Oct 2013
    Posts
    5,872

    Re: [VB6] Loader, shellcode, without runtime...

    Quote Originally Posted by loquat View Post
    i want to embed some exe in type of res file, in another exe
    and call it from memory, for not expose the embed res exe file.
    i m only a new comer, have no ability to write malware at all.

  27. #27
    New Member
    Join Date
    Mar 2021
    Location
    Singapore
    Posts
    12

    Re: [VB6] Loader, shellcode, without runtime...

    It's a great job.
    Thanks!

  28. #28
    Hyperactive Member
    Join Date
    Jan 2015
    Posts
    323

    Re: [VB6] Loader, shellcode, without runtime...

    Quote Originally Posted by The trick View Post
    What's the troubles?
    some exe are compile by dotnet
    so my vb6 program cannot run it from memory

  29. #29

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width