Objcopy --writable-text not making elf binary text section writable?
I was trying to make the .text
section of an elf
binary writable using objcopy --writable-text executable_name
. The command executes normally without any errors.
On checking the section permission through readelf
I can see that the text section is still has only read and execute permissions.
On going through the objcopy
man pages for this particular option is is mentioned that the option is not meaningful for all the binary formats. (Is this the reason I am not able to do so ?).
Can anyone point out what I am missing here.
Thanks
(Ubuntu x86_64 bit machine, GNU objcopy (GNU Binutils for Ubuntu) 2.22.90.20120924)
Solution 1:
On going through the objcopy man pages for this particular option is is mentioned that the option is not meaningful for all the binary formats. (Is this the reason I am not able to do so ?).
Yes.
At this rather detailed description of special sections of the ELF format, you see that .text
has the SHF_ALLOC + SHF_EXECINSTR
attributes (has space allocated for it and the space has executable code in it), but not SHF_WRITE
(space can be written to). What you are asking objcopy
to do simply isn't valid for ELF .text
sections.
Solution 2:
On Debian i can just link with -N and that produces an executable with a writable .text
so: ld -N obj.o
Solution 3:
First complete this: objcopy --writable-text --set-section-flags .text=CONTENTS,ALLOC,LOAD,CODE
Then objdump -x or readelf -a to view the loading section table which are typically after the program header. Please see the man page for ELF. For 32-bit executables for example:
Open the binary with hexedit and look at the value at file offset 0x1C (often 0x34), then traverse through the 0x20 byte structures (a size listed at file offset 0x2a) until you find the one which you identified in the previous dump as containing the .text section. The second to last long value will be 00000005 (05 00 00 00) and needs to have write added which will become thereby 00000007 (07 00 00 00). Now it will work as expected without any limitations such as shared library issues with -Wl,--omagic. A bit technical, but takes a few seconds to do.
Regardless, this one bit flag has caused countless problems and no explanation has clarified this small point which allows it to work flawlessly.
A code solution can be easily compiled with GCC to do the change which is likely easier and a better solution if doing it routinely:
#include <stdlib.h>
#include <stdio.h>
#include <elf.h>
int main(int argc, char** argv)
{
if (argc <= 1) return -1;
FILE* fp = fopen(argv[1], "r+");
Elf64_Ehdr teh;
fread(&teh, sizeof(teh), 1, fp);
fseek(fp, 0, SEEK_SET);
if (teh.e_ident[EI_CLASS] == ELFCLASS64) {
Elf64_Ehdr eh;
fread(&eh, sizeof(eh), 1, fp);
Elf64_Phdr* ph = malloc(eh.e_phnum * eh.e_phentsize);
Elf64_Shdr* sh = malloc(eh.e_shnum * eh.e_shentsize);
fseek(fp, eh.e_phoff, SEEK_SET);
fread(ph, eh.e_phentsize, eh.e_phnum, fp);
fseek(fp, eh.e_shoff, SEEK_SET);
fread(sh, eh.e_shentsize, eh.e_shnum, fp);
for (int i = 0; i < eh.e_phnum; i++) {
if (ph[i].p_vaddr <= eh.e_entry && ph[i].p_vaddr + ph[i].p_memsz > eh.e_entry) {
fseek(fp, eh.e_phoff + i * eh.e_phentsize + (unsigned int)&((Elf64_Phdr*)0)->p_flags, SEEK_SET);
ph[i].p_flags |= PF_W;
fwrite(&ph[i].p_flags, sizeof(ph[i].p_flags), 1, fp);
}
}
for (int i = 0; i < eh.e_shnum; i++) {
if (sh[i].sh_addr <= eh.e_entry && sh[i].sh_addr + sh[i].sh_size > eh.e_entry) {
fseek(fp, eh.e_shoff + i * eh.e_shentsize + (unsigned int)&((Elf64_Shdr*)0)->sh_flags, SEEK_SET);
sh[i].sh_flags |= SHF_WRITE;
fwrite(&sh[i].sh_flags, sizeof(sh[i].sh_flags), 1, fp);
}
}
free(ph);
free(sh);
} else {
Elf32_Ehdr eh;
fread(&eh, sizeof(eh), 1, fp);
Elf32_Phdr* ph = malloc(eh.e_phnum * eh.e_phentsize);
Elf32_Shdr* sh = malloc(eh.e_shnum * eh.e_shentsize);
fseek(fp, eh.e_phoff, SEEK_SET);
fread(ph, eh.e_phentsize, eh.e_phnum, fp);
fseek(fp, eh.e_shoff, SEEK_SET);
fread(sh, eh.e_shentsize, eh.e_shnum, fp);
for (int i = 0; i < eh.e_phnum; i++) {
if (ph[i].p_vaddr <= eh.e_entry && ph[i].p_vaddr + ph[i].p_memsz > eh.e_entry) {
fseek(fp, eh.e_phoff + i * eh.e_phentsize + (unsigned int)&((Elf32_Phdr*)0)->p_flags, SEEK_SET);
ph[i].p_flags |= PF_W;
fwrite(&ph[i].p_flags, sizeof(ph[i].p_flags), 1, fp);
}
}
for (int i = 0; i < eh.e_shnum; i++) {
if (sh[i].sh_addr <= eh.e_entry && sh[i].sh_addr + sh[i].sh_size > eh.e_entry) {
fseek(fp, eh.e_shoff + i * eh.e_shentsize + (unsigned int)&((Elf32_Shdr*)0)->sh_flags, SEEK_SET);
sh[i].sh_flags |= SHF_WRITE;
fwrite(&sh[i].sh_flags, sizeof(sh[i].sh_flags), 1, fp);
}
}
free(ph);
free(sh);
}
fflush(fp);
fclose(fp);
return 0;
}