PE 파일 작업 - 동적 로드
10829 단어 Win32
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;
}