查看APK Java代码
用到了hello-jni.so里的Native函数sign1。
package com.example.hellojni;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.example.hellojni_sign2.R;
import org.apache.commons.lang3.RandomStringUtils;
public class HelloJni extends AppCompatActivity {
TextView tv;
public native String sign1(String str);
public native String stringFromJNI();
/* access modifiers changed from: protected */
@Override // android.support.v7.app.AppCompatActivity, android.support.v4.app.SupportActivity, android.support.v4.app.FragmentActivity
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_hello_jni);
this.tv = (TextView) findViewById(R.id.hello_textview);
this.tv.setText(stringFromJNI());
((Button) findViewById(R.id.button_sign1)).setOnClickListener(new View.OnClickListener() {
/* class com.example.hellojni.HelloJni.AnonymousClass1 */
public void onClick(View view) {
HelloJni.this.tv.setText(HelloJni.this.sign1(RandomStringUtils.randomAscii(16)));
}
});
}
static {
System.loadLibrary("hello-jni");
}
}
IDA Exports 查找JNI_Onload
:
双击:
加载
Shift+F11打开Type libraries窗口,并加载Android Arm
:
根据源码可知:
JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
base::android::InitVM(vm);
if (!base::android::OnJNIOnLoadInit())
return -1;
mojo::core::Init();
return JNI_VERSION_1_4;
}
JNI_OnLoad第一个参数类型是JavaVM*
,因此在IDA里修改参数类型,IDA会自动把GetEnv等函数自动更改为对应的函数名称:
在GetEnv上,右键,Force call type
:
result = (*a1)->GetEnv(a1, (void **)&v5, 65540);
查看定义:
可知,参数v5是JNIEnv*
类型,然后修改v5类型:
可以看到FindClass
,RegisterNatives
等函数名,如果没出来以上函数名,可以多试几下。
Force call type
:
RegisterNatives函数定义:
v6应该是methods数组,双击v6 = off_33D60
中的off_33D60
,
按键D三次,变化为:DCB->DCW->DCD->DCQ,从Byte->Word->DoubleWord(32位)->Quadword(64位):
查看aYcmd的交叉引用(x):
一般.datadiv_decode开始的,紧接着随机数的,OLLVM混淆的字符串的解密函数。查看一下调用位置(随便找一个.datadiv_decode,查一下交叉引用,找到上面的EXPORT,再查看一下交叉引用):
在.init_array
节里面,比JNI_Onload
加载的还早。
Hook查看数据
查看一下加密字符串ycmd;
(虚拟地址是0x3070
)的解密字符串:
function print_hex(addr) {
var base_hello_jni = Module.findBaseAddress("libhello-jni.so");
console.log(hexdump(base_hello_jni.add(addr)));
}
// print_hex(0x37070)
看到解密的字符串:
7f3a0be070 73 69 67 6e 31 00 00 00 00 00 00 00 00 00 00 00 sign1...........
7f3a0be080 28 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 (Ljava/lang/Stri
7f3a0be090 6e 67 3b 29 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 ng;)Ljava/lang/S
7f3a0be0a0 74 72 69 6e 67 3b 00 00 62 61 73 69 63 5f 73 74 tring;..basic_st
7f3a0be0b0 72 69 6e 67 00 00 00 00 00 00 00 00 00 00 00 00 ring............
Hook Env函数
先获取到sign1函数虚拟地址(根据函数物理地址找到模块基地址,然后算出函数虚拟地址),本例是0xe76c
,跳转到函数(G):
修改第一个参数类型为JNIEnv*
,修改第一个参数名为:env
:
Hook Env的这几个函数:GetStringUTFChars
、NewStringUTF
、RegisterNatives
。
function hook_libart() {
var module_libart = Process.findModuleByName("libart.so");
console.log(module_libart);
var addr_RegisterNatives = null;
var addr_GetStringUTFChars = null;
var addr_NewStringUTF = null;
//枚举模块的符号
var symbols = module_libart.enumerateSymbols();
for (var i = 0; i < symbols.length; i++) {
var name = symbols[i].name;
if (name.indexOf("CheckJNI") == -1 && name.indexOf("JNI") > 0) {
if (name.indexOf("RegisterNatives") > 0) {
console.log(name);
addr_RegisterNatives = symbols[i].address;
} else if (name.indexOf("GetStringUTFChars") > 0) {
console.log(name);
addr_GetStringUTFChars = symbols[i].address;
} else if (name.indexOf("NewStringUTF") > 0) {
console.log(name);
addr_NewStringUTF = symbols[i].address;
}
}
}
if (addr_RegisterNatives) {
Interceptor.attach(addr_RegisterNatives, {
onEnter: function (args) {
var java_class = Java.vm.tryGetEnv().getClassName(args[1]);
var methods = args[2];
var method_count = parseInt(args[3]);
console.log("addr_RegisterNatives java_class:", java_class, "method_count:", method_count);
for (var i = 0; i < method_count; i++) {
console.log(methods.add(i * Process.pointerSize * 3).readPointer().readCString());
console.log(methods.add(i * Process.pointerSize * 3 + Process.pointerSize).readPointer().readCString());
var fnPtr = methods.add(i * Process.pointerSize * 3 + Process.pointerSize * 2).readPointer();
var module_so = Process.findModuleByAddress(fnPtr);
// 找到函数虚拟地址
console.log(module_so.name + "!" + fnPtr.sub(module_so.base));
}
}, onLeave: function (retval) {
}
})
}
if (addr_GetStringUTFChars) {
Interceptor.attach(addr_GetStringUTFChars, {
onLeave : function(retval) {
console.log("[GetStringUTFChars] : ", ptr(retval).readCString());
}
})
}
if (addr_NewStringUTF) {
Interceptor.attach(addr_NewStringUTF, {
onEnter : function(args) {
console.log("[NewStringUTF] : ", ptr(args[1]).readCString());
}
})
}
}
通过不断的交叉引用分析,找到了类似md5的函数:
改名(N):(*v4)->NewStringUTF(v4, (const char *)&result_buffer);
print_hex(0x37040)
是02x
:
继续往上回溯,sub_103F0
:
其中参数a3是返回结果,改名(N):
继续查看result_buffer的交叉引用(x):
查看sub_FD90
,并改名:
查看_result_buffer交叉引用:
改名v3为__result_buffer
:
查看交叉引用,在sub_FD90发现:
查看v6的处理函数sub_F008
,发现md的常量:0xD76AA478
:
查看sub_FD90的交叉引用,发现lrand48
:
function hook_java() {
Java.perform(function() {
var HelloJni = Java.use("com.example.hellojni.HelloJni");
HelloJni.sign1.implementation = function(args) {
// 传入固定的参数
return this.sign1("0123456789abcdef");
}
});
}
function hook_native() {
var base_hello_jni = Module.findBaseAddress("libhello-jni.so");
Interceptor.attach(base_hello_jni.add(0xFD90), {
// 查看虚拟地址为0xFD90的函数
onEnter : function(args) {
this.arg0 = args[0]; //把参数保存到局部变量里面
this.arg1 = args[1];
}, onLeave : function(retval) {
console.log("0xFD90:\r\n", hexdump(this.arg0), "\r\n", hexdump(this.arg1));
}
});
Interceptor.attach(base_hello_jni.add(0xF008), {
// Hook md5函数(虚拟地址0xF008),查看参数
onEnter : function(args) {
this.arg0 = args[0]; //把参数保存到局部变量里面
this.arg1 = args[1];
}, onLeave : function(retval) {
console.log("0xF008:\r\n", hexdump(this.arg0), "\r\n", hexdump(this.arg1));
// +++++++++salt2+0123456789abcdef
console.log("0xF008:", ptr(this.arg1).readCString());
}
});
}
function hook_libc() {
Interceptor.attach(Module.findExportByName("libc.so", "lrand48"), {
// Hook lrand48
onLeave : function(retval) {
// 替换生成的随机数,固定随机数
retval.replace(0xAAAAAAAA);
console.log("lrand48:", retval);
}
})
}