I’m trying to do a packer project for my school, it is made in C and ASM, and for the moment it is supposed to target only ELF
files.
I have managed to do my section injection, correctly align my new section and update the segments and section offsets/addresses (see code below). If I inspect the executable everything looks correct.
When the packed program is launched, it executes the payload (simply write ....WOODY....
), then the program correctly, and finally segfault when leaving the program.
I tried on several Linux distros, (on Unbutu no problem, on Debian and Kali it crashes).
$> lldb -- ./woody
(lldb) target create "./woody"
Current executable set to './woody' (x86_64).
(lldb) r
Process 3312 launched: '/home/maxence/Desktop/woody-woodpacker/woody' (x86_64)
....WOODY....
Hello, World!
Process 3312 stopped
* thread #1, name = 'woody', stop reason = signal SIGSEGV: address access protected (fault address: 0x7fffffffe2ea)
frame #0: 0x00007fffffffe2ea
-> 0x7fffffffe2ea: popq %rax
(lldb) bt
* thread #1, name = 'woody', stop reason = signal SIGSEGV: address access protected (fault address: 0x7fffffffe2ea)
* frame #0: 0x00007fffffffe2ea
frame #1: 0x00007ffff7c45495 libc.so.6`__run_exit_handlers(status=0, listp=0x00007ffff7e1a838, run_list_atexit=true, run_dtors=true) at exit.c:113:8
frame #2: 0x00007ffff7c45610 libc.so.6`__GI_exit(status=<unavailable>) at exit.c:143:3
frame #3: 0x00007ffff7c29d97 libc.so.6`__libc_start_call_main(main=(woody`main), argc=-7394, argv=0x00007fffffffdf18) at libc_start_call_main.h:74:3
frame #4: 0x00007ffff7c29e40 libc.so.6`__libc_start_main_impl(main=(woody`main), argc=-7394, argv=0x00007fffffffdf18, init=0x00007ffff7ffd040, fini=<unavailable>, rtld_fini=<unavailable>, stack_end=0x00007fffffffdf08) at libc-start.c:392:3
frame #5: 0x0000555555555085 woody`_start + 37
Debian:
On debian only the payload executes
$> lldb -- ./woody
(lldb) target create "./woody"
Current executable set to './woody' (x86_64).
(lldb) r
Process 4374 launched: '/home/bitnami/woody-woodpacker/woody' (x86_64)
....WOODY....
Process 4374 stopped
* thread #1, name = 'woody', stop reason = signal SIGSEGV: invalid address (fault address: 0x49)
frame #0: 0x0000555555555054 woody`_start + 4
woody`_start:
-> 0x555555555054 <+4>: rcrl 0x48(%rsi)
0x555555555057 <+7>: movl %esp, %edx
0x555555555059 <+9>: andq $-0x10, %rsp
0x55555555505d <+13>: pushq %rax
(lldb)
Here is a breakdown of my process:
- search for the last program header with a PT_LOAD type
- search for the last section of the previously found segment
- create a new section, define its attributes (type, flags, alignment, …), calculate its offset and address, copy the payload.
- update the size of the segment containing our new section
- update the offsets of the sections behind our new section
- finally update the entry point and write the offset to the old one in the payload (replacing the temporary jump addr)
Once done I move on to the writing process, making sure to add the necessary padding between the sections to maintain the alignments.
I specify that the payload is in assembly and that for the moment it only writes a message (the final payload is supposed to decrypt .text section before jumping to it).
I checked the alignments of the sections, the addresses, the offsets, the sizes of the sections and segments, the paddings, I tested several payloads down to the smallest possible with a simple jump or more complex, inspected instruction by instruction, inspected the binary byte by byte, nothing worked, I couldn’t find anything. Honestly I’m out of ideas and I need some fresh air.
Here is my code for the section insertion:
typedef struct s_elf_file {
union u_elf_e_ident
{
struct {
uint32_t ei_magic;
uint8_t ei_class;
uint8_t ei_data;
uint8_t ei_version;
uint8_t ei_osabi;
uint8_t ei_abi_version;
char ei_pad[7];
};
uint8_t raw[16];
} e_ident;
uint16_t e_type;
uint16_t e_machine;
uint32_t e_version;
uint64_t e_entry;
uint64_t e_phoff;
uint64_t e_shoff;
uint32_t e_flags;
uint16_t e_ehsize;
uint16_t e_phentsize;
uint16_t e_phnum;
uint16_t e_shentsize;
uint16_t e_shnum;
uint16_t e_shstrndx;
char *e_type_name;
t_elf_program_header *program_headers;
t_elf_section_table *section_tables;
} t_elf_file;
typedef struct s_elf_section_table {
uint32_t sh_name_offset;
uint32_t sh_type;
uint64_t sh_flags;
uint64_t sh_address;
uint64_t sh_offset;
uint64_t sh_size;
uint32_t sh_link;
uint32_t sh_info;
uint64_t sh_addralign;
uint64_t sh_entsize;
uint8_t *data;
char *sh_name;
} t_elf_section_table;
typedef struct s_elf_program_header
{
uint32_t p_type;
uint32_t p_flags;
uint64_t p_offset;
uint64_t p_vaddr;
uint64_t p_paddr;
uint64_t p_filesz;
uint64_t p_memsz;
uint64_t p_align;
} t_elf_program_header;
size_t calculate_padding(size_t size, size_t alignment) {
size_t padding = (alignment - (size % alignment)) % alignment;
return (padding);
}
size_t calculate_padded_size(size_t size, size_t alignment) {
size_t padding = (alignment - (size % alignment)) % alignment;
return (size + padding);
}
int efl_find_last_prog_header(t_elf_file *elf)
{
int index = 0;
for (int i = 0; i < elf->e_phnum; i++)
{
if (elf->program_headers[i].p_type == PT_LOAD)
{
index = i;
}
}
return (index);
}
int efl_find_last_section_header(t_elf_file *elf, int progindex)
{
int index = 0;
t_elf_program_header *prog = (t_elf_program_header *)elf->program_headers + progindex;
for (int j = 1; j < elf->e_shnum; j++)
{
if (elf->section_tables[j].sh_offset >= prog->p_offset &&
elf->section_tables[j].sh_offset + elf->section_tables[j].sh_size <= prog->p_offset + prog->p_filesz)
{
index = j;
}
}
return (index);
}
uint8_t *prepare_payload(t_elf_section_table *new_section_headers, t_packer *packer)
{
uint8_t *payload = (uint8_t *)malloc(packer->payload_64_size);
if (!payload)
return (NULL);
ft_memcpy(payload, packer->payload_64, packer->payload_64_size);
ft_memcpy(payload + packer->payload_64_size - WD_PAYLOAD_OFF_KEY, key_aes, WD_AES_KEY_SIZE);
return (payload);
}
int set_new_elf_section_string_table(t_elf_file *elf, t_elf_section_table *new_section)
{
char *section_name = WB_SECTION_NAME;
size_t section_name_len = ft_strlen(section_name);
int section_string_table_index = elf->e_shstrndx;
int old_size = elf->section_tables[section_string_table_index].sh_size;
size_t new_string_table_size = elf->section_tables[section_string_table_index].sh_size + section_name_len + 1;
elf->section_tables[section_string_table_index].data = ft_realloc(elf->section_tables[section_string_table_index].data, new_string_table_size);
if (elf->section_tables[section_string_table_index].data == NULL) {
return (-1);
}
ft_memcpy(elf->section_tables[section_string_table_index].data + old_size, section_name, section_name_len + 1);
new_section->sh_name_offset = old_size;
new_section->sh_name = WB_SECTION_NAME;
elf->section_tables[section_string_table_index].sh_size = new_string_table_size;
return (0);
}
int create_new_elf_section(t_elf_file *elf, t_packer *packer, int last_load_prog, int last_section_in_prog)
{
t_elf_section_table *new_section_headers;
elf->e_shnum += 1;
size_t new_section_headers_size = sizeof(t_elf_section_table) * elf->e_shnum;
elf->section_tables = ft_realloc(elf->section_tables, new_section_headers_size);
if (!elf->section_tables)
return (-1);
t_elf_section_table *new_section = ft_calloc(1, sizeof(t_elf_section_table));
if (!new_section)
return (-1);
new_section->sh_type = SHT_PROGBITS;
new_section->sh_flags = SHF_EXECINSTR | SHF_ALLOC;
new_section->sh_addralign = 16;
uint64_t current_offset_padding = calculate_padding(elf->section_tables[last_section_in_prog].sh_offset + elf->section_tables[last_section_in_prog].sh_size, elf->section_tables[last_section_in_prog].sh_addralign);
uint64_t current_offset = elf->section_tables[last_section_in_prog].sh_offset + elf->section_tables[last_section_in_prog].sh_size + current_offset_padding;
uint64_t current_vaddr_padding = calculate_padding(elf->section_tables[last_section_in_prog].sh_address + elf->section_tables[last_section_in_prog].sh_size, elf->section_tables[last_section_in_prog].sh_addralign);
uint64_t current_vaddr = elf->section_tables[last_section_in_prog].sh_address + elf->section_tables[last_section_in_prog].sh_size + current_vaddr_padding;
new_section->sh_offset = current_offset;
new_section->sh_address = current_vaddr;
packer->new_section_size = current_offset_padding;
packer->loader_offset = new_section->sh_address;
if ((new_section->data = prepare_payload(new_section, packer)) == NULL)
{
free(new_section);
return (-1);
}
new_section->sh_size = calculate_padded_size(packer->payload_64_size, new_section->sh_addralign);
packer->new_section_size += new_section->sh_size;
size_t remaining_after_section_headers_data_size = sizeof(t_elf_section_table) * (elf->e_shnum - last_section_in_prog - 1 - 1);
size_t remaining_after_section_headers_count = sizeof(char *) * (elf->e_shnum - last_section_in_prog - 1 - 1);
memmove(elf->section_tables + last_section_in_prog + 2, elf->section_tables + last_section_in_prog + 1, remaining_after_section_headers_data_size);
last_section_in_prog += 1;
if (elf->e_shstrndx != 0)
{
if (elf->e_shstrndx >= last_section_in_prog) {
elf->e_shstrndx += 1;
}
if (set_new_elf_section_string_table(elf, new_section) == -1)
{
free(new_section);
return (-1);
}
}
else
{
dprintf(2, "No section string table foundn");
}
ft_memcpy(&elf->section_tables[last_section_in_prog], new_section, sizeof(t_elf_section_table));
for (int i = 0; i < elf->e_shnum; i++) {
char *section_name = elf->section_tables[i].sh_name;
if (strcmp(section_name, ".symtab") == 0) {
elf->section_tables[i].sh_link += 1;
}
}
free(new_section);
return (0);
}
void update_program_header(t_elf_file *elf, t_packer *packer, int last_loadable, int last_loadable_section)
{
elf->program_headers[last_loadable].p_memsz += packer->new_section_size;
elf->program_headers[last_loadable].p_filesz += packer->new_section_size;
elf->program_headers[last_loadable].p_flags |= PF_X | PF_W | PF_R;
}
void update_section_addr(t_elf_file *elf, t_packer *packer, int last_loadable)
{
for (int i = last_loadable; i < elf->e_shnum - 1; i++) {
if (elf->section_tables[i].sh_type == SHT_NOBITS) {
elf->section_tables[i + 1].sh_offset = elf->section_tables[i].sh_offset;
continue;
}
uint64_t offset_padding = calculate_padded_size(
elf->section_tables[i].sh_offset + elf->section_tables[i].sh_size,
elf->section_tables[i + 1].sh_addralign
) - (elf->section_tables[i].sh_offset + elf->section_tables[i].sh_size);
elf->section_tables[i + 1].sh_offset = elf->section_tables[i].sh_offset + elf->section_tables[i].sh_size + offset_padding;
elf->section_tables[i + 1].sh_address = elf->section_tables[i].sh_address + elf->section_tables[i].sh_size + offset_padding;
}
int section_count = elf->e_shnum;
elf->e_shoff = elf->section_tables[section_count - 1].sh_offset + elf->section_tables[section_count - 1].sh_size;
}
void update_entry_point(t_elf_file *elf, t_packer *packer, int last_loadable)
{
uint64_t last_entry_point = elf->e_entry;
elf->e_entry = elf->section_tables[last_loadable].sh_address;
uint64_t jmp_instruction_address = elf->e_entry + packer->payload_64_size - WD_PAYLOAD_RETURN_ADDR;
uint64_t next_instruction_address = jmp_instruction_address;
int32_t offset = (int32_t)(last_entry_point - next_instruction_address);
ft_memcpy(elf->section_tables[last_loadable].data + packer->payload_64_size - WD_PAYLOAD_RETURN_ADDR, &offset, sizeof(offset));
}
int elf_insert_section(t_elf_file *elf)
{
t_packer packer;
packer.payload_64_size = WB_PAYLOAD_SIZE;
packer.payload_64 = (uint8_t *)wd_playload_64;
int progi = efl_find_last_prog_header(elf);
int sectioni = efl_find_last_section_header(elf, progi);
if (create_new_elf_section(elf, &packer, progi, sectioni) == -1)
return (-1);
sectioni += 1;
update_program_header(elf, &packer, progi, sectioni);
update_section_addr(elf, &packer, sectioni);
update_entry_point(elf, &packer, sectioni);
return (0);
}
And here is my payload:
global _payload_64
[BITS 64]
segment .text align=16
_payload_64:
push rax
push rdx
push rsi
push rdi
jmp .print_start_msg
.displayed_str:
db "....WOODY....", 0x0a, 0
.print_start_msg:
mov rax, 0x1
mov rdi, 1
lea rsi, [rel .displayed_str]
mov rdx, 15
syscall
pop rdi
pop rsi
pop rdx
pop rax
jmp 0x01020304
info_start:
key: dq "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ; reserved space for the key
And finaly here is the writing process:
int write_to_file(int fd, void *data, size_t size) {
ssize_t written = write(fd, data, size);
if (written == -1) {
return (-1);
}
offset += written;
return (0);
}
void add_zero_padding(int fd, size_t end_offset) {
char c = 0;
while(offset < end_offset) {
write_to_file(fd, &c, sizeof(c));
}
}
int packer(t_elf_file *elf)
{
...
size_t elf_header_size = sizeof(t_elf_file) - sizeof(char *) - sizeof(t_elf_program_header *) - sizeof(t_elf_section_table *);
size_t elf_section_header_size = sizeof(t_elf_section_table) - sizeof(char *) - sizeof(uint8_t *);
write_to_file(fd, elf, elf_header_size);
add_zero_padding(fd, elf->e_phoff);
for (int i = 0; i < elf->e_phnum; i++) {
write_to_file(fd, &elf->program_headers[i], sizeof(t_elf_program_header));
}
for (int i = 0; i < elf->e_shnum; i++) {
if (elf->section_tables[i].sh_type != SHT_NOBITS)
{
add_zero_padding(fd, elf->section_tables[i].sh_offset);
write_to_file(fd, elf->section_tables[i].data, elf->section_tables[i].sh_size);
}
}
add_zero_padding(fd, elf->e_shoff);
for (int i = 0; i < elf->e_shnum; i++) {
write_to_file(fd, &elf->section_tables[i], elf_section_header_size);
}
...
}
If it can help here is some info (my custom section is .i'm a teapot
, it’s a private jock ;))
ELF Header:
Magic: 7F 45 4C 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN
Version: 0x1
Entry point: 0x4010
Start of program headers: 64 (bytes into file)
Start of section headers: 14139 (bytes into file)
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 13
Size of section headers: 64 (bytes)
Number of section headers: 32
Section header string table index: 31
Section Headers:
[Nr] Name Type Address Offset Size Align
[ 0] NULL 000000000000000000 000000000000000000 000000000000000000 000000000000000000
[ 1] .interp PROGBITS 0x0000000000000318 0x0000000000000318 0x000000000000001c 0x0000000000000001
[ 2] .note.gnu.property NOTE 0x0000000000000338 0x0000000000000338 0x0000000000000030 0x0000000000000008
[ 3] .note.gnu.build-id NOTE 0x0000000000000368 0x0000000000000368 0x0000000000000024 0x0000000000000004
[ 4] .note.ABI-tag NOTE 0x000000000000038c 0x000000000000038c 0x0000000000000020 0x0000000000000004
[ 5] .gnu.hash 0x6ffffff6 0x00000000000003b0 0x00000000000003b0 0x0000000000000024 0x0000000000000008
[ 6] .dynsym DYNSYM 0x00000000000003d8 0x00000000000003d8 0x00000000000000a8 0x0000000000000008
[ 7] .dynstr STRTAB 0x0000000000000480 0x0000000000000480 0x000000000000008d 0x0000000000000001
[ 8] .gnu.version 0x6fffffff 0x000000000000050e 0x000000000000050e 0x000000000000000e 0x0000000000000002
[ 9] .gnu.version_r 0x6ffffffe 0x0000000000000520 0x0000000000000520 0x0000000000000030 0x0000000000000008
[10] .rela.dyn RELA 0x0000000000000550 0x0000000000000550 0x00000000000000c0 0x0000000000000008
[11] .rela.plt RELA 0x0000000000000610 0x0000000000000610 0x0000000000000018 0x0000000000000008
[12] .init PROGBITS 0x0000000000001000 0x0000000000001000 0x000000000000001b 0x0000000000000004
[13] .plt PROGBITS 0x0000000000001020 0x0000000000001020 0x0000000000000020 0x0000000000000010
[14] .plt.got PROGBITS 0x0000000000001040 0x0000000000001040 0x0000000000000010 0x0000000000000010
[15] .plt.sec PROGBITS 0x0000000000001050 0x0000000000001050 0x0000000000000010 0x0000000000000010
[16] .text PROGBITS 0x0000000000001060 0x0000000000001060 0x0000000000000112 0x0000000000000010
[17] .fini PROGBITS 0x0000000000001174 0x0000000000001174 0x000000000000000d 0x0000000000000004
[18] .rodata PROGBITS 0x0000000000002000 0x0000000000002000 0x0000000000000012 0x0000000000000004
[19] .eh_frame_hdr PROGBITS 0x0000000000002014 0x0000000000002014 0x0000000000000034 0x0000000000000004
[20] .eh_frame PROGBITS 0x0000000000002048 0x0000000000002048 0x00000000000000ac 0x0000000000000008
[21] .init_array PREINIT_ARRAY 0x0000000000003db8 0x0000000000002db8 0x0000000000000008 0x0000000000000008
[22] .fini_array GROUP 0x0000000000003dc0 0x0000000000002dc0 0x0000000000000008 0x0000000000000008
[23] .dynamic DYNAMIC 0x0000000000003dc8 0x0000000000002dc8 0x00000000000001f0 0x0000000000000008
[24] .got PROGBITS 0x0000000000003fb8 0x0000000000002fb8 0x0000000000000048 0x0000000000000008
[25] .data PROGBITS 0x0000000000004000 0x0000000000003000 0x0000000000000010 0x0000000000000008
[26] .i'm a teapot PROGBITS 0x0000000000004010 0x0000000000003010 0x0000000000000090 0x0000000000000010
[27] .bss NOBITS 0x00000000000040a0 0x00000000000030a0 0x0000000000000008 0x0000000000000001
[28] .comment PROGBITS 000000000000000000 0x00000000000030a0 0x000000000000002b 0x0000000000000001
[29] .symtab SYMTAB 0x0000000000000030 0x00000000000030d0 0x0000000000000368 0x0000000000000008
[30] .strtab STRTAB 0x0000000000000398 0x0000000000003438 0x00000000000001db 0x0000000000000001
[31] .shstrtab STRTAB 0x0000000000000573 0x0000000000003613 0x0000000000000128 0x0000000000000001
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040 0x00000000000002d8 0x00000000000002d8 R 0x8
INTERP 0x0000000000000318 0x0000000000000318 0x0000000000000318 0x000000000000001c 0x000000000000001c R 0x1
LOAD 000000000000000000 000000000000000000 000000000000000000 0x0000000000000628 0x0000000000000628 R 0x1000
LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000 0x0000000000000181 0x0000000000000181 XR 0x1000
LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000 0x00000000000000f4 0x00000000000000f4 R 0x1000
LOAD 0x0000000000002db8 0x0000000000003db8 0x0000000000003db8 0x00000000000002e8 0x00000000000002f0 XWR 0x1000
DYNAMIC 0x0000000000002dc8 0x0000000000003dc8 0x0000000000003dc8 0x00000000000001f0 0x00000000000001f0 WR 0x8
NOTE 0x0000000000000338 0x0000000000000338 0x0000000000000338 0x0000000000000030 0x0000000000000030 R 0x8
NOTE 0x0000000000000368 0x0000000000000368 0x0000000000000368 0x0000000000000044 0x0000000000000044 R 0x4
0x6474e553 0x0000000000000338 0x0000000000000338 0x0000000000000338 0x0000000000000030 0x0000000000000030 R 0x8
0x6474e550 0x0000000000002014 0x0000000000002014 0x0000000000002014 0x0000000000000034 0x0000000000000034 R 0x4
0x6474e551 000000000000000000 000000000000000000 000000000000000000 000000000000000000 000000000000000000 WR 0x10
0x6474e552 0x0000000000002db8 0x0000000000003db8 0x0000000000003db8 0x0000000000000248 0x0000000000000248 R 0x1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
03 .init .plt .plt.got .plt.sec .text .fini
04 .rodata .eh_frame_hdr .eh_frame
05 .init_array .fini_array .dynamic .got .data .i'm a teapot .bss
06 .dynamic
07 .note.gnu.property
08 .note.gnu.build-id .note.ABI-tag
09 .note.gnu.property
10 .eh_frame_hdr
11
12 .init_array .fini_array .dynamic .got
Thank you very much in advance for your help 😉
Maxence Gama is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
6