@oro-oro
2015-08-19T10:39:22.000000Z
字数 13916
阅读 5943
IDAForAndroid
通过实例来了解本地函数注册原理,以及如何分析加密后的本地函数对应关系。
在hello-jni项目,Java_com_example_hellojni_HelloJni_stringFromJNI
这种函数名太麻烦了,所以,有必要重新注册一下本地函数。
stringFromJNI注册为fun1,fun注册为fun2。
#include <string.h>
#include <jni.h>
#include <android/log.h>
#define TAG "NATIVE DEBUG LOG"
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO , TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN , TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR , TAG, __VA_ARGS__)
jstring stringFromJNI( JNIEnv* env, jobject thiz )
{
#if defined(__arm__)
#if defined(__ARM_ARCH_7A__)
#if defined(__ARM_NEON__)
#if defined(__ARM_PCS_VFP)
#define ABI "armeabi-v7a/NEON (hard-float)"
#else
#define ABI "armeabi-v7a/NEON"
#endif
#else
#if defined(__ARM_PCS_VFP)
#define ABI "armeabi-v7a (hard-float)"
#else
#define ABI "armeabi-v7a"
#endif
#endif
#else
#define ABI "armeabi"
#endif
#elif defined(__i386__)
#define ABI "x86"
#elif defined(__x86_64__)
#define ABI "x86_64"
#elif defined(__mips64) /* mips64el-* toolchain defines __mips__ too */
#define ABI "mips64"
#elif defined(__mips__)
#define ABI "mips"
#elif defined(__aarch64__)
#define ABI "arm64-v8a"
#else
#define ABI "unknown"
#endif
return (*env)->NewStringUTF(env, "Hello from JNI ! Compiled with ABI " ABI ".");
}
void fun(JNIEnv* env,jobject thiz){
LOGI("Hello JNI!");
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* pVm, void* reserved)
{
JNIEnv* env;
if ((*pVm)->GetEnv(pVm, (void **)&env, JNI_VERSION_1_6)) {
return -1;
}
JNINativeMethod nm[2];
nm[0].name = "fun1";
nm[0].signature = "()Ljava/lang/String;";
nm[0].fnPtr = stringFromJNI;
nm[1].name = "fun2";
nm[1].signature = "()V";
nm[1].fnPtr = fun;
jclass cls = (*env)->FindClass(env, "com/example/hellojni/HelloJni");
// Register methods with env->RegisterNatives.
(*env)->RegisterNatives(env, cls, nm, 2);
return JNI_VERSION_1_6;
}
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
include $(BUILD_SHARED_LIBRARY)
APP_ABI := armeabi
编译:
$ ndk-build
[armeabi] Gdbserver : [arm-linux-androideabi-4.8] libs/armeabi/gdbserver
[armeabi] Gdbsetup : libs/armeabi/gdb.setup
[armeabi] Compile thumb : hello-jni <= hello-jni.c
[armeabi] SharedLibrary : libhello-jni.so
[armeabi] Install : libhello-jni.so => libs/armeabi/libhello-jni.so
显示完JNI函数,直接看F5伪代码。函数的对应关系还是比较清晰的。
signed int __fastcall JNI_OnLoad(int a1)
{
signed int v1; // r3@2
int v3; // [sp+8h] [bp-2Ch]@1
int v4; // [sp+Ch] [bp-28h]@3
int v5; // [sp+10h] [bp-24h]@3
int v6; // [sp+14h] [bp-20h]@3
int (__fastcall *v7)(int); // [sp+18h] [bp-1Ch]@3
int v8; // [sp+1Ch] [bp-18h]@3
char v9[4]; // [sp+20h] [bp-14h]@3
int (*v10)(); // [sp+24h] [bp-10h]@3
if ( (*(int (__cdecl **)(int, int *))(*(_DWORD *)a1 + offsetof(JNINativeInterface, FindClass)))(a1, &v3) )
{
v1 = -1;
}
else
{
v5 = (int)"fun1";
v6 = (int)"()Ljava/lang/String;";
v7 = stringFromJNI;
v8 = (int)"fun2";
*(_DWORD *)v9 = "()V";
v10 = fun;
v4 = (*(int (__fastcall **)(int, _DWORD))(*(_DWORD *)v3 + offsetof(JNINativeInterface, FindClass)))(
v3,
"com/example/hellojni/HelloJni");
(*(void (__fastcall **)(int, int, int *, signed int))(*(_DWORD *)v3 + offsetof(JNINativeInterface, RegisterNatives)))(
v3,
v4,
&v5,
2);
v1 = 65542;
}
return v1;
}
int __fastcall stringFromJNI(int a1)
{
return (*(int (__cdecl **)(int, _DWORD))(*(_DWORD *)a1 + offsetof(JNINativeInterface, NewStringUTF)))(
a1,
"Hello from JNI ! Compiled with ABI armeabi.");
}
int fun()
{
return _android_log_print(4, "NATIVE DEBUG LOG", "Hello JNI!");
}
《Android 软件安全与逆向分析》9.6.2的例子。
http://yunpan.cn/cdRWxKaf9nJuL 访问密码 0939
除了常见的B指令外,LDR PC, #Address
也是一种常见的函数调用指令。
.text:00001374 LDR R3, [R3]
.text:00001378 MOV LR, PC
.text:0000137C LDR PC, [R3,#JNINativeInterface.FindClass] ; 函数调用
.text:00001380 LDR R2, =(native_class_ptr - 0x1394)
.text:00001384 LDR R3, [R5]
.text:00001388 MOV R1, R0
.text:0000138C LDR R2, [PC,R2] ; native_class
.text:00001390 STR R0, [R2]
.text:00001394 LDR R2, =(__data_start - 0x13A8)
.text:00001398 MOV R0, R3
.text:0000139C LDR R12, [R3]
.text:000013A0 ADD R2, PC, R2 ; __data_start
.text:000013A4 MOV R3, #3
.text:000013A8 MOV LR, PC
.text:000013AC LDR PC, [R12,#JNINativeInterface.RegisterNatives] ; 函数调用
.text:000013B0 CMP R0, #0
.text:000013B4 BNE loc_13D8
.text:000013B8 LDR R2, =(aRegisternative - 0x13CC)
JNI_OnLoad
的伪代码:
signed int __fastcall JNI_OnLoad(int a1)
{
signed int result; // r0@2
if ( (*(int (**)(void))(*(_DWORD *)a1 + 24))() )
{
result = -1;
}
else
{
_android_log_print(2, "com.droider.ndkapp", "JNI_OnLoad()");
native_class = (*(int (__fastcall **)(_DWORD *, _DWORD))(*g_env + offsetof(JNINativeInterface, FindClass)))(
g_env,
"com/droider/ndkapp/MyApp");
if ( (*(int (__fastcall **)(_DWORD *, _DWORD, _DWORD, signed int))(*g_env
+ offsetof(JNINativeInterface, RegisterNatives)))(
g_env,
native_class,
_data_start,
3) )
{
_android_log_print(6, "com.droider.ndkapp", "RegisterNatives() --> nativeMethod() failed");
result = -1;
}
else
{
_android_log_print(2, "com.droider.ndkapp", "RegisterNatives() --> nativeMethod() ok");
result = 65542;
}
}
return result;
}
RegisterNatives函数注册的是一个函数数组,_data_start
就是保存函数的数组。
单击进去,看看_data_start
,便可以找到函数的对应关系了。
.data:00004EA4 ; Segment type: Pure data
.data:00004EA4 AREA .data, DATA
.data:00004EA4 ; ORG 0x4EA4
.data:00004EA4 EXPORT __data_start
.data:00004EA4 __data_start DCD aInitsn ; DATA XREF: JNI_OnLoad+8Co
.data:00004EA4 ; .text:off_1408o
.data:00004EA4 ; "initSN"
.data:00004EA8 DCD aV ; "()V"
.data:00004EAC DCD n1
.data:00004EB0 DCD aSavesn ; "saveSN"
.data:00004EB4 DCD aLjavaLangStrin ; "(Ljava/lang/String;)V"
.data:00004EB8 DCD n2
.data:00004EBC DCD aWork ; "work"
.data:00004EC0 DCD aV ; "()V"
.data:00004EC4 DCD n3
有时候这个数组可能是加密过的,这就需要去解密这个数组了。
例子:http://yunpan.cn/cdMsjyYeKbdRj 访问密码 cabb
这里主要是分析,该样本的函数对应关系。
jniExport 类下面有这些native函数:
public native void nq10(String arg1, String arg2, int arg3) {
}
public native void nq11(String arg1, int arg2) {
}
public native void nq12(Context arg1, ClassLoader arg2, int arg3) {
}
public native void nq13(String arg1, int arg2) {
}
public native void nq14(String arg1, int arg2) {
}
用IDA打开nqshield.so,却没有发现对应的函数,直接看JNI_OnLoad
函数。
signed int __fastcall JNI_OnLoad(int a1)
{
int v1; // r4@1
int v2; // r3@1
int v3; // r4@2
int v4; // r0@2
int v5; // r1@2
signed int result; // r0@4
int v7; // [sp+4h] [bp-Ch]@1
v1 = a1;
sub_2438();
v2 = *(_DWORD *)v1;
v7 = 0;
if ( (*(int (__fastcall **)(int, int *, signed int))(v2 + offsetof(JNINativeInterface, FindClass)))(v1, &v7, 65540)
|| (v3 = v7,
v4 = dcf13197d7a5901a5ad213f34001fe4(79),
(v5 = (*(int (__fastcall **)(int, int))(*(_DWORD *)v3 + offsetof(JNINativeInterface, FindClass)))(v3, v4)) == 0) )
{
result = -1;
}
else if ( (*(int (__fastcall **)(int, int, int *, signed int))(*(_DWORD *)v3
+ offsetof(JNINativeInterface, RegisterNatives)))(
v3,
v5,
&dword_D004, // 这里就是注册的函数素组。
5) < 0 )
{
result = -1;
}
else
{
result = 65540;
}
return result;
}
点击dword_D004
,跳到对应的数组地址:
.data:0000D004 00 00 00 00 dword_D004 DCD 0 ; DATA XREF: JNI_OnLoad+74o
.data:0000D004 ; .text:off_64BCo ...
.data:0000D008 00 00 00 00 dword_D008 DCD 0 ; DATA XREF: sub_64C0+20w
.data:0000D00C 4C 22 00 00 DCD b9df1ce797fd03603fd7137afff2957
.data:0000D010 00 00 00 00 dword_D010 DCD 0 ; DATA XREF: sub_64C0+2Cw
.data:0000D014 00 00 00 00 dword_D014 DCD 0 ; DATA XREF: sub_64C0+38w
.data:0000D018 78 23 00 00 DCD eb5993d402df42eac47e82555b7ae31
.data:0000D01C 00 00 00 00 dword_D01C DCD 0 ; DATA XREF: sub_64C0+44w
.data:0000D020 00 00 00 00 dword_D020 DCD 0 ; DATA XREF: sub_64C0+50w
.data:0000D024 6C 61 00 00 DCD fa3f3a501e8754e88bed48c128ef90
.data:0000D028 00 00 00 00 dword_D028 DCD 0 ; DATA XREF: sub_64C0+5Cw
.data:0000D02C 00 00 00 00 dword_D02C DCD 0 ; DATA XREF: sub_64C0+68w
.data:0000D030 48 5D 00 00 DCD abc020d3ee6fbc05ab1ed6b4da7252d
.data:0000D034 00 00 00 00 dword_D034 DCD 0 ; DATA XREF: sub_64C0+74w
.data:0000D038 00 00 00 00 dword_D038 DCD 0 ; DATA XREF: sub_64C0+80w
.data:0000D03C 10 58 00 00 DCD e300588cc034bcbaa7d61
以上有5组函数关系:
函数名 | 参数 | 本地方法 |
---|---|---|
dword_D004 | dword_D008 | b9df1ce797fd03603fd7137afff2957 |
dword_D010 | dword_D014 | eb5993d402df42eac47e82555b7ae31 |
dword_D01C | dword_D020 | fa3f3a501e8754e88bed48c128ef90 |
dword_D028 | dword_D02C | abc020d3ee6fbc05ab1ed6b4da7252d |
dword_D034 | dword_D038 | e300588cc034bcbaa7d61 |
dword_****
,这些都是字符串来的,这些东西在哪里初始化的呢?
因为这个在JNI_OnLoad
函数就要使用了,那么这些字符串,肯定在该函数之前初始化的。
譬如,.init_array
。
按快捷键Shift+F7
或者View->Open Subview->Segments
,可以打开段视图。
点击进去:
.init_array:0000CD24 ; Segment type: Pure data
.init_array:0000CD24 AREA .init_array, DATA
.init_array:0000CD24 ; ORG 0xCD24
.init_array:0000CD24 C0 64 00 00 DCD sub_64C0
.init_array:0000CD28 29 8C 00 00 DCD sub_8C28+1
.init_array:0000CD2C 00 00 00 00 ALIGN 0x10
.init_array:0000CD2C ; .init_array ends
不过,本人直接按快捷键x
,调出dword_D004
的交叉引用窗口,看它是在哪里初始化的。
选中w
,数据写入的地址,进去,可以找到初始化的代码:
int sub_64C0()
{
int result; // r0@1
dword_D004 = dcf13197d7a5901a5ad213f34001fe4(110);
dword_D008 = dcf13197d7a5901a5ad213f34001fe4(115);
dword_D010 = dcf13197d7a5901a5ad213f34001fe4(111);
dword_D014 = dcf13197d7a5901a5ad213f34001fe4(116);
dword_D01C = dcf13197d7a5901a5ad213f34001fe4(112);
dword_D020 = dcf13197d7a5901a5ad213f34001fe4(117);
dword_D028 = dcf13197d7a5901a5ad213f34001fe4(113);
dword_D02C = dcf13197d7a5901a5ad213f34001fe4(118);
dword_D034 = dcf13197d7a5901a5ad213f34001fe4(114);
result = dcf13197d7a5901a5ad213f34001fe4(119);
dword_D038 = result;
return result;
}
解密函数找到了,剩下就是解密问题了。
接下来直接调用dcf13197d7a5901a5ad213f34001fe4
方法进行解密即可。
点击函数,去看看它的原型:
int __fastcall dcf13197d7a5901a5ad213f34001fe4(int result)
快捷键Tab
,切反汇编视图,查看该函数名:
.text:00002A18
.text:00002A18 ; dcf13197d7a5901a5ad213f34001fe4(myString)
.text:00002A18 EXPORT _Z31dcf13197d7a5901a5ad213f34001fe48myString
.text:00002A18 _Z31dcf13197d7a5901a5ad213f34001fe48myString
.text:00002A18 ; CODE XREF: e78be2435917208c8fa0c2af796f6c38(_JNIEnv *,_jobject *,_jobject *,_jobject *)+1Cp
.text:00002A18 ; e78be2435917208c8fa0c2af796f6c38(_JNIEnv *,_jobject *,_jobject *,_jobject *)+40p ...
.text:00002A18 CMP R0, #0x9C ; switch 157 cases
.text:00002A1C ADDLS PC, PC, R0,LSL#2 ; switch jump
.text:00002A20 ; ---------------------------------------------------------------------------
.text:00002A20
.text:00002A20 loc_2A20 ; CODE XREF: dcf13197d7a5901a5ad213f34001fe4(myString)+4j
.text:00002A20 B locret_363C ; jumptable 00002A1C default case
.text:00002A24 ; ---------------------------------------------------------------------------
.text:00002A24
_Z31dcf13197d7a5901a5ad213f34001fe48myString
就是这个解密函数的名字。
接下来就可以写代码,调用so解密了:
hello.c
/* hello.c: Hello World program */
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
int main(int argc, char* argv[]){
int (*decode)(int);
void *handle;
handle = dlopen("/data/local/tmp/libnqshield.so", RTLD_LOCAL | RTLD_LAZY);
if ( !handle) {
printf("%s,%d, NULL == handle\n", __FUNCTION__, __LINE__);
return -1;
}
decode = (int (*)(int)) dlsym(handle, "_Z31dcf13197d7a5901a5ad213f34001fe48myString");
const char *errstr;
errstr = (char *) dlerror();
if (errstr != NULL)
printf ("A dynamic linking error occurred: (%s)\n", errstr);
printf("%s\n", decode(110));
printf("%s\n", decode(115));
printf("%s\n", decode(111));
printf("%s\n", decode(116));
printf("%s\n", decode(112));
printf("%s\n", decode(117));
printf("%s\n", decode(113));
printf("%s\n", decode(118));
printf("%s\n", decode(114));
printf("%s\n", decode(119));
dlclose(handle);
}
makefile
NDK_ROOT=D:\android-ndk-r10d
TOOLCHAINS_ROOT=$(NDK_ROOT)/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64
TOOLCHAINS_PREFIX=$(TOOLCHAINS_ROOT)/bin/arm-linux-androideabi
TOOLCHAINS_INCLUDE=$(TOOLCHAINS_ROOT)/lib/gcc/arm-linux-androideabi/4.9/include-fixed
PLATFORM_ROOT=$(NDK_ROOT)/platforms/android-19/arch-arm
PLATFORM_INCLUDE=$(PLATFORM_ROOT)/usr/include
PLATFORM_LIB=$(PLATFORM_ROOT)/usr/lib
MODULE_NAME=hello
RM=rm
FLAGS=-I$(TOOLCHAINS_INCLUDE) \
-I$(PLATFORM_INCLUDE) \
-L$(PLATFORM_LIB) \
-nostdlib \
-lgcc \
-Bdynamic \
-lc
OBJS=$(MODULE_NAME).o \
$(PLATFORM_LIB)/crtbegin_dynamic.o \
$(PLATFORM_LIB)/crtend_android.o
all:
$(TOOLCHAINS_PREFIX)-gcc $(FLAGS) -E $(MODULE_NAME).c -o $(MODULE_NAME).i
$(TOOLCHAINS_PREFIX)-gcc $(FLAGS) -S $(MODULE_NAME).i -o $(MODULE_NAME).s
$(TOOLCHAINS_PREFIX)-gcc $(FLAGS) -c $(MODULE_NAME).s -o $(MODULE_NAME).o
$(TOOLCHAINS_PREFIX)-gcc $(FLAGS) -ldl $(OBJS) -o $(MODULE_NAME)
clean:
$(RM) *.o
install:
adb push $(MODULE_NAME) /data/local/tmp/
adb push libnqshield.so /data/local/tmp/
adb shell chmod 755 /data/local/tmp/$(MODULE_NAME)
adb shell /data/local/tmp/$(MODULE_NAME)
编译,执行
$ make
D:\android-ndk-r10d/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/bin/arm-linux-androideabi-gcc -ID:\android-ndk-r10d/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/lib/gcc/arm-linux-androideabi/4.9/include-fixed -ID:\android-ndk-r10d/platforms/android-19/arch-arm/usr/include -LD:\android-ndk-r10d/platforms/android-19/arch-arm/usr/lib -nostdlib -lgcc -Bdynamic -lc -E hello.c -o hello.i
D:\android-ndk-r10d/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/bin/arm-linux-androideabi-gcc -ID:\android-ndk-r10d/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/lib/gcc/arm-linux-androideabi/4.9/include-fixed -ID:\android-ndk-r10d/platforms/android-19/arch-arm/usr/include -LD:\android-ndk-r10d/platforms/android-19/arch-arm/usr/lib -nostdlib -lgcc -Bdynamic -lc -S hello.i -o hello.s
D:\android-ndk-r10d/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/bin/arm-linux-androideabi-gcc -ID:\android-ndk-r10d/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/lib/gcc/arm-linux-androideabi/4.9/include-fixed -ID:\android-ndk-r10d/platforms/android-19/arch-arm/usr/include -LD:\android-ndk-r10d/platforms/android-19/arch-arm/usr/lib -nostdlib -lgcc -Bdynamic -lc -c hello.s -o hello.o
D:\android-ndk-r10d/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/bin/arm-linux-androideabi-gcc -ID:\android-ndk-r10d/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/lib/gcc/arm-linux-androideabi/4.9/include-fixed -ID:\android-ndk-r10d/platforms/android-19/arch-arm/usr/include -LD:\android-ndk-r10d/platforms/android-19/arch-arm/usr/lib -nostdlib -lgcc -Bdynamic -lc -ldl hello.o D:\android-ndk-r10d/platforms/android-19/arch-arm/usr/lib/crtbegin_dynamic.o D:\android-ndk-r10d/platforms/android-19/arch-arm/usr/lib/crtend_android.o -o hello
$ make install
adb push hello /data/local/tmp/
99 KB/s (6328 bytes in 0.062s)
adb push libnqshield.so /data/local/tmp/
1249 KB/s (50524 bytes in 0.039s)
adb shell chmod 755 /data/local/tmp/hello
adb shell /data/local/tmp/hello
nq10
(Ljava/lang/String;Ljava/lang/String;I)V
nq11
(Ljava/lang/String;I)V
nq12
(Landroid/content/Context;Ljava/lang/ClassLoader;I)V
nq13
(Ljava/lang/String;I)V
nq14
(Ljava/lang/String;I)V
得到最后的函数对应表:
函数名 | 参数 | 本地方法 |
---|---|---|
nq10 | (Ljava/lang/String;Ljava/lang/String;I)V | b9df1ce797fd03603fd7137afff2957 |
nq11 | (Ljava/lang/String;I)V | eb5993d402df42eac47e82555b7ae31 |
nq12 | (Landroid/content/Context;Ljava/lang/ClassLoader;I)V | fa3f3a501e8754e88bed48c128ef90 |
nq13 | (Ljava/lang/String;I)V | abc020d3ee6fbc05ab1ed6b4da7252d |
nq14 | (Ljava/lang/String;I)V | e300588cc034bcbaa7d61 |
接下来只需要对其重命名即可。