攻防世界 easy-dex解题思路

4 篇文章 2 订阅
4 篇文章 0 订阅

零、前言

这道题包含的知识感觉还挺多的,基本上是解出一步卡一步,然后不停的百度,看文章分析,才慢慢的摸索出来,首先是第一次接触到纯native开发的app这种东西,知道了其AXML大概的写法,以及SO文件中的入口为android_main ,其次是第一次接触到将dex文件写入SO中,在运行时由SO进行释放的概念。最后分析释放出的dex文件时,新接触到twofish加密算法。一环套装一环,是比较有价值的一道题。(但对于我们这种初学者来说也是相当不友好的一道题…)

一、jadx分析apk

先用jadx打开apk文件可以看到,源代码中没有java代码,AXML中显示android:hasCode="false",且下面有个NativeActivity。不明觉厉,寻思着应该和so有关系,随用ida pro打开libnative.so文件开始分析。
请添加图片描述

二、IDA PRO分析SO

打开so查看exports,目前比较能看到函数名的就一个android_main,和一个onCreate函数,打开oncreate发现里面的东西全都看不懂(= _=),随看一下android_main 函数。
请添加图片描述android_main 函数中看到一堆log文件,寻思着有戏,于是分析该函数
请添加图片描述从能看得懂的地方开始分析,可以知道,这个程序的规则是10秒晃动手机100下,然后接下一个循环,循环内应该是主要代码了,随开始分析循环。(整体代码注释请看四、SO代码)
请添加图片描述首先跳到循环的底部,查看挑战成功和失败的逻辑,根据成功的逻辑可以知道,成功后会先解压v3,然后保存到filename中进行加载,最后再删除掉,因此v3中的数据就是压缩后的dex的数据。随跟踪v3的处理流程。
请添加图片描述来到文件的开头,可以看到给变量V3分配了空间,且进行了数据拷贝,在此可以得出数据的大小为248336,数据所在的区域为0x7004->0x7004+0x3CA10这一块区域。
请添加图片描述使用memoryDUmp插件,将该内存的数据dump出来,保存为out.dump,继续跟进v3的处理,查看有没有进一步操作
请添加图片描述看到这两个地方对v3进行了操作,说明有数据加密算法,所以得从加密处向上寻找逻辑
请添加图片描述所有逻辑集中在第145行中的while循环中,循环前面内容应该是判断角度,不是重点,不用深究。继续向下由log的提示可以推出v10为摇晃次数,依次再向上就可以推出v6是开始时间,v14是每摇晃一次后新的摇晃次数
请添加图片描述根据逻辑,只要摇晃角度大于15度,则最终都会进入LABLE_31进行处理,这一段也是加密的核心内容,根据程序一步一步完善注释更改变量名,最终可以得出该加密逻辑为,将数据分为10部分,当摇晃次数达到(9,19,29,39,49,59,69,79,89)的时候对应每一部分的数据与摇晃次数做异或运算,当到达89的时候对最后一部分的数据与89做异或运算。
请添加图片描述由此加密函数分析完毕,可以写解密脚本了,pyton解密脚本如下(一定注意,解密后还有个解压缩的过程,否则文件无法使用):

import zlib

with open("./blog/out.dump", 'rb') as f:
    data = f.read()
    data_len = len(data)
    decode_bytes = [0]*data_len
    for i in range(0, 9):
        per_seg_len = int(len(data) // 10)
        print(i*per_seg_len, (i+1)*per_seg_len)
        print((i *10 + 9))
        start = i*per_seg_len
        while start < (i+1)*per_seg_len:
            decode_bytes[start] =  data[start] ^ (i *10 + 9)
            start += 1
    
    per_seg_len = int(len(data) // 10)
    start = 9*per_seg_len
    print(9*per_seg_len, data_len)
    while start < data_len:
        decode_bytes[start] =  data[start] ^ 89
        start += 1
    
with open("./blog/decode.dex", 'wb') as f:
    f.write(zlib.decompress(bytes(decode_bytes)))

三、jadx分析decode.dex文件

直接将dex拖入jadx会发现很多资源找不到,会以数字的的方式标注,这是因为我们只有dex文件,没有资源文件
请添加图片描述可以反编译原apk文件,将其中的resources.arsc 文件和dex文件一起拖入jadx,则可以使jadx自动解析
请添加图片描述MainActivity主要设置了监听事件,监听事件由a类完成,其中取了输入字符串edit_text 和固定字符串R.string.two_fish 的值,然后使用MainActivity.b函数做运算,得到的返回值与MainActivity.m这一串byteArray做对比,于是乎分析MainActivity.b函数。
请添加图片描述MainActiviy.b调用了b类,把输入的字符串分组,每组16个字节,调用b.a(),传入这16个字节与R.string.two_fish的值进行加密,加密后输出一个byteArray。这个array应该等于MainActivity.m
请添加图片描述可知,b类是一个加密函数,查看b类的算法。。。。算了,看不懂= =,看了网上大佬的解体思路大概是,根据传入的加密字符串R.string.two_fish 中含有关键字towfish,猜测是用了twofish加密算法,而twofish加密算法,是对成加密算法,根据key,把原始数据进行加密,并输出base64编码。在资源文件中可以找到对应的key应该是I have a male fish and a female fish.
请添加图片描述所以,MainActivity.m 的byteArray应该就是加密后输出的base64编码,将其转化为base64后为:iE3y2hEF1izgbVUfGKWQrUCtgFQFop7iEkbmRwWdwsZ1HdQGcPxRVAkWzV/eDC9N,解密python脚本为

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)

通过在线网站进行解密后即可得到flag:qwb{TH3y_Io<e_EACh_OTh3r_FOrEUER}
请添加图片描述

四、SO代码加注释

int __fastcall android_main(_DWORD *a1)
{
  void *v2; // r5
  char *v3; // r10
  int v4; // r2
  int v5; // r1
  time_t v6_start_time; // r8
  int *v7; // r0
  int v8; // r3
  int v9; // r6
  int v10_shake_times; // r4
  float v11; // s0
  int v12; // r0
  int v13; // r5
  int v14_new_shake_times; // r5
  int v15_data_index; // r3
  void *v16; // r0
  int v17_per_data; // r2
  int v18_next_data; // r1
  char *v19_start_index; // r3
  time_t v20; // r5
  int v21; // r8
  Bytef *dest; // [sp+8h] [bp-158h]
  int v24; // [sp+10h] [bp-150h] BYREF
  char v25[4]; // [sp+14h] [bp-14Ch] BYREF
  int v26[13]; // [sp+18h] [bp-148h] BYREF
  uLongf destLen; // [sp+4Ch] [bp-114h] BYREF
  char v28[8]; // [sp+50h] [bp-110h] BYREF
  int v29; // [sp+58h] [bp-108h]
  float v30_angle; // [sp+70h] [bp-F0h]
  char name[4]; // [sp+B8h] [bp-A8h] BYREF
  int v32; // [sp+BCh] [bp-A4h]
  int v33; // [sp+C0h] [bp-A0h]
  int v34; // [sp+C4h] [bp-9Ch]
  int v35; // [sp+C8h] [bp-98h]
  int v36; // [sp+CCh] [bp-94h]
  int v37; // [sp+D0h] [bp-90h]
  int v38; // [sp+D4h] [bp-8Ch]
  int v39; // [sp+D8h] [bp-88h]
  int v40; // [sp+DCh] [bp-84h]
  int v41; // [sp+E0h] [bp-80h]
  __int16 v42; // [sp+E4h] [bp-7Ch]
  char v43; // [sp+E6h] [bp-7Ah]
  char filename[4]; // [sp+E8h] [bp-78h] BYREF
  int v45; // [sp+ECh] [bp-74h]
  int v46; // [sp+F0h] [bp-70h]
  int v47; // [sp+F4h] [bp-6Ch]
  int v48; // [sp+F8h] [bp-68h]
  int v49; // [sp+FCh] [bp-64h]
  int v50; // [sp+100h] [bp-60h]
  int v51; // [sp+104h] [bp-5Ch]
  int v52; // [sp+108h] [bp-58h]
  int v53; // [sp+10Ch] [bp-54h]
  int v54; // [sp+110h] [bp-50h]
  int v55; // [sp+114h] [bp-4Ch]
  int v56; // [sp+118h] [bp-48h]
  char v57; // [sp+11Ch] [bp-44h]
  int v58; // [sp+124h] [bp-3Ch]

  destLen = 0x100000;
  dest = (Bytef *)malloc(0x100000u);
  v2 = off_43A18;                               // off_43A18是一个偏移量,该偏移量储存的值为unk_3CA10,v2是一个指针,所以此处存的应该是unk_3CA10的地址
  v3 = (char *)malloc((size_t)off_43A18);       // 给v3分配off_43A18个字节,即0X3CA10,248336个字节的地址
  qmemcpy(v3, &unk_7004, (size_t)v2);           // 从unk_7004的地址处即0x7004开始拷贝v2个字节(0x3CA10即248336个字节)到v3中,由此可推出数据所在区域为0x7004->0x7004+0x3CA10处
  *(_DWORD *)filename = -1651995345;
  v45 = -2003974520;
  v46 = -1966700387;
  v47 = -2000190330;
  v48 = -2071422265;
  v49 = -947092071;
  v50 = -1920499569;
  v51 = -1936879484;
  v52 = -2138061167;
  v53 = -962950011;
  v54 = -1702328950;
  v55 = -946172774;
  v56 = -376337267;
  v57 = 0;
  *(_DWORD *)name = -1651995194;
  v32 = -2003974520;
  v33 = -1966700387;
  v34 = -2000190330;
  v35 = -2071422265;
  v36 = -947092071;
  v37 = -1920499569;
  v38 = -1936879484;
  v39 = -2138061167;
  v40 = -962950011;
  v41 = -1853059706;
  v43 = 0;
  v4 = 1;
  v42 = -5690;
  do
    filename[v4++] ^= 0xE9u;
  while ( v4 != 53 );
  v5 = 1;
  name[0] = 47;
  do
    name[v5++] ^= 0xE9u;
  while ( v5 != 47 );
  j_app_dummy();
  memset(v26, 0, sizeof(v26));
  *a1 = v26;
  a1[1] = sub_29B8;
  a1[2] = sub_2B90;
  v26[0] = (int)a1;
  v26[1] = ASensorManager_getInstance();
  v26[2] = ASensorManager_getDefaultSensor(v26[1], 1);
  v6_start_time = 0;
  v26[3] = ASensorManager_createEventQueue(v26[1], a1[7], 3, 0, 0);
  v7 = (int *)a1[5];
  if ( v7 )
  {
    v8 = v7[1];
    v9 = v7[2];
    v26[10] = *v7;
    v26[11] = v8;
    v26[12] = v9;
  }
  _android_log_print(4, "FindMyDex", "Can you shake your phone 100 times in 10 seconds?");
  v10_shake_times = 0;
  do
  {
    while ( 1 )
    {
      v12 = 0;
      if ( !v26[4] )
        v12 = -1;
      v13 = ALooper_pollAll(v12, 0, v25, &v24);
      if ( v13 >= 0 )
        break;
      if ( v26[4] )
      {
        v11 = *(float *)&v26[10] + 0.01;
        if ( (float)(*(float *)&v26[10] + 0.01) > 1.0 )
          v11 = 0.0;
        *(float *)&v26[10] = v11;
        sub_2C14(v26);
      }
    }                                           // 第一个while循环结束
    if ( v24 )
      (*(void (__fastcall **)(_DWORD *))(v24 + 8))(a1);
    if ( v13 == 3 && v26[2] )
    {
      while ( 1 )
      {
        do
        {
          if ( ASensorEventQueue_getEvents(v26[3], v28, 1) < 1 )
            goto LABEL_51;
        }
        while ( v29 != 1 );
        if ( (v10_shake_times & 1) != 0 )
        {
          if ( v30_angle >= -15.0 )             // 15.0看着像是个角度,而且摇晃应该有角度判定,所以猜测V30为摇晃角度angle
          {
LABEL_30:
            v14_new_shake_times = v10_shake_times;
            goto LABEL_31;
          }
          if ( v10_shake_times == 1 )           // 当摇晃次数为1的时候初始化开始时间
            v6_start_time = time(0);            // 由此推出v6为摇晃开始时间,start_time
          v14_new_shake_times = v10_shake_times + 1;// 由此推出V14为摇晃后的次数,暂定为new_shake_times
        }
        else
        {
          if ( v30_angle <= 15.0 )
            goto LABEL_30;
          v14_new_shake_times = v10_shake_times + 1;
          if ( v10_shake_times >= 0 )
            _android_log_print(4, "FindMyDex", "Oh yeah~ You Got it~ %d times to go~", 99 - v10_shake_times);// 由此可推出v10为摇晃的次数,shake_times
        }
LABEL_31:
        v10_shake_times = v14_new_shake_times;  // 更新摇晃次数
        if ( (unsigned int)(v14_new_shake_times - 1) <= 0x58 )// 如果摇晃次数<=89,(0x58是16进制,转换为十进制为88)
        {
          v10_shake_times = v14_new_shake_times;// 更新摇晃次数
          v15_data_index = v14_new_shake_times / 10;// V15为int,所以次数是取摇晃次数除以10的整数部分,也就是取值为(0,1,2,3,4,5,6,7,8)
          if ( v14_new_shake_times % 10 == 9 )  // 判断摇晃测试模10是否等于9,也就是摇晃测试的取值为(9,19,29,39,49,59,69,79,89)
          {
            v16 = off_43A18;                    // off_43A18是一个偏移量,该偏移量储存的值为unk_3CA10,v16是一个指针,所以此处存的应该是unk_3CA10的地址
            v17_per_data = (int)off_43A18 / 10; // 将unk_3CA10的地址,也就是0x3CA10转化为int十进制并除以10,即数据长度除以10,把数据10等分,每份数据的长度per_data
            v18_next_data = (v15_data_index + 1) * ((int)off_43A18 / 10);// v15的取值+1,后乘以per_data,由v15的取值可以得出,v18为数据编号+1的数据的起始位置,v15为每份数据的编号
            if ( (int)off_43A18 / 10 * v15_data_index < v18_next_data )// 判断这份数据的起始位置是否小于下一份数据的其实位置(感觉这个条件是恒为真的。。。)
            {
              v19_start_index = &v3[v17_per_data * v15_data_index];// v15_data_index*v14_per_data为当前数据编号下数据的开始位置,因此,v19指向的是当前数据编号开始的位置的地址
              do
              {
                --v17_per_data;
                *v19_start_index++ ^= v14_new_shake_times;// 数据的每一位,与对应摇晃次数做异或运算
              }
              while ( v17_per_data );           // 该循环即对数据的每一位与对应的摇晃次数做或运算
            }
            if ( v14_new_shake_times == 89 )
            {
              while ( v18_next_data < (int)v16 )// 当摇晃次数等于89时,下一段数据就为最后一段数据,判断最后数据的开始位置是否小于数据总长度,即开始位置有没有达到数据的尾部
                v3[v18_next_data++] ^= 0x59u;   // 每一位与无符号的0x59u即89做异或运算
            }
            v10_shake_times = v14_new_shake_times + 1;
          }
        }
        if ( v14_new_shake_times == 100 )
        {
          if ( time(0) - v6_start_time > 9 )
          {
            _android_log_print(4, "FindMyDex", "OH~ You are too slow. Please try again");// 挑战失败
            qmemcpy(v3, &unk_7004, (size_t)off_43A18);
            v10_shake_times = 0;
          }
          else                                  // 挑战成功的逻辑
          {
            v20 = v6_start_time;
            if ( uncompress(dest, &destLen, (const Bytef *)v3, (uLong)off_43A18) )// 解压数据,将v3的内容解压到dest缓冲区中,长度为off_43A18所在的数据,即unk_3CA10的地址,即0x3CA10
              _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);          // 将解压后的文件写入filename中
            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_shake_times = 0x80000000;
            v6_start_time = v20;
          }
        }
      }
    }
LABEL_51:
    ;
  }
  while ( !a1[15] );
  sub_2BDA(v26);
  return _stack_chk_guard - v58;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值