编写测试 APP
为方便测试,先写一个简单的 APP。
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/sample_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
MainActivity
代码:
package com.example.mysoapplication;
import android.os.Bundle;
import android.view.Gravity;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
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());
Button btn = findViewById(R.id.button);
btn.setOnClickListener(v -> test());
}
private void show(String message) {
Toast toast = Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG);
toast.setGravity(Gravity.BOTTOM, 0, 0);
toast.show();
}
public void test() {
show(functionA(""));
}
public native String stringFromJNI();
public native String functionA(String str);
}
native-lib.cpp
代码:
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_mysoapplication_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
std::string aaa(std::string str) {
return "AAAAA" + str;
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_mysoapplication_MainActivity_functionA(
JNIEnv *env,
jobject /* this */,
jstring str) {
const char *strChar = env->GetStringUTFChars(str, JNI_FALSE);
std::string hello = aaa(strChar);
return env->NewStringUTF(hello.c_str());
}
导出 APK 并安装到夜神模拟器,测试运行:
用 frida 注入 so 层函数
通过 Module.findExportByName(库名, 导出函数名)
可以获取导出函数的指针,之后可以通过 Interceptor.attach
来完成 Hook。
直接上代码:
Java.performNow(function () {
// 附加到libnative-lib.so,获取导出函数functionA,设置调用和返回时的回调
Interceptor.attach(Module.findExportByName("libnative-lib.so","Java_com_example_mysoapplication_MainActivity_functionA"),{
onEnter: function(args) {
console.log('onEnter');
console.log('javaEnv: ' + args[0]);
console.log('javaObject: ' + args[1]);
console.log('str: ' + args[2]);
},
onLeave: function(retval) {
console.log('onLeave');
console.log('return: ' + retval);
}
});
});
点击按钮,看到命令行中打印了相应的信息:
不难发现,打印出来的都是指针。
函数 functionA
的参数和返回值的类型都是 java.lang.String
对象,可以将其强转后输出。代码如下:
Java.performNow(function () {
var String = Java.use('java.lang.String');
Interceptor.attach(Module.findExportByName("libnative-lib.so","Java_com_example_mysoapplication_MainActivity_functionA"),{
onEnter: function(args) {
console.log('onEnter');
console.log('str: ' + Java.cast(args[2], String));
},
onLeave: function(retval) {
console.log('onLeave');
console.log('return: ' + Java.cast(retval, String));
}
});
});
同样地,如果想要替换函数的返回结果,不能直接返回一个字符串,而必须返回一个字符串指针。替换输出结果的代码如下:
Java.performNow(function () {
Interceptor.attach(Module.findExportByName("libnative-lib.so","Java_com_example_mysoapplication_MainActivity_functionA"),{
onLeave: function(retval) {
var jvm = Java.vm.getEnv();
retval.replace(jvm.newStringUtf("BBBBB"));
}
});
});
要实现替换字符串的目的,直接 Hook aaa
这个函数应该也是可以的。
在 native-lib.cpp
中没有给这个函数添加 extern "C"
,因此其编译后其命名会发生改变。
由于 aaa
的参数类型是 std::string
,其修饰名将非常复杂。通过 JEB 搜索 aaa
直接取得其修饰名 _Z3aaaNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE
。
Hook 代码如下:
Java.performNow(function () {
Interceptor.attach(Module.findExportByName("libnative-lib.so","_Z3aaaNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE"),{
onEnter: function(args) {
console.log('onEnter');
console.log('str pointer: ' + args[0]);
console.log('str: ' + Memory.readCString(args[0]));
},
onLeave: function(retval) {
console.log('onLeave');
console.log('return pointer: ' + retval);
console.log('return: ' + Memory.readCString(retval));
retval.replace(Memory.allocUtf8String('BBBBB'));
}
});
});
运行之后发现没有起到作用,用 IDA 进行静态分析后发现,其实是编译器做了优化,functionA
函数直接返回了 AAAAA
,而没有去调用 aaa
进行字符串拼接。
把函数 aaa
的代码改一下,防止编译器优化:
std::string aaa(std::string str) {
return std::to_string(rand() % 100) + "AAAAA" + str;
}
重新编译安装 apk,再次尝试,这回成功了:
然而,在安卓界面上,显示的仍然是 XXAAAAA
。把 aaa
和 functionA
都 Hook 看一下:
Java.performNow(function () {
Interceptor.attach(Module.findExportByName("libnative-lib.so","_Z3aaaNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE"),{
onEnter: function(args) {
console.log('aaa onEnter');
// 这里好像应该取arg[1]
console.log('aaa str pointer: ' + args[0]);
console.log('aaa str: ' + Memory.readCString(args[0]));
},
onLeave: function(retval) {
console.log('aaa onLeave');
console.log('aaa return pointer: ' + retval);
console.log('aaa return: ' + Memory.readCString(retval));
var bbbbb = Memory.allocUtf8String('BBBBB');
retval.replace(bbbbb);
console.log('aaa return pointer replace: ' + retval);
console.log('aaa return replace: ' + Memory.readCString(retval));
}
});
var String = Java.use('java.lang.String');
Interceptor.attach(Module.findExportByName("libnative-lib.so","Java_com_example_mysoapplication_MainActivity_functionA"),{
onEnter: function(args) {
console.log('functionA onEnter');
console.log('functionA str: ' + Java.cast(args[2], String));
},
onLeave: function(retval) {
console.log('functionA onLeave');
console.log('functionA return: ' + Java.cast(retval, String));
}
});
});
运行结果:
一脸懵逼。
搜索无果,加上不太懂 C/C++,汇编也忘光了,静态分析也找不出原因,暂时只能先不管它了。但愿随着学习的深入,之后能搞明白问题出在哪里。