Skip to content

Instantly share code, notes, and snippets.

@nnnewb
Last active October 20, 2021 01:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nnnewb/28ca24ed4ee53f446120d64570c7ad01 to your computer and use it in GitHub Desktop.
Save nnnewb/28ca24ed4ee53f446120d64570c7ad01 to your computer and use it in GitHub Desktop.
PE32 Packer NoRelocations
#include <Windows.h>
#include <memory.h>
#include <stdlib.h>
#include <winnt.h>
int main(void) {
MessageBoxA(NULL, "Hello world!", "MSGBOX", MB_OK);
return 0;
}
#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 *)&sections[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];
}
}
# %%
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