2

I'm trying to merge some extra logging code into a statically linked (android arm linux) executable.

(Normal tracing methods don't seem to work, as it's a daemon process that clone()s just before doing anything interesting - telling strace to follow this just crashes it).

Hex editing the existing code to insert jump instructions into the new code is tested and working, the problem is getting the new code merged into the executable file in such a way that it doesn't interfere with the existing segments, and does get loaded into an executable page.

I have been able to condense all of the additional code into a single object file section, but can't figure out how to use objcopy (or even ld) to merge that in such a way that it will be properly loaded - it seems I need to either resize & move the existing load segments, or add an additional one which will be honored.

Adding the code in a shared library might be another option, if there's a way to add the necessary stubs to an already linked and presently static executable (I would then hex-edit in jump instructions to the known locations of the stubs, which the runtime linker would then point at the added code)

Chris Stratton
  • 39,853
  • 6
  • 84
  • 117
  • Not necessarily for everything I want to do, and I'd like a fairly general method. But I might have enough space to encode syscalls mmap()ing an executable page from an external file and hex edit the startup code to jump into that. – Chris Stratton Apr 22 '11 at 18:37
  • 1
    Why on earth don't you just recompile the kernel...? – Turbo J Apr 22 '11 at 22:19
  • @Turbo J It's not kernel code. Instrumenting the kernel to monitor user code is both drastic on a device which can't boot a kernel from an alternate flash location, and difficult when source of the precise kernel has not yet been released (as should have been done before the device went on sale). – Chris Stratton Apr 22 '11 at 22:57
  • Patching in an mmap(2) svc call coded directly in assembly is working, but I have to figure out how to generate a target blob to load with correct internal fixups in its more extensive (compiled-from-C) code. Incidentally Linux syscalls don't follow the arm abi... atypically the 5th and 6th parameters go in r4 and r5 – Chris Stratton Apr 22 '11 at 23:46

1 Answers1

0

This is inelegant, so I'd be interested in better ideas, but here's a summary of what I've been able to make basically work.

This is a two prong approach of a small bootstrap payload inserted into padding of the original elf, which then mmap()'s an arbitrarily larger binary blob in to do the actual work.

Part I: the bootstrap payload

Basically I insert a small amount of code in some padding between the .ARM.exidx section (which loads at the top of the code segment) and the .preinit_array section. This code just opens another binary blob and mmap()s it as read only and executable at a hard coded virtual address I hope is safe.

In order to get my inserted code to load as part of the main executable, I had to modify the size of the load segment in the elf file, in this case it's second phdr struct which starts at 0x54. Both the p_filesz at 0x64 (0x54+0x20) and the p_memsz at 0x68 (0x54+0x24) were changed.

I also changed the e_entry start address in the elf header at offset 0x18 to point to my inserted code. My inserted code does a jump to the old start address when it's done setting things up (actually it first jumps to stage two setup in the larger payload, which then jumps to the original).

Finally I changed the statically linked syscall stubs for the functions I wanted to trap to point to my replacements at the load address of the larger payload I'm mmap()ing in.

Part II: the big payload

This implements whatever modifications are being made - in my case, replacing syscalls with functions that log when certain conditions are met. Since the main executable is statically linked, this would have to be as well - or more simply, it can't use the C library. Instead it uses assembly language to issues syscalls for basic I/O. I realized that without being loaded as an executable I have no persistent local variable storage, so on startup I mmap() an anonymous page to hold local variables - mostly the fd of the file I'm logging to and the device driver fd's operations on which should be logged.

Compiling this part is a bit inelegant. I'm compiling to assembly with the -S switch to gcc, then removing all section keywords. I then pass that back through gcc to assemble and generate an object. I run this through the linker specifying the name of my first function as the entry point (-e) and using a customization of the usual linker script which removes the 0x8000 start offset. But there's still some offset due to headers, in this case 128 bytes. to preserve the fixups I objcopy the contents of the linked elf out into a binary blob, dd myself 128 bytes from /dev/zero, and cat that onto the beginning....

As I was saying... this is inelegant, so I'm open to better ideas

Chris Stratton
  • 39,853
  • 6
  • 84
  • 117