Let's say you need to alter behavior of some Android app you do not have source code for.
For example, you want to redirect all socket
How this can be achieved (without having to root the device and recompile system libs) ?
On normal Linux systems one can easily use dynamic linker
I will not be going into describing how PLT works and details on ELF binary format. It is suffice to say that compiled code which imports symbols from other .so-s needs to figure out where these functions are located in memory. Since libraries can be loaded at a different place in memory for different processes, it is impossible to know imported functions addresses at compile time. Detailed description can be found here.
However, Android's Bionic library is different, and code specific to glib/gcc dynamic linker won't work. Fortunately, it is easily possible to access ld's internal structures and fish out relevant data. Consider signature of regular POSIX dlopen call:
Interestingly, this gives us
From linker/linker.cpp:
For example, you want to redirect all socket
connect()
s made to badhost.com
to myhost.com
or perhaps even 127.0.0.1
.How this can be achieved (without having to root the device and recompile system libs) ?
On normal Linux systems one can easily use dynamic linker
LD_PRELOAD
environment variable to let ld change symbol resolution order and thus inject any code you want. However, on Android it is not possible for following reasons:LD_PRELOAD
is not useful because zygote already forked JVM- Once an app is running, JVM is already started
- It not possible to (easily) modify environment variables for JVM invokation
.rel.plt
) (procedure linkage tables) of a running process in-flight!I will not be going into describing how PLT works and details on ELF binary format. It is suffice to say that compiled code which imports symbols from other .so-s needs to figure out where these functions are located in memory. Since libraries can be loaded at a different place in memory for different processes, it is impossible to know imported functions addresses at compile time. Detailed description can be found here.
However, Android's Bionic library is different, and code specific to glib/gcc dynamic linker won't work. Fortunately, it is easily possible to access ld's internal structures and fish out relevant data. Consider signature of regular POSIX dlopen call:
void *dlopen(const char *filename, int flag);
Interestingly, this gives us
void*
abstract 'handle'. In practice, this handle is pointer to a struct soinfo
which contains all the information we ever need to override PLT tables.From linker/linker.cpp:
soinfo* do_dlopen(const char* name, int flags)
Now, all we need to is to simply re-
Here is the complete hooking code:
So, in order to hook connect call you would need to call:
All you now need is to write appropriate
Next thing to worry about is how to get this code to be executed on target app start, but perhaps it is a topic for a separate post.
Stay tuned ;-)
update: some people told me this interception does not work anymore. This works fine for 2.3.x and 4.0.x. For >4.1.x you would want to intercept libjavacore.so. Thanks for Madhavi Rao for figuring this out
dlopen()
shared library we want (in my case it libandroid_runtime.so
) and walk through plt table, patch connect() method to our own.Here is the complete hooking code:
/*
Copyright (C) 2013 Andrey Petrov
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#define ANDROID_ARM_LINKER
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/atomics.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include "linker.h" // get it from bionic
static unsigned elfhash(const char *_name)
{
const unsigned char *name = (const unsigned char *) _name;
unsigned h = 0, g;
while(*name) {
h = (h << 4) + *name++;
g = h & 0xf0000000;
h ^= g;
h ^= g >> 24;
}
return h;
}
static Elf32_Sym *soinfo_elf_lookup(soinfo *si, unsigned hash, const char *name)
{
Elf32_Sym *s;
Elf32_Sym *symtab = si->symtab;
const char *strtab = si->strtab;
unsigned n;
n = hash % si->nbucket;
for(n = si->bucket[hash % si->nbucket]; n != 0; n = si->chain[n]){
s = symtab + n;
if(strcmp(strtab + s->st_name, name)) continue;
return s;
}
return NULL;
}
int hook_call(char *soname, char *symbol, unsigned newval) {
soinfo *si = NULL;
Elf32_Rel *rel = NULL;
Elf32_Sym *s = NULL;
unsigned int sym_offset = 0;
if (!soname || !symbol || !newval)
return 0;
si = (soinfo*) dlopen(soname, 0);
if (!si)
return 0;
s = soinfo_elf_lookup(si, elfhash(symbol), symbol);
if (!s)
return 0;
sym_offset = s - si->symtab;
rel = si->plt_rel;
/* walk through reloc table, find symbol index matching one we've got */
for (int i = 0; i < si->plt_rel_count; i++, rel++) {
unsigned type = ELF32_R_TYPE(rel->r_info);
unsigned sym = ELF32_R_SYM(rel->r_info);
unsigned reloc = (unsigned)(rel->r_offset + si->base);
unsigned oldval = 0;
if (sym_offset == sym) {
switch(type) {
case R_ARM_JUMP_SLOT:
/* we do not have to read original value, but it would be good
idea to make sure it contains what we are looking for */
oldval = *(unsigned*) reloc;
*((unsigned*)reloc) = newval;
return 1;
default:
return 0;
}
}
}
return 0;
}
So, in order to hook connect call you would need to call:
hook_call("libandroid_runtime.so", "connect", &my_connect);
All you now need is to write appropriate
my_connect
function which will inspect then modify input parameters and then delegate call to real connect()
Next thing to worry about is how to get this code to be executed on target app start, but perhaps it is a topic for a separate post.
Stay tuned ;-)
update: some people told me this interception does not work anymore. This works fine for 2.3.x and 4.0.x. For >4.1.x you would want to intercept libjavacore.so. Thanks for Madhavi Rao for figuring this out