Friday, February 8, 2013

Android hacking: replacing system classes methods with your own

If you read my previous post you already know it is really easy to intercept libc functions used by Dalvik. However we can go a bit further and hack DVM from inside out. This time we will be replacing Android framework system classes in runtime, without rooting the device or recompilation of system class libs.

Sounds impossible? Yet, it can be done.
We will dive deeply into Dalvik internals, a bit in DEX bytecode and plain old hacking.

The challenge

It is pretty much impossible to replace a loaded system class on Android (unless you've got root).
  • When your app is started, most classes are already loaded/linked thanks to Zygote
  • Because of that, you can not override system class path
  • You may not load your custom classes with system class loader
  • In other words, you can not replace system java class from inside of java

DVM internals


Fortunately, many DVM methods and global variables are exported libdvm.so. In fact several libs like androidruntime.so and libbinder.so relay on that.


So even though we may not just link with -ldvm (at least it didn't work for me, so I gave up) it is still possible to dlopen() and dlsym() these functions/globals and hack our way into Dalvik.

On Dalvik all Java class/object mapping to native C structs is happening in vm/oo/* files.
Object instances are mirrored with ClassObject structs, and methods with Methods.
So each ClassObject comes with 2d vtable array, which simply contains pointers to Methods.

Now then, from JNI code we can issue following calls to get the ClassObjects we want:
We can easily invoke these to get class we need. Note however the class descriptor is different from traditional class name and dalvik.system.class would look like  "Ldalvik/system/class;"

So, what we could do is to dvmFindSystemClass() the target class and dvmFindClass() and swap Method's class bytecode pointers, right? Wrong.

Dalvik bytecode


On Android, compiled system classes bytecode sit in .dex file. They are "optimized". Normally it is a good thing, but makes injecting code harder. Why?
Because unlike traditional Java's invokevirtual, Android's invoke-virtual opcode takes method reference index as an argument. Lets disassemble some .dex and see how methods are invoked:


So this invoke-virtual opcode address println method by @001f index, rather than string. This index is specific to .dex file and is populated during its creation. So if we just copy new bytecode over original bytecode, DVM will end up calling wrong methods. Unless we want to manually patch bytecode or hack into Dalvik's method index tables, we better leave this approach. Another approach would be to write bytecode payload by hand, but it it is a tedious and the code will be extremely version-specific and probably break on different device/version.

What we could also do is to swap the  Method structure pointer in vtable[]. Since every Method contains reference to parent object pointer, the interpreter will be able to invoke referenced methods properly. However, access to local variables will not work, because DVM does not use any kind of indexing in such case. If we need to access some local variables, we will have to use a helper singleton class. On native side, replacing Method pointers in vtable[] is not enough, we would also need to update methodIndex. If we need to call original method, we will need to prefix it and rename it, and then we will be able to use Reflection API to call it.

Putting it all together

To summarize all we need to do is:

  • In your class, add a method with exactly the same signature as your target method
  • Write the method as you normally would, but
  • Avoid accessing class members
  • Local variables are fine
  • Use helper singleton if you need to speak to outside world
  • Use Reflection API to call original method, but add "_orig_" to your method name
Lets override ClipboardManager.setText() method so that we whatever something is send to clipboard, we add a custom string.


So the actual code to do swapping is trivially simple:

Now, once you do the following from your .so:
every time something is pasted in clipboard, the "pwned:" will be added to the clip :)

Congratulations! you've successfully overridden system class on Android.
compilable source

Thursday, January 31, 2013

Android hacking: hooking system functions used by Dalvik

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 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 
So, how do we do it? By patching PLT (.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-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

Friday, September 28, 2012

How to call for free with Google Voice, Android phone and Asterisk

Phone service is pretty expensive in US compared to many other countries. So instead of paying lets use Asterisk+GVoice+CSipSimple. Since most of my time I spend at the office or at home, I am rarely outside of WiFi coverage. This way I get away with paying $2-3 per month for prepaid when I need to make/receive calls when you are outside of Wifi.

You will need the following things:
  1. Google Voice account
  2. Android phone (CyanogenMod is recommended)
  3. Some Linux server, for Asterisk1.8. (Recommended: VPS. You can get OpenVZ hosting for as low as $3/mnth)
  4. Some WiFi coverage
  5. Some prepaid SIM card, for emergency situations when you are out of WiFi coverage. (I use T-Mobile prepaid)

How it works

Google Voice allows free incoming and outgoing calls in US and Canada. They do not provide any SIP service though. However, Asterisk provides SIP server and it can use XMPP channels to make/receive calls.
When phone losts WiFi coverage, Asterisk detects it and routes incoming calls to a regular cell phone number you provide. This solution does not use any data over 3G.

Installation & configuration

Google Voice account

  1. Set up Google Voice as normal.
  2. On your Settings page, make sure the only forwarded phone is your google account.
  3. On your "Calls" page, make sure:
    "call screening" is diabled
    "call id(incoming)" says "Display callers' number"
    "call id(outgoing)" says "Display my google voice number"
  4. Modify the rest to your liking

Asterisk Server

First lets install asterisk and download reference configuration files
# apt-get install asterisk
# cd /etc/asterisk
# wget -nd -p embeddedwits.com/asterisk/extensions.conf
# wget -nd -p embeddedwits.com/asterisk/sip.conf
# wget -nd -p embeddedwits.com/asterisk/gtalk.conf
# wget -nd -p embeddedwits.com/asterisk/jabber.conf
# wget -nd -p embeddedwits.com/asterisk/codecs.conf
 Then:

  1.  edit gtalk.conf and put your username
  2.  edit jabber.conf and replace your username and secret fields. 
  3.  edit extensions.conf and put your backup SIM number in line 25
  4.  edit sip.conf and update your secret field.

When you are done, issue:
# service asterisk restart

Android phone

  1. Go to Google Play and install CSipSimple application.
  2. Run the application
  3. Menu->Settings->Activate Expert mode
  4. Easy configuration-> check "always available"
  5. Network-> check "Lock WiFi".
    Check "UDP"
    Uncheck "TCP"
    check "Disable TCP switch".
    Connection Keepalive->Wifi UDP keep alive -> enter 15
    Network->For incoming calls - check all
    Network->For outgoing calls- check all
  6. Media settings:
    Check "echo cancellation"
    Echo mode - select "speex"
    Voice audio detection - uncheck
    Clock rate - select 8khz
    Codec priority list per bandwidth - uncheck
    Codecs - select "speex 8khz"
    Micro amplification - change to 8dB
    Speaker amplification - leave at 1dB
  7. Settings->user interface:
    Check "use partial wake lock"
  8. Now you need to add sip server.
    Go to main menu and click on the key icon.
    Click "Add account"
    Select "Expert",
    type some account name, account id, server ip and, username and password.
    Type * as "Realm"
    Select "UDP" as transport.
  9. Decide how you want the app to integrate with your dialing. This is up to you.
  10. Now CSipSimple should be able to register with your SIP server.

This is not enough. You also probably want to make sure your Android phone keeps on looking for WiFi when the screen is off. So you need to:

  1. Install "Advanced Wifi Lock Free" application 
  2. Run the app, click "enabled" in settings
  3. Untick "enable timeout"
  4. Untick "Auto Wifi disable"
  5. Tick "foreground service"
In addition, you might want to install "Google Voice" application itself, so that you can send/receive text messages with ease.

Testing it out

Now you should be able to make and receive calls.

  • One of the most important things is to make sure CSipSimple stays connected all the time. First, make sure your connection is not dropped. This usually happens due to NAT routers tables expiration.
    On your VPS type:
    # asterisk -r
      android/android   66.201.46.98   D   N       31965    OK (404 ms) 
      1 sip peers [Monitored: 1 online, 0 offline Unmonitored: 0 online, 0 offline]
    
  • You might want to call echo test service to test your call quality. Echo service just echos everything you say back to you.
    Add this line in your extensions.conf in [outbound] context :
    exten => 107, 1, Dial(SIP/echo@iptel.org,,r)
    Then dial "107" from your phone and check quality.

Unresolved issues

When making calls without Wifi coverage, your callerid is not your GV number

Unfortunately if you need to make a call outside of wifi coverage, you will obviously have to pay. Also, the called party will not see your google voice number, but your callid will be of your SIM card. You can somewhat mitigate that issue by hiding your caller id with your cell phone, but that is ugly. Somehow Google Voice Android application manages to make calls on "fake" numbers which google reroutes to the number you wanted and makes it show your google vocie caller id. I do not know how it works and if data connection is needed for that.

Battery usage

Now, battery usage is a problem.
This happens because of the following:
  • WiFi radio never sleeps
  • Android system itself does not sleep
  • When WiFi is lost, WiFi keeps on scanning
  • When WiFi is connected, 3G connection is still kept unnecessary. 
We do that only to be able to send UDP keepalives every 15 sec and answer asterisk's ping every 30 seconds. Perhaps we could in fact go to sleep at least for 10 seconds. This needs further investigation.

In my next post I will discuss  the following issues: 
  • Asterisk 11 and improved google voice support 
  • SILK (skype) codecs.
I hope this post was useful. I would appreciate any comments and some ideas how known problems can be workarounded. Feel free to comment if you run into issues with installation or operation.