吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 8081|回复: 7
收起左侧

[Android CTF] XCTF攻防世界 easy-dex 解题过程分析

[复制链接]
neilwu 发表于 2020-2-14 20:23
本帖最后由 neilwu 于 2020-2-15 09:53 编辑

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


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

0x02 Java层逻辑分析
image.png

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

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层分析
image.png

ANativeActivity_onCreate

[C++] 纯文本查看 复制代码
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; // [sp+4h] [bp-30h]

  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[7].functions = (const JNINativeInterface *)v8;
  return _stack_chk_guard - *(&attr.__align + 8);
}


其中


[C++] 纯文本查看 复制代码
pthread_create((pthread_t *)v8 + 20, &attr, (void *(*)(void *))sub_325C, v8);

image.png


java_main代码如下

[C++] 纯文本查看 复制代码
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; // [sp+8h] [bp-158h]
  void *outData; // [sp+10h] [bp-150h]
  int outEvents; // [sp+14h] [bp-14Ch]
  int v26; // [sp+18h] [bp-148h]
  ASensorManager *manager; // [sp+1Ch] [bp-144h]
  const ASensor *v28; // [sp+20h] [bp-140h]
  ASensorEventQueue *queue; // [sp+24h] [bp-13Ch]
  int v30; // [sp+28h] [bp-138h]
  float v31; // [sp+40h] [bp-120h]
  int v32; // [sp+44h] [bp-11Ch]
  int v33; // [sp+48h] [bp-118h]
  uLongf destLen; // [sp+4Ch] [bp-114h]
  char v35; // [sp+50h] [bp-110h]
  int v36; // [sp+58h] [bp-108h]
  float v37; // [sp+70h] [bp-F0h]
  int name; // [sp+B8h] [bp-A8h]
  int v39; // [sp+BCh] [bp-A4h]
  int v40; // [sp+C0h] [bp-A0h]
  int v41; // [sp+C4h] [bp-9Ch]
  int v42; // [sp+C8h] [bp-98h]
  int v43; // [sp+CCh] [bp-94h]
  int v44; // [sp+D0h] [bp-90h]
  int v45; // [sp+D4h] [bp-8Ch]
  int v46; // [sp+D8h] [bp-88h]
  int v47; // [sp+DCh] [bp-84h]
  int v48; // [sp+E0h] [bp-80h]
  __int16 v49; // [sp+E4h] [bp-7Ch]
  char v50; // [sp+E6h] [bp-7Ah]
  int filename; // [sp+E8h] [bp-78h]
  int v52; // [sp+ECh] [bp-74h]
  int v53; // [sp+F0h] [bp-70h]
  int v54; // [sp+F4h] [bp-6Ch]
  int v55; // [sp+F8h] [bp-68h]
  int v56; // [sp+FCh] [bp-64h]
  int v57; // [sp+100h] [bp-60h]
  int v58; // [sp+104h] [bp-5Ch]
  int v59; // [sp+108h] [bp-58h]
  int v60; // [sp+10Ch] [bp-54h]
  int v61; // [sp+110h] [bp-50h]
  int v62; // [sp+114h] [bp-4Ch]
  int v63; // [sp+118h] [bp-48h]
  char v64; // [sp+11Ch] [bp-44h]
  int v65; // [sp+124h] [bp-3Ch]

  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[v17 * v15];
              do
              {
                --v17;
                *v19++ ^= v14;
              }
              while ( v17 );
            }
            if ( v14 == 89 )
            {
              while ( v18 < (signed int)v16 )
                v3[v18++] ^= 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掉;


image.png

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



IDA py脚本进行dump

[C++] 纯文本查看 复制代码
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()


解密算法
image.png

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

用Java实现一下解密dex
[Java] 纯文本查看 复制代码
try {
            FileInputStream fis =  new FileInputStream("xx://dump");
            int length = 0x3ca10;
            byte[] b = new byte[length];
            int tempchar;
            int index = 0;
            while((tempchar  = fis.read()) != -1){
                b[index] = (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[v19++] ^= v14;
                        }
                        while (v17>0);
                    }
                    if(v14 == 89){
                        while (v18 < v16){
                            b[v18++] ^= 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[1024];
            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
image.png

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

image.png




由此可见是 明文+key = 密文
image.png


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

[Java] 纯文本查看 复制代码
new String(Base64.getEncoder().encode(m)


image.png

然后找个工具验证一下
image.png

到此获得成获得flag

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

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

免费评分

参与人数 7威望 +3 吾爱币 +18 热心值 +5 收起 理由
q0o0p + 1 我很赞同!
a1046830 + 1 + 1 谢谢@Thanks!
pengxx0109 + 1 + 1 [@Thanks!
AssassinQ + 1 + 1 谢谢@Thanks!
蜗巨 + 1 谢谢@Thanks!
androidhan + 1 + 1 谢谢@Thanks!
qtfreet00 + 3 + 12 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

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
请教下问题
[C] 纯文本查看 复制代码
 do
        {
          if ( ASensorEventQueue_getEvents(v26[3], v28, 1) < 1 )
            goto LABEL_51;
        }
        while ( v29 != 1 );

这个代码的v29什么时候变1,也就是上面代码的v36
wantwill 发表于 2022-11-17 08:45

好帖子,感谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-15 12:56

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表