Skip to content

Commit a9d8f3d

Browse files
committed
fix: use notes for elf implementation
Signed-off-by: Robert Günzler <r@gnzler.io>
1 parent a626430 commit a9d8f3d

File tree

3 files changed

+60
-103
lines changed

3 files changed

+60
-103
lines changed

README.markdown

Lines changed: 2 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -65,53 +65,6 @@ The run-time lookup uses APIs from `<mach-o/getsect.h>`.
6565

6666
### Linux
6767

68-
For ELF executables, the resources are added as sections. Unfortunately
69-
there is no guaranteed metadata about sections in the ELF format, the
70-
section header table (SHT) is optional and can be stripped after linking
71-
(`llvm-strip --strip-sections`). So while the resources are added as
72-
sections, the run-time lookup requires a bespoke implementation, which
73-
makes it the most complex and non-standard out of the platforms. There
74-
are N+1 sections used for the implementation, where the extra section
75-
serves as our own version of the SHT (which can't be stripped). Finding
76-
the SHT section at run-time is done via a static pointer in the code
77-
which is looked up by its symbol name and has its value updated after
78-
the sections are injected.
79-
80-
The build-time equivalent is somewhat involved since our version of
81-
the SHT has to be manually constructed after adding the sections, and
82-
the section updated with the new content. There's also not a standard
83-
tool that can change the value of a static variable after linking, so
84-
instead the SHT is found by a symbol which is added via
85-
`objcopy --binary-architecture`. The following instructions show how
86-
to embed the binary data at build-time for ELF executables, with the
87-
section holding the data name "foobar":
68+
For ELF executables, the resources are added as notes.
8869

89-
```sh
90-
# The binary data should be on disk in a file named foobar - the name
91-
# of the file is important as it is used in the added symbols
92-
$ objcopy --input binary --output elf64-x86-64 \
93-
--binary-architecture i386:x86-64 \
94-
--rename-section .data=foobar,CONTENTS,ALLOC,LOAD,READONLY,DATA \
95-
foobar foobar.o
96-
# Also create an empty section for the SHT
97-
$ objcopy --input binary --output elf64-x86-64 \
98-
--binary-architecture i386:x86-64 \
99-
--rename-section .data=foobar,CONTENTS,ALLOC,LOAD,READONLY,DATA \
100-
postject_sht postject_sht.o
101-
# Link the created .o files at build-time. Also define
102-
# `__POSTJECT_NO_SHT_PTR` so that the run-time code uses the
103-
# symbol added by `--binary-architecture` to find the SHT
104-
$ clang++ -D__POSTJECT_NO_SHT_PTR test.cpp foobar.o postject_sht.o
105-
# Dump the sections with readelf, and grab the virtual address and
106-
# size for the "foobar" section
107-
$ readelf -S a.out
108-
# Manually create the SHT - replace 0xAA with the virtual address
109-
# and 0xBB with the size from the readelf output
110-
$ python3 -c "import struct; print((struct.pack('<I', 1) + bytes('foobar', 'ascii') + bytes([0]) + struct.pack('<QI', 0xAA, 0xBB)).decode('ascii'), end='');" > postject_sht
111-
# Update the SHT section with the correct content
112-
$ objcopy --update-section postject_sht=postject_sht a.out
113-
```
114-
115-
The run-time lookup finds our version of the SHT through the static
116-
pointer, and then parses the contents of our SHT to look for the
117-
section with the requested name.
70+
The build-time equivalent is to use a linker script.

postject-api.h

Lines changed: 49 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
#include <mach-o/dyld.h>
1212
#include <mach-o/getsect.h>
1313
#elif defined(__linux__)
14-
#include <link.h>
1514
#include <elf.h>
16-
#include <fcntl.h>
17-
#include <unistd.h>
15+
#include <link.h>
16+
#include <sys/param.h>
17+
#include <sys/auxv.h>
1818
#elif defined(_WIN32)
1919
#include <windows.h>
2020
#endif
@@ -92,50 +92,52 @@ static const void* postject_find_resource(const char* name,
9292

9393
return ptr;
9494
#elif defined(__linux__)
95-
void* ptr = NULL;
96-
int fd, i;
97-
#if defined(__LP64__)
98-
Elf64_Ehdr e;
99-
Elf64_Shdr s;
100-
#else
101-
Elf32_Ehdr e;
102-
Elf32_Shdr s;
103-
#endif
104-
char *strs;
105-
106-
if (options != NULL && options->elf_section_name != NULL) {
107-
name = options->elf_section_name;
108-
}
109-
110-
// This executable might be a Position Independent Executable (PIE), so
111-
// the virtual address values need to be added to the relocation address
112-
uintptr_t relocation_addr = _r_debug.r_map->l_addr;
113-
114-
if ((fd = open("/proc/self/exe", O_RDONLY, 0)) != -1) {
115-
if (read(fd, &e, sizeof(e)) == sizeof(e)) {
116-
lseek(fd, e.e_shoff + (e.e_shstrndx * e.e_shentsize), SEEK_SET);
117-
if (read(fd, &s, sizeof(s)) == sizeof(s)) {
118-
strs = (char *)malloc(s.sh_size);
119-
lseek(fd, s.sh_offset, SEEK_SET);
120-
if (read(fd, strs, s.sh_size) == s.sh_size) {
121-
for (i = 0; i < e.e_shnum; i++) {
122-
lseek(fd, e.e_shoff + (i * e.e_shentsize), SEEK_SET);
123-
if (read(fd, &s, sizeof(s)) != sizeof(s)) {
124-
break;
125-
}
126-
if ((!strcmp(strs + s.sh_name, name)) && (s.sh_type == SHT_PROGBITS)) {
127-
ptr = (void*)(relocation_addr + (uintptr_t)s.sh_addr);
128-
*size = s.sh_size;
129-
break;
130-
}
131-
}
132-
}
133-
free(strs);
134-
}
135-
}
136-
close(fd);
137-
}
138-
return ptr;
95+
void *ptr = NULL;
96+
97+
if (options != NULL && options->elf_section_name != NULL) {
98+
name = options->elf_section_name;
99+
}
100+
101+
uintptr_t p = getauxval(AT_PHDR);
102+
size_t n = getauxval(AT_PHNUM);
103+
uintptr_t base_addr = p - sizeof(ElfW(Ehdr));
104+
105+
// iterate program header
106+
for (; n > 0; n--, p += sizeof(ElfW(Phdr))) {
107+
ElfW(Phdr) *phdr = (ElfW(Phdr) *)p;
108+
109+
// skip everything but notes
110+
if (phdr->p_type != PT_NOTE) {
111+
continue;
112+
}
113+
114+
// note segment starts at base address + segment virtual address
115+
uintptr_t pos = (base_addr + phdr->p_vaddr);
116+
uintptr_t end = (pos + phdr->p_memsz);
117+
118+
// iterate through segment until we reach the end
119+
while (pos < end) {
120+
if (pos + sizeof(ElfW(Nhdr)) > end) {
121+
break; // invalid
122+
}
123+
124+
ElfW(Nhdr) *note = (ElfW(Nhdr) *)(uintptr_t)pos;
125+
if (note->n_namesz != 0 && note->n_descsz != 0 &&
126+
strncmp((char *)(pos + sizeof(ElfW(Nhdr))),
127+
(char *)name, sizeof(name)) == 0) {
128+
*size = note->n_descsz;
129+
// advance past note header and aligned name
130+
// to get to description data
131+
return (void *)((uintptr_t)note +
132+
sizeof(ElfW(Nhdr)) +
133+
roundup(note->n_namesz, 4));
134+
}
135+
136+
pos += (sizeof(ElfW(Nhdr)) + roundup(note->n_namesz, 4) +
137+
roundup(note->n_descsz, 4));
138+
}
139+
}
140+
return NULL;
139141

140142
#elif defined(_WIN32)
141143
void* ptr = NULL;

postject.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,19 +59,21 @@ def get_executable_format(filename):
5959
def inject_into_elf(filename, section_name, data, overwrite=False):
6060
app = lief.ELF.parse(filename)
6161

62-
existing_section = app.get_section(section_name)
62+
existing_section = None
63+
for note in app.notes:
64+
if note.name == section_name:
65+
existing_section = note
6366

6467
if existing_section:
6568
if not overwrite:
6669
return False
6770

68-
app.remove_section(section_name, clear=True)
71+
app.remove(note)
6972

70-
section = lief.ELF.Section()
71-
section.name = section_name
72-
section.content = data
73-
section.add(lief.ELF.SECTION_FLAGS.ALLOC) # Ensure it's loaded into memory
74-
section = app.add(section, loaded=True)
73+
note = lief.ELF.Note()
74+
note.name = section_name
75+
note.description = data
76+
note = app.add(note)
7577

7678
app.write(filename)
7779

0 commit comments

Comments
 (0)