PE 파일 작업 - 동적 로드

10829 단어 Win32
프로세스 공간에 dll을 불러오거나 주소 공간의 랜덤화 기술에 대처하려면 PELoader가 필요합니다.PE 파일에서 많은 데이터가 메모리에 있는 위치는 RVA 형식으로 링크될 때 제공된다. 우리는 절표의 설명에 따라 정확하게 위치에 따라 불러오기만 하면 대부분의 프로그램이 정확하게 실행될 수 있지만 몇 가지 유형의 데이터는 로더가 조정해야 한다.
  • IAT는PE 파일에서 정적 링크를 호출하는 DLL 절차는 보통callds:[xxxxxxxh]의 형식으로 되어 있는데 그 중에서 이 주소는 IAT표의 대응 항목을 가리키고 파일에서 IAT표의 모든 항목은INT표와 같이 IMAGE 를 가리킨다IMPORT_BY_NAME 또는 Id이기 때문에 메모리에 불러올 때 표 항목을 함수 주소로 수정해야 합니다.
  • 리셋 테이블은 일반적으로exe 파일을 불러오는 데 리셋 문제가 없습니다. exe 파일은 기본 주소에 불러오기 때문에 프로세스 주소 공간이 초기화되면 이 주소는 거의 분배되지 않은 것이 틀림없습니다. 그러나 dll이나 주소 공간 랜덤화 기술을 사용한exe는 리셋 테이블에 사용되기 때문에 불러오는 주소와OPTIONAL 에 따라HEADER의 기본 주소로 오프셋 산출
  • 주: 이 편에서는 dll을 불러오는 것을 주로 씁니다.exe 디버깅을 할 때 불안정한 상황이 좀 있으니 나중에 다시 이야기합시다.글 중의ctx는 자신이 쓴pe해상기로서 완선되면 오픈소스 사이트에 올립니다.우선, CreateSection 테스트를 피하기 위해 CreateFile을 사용합니다.
        IMAGE_DOS_HEADER dos_header;
        IMAGE_NT_HEADERS32 nt_header;
    
        SetFilePointer(pe_file, 0, 0, FILE_BEGIN);
        ReadFile(pe_file, &dos_header, sizeof(IMAGE_DOS_HEADER), &ret, NULL);
        if (ret != sizeof(IMAGE_DOS_HEADER))
            return false;
    
        SetFilePointer(pe_file, dos_header.e_lfanew, NULL, FILE_BEGIN);
        ReadFile(pe_file, &nt_header, sizeof(IMAGE_NT_HEADERS32), &ret, NULL);
        if (ret != sizeof(IMAGE_NT_HEADERS32))
            return false;
    
        // OptionalHeader          
        PVOID map_ptr = VirtualAlloc(NULL, 
            nt_header.OptionalHeader.SizeOfImage, 
            MEM_COMMIT, PAGE_READWRITE);
        ZeroMemory(map_ptr, nt_header.OptionalHeader.SizeOfImage);
    
        SetFilePointer(pe_file, 0, 0, FILE_BEGIN);
        ReadFile(pe_file, map_ptr, nt_header.OptionalHeader.SizeOfHeaders, &ret, NULL);
        if (ret != nt_header.OptionalHeader.SizeOfHeaders)
            return false;

    그리고 우리는 메모리를 맞추어 각 섹션을 메모리에 읽는 것이 그에 상응하는 속성을 불러오는 것보다 낫다. 이것은 읽기 전용 속성을 급히 불러오지 않는다. 왜냐하면 뒤에 있는 IAT와 RELOC는 모두 쓰기 작업이 필요하기 때문이다.
        for (int i = 0;i < get_section_count(ctx);i++)
        {
            PIMAGE_SECTION_HEADER sec_hdr;
            get_section_entry_by_index(ctx, i, &sec_hdr);
            SetFilePointer(pe_file, sec_hdr->PointerToRawData, NULL, FILE_BEGIN);
            if (!ReadFile(pe_file, (PUCHAR)map_ptr + sec_hdr->VirtualAddress, sec_hdr->SizeOfRawData, &ret, NULL))
                return false;
    
            if ((sec_hdr->Characteristics&IMAGE_SCN_CNT_UNINITIALIZED_DATA) != 0)
            {
                ZeroMemory((PUCHAR)map_ptr + sec_hdr->VirtualAddress, sec_hdr->Misc.VirtualSize);
            }
            if ((sec_hdr->Characteristics&IMAGE_SCN_MEM_EXECUTE) != 0)
            {
                DWORD oldVp;
                VirtualProtect((PUCHAR)map_ptr + sec_hdr->VirtualAddress,
                    sec_hdr->Misc.VirtualSize,
                    PAGE_EXECUTE_READWRITE, 
                    &oldVp);
            }
        }

    이후에 IAT를 채워야 합니다. 이것은 반복 작업일 뿐입니다. 여기서 번호 가져오기와 귀속 가져오기를 잠시 고려하지 않았습니다.
        for (int i = 0;i < get_import_count(ctx);i++)
        {
    
            get_import_descriptor_by_index(ctx, i, &import_desc);
            //           
            PCHAR import_name;
            get_import_descriptor_name(ctx, import_desc, (PUCHAR*)&import_name);
            HMODULE mod = LoadLibraryA(import_name);
            if (mod == NULL)
                return FALSE;
    
            get_import_trunk32(ctx, import_desc, &trunk_arr);//  INT
            for (int j = 0;j < get_import_trunk_count(ctx, import_desc);j++)
            {
                get_import_trunk32_by_index(ctx, import_desc, j, &trunk);
                get_import_item_by_name(ctx, trunk, &import_name);
                //       
                PVOID f_addr = GetProcAddress(mod, import_name->Name);
                trunk_arr[j] = (ULONG)f_addr;
            }
        }
    

    그 다음에 리셋 테이블을 수정합니다. 리셋이 필요한 곳의 원래 주소는 Optional Header에서 Image Base의 선형 절대 주소를 기반으로 하기 때문에 새 주소와 기본 주소의 차이점을 추가해야 합니다.여기에 문제가 하나 있다. 일반적인 dll은 모두 리셋 테이블이 있지만 ASLR이 없는exe는 리셋 테이블이 없을 가능성이 높다. 우리는 PE를 ImageBase의 주소에 불러올 수 없다. 그러면 PE가 불러와도 실행할 수 없다. 그러면 리셋 테이블을 복원하는 것을 고려해야 한다. 이 다음에 리셋 하강 분석으로 리셋 테이블을 복원하는 것을 소개할 것이다.
        PIMAGE_BASE_RELOCATION first_reloc;
        //                 
        ULONG offset = ((ULONG_PTR)map_ptr - (((PIMAGE_NT_HEADERS)(ctx->nt_ptr))->OptionalHeader.ImageBase));
        for (int i = 0;i < get_reloc_table_count(ctx, &first_reloc);i++)
        {
            PIMAGE_BASE_RELOCATION base_reloc;
            get_reloc_table_entry(ctx, first_reloc, i, &base_reloc);
            //          RVA  
            PUCHAR base_va = ((PUCHAR)ctx->map_ptr + base_reloc->VirtualAddress);
            for (int j = 0;j < (base_reloc->SizeOfBlock - 8) / 2;j++)
            {
                //   ,      
                if (((*(PUSHORT)((PUCHAR)base_reloc + 8 + j * 2)) & 0xf000) == 0)
                    continue;
                //           
                *((PULONG_PTR)(base_va + ((*(PUSHORT)((PUCHAR)base_reloc + 8 + j * 2)) & 0xfff))) += offset;
            }
    
        }

    이 모든 것이 끝난 후에 바이트 메모리의 읽기와 쓰기 속성을 다시 수정한 후에 PE 파일은 이미 사용할 수 있다. PE 파일이 직접 하드코딩으로 메모리에 접근하지 않으면 기본적으로 문제가 없다.우리는 라인을 만들고 입구 함수로 이동해서 PE 파일을 실행할 수 있습니다.
    typedef BOOL(_stdcall *DllInit)(HINSTANCE hinstDLL,  // handle to DLL module
        DWORD fdwReason,     // reason for calling function
        LPVOID lpReserved);
    
        DllInit locDllinit = (DllInit)((PUCHAR)ctx->map_ptr + ((PIMAGE_NT_HEADERS)(ctx->nt_ptr))->OptionalHeader.AddressOfEntryPoint);
    
        locDllinit((HINSTANCE)map_ptr, DLL_PROCESS_ATTACH, 0);
        locDllinit((HINSTANCE)map_ptr, DLL_THREAD_ATTACH, 0);

    EXE 파일을 불러올 때 TLS의AddressOfIndex를 처리해야 하지만, 보통 TLS를 사용하는 사람이 없고, 대부분의 경우 이 인덱스 값도 0을 채우기 때문에 여기서는 처리하지 않습니다.pecoff에서는 loader의 역할에 대해 설명합니다.
    The loader assigns the value of the TLS index to the place that was indicated by the Address of Index field.

    (??) GetProcAddress는 내보내기 테이블을 반복합니다.
    PVOID get_proc_address(PPE_CONTEXT ctx, LPCSTR func)
    {
    
        PIMAGE_EXPORT_DIRECTORY export_dir;
        get_export_descriptor(ctx, &export_dir);
        if (export_dir == NULL)
            return NULL;
    
        for (int i = 0;i < export_dir->AddressOfFunctions;i++)
        {
            CHAR* name;
            get_export_name_by_index(ctx, export_dir, i, &name);
            if (!strcmp(name, func))
            {
                PVOID f_addr;
                //         RVA  VA   
                get_export_addr_by_index(ctx, export_dir, i, &f_addr);
                return f_addr;
            }
        }
    
        return NULL;
    }

    좋은 웹페이지 즐겨찾기