Android逆向系列之静态分析(四)–Native

基本方法

静态分析原生层程序基本的过程如下

  1. 直接解压提取 so 文件(/lib文件夹)
  2. ida 反编译 so 文件阅读Arm汇编or反汇编代码
  3. 根据 java 层的代码来分析 so 代码。
  4. 根据 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(&amp;s, 0, 0x80u);
password = (*jniEnv)-&gt;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)-&gt;FindClass(jniEnv, "com/example/mobicrackndk/Calc");
if ( !class_calc )
{
v7 = "class,failed";
LABEL_11:
_android_log_print(4, "log", v7);
exit(1);
}
clackeyID = (*jniEnv)-&gt;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)-&gt;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)-&gt;GetStaticObjectField)(env, class_calc, keyID);// forceCallType
key = (*jniEnv)-&gt;GetStringUTFChars(jniEnv, key_, 0);// ,ZHVW^7c
while ( i &lt; strlen(key) + 8 )
{
secondPart[i - 8] = password[i] - i;
++i;
}
v18 = 0;
rel = (unsigned int)strcmp(key, secondPart) &lt;= 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 &gt;= 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

发表评论

电子邮件地址不会被公开。

You must enable javascript to see captcha here!