内容简介:静态分析原生层程序基本的过程如下利用jadx反编译apk,确定应用的主活动程序的主活动为 com.example.mobicrackndk.CrackMe。
基本方法
静态分析原生层程序基本的过程如下
- 直接解压提取 so 文件(/lib文件夹)
- ida 反编译 so 文件阅读Arm汇编or反汇编代码
- 根据 java 层的代码来分析 so 代码。
- 根据 so 代码的逻辑辅助整个程序的分析。
原生层静态分析例子
2015-福建海峡两岸CTF-APK逆向,逆向试试吧
反编译
利用jadx反编译apk,确定应用的主活动
<!--?xml version="1.0" encoding="utf-8"?--> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:versioncode="1" android:versionname="1.0" package="com.example.mobicrackndk"> <uses-sdk android:minsdkversion="8" android:targetsdkversion="17"> <application android:theme="@style/AppTheme" android:label="@string/app_name" android:icon="@drawable/ic_launcher" android:allowbackup="true"> <activity android:label="@string/app_name" android:name="com.example.mobicrackndk.CrackMe"> <intent-filter> <action android:name="android.intent.action.MAIN"> <category android:name="android.intent.category.LAUNCHER"> </category></action></intent-filter> </activity> </application> </uses-sdk></manifest>
程序的主活动为 com.example.mobicrackndk.CrackMe。
分析主活动
程序的基本情况就是利用 native 函数 testFlag 判断用户传入的 pwdEditText 是否满足要求。
public native boolean testFlag(String str); static { System.loadLibrary("mobicrackNDK"); } protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView((int) R.layout.activity_crack_me); this.inputButton = (Button) findViewById(R.id.input_button); this.pwdEditText = (EditText) findViewById(R.id.pwd); this.inputButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { CrackMe.this.input = CrackMe.this.pwdEditText.getText().toString(); if (CrackMe.this.input == null) { return; } if (CrackMe.this.testFlag(CrackMe.this.input)) { Toast.makeText(CrackMe.this, CrackMe.this.input, 1).show(); } else { Toast.makeText(CrackMe.this, "Wrong flag", 1).show(); } } }); }
分析so文件
自然我们首先会去直接找 testFlag 函数,凡是并没有直接找到。我们只好首先分析 JNI_Onload 函数,如下
signed int __fastcall JNI_OnLoad(JNIEnv *a1) { JNIEnv *v1; // r4 JNIEnv *env; // r5 char *v3; // r7 int class_; // r1 const char *v5; // r1 JNIEnv *v7; // [sp+Ch] [bp-1Ch] v1 = a1; v7 = 0; printf("JNI_OnLoad"); if ( ((int (__fastcall *)(JNIEnv *, JNIEnv **, signed int))(*v1)->FindClass)(v1, &v7, 65540) ) goto LABEL_7; env = v7; v3 = classPathName[0]; fprintf((FILE *)((char *)&_sF + 168), "RegisterNatives start for '%s'", classPathName[0]); class_ = ((int (__fastcall *)(JNIEnv *, char *))(*env)->FindClass)(env, v3); if ( !class_ ) { v5 = "Native registration unable to find class '%s'"; LABEL_6: fprintf((FILE *)((char *)&_sF + 168), v5, v3); LABEL_7: fputs("GetEnv failed", (FILE *)((char *)&_sF + 168)); return -1; } if ( ((int (__fastcall *)(JNIEnv *, int, char **, signed int))(*env)->RegisterNatives)(env, class_, off_400C, 2) < 0 ) { v5 = "RegisterNatives failed for '%s'"; goto LABEL_6; } return 65540; }
可以发现,程序在这里动态注册了类和相应的函数 off_400C。仔细看一下该函数
.data:0000400C off_400C DCD aTestflag ; DATA XREF: JNI_OnLoad+68↑o .data:0000400C ; .text:off_1258↑o .data:0000400C ; "testFlag" .data:00004010 DCD aLjavaLangStrin_0 ; "(Ljava/lang/String;)Z" .data:00004014 DCD abcdefghijklmn+1 .data:00004018 DCD aHello ; "hello" .data:0000401C DCD aLjavaLangStrin_1 ; "()Ljava/lang/String;" .data:00004020 DCD native_hello+1 .data:00004020 ; .data ends
可以发现,确实就是 testflag 函数,其对应的函数名为 abcdefghijklmn。
分析abcdefghijklmn
可以发现,程序主要在三个部分对输入进行判断和计算
bool __fastcall abcdefghijklmn(JNIEnv *a1, int a2, void *a3) { void *input; // r6 JNIEnv *env; // r7 _BOOL4 rel; // r4 size_t i; // r6 const char *v7; // r2 jmethodID clackeyID; // r2 int keyID; // r4 void *key_; // r0 const char *key; // r5 jclass class_calc; // [sp+4h] [bp-C4h] const char *password; // [sp+8h] [bp-C0h] char firstPart[8]; // [sp+14h] [bp-B4h] char v16; // [sp+1Ch] [bp-ACh] char secondPart[8]; // [sp+20h] [bp-A8h] char v18; // [sp+28h] [bp-A0h] char s; // [sp+2Ch] [bp-9Ch] input = a3; env = a1; if ( !jniEnv ) jniEnv = a1; memset(&s, 0, 0x80u); password = (*jniEnv)->GetStringUTFChars(jniEnv, input, 0); rel = 0; if ( strlen(password) == 16 ) { i = 0; do { firstPart[i] = password[i] - i; ++i; } while ( i != 8 ); // i==8的时候退出 rel = 0; v16 = 0; if ( !strcmp(seed[0], firstPart) ) // QflMn`fH { class_calc = (*jniEnv)->FindClass(jniEnv, "com/example/mobicrackndk/Calc"); if ( !class_calc ) { v7 = "class,failed"; LABEL_11: _android_log_print(4, "log", v7); exit(1); } clackeyID = (*jniEnv)->GetStaticMethodID(jniEnv, class_calc, "calcKey", "()V"); if ( !clackeyID ) { v7 = "method,failed"; goto LABEL_11; } _JNIEnv::CallStaticVoidMethod(jniEnv, class_calc, clackeyID); keyID = ((int (__fastcall *)(JNIEnv *, jclass, const char *, const char *))(*env)->GetStaticFieldID)( env, class_calc, "key", "Ljava/lang/String;"); if ( !keyID ) _android_log_print(4, "log", "fid,failed"); key_ = (void *)((int (__fastcall *)(JNIEnv *, jclass, int))(*env)->GetStaticObjectField)(env, class_calc, keyID);// forceCallType key = (*jniEnv)->GetStringUTFChars(jniEnv, key_, 0);// ,ZHVW^7c while ( i < strlen(key) + 8 ) { secondPart[i - 8] = password[i] - i; ++i; } v18 = 0; rel = (unsigned int)strcmp(key, secondPart) <= 0; } } return rel; }
并在之后获得了key的内容。
public static String key; public static void calcKey() { key = new StringBuffer("c7^WVHZ,").reverse().toString(); } }
获取flag
根据这三个判断,我们可以得到输入的字符串内容
s = "QflMn`fH,ZHVW^7c" flag = "" for idx,c in enumerate(s): flag +=chr(ord(c)+idx) print flag
结果如下
QgnPrelO4cRackEr
输入之后并不对。
再次分析
想到这里就要考虑下,程序是不是在哪里修改了对应的字符串。这里首先看一下seed。按 x 进行交叉引用,发现其在 _init_my 中使用了,如下
size_t _init_my() { size_t i; // r7 char *v1; // r4 size_t result; // r0 for ( i = 0; ; ++i ) { v1 = seed[0]; result = strlen(seed[0]); if ( i >= result ) break; t[i] = v1[i] - 3; } seed[0] = t; byte_4038 = 0; return result; }
所以最初程序对 seed 进行了修改。
再次获取flag
修改脚本如下
s = "QflMn`fH,ZHVW^7c" flag="" for i in xrange(0,8): flag = flag + chr(ord(s[i]) - 3 + i) for i in xrange(8,16): flag = flag + chr(ord(s[i]) + i) print "flag: " + flag
flag 如下
➜ 2015-海峡两岸一个APK,逆向试试吧 python exp.py NdkMobiL4cRackEr
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Hades:移动端静态分析框架
- Hades:移动端静态分析框架
- iOS 常用调试方法:静态分析
- iOS常用调试方法:静态分析
- Android静态分析之初级篇
- so静态分析进阶练习——一个CreakeMe的分析思路
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。