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

栏目: Android · 发布时间: 7年前

内容简介:静态分析原生层程序基本的过程如下利用jadx反编译apk,确定应用的主活动程序的主活动为 com.example.mobicrackndk.CrackMe。

基本方法

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

  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(&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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Microsoft Windows程序设计

Microsoft Windows程序设计

佩措尔德 / 章立民 / 华中科技 / 2004-1 / 118.00元

Charles Petzold是全球最权威且知名的Windows程序设计专家,他将其最畅销Programming Microsoft Windows with C#一书加以改写,使之能完全适用于Visual Basic.NET的开发人员。这位畅销书的作家示范了如何使用Visual Basic.NET将Windows Forms的功能发挥到极致(Windows Forms是新一代的Windows程序......一起来看看 《Microsoft Windows程序设计》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具