Last active
October 20, 2021 01:44
-
-
Save nnnewb/28ca24ed4ee53f446120d64570c7ad01 to your computer and use it in GitHub Desktop.
PE32 Packer NoRelocations
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <Windows.h> | |
#include <memory.h> | |
#include <stdlib.h> | |
#include <winnt.h> | |
int main(void) { | |
MessageBoxA(NULL, "Hello world!", "MSGBOX", MB_OK); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <Windows.h> | |
#include <winnt.h> | |
#pragma runtime_checks("", off) | |
void *load_PE(char *PE_data); | |
void fix_iat(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers); | |
void fix_base_reloc(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers); | |
int mystrcmp(const char *str1, const char *str2); | |
void mymemcpy(char *dest, const char *src, size_t length); | |
int _start(void) { | |
char *unpacker_VA = (char *)GetModuleHandleA(NULL); | |
IMAGE_DOS_HEADER *p_DOS_header = (PIMAGE_DOS_HEADER)unpacker_VA; | |
IMAGE_NT_HEADERS *p_NT_headers = (PIMAGE_NT_HEADERS)(unpacker_VA + p_DOS_header->e_lfanew); | |
IMAGE_SECTION_HEADER *sections = (PIMAGE_SECTION_HEADER)(p_NT_headers + 1); | |
sections = (PIMAGE_SECTION_HEADER)((char *)sections - (IMAGE_NUMBEROF_DIRECTORY_ENTRIES - | |
p_NT_headers->OptionalHeader.NumberOfRvaAndSizes) * | |
sizeof(IMAGE_DATA_DIRECTORY)); | |
char *packed = NULL; | |
char packed_section_name[] = ".packed"; | |
for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; i++) { | |
if (mystrcmp((char *)§ions[i].Name[0], packed_section_name) == 0) { | |
packed = unpacker_VA + sections[i].VirtualAddress; | |
break; | |
} | |
} | |
if (packed != NULL) { | |
void (*entrypoint)(void) = (void (*)(void))load_PE(packed); | |
entrypoint(); | |
return 0; | |
} | |
MessageBoxA(NULL, ".packed section not found", "loader error", MB_OK); | |
return 0; | |
} | |
void *load_PE(char *PE_data) { | |
IMAGE_DOS_HEADER *p_DOS_header = (IMAGE_DOS_HEADER *)PE_data; | |
IMAGE_NT_HEADERS *p_NT_headers = (IMAGE_NT_HEADERS *)(PE_data + p_DOS_header->e_lfanew); | |
// extract information from PE header | |
DWORD size_of_image = p_NT_headers->OptionalHeader.SizeOfImage; | |
DWORD entry_point_RVA = p_NT_headers->OptionalHeader.AddressOfEntryPoint; | |
DWORD size_of_headers = p_NT_headers->OptionalHeader.SizeOfHeaders; | |
// base address | |
char *p_image_base = (char *)GetModuleHandleA(NULL); | |
if (p_image_base == NULL) { | |
return NULL; | |
} | |
// disable write protect | |
DWORD old_protect; | |
VirtualProtect(p_image_base, p_NT_headers->OptionalHeader.SizeOfHeaders, PAGE_READWRITE, &old_protect); | |
// copy PE headers in memory | |
mymemcpy(p_image_base, PE_data, size_of_headers); | |
// Section headers starts right after the IMAGE_NT_HEADERS struct, so we do some pointer arithmetic-fu here. | |
IMAGE_SECTION_HEADER *sections = (IMAGE_SECTION_HEADER *)(p_NT_headers + 1); | |
for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; i++) { | |
// calculate the VA we need to copy the content, from the RVA | |
// section[i].VirtualAddress is a RVA, mind it | |
char *dest = p_image_base + sections[i].VirtualAddress; | |
// check if there is Raw data to copy | |
if (sections[i].SizeOfRawData > 0) { | |
// make sure we can write in allocated sections | |
VirtualProtect(dest, sections[i].SizeOfRawData, PAGE_READWRITE, &old_protect); | |
// We copy SizeOfRaw data bytes, from the offset PointerToRawData in the file | |
mymemcpy(dest, PE_data + sections[i].PointerToRawData, sections[i].SizeOfRawData); | |
} else { | |
VirtualProtect(dest, sections[i].Misc.VirtualSize, PAGE_READWRITE, &old_protect); | |
for (size_t i = 0; i < sections[i].Misc.VirtualSize; i++) { | |
dest[i] = 0; | |
} | |
} | |
} | |
fix_iat(p_image_base, p_NT_headers); | |
fix_base_reloc(p_image_base, p_NT_headers); | |
// Set permission for the PE header to read only | |
DWORD oldProtect; | |
VirtualProtect(p_image_base, p_NT_headers->OptionalHeader.SizeOfHeaders, PAGE_READONLY, &old_protect); | |
for (int i = 0; i < p_NT_headers->FileHeader.NumberOfSections; ++i) { | |
char *dest = p_image_base + sections[i].VirtualAddress; | |
DWORD s_perm = sections[i].Characteristics; | |
DWORD v_perm = 0; // flags are not the same between virtal protect and the section header | |
if (s_perm & IMAGE_SCN_MEM_EXECUTE) { | |
v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; | |
} else { | |
v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_READWRITE : PAGE_READONLY; | |
} | |
VirtualProtect(dest, sections[i].Misc.VirtualSize, v_perm, &old_protect); | |
} | |
return (void *)(p_image_base + entry_point_RVA); | |
} | |
void fix_iat(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers) { | |
IMAGE_DATA_DIRECTORY *data_directory = p_NT_headers->OptionalHeader.DataDirectory; | |
// load the address of the import descriptors array | |
IMAGE_IMPORT_DESCRIPTOR *import_descriptors = | |
(IMAGE_IMPORT_DESCRIPTOR *)(p_image_base + data_directory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); | |
// this array is null terminated | |
for (int i = 0; import_descriptors[i].OriginalFirstThunk != 0; ++i) { | |
// Get the name of the dll, and import it | |
char *module_name = p_image_base + import_descriptors[i].Name; | |
HMODULE import_module = LoadLibraryA(module_name); | |
if (import_module == NULL) { | |
// panic! | |
ExitProcess(255); | |
} | |
// the lookup table points to function names or ordinals => it is the IDT | |
IMAGE_THUNK_DATA *lookup_table = (IMAGE_THUNK_DATA *)(p_image_base + import_descriptors[i].OriginalFirstThunk); | |
// the address table is a copy of the lookup table at first | |
// but we put the addresses of the loaded function inside => that's the IAT | |
IMAGE_THUNK_DATA *address_table = (IMAGE_THUNK_DATA *)(p_image_base + import_descriptors[i].FirstThunk); | |
// null terminated array, again | |
for (int i = 0; lookup_table[i].u1.AddressOfData != 0; ++i) { | |
void *function_handle = NULL; | |
// Check the lookup table for the adresse of the function name to import | |
DWORD lookup_addr = lookup_table[i].u1.AddressOfData; | |
if ((lookup_addr & IMAGE_ORDINAL_FLAG) == 0) { // if first bit is not 1 | |
// import by name : get the IMAGE_IMPORT_BY_NAME struct | |
IMAGE_IMPORT_BY_NAME *image_import = (IMAGE_IMPORT_BY_NAME *)(p_image_base + lookup_addr); | |
// this struct points to the ASCII function name | |
char *funct_name = (char *)&(image_import->Name); | |
// get that function address from it's module and name | |
function_handle = (void *)GetProcAddress(import_module, funct_name); | |
} else { | |
// import by ordinal, directly | |
function_handle = (void *)GetProcAddress(import_module, (LPSTR)lookup_addr); | |
} | |
if (function_handle == NULL) { | |
ExitProcess(255); | |
} | |
// change the IAT, and put the function address inside. | |
address_table[i].u1.Function = (DWORD)function_handle; | |
} | |
} | |
} | |
void fix_base_reloc(char *p_image_base, IMAGE_NT_HEADERS *p_NT_headers) { | |
IMAGE_DATA_DIRECTORY *data_directory = p_NT_headers->OptionalHeader.DataDirectory; | |
// this is how much we shifted the ImageBase | |
DWORD delta_VA_reloc = ((DWORD)p_image_base) - p_NT_headers->OptionalHeader.ImageBase; | |
// if there is a relocation table, and we actually shitfted the ImageBase | |
if (data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress != 0 && delta_VA_reloc != 0) { | |
// calculate the relocation table address | |
IMAGE_BASE_RELOCATION *p_reloc = | |
(IMAGE_BASE_RELOCATION *)(p_image_base + data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); | |
// once again, a null terminated array | |
while (p_reloc->VirtualAddress != 0) { | |
// how any relocation in this block | |
// ie the total size, minus the size of the "header", divided by 2 (those are words, so 2 bytes for each) | |
DWORD size = (p_reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2; | |
// the first relocation element in the block, right after the header (using pointer arithmetic again) | |
WORD *fixups = (WORD *)(p_reloc + 1); | |
for (size_t i = 0; i < size; ++i) { | |
// type is the first 4 bits of the relocation word | |
int type = fixups[i] >> 12; | |
// offset is the last 12 bits | |
int offset = fixups[i] & 0x0fff; | |
// this is the address we are going to change | |
DWORD *change_addr = (DWORD *)(p_image_base + p_reloc->VirtualAddress + offset); | |
// there is only one type used that needs to make a change | |
switch (type) { | |
case IMAGE_REL_BASED_HIGHLOW: | |
*change_addr += delta_VA_reloc; | |
break; | |
default: | |
break; | |
} | |
} | |
// switch to the next relocation block, based on the size | |
p_reloc = (IMAGE_BASE_RELOCATION *)(((DWORD)p_reloc) + p_reloc->SizeOfBlock); | |
} | |
} | |
} | |
int mystrcmp(const char *str1, const char *str2) { | |
while (*str1 == *str2 && *str1 != 0) { | |
str1++; | |
str2++; | |
} | |
if (*str1 == 0 && *str2 == 0) { | |
return 0; | |
} | |
return -1; | |
} | |
void mymemcpy(char *dest, const char *src, size_t length) { | |
for (size_t i = 0; i < length; i++) { | |
dest[i] = src[i]; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# %% | |
import lief | |
from subprocess import STDOUT, CalledProcessError, check_output | |
def align(x, al): | |
""" return <x> aligned to <al> """ | |
return ((x+(al-1))//al)*al | |
# %% | |
# compile origin demo program | |
try: | |
check_output('gcc example.c -m32 -O2 -o example.exe', shell=True, stderr=STDOUT) | |
except CalledProcessError as e: | |
print(f'[!] demo program compilation failed, {e.stdout.decode()}') | |
raise | |
binary = lief.PE.parse('example.exe') | |
print('[+] compile origin demo program success.') | |
# %% | |
# calculate shift offset and reserved section size | |
image_base = binary.optional_header.imagebase | |
lowest_rva = min([s.virtual_address for s in binary.sections]) | |
highest_rva = max([s.virtual_address + s.size for s in binary.sections]) | |
sect_alignment = binary.optional_header.section_alignment | |
print('[+] analyze origin demo program binary success.') | |
# %% | |
# compile shifted loader program | |
compile_args = [ | |
'loader.c', | |
'-m32', | |
'-O2', | |
'-Wall', | |
'-Wl,--entry=__start', | |
'-nodefaultlibs', | |
'-nostartfiles', | |
'-lkernel32', | |
'-luser32', | |
f'-Wl,--image-base={hex(image_base)}', | |
f'-Wl,--section-start=.text={hex(align(image_base+highest_rva,sect_alignment))}', | |
'-o', | |
'shifted-loader.exe' | |
] | |
try: | |
check_output(' '.join(['gcc', *compile_args]), shell=True, stderr=STDOUT) | |
print('[+] compile shifted loader program success.') | |
except CalledProcessError as e: | |
print(f'[!] loader compilation failed, {e.stdout.decode()}') | |
raise | |
shifted_loader = lief.PE.parse('shifted-loader.exe') | |
sect_alignment = shifted_loader.optional_header.section_alignment | |
file_alignment = shifted_loader.optional_header.file_alignment | |
# %% | |
# create new binary from scratch | |
output = lief.PE.Binary('packed', lief.PE.PE_TYPE.PE32) | |
# copy essential fields from shifted_loader | |
output.optional_header.imagebase = shifted_loader.optional_header.imagebase | |
output.optional_header.section_alignment = shifted_loader.optional_header.section_alignment | |
output.optional_header.file_alignment = shifted_loader.optional_header.file_alignment | |
# disable ASLR | |
output.optional_header.dll_characteristics = 0 | |
# add .alloc section | |
allocate_size = align(highest_rva-lowest_rva, sect_alignment) | |
allocate_section = lief.PE.Section(".alloc") | |
allocate_section.virtual_address = lowest_rva | |
allocate_section.virtual_size = allocate_size | |
allocate_section.characteristics = (lief.PE.SECTION_CHARACTERISTICS.MEM_READ | |
| lief.PE.SECTION_CHARACTERISTICS.MEM_WRITE | |
| lief.PE.SECTION_CHARACTERISTICS.CNT_UNINITIALIZED_DATA) | |
output.add_section(allocate_section) | |
# copy sections | |
for s in shifted_loader.sections: | |
# let lief recalculate section offset and sizeof raw data | |
s.offset = 0 | |
s.sizeof_raw_data = 0 | |
output.add_section(s) | |
# add packed section | |
with open('example.exe', 'rb') as f: | |
packed_section = lief.PE.Section('.packed') | |
packed_section.content = list(f.read()) | |
packed_section.characteristics = (lief.PE.SECTION_CHARACTERISTICS.MEM_READ | | |
lief.PE.SECTION_CHARACTERISTICS.CNT_INITIALIZED_DATA) | |
output.add_section(packed_section) | |
# copy data directories | |
for i in range(0, 15): | |
src = shifted_loader.data_directories[i] | |
output.data_directories[i].rva = src.rva | |
output.data_directories[i].size = src.size | |
# correct number of data directories | |
# warning: size of data directories may disagree with IMAGE_NT_HEADERS.DataDirectory in winnt.h | |
output.optional_header.numberof_rva_and_size = len(output.data_directories) | |
# copy original address of entrypoint | |
output.optional_header.addressof_entrypoint = shifted_loader.optional_header.addressof_entrypoint | |
# let lief recalculate size of image | |
output.optional_header.sizeof_image = 0 | |
# build output binary | |
builder = lief.PE.Builder(output) | |
builder.build() | |
builder.write('packed.exe') | |
print('[+] create packed binary success.') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment