/* * This file is part of patchelfcrc. * Copyright (c) 2022 Mario Hüttel. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2 only. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct elf_section { GElf_Shdr section_header; Elf_Scn *scn; char *name; uint64_t lma; /**< @Resolved load memory address of a section. May be equivalent to VMA */ }; struct elfpatch { uint32_t magic; int fd; bool readonly; Elf *elf; GElf_Ehdr ehdr; int class; SlList *sections; GElf_Phdr *program_headers; /**< @brief Program headers */ size_t program_headers_count; /**< @brief Number of program headers in the program headers array */ }; #define ELFPATCH_MAGIC 0x8545637Aul #define is_elfpatch_struct(x) ((x) && (x)->magic == (ELFPATCH_MAGIC)) #define ret_if_ep_err(ep) do { \ if (!is_elfpatch_struct((ep))) { \ return; \ } \ } while(0) #define ret_val_if_ep_err(ep, val) do { \ if (!is_elfpatch_struct((ep))) { \ return (val); \ } \ } while(0) /** * @brief Convert a series of 4 bytes into a uint32_t dpending on endianess * @param data 4 bytes * @param little_endian data is little endian * @return uint32 */ static uint32_t get_uint32_from_byte_string(const uint8_t *data, bool little_endian) { uint32_t out = 0ul; int i; for (i = 0; i < 4; i++) { if (little_endian) out >>= 8u; else out <<= 8u; out |= (((uint32_t)data[i]) << (little_endian ? 24u : 0u)); } return out; } static void write_crc_to_byte_array(uint8_t *byte_array, uint32_t crc, uint8_t crc_size_bytes, bool little_endian) { int i; if (!byte_array) return; for (i = 0; i < crc_size_bytes; i++) { if (little_endian) { byte_array[i] = (uint8_t)(crc & 0xFFul); crc >>= 8u; } else { byte_array[i] = (uint8_t)((crc & 0xFF000000ul) >> 24u); crc <<= 8u; } } } static void free_elf_section_element(struct elf_section *sec) { if (sec) { if (sec->name) free(sec->name); sec->name = NULL; free(sec); } } static const char *section_type_to_str(Elf64_Word type) { switch (type) { case SHT_NULL: return "NULL"; case SHT_PROGBITS: return "PROGBITS"; case SHT_SYMTAB: return "SYMTAB"; case SHT_STRTAB: return "STRTAB"; case SHT_NOBITS: return "NOBITS"; case SHT_ARM_EXIDX: return "ARM_EXIDX"; case SHT_INIT_ARRAY: return "INIT_ARRAY"; case SHT_FINI_ARRAY: return "FINI_ARRAY"; default: break; } return "unknown"; } static void print_sections(elfpatch_handle_t *ep) { SlList *iter; ft_table_t *table; const struct elf_section *section; ret_if_ep_err(ep); if (!ep->sections) { print_err("No sections found\n"); return; } if (!reporting_get_verbosity()) return; table = ft_create_table(); /* Write header */ ft_set_cell_prop(table, 0, FT_ANY_COLUMN, FT_CPROP_ROW_TYPE, FT_ROW_HEADER); ft_write_ln(table, "Section", "Type", "Size", "VMA", "LMA", "File Offset"); for (iter = ep->sections; iter; iter = sl_list_next(iter)) { section = (const struct elf_section *)iter->data; if (!section) continue; ft_printf_ln(table, "%s|%s|%lu|%p|%p|%p", section->name, section_type_to_str(section->section_header.sh_type), section->section_header.sh_size, (void *)section->section_header.sh_addr, (void *)section->lma, (void *)section->section_header.sh_offset ); } print_debug("%s\n", ft_to_string(table)); ft_destroy_table(table); } static SlList *elf_patch_get_sections(elfpatch_handle_t *ep) { SlList *ret = NULL; Elf_Scn *scn; struct elf_section *sec; char *name; size_t shstrndx; ret_val_if_ep_err(ep, NULL); if (ep->sections) sl_list_free_full(ret, (void (*)(void *))free_elf_section_element); ep->sections = NULL; if (elf_getshdrstrndx (ep->elf , &shstrndx) != 0) { print_err("ELF error: %s\n", elf_errmsg(-1)); goto ret_free_section_list; } scn = NULL; while ((scn = elf_nextscn(ep->elf, scn)) != NULL) { sec = (struct elf_section *)calloc(1u, sizeof(struct elf_section)); sec->name = NULL; sec->scn = scn; if (gelf_getshdr(scn, &sec->section_header) != &sec->section_header) { print_err("Error reading section header: %s\n", elf_errmsg(-1)); free(sec); continue; } /* Default setting of LMA if not modified by segment */ sec->lma = (uint64_t)sec->section_header.sh_addr; name = elf_strptr(ep->elf, shstrndx, sec->section_header.sh_name); if (name) { sec->name = strdup(name); } ret = sl_list_append(ret, sec); } ep->sections = ret; return ret; ret_free_section_list: sl_list_free_full(ret, (void (*)(void *))free_elf_section_element); ret = NULL; return ret; } /** * @brief Read program headers from ELF file and store them more conviniently in a linkled list * @param ep Elfpatch object * @return 0 if successful * @return negative if error. * @note The function will succeed even if no program heder is found in the file. */ static int elf_patch_read_program_headers(elfpatch_handle_t *ep) { size_t header_count = 0ull; GElf_Phdr *hdr; size_t i; ret_val_if_ep_err(ep, -1001); if (ep->program_headers_count > 0 && ep->program_headers) { /* Free the program headers. They are owned by the ELF object. So no need to free them */ free(ep->program_headers); ep->program_headers_count = 0; } if (elf_getphdrnum(ep->elf, &header_count)) { print_err("Error reading program headers: %s\n", elf_errmsg(-1)); return -1; } ep->program_headers = (GElf_Phdr *)malloc(header_count * sizeof(GElf_Phdr)); if (!ep->program_headers) { /* Mem error. Abort. Program will crash eventually */ return -1; } for (i = 0u; i < header_count; i++) { hdr = &ep->program_headers[i]; if (gelf_getphdr(ep->elf, (int)i, hdr) != hdr) { print_err("Error reading program header (%zu): %s\n", i, elf_errmsg(-1)); goto ret_free_err; } print_debug("Program Header (%zu): mem_size: %zu, file_size: %zu, vma: %p, lma: %p, file offset: %zu\n", i, (size_t)hdr->p_memsz, (size_t)hdr->p_filesz, (void *)hdr->p_vaddr, (void *)hdr->p_paddr, hdr->p_offset); } ep->program_headers_count = header_count; return 0; ret_free_err: if (ep->program_headers) free(ep->program_headers); ep->program_headers_count = 0u; return -1; } static void resolve_section_lmas(elfpatch_handle_t *ep) { SlList *sec_iter; struct elf_section *sec; size_t idx; uint64_t sec_file_offset; uint64_t section_offset_in_segment; const GElf_Phdr *phdr; ret_if_ep_err(ep); for (sec_iter = ep->sections; sec_iter; sec_iter = sl_list_next(sec_iter)) { sec = (struct elf_section *)sec_iter->data; if (!sec) continue; if (sec->section_header.sh_type == SHT_NOBITS) { /* Section does not contain data. It may be allocated but is not loaded. Therefore, LMA=VMA. */ sec->lma = (uint64_t)sec->section_header.sh_addr; continue; } sec_file_offset = (uint64_t) sec->section_header.sh_offset; /* Check in which segment the file offset is located */ for (idx = 0; idx < ep->program_headers_count; idx++) { phdr = &ep->program_headers[idx]; if (sec_file_offset >= phdr->p_offset && sec_file_offset < (phdr->p_offset + phdr->p_filesz)) { /* Section lies within this segment */ section_offset_in_segment = sec_file_offset - phdr->p_offset; sec->lma = ((uint64_t)phdr->p_paddr) + section_offset_in_segment; break; } } } } static int elf_patch_update_info(elfpatch_handle_t *ep) { Elf_Kind ek; const char *type_string = "unrecognized"; ret_val_if_ep_err(ep, -1001); ek = elf_kind(ep->elf); switch (ek) { case ELF_K_AR: type_string = "archive"; break; case ELF_K_ELF: type_string = "elf object"; break; default: /* Unrecognized is the default. Do nothing */ break; } print_debug("ELF File Type: %s\n", type_string); if (ek != ELF_K_ELF) return -1; gelf_getehdr(ep->elf, &ep->ehdr); ep->class = gelf_getclass(ep->elf); switch (ep->class) { case ELFCLASS32: print_debug("ELF class: 32 bit\n"); break; case ELFCLASS64: print_debug("ELF class: 64 bit\n"); break; default: print_err("Unsupported ELF class: %d\n", ep->class); return -1; } if (!elf_patch_get_sections(ep)) { print_err("No sections in file.\n"); return -1; } if (elf_patch_read_program_headers(ep)) { print_err("Error reading program headers.\n"); return -1; } /* Resolve section to segment mapping to calculate the LMA of eachs section */ resolve_section_lmas(ep); /* Print the debug section table */ print_sections(ep); return 0; } elfpatch_handle_t *elf_patch_open(const char *path, bool readonly, bool expect_little_endian) { struct elfpatch *ep; const char *ident; /* This is important to guarantee structure packing behavior */ CRC_OUT_CHECK_STRUCT_SIZES; if (!path) { print_err("Internal error while opeing ELF file. No path specified\n"); return NULL; } ep = (struct elfpatch *)calloc(1u, sizeof(struct elfpatch)); ep->magic = ELFPATCH_MAGIC; ep->readonly = readonly; /* This shouldn't really be necessary due to the use of calloc() */ ep->sections = NULL; ep->program_headers = NULL; ep->program_headers_count = 0u; ep->fd = open(path, readonly ? O_RDONLY : O_RDWR, 0); if (ep->fd < 0) { print_err("Error opening file: %s\n", path); goto free_struct; } ep->elf = elf_begin(ep->fd, readonly ? ELF_C_READ : ELF_C_RDWR, NULL); if (!ep->elf) { print_err("[LIBELF] %s\n", elf_errmsg(-1)); goto close_fd; } /* Prevent Libelf from relayouting the sections, which would brick the load segments */ elf_flagelf(ep->elf, ELF_C_SET, ELF_F_LAYOUT); if (elf_patch_update_info(ep)) { print_err("File malformatted. Cannot use for CRC patching\n"); goto close_elf; } ident = elf_getident(ep->elf, NULL); if (ident) { switch (ident[5]) { case 1: print_debug("ELF Endianess: little\n"); if (!expect_little_endian) { print_err("Big endian format expected. File is little endian. Double check settings!\n"); } break; case 2: print_debug("ELF Endianess: big\n"); if (expect_little_endian) { print_err("Little endian format expected. File is big endian. Double check settings!\n"); } break; default: print_err("Cannot determine endianess of ELF file. EI_DATA is: %d\n", ident[5]); break; } } return (elfpatch_handle_t *)ep; close_elf: if (ep->elf) { elf_end(ep->elf); ep->elf = NULL; } close_fd: if (ep->fd > 0) { close(ep->fd); } free_struct: free(ep); ep = NULL; return (elfpatch_handle_t *)ep; } static struct elf_section *find_section_in_list(SlList *list, const char *name) { SlList *iter; struct elf_section *ret = NULL; struct elf_section *sec; for (iter = list; iter; iter = sl_list_next(iter)) { sec = (struct elf_section *)iter->data; if (strcmp(sec->name, name) == 0) { ret = sec; break; } } return ret; } int elf_patch_check_for_section(elfpatch_handle_t *ep, const char *section) { int ret; ret_val_if_ep_err(ep, -1001); ret = find_section_in_list(ep->sections, section) ? 0 : -1; return ret; } static size_t translate_index(size_t index, enum granularity granularity, bool little_endian) { size_t word_idx; size_t part_idx; size_t d_index; size_t gran_in_bytes; if (!little_endian || granularity == GRANULARITY_BYTE) return index; gran_in_bytes = (size_t)granularity / 8u; word_idx = index / gran_in_bytes; part_idx = index - word_idx * gran_in_bytes; d_index = word_idx * gran_in_bytes + gran_in_bytes - 1u - part_idx; return d_index; } int elf_patch_compute_crc_over_section(elfpatch_handle_t *ep, const char *section, struct crc_calc *crc, enum granularity granularity, bool little_endian) { const struct elf_section *sec; Elf_Data *data; size_t idx; unsigned int gran_in_bytes = (unsigned int)granularity / 8u; unsigned int padding_count = 0u; ret_val_if_ep_err(ep, -1001); if (!section || !crc) return -1000; /* Find section */ sec = find_section_in_list(ep->sections, section); if (!sec) { print_err("Cannot find section %s\n", section); return -1; } data = elf_getdata(sec->scn, NULL); if (!data) { print_err("Error reading section data from %s: %s\n", section, elf_errmsg(-1)); return -1; } print_debug("Section data length: %lu\n", data->d_size); if (!data->d_size) { print_err("Section %s contains no data.\n", section); return -2; } /* NOBIT sections have a length but no data in the file. Abort in this case */ if (!data->d_buf) { print_err("Section %s does not contain loadable data.\n", section); return -2; } /* If big endian or granularity is byte, simply compute CRC. No reordering is necessary */ if (!little_endian || granularity == GRANULARITY_BYTE) { crc_push_bytes(crc, data->d_buf, data->d_size); } else { /* Little endian case with > byte sized chunks */ /* Check granularity vs size of section */ padding_count = (gran_in_bytes - data->d_size % gran_in_bytes) % gran_in_bytes; if (padding_count) { print_err("Section '%s' is not a multiple size of the given granularity. %u zero padding bytes will be added.\n", section, padding_count); } for (idx = 0; idx < data->d_size; idx++) { crc_push_byte(crc, ((char *)data->d_buf)[translate_index(idx, granularity, little_endian)]); } /* Pad with zeroes */ for (idx = 0; idx < padding_count; idx++) { crc_push_byte(crc, 0x00); } } return 0; } static size_t calculate_needed_space_for_crcs(elfpatch_handle_t *ep, enum crc_format format, bool check_start_magic, bool check_end_magic, uint8_t crc_size_bytes, size_t crc_count) { size_t needed_space = 0ull; switch (format) { case FORMAT_BARE: needed_space = crc_size_bytes * crc_count; break; case FORMAT_STRUCT: /* Calculate space for CRCs including sentinel struct at the end */ needed_space = (crc_count + 1) * (ep->class == ELFCLASS32 ? sizeof(struct crc_out_struct_32bit) : sizeof(struct crc_out_struct_64bit)); break; default: needed_space = 0; print_err("Unsupported CRC output format\n"); } /* Add existing magic numbers to required space */ if (check_start_magic) { needed_space += 4u; /* Account for paading after 32 bit magic value in case of structure usage on 64 bit systems */ if (ep->class == ELFCLASS64 && format == FORMAT_STRUCT) needed_space += 4u; } if (check_end_magic) needed_space += 4u; return needed_space; } static void get_section_addr_and_length(const struct elf_section *sec, uint64_t *vma, uint64_t *len) { if (!sec) return; if (vma) *vma = sec->section_header.sh_addr; if (len) *len = sec->section_header.sh_size; } static void get_section_load_addr(const struct elf_section *sec, uint64_t *lma) { if (!sec || !lma) return; *lma = sec->lma; } int elf_patch_write_crcs_to_section(elfpatch_handle_t *ep, const char *section, const SlList *section_name_list, const uint32_t *crcs, uint8_t crc_size_bits, uint32_t start_magic, uint32_t end_magic, bool check_start_magic, bool check_end_magic, enum crc_format format, bool little_endian) { int ret = -1; struct elf_section *output_section; struct elf_section *input_section; Elf_Data *output_sec_data; const SlList *iter; size_t needed_space; size_t crc_count; uint8_t crc_size_bytes; uint8_t *sec_bytes; size_t idx; struct crc_out_struct_32bit crc_32bit; struct crc_out_struct_64bit crc_64bit; ret_val_if_ep_err(ep, -1000); print_debug("== Patch output file ==\n"); if (crc_size_bits < 1u || crc_size_bits > 32u) { print_err("Unsupported CRC size: %u", (unsigned int)crc_size_bits); return -1; } /* All pointer parameters are required */ if (!section || !section_name_list || !crcs) return -1000; output_section = find_section_in_list(ep->sections, section); if (!output_section) { print_err("Cannot find output section '%s' to place CRCs. Exiting.\n", section); goto ret_err; } /* Get data object of section */ output_sec_data = elf_getdata(output_section->scn, NULL); sec_bytes = (uint8_t *)output_sec_data->d_buf; if (!sec_bytes) { print_err("Output section '%s' does not contain loadable data. It has to be allocated in the ELF file\n", section); goto ret_err; } /* Check the start and end magics */ if (check_start_magic) { if (get_uint32_from_byte_string(sec_bytes, little_endian) != start_magic) { print_err("Start magic does not match: expected: 0x%08x, got: 0x%08x\n", start_magic, get_uint32_from_byte_string(sec_bytes, little_endian)); goto ret_err; } print_debug("Start magic matching: 0x%08x\n", start_magic); } if (check_end_magic) { if (get_uint32_from_byte_string(&sec_bytes[output_sec_data->d_size - 4], little_endian) != end_magic) { print_err("End magic does not match: expected: 0x%08x, got: 0x%08x\n", end_magic, get_uint32_from_byte_string(&sec_bytes[output_sec_data->d_size - 4], little_endian)); goto ret_err; } print_debug("End magic matching: 0x%08x\n", end_magic); } /* Calculate Bytes needed for CRC */ crc_size_bytes = (crc_size_bits + 7u) / 8u; crc_count = sl_list_length(section_name_list); if (crc_count < 1) { /* No CRCs to patch... */ ret = -1; print_err("No CRCs to patch. This is probably an internal error.\n"); goto ret_err; } print_debug("Single CRC requires %u bytes.\n", (unsigned int)crc_size_bytes); needed_space = calculate_needed_space_for_crcs(ep, format, check_start_magic, check_end_magic, crc_size_bytes, crc_count); print_debug("Required space for %zu CRCs %s: %zu (available: %zu)\n", crc_count, (check_start_magic || check_end_magic ? "including magic values" : ""), needed_space, output_sec_data->d_size ); if (needed_space > output_sec_data->d_size) { print_err("Not enough space in section. %zu bytes available but %zu needed\n", output_sec_data->d_size, needed_space); ret = -1; goto ret_err; } /* Checks finished. Write data to output section */ if (format == FORMAT_BARE) { if (check_start_magic) sec_bytes += 4u; for (iter = section_name_list, idx = 0; iter; iter = sl_list_next(iter), idx++) { print_debug("Write CRC 0x%08x (%u bytes) for section %s\n", crcs[idx], (unsigned int)crc_size_bytes, iter->data); write_crc_to_byte_array(sec_bytes, crcs[idx], crc_size_bytes, little_endian); sec_bytes += crc_size_bytes; } } else if (format == FORMAT_STRUCT) { if (check_start_magic) sec_bytes += 4u; if (check_start_magic && ep->class == ELFCLASS64) sec_bytes += 4u; for (iter = section_name_list, idx = 0; iter; iter = sl_list_next(iter), idx++) { input_section = find_section_in_list(ep->sections, (const char *)iter->data); if (!input_section) { print_err("Internal error. Please report this. %s:%d ", __FILE__, __LINE__); ret = -2; goto ret_err; } print_debug("Write CRC 0x%08x (%u bytes) for section %s.\n", crcs[idx], (unsigned int)crc_size_bytes, iter->data); print_debug("Corresponding input section at 0x%"PRIx64", length: %"PRIu64"\n", (uint64_t)input_section->section_header.sh_addr, (uint64_t)input_section->section_header.sh_size); if (ep->class == ELFCLASS32) { crc_32bit.crc = crcs[idx]; crc_32bit.length = (uint32_t)input_section->section_header.sh_size; crc_32bit.start_address = (uint32_t)input_section->section_header.sh_addr; memcpy(sec_bytes, &crc_32bit, sizeof(crc_32bit)); sec_bytes += sizeof(crc_32bit); } else { /* 64 bit case */ crc_64bit.crc = crcs[idx]; crc_64bit._unused_dummy = 0ul; crc_64bit.length = (uint64_t)input_section->section_header.sh_size; crc_64bit.start_address = (uint64_t)input_section->section_header.sh_addr; memcpy(sec_bytes, &crc_64bit, sizeof(crc_64bit)); sec_bytes += sizeof(crc_64bit); } } /* Append sentinel struct */ crc_32bit.crc = 0ul; crc_32bit.length = 0ul; crc_32bit.start_address = 0ul; crc_64bit.crc = 0ul; crc_64bit.length = 0ull; crc_64bit.start_address = 0ull; if (ep->class == ELFCLASS32) { memcpy(sec_bytes, &crc_32bit, sizeof(crc_32bit)); } else { memcpy(sec_bytes, &crc_64bit, sizeof(crc_64bit)); } } /* Flag section data as invalid to trigger rewrite. * This is needed due to the forced memory layout */ elf_flagdata(output_sec_data, ELF_C_SET, ELF_F_DIRTY); ret = 0; ret_err: return ret; } void elf_patch_close_and_free(elfpatch_handle_t *ep) { ret_if_ep_err(ep); if (ep->elf) { /* Update ELF file */ if (ep->readonly) { print_debug("DRY RUN: File will not be updated\n"); } else { if (elf_update(ep->elf, ELF_C_WRITE) < 0) { print_err("Error writing ELF file: %s\n", elf_errmsg(-1)); } } } if (ep->elf) elf_end(ep->elf); if (ep->fd > 0) close(ep->fd); if (ep->sections) sl_list_free_full(ep->sections, (void (*)(void *))free_elf_section_element); ep->sections = NULL; if (ep->program_headers) { free(ep->program_headers); ep->program_headers = NULL; } ep->program_headers_count = 0u; ep->elf = NULL; ep->fd = 0; free(ep); } int elf_patch_get_section_address(elfpatch_handle_t *ep, const char *section, uint64_t *vma, uint64_t *lma, uint64_t *len) { const struct elf_section *sec; ret_val_if_ep_err(ep, -1001); if (!section) return -1002; sec = find_section_in_list(ep->sections, section); if (!sec) return -1; get_section_addr_and_length(sec, vma, len); get_section_load_addr(sec, lma); return 0; } int elf_patch_get_bits(elfpatch_handle_t *ep) { int bitsize; ret_val_if_ep_err(ep, -1001); switch (ep->class) { case ELFCLASS32: bitsize = 32; break; case ELFCLASS64: bitsize = 64; break; default: bitsize = -1; break; } return bitsize; }