【Android 逆向】【攻防世界】easy-dex

发布时间 2023-03-22 21:16:22作者: 明月照江江

这一题不easy,不知为何叫这个名字。。。。

1. apk 安装到手机,不知所云,各种亮瞎眼闪光

2. jadx 打开apk,一行java代码都没有,打开AndroidManifest看看

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.a.sample.findmydex" platformBuildVersionCode="24" platformBuildVersionName="7">
    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="24"/>
    <application android:theme="@style/AppTheme" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:hasCode="false" android:allowBackup="false" android:fullBackupContent="false">
        <activity android:label="@string/app_name" android:name="android.app.NativeActivity" android:configChanges="orientation|keyboardHidden">
            <meta-data android:name="android.app.lib_name" android:value="native"/>
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity android:name="com.a.sample.findmydex.MainActivity">
            <intent-filter>
                <action android:name="com.a.sample.findmydex.MAIN"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>
    </application>
</manifest>

注意这一行<meta-data android:name="android.app.lib_name" android:value="native"/>
还有android:hasCode="false"
说明代码在native层

2. so拖入到IDA中进行分析

符号表中看到一个叫android_main的函数,打开看看
,函数比较大,看留下的日志可以得出,需要我们摇晃手机100次的样子;先摇了再说

2023-03-22 15:21:29.741 6951-6976/? I/FindMyDex: Oh yeah~ You Got it~ 17 times to go~
2023-03-22 15:21:29.891 6951-6976/? I/FindMyDex: Oh yeah~ You Got it~ 15 times to go~
2023-03-22 15:21:30.041 6951-6976/? I/FindMyDex: Oh yeah~ You Got it~ 13 times to go~
2023-03-22 15:21:30.192 6951-6976/? I/FindMyDex: Oh yeah~ You Got it~ 11 times to go~
2023-03-22 15:21:30.207 6951-6976/? I/FindMyDex: Oh yeah~ You Got it~ 9 times to go~
2023-03-22 15:21:30.342 6951-6976/? I/FindMyDex: Oh yeah~ You Got it~ 7 times to go~
2023-03-22 15:21:30.491 6951-6976/? I/FindMyDex: Oh yeah~ You Got it~ 5 times to go~
2023-03-22 15:21:30.658 6951-6976/? I/FindMyDex: Oh yeah~ You Got it~ 3 times to go~
2023-03-22 15:21:30.790 6951-6976/? I/FindMyDex: Oh yeah~ You Got it~ 1 times to go~

看来实在监听我们摇晃的次数,达到次数后就打开界面
达到100次后,这里有一串代码

if ( v14 == 100 )
        {
          if ( time(0) - v6 > 9 )
          {
            _android_log_print(4, "FindMyDex", "OH~ You are too slow. Please try again");
            qmemcpy(v3, &unk_7004, (size_t)off_43A18);
            v10 = 0;
          }
          else
          {
            v20 = v6;
            if ( uncompress(dest, &destLen, (const Bytef *)v3, (uLong)off_43A18) )
              _android_log_print(5, "FindMyDex", "Dangerous operation detected.");
            v21 = open(filename, 577, 511);
            if ( !v21 )
              _android_log_print(5, "FindMyDex", "Something wrong with the permission.");
            write(v21, dest, destLen);
            close(v21);
            free(dest);
            free(v3);
            if ( access(name, 0) && mkdir(name, 0x1FFu) )
              _android_log_print(5, "FindMyDex", "Something wrong with the permission..");
            sub_2368(a1);
            remove(filename);
            _android_log_print(4, "FindMyDex", "Congratulations!! You made it!");
            sub_2250(a1);
            v10 = 0x80000000;
            v6 = v20;
          }
        }

感觉实在写文件,然后又删了,看看删之前的sub_2368函数

  if ( (*(int (__fastcall **)(_DWORD, JNIEnv **, _DWORD))(**(_DWORD **)(*(_DWORD *)(a1 + 12) + 4) + 16))(
         *(_DWORD *)(*(_DWORD *)(a1 + 12) + 4),
         &v21,
         0) != -1 )
  {
    v4 = (*v21)->FindClass(v21, "android/app/Activity");
    v5 = (*v21)->GetMethodID(v21, v4, "getClassLoader", "()Ljava/lang/ClassLoader;");
    v6 = (*v21)->CallObjectMethod(v21, *(jobject *)(*(_DWORD *)(a1 + 12) + 12), v5);
    v7 = (*v21)->NewStringUTF(v21, v25);
    v8 = (*v21)->NewStringUTF(v21, v22);
    v9 = (*v21)->FindClass(v21, "dalvik/system/DexClassLoader");
    v10 = (*v21)->GetMethodID(
            v21,
            v9,
            "<init>",
            "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V");
    v11 = (*v21)->NewObject(v21, v9, v10, v7, v8, 0, v6);
    v12 = (*v21)->FindClass(v21, "android/content/ContextWrapper");
    v13 = (*v21)->GetFieldID(v21, v12, "mBase", "Landroid/content/Context;");
    v14 = (*v21)->GetObjectField(v21, *(_DWORD *)(*(_DWORD *)(a1 + 12) + 12), v13);
    v15 = (*v21)->GetObjectClass(v21, v14);
    v16 = (*v21)->GetFieldID(v21, v15, "mPackageInfo", "Landroid/app/LoadedApk;");
    v17 = (*v21)->GetObjectField(v21, v14, v16);
    v18 = (*v21)->GetObjectClass(v21, v17);
    v19 = (*v21)->GetFieldID(v21, v18, "mClassLoader", "Ljava/lang/ClassLoader;");
    (*v21)->GetObjectField(v21, v17, v19);
    (*v21)->SetObjectField(v21, v17, v19, v11);
  }
  return _stack_chk_guard - v27;
}

这一看就是在动态加载dex,那么可以怀疑时so中动态释放出来了dex,然后加载

4. hook 以下remove 看一下文件写在哪了

    var lib_handler = Process.findModuleByName("libnative.so")
    var dst_addr = new NativePointer(lib_handler.base.add(0x00002762 + 1))
    console.log("==== " + dst_addr)
    Interceptor.attach(dst_addr, {
        onEnter:function(args) {
            console.log("==== args: " + ptr(args[0]).readCString())
            console.log("==== r0: " + print_dump(this.context.r0 ))
        }
    })

得出:/data/data/com.a.sample.findmydex/files/classes.dex

5. nop remove 不能让他删了,好取出文件


    var lib_handler = Process.findModuleByName("libnative.so")
    var dst_addr = new NativePointer(lib_handler.base.add(0x00002762))
    Memory.patchCode(dst_addr, 4, function (code) {
        var cw = new ArmWriter(code, { pc: dst_addr });
        cw.putNop()
        cw.flush();
    });

成功得到classes.dex

6. 将classes.dex 和 之前反编译出来的resources.arsc 一起拖入jadx中就可以看到代码了

public class MainActivity extends u {
    private static byte[] m = {-120, 77, -14, -38, 17, 5, -42, 44, -32, 109, 85, 31, 24, -91, -112, -83, 64, -83, Byte.MIN_VALUE, 84, 5, -94, -98, -30, 18, 70, -26, 71, 5, -99, -62, -58, 117, 29, -44, 6, 112, -4, 81, 84, 9, 22, -51, 95, -34, 12, 47, 77};

    /* JADX INFO: Access modifiers changed from: private */
    public static byte[] b(String str, String str2) {
        try {
            BufferedInputStream bufferedInputStream = new BufferedInputStream(new ByteArrayInputStream(str.getBytes()));
            ArrayList arrayList = new ArrayList();
            Object a = b.a(str2.getBytes());
            for (byte[] bArr = new byte[16]; bufferedInputStream.read(bArr, 0, 16) != -1; bArr = new byte[16]) {
                arrayList.add(b.a(bArr, 0, a));
            }
            ByteBuffer allocate = ByteBuffer.allocate(arrayList.size() * 16);
            for (Object obj : arrayList.toArray()) {
                allocate.put((byte[]) obj);
            }
            return allocate.array();
        } catch (Exception e) {
            return new byte[1];
        }
    }

    /* JADX INFO: Access modifiers changed from: protected */
    @Override // android.support.v7.a.u, android.support.v4.a.v, android.support.v4.a.p, android.app.Activity
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_main);
        ((Button) findViewById(R.id.button)).setOnClickListener(new a(this, (EditText) findViewById(R.id.edit_text), this));
    }
}

通过分析(查看其他大佬的分析)可知,这是用了twofish算法实现的加密,twofish 首先时对称加密输出byte数组,然后通过base64生成字符串输出,推测m就是没有被base64编码过的byte数组,处理成base64后,

import base64
a = [-120, 77, -14, -38, 17, 5, -42, 44, -32, 109, 85, 31, 24, -91, -112, -83, 64, -83, -128, 84, 5, -94, -98, -30, 18, 70, -26, 71, 5, -99, -62, -58, 117, 29, -44, 6, 112, -4, 81, 84, 9, 22, -51, 95, -34, 12, 47, 77]
a = [i&255 for i in a]
b = base64.b64encode(bytes(a))
print(b)

日志
iE3y2hEF1izgbVUfGKWQrUCtgFQFop7iEkbmRwWdwsZ1HdQGcPxRVAkWzV/eDC9N

用在线的twofish算法解密可得出结果qwb{TH3y_Io<e_EACh_OTh3r_FOrEUER}