├── .gitignore ├── README.md ├── part1 └── main.c ├── part2 └── main.c ├── part3 ├── packer.py └── unpack.c ├── part4 ├── hello_world.c ├── packer.py └── unpack.c └── part5 ├── packer.py └── unpack.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.o -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tutorial : writing a PE packer 2 | 3 | The files in this repository are meant to complete the tutorial for writing a PE packer at this address : 4 | 5 | [https://bidouillesecurity.com/writing-a-pe-packer-intro](https://bidouillesecurity.com/writing-a-pe-packer-intro) -------------------------------------------------------------------------------- /part1/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | // loads a PE in memory, returns the entry point address 8 | void* load_PE (char* PE_data); 9 | 10 | int main(int argc, char** argv) { 11 | if(argc<2) { 12 | printf("missing path argument\n"); 13 | return 1; 14 | } 15 | 16 | FILE* exe_file = fopen(argv[1], "rb"); 17 | if(!exe_file) { 18 | printf("error opening file\n"); 19 | return 1; 20 | } 21 | 22 | // Get file size : put pointer at the end 23 | fseek(exe_file, 0L, SEEK_END); 24 | // and read its position 25 | long int file_size = ftell(exe_file); 26 | // put the pointer back at the beginning 27 | fseek(exe_file, 0L, SEEK_SET); 28 | 29 | //allocate memory and read the whole file 30 | char* exe_file_data = malloc(file_size+1); 31 | 32 | //read whole file 33 | size_t n_read = fread(exe_file_data, 1, file_size, exe_file); 34 | if(n_read != file_size) { 35 | printf("reading error (%d)\n", n_read); 36 | return 1; 37 | } 38 | 39 | // load the PE in memory 40 | printf("[+] Loading PE file\n"); 41 | void* start_address = load_PE(exe_file_data); 42 | if(start_address) { 43 | // call its entry point 44 | ((void (*)(void)) start_address)(); 45 | } 46 | return 0; 47 | } 48 | 49 | void* load_PE (char* PE_data) { 50 | 51 | /** Parse header **/ 52 | 53 | IMAGE_DOS_HEADER* p_DOS_HDR = (IMAGE_DOS_HEADER*) PE_data; 54 | IMAGE_NT_HEADERS* p_NT_HDR = (IMAGE_NT_HEADERS*) (((char*) p_DOS_HDR) + p_DOS_HDR->e_lfanew); 55 | 56 | DWORD hdr_image_base = p_NT_HDR->OptionalHeader.ImageBase; 57 | DWORD size_of_image = p_NT_HDR->OptionalHeader.SizeOfImage; 58 | DWORD entry_point_RVA = p_NT_HDR->OptionalHeader.AddressOfEntryPoint; 59 | DWORD size_of_headers = p_NT_HDR->OptionalHeader.SizeOfHeaders; 60 | 61 | /** Allocate Memory **/ 62 | char* ImageBase = (char*) VirtualAlloc(NULL, size_of_image, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 63 | if(ImageBase == NULL) { 64 | // Allocation failed 65 | return NULL; 66 | } 67 | 68 | /** Map PE sections in memory **/ 69 | memcpy(ImageBase, PE_data, size_of_headers); 70 | 71 | 72 | // Section headers starts right after the IMAGE_NT_HEADERS struct, so we do some pointer arithmetic-fu here. 73 | IMAGE_SECTION_HEADER* sections = (IMAGE_SECTION_HEADER*) (p_NT_HDR + 1); 74 | 75 | // For each sections 76 | for(int i=0; iFileHeader.NumberOfSections; ++i) { 77 | // calculate the VA we need to copy the content, from the RVA 78 | // section[i].VirtualAddress is a RVA, mind it 79 | char* dest = ImageBase + sections[i].VirtualAddress; 80 | 81 | // check if there is Raw data to copy 82 | if(sections[i].SizeOfRawData > 0) { 83 | // We copy SizeOfRaw data bytes, from the offset PointertoRawData in the file 84 | memcpy(dest, PE_data + sections[i].PointerToRawData, sections[i].SizeOfRawData); 85 | } else { 86 | memset(dest, 0, sections[i].Misc.VirtualSize); 87 | } 88 | } 89 | 90 | /** Map PE sections privileges **/ 91 | 92 | //Set permission for the PE hader to read only 93 | DWORD oldProtect; 94 | VirtualProtect(ImageBase, p_NT_HDR->OptionalHeader.SizeOfHeaders, PAGE_READONLY, &oldProtect); 95 | 96 | for(int i=0; iFileHeader.NumberOfSections; ++i) { 97 | char* dest = ImageBase + sections[i].VirtualAddress; 98 | DWORD s_perm = sections[i].Characteristics; 99 | DWORD v_perm = 0; //flags are not the same between virtal protect and the section header 100 | if(s_perm & IMAGE_SCN_MEM_EXECUTE) { 101 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; 102 | } else { 103 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_READWRITE : PAGE_READONLY; 104 | } 105 | VirtualProtect(dest, sections[i].Misc.VirtualSize, v_perm, &oldProtect); 106 | } 107 | 108 | return (void*) (ImageBase + entry_point_RVA); 109 | } -------------------------------------------------------------------------------- /part2/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | // loads a PE in memory, returns the entry point address 8 | void* load_PE (char* PE_data); 9 | 10 | int main(int argc, char** argv) { 11 | if(argc<2) { 12 | printf("missing path argument\n"); 13 | return 1; 14 | } 15 | 16 | FILE* exe_file = fopen(argv[1], "rb"); 17 | if(!exe_file) { 18 | printf("error opening file\n"); 19 | return 1; 20 | } 21 | 22 | // Get file size : put pointer at the end 23 | fseek(exe_file, 0L, SEEK_END); 24 | // and read its position 25 | long int file_size = ftell(exe_file); 26 | // put the pointer back at the beginning 27 | fseek(exe_file, 0L, SEEK_SET); 28 | 29 | //allocate memory and read the whole file 30 | char* exe_file_data = malloc(file_size+1); 31 | 32 | //read whole file 33 | size_t n_read = fread(exe_file_data, 1, file_size, exe_file); 34 | if(n_read != file_size) { 35 | printf("reading error (%d)\n", n_read); 36 | return 1; 37 | } 38 | 39 | // load the PE in memory 40 | printf("[+] Loading PE file\n"); 41 | void* start_address = load_PE(exe_file_data); 42 | if(start_address) { 43 | // call its entry point 44 | ((void (*)(void)) start_address)(); 45 | } 46 | return 0; 47 | } 48 | 49 | void* load_PE (char* PE_data) { 50 | 51 | /** Parse header **/ 52 | 53 | IMAGE_DOS_HEADER* p_DOS_HDR = (IMAGE_DOS_HEADER*) PE_data; 54 | IMAGE_NT_HEADERS* p_NT_HDR = (IMAGE_NT_HEADERS*) (((char*) p_DOS_HDR) + p_DOS_HDR->e_lfanew); 55 | 56 | DWORD hdr_image_base = p_NT_HDR->OptionalHeader.ImageBase; 57 | DWORD size_of_image = p_NT_HDR->OptionalHeader.SizeOfImage; 58 | DWORD entry_point_RVA = p_NT_HDR->OptionalHeader.AddressOfEntryPoint; 59 | DWORD size_of_headers = p_NT_HDR->OptionalHeader.SizeOfHeaders; 60 | 61 | /** Allocate Memory **/ 62 | char* ImageBase = (char*) VirtualAlloc(NULL, size_of_image, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 63 | if(ImageBase == NULL) { 64 | // Allocation failed 65 | return NULL; 66 | } 67 | 68 | /** Map PE sections in memory **/ 69 | 70 | memcpy(ImageBase, PE_data, p_NT_HDR->OptionalHeader.SizeOfHeaders); 71 | 72 | 73 | // Section headers starts right after the IMAGE_NT_HEADERS struct, so we do some pointer arithmetic-fu here. 74 | IMAGE_SECTION_HEADER* sections = (IMAGE_SECTION_HEADER*) (p_NT_HDR + 1); 75 | 76 | // For each sections 77 | for(int i=0; iFileHeader.NumberOfSections; ++i) { 78 | // calculate the VA we need to copy the content, from the RVA 79 | // section[i].VirtualAddress is a RVA, mind it 80 | char* dest = ImageBase + sections[i].VirtualAddress; 81 | 82 | // check if there is Raw data to copy 83 | if(sections[i].SizeOfRawData > 0) { 84 | // We copy SizeOfRaw data bytes, from the offset PointertoRawData in the file 85 | memcpy(dest, PE_data + sections[i].PointerToRawData, sections[i].SizeOfRawData); 86 | } else { 87 | memset(dest, 0, sections[i].Misc.VirtualSize); 88 | } 89 | } 90 | 91 | IMAGE_DATA_DIRECTORY* data_directory = p_NT_HDR->OptionalHeader.DataDirectory; 92 | 93 | /** Handle imports **/ 94 | 95 | // load the address of the import descriptors array 96 | IMAGE_IMPORT_DESCRIPTOR* import_descriptors = (IMAGE_IMPORT_DESCRIPTOR*) (ImageBase + data_directory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); 97 | 98 | // this array is null terminated 99 | for(int i=0; import_descriptors[i].OriginalFirstThunk != 0; ++i) { 100 | 101 | // Get the name of the dll, and import it 102 | char* module_name = ImageBase + import_descriptors[i].Name; 103 | HMODULE import_module = LoadLibraryA(module_name); 104 | if(import_module == NULL) { 105 | return NULL; 106 | } 107 | 108 | // the lookup table points to function names or ordinals => it is the IDT 109 | IMAGE_THUNK_DATA* lookup_table = (IMAGE_THUNK_DATA*) (ImageBase + import_descriptors[i].OriginalFirstThunk); 110 | 111 | // the address table is a copy of the lookup table at first 112 | // but we put the addresses of the loaded function inside => that's the IAT 113 | IMAGE_THUNK_DATA* address_table = (IMAGE_THUNK_DATA*) (ImageBase + import_descriptors[i].FirstThunk); 114 | 115 | // null terminated array, again 116 | for(int i=0; lookup_table[i].u1.AddressOfData != 0; ++i) { 117 | void* function_handle = NULL; 118 | 119 | // Check the lookup table for the adresse of the function name to import 120 | DWORD lookup_addr = lookup_table[i].u1.AddressOfData; 121 | 122 | if((lookup_addr & IMAGE_ORDINAL_FLAG) == 0) { //if first bit is not 1 123 | // import by name : get the IMAGE_IMPORT_BY_NAME struct 124 | IMAGE_IMPORT_BY_NAME* image_import = (IMAGE_IMPORT_BY_NAME*) (ImageBase + lookup_addr); 125 | // this struct points to the ASCII function name 126 | char* funct_name = (char*) &(image_import->Name); 127 | // get that function address from it's module and name 128 | function_handle = (void*) GetProcAddress(import_module, funct_name); 129 | } else { 130 | // import by ordinal, directly 131 | function_handle = (void*) GetProcAddress(import_module, (LPSTR) lookup_addr); 132 | } 133 | 134 | if(function_handle == NULL) { 135 | return NULL; 136 | } 137 | 138 | // change the IAT, and put the function address inside. 139 | address_table[i].u1.Function = (DWORD) function_handle; 140 | } 141 | } 142 | 143 | /** Handle relocations **/ 144 | 145 | //this is how much we shifted the ImageBase 146 | DWORD delta_VA_reloc = ((DWORD) ImageBase) - p_NT_HDR->OptionalHeader.ImageBase; 147 | 148 | // if there is a relocation table, and we actually shitfted the ImageBase 149 | if(data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress != 0 && delta_VA_reloc != 0) { 150 | 151 | //calculate the relocation table address 152 | IMAGE_BASE_RELOCATION* p_reloc = (IMAGE_BASE_RELOCATION*) (ImageBase + data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); 153 | 154 | //once again, a null terminated array 155 | while(p_reloc->VirtualAddress != 0) { 156 | 157 | // how any relocation in this block 158 | // ie the total size, minus the size of the "header", divided by 2 (those are words, so 2 bytes for each) 159 | DWORD size = (p_reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION))/2; 160 | // the first relocation element in the block, right after the header (using pointer arithmetic again) 161 | WORD* reloc = (WORD*) (p_reloc + 1); 162 | for(int i=0; i> 12; 165 | // offset is the last 12 bits 166 | int offset = reloc[i] & 0x0fff; 167 | //this is the address we are going to change 168 | DWORD* change_addr = (DWORD*) (ImageBase + p_reloc->VirtualAddress + offset); 169 | 170 | // there is only one type used that needs to make a change 171 | switch(type){ 172 | case IMAGE_REL_BASED_HIGHLOW : 173 | *change_addr += delta_VA_reloc; 174 | break; 175 | default: 176 | break; 177 | } 178 | } 179 | 180 | // switch to the next relocation block, based on the size 181 | p_reloc = (IMAGE_BASE_RELOCATION*) (((DWORD) p_reloc) + p_reloc->SizeOfBlock); 182 | } 183 | } 184 | 185 | /** Map PE sections privileges **/ 186 | 187 | //Set permission for the PE hader to read only 188 | DWORD oldProtect; 189 | VirtualProtect(ImageBase, p_NT_HDR->OptionalHeader.SizeOfHeaders, PAGE_READONLY, &oldProtect); 190 | 191 | for(int i=0; iFileHeader.NumberOfSections; ++i) { 192 | char* dest = ImageBase + sections[i].VirtualAddress; 193 | DWORD s_perm = sections[i].Characteristics; 194 | DWORD v_perm = 0; //flags are not the same between virtal protect and the section header 195 | if(s_perm & IMAGE_SCN_MEM_EXECUTE) { 196 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; 197 | } else { 198 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_READWRITE : PAGE_READONLY; 199 | } 200 | VirtualProtect(dest, sections[i].Misc.VirtualSize, v_perm, &oldProtect); 201 | } 202 | 203 | return (void*) (ImageBase + entry_point_RVA); 204 | } -------------------------------------------------------------------------------- /part3/packer.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import lief 3 | import os 4 | 5 | 6 | def align(x, al): 7 | """ return aligned to """ 8 | if x % al == 0: 9 | return x 10 | else: 11 | return x - (x % al) + al 12 | 13 | 14 | def pad_data(data, al): 15 | """ return padded with 0 to a size aligned with """ 16 | return data + ([0] * (align(len(data), al) - len(data))) 17 | 18 | 19 | if __name__ =="__main__" : 20 | 21 | parser = argparse.ArgumentParser(description='Pack PE binary') 22 | parser.add_argument('input', metavar="FILE", help='input file') 23 | parser.add_argument('-p', metavar="UNPACKER", help='unpacker .exe', required=True) 24 | parser.add_argument('-o', metavar="FILE", help='output', default="packed.exe") 25 | 26 | args = parser.parse_args() 27 | 28 | # open the unpack.exe binary 29 | unpack_PE = lief.PE.parse(args.p) 30 | 31 | # we're going to keep the same alignment as the ones in unpack_PE, 32 | # because this is the PE we are modifying 33 | file_alignment = unpack_PE.optional_header.file_alignment 34 | section_alignment = unpack_PE.optional_header.section_alignment 35 | 36 | # then create the a .packed section, with the packed PE inside : 37 | 38 | # read the whole file to be packed 39 | with open(args.input, "rb") as f: 40 | input_PE_data = f.read() 41 | 42 | # create the section in lief 43 | packed_data = list(input_PE_data) # lief expects a list, not a "bytes" object. 44 | packed_data = pad_data(packed_data, file_alignment) # pad with 0 to align with file alignment (removes a lief warning) 45 | 46 | packed_section = lief.PE.Section(".packed") 47 | packed_section.content = packed_data 48 | packed_section.size = len(packed_data) 49 | packed_section.characteristics = (lief.PE.SECTION_CHARACTERISTICS.MEM_READ 50 | | lief.PE.SECTION_CHARACTERISTICS.MEM_WRITE 51 | | lief.PE.SECTION_CHARACTERISTICS.CNT_INITIALIZED_DATA) 52 | # We don't need to specify a Relative Virtual Address here, lief will just put it at the end, that doesn't matter. 53 | unpack_PE.add_section(packed_section) 54 | 55 | # remove the SizeOfImage, which should change, as we added a section. Lief will compute this for us. 56 | unpack_PE.optional_header.sizeof_image = 0 57 | 58 | 59 | # save the resulting PE 60 | if(os.path.exists(args.o)): 61 | # little trick here : lief emits no warning when it cannot write because the output 62 | # file is already opened. Using this function ensure we fail in this case (avoid errors). 63 | os.remove(args.o) 64 | 65 | builder = lief.PE.Builder(unpack_PE) 66 | builder.build() 67 | builder.write(args.o) 68 | 69 | 70 | -------------------------------------------------------------------------------- /part3/unpack.c: -------------------------------------------------------------------------------- 1 | /** 2 | Compile with : 3 | 4 | mingw32-gcc.exe unpack.c -o unpacker.exe "-Wl,--entry=__start" -nostartfiles -nostdlib -lkernel32 5 | 6 | **/ 7 | 8 | #include 9 | #include 10 | 11 | // loads a PE in memory, returns the entry point address 12 | void* load_PE (char* PE_data); 13 | 14 | // some basic functions intended to limits the external libraries needed 15 | int mystrcmp(char* a, char* b); 16 | void mymemcpy(char* dst, char* src, unsigned int size); 17 | void mymemset(char* dst, char c, unsigned int size); 18 | 19 | int _start(void) { //Entrypoint for the program 20 | 21 | // Get the current module VA (ie PE header addr) 22 | char* unpacker_VA = (char*) GetModuleHandleA(NULL); 23 | 24 | // get to the section header 25 | IMAGE_DOS_HEADER* p_DOS_HDR = (IMAGE_DOS_HEADER*) unpacker_VA; 26 | IMAGE_NT_HEADERS* p_NT_HDR = (IMAGE_NT_HEADERS*) (((char*) p_DOS_HDR) + p_DOS_HDR->e_lfanew); 27 | IMAGE_SECTION_HEADER* sections = (IMAGE_SECTION_HEADER*) (p_NT_HDR + 1); 28 | 29 | char* packed_PE = NULL; 30 | char packed_section_name[] = ".packed"; 31 | 32 | // search for the ".packed" section 33 | for(int i=0; iFileHeader.NumberOfSections; ++i) { 34 | if (mystrcmp(sections[i].Name, packed_section_name)) { 35 | packed_PE = unpacker_VA + sections[i].VirtualAddress; 36 | break; 37 | } 38 | } 39 | 40 | //load the data located at the .packed section 41 | if(packed_PE != NULL) { 42 | void (*packed_entry_point)(void) = (void(*)()) load_PE(packed_PE); 43 | packed_entry_point(); 44 | } 45 | } 46 | 47 | 48 | void* load_PE (char* PE_data) { 49 | 50 | /** Parse header **/ 51 | 52 | IMAGE_DOS_HEADER* p_DOS_HDR = (IMAGE_DOS_HEADER*) PE_data; 53 | IMAGE_NT_HEADERS* p_NT_HDR = (IMAGE_NT_HEADERS*) (((char*) p_DOS_HDR) + p_DOS_HDR->e_lfanew); 54 | 55 | DWORD hdr_image_base = p_NT_HDR->OptionalHeader.ImageBase; 56 | DWORD size_of_image = p_NT_HDR->OptionalHeader.SizeOfImage; 57 | DWORD entry_point_RVA = p_NT_HDR->OptionalHeader.AddressOfEntryPoint; 58 | DWORD size_of_headers = p_NT_HDR->OptionalHeader.SizeOfHeaders; 59 | 60 | /** Allocate Memory **/ 61 | char* ImageBase = (char*) VirtualAlloc(NULL, size_of_image, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 62 | if(ImageBase == NULL) { 63 | // Allocation failed 64 | return NULL; 65 | } 66 | 67 | /** Map PE sections in memory **/ 68 | 69 | mymemcpy(ImageBase, PE_data, p_NT_HDR->OptionalHeader.SizeOfHeaders); 70 | 71 | 72 | // Section headers starts right after the IMAGE_NT_HEADERS struct, so we do some pointer arithmetic-fu here. 73 | IMAGE_SECTION_HEADER* sections = (IMAGE_SECTION_HEADER*) (p_NT_HDR + 1); 74 | 75 | // For each sections 76 | for(int i=0; iFileHeader.NumberOfSections; ++i) { 77 | // calculate the VA we need to copy the content, from the RVA 78 | // section[i].VirtualAddress is a RVA, mind it 79 | char* dest = ImageBase + sections[i].VirtualAddress; 80 | 81 | // check if there is Raw data to copy 82 | if(sections[i].SizeOfRawData > 0) { 83 | // We copy SizeOfRaw data bytes, from the offset PointertoRawData in the file 84 | mymemcpy(dest, PE_data + sections[i].PointerToRawData, sections[i].SizeOfRawData); 85 | } else { 86 | mymemset(dest, 0, sections[i].Misc.VirtualSize); 87 | } 88 | } 89 | 90 | IMAGE_DATA_DIRECTORY* data_directory = p_NT_HDR->OptionalHeader.DataDirectory; 91 | 92 | /** Handle imports **/ 93 | 94 | // load the address of the import descriptors array 95 | IMAGE_IMPORT_DESCRIPTOR* import_descriptors = (IMAGE_IMPORT_DESCRIPTOR*) (ImageBase + data_directory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); 96 | 97 | // this array is null terminated 98 | for(int i=0; import_descriptors[i].OriginalFirstThunk != 0; ++i) { 99 | 100 | // Get the name of the dll, and import it 101 | char* module_name = ImageBase + import_descriptors[i].Name; 102 | HMODULE import_module = LoadLibraryA(module_name); 103 | if(import_module == NULL) { 104 | return NULL; 105 | } 106 | 107 | // the lookup table points to function names or ordinals => it is the IDT 108 | IMAGE_THUNK_DATA* lookup_table = (IMAGE_THUNK_DATA*) (ImageBase + import_descriptors[i].OriginalFirstThunk); 109 | 110 | // the address table is a copy of the lookup table at first 111 | // but we put the addresses of the loaded function inside => that's the IAT 112 | IMAGE_THUNK_DATA* address_table = (IMAGE_THUNK_DATA*) (ImageBase + import_descriptors[i].FirstThunk); 113 | 114 | // null terminated array, again 115 | for(int i=0; lookup_table[i].u1.AddressOfData != 0; ++i) { 116 | void* function_handle = NULL; 117 | 118 | // Check the lookup table for the adresse of the function name to import 119 | DWORD lookup_addr = lookup_table[i].u1.AddressOfData; 120 | 121 | if((lookup_addr & IMAGE_ORDINAL_FLAG) == 0) { //if first bit is not 1 122 | // import by name : get the IMAGE_IMPORT_BY_NAME struct 123 | IMAGE_IMPORT_BY_NAME* image_import = (IMAGE_IMPORT_BY_NAME*) (ImageBase + lookup_addr); 124 | // this struct points to the ASCII function name 125 | char* funct_name = (char*) &(image_import->Name); 126 | // get that function address from it's module and name 127 | function_handle = (void*) GetProcAddress(import_module, funct_name); 128 | } else { 129 | // import by ordinal, directly 130 | function_handle = (void*) GetProcAddress(import_module, (LPSTR) lookup_addr); 131 | } 132 | 133 | if(function_handle == NULL) { 134 | return NULL; 135 | } 136 | 137 | // change the IAT, and put the function address inside. 138 | address_table[i].u1.Function = (DWORD) function_handle; 139 | } 140 | } 141 | 142 | /** Handle relocations **/ 143 | 144 | //this is how much we shifted the ImageBase 145 | DWORD delta_VA_reloc = ((DWORD) ImageBase) - p_NT_HDR->OptionalHeader.ImageBase; 146 | 147 | // if there is a relocation table, and we actually shitfted the ImageBase 148 | if(data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress != 0 && delta_VA_reloc != 0) { 149 | 150 | //calculate the relocation table address 151 | IMAGE_BASE_RELOCATION* p_reloc = (IMAGE_BASE_RELOCATION*) (ImageBase + data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); 152 | 153 | //once again, a null terminated array 154 | while(p_reloc->VirtualAddress != 0) { 155 | 156 | // how any relocation in this block 157 | // ie the total size, minus the size of the "header", divided by 2 (those are words, so 2 bytes for each) 158 | DWORD size = (p_reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION))/2; 159 | // the first relocation element in the block, right after the header (using pointer arithmetic again) 160 | WORD* reloc = (WORD*) (p_reloc + 1); 161 | for(int i=0; i> 12; 164 | // offset is the last 12 bits 165 | int offset = reloc[i] & 0x0fff; 166 | //this is the address we are going to change 167 | DWORD* change_addr = (DWORD*) (ImageBase + p_reloc->VirtualAddress + offset); 168 | 169 | // there is only one type used that needs to make a change 170 | switch(type){ 171 | case IMAGE_REL_BASED_HIGHLOW : 172 | *change_addr += delta_VA_reloc; 173 | break; 174 | default: 175 | break; 176 | } 177 | } 178 | 179 | // switch to the next relocation block, based on the size 180 | p_reloc = (IMAGE_BASE_RELOCATION*) (((DWORD) p_reloc) + p_reloc->SizeOfBlock); 181 | } 182 | } 183 | 184 | /** Map PE sections privileges **/ 185 | 186 | //Set permission for the PE hader to read only 187 | DWORD oldProtect; 188 | VirtualProtect(ImageBase, p_NT_HDR->OptionalHeader.SizeOfHeaders, PAGE_READONLY, &oldProtect); 189 | 190 | for(int i=0; iFileHeader.NumberOfSections; ++i) { 191 | char* dest = ImageBase + sections[i].VirtualAddress; 192 | DWORD s_perm = sections[i].Characteristics; 193 | DWORD v_perm = 0; //flags are not the same between virtal protect and the section header 194 | if(s_perm & IMAGE_SCN_MEM_EXECUTE) { 195 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; 196 | } else { 197 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_READWRITE : PAGE_READONLY; 198 | } 199 | VirtualProtect(dest, sections[i].Misc.VirtualSize, v_perm, &oldProtect); 200 | } 201 | 202 | return (void*) (ImageBase + entry_point_RVA); 203 | } 204 | 205 | int mystrcmp(char* a, char* b) { 206 | while(*a == *b && *a) { 207 | a++; 208 | b++; 209 | } 210 | return (*a == *b); 211 | } 212 | 213 | void mymemcpy(char* dst, char* src, unsigned int size) { 214 | for(unsigned int i=0; i 2 | #include 3 | 4 | int main() { 5 | printf("Hello world with no reloc !\n"); 6 | return 0; 7 | } -------------------------------------------------------------------------------- /part4/packer.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import lief 3 | import os 4 | import subprocess 5 | 6 | def align(x, al): 7 | """ return aligned to """ 8 | if x % al == 0: 9 | return x 10 | else: 11 | return x - (x % al) + al 12 | 13 | 14 | def pad_data(data, al): 15 | """ return padded with 0 to a size aligned with """ 16 | return data + ([0] * (align(len(data), al) - len(data))) 17 | 18 | 19 | def compile_stub(input_cfile, output_exe_file, more_parameters = []): 20 | cmd = (["mingw32-gcc.exe", input_cfile, "-o", output_exe_file] # Force the ImageBase of the destination PE 21 | + more_parameters + 22 | ["-Wl,--entry=__start", # define the entry point 23 | "-nostartfiles", "-nostdlib", # no standard lib 24 | "-lkernel32" # Add necessary imports 25 | ]) 26 | print("[+] Compiling stub : "+" ".join(cmd)) 27 | subprocess.run(cmd) 28 | 29 | 30 | if __name__ =="__main__" : 31 | 32 | parser = argparse.ArgumentParser(description='Pack PE binary') 33 | parser.add_argument('input', metavar="FILE", help='input file') 34 | parser.add_argument('-o', metavar="FILE", help='output', default="packed.exe") 35 | 36 | args = parser.parse_args() 37 | 38 | # Opens the input PE 39 | input_PE = lief.PE.parse(args.input) 40 | 41 | # Compiles the unpacker stub a first time, with no particular options 42 | compile_stub("unpack.c", "unpack.exe", more_parameters=[]); 43 | 44 | # open the unpack.exe binary 45 | unpack_PE = lief.PE.parse("unpack.exe") 46 | 47 | # we're going to keep the same alignment as the ones in unpack_PE, 48 | # because this is the PE we are modifying 49 | file_alignment = unpack_PE.optional_header.file_alignment 50 | section_alignment = unpack_PE.optional_header.section_alignment 51 | 52 | 53 | ASLR = (input_PE.optional_header.dll_characteristics & lief.PE.DLL_CHARACTERISTICS.DYNAMIC_BASE != 0) 54 | if ASLR: 55 | output_PE = unpack_PE # we can use the current state of unpack_PE as our output 56 | else: 57 | # We need to add an empty section, ".alloc" just after the header 58 | # It's size will at least the size offcupied by the sections on the input PE 59 | 60 | # The RVA of the lowset section of input PE 61 | min_RVA = min([x.virtual_address for x in input_PE.sections]) # should be = 0x1000, the section alignment 62 | # The RVA of the end of the highest section 63 | max_RVA = max([x.virtual_address + x.size for x in input_PE.sections]) 64 | 65 | # Now we create the section 66 | alloc_section = lief.PE.Section(".alloc") 67 | alloc_section.virtual_address = min_RVA 68 | alloc_section.virtual_size = align(max_RVA - min_RVA, section_alignment) 69 | alloc_section.characteristics = (lief.PE.SECTION_CHARACTERISTICS.MEM_READ 70 | | lief.PE.SECTION_CHARACTERISTICS.MEM_WRITE 71 | | lief.PE.SECTION_CHARACTERISTICS.CNT_UNINITIALIZED_DATA) 72 | 73 | # to put the section just after ours, find the lowest section RVA in the stub 74 | min_unpack_RVA = min([x.virtual_address for x in unpack_PE.sections]) 75 | # and compute how much we need to move to be exactly after the .alloc section 76 | shift_RVA = (min_RVA + alloc_section.virtual_size) - min_unpack_RVA 77 | 78 | # We need to recompile the stub to make room for the .alloc section, by shifting all its sections 79 | compile_parameters = [f"-Wl,--image-base={hex(input_PE.optional_header.imagebase)}"] 80 | 81 | for s in unpack_PE.sections: 82 | compile_parameters += [f"-Wl,--section-start={s.name}={hex(input_PE.optional_header.imagebase + s.virtual_address + shift_RVA )}"] 83 | 84 | # recompile the stub with the shifted sections 85 | compile_stub("unpack.c", "shifted_unpack.exe", compile_parameters) 86 | 87 | unpack_shifted_PE = lief.PE.parse("shifted_unpack.exe") 88 | 89 | # This would insert .alloc section at the end of the table, so the RVA would not be in order. 90 | # but Windows doesn' t seem to like it : the binary doesn' t load. 91 | # output_PE = unpack_shifted_PE 92 | # output_PE.add_section(alloc_section) 93 | 94 | # Here is how we make a completely new PE, copying the important properties 95 | # And adding the sections in order 96 | output_PE = lief.PE.Binary("pe_from_scratch", lief.PE.PE_TYPE.PE32) 97 | 98 | # Copy optional headers important fields 99 | output_PE.optional_header.imagebase = unpack_shifted_PE.optional_header.imagebase 100 | output_PE.optional_header.addressof_entrypoint = unpack_shifted_PE.optional_header.addressof_entrypoint 101 | output_PE.optional_header.section_alignment = unpack_shifted_PE.optional_header.section_alignment 102 | output_PE.optional_header.file_alignment = unpack_shifted_PE.optional_header.file_alignment 103 | output_PE.optional_header.sizeof_image = unpack_shifted_PE.optional_header.sizeof_image 104 | 105 | # make sure output_PE cannot move 106 | output_PE.optional_header.dll_characteristics = 0 107 | 108 | # copy the data directories (imports most notably) 109 | for i in range(0, 15): 110 | output_PE.data_directories[i].rva = unpack_shifted_PE.data_directories[i].rva 111 | output_PE.data_directories[i].size = unpack_shifted_PE.data_directories[i].size 112 | 113 | # add the sections in order 114 | output_PE.add_section(alloc_section) 115 | for s in unpack_shifted_PE.sections: 116 | output_PE.add_section(s) 117 | 118 | # We now are ok to continue with the .packed section, as before 119 | 120 | 121 | # Create the a .packed section, with the packed PE inside : 122 | 123 | # read the whole file to be packed 124 | with open(args.input, "rb") as f: 125 | input_PE_data = f.read() 126 | 127 | # create the section in lief 128 | packed_data = list(input_PE_data) # lief expects a list, not a "bytes" object. 129 | packed_data = pad_data(packed_data, file_alignment) # pad with 0 to align with file alignment (removes a lief warning) 130 | 131 | packed_section = lief.PE.Section(".packed") 132 | packed_section.content = packed_data 133 | packed_section.size = len(packed_data) 134 | packed_section.characteristics = (lief.PE.SECTION_CHARACTERISTICS.MEM_READ 135 | | lief.PE.SECTION_CHARACTERISTICS.MEM_WRITE 136 | | lief.PE.SECTION_CHARACTERISTICS.CNT_INITIALIZED_DATA) 137 | # We don't need to specify a Relative Virtual Address here, lief will just put it at the end, that doesn't matter. 138 | output_PE.add_section(packed_section) 139 | 140 | # remove the SizeOfImage, which should change, as we added a section. Lief will compute this for us. 141 | output_PE.optional_header.sizeof_image = 0 142 | 143 | 144 | # save the resulting PE 145 | if(os.path.exists(args.o)): 146 | # little trick here : lief emits no warning when it cannot write because the output 147 | # file is already opened. Using this function ensure we fail in this case (avoid errors). 148 | os.remove(args.o) 149 | 150 | builder = lief.PE.Builder(output_PE) 151 | builder.build() 152 | builder.write(args.o) 153 | 154 | 155 | -------------------------------------------------------------------------------- /part4/unpack.c: -------------------------------------------------------------------------------- 1 | /** 2 | Compile with : 3 | 4 | mingw32-gcc.exe unpack.c -o unpacker.exe "-Wl,--entry=__start" -nostartfiles -nostdlib -lkernel32 5 | 6 | **/ 7 | 8 | #include 9 | #include 10 | 11 | // loads a PE in memory, returns the entry point address 12 | void* load_PE (char* PE_data); 13 | 14 | // some basic functions intended to limits the external libraries needed 15 | int mystrcmp(char* a, char* b); 16 | void mymemcpy(char* dst, char* src, unsigned int size); 17 | void mymemset(char* dst, char c, unsigned int size); 18 | 19 | int _start(void) { //Entrypoint for the program 20 | 21 | // Get the current module VA (ie PE header addr) 22 | char* unpacker_VA = (char*) GetModuleHandleA(NULL); 23 | 24 | // get to the section header 25 | IMAGE_DOS_HEADER* p_DOS_HDR = (IMAGE_DOS_HEADER*) unpacker_VA; 26 | IMAGE_NT_HEADERS* p_NT_HDR = (IMAGE_NT_HEADERS*) (((char*) p_DOS_HDR) + p_DOS_HDR->e_lfanew); 27 | IMAGE_SECTION_HEADER* sections = (IMAGE_SECTION_HEADER*) (p_NT_HDR + 1); 28 | 29 | char* packed_PE = NULL; 30 | char packed_section_name[] = ".packed"; 31 | 32 | // search for the ".packed" section 33 | for(int i=0; iFileHeader.NumberOfSections; ++i) { 34 | if (mystrcmp(sections[i].Name, packed_section_name)) { 35 | packed_PE = unpacker_VA + sections[i].VirtualAddress; 36 | break; 37 | } 38 | } 39 | 40 | //load the data located at the .packed section 41 | if(packed_PE != NULL) { 42 | void (*packed_entry_point)(void) = (void(*)()) load_PE(packed_PE); 43 | packed_entry_point(); 44 | } 45 | } 46 | 47 | 48 | void* load_PE (char* PE_data) { 49 | 50 | /** Parse header **/ 51 | 52 | IMAGE_DOS_HEADER* p_DOS_HDR = (IMAGE_DOS_HEADER*) PE_data; 53 | IMAGE_NT_HEADERS* p_NT_HDR = (IMAGE_NT_HEADERS*) (((char*) p_DOS_HDR) + p_DOS_HDR->e_lfanew); 54 | 55 | /** Allocate Memory **/ 56 | 57 | char* ImageBase = NULL; 58 | if(p_NT_HDR->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) { 59 | ImageBase = (char*) VirtualAlloc(NULL, p_NT_HDR->OptionalHeader.SizeOfImage, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 60 | if(ImageBase == NULL) { 61 | // Allocation failed 62 | return NULL; 63 | } 64 | } else { 65 | //if no ASLR : the packer would have placed us at the expected image base already 66 | ImageBase = (char*) GetModuleHandleA(NULL); 67 | } 68 | 69 | /** Map PE sections in memory **/ 70 | 71 | DWORD oldProtect; 72 | //The PE header is readonly, we have to make it writable to be able to change it 73 | VirtualProtect(ImageBase, p_NT_HDR->OptionalHeader.SizeOfHeaders, PAGE_READWRITE, &oldProtect); 74 | mymemcpy(ImageBase, PE_data, p_NT_HDR->OptionalHeader.SizeOfHeaders); 75 | 76 | // Section headers starts right after the IMAGE_NT_HEADERS struct, so we do some pointer arithmetic-fu here. 77 | IMAGE_SECTION_HEADER* sections = (IMAGE_SECTION_HEADER*) (p_NT_HDR + 1); 78 | 79 | // For each sections 80 | for(int i=0; iFileHeader.NumberOfSections; ++i) { 81 | // calculate the VA we need to copy the content, from the RVA 82 | // section[i].VirtualAddress is a RVA, mind it 83 | char* dest = ImageBase + sections[i].VirtualAddress; 84 | 85 | // check if there is Raw data to copy 86 | if(sections[i].SizeOfRawData > 0) { 87 | // A VirtualProtect to be sure we can write in the allocated section 88 | VirtualProtect(dest, sections[i].SizeOfRawData, PAGE_READWRITE, &oldProtect); 89 | // We copy SizeOfRaw data bytes, from the offset PointertoRawData in the file 90 | mymemcpy(dest, PE_data + sections[i].PointerToRawData, sections[i].SizeOfRawData); 91 | } else { 92 | // if no raw data to copy, we just put zeroes, based on the VirtualSize 93 | VirtualProtect(dest, sections[i].Misc.VirtualSize, PAGE_READWRITE, &oldProtect); 94 | mymemset(dest, 0, sections[i].Misc.VirtualSize); 95 | } 96 | } 97 | 98 | IMAGE_DATA_DIRECTORY* data_directory = p_NT_HDR->OptionalHeader.DataDirectory; 99 | 100 | /** Handle imports **/ 101 | 102 | // load the address of the import descriptors array 103 | IMAGE_IMPORT_DESCRIPTOR* import_descriptors = (IMAGE_IMPORT_DESCRIPTOR*) (ImageBase + data_directory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); 104 | 105 | // this array is null terminated 106 | for(int i=0; import_descriptors[i].OriginalFirstThunk != 0; ++i) { 107 | 108 | // Get the name of the dll, and import it 109 | char* module_name = ImageBase + import_descriptors[i].Name; 110 | HMODULE import_module = LoadLibraryA(module_name); 111 | if(import_module == NULL) { 112 | return NULL; 113 | } 114 | 115 | // the lookup table points to function names or ordinals => it is the IDT 116 | IMAGE_THUNK_DATA* lookup_table = (IMAGE_THUNK_DATA*) (ImageBase + import_descriptors[i].OriginalFirstThunk); 117 | 118 | // the address table is a copy of the lookup table at first 119 | // but we put the addresses of the loaded function inside => that's the IAT 120 | IMAGE_THUNK_DATA* address_table = (IMAGE_THUNK_DATA*) (ImageBase + import_descriptors[i].FirstThunk); 121 | 122 | // null terminated array, again 123 | for(int i=0; lookup_table[i].u1.AddressOfData != 0; ++i) { 124 | void* function_handle = NULL; 125 | 126 | // Check the lookup table for the adresse of the function name to import 127 | DWORD lookup_addr = lookup_table[i].u1.AddressOfData; 128 | 129 | if((lookup_addr & IMAGE_ORDINAL_FLAG) == 0) { //if first bit is not 1 130 | // import by name : get the IMAGE_IMPORT_BY_NAME struct 131 | IMAGE_IMPORT_BY_NAME* image_import = (IMAGE_IMPORT_BY_NAME*) (ImageBase + lookup_addr); 132 | // this struct points to the ASCII function name 133 | char* funct_name = (char*) &(image_import->Name); 134 | // get that function address from it's module and name 135 | function_handle = (void*) GetProcAddress(import_module, funct_name); 136 | } else { 137 | // import by ordinal, directly 138 | function_handle = (void*) GetProcAddress(import_module, (LPSTR) lookup_addr); 139 | } 140 | 141 | if(function_handle == NULL) { 142 | return NULL; 143 | } 144 | 145 | // change the IAT, and put the function address inside. 146 | address_table[i].u1.Function = (DWORD) function_handle; 147 | } 148 | } 149 | 150 | /** Handle relocations **/ 151 | 152 | //this is how much we shifted the ImageBase 153 | DWORD delta_VA_reloc = ((DWORD) ImageBase) - p_NT_HDR->OptionalHeader.ImageBase; 154 | 155 | // if there is a relocation table, and we actually shitfted the ImageBase 156 | if(data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress != 0 && delta_VA_reloc != 0) { 157 | 158 | //calculate the relocation table address 159 | IMAGE_BASE_RELOCATION* p_reloc = (IMAGE_BASE_RELOCATION*) (ImageBase + data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); 160 | 161 | //once again, a null terminated array 162 | while(p_reloc->VirtualAddress != 0) { 163 | 164 | // how any relocation in this block 165 | // ie the total size, minus the size of the "header", divided by 2 (those are words, so 2 bytes for each) 166 | DWORD size = (p_reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION))/2; 167 | // the first relocation element in the block, right after the header (using pointer arithmetic again) 168 | WORD* reloc = (WORD*) (p_reloc + 1); 169 | for(int i=0; i> 12; 172 | // offset is the last 12 bits 173 | int offset = reloc[i] & 0x0fff; 174 | //this is the address we are going to change 175 | DWORD* change_addr = (DWORD*) (ImageBase + p_reloc->VirtualAddress + offset); 176 | 177 | // there is only one type used that needs to make a change 178 | switch(type){ 179 | case IMAGE_REL_BASED_HIGHLOW : 180 | *change_addr += delta_VA_reloc; 181 | break; 182 | default: 183 | break; 184 | } 185 | } 186 | 187 | // switch to the next relocation block, based on the size 188 | p_reloc = (IMAGE_BASE_RELOCATION*) (((DWORD) p_reloc) + p_reloc->SizeOfBlock); 189 | } 190 | } 191 | 192 | /** Map PE sections privileges **/ 193 | 194 | //Set permission for the PE hader to read only 195 | VirtualProtect(ImageBase, p_NT_HDR->OptionalHeader.SizeOfHeaders, PAGE_READONLY, &oldProtect); 196 | 197 | for(int i=0; iFileHeader.NumberOfSections; ++i) { 198 | char* dest = ImageBase + sections[i].VirtualAddress; 199 | DWORD s_perm = sections[i].Characteristics; 200 | DWORD v_perm = 0; //flags are not the same between virtal protect and the section header 201 | if(s_perm & IMAGE_SCN_MEM_EXECUTE) { 202 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; 203 | } else { 204 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_READWRITE : PAGE_READONLY; 205 | } 206 | VirtualProtect(dest, sections[i].Misc.VirtualSize, v_perm, &oldProtect); 207 | } 208 | 209 | return (void*) (ImageBase + p_NT_HDR->OptionalHeader.AddressOfEntryPoint); 210 | } 211 | 212 | int mystrcmp(char* a, char* b) { 213 | while(*a == *b && *a) { 214 | a++; 215 | b++; 216 | } 217 | return (*a == *b); 218 | } 219 | 220 | void mymemcpy(char* dst, char* src, unsigned int size) { 221 | for(unsigned int i=0; i aligned to """ 8 | if x % al == 0: 9 | return x 10 | else: 11 | return x - (x % al) + al 12 | 13 | 14 | def pad_data(data, al): 15 | """ return padded with 0 to a size aligned with """ 16 | return data + ([0] * (align(len(data), al) - len(data))) 17 | 18 | 19 | def compile_stub(input_cfile, output_exe_file, more_parameters = []): 20 | cmd = (["mingw32-gcc.exe", input_cfile, "-o", output_exe_file] # Force the ImageBase of the destination PE 21 | + more_parameters + 22 | ["-Wl,--entry=__start", # define the entry point 23 | "-nostartfiles", "-nostdlib", # no standard lib 24 | "-fno-ident", "-fno-asynchronous-unwind-tables", # Remove unnecessary sections 25 | "-lkernel32" # Add necessary imports 26 | ]) 27 | print("[+] Compiling stub : "+" ".join(cmd)) 28 | subprocess.run(cmd) 29 | subprocess.run(["strip.exe", output_exe_file]) 30 | 31 | def pack_data(data) : 32 | KEY = 0xAA 33 | result = [0] * len(data) 34 | for i in range(0, len(data)): 35 | KEY = data[i] ^ KEY 36 | result[i] = KEY 37 | return result 38 | 39 | 40 | if __name__ =="__main__" : 41 | 42 | parser = argparse.ArgumentParser(description='Pack PE binary') 43 | parser.add_argument('input', metavar="FILE", help='input file') 44 | parser.add_argument('-o', metavar="FILE", help='output', default="packed.exe") 45 | 46 | args = parser.parse_args() 47 | 48 | # Opens the input PE 49 | input_PE = lief.PE.parse(args.input) 50 | 51 | # Compiles the unpacker stub a first time, with no particular options 52 | compile_stub("unpack.c", "unpack.exe", more_parameters=[]); 53 | 54 | # open the unpack.exe binary 55 | unpack_PE = lief.PE.parse("unpack.exe") 56 | 57 | # we're going to keep the same alignment as the ones in unpack_PE, 58 | # because this is the PE we are modifying 59 | file_alignment = unpack_PE.optional_header.file_alignment 60 | section_alignment = unpack_PE.optional_header.section_alignment 61 | 62 | 63 | ASLR = (input_PE.optional_header.dll_characteristics & lief.PE.DLL_CHARACTERISTICS.DYNAMIC_BASE != 0) 64 | if ASLR: 65 | output_PE = unpack_PE # we can use the current state of unpack_PE as our output 66 | else: 67 | # We need to add an empty section, ".alloc" just after the header 68 | # It's size will at least the size offcupied by the sections on the input PE 69 | 70 | # The RVA of the lowset section of input PE 71 | min_RVA = min([x.virtual_address for x in input_PE.sections]) # should be = 0x1000, the section alignment 72 | # The RVA of the end of the highest section 73 | max_RVA = max([x.virtual_address + x.size for x in input_PE.sections]) 74 | 75 | # Now we create the section 76 | alloc_section = lief.PE.Section(".alloc") 77 | alloc_section.virtual_address = min_RVA 78 | alloc_section.virtual_size = align(max_RVA - min_RVA, section_alignment) 79 | alloc_section.characteristics = (lief.PE.SECTION_CHARACTERISTICS.MEM_READ 80 | | lief.PE.SECTION_CHARACTERISTICS.MEM_WRITE 81 | | lief.PE.SECTION_CHARACTERISTICS.CNT_UNINITIALIZED_DATA) 82 | 83 | # to put the section just after ours, find the lowest section RVA in the stub 84 | min_unpack_RVA = min([x.virtual_address for x in unpack_PE.sections]) 85 | # and compute how much we need to move to be exactly after the .alloc section 86 | shift_RVA = (min_RVA + alloc_section.virtual_size) - min_unpack_RVA 87 | 88 | # We need to recompile the stub to make room for the .alloc section, by shifting all its sections 89 | compile_parameters = [f"-Wl,--image-base={hex(input_PE.optional_header.imagebase)}"] 90 | 91 | for s in unpack_PE.sections: 92 | compile_parameters += [f"-Wl,--section-start={s.name}={hex(input_PE.optional_header.imagebase + s.virtual_address + shift_RVA )}"] 93 | 94 | # recompile the stub with the shifted sections 95 | compile_stub("unpack.c", "shifted_unpack.exe", compile_parameters) 96 | 97 | unpack_shifted_PE = lief.PE.parse("shifted_unpack.exe") 98 | 99 | # This would insert .alloc section at the end of the table, so the RVA would not be in order. 100 | # but Windows doesn' t seem to like it : the binary doesn' t load. 101 | # output_PE = unpack_shifted_PE 102 | # output_PE.add_section(alloc_section) 103 | 104 | # Here is how we make a completely new PE, copying the important properties 105 | # And adding the sections in order 106 | output_PE = lief.PE.Binary("pe_from_scratch", lief.PE.PE_TYPE.PE32) 107 | 108 | # Copy optional headers important fields 109 | output_PE.optional_header.imagebase = unpack_shifted_PE.optional_header.imagebase 110 | output_PE.optional_header.addressof_entrypoint = unpack_shifted_PE.optional_header.addressof_entrypoint 111 | output_PE.optional_header.section_alignment = unpack_shifted_PE.optional_header.section_alignment 112 | output_PE.optional_header.file_alignment = unpack_shifted_PE.optional_header.file_alignment 113 | output_PE.optional_header.sizeof_image = unpack_shifted_PE.optional_header.sizeof_image 114 | 115 | # make sure output_PE cannot move 116 | output_PE.optional_header.dll_characteristics = 0 117 | 118 | # copy the data directories (imports most notably) 119 | for i in range(0, 15): 120 | output_PE.data_directories[i].rva = unpack_shifted_PE.data_directories[i].rva 121 | output_PE.data_directories[i].size = unpack_shifted_PE.data_directories[i].size 122 | 123 | # add the sections in order 124 | output_PE.add_section(alloc_section) 125 | for s in unpack_shifted_PE.sections: 126 | output_PE.add_section(s) 127 | 128 | # We now are ok to continue with the .packed section, as before 129 | 130 | 131 | # Create the a .packed section, with the packed PE inside : 132 | 133 | # read the whole file to be packed 134 | with open(args.input, "rb") as f: 135 | input_PE_data = f.read() 136 | 137 | # create the section in lief 138 | packed_data = pack_data(list(input_PE_data)) # pack the input file data 139 | packed_data = pad_data(packed_data, file_alignment) # pad with 0 to align with file alignment (removes a lief warning) 140 | 141 | packed_section = lief.PE.Section(".rodata") 142 | packed_section.content = packed_data 143 | packed_section.size = len(packed_data) 144 | packed_section.characteristics = (lief.PE.SECTION_CHARACTERISTICS.MEM_READ 145 | | lief.PE.SECTION_CHARACTERISTICS.CNT_INITIALIZED_DATA) 146 | # We don't need to specify a Relative Virtual Address here, lief will just put it at the end, that doesn't matter. 147 | output_PE.add_section(packed_section) 148 | 149 | # remove the SizeOfImage, which should change, as we added a section. Lief will compute this for us. 150 | output_PE.optional_header.sizeof_image = 0 151 | 152 | 153 | # save the resulting PE 154 | if(os.path.exists(args.o)): 155 | # little trick here : lief emits no warning when it cannot write because the output 156 | # file is already opened. Using this function ensure we fail in this case (avoid errors). 157 | os.remove(args.o) 158 | 159 | builder = lief.PE.Builder(output_PE) 160 | builder.build() 161 | builder.write(args.o) 162 | 163 | 164 | -------------------------------------------------------------------------------- /part5/unpack.c: -------------------------------------------------------------------------------- 1 | /** 2 | Compile with : 3 | 4 | mingw32-gcc.exe unpack.c -o unpacker.exe "-Wl,--entry=__start" -nostartfiles -nostdlib -lkernel32 5 | 6 | **/ 7 | 8 | #include 9 | #include 10 | 11 | // loads a PE in memory, returns the entry point address 12 | void* load_PE (char* PE_data); 13 | 14 | // some basic functions intended to limits the external libraries needed 15 | int mystrcmp(char* a, char* b); 16 | void mymemcpy(char* dst, char* src, unsigned int size); 17 | void mymemset(char* dst, char c, unsigned int size); 18 | 19 | void unpack_data(char* src, DWORD size) { 20 | DWORD oldProtect; 21 | //make sure we can write on the destination 22 | VirtualProtect(src, size, PAGE_READWRITE, &oldProtect); 23 | 24 | DWORD KEY = 0xAA; 25 | DWORD new_key = 0; 26 | for(DWORD i=0; ie_lfanew); 41 | IMAGE_SECTION_HEADER* sections = (IMAGE_SECTION_HEADER*) (p_NT_HDR + 1); 42 | 43 | char* packed_PE = unpacker_VA + sections[p_NT_HDR->FileHeader.NumberOfSections - 1].VirtualAddress; 44 | 45 | // "decrypt" in place. We could have VirtualAlloc some memory for the decryption. 46 | unpack_data(packed_PE, sections[p_NT_HDR->FileHeader.NumberOfSections - 1].SizeOfRawData); 47 | 48 | void (*packed_entry_point)(void) = (void(*)()) load_PE(packed_PE); 49 | packed_entry_point(); 50 | } 51 | 52 | 53 | void* load_PE (char* PE_data) { 54 | 55 | /** Parse header **/ 56 | 57 | IMAGE_DOS_HEADER* p_DOS_HDR = (IMAGE_DOS_HEADER*) PE_data; 58 | IMAGE_NT_HEADERS* p_NT_HDR = (IMAGE_NT_HEADERS*) (((char*) p_DOS_HDR) + p_DOS_HDR->e_lfanew); 59 | 60 | /** Allocate Memory **/ 61 | 62 | char* ImageBase = NULL; 63 | if(p_NT_HDR->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) { 64 | ImageBase = (char*) VirtualAlloc(NULL, p_NT_HDR->OptionalHeader.SizeOfImage, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 65 | if(ImageBase == NULL) { 66 | // Allocation failed 67 | return NULL; 68 | } 69 | } else { 70 | //if no ASLR : the packer would have placed us at the expected image base already 71 | ImageBase = (char*) GetModuleHandleA(NULL); 72 | } 73 | 74 | /** Map PE sections in memory **/ 75 | 76 | DWORD oldProtect; 77 | //The PE header is readonly, we have to make it writable to be able to change it 78 | VirtualProtect(ImageBase, p_NT_HDR->OptionalHeader.SizeOfHeaders, PAGE_READWRITE, &oldProtect); 79 | mymemcpy(ImageBase, PE_data, p_NT_HDR->OptionalHeader.SizeOfHeaders); 80 | 81 | // Section headers starts right after the IMAGE_NT_HEADERS struct, so we do some pointer arithmetic-fu here. 82 | IMAGE_SECTION_HEADER* sections = (IMAGE_SECTION_HEADER*) (p_NT_HDR + 1); 83 | 84 | // For each sections 85 | for(int i=0; iFileHeader.NumberOfSections; ++i) { 86 | // calculate the VA we need to copy the content, from the RVA 87 | // section[i].VirtualAddress is a RVA, mind it 88 | char* dest = ImageBase + sections[i].VirtualAddress; 89 | 90 | // check if there is Raw data to copy 91 | if(sections[i].SizeOfRawData > 0) { 92 | // A VirtualProtect to be sure we can write in the allocated section 93 | VirtualProtect(dest, sections[i].SizeOfRawData, PAGE_READWRITE, &oldProtect); 94 | // We copy SizeOfRaw data bytes, from the offset PointertoRawData in the file 95 | mymemcpy(dest, PE_data + sections[i].PointerToRawData, sections[i].SizeOfRawData); 96 | } else { 97 | // if no raw data to copy, we just put zeroes, based on the VirtualSize 98 | VirtualProtect(dest, sections[i].Misc.VirtualSize, PAGE_READWRITE, &oldProtect); 99 | mymemset(dest, 0, sections[i].Misc.VirtualSize); 100 | } 101 | } 102 | 103 | IMAGE_DATA_DIRECTORY* data_directory = p_NT_HDR->OptionalHeader.DataDirectory; 104 | 105 | /** Handle imports **/ 106 | 107 | // load the address of the import descriptors array 108 | IMAGE_IMPORT_DESCRIPTOR* import_descriptors = (IMAGE_IMPORT_DESCRIPTOR*) (ImageBase + data_directory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); 109 | 110 | // this array is null terminated 111 | for(int i=0; import_descriptors[i].OriginalFirstThunk != 0; ++i) { 112 | 113 | // Get the name of the dll, and import it 114 | char* module_name = ImageBase + import_descriptors[i].Name; 115 | HMODULE import_module = LoadLibraryA(module_name); 116 | if(import_module == NULL) { 117 | return NULL; 118 | } 119 | 120 | // the lookup table points to function names or ordinals => it is the IDT 121 | IMAGE_THUNK_DATA* lookup_table = (IMAGE_THUNK_DATA*) (ImageBase + import_descriptors[i].OriginalFirstThunk); 122 | 123 | // the address table is a copy of the lookup table at first 124 | // but we put the addresses of the loaded function inside => that's the IAT 125 | IMAGE_THUNK_DATA* address_table = (IMAGE_THUNK_DATA*) (ImageBase + import_descriptors[i].FirstThunk); 126 | 127 | // null terminated array, again 128 | for(int i=0; lookup_table[i].u1.AddressOfData != 0; ++i) { 129 | void* function_handle = NULL; 130 | 131 | // Check the lookup table for the adresse of the function name to import 132 | DWORD lookup_addr = lookup_table[i].u1.AddressOfData; 133 | 134 | if((lookup_addr & IMAGE_ORDINAL_FLAG) == 0) { //if first bit is not 1 135 | // import by name : get the IMAGE_IMPORT_BY_NAME struct 136 | IMAGE_IMPORT_BY_NAME* image_import = (IMAGE_IMPORT_BY_NAME*) (ImageBase + lookup_addr); 137 | // this struct points to the ASCII function name 138 | char* funct_name = (char*) &(image_import->Name); 139 | // get that function address from it's module and name 140 | function_handle = (void*) GetProcAddress(import_module, funct_name); 141 | } else { 142 | // import by ordinal, directly 143 | function_handle = (void*) GetProcAddress(import_module, (LPSTR) lookup_addr); 144 | } 145 | 146 | if(function_handle == NULL) { 147 | return NULL; 148 | } 149 | 150 | // change the IAT, and put the function address inside. 151 | address_table[i].u1.Function = (DWORD) function_handle; 152 | } 153 | } 154 | 155 | /** Handle relocations **/ 156 | 157 | //this is how much we shifted the ImageBase 158 | DWORD delta_VA_reloc = ((DWORD) ImageBase) - p_NT_HDR->OptionalHeader.ImageBase; 159 | 160 | // if there is a relocation table, and we actually shitfted the ImageBase 161 | if(data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress != 0 && delta_VA_reloc != 0) { 162 | 163 | //calculate the relocation table address 164 | IMAGE_BASE_RELOCATION* p_reloc = (IMAGE_BASE_RELOCATION*) (ImageBase + data_directory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); 165 | 166 | //once again, a null terminated array 167 | while(p_reloc->VirtualAddress != 0) { 168 | 169 | // how any relocation in this block 170 | // ie the total size, minus the size of the "header", divided by 2 (those are words, so 2 bytes for each) 171 | DWORD size = (p_reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION))/2; 172 | // the first relocation element in the block, right after the header (using pointer arithmetic again) 173 | WORD* reloc = (WORD*) (p_reloc + 1); 174 | for(int i=0; i> 12; 177 | // offset is the last 12 bits 178 | int offset = reloc[i] & 0x0fff; 179 | //this is the address we are going to change 180 | DWORD* change_addr = (DWORD*) (ImageBase + p_reloc->VirtualAddress + offset); 181 | 182 | // there is only one type used that needs to make a change 183 | switch(type){ 184 | case IMAGE_REL_BASED_HIGHLOW : 185 | *change_addr += delta_VA_reloc; 186 | break; 187 | default: 188 | break; 189 | } 190 | } 191 | 192 | // switch to the next relocation block, based on the size 193 | p_reloc = (IMAGE_BASE_RELOCATION*) (((DWORD) p_reloc) + p_reloc->SizeOfBlock); 194 | } 195 | } 196 | 197 | /** Map PE sections privileges **/ 198 | 199 | //Set permission for the PE hader to read only 200 | VirtualProtect(ImageBase, p_NT_HDR->OptionalHeader.SizeOfHeaders, PAGE_READONLY, &oldProtect); 201 | 202 | for(int i=0; iFileHeader.NumberOfSections; ++i) { 203 | char* dest = ImageBase + sections[i].VirtualAddress; 204 | DWORD s_perm = sections[i].Characteristics; 205 | DWORD v_perm = 0; //flags are not the same between virtal protect and the section header 206 | if(s_perm & IMAGE_SCN_MEM_EXECUTE) { 207 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; 208 | } else { 209 | v_perm = (s_perm & IMAGE_SCN_MEM_WRITE) ? PAGE_READWRITE : PAGE_READONLY; 210 | } 211 | VirtualProtect(dest, sections[i].Misc.VirtualSize, v_perm, &oldProtect); 212 | } 213 | 214 | return (void*) (ImageBase + p_NT_HDR->OptionalHeader.AddressOfEntryPoint); 215 | } 216 | 217 | int mystrcmp(char* a, char* b) { 218 | while(*a == *b && *a) { 219 | a++; 220 | b++; 221 | } 222 | return (*a == *b); 223 | } 224 | 225 | void mymemcpy(char* dst, char* src, unsigned int size) { 226 | for(unsigned int i=0; i