Skip to content

Instantly share code, notes, and snippets.

@Justasic
Last active June 9, 2021 10:53
Show Gist options
  • Save Justasic/176adbcc1cb4b738a656 to your computer and use it in GitHub Desktop.
Save Justasic/176adbcc1cb4b738a656 to your computer and use it in GitHub Desktop.
This program writes an ELF program out that returns 42 as it's exit value.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
// NOTICE: The program this program generates is explicitly 32-bit only (working only on the x86 architecture)
// Reference Documents:
// 1. http://docs.cs.up.ac.za/programming/asm/derick_tut/syscalls.html
// 2. http://ref.x86asm.net/geek32.html
// 3. http://man7.org/linux/man-pages/man2/mmap.2.html
// 4. http://www.bigmessowires.com/2015/10/08/a-handmade-executable-file/
// 5. https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
// 6. http://man7.org/linux/man-pages/man5/elf.5.html
void WriteValue(uint8_t **buffer, const void *value, size_t len)
{
// Copy the data to the memory space.
memcpy(*buffer, value, len);
// Increment the current location of the pointer.
*buffer += len;
}
void WriteInt32(uint8_t **buffer, uint32_t value) { WriteValue(buffer, &value, sizeof(uint32_t)); }
void WriteInt16(uint8_t **buffer, uint16_t value) { WriteValue(buffer, &value, sizeof(uint16_t)); }
void WriteInt8(uint8_t **buffer, uint8_t value) { WriteValue(buffer, &value, sizeof(uint8_t)); }
#define PROGRAM_SIZE (1024)
int main()
{
// We must use MMAP here because malloc is given memory blocks which are set with read/write permissions
// but we require execute permissions, this means that if we try to execute memory allocated from malloc
// the CPU will send a page fault to the kernel and result in a Segmentation Fault due to the NX bit being
// enabled on the CPU. Therefore we must explicitly request a memory block with +rwx permissions from the
// kernel via mmap.
// MAP_PRIVATE = private memory allocation to the process
// MAP_ANON = memory is not backed by a file and memory space is initialized to 0
uint8_t *exebuf = malloc(PROGRAM_SIZE);
if (!exebuf)
{
perror("malloc");
return EXIT_FAILURE;
}
// zero the memory just in case.
bzero(exebuf, PROGRAM_SIZE);
// Save our initial allocated position so we can determine the length of stuff.
uint8_t *start = exebuf;
////////////////////////////////////////////////////
// Build the ELF header
//
// e_ident - ELF identification array
WriteValue(&exebuf, "\x7F""ELF", 4); // EI_MAG0 to EI_MAG3 and 0x7F beginning of elf header
WriteInt8(&exebuf, 0x01); // EI_CLASS - x86 code (not x86_64)
WriteInt8(&exebuf, 0x01); // EI_DATA - the endianness of the code (1 = little endian, 2 = big endian, x86 and x86_64 are little endian)
WriteInt8(&exebuf, 0x01); // EI_VERSION - ELF version (always 1)
WriteInt8(&exebuf, 0x03); // EI_OSABI - the kernel ABI (Linux, Solaris, OpenBSD, etc.)
exebuf += 8; // EI_PAD - padding for unused options, 8 bytes.
// Continue with the structure.
WriteInt16(&exebuf, 0x02); // e_type - What kind of ELF file is it (Executable, relocatable, shared object, etc.)
WriteInt16(&exebuf, 0x03); // e_machine - What CPU architecture we're using.
WriteInt32(&exebuf, 0x01); // e_version - ELF version (again always 1)
uint8_t *e_entry = exebuf;
exebuf += sizeof(uint32_t) * 3;
//WriteInt32(&exebuf, 0x8000090); // e_entry - Entry point of the application.
//WriteInt32(&exebuf, ); // e_phoff - Offset to the Program Header table.
//WriteInt32(&exebuf, ); // e_shoff - Offset to the Section Header table.
WriteInt32(&exebuf, 0x00); // e_flags - ? used for processor flags?
WriteInt16(&exebuf, 0x34); // e_ehsize - Size of this header (52 bytes total so it will be 0x34)
WriteInt16(&exebuf, 0x20); // e_phentsize - Contains the size of a program header table entry.
WriteInt16(&exebuf, 0x01); // e_phnum - Contains the number of entries in the program header table.
WriteInt16(&exebuf, 0x28); // e_shentsize - Contains the size of a section header table entry.
WriteInt16(&exebuf, 0x03); // e_shnum - Contains the number of entries in the section header table.
WriteInt16(&exebuf, 0x02); // e_shstrndx - Contains index of the section header table entry that contains the section names.
// Write e_phoff here cuz this is where it starts.
e_entry += sizeof(uint32_t); // Skip e_entry
// Write e_phoff
WriteInt32(&e_entry, (uint32_t)(exebuf - start));
// Reset e_entry to its original state.
e_entry = e_entry - (sizeof(uint32_t) * 2);
////////////////////////////////////////////////////
// Build the program header table.
//
WriteInt32(&exebuf, 0x01); // p_type - What kind of segment this is. (a loadable one)
WriteInt32(&exebuf, 0x00); // p_offset - Offset where it should be read
WriteInt32(&exebuf, 0x400000); // p_vaddr - Virtual address where it should be loaded
WriteInt32(&exebuf, 0x400000); // p_paddr - Physical address where it should be loaded
uint8_t *p_filesz = exebuf;
exebuf += sizeof(uint32_t) * 2;
// Segments were super confusing at first but I believe these are simply
// the chunks of memory that is actually copied to RAM for execution whereas
// sections simply house various data in the file itself.
// In this case, our segment is the same as the .text section and p_filesz == p_memsz
//WriteInt32(&exebuf, 0x); // p_filesz - Size of segment in this file
//WriteInt32(&exebuf, 0x); // p_memsz - Size of segment in memory
WriteInt32(&exebuf, 0x05); // p_flags - Flags related to permissions on the segment.
WriteInt32(&exebuf, 0x1000); // p_align - Alignment of the segment.
// Write entry point
WriteInt32(&e_entry, (uint32_t)(exebuf - start) + 0x400000); // location inside file + p_vaddr
e_entry += sizeof(uint32_t); // skip e_phoff
uint8_t *e_textSection = exebuf;
////////////////////////////////////////////////////
// Build the code to put in the .text section
//
// The below code calls the following C function:
// sys_exit(42);
// Insert our syscall value
WriteInt8(&exebuf, 0xB8); // MOV eax,
WriteInt32(&exebuf, 0x01); // 0x01 = sys_exit
// Insert our exit value
WriteInt8(&exebuf, 0xBB); // MOV ebx,
WriteInt32(&exebuf, 0x2A); // 42 (our exit value)
// Call the linux system call interrupt
WriteInt8(&exebuf, 0xCD); // INT
WriteInt8(&exebuf, 0x80); // 0x80 - linux syscall interrupt.
// Save the location of the section names section (below).
uint8_t *e_sections = exebuf;
// Write the segment?
uint32_t size = (e_sections - e_textSection);
// Double write to fill out p_memsz as well as p_filesz.
WriteInt32(&p_filesz, size);
WriteInt32(&p_filesz, size);
////////////////////////////////////////////////////
// Build the section names section: .shrtrtab
//
// Map out our section names according to how we've written them
// in this program.
static char sections[] = "\0.text\0.shrtrtab\0";
WriteValue(&exebuf, sections, sizeof(sections));
// Write the section header table location to the elf header
WriteInt32(&e_entry, (uint32_t)(exebuf - start));
// Reset e_entry to its original state.
e_entry = e_entry - (sizeof(uint32_t) * 2);
////////////////////////////////////////////////////
// Build the section headers. Currently there is only
// 2 sections: .text (assembly) and .shrtrtab (section names)
//
// Null section
WriteInt32(&exebuf, (e_sections - start)); // sh_name - Offset to section name in section string table.
WriteInt32(&exebuf, 0x00); // sh_type - What type of section this is
WriteInt32(&exebuf, 0x00); // sh_flags - Section attributes as 1-bit flags
WriteInt32(&exebuf, 0x00); // sh_addr - If this section is loaded at runtime, this is the virtual memory location of this section.
WriteInt32(&exebuf, 0x00); // sh_offset - Offset of the section inside the file from file beginning.
WriteInt32(&exebuf, 0x00); // sh_size - Size of the section
WriteInt32(&exebuf, 0x00); // sh_link - "This member holds a section header table index link, whose interpretation depends on the section type." - man 5 elf
WriteInt32(&exebuf, 0x00); // sh_info - Any extra information on the section.
WriteInt32(&exebuf, 0x00); // sh_addralign - Section address alignment.
WriteInt32(&exebuf, 0x00); // sh_entsize - Size of any external tables for example: A symbol table.
// .text section (our code)
WriteInt32(&exebuf, (e_sections - start) + 7); // sh_name - Offset to section name in section string table.
WriteInt32(&exebuf, 0x01); // sh_type - What type of section this is (SHT_DYNAMIC)
WriteInt32(&exebuf, 0x06); // sh_flags - Section attributes as 1-bit flags
WriteInt32(&exebuf, (uint32_t)*e_entry); // sh_addr - If this section is loaded at runtime, this is the virtual memory location of this section.
WriteInt32(&exebuf, (uint32_t)(e_textSection - start)); // sh_offset - Offset of the section inside the file from file beginning.
WriteInt32(&exebuf, (uint32_t)(e_sections - e_textSection)); // sh_size - Size of the section
WriteInt32(&exebuf, 0x00); // sh_link - "This member holds a section header table index link, whose interpretation depends on the section type." - man 5 elf
WriteInt32(&exebuf, 0x00); // sh_info - Any extra information on the section.
WriteInt32(&exebuf, 0x00); // sh_addralign - Section address alignment.
WriteInt32(&exebuf, 0x00); // sh_entsize - Size of any external tables for example: A symbol table.
// .shrtrtab section (string index for section table)
WriteInt32(&exebuf, (e_sections - start) + 1); // sh_name - Offset to section name in section string table.
WriteInt32(&exebuf, 0x03); // sh_type - What type of section this is
WriteInt32(&exebuf, 0x00); // sh_flags - Section attributes as 1-bit flags
WriteInt32(&exebuf, 0x00); // sh_addr - If this section is loaded at runtime, this is the virtual memory location of this section.
WriteInt32(&exebuf, (e_sections - start)); // sh_offset - Offset of the section inside the file from file beginning.
WriteInt32(&exebuf, sizeof(sections)); // sh_size - Size of the section
WriteInt32(&exebuf, 0x00); // sh_link - "This member holds a section header table index link, whose interpretation depends on the section type." - man 5 elf
WriteInt32(&exebuf, 0x00); // sh_info - Any extra information on the section.
WriteInt32(&exebuf, 0x01); // sh_addralign - Section address alignment.
WriteInt32(&exebuf, 0x00); // sh_entsize - Size of any external tables for example: A symbol table.
////////////////////////////////////////////////////
// Write the now compiled ELF executable to a file
//
FILE *f = fopen("generated.elf", "wb");
if (!f)
{
perror("fopen");
free(start);
return EXIT_FAILURE;
}
// Write the entire 1K block of memory
fwrite(start, 1, PROGRAM_SIZE, f);
fclose(f);
printf("Executable written, total program bytes %d\n", exebuf - start);
free(start);
return EXIT_SUCCESS;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment