neilwu 发表于 2020-2-14 20:23

XCTF攻防世界 easy-dex 解题过程分析

本帖最后由 neilwu 于 2020-2-15 09:53 编辑

0x00 前言
easy-dex.APK 是xctf moblie 新手区第11题,获取Flag的过程比较有学习价值,固记录一次Crack过程。


0x01easy-dex app 运行界面
运行界面没有任何组件显示,只是变换界面颜色。

0x02 Java层逻辑分析


apk中没有dex,找到dex应该就可以找到flag


android.app.NativeActivity Android的ndk开发,dex应该是藏在了so中。

Android NativeActivity 原理简单介绍一下,后面的分析需要用到;

1、 NativeActivity是Android进行纯NDK开发使用的,和普通的Activity没有区别;
2、 NativeActivity通过native_app_glu 启动c++线程, 然后创建线程在应用程序的.so动态链接库中寻找函数 __ANativeActivity_onCreate(ANativeActivity, void* size_t),并调用 android_main(android_app* pApplication)函数;
3、C++的入口函数就是android_main

0x03 Native层分析


ANativeActivity_onCreate

int __fastcall ANativeActivity_onCreate(_JNIEnv *a1, int a2, size_t a3)
{
_JNIEnv *v3; // r8
int v4; // r10
size_t v5; // r9
JNINativeInterface *v6; // r0
__int64 v7; // r2
char *v8; // r5
void *v9; // r0
int *v10; // r0
char *v11; // r0
pthread_attr_t attr; //

v3 = a1;
v4 = a2;
v5 = a3;
v6 = (JNINativeInterface *)a1->functions;
v6->DefineClass = (jclass (*)(JNIEnv *, const char *, jobject, const jbyte *, jsize))sub_3000;
v6->reserved0 = sub_305A;
v6->reserved1 = sub_3062;
v6->reserved2 = sub_306A;
v6->reserved3 = sub_30BE;
HIDWORD(v7) = sub_30EE;
v6->GetVersion = (jint (*)(JNIEnv *))sub_30C6;
LODWORD(v7) = sub_30DE;
v6->ThrowNew = (jint (*)(JNIEnv *, jclass, const char *))sub_30CE;
v6->ExceptionOccurred = (jthrowable (*)(JNIEnv *))sub_30D6;
*(_QWORD *)&v6->FindClass = v7;
v6->GetSuperclass = (jclass (*)(JNIEnv *, jclass))sub_30F4;
v6->IsAssignableFrom = (jboolean (*)(JNIEnv *, jclass, jclass))sub_30FC;
v6->ToReflectedField = (jobject (*)(JNIEnv *, jclass, jfieldID, jboolean))sub_3102;
v8 = (char *)malloc(0x94u);
_aeabi_memclr4();
*((_DWORD *)v8 + 3) = v3;
pthread_mutex_init((pthread_mutex_t *)(v8 + 64), 0);
pthread_cond_init((pthread_cond_t *)(v8 + 68), 0);
if ( v4 )
{
    v9 = malloc(v5);
    *((_DWORD *)v8 + 5) = v9;
    *((_DWORD *)v8 + 6) = v5;
    _aeabi_memcpy(v9, v4, v5);
}
if ( pipe(&attr.__align + 6) )
{
    v10 = (int *)_errno();
    v11 = strerror(*v10);
    _android_log_print(6, "threaded_app", "could not create pipe: %s", v11);
    v8 = 0;
}
else
{
    *((_QWORD *)v8 + 9) = *((_QWORD *)&attr.__align + 3);
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, 1);
    pthread_create((pthread_t *)v8 + 20, &attr, (void *(*)(void *))sub_325C, v8);
    pthread_mutex_lock((pthread_mutex_t *)(v8 + 64));
    while ( !*((_DWORD *)v8 + 27) )
      pthread_cond_wait((pthread_cond_t *)(v8 + 68), (pthread_mutex_t *)(v8 + 64));
    pthread_mutex_unlock((pthread_mutex_t *)(v8 + 64));
}
v3.functions = (const JNINativeInterface *)v8;
return _stack_chk_guard - *(&attr.__align + 8);
}

其中

pthread_create((pthread_t *)v8 + 20, &attr, (void *(*)(void *))sub_325C, v8);



java_main代码如下

int __fastcall android_main(int a1)
{
int v1; // r11
void *v2; // r5
Bytef *v3; // r10
signed int v4; // r2
signed int v5; // r1
time_t v6; // r8
int v7; // r0
int v8; // r3
int v9; // r6
int v10; // r4
float v11; // s0
int v12; // r0
int v13; // r5
int v14; // r5
int v15; // r3
void *v16; // r0
int v17; // r2
int v18; // r1
Bytef *v19; // r3
time_t v20; // r5
int v21; // r8
Bytef *dest; //
void *outData; //
int outEvents; //
int v26; //
ASensorManager *manager; //
const ASensor *v28; //
ASensorEventQueue *queue; //
int v30; //
float v31; //
int v32; //
int v33; //
uLongf destLen; //
char v35; //
int v36; //
float v37; //
int name; //
int v39; //
int v40; //
int v41; //
int v42; //
int v43; //
int v44; //
int v45; //
int v46; //
int v47; //
int v48; //
__int16 v49; //
char v50; //
int filename; //
int v52; //
int v53; //
int v54; //
int v55; //
int v56; //
int v57; //
int v58; //
int v59; //
int v60; //
int v61; //
int v62; //
int v63; //
char v64; //
int v65; //

v1 = a1;
destLen = 0x100000;
dest = (Bytef *)malloc(0x100000u);
v2 = off_43A18;
v3 = (Bytef *)malloc((size_t)off_43A18);
_aeabi_memcpy(v3, &unk_7004, v2);
filename = -1651995194;
v52 = -2003974520;
v53 = -1966700387;
v54 = -2000190330;
v55 = -2071422265;
v56 = -947092071;
v57 = -1920499569;
v58 = -1936879484;
v59 = -2138061167;
v60 = -962950011;
v61 = -1702328950;
v62 = -946172774;
v63 = -376337267;
v64 = 0;
name = -1651995194;
v39 = -2003974520;
v40 = -1966700387;
v41 = -2000190330;
v42 = -2071422265;
v43 = -947092071;
v44 = -1920499569;
v45 = -1936879484;
v46 = -2138061167;
v47 = -962950011;
v48 = -1853059706;
v50 = 0;
v4 = 1;
v49 = -5690;
LOBYTE(filename) = 47;
do
    *((_BYTE *)&filename + v4++) ^= 0xE9u;
while ( v4 != 53 );
v5 = 1;
LOBYTE(name) = 47;
do
    *((_BYTE *)&name + v5++) ^= 0xE9u;
while ( v5 != 47 );
j_app_dummy();
_aeabi_memclr8(&v26, 52);
*(_DWORD *)v1 = &v26;
*(_DWORD *)(v1 + 4) = sub_29B8;
*(_DWORD *)(v1 + 8) = &sub_2B90;
v26 = v1;
manager = ASensorManager_getInstance();
v28 = ASensorManager_getDefaultSensor(manager, 1);
v6 = 0;
queue = ASensorManager_createEventQueue(manager, *(ALooper **)(v1 + 28), 3, 0, 0);
v7 = *(_DWORD *)(v1 + 20);
if ( v7 )
{
    v8 = *(_DWORD *)(v7 + 4);
    v9 = *(_DWORD *)(v7 + 8);
    v31 = *(float *)v7;
    v32 = v8;
    v33 = v9;
}
_android_log_print(4, "FindMyDex", "Can you shake your phone 100 times in 10 seconds?");
v10 = 0;
do
{
    while ( 1 )
    {
      v12 = 0;
      if ( !v30 )
      v12 = -1;
      v13 = ALooper_pollAll(v12, 0, &outEvents, &outData);
      if ( v13 >= 0 )
      break;
      if ( v30 )
      {
      v11 = v31 + 0.01;
      if ( (float)(v31 + 0.01) > 1.0 )
          v11 = 0.0;
      v31 = v11;
      sub_2C14(&v26);
      }
    }
    if ( outData )
      (*((void (__fastcall **)(int))outData + 2))(v1);
    if ( v13 == 3 && v28 )
    {
      while ( 1 )
      {
      do
      {
          if ( ASensorEventQueue_getEvents(queue, (ASensorEvent *)&v35, 1u) < 1 )
            goto LABEL_51;
      }
      while ( v36 != 1 );
      if ( v10 & 1 )
      {
          if ( v37 >= -15.0 )
          {
LABEL_30:
            v14 = v10;
            goto LABEL_31;
          }
          if ( v10 == 1 )
            v6 = time(0);
          v14 = v10 + 1;
      }
      else
      {
          if ( v37 <= 15.0 )
            goto LABEL_30;
          v14 = v10 + 1;
          if ( v10 >= 0 )
            _android_log_print(4, "FindMyDex", "Oh yeah~ You Got it~ %d times to go~", 99 - v10);
      }
LABEL_31:
      v10 = v14;
      if ( (unsigned int)(v14 - 1) <= 88 )
      {
          v10 = v14;
          v15 = v14 / 10;
          if ( v14 % 10 == 9 )
          {
            v16 = off_43A18;
            v17 = (signed int)off_43A18 / 10;
            v18 = (v15 + 1) * ((signed int)off_43A18 / 10);
            if ( (signed int)off_43A18 / 10 * v15 < v18 )
            {
            v19 = &v3;
            do
            {
                --v17;
                *v19++ ^= v14;
            }
            while ( v17 );
            }
            if ( v14 == 89 )
            {
            while ( v18 < (signed int)v16 )
                v3 ^= 0x59u;
            }
            v10 = v14 + 1;
          }
      }
      if ( v14 == 100 )
      {
          if ( time(0) - v6 > 9 )
          {
            _android_log_print(4, "FindMyDex", "OH~ You are too slow. Please try again");
            _aeabi_memcpy(v3, &unk_7004, off_43A18);
            v10 = 0;
          }
          else
          {
            v20 = v6;
            if ( uncompress(dest, &destLen, v3, (uLong)off_43A18) )
            _android_log_print(5, "FindMyDex", "Dangerous operation detected.");
            v21 = open((const char *)&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((const char *)&name, 0) && mkdir((const char *)&name, 0x1FFu) )
            _android_log_print(5, "FindMyDex", "Something wrong with the permission..");
            sub_2368(v1);
            remove((const char *)&filename);
            _android_log_print(4, "FindMyDex", "Congratulations!! You made it!");
            sub_2250(v1);
            v10 = 2147483648;
            v6 = v20;
          }
      }
      }
    }
LABEL_51:
    ;
}
while ( !*(_DWORD *)(v1 + 60) );
sub_2BDA(&v26);
return _stack_chk_guard - v65;
}

代码整体逆向下来,思路如下
1、读取字节流,通过解密后进行解压缩获得dex;
2、手机在1s中摇100次,会在/data/data/com.a.sample.findmydex/files 目录下生成 class.dex和odex,然后remove掉;




加密的dex以字节流的方式写在.data中,起始位置是 0x7004长度是 0x3ca10



用ida py脚本进行dump

import idaapi
start_address=0x7004
data_length=0x3ca10
data=idaapi.get_bytes(start_address, data_length)
fp = open('dump', 'wb')
fp.write(data)
fp.close()

解密算法


解密->解压缩->还原dex->删除

用Java实现一下解密dex
try {
            FileInputStream fis =new FileInputStream("xx://dump");
            int length = 0x3ca10;
            byte[] b = new byte;
            int tempchar;
            int index = 0;
            while((tempchar= fis.read()) != -1){
                b = (byte) tempchar;
                index++;
            }
            int v15,v16,v17,v18,v19;
            for(int v14=0;v14<90;v14++){
                v15 = v14 / 10;
                if(v14 % 10 == 9){
                  v16 = length;
                  v17 = length / 10;
                  v18 = (v15 + 1) * (length / 10);
                  if(length / 10 * v15 < v18){
                        v19 = v17 * v15;
                        do{
                            --v17;
                            b ^= v14;
                        }
                        while (v17>0);
                  }
                  if(v14 == 89){
                        while (v18 < v16){
                            b ^= 89;
                        }
                  }
                }
            }

            fis.close();

            FileOutputStream fos = new FileOutputStream("xx://class.dex");
            for(byte ch : unjzlib(b)){
                fos.write(ch);
            }
            fos.flush();
            fos.close();

      } catch (IOException e) {
            e.printStackTrace();
      }

    }

    public static byte[] unjzlib(byte[] object) {
      byte[] data = null;
      try {
            ByteArrayInputStream in = new ByteArrayInputStream(object);
            ZInputStream zIn = new ZInputStream(in);
            byte[] buf = new byte;
            int num = -1;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            while ((num = zIn.read(buf, 0, buf.length)) != -1) {
                baos.write(buf, 0, num);
            }
            data = baos.toByteArray();
            baos.flush();
            baos.close();
            zIn.close();
            in.close();

      } catch (IOException e) {
            e.printStackTrace();
      }
      return data;
    }


获得的dex


拿到dex就可以开始搞一下flag了







由此可见是 明文+key = 密文



由此可以看到 应该是使用twofish算法
对m进行一下转义

new String(Base64.getEncoder().encode(m)



然后找个工具验证一下


到此获得成获得flag

0x04 总结
这道题主要是考察了Anddroid NativeActivity的理解,记录一下过程,大佬勿喷。

我在看雪发过一个类似的题目解题过程,题目几乎是一样的
https://bbs.pediy.com/thread-257289.htm

Hmily 发表于 2020-2-14 22:53

格式太乱了,编辑下吧。

neilwu 发表于 2020-2-15 09:12

为啥图全挂了?我重新编辑一下吧

neilwu 发表于 2020-2-15 09:56

Hmily 发表于 2020-2-14 22:53
格式太乱了,编辑下吧。

版主 我重新编辑了一下 我看是正常了 你看看

5ud0 发表于 2020-8-19 16:52

请问解密dex的Java代码中ZipInputStream是什么类

neilwu 发表于 2020-8-19 17:49

5ud0 发表于 2020-8-19 16:52
请问解密dex的Java代码中ZipInputStream是什么类

jzlib-1.1.3

川木 发表于 2021-5-15 16:53

还是不是很懂T^T

18368348237 发表于 2022-9-6 11:30

请教下问题 do
      {
          if ( ASensorEventQueue_getEvents(v26, v28, 1) < 1 )
            goto LABEL_51;
      }
      while ( v29 != 1 );
这个代码的v29什么时候变1,也就是上面代码的v36

wantwill 发表于 2022-11-17 08:45


好帖子,感谢分享
页: [1]
查看完整版本: XCTF攻防世界 easy-dex 解题过程分析