Frida Android Native Library Hooking

Frida ile java fonksiyonları hook edilebildiği gibi JNI Hooking de yapılabilmekte.

Bu yazımda JNI hook için örnek bir uygulama oluşturup bu uygulamada native fonksiyonu hook edeceğiz.

  1. Uygulamayı Hazırlama
  2. Hooking

Android Uygulamasını Hazırlama

Öncelikle native library kullanan bir örnek android projesi yaratacağız

Öncelikle LLDB,CMake ve NDK nın android studio da yüklü olduğuna emin oluyoruz.

 Sonrasında yeni bir Native C++ projesi oluşturuyoruz. C++ Standardını varsayılan olarak bırakabilirsiniz.

Mainactivity içerisini bu şekilde dolduruyoruz.

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI("SELAM "));

    }

    public native String stringFromJNI(String byPercent);
}

native-lib.cpp içerisini de bu şekilde doldurduyoruz.

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_noplay_myapplication_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */
        ,jstring jstr) {

    const char* str = env->GetStringUTFChars(jstr,0);

    char result[100] = "";   // array to hold the result.

    strcat(result,str); // append string two to the result.
    strcat(result," BERKAY"); // append string two to the result.

    return env->NewStringUTF(result);
}

Build.gradle (Module : app) dosyasında;
android {…} içerisine tüm mimarilere uyumlu apk oluşturması için gereken değişikliği yapıyoruz.

splits {
        abi {
            enable true
            reset()
            include 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' //select ABIs to build APKs for
            universalApk true //generate an additional APK that contains all the ABIs
        }
    }

Android uygulamamız hazır uygulama native fonksiyona SELAM stringi yolluyor c kodunda bu yazıya BERKAY yazısı eklenip return ediliyor.


JNI Hooking

Apk içerisindeki .so uzantılı nativelibrary dosyasının export fonksiyonlarını aşağıdaki kodla buluyoruz.

root@kali: nm -D libnative-lib.so | grep Java

 

https://github.com/Areizen/JNI-Frida-Hook Projesini indirip dependencyleri yüklüyoruz.

frida-compile yüklemek için npm install frida-compile -g kullanabiliriz.(NPM Module Global Install) (Bknz)

agent.js dosyasını düzenliyoruz:

library_name= .so uzantılı kütüphane dosyamız ex:libnative-lib.so

function_name= Java_com.paketismi.sec şeklindeki library export fonksiyonu

Tek bir export fonksiyonu hook etmek için; jni.hook_all(jnienv_addr) satırını siliyoruz

Projede belirtildiği gibi compile edip;

frida-compile agent.js -o _agent.js

çalıştırıyoruz (Frida serverin çalıştığına emin olun);

frida -U -l _agent.js --no-pause -f <your package_name>

Sonra agent.js scriptini amacımıza uygun olarak değiştiriyoruz.

 

Şimdi Frida ile JNI Hooking ile ilgili birkaç örnek script inceleyelim:

Frida Javacript api den aldığımız kod onEnter ve OnLeave değiştirerek fonksiyonu hook edebiliriz.

onEnter: function (args) {    
    console.log('Context information:');
    console.log('Context  : ' + JSON.stringify(this.context));
    console.log('Return   : ' + this.returnAddress);
    console.log('ThreadId : ' + this.threadId);
    console.log('Depth    : ' + this.depth);
    console.log('Errornr  : ' + this.err);
    // Save arguments for processing in onLeave.
    this.fd = args[0].toInt32();
    this.buf = args[1];
    this.count = args[2].toInt32();
  },
  onLeave: function (result) {
    console.log('----------')
    // Show argument 1 (buf), saved during onEnter.
    var numBytes = result.toInt32();
    if (numBytes > 0) {
      console.log(hexdump(this.buf, { length: numBytes, ansi: true }));
    }
    console.log('Result   : ' + numBytes);
  }
})

Fonksiyonun döndürdüğü int değeri böyle alabiliriz eğer bu değerleri değiştirmek istersek;

onLeave: function (result) {
    console.log('----------')
	result.replace(0)
  }
Interceptor.attach(funcAddr, {
    onEnter: function (args) {
        return 0;
    },
    onLeave: function (retval) {
        const dstAddr = Java.vm.getEnv().newStringUtf("1234");
        retval.replace(dstAddr);
    },
});

 

Şimdi de kendi uygulamamız için hook scripti hazırlayalım:

Scriptimiz native fonksiyona giren ve çıkan bilgiyi bize gösterecektir.

 onEnter: function (args) {
 	console.log('-----onEnter-----')

            var String = Java.use("java.lang.String");
            var promt = Java.cast(ptr(args[2]), String);
            console.log(promt)
  },
  onLeave: function (result) {
    console.log('-----onLeave-----')

            var String = Java.use("java.lang.String");
            var promt = Java.cast(ptr(result), String);
            console.log(promt)
  }
})
        }
    })

 

Şimdi onleave in al altına ekleme yaparak return değerini değiştiriyoruz

onLeave: function (result) {  
      ......
      ......    
      const dstAddr = Java.vm.getEnv().newStringUtf("SELAM YILDIZ");
      result.replace(dstAddr);
}

Öncesi ve sonrası: