Last active
June 9, 2021 10:53
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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