Pokology - a community-driven site around GNU poke

_____ ---' __\_______ ______)

Fun with ELF files

__) __) ---._______) Table of Contents _________________ 1. Substitute a string with another same-size string 2. Replace main function .. 1. With objdump assistance 3. Building "Hello, world" from scratch 1 Substitute a string with another same-size string =================================================== Let's assume that we only have the compiled version of the following C code (produced by gcc -o hello hello.c on a 64-bit machine): ,---- | #include | int | main() | { | puts("Hello, Jose!"); | return 0; | } `---- To replace Jose with Luca in binary file hello, you can do this using GNU poke: ,---- | (poke) .set obase 16 | (poke) .set endian little | (poke) .set pretty-print no | (poke) load elf | (poke) .file hello | (poke) var efile = Elf64_File @ 0#B | (poke) var rodata_arr = efile.get_sections_by_name(".rodata") | (poke) rodata_arr'length | 0x1UL | (poke) var rodata = rodata_arr[0] | (poke) efile.get_section_name(rodata.sh_name) | ".rodata" | (poke) rodata | Elf64_Shdr { | sh_name=0xb3U#B, | sh_type=0x1U, | sh_flags=Elf64_SectionFlags { | flags=0x2UL | }, | sh_addr=0x2000UL#B, | sh_offset=0x2000UL#B, | sh_size=0x11UL#B, | sh_link=0x0U, | sh_info=0x0U, | sh_addralign=0x4UL, | sh_entsize=0x0UL#b | } | (poke) /* Dump the content of the section */ | (poke) dump :from rodata.sh_offset :size rodata.sh_size | 76543210 0011 2233 4455 6677 8899 aabb ccdd eeff 0123456789ABCDEF | 00002000: 0100 0200 4865 6c6c 6f2c 204a 6f73 6521 ....Hello, Jose! | 00002010: 00 . | (poke) byte[4] @ (rodata.sh_offset + 4#B + 7#B) = ['L', 'u', 'c', 'a'] | (poke) dump :from rodata.sh_offset :size rodata.sh_size | 76543210 0011 2233 4455 6677 8899 aabb ccdd eeff 0123456789ABCDEF | 00002000: 0100 0200 4865 6c6c 6f2c 204c 7563 6121 ....Hello, Luca! | 00002010: 00 . | (poke) .exit `---- If you run ./hello program, it'll show ,---- | Hello, Luca! `---- instead of original ,---- | Hello, Jose! `---- And obviously other names with at most 4 characters, are also acceptable :) 2 Replace main function ======================= Consider the following program: ,---- | #include | int | main() | { | puts("main()"); | return 0; | } | int | main2() | { | puts("main2()"); | return 0; | } `---- We want to change the executable file (produced by gcc -o main2 main2.c), to call main2 function instead of main after the startup. 2.1 With objdump assistance ~~~~~~~~~~~~~~~~~~~~~~~~~~~ By looking at the disassembly of .text section (using objdump -D -j .text main2), we can see these instructions at the end of _start label: ,---- | [...] | 105a: 48 8d 0d 0f 01 00 00 lea 0x10f(%rip),%rcx # 1170 <__libc_csu_init> | 1061: 48 8d 3d d1 00 00 00 lea 0xd1(%rip),%rdi # 1139 <main> | 1068: ff 15 72 2f 00 00 call *0x2f72(%rip) # 3fe0 <__libc_start_main@GLIBC_2.2.5> | 106e: f4 hlt | 106f: 90 nop `---- The instruction lea 0xd1(%rip),%rdi is loading the address of main into the %rdi. So we have to change the d1 00 00 00 part of the instruction which is a uint32 immediate value. Again, by looking at the objdump disassembly of main and main2, we can see that main2 - main = 0x1153 - 0x1139 = 0x1a = 26. ,---- | 0000000000001139 <main>: | 1139: 55 push %rbp | 113a: 48 89 e5 mov %rsp,%rbp | 113d: 48 8d 05 c0 0e 00 00 lea 0xec0(%rip),%rax # 2004 <_IO_stdin_used+0x4> | 1144: 48 89 c7 mov %rax,%rdi | 1147: e8 e4 fe ff ff call 1030 <puts@plt> | 114c: b8 00 00 00 00 mov $0x0,%eax | 1151: 5d pop %rbp | 1152: c3 ret | | 0000000000001153 <main2>: | 1153: 55 push %rbp | 1154: 48 89 e5 mov %rsp,%rbp | 1157: 48 8d 05 ab 0e 00 00 lea 0xeab(%rip),%rax # 2009 <_IO_stdin_used+0x9> | 115e: 48 89 c7 mov %rax,%rdi | 1161: e8 ca fe ff ff call 1030 <puts@plt> | 1166: b8 00 00 00 00 mov $0x0,%eax | 116b: 5d pop %rbp | 116c: c3 ret | 116d: 0f 1f 00 nopl (%rax) `---- In poke we have to update that immediate value to points to main2: ,---- | (poke) .file main2 | (poke) .set endian little | (poke) uint32 @ 0x1064#B = (uint32 @ 0x1064#B) + 0x1a | (poke) .close `---- After closing the poke editor, you can run the main2 and see the following output: ,---- | main2() `---- 3 Building "Hello, world" from scratch ====================================== This section is inspired by this nice blog post: "You be the linker -- building "Hello, world" from scratch, in hexadecimal" https://kevinboone.me/elfdemo.html?i=1 You can get this example file here: https://git.ageinghacker.net/git/cgit.cgi/pokology/tree/examples/fun_with_elf_hello_world.pk The idea here is to create a valid working ELF executable which runs on a x86_64 Linux machine. When you run this Poke script, it creates a fun-with-elf.exe execuable file in the current directory. And you can run it! ,---- | load elf; | | /* Gives the bytes (little-endian) of a byte-offset of 32-bit width. */ | fun u32off_as_le_bytes = (offset helloworld_adr_num) byte[4]: | { | var helloworld_adr = byte[4] (); | | /* Fill `helloworld_adr` array with bytes of `helloworld_adr_num` | in little-endian mode. */ | with_temp_ios | :endian ENDIAN_LITTLE | :do lambda void: | { | offset @ 0#B = helloworld_adr_num; | /* Disentangle the byte array from the temp IOS to make it | normal unmapped array. */ | helloworld_adr = unmap (byte[4] @ 0#B); | } | ; | return helloworld_adr; | } | | set_endian(ENDIAN_LITTLE); | | var load_adr = 0x400000UL#B, | epoint_off = 0x78UL#B, | prghdr_off = 0x40UL#B, | sechdr_off = 0xc0UL#B; | | // Address of "Hello, World\n" string for the `mov` instruction | var str_adr_bytes = u32off_as_le_bytes (load_adr + 0xa2UL#B); | | /* Building the ELF header. */ | | var ehdr = | Elf64_Ehdr | { | e_ident = Elf_Ident { | // ei_mag = [0x7fUB,'E', 'L', 'F'], | ei_class = 0x02UB, /* 64-bit */ | ei_data = 0x01UB, /* little-endian */ | ei_version = 0x01UB, | // ei_osabi = 0x0UB, | // ei_abiversion = 0x0UB, | // ei_pad = [0x0UB,0x0UB,0x0UB,0x0UB,0x0UB,0x0UB], | // ei_nident = 0x0UB#B | }, | e_type = ET_EXEC, | e_machine = 0x3eUH, /* amd64 architecture */ | e_version = 0x1U, | e_entry = load_adr + epoint_off, | e_phoff = prghdr_off, | e_shoff = sechdr_off, | // e_flags = 0x0U, | e_ehsize = Elf64_Ehdr {}'size, | e_phentsize = Elf64_Phdr {}'size, | e_phnum = 0x1UH, | e_shentsize = Elf64_Shdr {}'size, | e_shnum = 0x3UH, | e_shstrndx = 0x2UH | }; | | /* Constructing the program header. */ | | var phdr = | Elf64_Phdr | { | p_type = PT_LOAD, | p_flags = Elf_SegmentFlags { flags = PF_X | PF_R }, | // p_offset = 0UL#B, // offset within file | p_vaddr = load_adr, // in virtual memory | p_paddr = load_adr, // in physical memory | p_filesz = 0xB0UL#B, | p_memsz = 0xB0UL#B, | p_align = 0x200000UL#B, // alignment boundary for sections | }; | | /* "Compiling" the program | (x86-64 machine code) */ | | var program_text = [ | // mov 0x01, %rax ; sys_write | 0x48UB, 0xc7UB, 0xc0UB, 0x01UB, 0x00UB, 0x00UB, 0x00UB, | | // mov 0x01, %rdi ; file descriptor (stdout) | 0x48UB, 0xc7UB, 0xc7UB, 0x01UB, 0x00UB, 0x00UB, 0x00UB, | | // mov $helloworld_adr, %rsi ; location of string | 0x48UB, 0xc7UB, 0xc6UB, | str_adr_bytes[0], str_adr_bytes[1], str_adr_bytes[2], str_adr_bytes[3], | | // mov 0x0d,%rdx ; size of string, 13 bytes | 0x48UB, 0xc7UB, 0xc2UB, 0x0dUB, 0x00UB, 0x00UB, 0x00UB, | | // syscall | 0x0fUB, 0x05UB, | | // mov 0x3c,$rax ; exit program | 0x48UB, 0xc7UB, 0xc0UB, 0x3cUB, 0x00UB, 0x00UB, 0x00UB, | | // xor %rdi,%rdi ; exit code, 0 | 0x48UB, 0x31UB, 0xffUB, | | // syscall | 0x0fUB, 0x05UB, | | // Text "Hello, world\n\0" -- total 14 bytes including the null | 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '\n', '\0', | ]; | | /* Constructing the string table */ | | var string_table = [ | ".shstrtab", | ".text", | ]; | | /* Constructing the section header table */ | | var sections = [ | Elf64_Shdr {}, | Elf64_Shdr | { | sh_name = string_table[0]'size, // offset to the name of the section | sh_type = SHT_PROGBITS, // program data | sh_flags = Elf64_SectionFlags { flags = SHF_ALLOC | SHF_EXECINSTR }, | sh_addr = load_adr + epoint_off, | sh_offset = epoint_off, | sh_size = Elf64_Shdr {}'size, | // sh_link = 0U, | // sh_info = 0U, | sh_addralign = 1UL, | // sh_entsize = 0UL#B, | }, | Elf64_Shdr | { | sh_name = 0UL#B, | sh_type = SHT_STRTAB, | sh_flags = Elf64_SectionFlags {}, | // sh_addr = 0, | sh_offset = 0xB0UL#B, | sh_size = 0x10UL#B, | // sh_link = 0U, | // sh_info = 0U, | sh_addralign = 1UL, | // sh_entsize = 0UL#B, | }, | ]; | | /* Putting it all together */ | | var fd = open ("*elf*"); | var off = 0UL#B; | | Elf64_Ehdr @ off = ehdr; | off += ehdr'size; | | Elf64_Phdr @ off = phdr; | off += phdr'size; | | byte[] @ off = program_text; | off += program_text'size; | | string[] @ off = string_table; | off += string_table'size; | | Elf64_Shdr[] @ off = sections; | off += sections'size; | | save :ios fd | :file "fun-with-elf.exe" | :from 0#B | :size off | :append 0 | ; | | close (fd); `----