首页
社区
课程
招聘
[原创]第七题:东北奇闻
发表于: 2019-12-17 22:51 5155

[原创]第七题:东北奇闻

2019-12-17 22:51
5155
题目是个安卓app,没壳,于是直接解压。文件有一个dex,一个so。
静态分析
java层:
dex2jar+jd-gui打开,直接看MainActivity,发现继承了一个AppC0mpatActivity,OnClick点击调用了native函数ShowAssist
public void onClick(View paramAnonymousView)
      {
        if ((localEditText.getText() == null) || (TextUtils.isEmpty(localEditText.getText().toString())) || (MainActivity.this.showAssist(paramBundle))) {
          Toast.makeText(MainActivity.this, "null", 1).show();
        }
      }
输入错误什么也没发生,并且可以多次反复输入。
public void onClick(View paramAnonymousView)
      {
        if ((localEditText.getText() == null) || (TextUtils.isEmpty(localEditText.getText().toString())) || (MainActivity.this.showAssist(paramBundle))) {
          Toast.makeText(MainActivity.this, "null", 1).show();
        }
      }
输入错误什么也没发生,并且可以多次反复输入。
native层:
so为32位ELF,用ida打开,方法里有一个JNIOnload和一个StringFromJNI(看起来像示例项目自带),直接看函数有点奇怪
void __noreturn JNI_OnLoad()
{
  int v0; // r2
  v0 = dword_F3402F04;
  __dmb(0xBu);
  JUMPOUT(__CS__, *(&off_F3402E50 + (v0 == 0)));
}

没找到init段,不确定是否有壳,决定直接动态调试。
void __noreturn JNI_OnLoad()
{
  int v0; // r2
  v0 = dword_F3402F04;
  __dmb(0xBu);
  JUMPOUT(__CS__, *(&off_F3402E50 + (v0 == 0)));
}

没找到init段,不确定是否有壳,决定直接动态调试。
动态分析
使用xposed插件BDOpener强制开启debug,然后用这篇文章的方法动态调试,ida开着so文件断在JNIOnload开头,跟了一下,发现只是在第一次调用函数的时候进入上面图中每次两个函数里下面的分支解密字符串,之后进入正式代码,恰好是上面的分支。虽然字符串是加密了,但是没壳也没反调试,所以只要在字符串全解密完后看后面的代码就行。
定位关键代码:
不想看JNIOnload,偷个懒,用Frida启动程序并Frida hook Android so RegisterNatives,拿到ShowAssist函数地址,然后关闭Frida换成ida附加,找到注册的native方法地址,下断点,随便输入个字符串,顺着处理逻辑走。
因为是第一次执行,过程中解密了很多字符串,里面有一个"Correct flag is %s",应该是输入正确的显示。
并且解密出了这样一段字符串:
68dd8a0f7065609e
3106fb2bb1059423
e80fb1347318ffeb
83b8a074a7e6c9cf
d9b426919547bcfc
d9b426919547bcfc
d9b426919547bcfc
...后面全部一样,共25hang
程序使用JNIENV方法获取输入框中字符串后,进入正式处理。过程中又解密了很多数据,最后
68dd8a0f7065609e
3106fb2bb1059423
e80fb1347318ffeb
83b8a074a7e6c9cf
d9b426919547bcfc
d9b426919547bcfc
d9b426919547bcfc
...后面全部一样,共25hang
程序使用JNIENV方法获取输入框中字符串后,进入正式处理。过程中又解密了很多数据,最后

输入的字符串被复制到栈中,长度最长为200,并且用malloc申请了0x84大小的内存。接下来很长时间都是各种数据处理,填充了这0x84的数据,但是似乎一直没用到输入。
于是找到上面输入成功的字符串,从调用倒过来,最后找到了真正进行加密解密处理的地方:
每次取出8个字节数据进行处理

最后用printf格式化为"%2.x"和之前解密出的一大段16进制字符串进行比较

看上面的数据显然也是有规律了,后面几行完全相同,应该是全0的加密结果。真正的数据只有前四行,也就是输入在32字节以内。
显然前后解密互不影响,是以8字节为单位的ECB模式,只要对8字节数据进行爆破建一个表就可以了,唯一的问题是8字节有2^64种可能。
于是看单独解密代码如下:

dw0和dw1分别为前四个字节和后四个字节,本来读取的DWORD因为小端序的原因高地址的字节应该在左边,但是进入函数前有一个
.text:F33DFBE6                 REV.W           R8, R5
把寄存器按照字节反过来,于是左边是输入的低位,右边是输入的高位。
.text:F33DFBE6                 REV.W           R8, R5
把寄存器按照字节反过来,于是左边是输入的低位,右边是输入的高位。
T1~T4都是前面解密的数据表,按照16位int数组来分,都是256位。T0是前面alloc的0x84的内存。
经过多次测试,T1~T4在首次执行时解密一次之后就不会再变,T0虽然每次都要重新生成,但内容都不变,与输入无关,所以先把几个表复制出来备用。
算法分析
因为不能爆破,只能尝试找规律了。仔细观察可以发现:
每隔一行就有一个ROR4,并且都是从T0中取数据,并使用了前面的一个变量。同时,每次从T0中取数据的索引+4,一共12次,从T0中取出的数据和输入没有关系,只和次数有关。再看对变量的使用,从dw1,v9,v11,v13...三个为一组,分别是+,^,-三种循环使用。
把这个操作定义为函数R(i,x),i为次数,从0~11变化,x是使用的变量,R的定义有i和i%3可以确定,知道i的情况下R大概是可逆的。
接着看除了ROR4以外的部分,分别从四个表中取数据,使用了两个变量。例如v10由dw0和v8决定。其中v8由刚才定义的R返回,并且被用来作为从表中取数据的索引,而dw0只用来异或。后面几次也是这样,总是使用R的返回值来从T1~T4中取数据运算后与另一个值异或。

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2019-12-17 23:59 被mb_ibocelll编辑 ,原因: 还没写完不小心就发出去了
上传的附件:
收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//