@oro-oro
2015-08-19T10:39:22.000000Z
字数 13916
阅读 6259
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"#endifreturn (*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-jniLOCAL_SRC_FILES := hello-jni.cLOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -lloginclude $(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@2int v3; // [sp+8h] [bp-2Ch]@1int v4; // [sp+Ch] [bp-28h]@3int v5; // [sp+10h] [bp-24h]@3int v6; // [sp+14h] [bp-20h]@3int (__fastcall *v7)(int); // [sp+18h] [bp-1Ch]@3int v8; // [sp+1Ch] [bp-18h]@3char v9[4]; // [sp+20h] [bp-14h]@3int (*v10)(); // [sp+24h] [bp-10h]@3if ( (*(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@2if ( (*(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@1int v2; // r3@1int v3; // r4@2int v4; // r0@2int v5; // r1@2signed int result; // r0@4int v7; // [sp+4h] [bp-Ch]@1v1 = 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@1dword_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-r10dTOOLCHAINS_ROOT=$(NDK_ROOT)/toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64TOOLCHAINS_PREFIX=$(TOOLCHAINS_ROOT)/bin/arm-linux-androideabiTOOLCHAINS_INCLUDE=$(TOOLCHAINS_ROOT)/lib/gcc/arm-linux-androideabi/4.9/include-fixedPLATFORM_ROOT=$(NDK_ROOT)/platforms/android-19/arch-armPLATFORM_INCLUDE=$(PLATFORM_ROOT)/usr/includePLATFORM_LIB=$(PLATFORM_ROOT)/usr/libMODULE_NAME=helloRM=rmFLAGS=-I$(TOOLCHAINS_INCLUDE) \-I$(PLATFORM_INCLUDE) \-L$(PLATFORM_LIB) \-nostdlib \-lgcc \-Bdynamic \-lcOBJS=$(MODULE_NAME).o \$(PLATFORM_LIB)/crtbegin_dynamic.o \$(PLATFORM_LIB)/crtend_android.oall:$(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) *.oinstall: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)
编译,执行
$ makeD:\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.iD:\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.sD:\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.oD:\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 installadb 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/helloadb shell /data/local/tmp/hellonq10(Ljava/lang/String;Ljava/lang/String;I)Vnq11(Ljava/lang/String;I)Vnq12(Landroid/content/Context;Ljava/lang/ClassLoader;I)Vnq13(Ljava/lang/String;I)Vnq14(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 |
接下来只需要对其重命名即可。