PART 2 (by Krivous Anatoly Anatolevich)
Download from ME
Download from PSC
Inside shellcode
So, i made the several function inside the shellcode:
LoadExeFromMemory - is the main function of the shellcode;
GetImageNtHeaders - returns the IMAGE_NT_HEADERS structure and its address by the passed base address;
GetDataDirectory - returns the IMAGE_DATA_DIRECTORY structure and its address by the passed base address and catalog index;
EndProcess - shows the error message (if any) and ends of the process;
ProcessSectionsAndHeaders - allocates the memory for all headers (DOS, NT, sections) and all the sections. Copies all data to the sections;
ReserveMemory - reserves the sufficient memory for EXE;
ProcessRelocations - adjusts the addresses if an exe has not been loaded to base address;
ProcessImportTable - scans the import table of an exe file, loads the needed libraries and fills the import address table;
SetMemoryPermissions - adjusts the memory permissions for each section;
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":
' // Parse exe in memory Function LoadExeFromMemory( _ ByVal pRawData As Long, _ ByVal pMyBaseAddress As Long, _ ByVal pErrMsgTable As Long) As Boolean Dim NtHdrAs IMAGE_NT_HEADERS Dim pBaseAs Long Dim indexAs 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:
' // Reserve memory for EXE Function ReserveMemory( _ ByVal pRawExeData As Long, _ ByRef pBase As Long) As ERROR_MESSAGES Dim NtHdrAs IMAGE_NT_HEADERS Dim pLocBaseAs 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:
' // Allocate memory for sections and copy them data to there Function ProcessSectionsAndHeaders( _ ByVal pRawExeData As Long, _ ByVal pBase As Long) As ERROR_MESSAGES Dim iSecAs Long Dim pNtHdr As Long Dim NtHdrAs IMAGE_NT_HEADERS Dim sec As IMAGE_SECTION_HEADER Dim lpSecAs Long Dim pDataAs 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:
Type PEB NotUsed As Long Mutant As Long ImageBaseAddressAs Long LoaderData As Long ' // Pointer to PEB_LDR_DATA ProcessParametersAs 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:
Type PEB_LDR_DATA Length As Long Initialized As Long SsHandleAs Long InLoadOrderModuleListAs 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:
Type LDR_MODULE InLoadOrderModuleListAs LIST_ENTRY InMemoryOrderModuleList As LIST_ENTRY InInitOrderModuleListAs LIST_ENTRY BaseAddress As Long EntryPoint As Long SizeOfImage As Long FullDllName As UNICODE_STRING BaseDllName As UNICODE_STRING FlagsAs Long LoadCountAs Integer TlsIndexAs Integer HashTableEntry As LIST_ENTRY TimeDateStampAs 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:
Type PROCESS_BASIC_INFORMATION ExitStatus As Long PebBaseAddress As Long AffinityMaskAs Long BasePriorityAs Long UniqueProcessId As Long InheritedFromUniqueProcessIdAs Long End Type
The 'PebBaseAddress' field contains the address of the PEB structure.
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:
Function UpdateNewBaseAddress( _ ByVal pBase As Long) As ERROR_MESSAGES Dim pPBIAs Long:Dim PBIlen As Long Dim PBI As PROCESS_BASIC_INFORMATION:Dim cPEBAs 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:
Type IMAGE_IMPORT_DESCRIPTOR Characteristics As Long TimeDateStampAs Long ForwarderChain As Long pNameAs 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.
' // Process import table Function ProcessImportTable( _ ByVal pBase As Long) As ERROR_MESSAGES Dim NtHdrAs IMAGE_NT_HEADERS:Dim datDirectoryAs IMAGE_DATA_DIRECTORY Dim dsc As IMAGE_IMPORT_DESCRIPTOR: Dim hLibAs Long Dim thnkAs Long:Dim AddrAs Long Dim fnc As Long:Dim pDataAs 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:
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:
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:
' // Process relocations Function ProcessRelocations( _ ByVal pBase As Long) As ERROR_MESSAGES Dim NtHdrAs IMAGE_NT_HEADERS:Dim datDirectoryAs IMAGE_DATA_DIRECTORY Dim relBase As IMAGE_BASE_RELOCATION:Dim entriesCountAs Long Dim relType As Long:Dim dwAddressAs Long Dim dwOrig As Long:Dim pRelBaseAs Long Dim deltaAs Long:Dim pDataAs 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
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:
' // Set memory permissions Private Function SetMemoryPermissions( _ ByVal pBase As Long) As ERROR_MESSAGES Dim iSecAs Long:Dim pNtHdr As Long Dim NtHdrAs IMAGE_NT_HEADERS:Dim sec As IMAGE_SECTION_HEADER Dim AttrAs MEMPROTECT: Dim pSecAs 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.
Regards,
The trick.
No comments:
Post a Comment