首页
社区
课程
招聘
[原创]一款最简单的关于动态注册的APP分析
发表于: 2020-5-12 23:06 11876

[原创]一款最简单的关于动态注册的APP分析

2020-5-12 23:06
11876

题目来自 看雪2w班4月第2题

写在前面

本来以为凉凉了,但是最后竟然做出来了,惊奇

描述

找出flag

WriteUp

拿到手,一看360加固,先脱壳,发现程序关键步骤native化了,

再打开so文件一看,动态注册
本来想着动态调试吧,可是还得先过360加固的反调试,算了还是静态分析吧
先看.init.array

可以看到一一进去查看函数伪代码,发现其实最关键的就是一个字符串动态解密的函数

unsigned int str_decode()
{
  unsigned int v0; // r0
  unsigned int v1; // r0
  unsigned int v2; // r0
  unsigned int v3; // r0
  unsigned int v4; // r0
  unsigned int v5; // r0
  unsigned int v6; // r0
  unsigned int v7; // r0
  unsigned int v8; // r0
  unsigned int v9; // r0
  unsigned int v10; // r0
  unsigned int v11; // r0
  unsigned int v12; // r0
  unsigned int result; // r0
  unsigned int v14; // [sp+0h] [bp-38h]
  unsigned int v15; // [sp+4h] [bp-34h]
  unsigned int v16; // [sp+8h] [bp-30h]
  unsigned int v17; // [sp+Ch] [bp-2Ch]
  unsigned int v18; // [sp+10h] [bp-28h]
  unsigned int v19; // [sp+14h] [bp-24h]
  unsigned int v20; // [sp+18h] [bp-20h]
  unsigned int v21; // [sp+1Ch] [bp-1Ch]
  unsigned int v22; // [sp+20h] [bp-18h]
  unsigned int v23; // [sp+24h] [bp-14h]
  unsigned int v24; // [sp+28h] [bp-10h]
  unsigned int v25; // [sp+2Ch] [bp-Ch]
  unsigned int v26; // [sp+30h] [bp-8h]
  unsigned int i; // [sp+34h] [bp-4h]

  i = 0;
  do
  {
    v0 = i;
    ELF[i++] ^= 0x61u;                          // ELF
  }
  while ( v0 < 4 );
  v26 = 0;
  do
  {
    v1 = v26;
    read_maps[v26++] ^= 0xBAu;                  // read /proc/self/maps
  }
  while ( v1 < 0x14 );
  v25 = 0;
  do
  {
    v2 = v25;
    self_maps[v25++] ^= 0xE6u;                  // /proc/self/maps
                                                // 
  }
  while ( v2 < 0xF );
  v24 = 0;
  do
  {
    v3 = v24;
    frida[v24] ^= 0x51u;                        // frida
                                                // 
    ++v24;
  }
  while ( v3 < 5 );
  v23 = 0;
  do
  {
    v4 = v23;
    frida_so_found[v23++] ^= 0x98u;             // found frida.so in memory
  }
  while ( v4 < 0x18 );
  v22 = 0;
  do
  {
    v5 = v22;
    fmt[v22++] ^= 0x5Cu;                        // %x-%lx %4s %lx %*s %*s %s
  }
  while ( v5 < 0x19 );
  v21 = 0;
  do
  {
    v6 = v21;
    end_oat[v21++] ^= 0xA6u;                    // .oat
  }
  while ( v6 < 4 );
  v20 = 0;
  do
  {
    v7 = v20;
    found_frida[v20++] ^= 0x31u;                // found frida in memory
  }
  while ( v7 < 0x15 );
  v19 = 0;
  do
  {
    v8 = v19;
    course[v19++] ^= 0x86u;                     // course
  }
  while ( v8 < 6 );
  v18 = 0;
  do
  {
    v9 = v18;
    check[v18] ^= 0x24u;                        // check
    ++v18;
  }
  while ( v9 < 5 );
  v17 = 0;
  do
  {
    v10 = v17;
    sig[v17] ^= 0xFDu;                          // (Ljava/lang/Object;)Z
    ++v17;
  }
  while ( v10 < 0x15 );
  v16 = 0;
  do
  {
    v11 = v16;
    MainActivity[v16] ^= 0xD1u;                 // com/kanxue/reflectiontest/MainActivity
    ++v16;
  }
  while ( v11 < 0x26 );
  v15 = 0;
  do
  {
    v12 = v15;
    training[v15++] ^= 0x32u;                   // training
  }
  while ( v12 < 8 );
  v14 = 0;
  do
  {
    result = v14;
    kanxue[v14++] ^= 0x18u;                     // kanxue
  }
  while ( result < 6 );
  return result;
}

发现都是一堆异或,没有什么难的算法
直接IDA脚本,一一解密,从解密后的字符串可以看到,这个so存在检测frida的反调试,以及其他不知名的字符串
再之后看.init函数

 

 

实际上最终调用的是创建新线程执行函数sub_E80,跟进去看看

 

 

大概就是检测frida是否注入进程,以及一些elf魔数的检查和是否存在oat文件的检查
如果检测不通过,则kill掉进程
接下来,看JNI_Onload函数

 

 

就是动态注册而已,我们直接修改源码,跟踪动态注册的函数check

 

 

可以观察到check函数地址为0xb39444c9,
再通过cat /proc/13291/maps | grep native-lib命令查看libnative-lib.so的加载基地址为0xb3943000,这里的13291为进程pid

 

 

从而确认check的函数偏移为0x14c9,找到对应函数

 

 

可以发现这个函数做了4件事

  1. 检测输入string长度是否为20

  2. 再次动态注册check函数到,新的native函数偏移为0x1234

  3. 检测输入的string是否以kanxue开头

  4. 调用新的check函数,即sub_1234函数,并传递输入字符串的第六位之后的字符串为参数
    再次跟进sub_1234函数

可以看到同上个函数一样,这个函数也做了四件事

  1. 检测输入string长度是否为14

  2. 再次动态注册check函数到,新的native函数偏移为0x1148

  3. 检测输入的string是否以training开头

  4. 调用新的check函数,即sub_1148函数,并传递输入字符串的第8位之后的字符串为参数

最后再次跟进sub_1148函数

 

 

这个函数只是检测了最后的字符串长度是否为6,并且将之与字符串course对比

 

最终,拿到flag为kanxuetrainingcourse.
验证。

 

 

这个时候再看log,会发现确实动态注册了三次check 函数,且注册在了不同的位置

 

后记

这个程序貌似存在一个bug,就是我第一次验证成功后,不退出程序,再次输入同样的flag,这个时候再check就不对了,而输入course就对了,估计是多次动态注册导致再次check时只走了最后sub_1148函数的原因。


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
免费 4
支持
分享
最新回复 (13)
雪    币: 520
活跃值: (945)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢大佬分享,很好的学习案例,能放下apk下载链接吗,十分感谢
2020-5-13 11:01
0
雪    币: 2153
活跃值: (5347)
能力值: ( LV8,RANK:146 )
在线值:
发帖
回帖
粉丝
3
_air 感谢大佬分享,很好的学习案例,能放下apk下载链接吗,十分感谢

好的

上传的附件:
2020-5-14 09:55
0
雪    币: 1143
活跃值: (2360)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
我们直接修改源码,跟踪动态注册的函数check。能具体说一下是怎么找函数入口吗
2020-5-15 10:20
0
雪    币: 2153
活跃值: (5347)
能力值: ( LV8,RANK:146 )
在线值:
发帖
回帖
粉丝
5
yuhan梓 我们直接修改源码,跟踪动态注册的函数check。能具体说一下是怎么找函数入口吗
去跟踪RegisterNatives的函数实现,改源码,打log
2020-5-15 12:03
0
雪    币: 1084
活跃值: (340)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
7
拆包的时候必须加上-r(忽略资源) 否则重打包报错 楼主打包的时候应该也是加上-r了吧? 必须要加-r才行?
2020-5-15 23:30
0
雪    币: 2153
活跃值: (5347)
能力值: ( LV8,RANK:146 )
在线值:
发帖
回帖
粉丝
8
东京不热 拆包的时候必须加上-r(忽略资源) 否则重打包报错 楼主打包的时候应该也是加上-r了吧? 必须要加-r才行?
没有重打包。。。
2020-5-16 09:26
0
雪    币: 210
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
求ida脚本,谢谢大佬
2020-6-1 06:35
0
雪    币: 3212
活跃值: (703)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
Java.perform(function() {
    var clazz = Java.use('com.kanxue.reflectiontest.MainActivity');
    clazz.check.overload("java.lang.Object").implementation = function(x) {

        //

        console.log(this.check(x))
        return true
    }
});
2020-7-29 16:15
0
雪    币: 1671
活跃值: (2093)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
11
360的壳是用脱壳机还是直接frida砸壳,直接砸壳拖出来的dex不完整吧
2020-8-14 14:23
0
雪    币: 2924
活跃值: (4921)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
12
想问一下楼主的IDA是怎么识别出来RegisterNatives等一系列函数的?
2020-8-18 02:28
0
雪    币:
活跃值: (412)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
想问问怎么主动给这个应用读写手机存储的权限啊?如果应用本身没有申请的话在设置里面也给不了读写权限吧。没有权限fart怎么脱壳呢?
2021-4-1 11:44
0
雪    币: 2153
活跃值: (5347)
能力值: ( LV8,RANK:146 )
在线值:
发帖
回帖
粉丝
14
wx_Simba 想问问怎么主动给这个应用读写手机存储的权限啊?如果应用本身没有申请的话在设置里面也给不了读写权限吧。没有权限fart怎么脱壳呢?
编译一个保存到私有目录的fart版本就行了
2021-4-1 12:43
0
游客
登录 | 注册 方可回帖
返回
//