以下內容全部來源於 @r0ysue
完整項目在 1_native_tutorial.7z
https://www.jianshu.com/p/87ce6f565d37https://www.jianshu.com/p/fde40a8d80d3https://developer.android.google.cn/ndk/index.htmlJNI是做了二進位兼容的,所以C代碼編譯後可以在任意平臺中運行。
1.1 新建一個native項目Android Studio 新建native項目Start a new Android Studio projectFinish(選擇默認的Toolchain Default)安裝NDK(Install NDK '21.0.6113669' and sync project)Make Project(成功打包一個apk,生成在 /root/Codes/native_tutorial/app/build/outputs/apk/debug)構建的app-debug.apk解壓後的lib文件夾下有arm64-v8a / armeabi-v7a / x86 / x86_64 四種架構的so
NDK編譯的工具鏈在/root/Android/Sdk/ndk/21.0.6113669/toolchains
Android Studio自帶platform-tools在 /root/Android/Sdk/platform-tools
1.2 Native的基本開發就是一個基礎的native HelloWorld,相關的一些點都在代碼的備註部分。
/root/Codes/native_tutorial/app/src/main/java/com/example/native_tutorial/MainActivity.java
package com.example.native_tutorial;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
Log.i("native-lib", invokeMethodWithoutExternC());
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public native String invokeMethodWithoutExternC();
}/root/Codes/native_tutorial/app/src/main/cpp/native-lib.cpp
#include <jni.h>
#include <string>
// JNI調用android的log
// https://www.jianshu.com/p/3c1aff6f100f
#include <android/log.h>
#define TAG "JNI_LOG"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
/*
* 1. 靜態註冊 art對於JNI函數的查詢方式決定了native方法的函數名構成
* Java_com_example_native_1tutorial_MainActivity_stringFromJNI
* 2. extern "C"
* 避免C++編繹器按照C++的方式去編繹C函數(效果就是保持函數籤名不變)
* Java_com_example_native_1tutorial_MainActivity_stringFromJNI 這種類型的native函數是必須帶extern "C",不然會導致 incorrect linkage
* 3. 前兩個是默認參數 env + jobject(實例方法) / jclass(靜態方法)
* 4. JNIEnv 和 JavaVM
* JNIEnv只在當前線程有效, 一個虛擬機一個JavaVM
* 5. JNIEXPORT 和 JNICALL
* 需要具體看C代碼中的定義
*/
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_native_1tutorial_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
// C/C++方法收到的是JNI類型,返回值也是JNI類型(例如jstring, jintArray),但是程序內部使用的是C/C++類型,所以中間需要JNI類型與C/C++類型的相互轉換。
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
/*
* C++ 使用 name mangling 實現重載函數籤名之間的區分,可以使用 `c++filt` 還原原始的函數籤名,內存中實際存在的是 name mangling 後的函數籤名
* 不使用extern "C"就會被 name mangling
* # c++filt _Z20methodWithoutExternCv
* methodWithoutExternC()
*/
std::string methodWithoutExternC() {
std::string str = "methodWithoutExternC";
return str;
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_native_1tutorial_MainActivity_invokeMethodWithoutExternC(
JNIEnv *env,
jobject thiz) {
std::string str = methodWithoutExternC();
return env->NewStringUTF(str.c_str());
}
2. JNIEnv和反射完整項目在 2_Reflect.7z
FART源碼解析及編譯鏡像支持到Pixel2(xl)https://www.anquanke.com/post/id/201896https://www.jianshu.com/p/9be58ee20dee2.1 JNIEnv基礎/root/Codes/Reflect/app/src/main/java/com/example/reflect/MainActivity.java
package com.example.reflect;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
Log.i("native-lib", "getStringLengthFromJNI(\"hello\") = " + String.valueOf(getStringLengthFromJNI("hello")));
Log.i("native-lib", "changeString2HelloFromJNI(\"Hello World!\") = " + changeString2HelloFromJNI("Hello World!"));
Log.i("native-lib", "staticStringFromJNI() = " + MainActivity.staticStringFromJNI());
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public native int getStringLengthFromJNI(String j_str);
public native String changeString2HelloFromJNI(String j_str);
public static native String staticStringFromJNI();
}/root/Codes/Reflect/app/src/main/cpp/native-lib.cpp
#include <jni.h>
#include <string>
#include <android/log.h>
#define TAG "JNI_LOG"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_reflect_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C" JNIEXPORT jint JNICALL
Java_com_example_reflect_MainActivity_getStringLengthFromJNI(
JNIEnv *env,
jobject thiz,
jstring j_str) {
int length = env->GetStringUTFLength(j_str);
return length;
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_reflect_MainActivity_changeString2HelloFromJNI(
JNIEnv *env,
jobject thiz,
jstring j_str) {
// C/C++方法收到的是JNI類型,返回值也是JNI類型(例如jstring, jintArray),但是程序內部使用的是C/C++類型,所以中間需要JNI類型與C/C++類型的相互轉換。
const char * c_str = env->GetStringUTFChars(j_str, nullptr);
LOGI("original j_str = %s", c_str);
env->ReleaseStringUTFChars(j_str, c_str);
jstring result = env->NewStringUTF("Hello");
return result;
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_reflect_MainActivity_staticStringFromJNI(
JNIEnv *env,
jclass clazz) {
// 實例方法第二個參數是jobject,static方法第二個參數是jclass
std::string hello = "staticStringFromJNI";
return env->NewStringUTF(hello.c_str());
}
2.1 反射Xposed 和 FRIDA 都是基於反射
https://github.com/chame1eon/jnitracehttps://github.com/lasting-yang/frida_hook_libarthttps://mabin004.github.io/2018/07/31/Mac%E4%B8%8A%E7%BC%96%E8%AF%91Frida/https://bbs.pediy.com/thread-223713.htm對於Java層和Native層通過反射獲取類以及對屬性和方法的基礎調用案例。
/root/Codes/Reflect/app/src/main/java/com/example/reflect/MainActivity.java
package com.example.reflect;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
Log.i("native-lib", "getStringLengthFromJNI(\"hello\") = " + String.valueOf(getStringLengthFromJNI("hello")));
Log.i("native-lib", "changeString2HelloFromJNI(\"Hello World!\") = " + changeString2HelloFromJNI("Hello World!"));
Log.i("native-lib", "staticStringFromJNI() = " + MainActivity.staticStringFromJNI());
// test reflect
testClazz();
testField();
testMethod();
// invoke class Test from JNI
String retFromJNI = invokeTestFromJNI();
Log.i("retFromJNI", retFromJNI);
}
public void testClazz() {
// 反射獲取類的方法一
Class testClazz1 = null;
try {
testClazz1 = MainActivity.class.getClassLoader().loadClass("com.example.reflect.Test");
Log.i("testClazz", "MainActivity.class.getClassLoader().loadClass(\"com.example.reflect.Test\"): " + testClazz1.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 反射獲取類方法二
Class testClazz2 = null;
try {
testClazz2 = Class.forName("com.example.reflect.Test");
Log.i("testClazz", "Class.forName(\"com.example.reflect.Test\"): " + testClazz2.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 反射獲取類方法三
Class testClazz3 = Test.class;
Log.i("testClazz", "Test.class: " + testClazz3.getName());
}
public void testField() {
Class testClazz = Test.class;
try {
// 獲取任一public屬性
Field publicStaticField_field = testClazz.getField("publicStaticField");
Log.i("testField", "testClazz.getField(\"publicStaticField\"): " + publicStaticField_field);
// 獲取任一屬性
Field privateStaticField_field = testClazz.getDeclaredField("privateStaticField");
Log.i("testField", "testClazz.getDeclaredField(\"privateStaticField\"): " + privateStaticField_field);
// 獲取所有public屬性
Field[] fields = testClazz.getFields();
for(Field i: fields){
Log.i("testField", "testClazz.getFields(): " + i);
}
// 獲取所有屬性
Field[] declaredFields = testClazz.getDeclaredFields();
for(Field i: declaredFields){
Log.i("testField", "testClazz.getDeclaredFields(): " + i);
}
try {
// 獲取屬性的值: 靜態傳入null,動態傳入對象
String staticValue = (String) publicStaticField_field.get(null);
Log.i("testField", "(String) publicStaticField_field.get(null): " + staticValue);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
try {
// 獲取private屬性需要設置讀取權限
// xposed原理分析 https://blog.csdn.net/zhangmiaoping23/article/details/52572447
// setAccessible需要在get/set前設置
privateStaticField_field.setAccessible(true);
String staticPrivateValue = (String) privateStaticField_field.get(null);
Log.i("testField", "(String) privateStaticField_field.get(null): " + staticPrivateValue);
// 修改屬性值,靜態傳入null,動態傳入對象
privateStaticField_field.set(null, "MODIFIED privateStaticField");
staticPrivateValue = (String) privateStaticField_field.get(null);
Log.i("testField", "(String) privateStaticField_field.get(null): " + staticPrivateValue);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
public void testMethod() {
Class testClazz = Test.class;
// 獲取public方法
Method publicStaticFunc_method = null;
try {
publicStaticFunc_method = testClazz.getMethod("publicStaticFunc");
Log.i("testMethod", "testClazz.getMethod(\"publicStaticFunc\"): " + publicStaticFunc_method);
// 主動調用方法,靜態傳入null,動態傳入對象
publicStaticFunc_method.invoke(null);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
// 獲取private方法
Method privateStaticFunc_method = null;
try {
privateStaticFunc_method = testClazz.getDeclaredMethod("privateStaticFunc");
Log.i("testMethod", "testClazz.getDeclaredMethod(\"privateStaticFunc\"): " + privateStaticFunc_method);
privateStaticFunc_method.setAccessible(true);
privateStaticFunc_method.invoke(null);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
// 只能列印public方法,並且基類的public方法也可以列印
Method[] methods = testClazz.getMethods();
for(Method i: methods){
Log.i("testMethod", "testClazz.getMethods(): " + i);
}
// 可以列印自己的private方法,沒有基類
Method[] declaredMethods = testClazz.getDeclaredMethods();
for(Method i: declaredMethods){
Log.i("testMethod", "testClazz.getDeclaredMethods(): " + i);
}
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public native int getStringLengthFromJNI(String j_str);
public native String changeString2HelloFromJNI(String j_str);
public static native String staticStringFromJNI();
public static native String invokeTestFromJNI();
}/root/Codes/Reflect/app/src/main/java/com/example/reflect/Test.java
package com.example.reflect;
import android.util.Log;
public class Test {
private String identifier = null;
public static String publicStaticField = "publicStaticField";
public String publicField = "publicField";
private static String privateStaticField = "privateStaticField";
private String privateField = "privateField";
public Test(){
identifier = "Test()";
}
public Test(String arg1){
identifier = "Test(String arg1)";
}
public Test(String arg1, int arg2){
identifier = "Test(String arg1, int arg2)";
}
public static void publicStaticFunc(){
Log.i("Test", "publicStaticFunc");
}
public void publicFunc(){
Log.i("Test", "publicFunc");
}
private static void privateStaticFunc(){
Log.i("Test", "privateStaticFunc");
}
private void privateFunc(){
Log.i("Test", "privateFunc");
}
}/root/Codes/Reflect/app/src/main/cpp/native-lib.cpp
#include <jni.h>
#include <string>
#include <android/log.h>
#define TAG "JNI_LOG"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_reflect_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
extern "C" JNIEXPORT jint JNICALL
Java_com_example_reflect_MainActivity_getStringLengthFromJNI(
JNIEnv *env,
jobject thiz,
jstring j_str) {
int length = env->GetStringUTFLength(j_str);
return length;
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_reflect_MainActivity_changeString2HelloFromJNI(
JNIEnv *env,
jobject thiz,
jstring j_str) {
// C/C++方法收到的是JNI類型,返回值也是JNI類型(例如jstring, jintArray),但是程序內部使用的是C/C++類型,所以中間需要JNI類型與C/C++類型的相互轉換。
const char * c_str = env->GetStringUTFChars(j_str, nullptr);
LOGI("original j_str = %s", c_str);
env->ReleaseStringUTFChars(j_str, c_str);
jstring result = env->NewStringUTF("Hello");
return result;
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_reflect_MainActivity_staticStringFromJNI(
JNIEnv *env,
jclass clazz) {
// 實例方法第二個參數是jobject,static方法第二個參數是jclass
std::string hello = "staticStringFromJNI";
return env->NewStringUTF(hello.c_str());
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_reflect_MainActivity_invokeTestFromJNI(
JNIEnv *env,
jclass clazz) {
// so中獲取class,需要用/分隔
jclass testClass = env->FindClass("com/example/reflect/Test");
// so中獲取field
jfieldID publicStaticField = env->GetStaticFieldID(testClass, "publicStaticField", "Ljava/lang/String;");
jstring publicStaticField_value = (jstring) env->GetStaticObjectField(testClass, publicStaticField);
// 將字符串轉換為C才能列印
const char* value_publicStaticField = env->GetStringUTFChars(publicStaticField_value, nullptr);
LOGI("value_publicStaticField: %s", value_publicStaticField);
std::string str = "invokeTestFromJNI";
return env->NewStringUTF(str.c_str());
}
3. IDA調試so基本思路:開發一個Frida反調試完整項目在 3_AntiFrida.7z + 3_libnative-lib.so.anti_frida_patched + 3_libnative-lib.so.original
https://github.com/b-mueller/frida-detection-demohttps://github.com/qtfreet00/AntiFridahttps://bbs.pediy.com/thread-217482.htmhttps://developer.aliyun.com/article/71120https://bbs.pediy.com/thread-253868.htmhttps://github.com/WalterInSH/risk-management-note/blob/master/README.md對於 Java層API、Native層API 都是可以被 hook 的,如果自定義函數調用 Syscall 就會增加難度,對於syscall也是可以hook的但是需要自己編譯內核列印內核的系統調用(來自高維的對抗 - 逆向TinyTool自製),Unicorn列印反調試(Unicorn 在 Android 的應用),Nethunter可以直接列印syscall
風控的關鍵在於檢測的項目夠多,深層的混淆以及vmp(龍捲風),只是靠自己修改字符串特徵來編譯 Xposed / Frida 試圖過風控是不現實的。
3.1 使用 native 手寫一個簡易的 anti-frida/root/Codes/Reflect/app/src/main/java/com/example/reflect/MainActivity.java
package com.example.antifrida;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
tv.setText("ANTI-FRIDA");
antiFrida();
}
// adb logcat 報錯 read: unexpected EOF!,需要 設置 - 開發者模式 - 調試(日誌記錄器緩衝區大小) - 16M
public native void antiFrida();
}/root/Codes/Reflect/app/src/main/cpp/native-lib.cpp
#include <jni.h>
#include <string>
#include <stdio.h>
#include <jni.h>
#include <string.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <android/log.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <android/log.h>
#define TAG "JNI_LOG"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
// https://github.com/b-mueller/frida-detection-demo
// https://github.com/qtfreet00/AntiFrida
void *detect_frida_loop(void *) {
struct sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
inet_aton("0.0.0.0", &(sa.sin_addr));
int sock;
int i;
int ret;
char res[7];
while (true) {
LOGI("enter detect_frida_loop");
// TCP掃描需要開網絡權限
// /root/Codes/AntiFrida/app/src/main/AndroidManifest.xml 添加 <uses-permission android:name="android.permission.INTERNET" />
// 只檢測開啟在[8000, 9000)埠之間的Frida
for(i = 8000; i < 9000; i++){
sock = socket(AF_INET,SOCK_STREAM, 0);
sa.sin_port = htons(i);
LOGI("detect_frida_loop is scanning socket port %d", i);
if (connect(sock , (struct sockaddr*)&sa , sizeof sa) != -1) {
memset(res, 0 , 7);
send(sock, "\x00", 1, NULL);
send(sock, "AUTH\r\n", 6, NULL);
// give it some time to answer
usleep(500);
if ((ret = recv(sock, res, 6, MSG_DONTWAIT)) != -1) {
if (strcmp(res, "REJECT") == 0) {
LOGI("VITAL: Frida was appeared in port %d", i);
} else {
LOGI("SAFE: Frida was not found");
}
}
}
close(sock);
}
}
}
// 函數的返回值注意和聲明要一致!!!否則代碼自己會莫名其妙崩
extern "C" JNIEXPORT void JNICALL
Java_com_example_antifrida_MainActivity_antiFrida(
JNIEnv *env,
jobject thiz) {
pthread_t t;
pthread_create(&t,NULL, detect_frida_loop, (void*)NULL);
LOGI("Java_com_example_antifrida_MainActivity_antiFrida start");
}/root/Codes/AntiFrida/app/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.antifrida">
<!-- TCP掃描需要開網絡權限 -->
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
3.2 IDA 動靜態調試準備工作: 把IDA/dbgsrv下面的android_server64(android_server 32位手機) push 到 /data/local/tmp,添加執行權限後開啟
root@Nick:~# adb push Downloads/android_server64 /data/local/tmp
sailfish:/data/local/tmp # chmod +x android_server64
sailfish:/data/local/tmp # ./android_server64開啟對應手機架構版本的IDA - Debugger - Attach - Remote ARMLinux/Android debugger - hostname寫入手機ip - OK - 選擇進程 - OK
3.2.1 動態調試IDA 右側 Modules 選 libnative-lib.so(可以Ctrl + F過濾) - 雙擊 - 進入Module: libnative-lib.so 可以看到 detect_frida_loop 和 Java_com_example_antifrida_MainActivity_antiFrida - 下斷點調試
3.2.2 靜態調試直接IDA打開so即可
可以右鍵 代碼 Synchronize with - Hex View / PC
3.2.2.1 so硬編碼修改找到REJECT(右鍵Synchronize with hex view, 到hex view 視圖修改硬編碼,右鍵edit,右鍵apply change) - 保存找到REJECT設置 Synchronize with hex view - IDA左上角工具欄Edit - Patch program - Change byte/words - 修改字符 - OK - Edit - Patch program - Apply patches to input files(額外勾選Create backup) OKpatch後的libnative-lib.so 的 md5 是 6a5615f7f6f717212c02164e82e58b9fpatch前的libnative-lib.so 的 md5 是 e8f3be9db5b6e55afeb19d37761d88b8
3.2.2.2 objection找modules路徑root@Nick:~# objection -N -h 192.168.50.42 -p 8888 -g com.example.antifrida explore
com.example.antifrida on (google: 8.1.0) [net] # memory list modules
顯示:
libnative-lib.so 0x726de9f000 12288 (12.0 KiB) /data/app/com.example.antifrida-SwKcSsHgcvghJstE1gnqZg==/lib/arm64/libnativ...
3.2.2.3 patch soroot@Nick:~/Downloads/# adb push 3_libnative-lib.so.anti_frida_patched /sdcard/Download/
sailfish:/ $ su
sailfish:/ # cd /data/app/com.example.antifrida-SwKcSsHgcvghJstE1gnqZg==/lib/arm64/
sailfish:/data/app/com.example.antifrida-SwKcSsHgcvghJstE1gnqZg==/lib/arm64/ # cp /sdcard/Download/3_libnative-lib.so.anti_frida_patched libnative-lib.so
127|sailfish:/data/app/com.example.antifrida-SwKcSsHgcvghJstE1gnqZg==/lib/arm64/ # chmod 755 3_libnative-lib.so.anti_frida_patched對於加載的so需要做完整性校驗否則硬編碼就可以被修改
4. java native / 符號 / JNI(art) / libc hook完整項目在 4_Reflect.7z + 3_AntiFrida.7z + 4_anti_frida.js + 4_reflect.js
https://www.52pojie.cn/thread-742703-1-1.htmlhttps://www.jianshu.com/p/5092d6d5caa3ELF裡面的GOT段/PLT段找到當前的符號,連結到偏移的地址,然後拿到符號進行跳轉,ELF結構分節和區,裡面就有GOT/PLT區表。
Frida的native hook主要分為符號hook(PLT/GOT hook) 和 inline hook
4.1 IDA導入jni.h複製出jni.h(~/Android/Sdk/ndk/21.0.6113669/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/jni.h,不同的apk只需要導入一次jni.h即可)
導入 jni.h (左上角工具欄 File - Load file - Parse C header file - 打開)讓IDA完整反編譯出_JNIEnv *
導入會報錯, 刪除jni報錯對應的.h, 重新導入
如果編譯的函數參數不是_JNIEnv *,就右鍵修改一下 如果沒有用到函數的參數,那IDA反編譯不會顯示這個參數的
4.2 so研究方式主要就是通過trace來觀察so層的行為
/root/Codes/Reflect/app/src/main/java/com/example/reflect/MainActivity.java
在onCreate添加了一下調用native函數的死循環用於後面的so層hook實驗
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
Log.i("native-lib", "getStringLengthFromJNI(\"hello\") = " + String.valueOf(getStringLengthFromJNI("hello")));
Log.i("native-lib", "changeString2HelloFromJNI(\"Hello World!\") = " + changeString2HelloFromJNI("Hello World!"));
Log.i("native-lib", "staticStringFromJNI() = " + MainActivity.staticStringFromJNI());
// test reflect
testClazz();
testField();
testMethod();
// invoke class Test from JNI
String retFromJNI = invokeTestFromJNI();
Log.i("retFromJNI", retFromJNI);
while (true) {
try {
Thread.sleep(2000);
// Do some stuff
} catch (Exception e) {
e.getLocalizedMessage();
}
Log.i("while in onCreate", changeString2HelloFromJNI("string in Java"));
}
}reflect.js
/*
* frida -H 192.168.50.42:8888 com.example.reflect -l agent/reflect.js
*/
// 檢查 以module_name_pattern開頭 的 module(即so) 是否存在在當前apk
function if_module_existed(module_name_pattern) {
var modules = Process.enumerateModules()
var module = null
var existed = false
for (var i = 0; i < modules.length; i++) {
module = modules[i]
if (module.name.indexOf(module_name_pattern) != -1) {
if (!existed) {
console.log('search module_name_pattern[' + module_name_pattern + '] found:')
console.log()
existed = !existed
}
console.log('module.name = ' + module.name)
console.log('module.base = ' + module.base)
console.log()
}
}
return existed
}
function get_symbol_offset() {
var libnative_lib_so_name = "libnative-lib.so"
var existed = if_module_existed(libnative_lib_so_name)
if (existed) {
console.log(libnative_lib_so_name, 'is existed!')
} else {
console.log(libnative_lib_so_name, 'is not existed!')
}
var libnative_lib_so_address = Module.findBaseAddress(libnative_lib_so_name)
console.log("libnative_lib_so_address =", libnative_lib_so_address)
// 先找到module然後找到想要hook的符號
if (libnative_lib_so_address) {
var symbol_name = null
var symbol_address = null
var symbol_name = 'Java_com_example_reflect_MainActivity_changeString2HelloFromJNI'
symbol_address = Module.findExportByName(libnative_lib_so_name, symbol_name)
console.log(symbol_name + ' address = ' + symbol_address)
console.log(symbol_name + ' offset =', '0x' + (symbol_address - libnative_lib_so_address).toString(16))
}
}
// 需要定義async函數且frida使用v8 --runtime=v8
// async function sleep() {
// await new Promise(r => setTimeout(r, 5000))
// }
// 符號hook
function symbol_hook() {
// spawn 需要延遲執行否則libnative-lib.so還沒有加載, 使用 setTimeout 或者 代碼裡加sleep
// TODO 問: 對於只執行一次的native函數,spawn的時候so還沒有加載怎麼hook?
var libnative_lib_so_name = "libnative-lib.so"
var existed = if_module_existed(libnative_lib_so_name)
if (existed) {
console.log(libnative_lib_so_name, 'is existed!')
} else {
console.log(libnative_lib_so_name, 'is not existed!')
}
var libnative_lib_so_address = Module.findBaseAddress(libnative_lib_so_name)
console.log("libnative_lib_so_address =", libnative_lib_so_address)
// 先找到module然後找到想要hook的符號
if (libnative_lib_so_address) {
var symbol_name = null
var symbol_address = null
var symbol_name = 'Java_com_example_reflect_MainActivity_changeString2HelloFromJNI'
symbol_address = Module.findExportByName(libnative_lib_so_name, symbol_name)
console.log(symbol_name + ' address = ' + symbol_address)
console.log(symbol_name + ' offset =', '0x' + (symbol_address - libnative_lib_so_address).toString(16))
}
Interceptor.attach(symbol_address, {
onEnter: function(args) {
// args 是 jstring
// Java_com_example_reflect_MainActivity_changeString2HelloFromJNI 三個參數
console.log(symbol_address, "raw args[0], args[1], args[2]:", args[0], args[1], args[2])
// 列印方式1
// 星球jbytearray
// 可能需要使用32位機器,sailfish這邊是Error: access violation accessing 0x7133cabb8
// var args2_address = args[2].readPointer()
// console.log(hexdump(args2_address))
// 列印方式2
// 通過JNI接口列印jstring, 注意需要對應到frida的api, 不要直接使用java的
// https://github.com/frida/frida-java-bridge/blob/062999b618451e5ac414d08d4f52bec05bd6adf6/lib/env.js
console.log('args[2]:', Java.vm.getEnv().getStringUtfChars(args[2], null).readCString())
},
onLeave: function(ret) {
// ret 是 jstring
console.log("raw ret:", ret)
console.log('ret:', Java.vm.getEnv().getStringUtfChars(ret, null).readCString())
var new_ret = Java.vm.getEnv().newStringUtf("new_ret from " + symbol_name)
// 替換返回值
ret.replace(ptr(new_ret))
}
})
}
// https://github.com/chame1eon/jnitrace
function hook_libart_so() {
var libart_so_name = "libart.so"
var existed = if_module_existed(libart_so_name)
if (existed) {
console.log(libart_so_name, 'is existed!')
} else {
console.log(libart_so_name, 'is not existed!')
}
var libart_so_address = Module.findBaseAddress(libart_so_name)
console.log("libart_so_address =", libart_so_address)
// 由於 name mangling 導致符號被混淆(name mangling也不是完全混淆,真實函數名還是包含在結果中的), 所以需要枚舉所有符號
// 不要用 Process.findModuleByName(module_name).enumerateSymbols() 符號可能枚舉結果為空
var symbols = Module.enumerateSymbolsSync(libart_so_name)
var jni_function_name = 'GetStringUTFChars'
var jni_function_address = null
for (var i = 0; i < symbols.length; i++){
var symbol = symbols[i]
if((symbol.name.indexOf("CheckJNI") == -1) && (symbol.name.indexOf("JNI") >= 0)) {
if (symbol.name.indexOf(jni_function_name) >= 0) {
console.log('symbol.name =', symbol.name)
console.log('symbol.address =', symbol.address)
jni_function_address = symbol.address
}
}
}
console.log(jni_function_name + ' address =', jni_function_address)
// JNI 的 GetStringUTFChars 竟然可以列印android的Log.i, 陳總建議是可以看native的log實現, 然而現在並看不懂
Interceptor.attach(jni_function_address, {
onEnter: function(args) {
console.log(jni_function_name + " args[0] =", args[0])
console.log(jni_function_name + " args[1] =", args[1])
// args[0] 是 JNIEnv
// JNIEnv 可以用 hexdump(args[0].readPointer()), jxxx參數不能用, 要調用JNI的方法列印
console.log('hexdump args[0] =', hexdump(args[0].readPointer()))
// args[1] 是 jstring, 不能用hexdump
// console.log('hexdump args[1] =', hexdump(args[1].readPointer()))
// 兩個接口 getEnv / tryGetEnv
console.log('args[1] =', Java.vm.getEnv().getStringUtfChars(args[1]).readCString())
// console.log('args[1] =', Java.vm.tryGetEnv().getStringUtfChars(args[1]).readCString())
// 列印調用棧: https://frida.re/docs/javascript-api/
var trace_back_log = Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n')
console.log(jni_function_name + ' traceback:\n' + trace_back_log + '\n')
},
onLeave: function(ret) {
// char * 可以直接列印 ptr(ret).readCString()
console.log(jni_function_name + " ret =", ptr(ret).readCString())
}
})
}
function main() {
// get_symbol_offset()
// symbol_hook()
hook_libart_so()
}
setImmediate(main)anti_frida.js
/*
* frida -H 192.168.50.42:8888 -f com.example.antifrida -l agent/anti_frida.js
*/
// 1. 對native函數的hook,和普通java函數一致
function remove_antiFrida() {
// 避免logcat日誌過多無法看出來hook native函數是否生效,先清理一下logcat日誌
// 清除logcat日誌: adb logcat -c
var MainActivity = Java.use('com.example.antifrida.MainActivity')
MainActivity.antiFrida.implementation = function() {
console.log('enter remove_antiFrida')
return
}
}
// 檢查 以module_name_pattern開頭 的 module(即so) 是否存在在當前apk
function if_module_existed(module_name_pattern) {
var modules = Process.enumerateModules()
var module = null
var existed = false
for (var i = 0; i < modules.length; i++) {
module = modules[i]
if (module.name.indexOf(module_name_pattern) != -1) {
if (!existed) {
console.log('search module_name_pattern[' + module_name_pattern + '] found:')
console.log()
existed = !existed
}
console.log('module.name = ' + module.name)
console.log('module.base = ' + module.base)
console.log()
}
}
return existed
}
function enumerate_modules_and_exports(module_name) {
// 枚舉符號和導出符號
// symbols > exports
// 但enumerateSymbols可能搜索結果為空,並且as打包生成的so會出現有導出符號沒有符號的情況,不知道是做了什麼配置,日常請使用sync版本
// var symbols = Process.findModuleByName(libnative_lib_so_name).enumerateSymbols()
// var symbols = Module.enumerateSymbolsSync(libnative_lib_so_name)
// var symbols = Process.findModuleByName(module_name).enumerateSymbols()
var symbols = Module.enumerateSymbolsSync(module_name)
var exports = Process.findModuleByName(module_name).enumerateExports()
}
// 2. 找到偏移
function get_symbol_offset() {
// root@Nick:~# objection -N -h 192.168.50.42 -p 8888 -g com.example.antifrida explore
// com.example.antifrida on (google: 8.1.0) [net] # memory list modules
// 找到:
// libnative-lib.so 0x72b6e6c000 12288 (12.0 KiB) /data/app/com.example.antifrida-cUCV8773Ge8uJPawmLlRPw==/lib/arm64/libnativ...
var libnative_lib_so_name = "libnative-lib.so"
var existed = if_module_existed('libnative')
if (existed) {
console.log(libnative_lib_so_name, 'is existed!')
} else {
console.log(libnative_lib_so_name, 'is not existed!')
}
// 找到基址, app重啟後基址和導出函數地址是會變化的, 但偏移量是固定的
var libnative_lib_so_address = Module.findBaseAddress(libnative_lib_so_name)
console.log("libnative_lib_so_address =", libnative_lib_so_address)
// 先找到module然後找到想要hook的符號
if (libnative_lib_so_address) {
// 由於namemagling導致符號被混淆, detect_frida_loop 的導出符號是 _Z17detect_frida_loopPv
// extern "C" JNIEXPORT void JNICALL Java_com_example_antifrida_MainActivity_antiFrida
var symbol_name = null
var symbol_address = null
var symbol_name = 'Java_com_example_antifrida_MainActivity_antiFrida'
symbol_address = Module.findExportByName(libnative_lib_so_name, symbol_name)
console.log(symbol_name + ' address = ' + symbol_address)
// 偏移量是固定的, 和Frida的IDA-View按空格顯示的偏移一致
console.log(symbol_name + ' offset =', '0x' + (symbol_address - libnative_lib_so_address).toString(16))
symbol_name = '_Z17detect_frida_loopPv'
symbol_address = Module.findExportByName(libnative_lib_so_name, symbol_name)
console.log(symbol_name + ' address = ' + symbol_address)
console.log(symbol_name + ' offset =', '0x' + (symbol_address - libnative_lib_so_address).toString(16))
}
}
function hook_libc_so() {
var libc_so_name = "libc.so"
var existed = if_module_existed(libc_so_name)
if (existed) {
console.log(libc_so_name, 'is existed!')
} else {
console.log(libc_so_name, 'is not existed!')
}
var libc_so_address = Module.findBaseAddress(libc_so_name)
console.log("libc_so_address =", libc_so_address)
var libc_function_name = 'pthread_create'
var libc_function_address = null
var symbols = null
symbols = Module.enumerateSymbolsSync(libc_so_name)
// C函數名字該是什麼就是什麼
// objection: memory list exports libc.so --json libc_so.json
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i]
var name = symbol.name
var address = symbol.address
// 使用android native中的方法名
if ((name.indexOf(libc_function_name) >= 0) && (name.indexOf(".cpp") == -1)) {
console.log('name:', name)
console.log('address:', address)
libc_function_address = address
}
}
Interceptor.attach(libc_function_address, {
onEnter: function(args) {
console.log(libc_function_name, 'arg[0] =', args[0])
console.log(libc_function_name, 'arg[1] =', args[1])
console.log(libc_function_name, 'arg[2] =', args[2])
console.log(libc_function_name, 'arg[3] =', args[3])
},
onLeave: function(ret) {
console.log('ret =', ret)
}
})
}
function main() {
// Java.perform(remove_antiFrida)
// get_symbol_offset()
hook_libc_so()
}
setImmediate(main)
5. JNI_Onload / 主動調用 / inline_hook完整項目在 5_Reflect.7z + 5_AntiFrida.7z + + 5_anti_frida.js + 5_reflect.js
https://github.com/android/ndk-samples.gitAndroid逆向新手答疑解惑篇——JNI與動態註冊https://bbs.pediy.com/thread-224672.htmhttps://www.jianshu.com/p/b71aeb4ed13d5.1 動態註冊靜態註冊: Java_com_example_demo_MainActivity_stringFromJNI5.1.1 JNI_OnLoad動態註冊/root/Codes/Reflect/app/src/main/java/com/example/antifrida/MainActivity.java 修改onCreate 並添加 native方法 dynamicallyRegisteredStringFromJNI
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
Log.i("native-lib", "getStringLengthFromJNI(\"hello\") = " + String.valueOf(getStringLengthFromJNI("hello")));
Log.i("native-lib", "changeString2HelloFromJNI(\"Hello World!\") = " + changeString2HelloFromJNI("Hello World!"));
Log.i("native-lib", "staticStringFromJNI() = " + MainActivity.staticStringFromJNI());
// test reflect
testClazz();
testField();
testMethod();
// invoke class Test from JNI
String retFromJNI = invokeTestFromJNI();
Log.i("retFromJNI", retFromJNI);
while (true) {
try {
Thread.sleep(2000);
// Do some stuff
} catch (Exception e) {
e.getLocalizedMessage();
}
Log.i("while in onCreate", changeString2HelloFromJNI("string in Java"));
Log.i("RegisterNatives", dynamicallyRegisteredStringFromJNI());
}
}
// IDA中找不到動態註冊的dynamicallyRegisteredStringFromJNI
public native String dynamicallyRegisteredStringFromJNI();/root/Codes/Reflect/app/src/main/cpp/native-lib.cpp 添加下面兩個方法用於主動註冊
// 去除了extern "C", 這樣會被name mangling
JNIEXPORT jstring JNICALL
dynamicallyRegisteredString(
JNIEnv *env,
jobject thiz) {
std::string str = "dynamicallyRegisteredStringFromJNI";
return env->NewStringUTF(str.c_str());
}
// 在jni.h中定義但沒有實現, 類似Activity的onCreate
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv *env;
// 通過JavaVM獲取JNIEnv
vm->GetEnv((void**)&env, JNI_VERSION_1_6);
JNINativeMethod methods[] = {
// Java中native函數名, (參數)返回值, native函數
// 當參數為引用類型的時候,參數類型的標識為"L包名", 其中包名的.(點)要換成"/"
{"dynamicallyRegisteredStringFromJNI", "()Ljava/lang/String;", (void*)dynamicallyRegisteredString},
};
// 第一個參數: native函數所在的類,可通過FindClass獲取(將.換成/)
// 第二個參數: 一個數組,其中包含註冊信息
// 第三個參數: 數量
env->RegisterNatives(env->FindClass("com/example/reflect/MainActivity"), methods, 1);
return JNI_VERSION_1_6;
}
5.1.2 trace libart應該花時間多看大佬們寫的代碼,先抄後理解
RegisterNatives / libart(JNI)https://github.com/lasting-yang/frida_hook_libart注意hook時機,例如hook RegisterNatives就需要用spawn的方式啟動
5.1.3 frida反調試https://bbs.pediy.com/thread-255653.htm5.1.4 主動調用native函數Interceptor.replace
reflect.js
// https://frida.re/docs/javascript-api/
// Interceptor.replace
function native_initiatively_invoke() {
var module_name = 'libnative-lib.so'
var function_name = 'Java_com_example_reflect_MainActivity_changeString2HelloFromJNI'
var Java_com_example_reflect_MainActivity_changeString2HelloFromJNI_address = Module.getExportByName(module_name, function_name)
var Java_com_example_reflect_MainActivity_changeString2HelloFromJNI = new NativeFunction(Java_com_example_reflect_MainActivity_changeString2HelloFromJNI_address, 'pointer', ['pointer', 'pointer', 'pointer'])
// Interceptor.attach只能hook和修改返回值, Interceptor.replace可以主動調用
Interceptor.replace(Java_com_example_reflect_MainActivity_changeString2HelloFromJNI_address, new NativeCallback(function(env, thiz, j_str) {
var str = Java.vm.getEnv().getStringUtfChars(j_str).readCString()
console.log('j_str =', str)
// 主動調用
// var ret = Java_com_example_reflect_MainActivity_changeString2HelloFromJNI(env, thiz, j_str)
// 替換參數的主動調用
// 創建一個 jstring: Java.vm.getEnv().newStringUtf(""), 創建一個 char*: Memory.allocUtf8String("")
var new_j_str = Java.vm.getEnv().newStringUtf("HelloHello")
var ret = Java_com_example_reflect_MainActivity_changeString2HelloFromJNI(env, thiz, new_j_str)
// 但這裡也沒有做到完全的主動調用, replace是類似於Java.choose, native new參數解決就是真正的主動調用了
return ret
}, 'pointer', ['pointer', 'pointer', 'pointer']))
}anti_frida.js
function anti_anti_frida() {
var libnative_lib_so_name = "libnative-lib.so"
var existed = if_module_existed(libnative_lib_so_name)
if (existed) {
console.log(libnative_lib_so_name, 'is existed!')
} else {
console.log(libnative_lib_so_name, 'is not existed!')
}
var libnative_lib_so_address = Module.findBaseAddress(libnative_lib_so_name)
console.log("libnative_lib_so_address =", libnative_lib_so_address)
var detect_frida_loop_name = 'detect_frida_loop'
var detect_frida_loop_address = null
var symbols = null
// 坑坑坑!!!
// 找自己用as寫的native函數要enumerateExportsSync, 不能enumerateSymbolsSync
symbols = Module.enumerateExportsSync(libnative_lib_so_name)
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i]
var name = symbol.name
var address = symbol.address
if (name.indexOf(detect_frida_loop_name) >= 0) {
console.log('name:', name)
console.log('address:', address)
detect_frida_loop_address = address
}
}
// detect_frida_loop地址如下, 可以利用後三位地址偏移特徵進行過濾
// 0x72b6f34a7c
// 0x72b6e97a7c
var libc_so_name = "libc.so"
var existed = if_module_existed(libc_so_name)
if (existed) {
console.log(libc_so_name, 'is existed!')
} else {
console.log(libc_so_name, 'is not existed!')
}
var libc_so_address = Module.findBaseAddress(libc_so_name)
console.log("libc_so_address =", libc_so_address)
var libc_function_name = 'pthread_create'
var libc_function_address = null
var symbols = null
symbols = Module.enumerateSymbolsSync(libc_so_name)
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i]
var name = symbol.name
var address = symbol.address
// 使用android native中的方法名
if ((name.indexOf(libc_function_name) >= 0) && (name.indexOf(".cpp") == -1)) {
console.log('name:', name)
console.log('address:', address)
libc_function_address = address
}
}
// c的大函數的signature都可以直接搜到
var pthread_create = new NativeFunction(libc_function_address, 'int', ['pointer', 'pointer', 'pointer', 'pointer'])
Interceptor.replace(libc_function_address, new NativeCallback(function(arg0, arg1, arg2, arg3) {
var ret = null
if (String(arg2).endsWith('a7c')) {
// 這裡不能傳NULL,會導致報錯的(NULL就是空指針但為什麼會導致報錯呢),是因為NULL的address是0的原因嗎? 可以傳一個枚舉出來的合理的函數地址, 如何傳一個NULL pointer呢?
// ret = pthread_create(arg0, arg1, NULL, arg3)
ret = 0
console.log(detect_frida_loop_name, 'is passed')
} else {
ret = pthread_create(arg0, arg1, arg2, arg3)
console.log('normal pthread_create')
}
return ret
}, 'int', ['pointer', 'pointer', 'pointer', 'pointer']))
}
5.1.5 inline hookIDA(IDA View)判斷是ARM指令還是thumb指令Options - General - Number of opcodes byte 修改為8(或4)如果地址顯示是2個數字(可能需要按一下空格)就是thumb,地址需要+1,4個數字就不需要+1function inline_hook() {
var libnative_lib_so_name = "libnative-lib.so"
var existed = if_module_existed(libnative_lib_so_name)
if (existed) {
console.log(libnative_lib_so_name, 'is existed!')
} else {
console.log(libnative_lib_so_name, 'is not existed!')
}
var libnative_lib_so_address = Module.findBaseAddress(libnative_lib_so_name)
console.log("libnative_lib_so_address =", libnative_lib_so_address)
// 先找到module然後找到想要hook的符號
if (libnative_lib_so_address) {
var offset = 0xFC28
var FC28_address = libnative_lib_so_address.add(offset)
Interceptor.attach(FC28_address, {
onEnter: function(args) {
// hook地址沒有參數,一般只能列印寄存器
// 因為是64位所以是x
// IDA找到dynamicallyRegisteredString,找偏移末尾是C28的前面一點下斷點,可以發現斷點到C28時候寄存器的值和inline_hook是一致的
console.log('this.context.PC =', this.context.PC)
console.log('this.context.x1 =', this.context.x1)
console.log('this.context.x5 =', this.context.x5)
console.log('this.context.x10 =', this.context.x10)
},
onLeave: function(ret) {
console.log('ret =', ret)
}
})
}
}
6. Frida hook native App實戰完整項目在 6_xman.js
如果so中的函數符號被strip掉的話IDA是搜不到的
6.1 反調試https://gtoad.github.io/2017/06/25/Android-Anti-Debug/https://github.com/gnaixx/anti-debug6.2 xman用yang神的hook_RegisterNatives看一下找到偏移對應的native函數偏移,IDA直接跳轉到對應函數(IDA可以嘗試修改函數第一個參數類型為JNIEnv *)看native函數來猜一下整個流程,然後來trace驗證一下hook strcmp過驗證 / 兩種寫入文件過驗證function hook_MyApp() {
var MyApp = Java.use('com.gdufs.xman.MyApp')
MyApp.m.value = 1
var Toast = Java.use('android.widget.Toast')
Toast.makeText.overload('android.content.Context', 'java.lang.CharSequence', 'int').implementation = function(arg0, arg1, arg2) {
console.log('arg2:', arg1)
var ret = this.makeText(arg0, arg1, arg2)
return ret
}
}
function if_module_existed(module_name_pattern) {
var modules = Process.enumerateModules()
var module = null
var existed = false
for (var i = 0; i < modules.length; i++) {
module = modules[i]
if (module.name.indexOf(module_name_pattern) != -1) {
if (!existed) {
console.log('search module_name_pattern[' + module_name_pattern + '] found:')
console.log()
existed = !existed
}
console.log('module.name = ' + module.name)
console.log('module.base = ' + module.base)
console.log()
}
}
return existed
}
function hook_libc_so() {
var libc_so_name = "libc.so"
var existed = if_module_existed(libc_so_name)
if (existed) {
console.log(libc_so_name, 'is existed!')
} else {
console.log(libc_so_name, 'is not existed!')
}
var libc_so_address = Module.findBaseAddress(libc_so_name)
console.log("libc_so_address =", libc_so_address)
var libc_function_name = 'strcmp'
var libc_function_address = null
var symbols = null
symbols = Module.enumerateSymbolsSync(libc_so_name)
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i]
var name = symbol.name
var address = symbol.address
if ((name.indexOf(libc_function_name) >= 0) && (name.indexOf(".cpp") == -1)) {
console.log('name:', name)
console.log('address:', address)
libc_function_address = address
}
}
var libc_function = new NativeFunction(libc_function_address, 'int', ['pointer', 'pointer'])
Interceptor.replace(libc_function_address, new NativeCallback(function(s1, s2) {
var char_star_s1 = ptr(s1).readCString()
var char_star_s2 = ptr(s2).readCString()
if (char_star_s2 == 'EoPAoY62@ElRD') {
console.log('char_star_s1 =', char_star_s1)
console.log('char_star_s2 =', char_star_s2)
return 0
}
var ret = libc_function(s1, s2)
return ret
}, 'int', ['pointer', 'pointer']))
}
function frida_write_reg(){
var file = new File("/sdcard/reg.dat","w+")
file.write("EoPAoY62@ElRD")
file.flush()
file.close()
console.log('frida_write_reg')
}
// 主動調用so函數
function hook_libc_so_write_reg(){
var fopen_address = Module.findExportByName("libc.so", "fopen")
var fputs_address = Module.findExportByName("libc.so", "fputs")
var fclose_address = Module.findExportByName("libc.so", "fclose")
console.log('fopen_address =', fopen_address)
console.log('fputs_address =', fputs_address)
console.log('fclose_address =', fclose_address)
var fopen = new NativeFunction(fopen_address, "pointer", ["pointer", "pointer"])
var fputs = new NativeFunction(fputs_address, "int", ["pointer", "pointer"])
var fclose = new NativeFunction(fclose_address, "int", ["pointer"])
// 創建C的字符串,不能直接傳入js的字符串
var filename = Memory.allocUtf8String("/sdcard/reg.dat")
var mode = Memory.allocUtf8String("w+")
var file = fopen(filename, mode)
var contents = Memory.allocUtf8String("EoPAoY62@ElRD")
var fputs_ret = fputs(contents, file)
fclose(file)
console.log('fputs_ret =', fputs_ret)
console.log('hook_libc_so_write_reg')
}
function main() {
// Java.perform(hook_MyApp)
// hook_libc_so()
// frida_write_reg()
hook_libc_so_write_reg()
}
setImmediate(main)
7. trace工具鏈7.1 trace三件套https://github.com/chame1eon/jnitracehttps://frida.re/docs/frida-trace/https://github.com/lasting-yang/frida_hook_libart8. init_array開發和逆向自動化完整項目在 8_Reflect.7z + hook_linker.js
https://www.cnblogs.com/bingghost/p/6297325.htmlAndroid逆向新手答疑解惑篇——JNI與動態註冊https://bbs.pediy.com/thread-224672.htmlinker加載so(init_array) - hook linkerhttp://note.youdao.com/noteshare?id=38aa9a5ce7d03a9fd61a21d076eab219Android NDK中.init段和.init_array段函數的定義方式https://www.dllhook.com/post/213.html8.1 init & init_array/root/Codes/Reflect/app/src/main/cpp/native-lib.cpp添加
// 先init後init_array
// 編譯生成後在.init段 [名字不可更改]
extern "C" void _init(void) {
LOGI(".init");
}
// 編譯生成後在.init_array段 [名字可以更改]
__attribute__((__constructor__)) static void dot_init_array() {
LOGI(".init_array");
}.init可以直接在IDA Function window / Exports 搜到
shift + F7 / IDA View中Ctrl + s: 雙擊函數可以找到init_array中定義的函數內容
32位so通過call_function加載init_array中的方法的流程#2228-2236http://androidxref.com/6.0.1_r10/xref/bionic/linker/linker.cpp#2228https://www.cnblogs.com/bingghost/p/6297325.html/root/Codes/Reflect/app/build.gradle: android - defaultConfig - externalNativeBuild添加
// 編譯32位庫
ndk {
// Specifies the ABI configurations of your native
// libraries Gradle should build and package with your APK.
abiFilters "armeabi-v7a"
}
32位(資料多)動態調試init_array(我這邊沒有debug成功)linker 中找 call_function(其中的參數就是.init和.init_array)_dl__ZL13call_functionPKcPFvvES0 中的 CODE XREF: _dl__ZL13call_functionPKcPFvvES0+18↑j 後的 BLX R6(對應文章中的BLX R4)8.2 jnitrace 自吐 JNI_Onload & init_arrayJNI_Onload:
jnitrace -m spawn -l libnative-lib.so com.example.reflect
獲得如下列印:
417 ms [+] JNIEnv->RegisterNatives
417 ms |- JNIEnv* : 0xf39312a0
417 ms |- jclass : 0x85 { com/example/reflect/MainActivity }
417 ms |- JNINativeMethod* : 0xffae15c8
417 ms |: 0xd8d62495 - dynamicallyRegisteredStringFromJNI()Ljava/lang/String;
417 ms |- jint : 1
417 ms |= jint : 0
417 ms Backtrace
417 ms |-> 0xd8d625e1: _ZN7_JNIEnv15RegisterNativesEP7_jclassPK15JNINativeMethodi+0x2c (libnative-lib.so:0xd8d59000)
dynamicallyRegisteredStringFromJNI對應的so中函數的偏移即: 0xd8d62495 - 0xd8d59000 = 0x9495
init_array(感謝hanbing老師的腳本):
沒有支持arm64,可以在安裝app的時候 adb install --abi armeabi-v7a 強制讓app運行在32位模式
function LogPrint(log) {
var theDate = new Date();
var hour = theDate.getHours();
var minute = theDate.getMinutes();
var second = theDate.getSeconds();
var mSecond = theDate.getMilliseconds()
hour < 10 ? hour = "0" + hour : hour;
minute < 10 ? minute = "0" + minute : minute;
second < 10 ? second = "0" + second : second;
mSecond < 10 ? mSecond = "00" + mSecond : mSecond < 100 ? mSecond = "0" + mSecond : mSecond;
var time = hour + ":" + minute + ":" + second + ":" + mSecond;
var threadid = Process.getCurrentThreadId();
console.log("[" + time + "]" + "->threadid:" + threadid + "--" + log);
}
function hooklinker() {
var linkername = "linker";
var call_function_addr = null;
var arch = Process.arch;
LogPrint("Process run in:" + arch);
if (arch.endsWith("arm")) {
linkername = "linker";
} else {
linkername = "linker64";
LogPrint("arm64 is not supported yet!");
}
var symbols = Module.enumerateSymbolsSync(linkername);
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
//LogPrint(linkername + "->" + symbol.name + "---" + symbol.address);
if (symbol.name.indexOf("__dl__ZL13call_functionPKcPFviPPcS2_ES0_") != -1) {
call_function_addr = symbol.address;
LogPrint("linker->" + symbol.name + "---" + symbol.address)
}
}
if (call_function_addr != null) {
var func_call_function = new NativeFunction(call_function_addr, 'void', ['pointer', 'pointer', 'pointer']);
Interceptor.replace(new NativeFunction(call_function_addr,
'void', ['pointer', 'pointer', 'pointer']), new NativeCallback(function (arg0, arg1, arg2) {
var functiontype = null;
var functionaddr = null;
var sopath = null;
if (arg0 != null) {
functiontype = Memory.readCString(arg0);
}
if (arg1 != null) {
functionaddr = arg1;
}
if (arg2 != null) {
sopath = Memory.readCString(arg2);
}
var modulebaseaddr = Module.findBaseAddress(sopath);
LogPrint("after load:" + sopath + "--start call_function,type:" + functiontype + "--addr:" + functionaddr + "---baseaddr:" + modulebaseaddr);
if (sopath.indexOf('libnative-lib.so') >= 0 && functiontype == "DT_INIT") {
LogPrint("after load:" + sopath + "--ignore call_function,type:" + functiontype + "--addr:" + functionaddr + "---baseaddr:" + modulebaseaddr);
} else {
func_call_function(arg0, arg1, arg2);
LogPrint("after load:" + sopath + "--end call_function,type:" + functiontype + "--addr:" + functionaddr + "---baseaddr:" + modulebaseaddr);
}
}, 'void', ['pointer', 'pointer', 'pointer']));
}
}
setImmediate(hooklinker)
8.3 frida-trace 對 任意函數的 trace# -f 啟動有時候so沒加載會hook不到
frida-trace -H 192.168.50.42:8888 -I libnative-lib.so com.example.reflect
# 基於單個函數地址的hook
frida-trace -H 192.168.50.42:8888 -a libnative-lib.so\!0x9495 com.example.reflect
# 幾種trace工具的文檔和源碼去了解一下
9. C hook & CModule完整項目在 9_libnative-lib.so + 9_invoke_so.js + 9_cmodule.js + 9_explore.js + 9_explore.c
9.1 參數列印和構造jstring是沒法直接列印的,java vm中只能轉化為char*後列印,和native編程的流程一致jni的基本類型,要通過調用jni相關api轉化為c++對象才能列印和調用(參數構造可以java.vm.xx/直接獲取hook的參數進行傳遞),因為Interceptor(相當於在libart.so中執行)沒有在java.perform裡,所以不能使用java的方法9.2 Frida調用C++編寫的soinvoke_so.js
/*
* frida -H 192.168.50.42:8888 -f com.android.settings -l agent/invoke_so.js --no-pause
*/
function invoke_so() {
/**
* https://github.com/lasting-yang/frida_hook_libart
* 導入AntiFrida的libnative-lib.so在settings中執行
* root@Nick:~# adb push Downloads/libnative-lib.so /data/local/tmp
* root@Nick:~# adb shell su -c "cp /data/local/tmp/libnative-lib.so /data/app/libnative-lib.so"
* root@Nick:~# adb shell su -c "chown 1000.1000 /data/app/libnative-lib.so"
* root@Nick:~# adb shell su -c "chmod 777 /data/app/libnative-lib.so"
* root@Nick:~# adb shell su -c "ls -al /data/app/libnative-lib.so"
*/
var module_libnative_lib_so = Module.load("/data/app/libnative-lib.so")
var export_function_name = "_Z17detect_frida_loopPv"
var export_function_address = module_libnative_lib_so.findExportByName(export_function_name)
console.log('export_function_address =', export_function_address)
var detect_frida_loop = new NativeFunction(export_function_address, 'pointer', ['pointer'])
var void_star = Memory.allocUtf8String("hello")
detect_frida_loop(void_star)
// adb logcat 可以看到開始Frida埠檢測
}
function main() {
invoke_so()
}
setImmediate(main)
9.3 CModulehttps://frida.re/news/2019/09/18/frida-12-7-released/不依賴架構並且不用編譯為so直接可以將C代碼注入
root@Nick:~# frida -H 192.168.50.42:8888 com.android.settings
[Remote::com.android.settings]-> var cm = new CModule('int add(int a, int b) { return a + b; }')
[Remote::com.android.settings]-> cm
{
"add": "0x779d1d5000"
}
[Remote::com.android.settings]-> var add = new NativeFunction(cm.add, 'int', ['int', 'int'])
[Remote::com.android.settings]-> add(1, 2)
3cmodule.js
/**
* 1.
* frida -H 192.168.50.42:8888 com.android.settings -l agent/cmodule.js --runtime=v8
*
* 2.
* root@Nick:~/Downloads# ./frida-server-12.8.0-linux-x86_64
* root@Nick:~# mousepad cmodule.md
*/
function open_1() {
// 無法保存文件內容了!
const m = new CModule(`
#include <gum/guminterceptor.h>
#define EPERM 1
int
open (const char * path,
int oflag,
...)
{
GumInvocationContext * ic;
ic = gum_interceptor_get_current_invocation ();
ic->system_error = EPERM;
return -1;
}
`)
const openImpl = Module.getExportByName(null, 'open')
console.log('openImpl = ', openImpl)
Interceptor.replace(openImpl, m.open)
}
function open_2() {
const openImpl = Module.getExportByName(null, 'open')
Interceptor.attach(openImpl, new CModule(`
#include <gum/guminterceptor.h>
#include <stdio.h>
void
onEnter (GumInvocationContext * ic)
{
const char * path;
path = gum_invocation_context_get_nth_argument (ic, 0);
printf ("open() path=\\"%s\\"\\n", path);
}
void
onLeave (GumInvocationContext * ic)
{
int fd;
fd = (int) gum_invocation_context_get_return_value (ic);
printf ("=> fd=%d\\n", fd);
}
`))
}
function open_3() {
const openImpl = Module.getExportByName(null, 'open')
Interceptor.attach(openImpl, new CModule(`
#include <gum/guminterceptor.h>
extern void onMessage (const gchar * message);
static void log (const gchar * format, ...);
void
onEnter (GumInvocationContext * ic)
{
const char * path;
path = gum_invocation_context_get_nth_argument (ic, 0);
log ("open() path=\\"%s\\"", path);
}
void
onLeave (GumInvocationContext * ic)
{
int fd;
fd = (int) gum_invocation_context_get_return_value (ic);
log ("=> fd=%d", fd);
}
static void
log (const gchar * format,
...)
{
gchar * message;
va_list args;
va_start (args, format);
message = g_strdup_vprintf (format, args);
va_end (args);
onMessage (message);
g_free (message);
}
`,
{
onMessage: new NativeCallback(messagePtr => {
const message = messagePtr.readUtf8String()
console.log('onMessage:', message)
}, 'void', ['pointer'])
}
)
)
}
function open_4() {
const calls = Memory.alloc(4)
const openImpl = Module.getExportByName(null, 'open')
Interceptor.attach(
openImpl,
new CModule(`
#include <gum/guminterceptor.h>
extern volatile gint calls;
void
onEnter (GumInvocationContext * ic)
{
g_atomic_int_add (&calls, 1);
}
`,
{ calls }
)
)
setInterval(() => {
console.log('Calls so far:', calls.readInt())
}, 1000)
}
function main() {
// open_1()
// open_2()
// open_3()
open_4()
}
setImmediate(main)
9.4 Frida C + JS 混合編程https://frida.re/docs/javascript-api/root@Nick:~/Codes/github/others/frida-agent-example# frida -p 0 -l agent/explore.js -C agent/explore.c
explore.js
console.log('Hello from Javascript')
var counter = Memory.alloc(4)
var bump = null
cs.counter = counter
rpc.exports.init = function () {
bump = new NativeFunction(cm.bump, 'void', ['int'])
}explore.c
extern int counter;
void init(void) {
frida_log("Hello from C");
}
void bump(int n) {
counter += n;
}
關於逆向學習的幾個觀點學會hook大法之後trace是幾乎不需要手動調的QA:有時候-f so還沒有加載hook不到,可以延遲hook嗎?(但不能attach,有時候attach已經錯過調用時機了)