Repository navigation
Apply relocs for ifuncs #1536
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Apply relocs for ifuncs #1536
Changes from 13 commits
3c20b41
4ad7f56
6b932c1
c1e0bd4
8330418
112ad40
7e28ed9
7297c8d
6af66af
187602d
69b1cb9
63bdc0a
2d07c11
545fb49
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| import logging | ||
| from argparse import ArgumentParser | ||
|
|
||
| from miasm.analysis.binary import Container, ContainerELF | ||
| from miasm.analysis.machine import Machine | ||
| from miasm.core.locationdb import LocationDB | ||
| from miasm.jitter.loader.elf import RelocOptions, get_ifuncs, apply_ifunc | ||
| from miasm.loader.elf import ET_EXEC | ||
|
|
||
|
|
||
| def code_sentinelle(jitter): | ||
| jitter.running = False | ||
| jitter.pc = 0 | ||
| return False | ||
|
|
||
| def prepare(run_ifuncs: bool): | ||
| loc_db = LocationDB() | ||
|
|
||
| myjit = Machine("x86_64").jitter(loc_db, args.jitter) | ||
| myjit.init_stack() | ||
| base_addr = 0x400000 | ||
|
|
||
| reloc_options = RelocOptions(run_ifuncs=True, ifunc_jitter_engine=args.jitter) | ||
|
|
||
| with open(args.filename, 'rb') as f: | ||
| elf: ContainerELF = Container.from_stream(f, addr=base_addr, loc_db=loc_db, vm=myjit.vm, apply_reloc=True, reloc_options=reloc_options) | ||
| assert isinstance(elf, ContainerELF) | ||
| if elf.executable.Ehdr.type == ET_EXEC: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe clearer in the example if regrouped with the original definition of the base address ? (line 21) Maybe it can be passed as an argument and put the if statement in the main ?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm good with the first change but I'm not sure about the second since it would lead to unnecessarily duplicated code
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fair |
||
| # static executables can't be rebased | ||
| base_addr = 0 | ||
|
|
||
| return (base_addr, loc_db, elf, myjit) | ||
|
|
||
| def launch(jitter): | ||
| jitter.push_uint64_t(0x1337beef) | ||
| jitter.add_breakpoint(0x1337beef, code_sentinelle) | ||
|
|
||
| if args.verbose >= 2: | ||
| jitter.set_trace_log(True, True) | ||
| run_at = jitter.lifter.loc_db.get_name_offset("intermediate") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe find a clearer name for the symbol, if it's really not possible to have all the logic in the main
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. technically, it would be possible to have all the logic in main and put a breakpoint right after the call to func with a hardcoded address which might be fine for our example. |
||
| assert run_at is not None | ||
| jitter.run(run_at) | ||
| return jitter.get_c_str(jitter.cpu.RAX) | ||
|
|
||
| if __name__ == "__main__": | ||
| parser = ArgumentParser(description="x86 ELF ifunc relocs (e.g. `python resolve_ifunc.py -j llvm -vv ../samples/ifunc`)") | ||
| parser.add_argument("filename", help="ELF to apply (ifunc) relocs to") | ||
| parser.add_argument("-j", "--jitter", | ||
| help="Jitter engine (default is 'gcc')", | ||
| default="gcc") | ||
| parser.add_argument("--verbose", "-v", action="count", | ||
| help="Verbose mode (-v activates debug logging, -vv adds jitter tracing)", | ||
| default=0) | ||
| args = parser.parse_args() | ||
|
|
||
| # setup logging | ||
| log = logging.getLogger(__name__) | ||
| ch = logging.StreamHandler() | ||
| formatter = logging.Formatter("[%(levelname)-8s]: %(message)s") | ||
| ch.setFormatter(formatter) | ||
| if args.verbose >= 1: | ||
| log.setLevel(logging.DEBUG) | ||
| else: | ||
| log.setLevel(logging.WARNING) | ||
| log.addHandler(ch) | ||
|
|
||
|
|
||
| # 1. Run ifunc with default resolver behavior | ||
|
|
||
| _, _, _, jitter = prepare(run_ifuncs=True) | ||
| res1 = launch(jitter) | ||
| log.info(f"Default ifunc resolving returned {res1}.") | ||
| assert res1 == "zglorg" | ||
|
|
||
|
|
||
| # 2. Run ifunc with alternate resolver behavior | ||
|
|
||
| base_addr, loc_db, elf, jitter = prepare(run_ifuncs=False) | ||
|
|
||
| # The ifunc resolver in ../samples/ifunc uses .bss variable `use_func2` | ||
| # to know whether to redirect to `func1` or `func2` when we execute `func` | ||
| # which we proceed to set to `true` | ||
| # | ||
| # Note that we could've created a separate jitter to run ifuncs on (5th arg | ||
| # of apply_ifunc) if we wanted to keep our bss clean for the regular jitter | ||
| # We also could've prepared a special jitter to pass in RelocOptions to | ||
| # achieve the same goal | ||
| use_func2_addr = loc_db.get_name_offset("use_func2") | ||
|
|
||
| if use_func2_addr is None: | ||
| raise ValueError("symbol use_func2 was not found") | ||
| if not jitter.vm.is_mapped(use_func2_addr + base_addr, 1): | ||
| raise ValueError(f"use_func2 exists but doesn't seem to be loaded inside our vm ({use_func2_addr+base_addr=:x})\n{jitter.vm}") | ||
|
|
||
| jitter.vm.set_mem(use_func2_addr + base_addr, b'\x01') | ||
| log.info(f"Set symbol use_func2 (@0x{use_func2_addr + base_addr:x}) to 1") | ||
|
|
||
| # we can then run our ifunc resolver | ||
| ifunc_resolvers = get_ifuncs(elf.executable, base_addr, with_syms=False) | ||
| for reloc, ifunc_resolver in ifunc_resolvers: | ||
| apply_ifunc(reloc, ifunc_resolver, elf.executable, jitter) | ||
|
|
||
| res2 = launch(jitter) | ||
| log.info(f"Forcing ifunc resolving to func2 returned {res2}.") | ||
| assert res2 == "bloups" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| // Compile with gcc -fno-stack-protector -o ifunc ifunc.c | ||
|
|
||
| #include <stdbool.h> | ||
| #include <stdint.h> | ||
|
|
||
| const char *func_1() { | ||
| return "zglorg"; | ||
| } | ||
| const char *func_2() { | ||
| return "bloups"; | ||
| } | ||
|
|
||
| static bool use_func2; | ||
|
|
||
| char *func() __attribute__((ifunc("resolve_func"))); | ||
|
|
||
| // Function pointer type matching `func` prototype | ||
| typedef const char *(*func_type)(); | ||
|
|
||
| static func_type resolve_func() { | ||
| if (use_func2) { | ||
| return func_2; | ||
| } | ||
| return func_1; | ||
| } | ||
|
|
||
| char *intermediate() { | ||
| return func(); | ||
| } | ||
|
|
||
| int main() { | ||
| intermediate(); | ||
| return 0; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
|
|
||
| from miasm.core.bin_stream import bin_stream_str, bin_stream_elf, bin_stream_pe | ||
| from miasm.jitter.csts import PAGE_READ | ||
| from miasm.loader.elf import ET_EXEC | ||
|
|
||
|
|
||
| log = logging.getLogger("binary") | ||
|
|
@@ -163,12 +164,13 @@ def parse(self, data, vm=None, **kwargs): | |
| class ContainerELF(Container): | ||
| "Container abstraction for ELF" | ||
|
|
||
| def parse(self, data, vm=None, addr=0, apply_reloc=False, **kwargs): | ||
| def parse(self, data, vm=None, addr=0, apply_reloc=False, reloc_options=None, **kwargs): | ||
| """Load an ELF from @data | ||
| @data: bytes containing the ELF bytes | ||
| @vm (optional): VmMngr instance. If set, load the ELF in virtual memory | ||
| @addr (optional): base address the ELF in virtual memory | ||
| @apply_reloc (optional): if set, apply relocation during ELF loading | ||
| @reloc_options (optional): see miasm.jitter.loader.elf.RelocOptions | ||
|
|
||
| @addr and @apply_reloc are only meaningful in the context of a | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also the reloc options |
||
| non-empty @vm | ||
|
|
@@ -189,7 +191,8 @@ def parse(self, data, vm=None, addr=0, apply_reloc=False, **kwargs): | |
| data, | ||
| loc_db=self.loc_db, | ||
| base_addr=addr, | ||
| apply_reloc=apply_reloc | ||
| apply_reloc=apply_reloc, | ||
| reloc_options=reloc_options, | ||
| ) | ||
| else: | ||
| self._executable = elf_init.ELF(data) | ||
|
|
@@ -202,6 +205,9 @@ def parse(self, data, vm=None, addr=0, apply_reloc=False, **kwargs): | |
| # Build the bin_stream instance and set the entry point | ||
| try: | ||
| self._bin_stream = bin_stream_elf(self._executable) | ||
| if self._executable.Ehdr.type == ET_EXEC: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not sooner in the function ? Also the warning can only show up if addr is actually not 0. |
||
| addr = 0 | ||
| log.warning("This elf has Ehdr.type == ET_EXEC meaning it isn't relocatable. Using 0 as base_addr.") | ||
| self._entry_point = self._executable.Ehdr.entry + addr | ||
| except Exception as error: | ||
| raise ContainerParsingException('Cannot read ELF: %s' % error) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
run_ifuncs=True-> I think this is a typo, you might want to use the function argument