接触了一段时间的安卓后复现一下经典的题目,具体审计还有分析的步骤不做记录。

Analysis(classes.dex)

这道题目是阿里 14 年出的,先导入 jadx 看看反编译后大概的内容。在 AndroidManifest.xml 中,可以看到先设置了入口点为 com.ali.mobisecenhance.StubApplication,猜测这里可能是阿里加固自己添加的一个入口,用来执行一些初始化的操作,比如解密 dex,反调试,检测模拟器等等之类的。调用完 StubApplication 后,才会调用 MainActivity:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.ali.tg.testapp" android:versionCode="1" android:versionName="1.0">
    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="9" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <application android:theme="@style/AppTheme" android:label="@string/app_name" android:icon="@drawable/ic_launcher" android:name="com.ali.mobisecenhance.StubApplication" android:debuggable="true" android:allowBackup="true">
        <activity android:label="@string/app_name" android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".WebViewActivity" />
    </application>
</manifest>

在反编译出来的 Java 文件中,只能找到一个 StubApplication 类,其中的函数都是在 Native 层所实现,且加载了一个 mobisec 库。一般程序中是先执行 onCreate 函数,但 attachBaseContext 函数会早于 onCreate 函数执行:

package com.ali.mobisecenhance;

import android.app.Application;
import android.content.Context;

public class StubApplication extends Application {
    private native void b(ClassLoader classLoader, Context context);

    protected native void attachBaseContext(Context context);

    public native void onCreate();

    static {
        System.loadLibrary("mobisec");
    }
}

可以在 lib 文件夹下看到 libmobisec.so 库,同时还可以看到在 assets 文件夹中有两个 jar 文件:

$ tree .
.
├── AndroidManifest.xml
├── META-INF
│   ├── MANIFEST.MF
│   ├── TEST.RSA
│   └── TEST.SF
├── assets
│   ├── cls.jar
│   └── fak.jar
├── classes.dex
├── lib
│   ├── armeabi
│   │   ├── libhack.so
│   │   ├── libmobisec.so
│   │   └── libtranslate.so
│   ├── armeabi-v7a
│   │   ├── libhack.so
│   │   ├── libmobisec.so
│   │   └── libtranslate.so
│   └── x86
│       ├── libhack.so
│       ├── libmobisec.so
│       └── libtranslate.so
├── res
│   ├── drawable-hdpi
│   │   ├── android.jpg
│   │   └── android1.jpg
│   ├── drawable-mdpi
│   │   └── ic_launcher.png
│   ├── drawable-xhdpi
│   │   └── ic_launcher.png
│   ├── drawable-xxhdpi
│   │   └── ic_launcher.png
│   └── layout
│       ├── activity_main.xml
│       └── webviewlayout.xml
└── resources.arsc

12 directories, 24 files

file 一下,发现是 cls.jar 是一段不可识别的数据,fak.jar 判断出来是个 zip 文件:

$ file cls.jar
cls.jar: data

$ file fak.jar
fak.jar: Zip archive data, at least v?[0x314] to extract

经过以上粗略的审计,可以猜测可能是在 libmobisec.so 实现了 StubApplication 中的函数,并对 assets 文件夹下的两个文件进行操作来还原出 MainActivity 中的函数。

Analysis(libmobisec.so)

打开 IDA,最先定位到 JNI_OnLoad 函数,查看一下注册了哪些函数:

signed int __fastcall JNI_OnLoad(_JavaVM *vm, int a2)
{
  const char *v2; // r2
  jclass v3; // r1
  signed int result; // r0
  bool v5; // zf
  _JNIEnv *env; // [sp+4h] [bp-Ch]

  env = (_JNIEnv *)a2;
  if ( vm->functions->GetEnv(&vm->functions, (void **)&env, 65542) )
  {
    v2 = "Failed to get the environment";
LABEL_5:
    _android_log_print(6, "debug", v2);
    return -1;
  }
  v3 = env->functions->FindClass(&env->functions, "com/ali/mobisecenhance/StubApplication");// locate class
  if ( !v3 )
  {
    v2 = "failed to get class reference";
    goto LABEL_5;
  }
  v5 = env->functions->RegisterNatives(&env->functions, v3, (const JNINativeMethod *)gMethods, 2) == 0;// register 2 methods
  result = 65542;
  if ( !v5 )
    result = -1;
  return result;
}

在 RegisterNatives 函数的参数中可以看到注册了两个函数,分别为 attachBaseContext 和 onCreate。在内存中可以找到两个函数对应的指针:

.data:00054010 gMethods        DCD aAttachbasecont_0   ; DATA XREF: JNI_OnLoad+44↑o
.data:00054010                                         ; .text:off_24784↑o
.data:00054010                                         ; "attachBaseContext"
.data:00054014                 DCD aLandroidConten_1   ; "(Landroid/content/Context;)V"
.data:00054018                 DCD sub_24D3C+1
.data:0005401C                 DCD aOncreate           ; "onCreate"
.data:00054020                 DCD aV                  ; "()V"
.data:00054024                 DCD sub_24498+1

因为 attachBaseContext 先于 onCreate 函数执行,这里先看一下 attachBaseContext。跟着 log 可以对函数有一个大体的了解,在一处 log 里有“enter new application”的信息,猜测可能是完成了解码进入 MainActivity。在该处上下看看可以发现一个 parse_dex 函数,很有可能是解析出真正的 dex 文件的函数:

int __fastcall attachBaseContext(_JNIEnv *a1, jobject *a2, jobject *a3)
{
  jobject *v3; // r8
  jobject *v4; // r10
  _JNIEnv *env; // r4
  _JNIEnv *v6; // r1
  int v7; // r2
  int result; // r0
  ali *v9; // r0
  int v10; // r0
  int v11; // r0
  int v12; // r0
  int v13; // r5
  int v14; // r0
  int v15; // r0
  int v16; // r0
  int v17; // r0
  int v18; // r0
  char *v19; // r0
  int v20; // r0
  int v21; // r0
  void *v22; // r0
  void *v23; // r8
  const char *v24; // r0
  const char *v25; // r5
  size_t v26; // r0
  int v27; // r5
  int v28; // r8
  int v29; // r0
  int v30; // r5
  const char *v31; // r2
  int v32; // r0
  ali *v33; // r0
  int v34; // r4
  unsigned __int64 v35; // r2
  int v36; // [sp+8h] [bp-78h]
  __int64 v37; // [sp+18h] [bp-68h]
  char v38; // [sp+24h] [bp-5Ch]
  char v39; // [sp+3Ch] [bp-44h]
  char *v40; // [sp+4Ch] [bp-34h]
  char *v41; // [sp+50h] [bp-30h]

  v3 = a2;
  v4 = a3;
  env = a1;
  _android_log_print(6, "debug", "in...");
  result = ali::init_classes(env, v6, v7);      // init classes
  if ( result )
    return result;
  v9 = (ali *)_JNIEnv::CallNonvirtualVoidMethod(env, v3, ali::ContextWrapper, dword_54128, v4);
  v36 = ali::NanoTime(v9);
  v10 = _JNIEnv::GetObjectClass(env, v3);
  v11 = _JNIEnv::GetMethodID(env, v10, "getFilesDir", "()Ljava/io/File;");
  v12 = _JNIEnv::CallObjectMethod(env, v3, v11);
  v13 = v12;
  v14 = _JNIEnv::GetObjectClass(env, v12);
  v15 = _JNIEnv::GetMethodID(env, v14, "getAbsolutePath", "()Ljava/lang/String;");
  v16 = _JNIEnv::CallObjectMethod(env, v13, v15);
  sub_247D8(&v39, env, v16);
  if ( &v39 != (char *)&ali::g_filePath )
    std::string::_M_assign((std::string *)&ali::g_filePath, v41, v40);
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v39);
  _android_log_print(3, "debug", "global files path is %s", dword_540E8);
  v17 = _JNIEnv::CallObjectMethod(env, v3, dword_541A4);
  if ( ali::sdk_int <= 8 )
  {
    v20 = _JNIEnv::GetObjectField(env, v17, dword_5416C);
    sub_247D8(&v38, env, v20);
    std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v39, &v38, "/lib");
    if ( &v39 != (char *)&ali::g_libPath )
      std::string::_M_assign((std::string *)&ali::g_libPath, v41, v40);
    std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v39);
    v19 = &v38;
  }
  else
  {
    v18 = _JNIEnv::GetObjectField(env, v17, dword_54170);
    sub_247D8(&v39, env, v18);
    if ( &v39 != (char *)&ali::g_libPath )
      std::string::_M_assign((std::string *)&ali::g_libPath, v41, v40);
    v19 = &v39;
  }
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(v19);
  _android_log_print(3, "debug", "global native path is %s", dword_540D0);
  v21 = _JNIEnv::CallObjectMethod(env, v3, dword_541B0);
  sub_247D8(&v39, env, v21);
  if ( &v39 != (char *)&ali::g_apkPath )
    std::string::_M_assign((std::string *)&ali::g_apkPath, v41, v40);
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v39);
  setenv("APKPATH", (const char *)dword_540B8, 1);
  _android_log_print(3, "debug", "global apk path is %s", dword_540B8);
  sub_24A64(env, v3);
  v22 = (void *)_JNIEnv::CallObjectMethod(env, v4, dword_541A0);
  v23 = v22;
  if ( v22 )
  {
    v24 = env->functions->GetStringUTFChars(&env->functions, v22, 0);
    v25 = v24;
    v26 = strlen(v24);
    std::string::_M_assign((std::string *)&ali::g_pkgName, v25, &v25[v26]);
    env->functions->ReleaseStringUTFChars(&env->functions, v23, v25);
  }
  v37 = 0LL;
  v27 = _JNIEnv::CallObjectMethod(env, v4, dword_541A8);
  parse_dex(env, &v37);                         // parse dex?
  replace_classloader_cookie(env, v27, v37, HIDWORD(v37));
  _android_log_print(3, "debug", "enter new application");// enter MainActivity?
  v28 = dword_54120;
  _JNIEnv::NewStringUTF(env, "android.app.Application");
  v29 = _JNIEnv::CallObjectMethod(env, v27, v28);
  v30 = v29;
  if ( v29 )
  {
    v32 = _JNIEnv::GetMethodID(env, v29, "<init>", "()V");
    dword_540A0 = _JNIEnv::NewObject(env, v30, v32);
    _JNIEnv::CallVoidMethod(env, dword_540A0, dword_54134, v4);
    _JNIEnv::DeleteLocalRef(env, v30);
    v31 = "exit new application";
  }
  else
  {
    v31 = "can't findClass realAppClass";
  }
  v33 = (ali *)_android_log_print(3, "debug", v31);
  if ( dword_540A0 )
  {
    v33 = (ali *)env->functions->NewGlobalRef(&env->functions, (jobject)dword_540A0);
    dword_540A0 = (int)v33;
  }
  v34 = ali::NanoTime(v33);
  _android_log_print(3, "debug", "##### attachBaseContext spent:");
  ali::PrettyDuration((ali *)(v34 - v36), v35);
  result = _android_log_print(3, "debug", "exit attachBaseContext");
  return result;
}

接下来进入 parse_dex 进行分析。一开始判断了是采用了 Dalvik 模式还是 ART 模式。我的机器是 Android 4.4.4,用的是 Dalvik 模式,那就只分析一下 Dalvik 的部分。接下来对 SDK 的版本进行了判断,是否大于 SDK13。我用的机器是 SDK19,故下面应该是调用了 openWithHeader 函数。之后的部分看到是用 dlopen 打开 libdvm.so,并开始执行程序,所以就不做进一步分析。主要应该就是 openWithHeader 中的内容解析了出了 dex 文件:

signed int __fastcall parse_dex(_JNIEnv *a1, __int64 *a2)
{
  int v2; // r7
  const char *v3; // r1
  char *v4; // r0
  char *v5; // r9
  unsigned __int8 *v6; // r3
  int v7; // r2
  int v8; // t1
  int fd; // ST14_4
  int v10; // r8
  int v11; // r7
  int v12; // r5
  int v13; // r0
  int v14; // r5
  int v15; // r0
  int v16; // r7
  int v17; // r1
  int v18; // r5
  int (__fastcall *v19)(int, signed int); // r5
  int v20; // r5
  unsigned __int8 *v21; // r8
  const char *v22; // r3
  char *v23; // r0
  char *v24; // r0
  char *v25; // r6
  ali::EncFile *v26; // r7
  int v27; // r0
  int *v28; // r0
  char *v29; // r0
  int v30; // r10
  void *v31; // r7
  int (__fastcall *v32)(unsigned __int8 *, int, signed int *); // r9
  int (__fastcall *v33)(_DWORD); // r7
  const char *v34; // r2
  int v35; // r9
  signed int v36; // r7
  _DWORD *v37; // r9
  _BYTE *v38; // r5
  unsigned __int8 *v39; // r3
  void *v40; // r0
  JNINativeMethod *v41; // r0
  unsigned __int8 *v42; // r3
  signed int v43; // r3
  _JNIEnv *v45; // [sp+8h] [bp-2A0h]
  __int64 *v46; // [sp+10h] [bp-298h]
  int v47; // [sp+24h] [bp-284h]
  unsigned __int8 *v48; // [sp+28h] [bp-280h]
  unsigned __int8 *v49; // [sp+2Ch] [bp-27Ch]
  void (__cdecl *v50)(const unsigned int *, jvalue *); // [sp+30h] [bp-278h]
  char v51; // [sp+34h] [bp-274h]
  signed int v52[2]; // [sp+38h] [bp-270h]
  char s; // [sp+40h] [bp-268h]
  char v54; // [sp+54h] [bp-254h]
  int v55; // [sp+64h] [bp-244h]
  int v56; // [sp+68h] [bp-240h]
  char v57; // [sp+6Ch] [bp-23Ch]
  const char *v58; // [sp+80h] [bp-228h]
  char v59; // [sp+84h] [bp-224h]
  const char *v60; // [sp+98h] [bp-210h]
  char v61; // [sp+9Ch] [bp-20Ch]
  unsigned int v62; // [sp+B0h] [bp-1F8h]
  char v63; // [sp+B4h] [bp-1F4h]
  char v64; // [sp+CCh] [bp-1DCh]
  int v65; // [sp+E0h] [bp-1C8h]
  char v66; // [sp+E4h] [bp-1C4h]
  char v67; // [sp+FCh] [bp-1ACh]
  const char *v68; // [sp+110h] [bp-198h]
  char v69; // [sp+114h] [bp-194h]
  char v70; // [sp+12Ch] [bp-17Ch]
  const char *v71; // [sp+140h] [bp-168h]
  char v72; // [sp+144h] [bp-164h]
  char *v73; // [sp+154h] [bp-154h]
  char *v74; // [sp+158h] [bp-150h]
  char v75; // [sp+15Ch] [bp-14Ch]
  char v76; // [sp+174h] [bp-134h]
  char v77; // [sp+18Ch] [bp-11Ch]
  char v78; // [sp+1A4h] [bp-104h]
  char v79; // [sp+1BCh] [bp-ECh]
  char v80; // [sp+1D4h] [bp-D4h]
  char v81; // [sp+1ECh] [bp-BCh]
  char v82; // [sp+204h] [bp-A4h]
  char v83; // [sp+21Ch] [bp-8Ch]
  char v84; // [sp+234h] [bp-74h]
  int v85; // [sp+244h] [bp-64h]
  unsigned __int8 *v86; // [sp+248h] [bp-60h]
  char v87; // [sp+24Ch] [bp-5Ch]
  char v88; // [sp+264h] [bp-44h]
  char *v89; // [sp+274h] [bp-34h]
  char *v90; // [sp+278h] [bp-30h]

  v45 = a1;
  v46 = a2;
  _android_log_print(3, "debug", "enter parse_dex");
  if ( ali::isDalvik )                          // dalvik or art
  {
    v47 = 0;
    std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v88, &ali::g_filePath, "/cls.jar");// locate cls.jar
    v25 = v90;
    v26 = (ali::EncFile *)operator new(0xCu);
    ali::EncFile::EncFile(v26, v25);
    v48 = 0;
    v49 = 0;
    if ( ali::sdk_int > 13 )                    // sdk version > 13
    {
      v27 = ali::EncFile::openWithHeader(v26, &v48, (unsigned int *)&v47, 0x10u);
      v49 = v48 + 16;
    }
    else                                        // sdk version <= 13 ( android 3.x )
    {
      v27 = ali::EncFile::open(v26, &v49, (unsigned int *)&v47);
    }
    if ( v49 == (unsigned __int8 *)-1 )
    {
      v28 = (int *)_errno(v27);
      v29 = strerror(*v28);
      _android_log_print(3, "debug", "mmap dex file :%s", v29);
LABEL_45:
      v24 = &v88;
      goto LABEL_46;
    }
    v30 = *((_DWORD *)v49 + 8);
    if ( ali::sdk_int > 13 )                    // sdk version > 13
    {
      v40 = dlopen("libdvm.so", 1);             // open libdvm.so and start execute program
      v41 = (JNINativeMethod *)dlsym(v40, "dvm_dalvik_system_DexFile");
      v50 = 0;
      lookup(v41, "openDexFile", "([B)I", &v50);
      v42 = v48;
      *((_DWORD *)v48 + 2) = v47;
      *(_DWORD *)&v51 = v42;
      ((void (*)(void))v50)();
      v43 = v52[0];
      *v46 = v52[0];
      *(_DWORD *)(*(_DWORD *)(*(_DWORD *)(v43 + 8) + 4) + 32) = *(_DWORD *)(v43 + 16);
      *(_DWORD *)(*(_DWORD *)(*(_DWORD *)(v43 + 8) + 4) + 36) = v47;
      ali::EncFile::~EncFile(v26);
      operator delete((void *)v26);
    }
    else                                        // sdk version <= 13 ( android 3.x )
    {
      v31 = dlopen("libdvm.so", 1);
      v32 = (int (__fastcall *)(unsigned __int8 *, int, signed int *))dlsym(v31, "dvmDexFileOpenPartial");
      v33 = (int (__fastcall *)(_DWORD))dlsym(v31, "dexCreateClassLookup");
      v52[0] = 0;
      if ( v32(v49, v30, v52) == -1 )
      {
        v34 = "dvmDexFileOpenPartial error";
LABEL_40:
        _android_log_print(3, "debug", v34);
        goto LABEL_45;
      }
      v35 = *(_DWORD *)v52[0];
      *(_DWORD *)(v35 + 36) = v33(*(_DWORD *)v52[0]);
      v36 = v52[0];
      if ( !*(_DWORD *)(*(_DWORD *)v52[0] + 36) )
      {
        v34 = "dexCreateClassLookup error";
        goto LABEL_40;
      }
      v37 = malloc(0x2Cu);
      v38 = malloc(0x14u);
      strdup((const char *)&unk_4CEE9);
      v38[4] = 0;
      v38[5] = 0;
      *((_DWORD *)v38 + 2) = 0;
      v39 = v49;
      *(_DWORD *)v38 = v38;
      *((_DWORD *)v38 + 3) = v37;
      v37[10] = v36;
      *(_DWORD *)(v36 + 32) = v39;
      *(_DWORD *)(v36 + 36) = v47;
      *v46 = (signed int)v38;
    }
    v23 = &v88;
    goto LABEL_44;
  }
  std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v54, &ali::g_filePath, "/cls.jar");
  std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v57, &ali::g_filePath, "/cls.dex");
  std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v59, &ali::g_filePath, "/fak.jar");
  _android_log_print(3, "debug", "before oat gen");
  if ( !access(v58, 0) )
    goto LABEL_24;
  v2 = android_getCpuFamily();
  std::string::string(&v63, "arm", &v51);
  switch ( v2 )
  {
    case 1:
      v3 = "arm";
LABEL_5:
      std::string::operator=(&v63, v3);
      break;
    case 2:
      v3 = "x86";
      goto LABEL_5;
    case 3:
    case 6:
      v3 = "mips";
      goto LABEL_5;
    case 4:
      v3 = "arm64";
      goto LABEL_5;
    case 5:
      v3 = "x86_64";
      goto LABEL_5;
  }
  std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v64, &ali::g_libPath, "/libhack.so");
  v4 = getenv("LD_PRELOAD");
  v5 = v4;
  if ( v4 )
  {
    _android_log_print(3, "debug", "the system already define LD_PRELOAD=%s", v4);
    std::string::string(&v84, v5, v52);
    v6 = v86;
    v7 = v85;
    while ( v6 != (unsigned __int8 *)v7 )
    {
      v8 = *v6++;
      if ( v8 == 32 )
        *(v6 - 1) = 58;
    }
    std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v87, &v84, ":");
    std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v88, &v87, &v64);
    std::string::_M_assign((std::string *)&v64, v90, v89);
    std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v88);
    std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v87);
    std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v84);
  }
  _android_log_print(3, "debug", "the new LD_PRELOAD is %s", v65);
  std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v66, &ali::g_filePath, "/juice.data");
  std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v67, &ali::g_filePath, "/fak.jar");
  fd = open(v68, 0);
  memset(&s, 0, 0x14u);
  sprintf(&s, "%d", fd);
  std::string::string(&v69, &s, v52);
  v73 = &v72;
  v74 = &v72;
  std::priv::_String_base<char,std::allocator<char>>::_M_allocate_block(&v72, v55 - v56 + 10);
  *v73 = 0;
  std::string::_M_appendT<char const*>(&v72, "DEX_FILE=", "", v52);
  std::string::append((std::string *)&v72, (const std::string *)&v54);
  std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v75, &v72, "                     JUICE_FILE=");
  std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v76, &v75, &v66);
  std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v77, &v76, "                     LD_PRELOAD=");
  std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v78, &v77, &v64);
  std::operator+<char,std::char_traits<char>,std::allocator<char>>(
    &v79,
    &v78,
    "                     /system/bin/dex2oat \t\t\t\t  \t --runtime-arg -Xms64m \t\t\t\t\t --runtime-arg -Xmx64m \t\t\t\t"
    "\t --boot-image=/system/framework/boot.art                      --zip-fd=");
  std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v80, &v79, &v69);
  std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v81, &v80, "\t\t\t\t\t --zip-location=");
  std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v82, &v81, &v67);
  std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v83, &v82, "\t\t\t\t\t --oat-file=");
  std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v84, &v83, &v57);
  std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v70, &v84, "                     ");
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v84);
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v83);
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v82);
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v81);
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v80);
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v79);
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v78);
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v77);
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v76);
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v75);
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v72);
  _android_log_print(3, "debug", "cmd is %s", v71);
  system(v71);
  close(fd);
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v70);
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v69);
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v67);
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v66);
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v64);
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v63);
LABEL_24:
  _android_log_print(3, "debug", "after oat gen");
  v10 = ali::JDexFile;
  v11 = dword_54140;
  if ( ali::sdk_int <= 19 )                     // sdk version <= 19
  {
    v12 = _JNIEnv::NewStringUTF(v45, v60);
    v13 = _JNIEnv::NewStringUTF(v45, v58);
    v16 = _JNIEnv::CallStaticIntMethod(v45, v10, v11, v12, v13, 0);
    v18 = 0;
  }
  else                                          // sdk version > 19
  {
    v14 = _JNIEnv::NewStringUTF(v45, v60);
    v15 = _JNIEnv::NewStringUTF(v45, v58);
    v16 = _JNIEnv::CallStaticLongMethod(v45, v10, v11, v14, v15, 0);
    v18 = v17;
  }
  _android_log_print(3, "debug", "cookie is %llx");
  *(_DWORD *)v46 = v16;
  *((_DWORD *)v46 + 1) = v18;
  v19 = (int (__fastcall *)(int, signed int))dlsym((void *)0xFFFFFFFF, "_ZNK3art7DexFile12FindClassDefEt");
  _android_log_print(3, "debug", "DexFile::FindClassDefFn is %p", v19);
  v20 = v19(v16, 1);
  _android_log_print(3, "debug", "call FindClassDefFn(%p,%d) => %p", v16, 1, v20);
  _android_log_print(3, "debug", "dex position is %p", v20 - 572);
  _android_log_print(3, "debug", "dex head is %08x %08x", *(_DWORD *)(v20 - 572), *(_DWORD *)(v20 - 568));
  v21 = *(unsigned __int8 **)(v20 - 540);
  _android_log_print(3, "debug", "dex size is %d", v21);
  MemEnableWrite((unsigned __int8 *)(v20 - 572), &v21[v20 - 572]);
  std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v61, &ali::g_filePath, "/juice.data");
  if ( !ali::dex_juicer_patch((ali *)(v20 - 572), v21, v62, v22) )
  {
    std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v61);
    std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v59);
    std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v57);
    v23 = &v54;
LABEL_44:
    std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(v23);
    _android_log_print(3, "debug", "exit parse_dex");
    return 0;
  }
  _android_log_print(6, "debug", "fail to patch dex");
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v61);
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v59);
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(&v57);
  v24 = &v54;
LABEL_46:
  std::priv::_String_base<char,std::allocator<char>>::_M_deallocate_block(v24);
  _android_log_print(3, "debug", "exit parse_dex error");
  return -1;
}

在 openWithHeader 中,log 了三次 dex 的 magic number,中间分别进行了 RC4 解密和 LZMA 解压缩。最后得到的结果应该就是最终的 dex 文件:

int __fastcall ali::EncFile::openWithHeader(ali::EncFile *this, unsigned __int8 **a2, unsigned int *a3, unsigned int a4)
{
  ali::EncFile *v4; // r5
  unsigned __int8 **v5; // r11
  unsigned int *v6; // r6
  unsigned int v7; // r7
  const char *v8; // r2
  int fd; // r8
  int v10; // r10
  __blksize_t v12; // r3
  unsigned __int8 *v13; // r4
  ali *v14; // r0
  __int64 v15; // r0
  __int64 v16; // ST18_8
  unsigned int *v17; // r3
  ali *v18; // r0
  __int64 v19; // r0
  int v20; // r8
  int v21; // r3
  char v22; // r2
  unsigned __int64 v23; // r0
  ali *v24; // r0
  unsigned __int8 *v25; // r9
  ali *v26; // ST24_4
  __int64 v27; // r0
  __int64 v28; // ST18_8
  ali *v29; // r0
  __int64 v30; // r0
  size_t v31; // [sp+2Ch] [bp-9Ch]
  int v32; // [sp+30h] [bp-98h]
  char v33; // [sp+34h] [bp-94h]
  struct stat buf; // [sp+38h] [bp-90h]

  v4 = this;
  v5 = a2;
  v6 = a3;
  v7 = a4;
  if ( !*((_DWORD *)this + 2) )
  {
    v8 = "file path is null";
LABEL_5:
    _android_log_print(6, "debug", v8);
    return 0;
  }
  fd = open(*((const char **)this + 2), 0);
  v10 = fstat(fd, &buf);
  if ( v10 )
  {
    v8 = "fstat failed";
    goto LABEL_5;
  }
  v12 = buf.st_blksize;
  *v6 = buf.st_blksize;
  *(_DWORD *)v4 = v12;
  v13 = (unsigned __int8 *)mmap(0, *v6, 3, 2, fd, 0);
  *((_DWORD *)v4 + 1) = v13;
  close(fd);
  v14 = (ali *)_android_log_print(
                 3,
                 "debug",
                 "dex magic %c %c %c %c %c %c %c",// original dex magic
                 *v13,
                 v13[1],
                 v13[2],
                 v13[3],
                 v13[4],
                 v13[5],
                 v13[6]);
  LODWORD(v15) = ali::NanoTime(v14);
  v16 = v15;
  v18 = (ali *)ali::decryptRc4((ali *)v13, v13, (unsigned __int8 *)v6, v17);// RC4 decrypt
  LODWORD(v19) = ali::NanoTime(v18);
  ali::PrettyDuration((ali *)(v19 - v16), v19 - v16);
  _android_log_print(3, "debug", "decrypted len:%u", *v6);
  v20 = 0;
  _android_log_print(
    3,
    "debug",
    "after decrypt dex magic %c %c %c %c %c %c %c",// dex magic after RC4
    *v13,
    v13[1],
    v13[2],
    v13[3],
    v13[4],
    v13[5],
    v13[6]);
  v21 = (int)(v13 + 4);
  do
  {
    v22 = 8 * v10++;
    v23 = (unsigned __int64)*(unsigned __int8 *)(v21++ + 1) << v22;
    v20 += v23;
  }
  while ( v10 != 8 );
  _android_log_print(3, "debug", "unpackSize: %u", v20);
  *(_DWORD *)v4 = v7 + v20;
  v24 = (ali *)mmap(0, v7 + v20, 3, 34, -1, 0);
  *((_DWORD *)v4 + 1) = v24;
  v25 = (unsigned __int8 *)v24 + v7;
  v26 = v24;
  LODWORD(v27) = ali::NanoTime(v24);
  v31 = *v6;
  v28 = v27;
  v32 = v20;
  v29 = (ali *)LzmaDecode(v25, &v32, v13 + 13, &v31, v13, 5, 1, &v33, &off_54028);// LZMA uncompress
  LODWORD(v30) = ali::NanoTime(v29);
  ali::PrettyDuration((ali *)(v30 - v28), v30 - v28);
  munmap(v13, buf.st_blksize);
  _android_log_print(
    3,
    "debug",
    "after uncompressed dex magic %c %c %c %c %c %c %c",// dex magic after LZMA
    *((unsigned __int8 *)v26 + v7),
    v25[1],
    v25[2],
    v25[3],
    v25[4],
    v25[5],
    v25[6]);
  *v6 = v20;
  if ( v5 )
    *v5 = (unsigned __int8 *)*((_DWORD *)v4 + 1);
  return *((_DWORD *)v4 + 1);
}

Dump Dex File

知道了解析 dex 的流程,接下来就通过动态调试来吧 dex 文件 dump 下来。现在 BL 跳转到 openWithHeader 的语句处设下断点:

.text:00026A7E loc_26A7E                               ; CODE XREF: parse_dex(_JNIEnv *,long long *)+622↑j
.text:00026A7E                 MOV             R1, R9  ; unsigned __int8 **
.text:00026A80                 MOV             R2, R4  ; unsigned int *
.text:00026A82                 MOVS            R3, #0x10 ; unsigned int
.text:00026A84                 BL              _ZN3ali7EncFile14openWithHeaderEPPhPjj ; ali::EncFile::openWithHeader(uchar **,uint *,uint)
.text:00026A88                 LDR.W           R3, [R9]
.text:00026A8C                 ADDS            R3, #0x10
.text:00026A8E                 STR             R3, [R6]

运行到断点处,单步步入 openWithHeader 函数,然后单步步过一直到 return,中间可以在 monitor 中用 tag:debug 过滤来查看 log。运行完后看到 log 输出的 magic number 已经是真实 dex 文件的样子了:

根据函数的返回值存放在 R0 中,可以看到 R0 所指向的部分是一个 dex 文件的数据了:

接下来我们可以把 dex 文件给 dump 下来。但文件的大小为多少?根据 dex 的数据结构,可以知道 dex 文件的大小位于偏移 0x20 处:

接下来使用 IDC 脚本来 dump 数据:

static main(void) {
    auto fp, begin, end, len, b;
    fp = fopen("dump.dex", "wb");
    begin = 0x7584C010; // 解密后数据在内存中的位置
    len = 0x0941FC; // 文件大小
    end = begin + len;
    for (b = begin; b < end; b++) {
        fputc(Byte(b), fp);
    }
}

最后将 dump 下来的数据放进 JEB 中,反汇编可以得到真实的 MainActivity 代码:

Fix Application

使用 AndroidKiller 反编译加固后的 apk,找到 AndroidManifest.xml,删除 Application 的 android:name 属性:

<?xml version="1.0" encoding="utf-8" standalone="no"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.ali.tg.testapp" platformBuildVersionCode="23" platformBuildVersionName="6.0-2438415">
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <application android:allowBackup="true" android:debuggable="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme">
        <activity android:label="@string/app_name" android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity android:name=".WebViewActivity"/>
    </application>
</manifest>

回编译后,找到生成的 apk,压缩软件打开,替换我们 dump 出来的 classes.dex,同时删除 assets 文件夹,其他 so 文件不用管。修改完后重新签名打包并安装运行,可以正常使用。

Find Flag

定位到真实的程序后,开始分析具体的内容。先来看 MainActivity,主要是一个点击事件,其中获取了 EditText 中的字符串并作为参数传入并启动 WebViewActivity:

package com.ali.tg.testapp;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.ActionBarDrawerToggleJellybeanMR2n;
import android.support.v4.widget.ListViewAutoScrollHelpern;
import android.view.View$OnClickListener;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity extends Activity {
    class com.ali.tg.testapp.MainActivity$1 implements View$OnClickListener {
        com.ali.tg.testapp.MainActivity$1(MainActivity arg1) {
            MainActivity.this = arg1;
            super();
        }

        public void onClick(View arg6) {
            ActionBarDrawerToggleJellybeanMR2n.b(ActionBarDrawerToggleJellybeanMR2n.a());
            String v1 = MainActivity.this.edit.getText().toString(); // 获取EditText中的字符串
            Intent v0 = new Intent();
            v0.putExtra(ListViewAutoScrollHelpern.decrypt_native("dV.", 2), v1); // 将v1的值传给Intent,变量名为“dV.”解密后的值
            v0.setClass(MainActivity.this, WebViewActivity.class); // 设置Intent要跳转的类为WebViewActivity
            MainActivity.this.startActivity(v0); // 启动WebViewActivity
        }
    }

    Button btn_enter;
    View$OnClickListener btn_listener;
    EditText edit;

    public MainActivity() {
        super();
        this.btn_enter = null;
        this.edit = null;
        this.btn_listener = new com.ali.tg.testapp.MainActivity$1(this);
    }

    protected void onCreate(Bundle arg4) {
        ActionBarDrawerToggleJellybeanMR2n.b(ActionBarDrawerToggleJellybeanMR2n.a());
        super.onCreate(arg4);
        this.setContentView(0x7F030000);
        this.edit = this.findViewById(0x7F060001);
        this.btn_enter = this.findViewById(0x7F060002);
        this.btn_enter.setOnClickListener(this.btn_listener);
    }
}

然后来看看 WebViewActivity,主要就是新建了一个 JavaScriptInterface 对象,对象的名称同样被加密了。然后加载输入的 url,目标是最后能够成功调用对象 JavaScriptInterface 里的 showToast 方法。接下来需要根据密文解出对象名,并构造出相应的网页来弹出 Toast。不过这里好像 flag 就是“祥龙”,但还是继续往下尝试构造出能够弹 Toast 的方法:

package com.ali.tg.testapp;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.ActionBarDrawerToggleJellybeanMR2n;
import android.support.v4.widget.ListViewAutoScrollHelpern;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Toast;

public class WebViewActivity extends Activity {
    public class JavaScriptInterface {
        Context mContext;

        JavaScriptInterface(WebViewActivity arg1, Context arg2) {
            WebViewActivity.this = arg1;
            super();
            this.mContext = arg2;
        }

        public void showToast() { // 构造出一个页面能够调用这个函数就成功了
            ActionBarDrawerToggleJellybeanMR2n.b(ActionBarDrawerToggleJellybeanMR2n.a());
            Toast.makeText(this.mContext, "祥龙!", 0).show();
        }
    }

    WebView wView;

    public WebViewActivity() {
        super();
        this.wView = null;
    }

    protected void onCreate(Bundle arg7) {
        ActionBarDrawerToggleJellybeanMR2n.b(ActionBarDrawerToggleJellybeanMR2n.a());
        super.onCreate(arg7);
        this.setContentView(0x7F030001);
        this.wView = this.findViewById(0x7F060004);
        WebSettings v2 = this.wView.getSettings();
        v2.setJavaScriptEnabled(true);
        v2.setJavaScriptCanOpenWindowsAutomatically(true);
        this.wView.addJavascriptInterface(new JavaScriptInterface(this, ((Context)this)), ListViewAutoScrollHelpern.decrypt_native("BQ1$*[w6G_", 2)); // 添加一个JavascriptInterface对象,对象的变量名为“BQ1$*[w6G_”解密后的值
        this.wView.loadUrl(this.getIntent().getStringExtra(ListViewAutoScrollHelpern.decrypt_native("dV.", 2))); // 把在MainActivity中获取的变量作为url来加载
    }
}

Crack

接下来再看看 ListViewAutoScrollHelpern 中的 decrypt_native 方法,发现是在 Native 层中的 translate 库实现的:

package android.support.v4.widget;

import android.util.Log;

public class ListViewAutoScrollHelpern {
    static {
        System.loadLibrary("translate");
    }

    public ListViewAutoScrollHelpern() {
        super();
    }

    public static native String decrypt_native(String arg0, int arg1) {
    }

    public static void testLogv(String arg1) {
        Log.v("cheatecore", arg1);
    }

    public static void testLogw(String arg1) {
        Log.w("cheatecore", arg1);
    }
}

然后将 libtranslat.so 载入 IDA。先看看 JNI_OnLoad,其中有两个函数 register_Algorithm 和 register_translate:

int __fastcall JNI_OnLoad(_JavaVM *a1)
{
  int v1; // r1
  jint v2; // r2
  _JNIEnv *v3; // r4
  jint v4; // r0
  bool v5; // cf
  int result; // r0
  _JNIEnv *env; // [sp+4h] [bp-Ch]

  env = 0;
  if ( a1->functions->GetEnv(&a1->functions, (void **)&env, 65540) )
    return -1;
  v3 = env;
  register_Algorithm(env, v1, v2);
  v4 = register_translate(v3);
  v5 = v4 < 0;
  result = v4 & (v4 >> 32);
  if ( !v5 )
    result = 65540;
  return result;
}

在 register_Algorithm 中发现了目标函数:

int __fastcall register_Algorithm(_JNIEnv *a1, int a2, jint a3)
{
  _JNIEnv *v3; // r4
  jclass v4; // r0
  jclass v5; // r0

  v3 = a1;
  v4 = a1->functions->FindClass(&a1->functions, "android/support/v4/widget/ListViewAutoScrollHelpern");
  v3->functions->RegisterNatives(&v3->functions, v4, (const JNINativeMethod *)off_607C, 1);
  v5 = v3->functions->FindClass(&v3->functions, "android/support/v4/view/PagerTitleStripIcsn");
  v3->functions->RegisterNatives(&v3->functions, v5, (const JNINativeMethod *)off_607C, 1);
  return 0;
}

定位到目标函数,发现其中调用了一个 vigenere_decrypt:

jstring __fastcall decrypt_native(_JNIEnv *a1, jobject a2, jstring a3, jint a4)
{
  jstring data; // r6
  jint num; // r9
  _JNIEnv *env; // r4
  const char *v7; // r0
  const char *v8; // r8
  jstring v9; // r7
  int v11; // [sp+4h] [bp+0h]

  data = a3;
  num = a4;
  env = a1;
  memset(&v11, 0, 0x1000u);
  v7 = env->functions->GetStringUTFChars(&env->functions, data, 0);
  v8 = v7;
  if ( num == 2 )
  {
    vigenere_decrypt(v7, (char *)&v11);
    v9 = env->functions->NewStringUTF(&env->functions, (const char *)&v11);
  }
  else
  {
    v9 = data;
  }
  env->functions->ReleaseStringUTFChars(&env->functions, data, v8);
  return v9;
}

在 vigenere_decrypt 函数中,对输入的数据进行了解密:

signed int __fastcall vigenere_decrypt(const char *ciphertext, char *plaintext)
{
  const char *ciphertext_1; // r8
  char *plaintext_1; // r6
  size_t len; // r0
  char *v5; // r2
  const char *table; // r3
  signed int v7; // r7
  int v8; // r0
  int v9; // r1
  int v10; // r5
  int v11; // r0
  int v12; // r10
  char v13; // r3
  signed int i; // r5
  signed int result; // r0
  signed int v16; // r9
  int ch; // r3
  char s; // [sp+4h] [bp-64h]
  char v19; // [sp+48h] [bp-20h]

  ciphertext_1 = ciphertext;
  plaintext_1 = plaintext;
  len = strlen(ciphertext);
  v5 = &s;
  table = "ncA8DaUPelq*S7Y9q#hLl0T##@XTuXHQpFA&65eaUaY33WigYMXO9y7JtCQU";
  v7 = len;
  do
  {
    v8 = *(_DWORD *)table;
    table += 8;
    v9 = *((_DWORD *)table - 1);
    *(_DWORD *)v5 = v8;
    *((_DWORD *)v5 + 1) = v9;
    v10 = (int)(v5 + 8);
    v5 += 8;
  }
  while ( table != "tCQU" );
  v11 = *(_DWORD *)table;
  v12 = 0;
  v13 = table[4];
  *(_DWORD *)v10 = v11;
  *(_BYTE *)(v10 + 4) = v13;
  i = 0;
  result = strlen(&s);
  v16 = result;
  while ( i < v7 )
  {
    ch = (unsigned __int8)ciphertext_1[i];
    if ( ch - 32 <= (unsigned int)'^' )         // chr(ch) <= 127
    {
      plaintext_1[i] = (ch - (unsigned __int8)*(&v19 + v12 - 68) + 95) % 95 + 32;// &v19 - 68 = &table
      result = (v12 + 1) / v16;
      v12 = (v12 + 1) % v16;
    }
    else                                        // chr(ch) > 127
    {
      plaintext_1[i] = ch;
    }
    ++i;
  }
  return result;
}

我先是通过动态调试来获取到了两个字符串的解密结果:

然后尝试自己实现一个解密函数进行验证:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *table = "ncA8DaUPelq*S7Y9q#hLl0T##@XTuXHQpFA&65eaUaY33WigYMXO9y7JtCQU";

char *vigenere_decrypt(char *ciphertext) {
    int j = 0;
    int len = strlen(ciphertext);
    printf("%d\n", len);
    char plaintext[len];
    for (int i = 0; i < len; i++) {
        char ch = ciphertext[i];
        if ((ch - 32) <= 0x5E) {
            plaintext[i] = (ch - table[j] + 95) % 95 + 32;
            j = (j + 1) % 16;
        } else {
            plaintext[i] = ch;
        }
    }
    plaintext[len] = '\x00';
    return plaintext;
}

int main() {
    char *ciphertext1 = "dV.";
    char *plaintext1 = vigenere_decrypt(ciphertext1);
    printf("%s\n", plaintext1); // url
    char *ciphertext2 = "BQ1$*[w6G_";
    char *plaintext2 = vigenere_decrypt(ciphertext2);
    printf("%s\n", plaintext2); // SmokeyBear
    return 0;
}

当然这里也可以 Hook 这个 so 文件,也可以直接编写代码调用 so 中的函数,条条大路通罗马。最后实现一个调用 Toast 的 html 页面:

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <script type="text/javascript">
      function alicrack() {
        SmokeyBear.showToast();
      }
    </script>
  </head>
  <body>
    Crack EvilAPK_3
    <script type="text/javascript">
      alicrack();
    </script>
  </body>
</html>

在软件中输入对应的地址,成功弹窗:

References

https://xz.aliyun.com/t/383
https://blog.csdn.net/AliMobileSecurity/article/details/53259788
https://yq.aliyun.com/articles/64691
http://pwn4.fun/2017/04/04/Android%E9%80%86%E5%90%91%E4%B9%8B%E8%84%B1%E5%A3%B3/


re android

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

分组密码的工作模式
Android逆向入门(四)