PART 1 (by Krivous Anatoly Anatolevich)
The description of PE format. Creation of the simple VB6-EXE loader/packer. The VB6-loader without the runtime (msvbvm60) dependencies. And so on...
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 programmer's says that a VB6-application 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:
Compiler - it is the biggest project of all. It creates an installation based on the loader, user files, commands and manifest;
Loader - it is the simple loader that performs commands, unpacks files and runs an executable from memory;
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):
- application installed path;
- system windows directory;
- System32 directory;
- system drive;
- temporary directory;
- user desktop.
Compiler
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:
' // Storage list item Private Type BinStorageListItem ofstFileNameAs Long ' // Offset of file name ofstDestPathAs Long ' // Offset of file path dwSizeOfFileAs Long ' // Size of file ofstBeginOfDataAs Long ' // Offset of beginning data dwFlagsAs FileFlags' // Flags End Type ' // Execute list item Private Type BinExecListItem ofstFileNameAs Long ' // Offset of file name ofstParametersAs Long ' // Offset of parameters dwFlagsAs ExeFlags' // Flags End Type ' // Storage descriptor Private Type BinStorageList dwSizeOfStructure As Long ' // Size of structure iExecutableIndex As Long ' // Index of main executable dwSizeOfItemAs Long ' // Size of BinaryStorageItem structure dwNumberOfItemsAs Long ' // Number of files in storage End Type ' // Execute list descriptor Private Type BinExecList dwSizeOfStructure As Long ' // Size of structure dwSizeOfItemAs Long ' // Size of BinaryExecuteItem structure dwNumberOfItemsAs 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 dwFileTableLenAs 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:
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.
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:
Type IMAGE_DOS_HEADER e_magicAs Integer e_cblpAs Integer e_cpAs Integer e_crlcAs Integer e_cparhdr As Integer e_minalloc As Integer e_maxalloc As Integer e_ssAs Integer e_spAs Integer e_csumAs Integer e_ipAs Integer e_csAs Integer e_lfarlc As Integer e_ovnoAs Integer e_res(0 To 3)As Integer e_oemidAs 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:
Type IMAGE_NT_HEADERS SignatureAs Long FileHeaderAs 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:
Type IMAGE_FILE_HEADER Machine As Integer NumberOfSectionsAs 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:
Type IMAGE_OPTIONAL_HEADER Magic As Integer MajorLinkerVersionAs Byte MinorLinkerVersionAs Byte SizeOfCodeAs Long SizeOfInitializedData As Long SizeOfUnitializedData As Long AddressOfEntryPointAs Long BaseOfCodeAs Long BaseOfDataAs Long ImageBaseAs Long SectionAlignmentAs Long FileAlignment As Long MajorOperatingSystemVersionAs Integer MinorOperatingSystemVersionAs Integer MajorImageVersionAs Integer MinorImageVersionAs Integer MajorSubsystemVersion As Integer MinorSubsystemVersion As Integer W32VersionValue As Long SizeOfImageAs Long SizeOfHeaders As Long CheckSumAs Long SubSystemAs Integer DllCharacteristicsAs Integer SizeOfStackReserveAs Long SizeOfStackCommitAs Long SizeOfHeapReserveAs Long SizeOfHeapCommitAs Long LoaderFlagsAs Long NumberOfRvaAndSizesAs 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:
Type IMAGE_SECTION_HEADER SectionName(7)As Byte VirtualSize As Long VirtualAddressAs Long SizeOfRawDataAs Long PointerToRawData As Long PointerToRelocationsAs Long PointerToLinenumbersAs Long NumberOfRelocations As Integer NumberOfLinenumbers As Integer CharacteristicsAs 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:
' // 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.
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:
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:
Type IMAGE_RESOURCE_DIRECTORY CharacteristicsAs Long TimeDateStampAs Long MajorVersionAs Integer MinorVersionAs Integer NumberOfNamedEntriesAs 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:
Type IMAGE_RESOURCE_DIRECTORY_ENTRY NameIdAs Long OffsetToDataAs 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:
Type IMAGE_RESOURCE_DIR_STRING_U LengthAs 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:
Type IMAGE_RESOURCE_DATA_ENTRY OffsetToDataAs Long SizeAs 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.
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:
- Set an user function as startup;
- Avoid any objects in project;
- Avoid immediate arrays. The fixed arrays in a type is not forbidden;
- Avoid string variables as well Variant/Object. In some cases Currency/Date;
- Avoid the API functions with Declare statement.
- Avoid VarPtr and some other standard functions.
- ....
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:
- modMain - startup function and working with storage/execute items;
- modConstants - working with string constants;
- modLoader - loader of EXE files.
When loader has been launched it starts the Main function.
' // 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:
' // 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 ' // 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 ctlAs 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:
' // Load project Function ReadProject() As Boolean Dim hResourceAs Long:Dim hMememoryAs Long Dim lResSizeAs Long:Dim pRawDataAs Long Dim status As Long:Dim pUncompressed As Long Dim lUncompressSize As Long:Dim lResultSizeAs 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.
If it is succeeded then it launches the 'CopyProcess' procedure that unpacks all the storage items according project data:
' // Copying process Function CopyProcess() As Boolean Dim bItemAs BinStorageListItem: Dim indexAs Long Dim pPathAs Long:Dim dwWritten As Long Dim msg As Long:Dim lStepAs Long Dim isErrorAs Boolean:Dim pItemAs Long Dim pErrMsgAs 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.
' // Create all subdirectories by path Function CreateSubdirectories( _ ByVal pPath As Long) As Boolean Dim pComponent As Long Dim tCharAs 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 lPtrAs 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:
' // Execution command process Function ExecuteProcess() As Boolean Dim indexAs Long:Dim bItemAs BinExecListItem Dim pPathAs Long:Dim pErrMsgAs Long Dim shInfoAs SHELLEXECUTEINFO: Dim pTempString As Long Dim pItemAs Long:Dim statusAs 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.
If it is succeeded then it is call the 'RunProcess' function that prepares the data for executing main executable from memory.
' // Run exe from project in memory Function RunProcess() As Boolean Dim bItemAs BinStorageListItem: Dim LengthAs 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:
' // Run EXE file by memory address Function RunExeFromMemory( _ ByVal pExeData As Long, _ ByVal IgnoreError As Boolean) As Boolean Dim Length As Long: Dim pCodeAs Long Dim pszMsg As Long: Dim pMsgTable As Long Dim index As Long: Dim pCurMsgAs 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:
' // 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 uni, que 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.
No comments:
Post a Comment