PE executable file loader

PE file loader

Imitate operating system, load the file into memory pe
The project is to examine the extent of learning pe whether fully understood. Of course not fully understood

Realize the function as follows:

  1. Imitate operating system, load the file into memory pe, pe and then execute the file to be executed
  2. Repair Important information IAT, reloc etc.

Of course, this is only a prototype, there is a lot of work not completed, TODO list

  1. DLL file is loaded, this is actually very simple, just need to parse export table, and then corrected on the line
  2. IAT binding loads, too lazy to do this
  3. Lazy loading, but also too lazy to do

So we have this small loader, it is only responsible for reparse resolution and relocation table positioning table. But for a small program in terms of good enough. Said the following about the idea

  1. The pe header optionalheader in SizeOfImage, application memory. Memory base address is ImageBase. SizeOfImage pe file as in the case of memory alignment, size of the space required. Base address this, it is recommended to ImageBase address, of course, if the file has pe relocation information, then it shows the pe file can be loaded into any memory location. The relocation table is then corrected on the line
  2. The pe header SizeOfHeader, pe obtain the size of the head. The value of the document is aligned. According to this value, we will call Rtlmemcopy head pe copy into memory
  3. Analytical pe head, obtaining numberofSection, according to this value, the processing section. The copy in memory section
  4. Iat iat process resolves the content of, and amendments
  5. Processing relocation table. If you load the base address for the ImageBase, then no action is required. Otherwise, it must deal with
  6. Jump to Address of entry, started pe file

Precautions:

  1. Ignore loadflag etc.
  2. For convenience, the application may perform read-write memory, and not to set the properties in accordance with section
  3. The program is loaded, the main program to use the same heap and stack. It need not be concerned sizeofstack equivalent
  4. Be sure to modify the base address of the main program is loaded, to modify the position of the non 0x0040000. Or you can not apply for the address 0x00400000. Modify the value, then, in vs link options

Below some details of the number of operations

Determine whether the file pe

This is simple, nothing to say, you can look at the code

    PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)BaseAddr;
    PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((UINT_PTR)BaseAddr + pDos->e_lfanew);

    if (pDos->e_magic == IMAGE_DOS_SIGNATURE && pNt->Signature == IMAGE_NT_SIGNATURE) {
        return true;
    }
Application Memory

According sizeofimage to apply to memory. Of course, my function is very thick, in the case of imagebase can not be used, and there is no case to determine whether the program can be relocated, forced to amend imagebase. I use the time to determine what's best.

    DWORD dwSizeOfImage = pnt->OptionalHeader.SizeOfImage;
    DWORD dwImageBaseAddr = pnt->OptionalHeader.ImageBase;
    //为了安全性,暂时将该申请的内存区域设置成可读可写,等一会再根据需要重新设置
    //必须要设置MEM_RESERVE,不然不能申请0x00400000地址
    LPVOID returnAddr = VirtualAlloc((LPVOID)dwImageBaseAddr, dwSizeOfImage, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (GetLastError() == 0) {
        printf("[+] 正在根据pe的加载基地址 申请内存,基地址为 0x%p\n", (LPVOID)dwImageBaseAddr);
        return returnAddr;
    }
    else {
        returnAddr = VirtualAlloc(NULL, dwSizeOfImage, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        printf("[+] pe的加载基地址不能用,正在重新申请地址中,基地址为 0x%p\n", (LPVOID)dwImageBaseAddr);
        return returnAddr;
    }
Head-to-memory copy pe

In fact, for our loader terms. Do not copy copy pe head, and will not affect the normal execution of the file. So this is an optional step. Of course, I for convenience, because later I'll be freed memory to read the file. It is necessary to copy pe head. This function is relatively simple, rtlcopy function can be called directly

static void __stdcall CopyNtHeaderToMem(IN LPVOID lpPemem, IN LPVOID Header, SIZE_T size) {
    //获取nt头的size,文件对齐值,一般是一页文件对齐
    RtlCopyMemory(lpPemem, Header, size);
    printf("[+] 正在拷贝pe头到 0x%p中\n", lpPemem);
}
Copy section into memory

This is relatively simple. Read sectionHeader, section VA-FOA and size header and described in, we only need the information copied to the specified memory location can be

static void __stdcall CopySectionToMem(IN LPVOID lpPeMem, IN LPVOID lpBaseAddr, IN PIMAGE_NT_HEADERS pNt) {
    //暂时不处理内存属性,全部可读可写可执行哈哈哈哈
    DWORD dwNumOfSection = pNt->FileHeader.NumberOfSections;
    DWORD dwSectionAlignment = pNt->OptionalHeader.SectionAlignment;
    PIMAGE_SECTION_HEADER pSecHed = (PIMAGE_SECTION_HEADER)((UINT_PTR)pNt + sizeof(IMAGE_NT_HEADERS));

    for (DWORD index = 0; index < dwNumOfSection; index++)
    {
        DWORD dwRva = pSecHed->VirtualAddress;
        DWORD dwFOA = pSecHed->PointerToRawData;
        DWORD dwSize = pSecHed->SizeOfRawData;
        //拷贝源是文件对齐的foa
        LPVOID SecDataSrc = (LPVOID)((UINT_PTR)lpBaseAddr + (UINT_PTR)dwFOA);
        //目的地址是RV
        LPVOID SecDataDst = (LPVOID)RVA2VA(lpPeMem, dwRva);
        //开始拷贝
        RtlCopyMemory(SecDataDst, SecDataSrc, dwSize);

        printf("[+] 正在拷贝 %s section 到内存的 0x%p, 大小为 %d\n", pSecHed->Name, SecDataDst, dwSize);
        pSecHed = (PIMAGE_SECTION_HEADER)((UINT_PTR)pSecHed + sizeof(IMAGE_SECTION_HEADER));
    }
    return;
}
IAT treatment

In the PE file, IAT (Import address Table) and INT (Import Name Tbable) is actually not too bad. Import table, then generally in .rdata section. In the pe, IAT will eventually store the memory address of the corresponding function. Below an example to illustrate
a program calls KERNEL32.dll! IsProcessorFeaturePresent function, following disassembly

 004013E3  6A17                             push    00000017h
 004013E5  E84F090000                       call    jmp_KERNEL32.dll!IsProcessorFeaturePresent
 004013EA  85C0                             test    eax,eax

0x004013E5 stored in machine code, E8 performed on behalf of call, the latter is offset from the address offset value is 0x0000094F.
The program will adjust to 0x004013EA + 0x0000094F, which is 0x0040 $ D19. The following look at the disassembly code at that address

 00401D39  FF251C204000                     jmp [KERNEL32.dll!IsProcessorFeaturePresent]

FF representative of absolute jump, JMP r / m32 absolute jump (32), the next instruction address given in r / m32 in. It takes a value that is the address of 0x0040201c25. Jump past. The 0x0040201c25, is rdata section. There is IAT.

The pe file, IAT will first store va, a point IMAGE_IMPORT_BY_NAME, which kept the name and hint import function.

So fix IAT very simple, first traverse INT, INT structure is as follows

Traversed INT, get the name of the dll is loaded. Call loadlobrary loaded.

Then FirstTrunk way to traverse the IAT. And then based on the information in the IAT, calling GetProcAddress function to get to the real function address. IAT can be corrected

code show as below

    PIMAGE_IMPORT_DESCRIPTOR pImportTab = (PIMAGE_IMPORT_DESCRIPTOR)RVA2VA(lpPeMem, dwImportTableRVA);
    //根据桥2修复就行了,不用根据桥1
    while (pImportTab->OriginalFirstThunk && pImportTab->FirstThunk) {
        char* DllName = (char*)(RVA2VA(lpPeMem, pImportTab->Name));
        printf("[+] 正在修正导入库 %s\n", DllName);

        PDWORD FirstTunkVA = (PDWORD)RVA2VA(lpPeMem, pImportTab->FirstThunk);
        HMODULE hModle = LoadLibraryA(DllName);
        while (*FirstTunkVA != 0) {
            PIMAGE_IMPORT_BY_NAME pImportName = (PIMAGE_IMPORT_BY_NAME)(RVA2VA(lpPeMem, *FirstTunkVA));
            //这块主要是为了处理exitprocess,拦截程序的exitprocess,我们可以从这里获取程序的返回结果
            if (strcmp(pImportName->Name, "ExitProcess") == 0) {
                procAddr = (FARPROC)& MyExitProcess;
            }
            else
            {
                procAddr = GetProcAddress(hModle, pImportName->Name);
            }
            *FirstTunkVA = (DWORD)procAddr;
            FirstTunkVA = (DWORD*)((DWORD)FirstTunkVA + sizeof(DWORD));
#ifdef _DEBUG
            printf("\t[+] 正在修正 %s 的导入地址, 修正后的函数地址为 0x%p\n", pImportName->Name, procAddr);
#endif // _DEBUG
        }
        printf("\n");
        pImportTab = (IMAGE_IMPORT_DESCRIPTOR*)((UINT_PTR)pImportTab + sizeof(IMAGE_IMPORT_DESCRIPTOR));
    }

Of course, we can also hook function here. For example, I results in order to intercept the loaded program. When repair ExitProcess function, the function call address was not corrected to the kernel32.dll. But to amend their code.

The wording hook functions, and hook functions in accordance with the parameters you want to write on the line. example

void MyExitProcess(_In_ UINT uExitCode) {
    printf("\n[+] 程序已退出,退出代码为 %d\n", uExitCode);
    ExitProcess(uExitCode);
}
Processing relocation table

According to the definition of the relocation tables, which store offset with respect ImageBase. We need to read this offset is converted into virtual address. Compared with the base address of the currently loaded. According to the offset can be repaired. Relocation table explained in FIG.

code show as below


    PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION)RVA2VA(lpPeMem, pRelocRVA);
    printf("[+] 发现重定位表,开始修正...\n");
    while (pReloc->VirtualAddress) {
        DWORD dwSizeOfBlock = (pReloc->SizeOfBlock - 8) >> 1;
        DWORD dwVa = pReloc->VirtualAddress;
        PWORD block = (PWORD)((UINT_PTR)pReloc + sizeof(IMAGE_BASE_RELOCATION));
        printf("[+] 发现 %d块需要重定位的地址信息\n", dwSizeOfBlock);
    DWORD dwDelta = (DWORD)lpPeMem - pNt->OptionalHeader.ImageBase;
        for (DWORD index = 0; index < dwSizeOfBlock; index++)
        {
            WORD relocBlock = *block;
            if (((relocBlock & 0xF000) >> 12) == IMAGE_REL_BASED_HIGHLOW) {
                DWORD wOffset = (relocBlock & 0x0FFF | 0x00000000) + dwVa;
                PDWORD pAddress = (PDWORD)(wOffset | (DWORD)lpPeMem);
                *pAddress = *pAddress + dwDelta;
#ifdef _DEBUG
                printf("[+] 修正后的地址为 0x%08x\t\n", pAddress);
#endif
            }
            block = (PWORD)((UINT_PTR)block + sizeof(WORD));
        }
        pReloc = (PIMAGE_BASE_RELOCATION)block;
    }

At this point, what a pe required documents, have all been resolved completely. Now we need to jump to the entry point. Optionalheader entry point of entry of address. The value of RVA. Need to be converted into a VA can. After the conversion is complete, we use the inline assembler in vs. jmp can jump in the past. code show as below

    DWORD EntryOfImage = RVA2VA(lpPeMem, pNt->OptionalHeader.AddressOfEntryPoint);
    printf("[+] 所有的内容都处理完毕,跳转到addresss of entry,地址为 0x%p\n\n", (LPVOID)EntryOfImage);

    __asm {
        jmp EntryOfImage;
    }

### Test Results

Let's test program compiled a vs 2019, the program uses the MessageBox bomb box, call printf output 1111. The release pattern compiler uses, the presence of relocation table. Load shots are as follows

Currently known bug

  1. Most of the fault tolerance mechanisms are not, after all, just a simple program.
  2. Prone to the problem can not allocate memory

Complete code, please go to the point of view github

https://github.com/potats0/PeLoader

Guess you like

Origin www.cnblogs.com/potatsoSec/p/12152856.html