Swap out an ELF executable's main function with another function from its symbol table, without touching its code at all.
Go to file
Triss b89215abd6 make it even more sneaky 2021-11-16 14:54:36 +01:00
.gitignore stuff 2021-01-23 05:20:24 +01:00
LICENSE stuff 2021-01-23 05:20:24 +01:00
Makefile stuff 2021-01-23 05:20:24 +01:00
README.md sdf 2021-01-23 05:54:32 +01:00
hello.c stuff 2021-01-23 05:20:24 +01:00
manip-exe.c stuff 2021-01-23 05:20:24 +01:00
manip-exe.h make it even more sneaky 2021-11-16 14:54:36 +01:00

README.md

relocmain

Swap out an ELF executable's main function with another function from its symbol table, without touching its code at all.

How it works

The typical ELF entrypoint isn't main directly, but rather _start, an assembly stub that first does some runtime initialization before calling main. But instead of directly calling the latter function, it is often passed as a parameter to __libc_start_main (which in turn calls main after doing more initialization stuff).

'Relocations' are instructions for an ELF linker or loader on how to patch a binary when moving it around in memory or when resolving functions. Some examples are "this value here is an absolute address, so when you move me around, please keep it updated" (R_<arch>_RELATIVE), or "I'm using this external function, and I'm accessing it through a PLT, so when you resolve the symbol, please put it in the PLT" (R_<arch>_JUMP_SLOT).

This programs adds another relocation entry that replaces the code that loads the address main (to pass it to __libc_start_main) with the address of a different symbol. Due to how relocations work, it is restricted to replacing it with another symbol that is either imported from another library, or exported by the executable itself.

Currently, only x86_64 is supported (but it's not too hard to add support for other instruction sets, 32-bit ELF support is a bit harder). The code looks for a lea rdi, [rel <pcrel32>] instruction near _start, this is often the instruction that loads main to pass it to __libc_start_main (at least on glibc and musl it is). This then gets overwritten at runtime by ld.so due to a relocation of type R_X86_64_PC32 targetting the pcrel32 operand of that instruction --- all without touching any code of the executable.

The result:

int main(int argc, char** argv[]){
	printf("hello, world\n");
	return 42;
}

extern int main2(int argc, char** argv[]);
int main2(int argc, char** argv[]) {
	printf("be gay do crimes\n");
	return 69;
}
$ ./hello.glibc
hello, world
$ ./manip-exe ./hello.glibc{,.manip} main2
$ objdump -d hello.glibc.manip

0000000000001050 <_start>:
    1050:	31 ed                	xor    %ebp,%ebp
    1052:	49 89 d1             	mov    %rdx,%r9
    1055:	5e                   	pop    %rsi
    1056:	48 89 e2             	mov    %rsp,%rdx
    1059:	48 83 e4 f0          	and    $0xfffffffffffffff0,%rsp
    105d:	50                   	push   %rax
    105e:	54                   	push   %rsp
    105f:	4c 8d 05 6a 01 00 00 	lea    0x16a(%rip),%r8      # 11d0 <__libc_csu_fini>
    1066:	48 8d 0d 03 01 00 00 	lea    0x103(%rip),%rcx     # 1170 <__libc_csu_init>
    106d:	48 8d 3d c1 00 00 00 	lea    0xc1(%rip),%rdi      # 1135 <main>
    1074:	ff 15 66 2f 00 00    	callq  *0x2f66(%rip)        # 3fe0 <__libc_start_main@GLIBC_2.2.5>
$ ./hello.glibc.manip
be gay do crimes
$ objdump -drR hello.glibc.manip

    106d:	48 8d 3d c1 00 00 00 	lea    0xc1(%rip),%rdi        # 1135 <main>
			1070: R_X86_64_PC32	main2@@Base-0x4                   # !!!

Usage

# build only the tool
make manip-exe
# usage: ./manip-exe <input> <output> <symbol>

# also show some example stuff
make all

License

be gay, do crimes, death to america