这道题必须在我的做题生涯里拥有姓名;

前言

本来想试水一下看雪CTF的Android题,结果就把自己试进去了,完全无法自拔;

这里知识有个断层,就是IDAPython的使用,怎么把那个缓冲区导出来的问题,搜了一下相关问题少得可怜,就改手动输入了;

相关工具

  • jadx
  • IDA
  • python
  • 草稿纸*

过程

静态分析

首先打开app后看到我们只需要输入密码就好了,然后打开jadx开始分析
p1

打开MainActivity之后,看到这里,以为直接把上面用户名倒着写就完事了,结果error了,在MainActivity里也没看到有弹出error的代码啊,遂放弃,以为又是什么高深的操作,比如在JNI里Toast之类的,发现并不是啊,这里甚至连load动态链接库的代码都没有啊,这题出的太迷了,看别人的wp才发现有个坑人的地方

AppCompatActivity,才是正确的写法,而这道题里
p2

这个i真的是从我好粗一个心的缝隙里溜掉了啊

找到父类,发现改写了onStart方法,JNI的注册和函数声明也有了,error也看到了,果然什么都在里面

重点在这边
p3

在上面也有声明
p4

所以需要逆一下这个eq函数,先拿IDA打开看看函数结构,打开Function Window
p5

第一眼发现重点在datadiv_devodexxxx这个函数和JNI_Onload,没有发现类似Java_包名_函数名之类的函数,应该是在函数内部使用了动态注册,首先看看datadiv_decodexxxx这个函数

注册了三个变量,应该是后面需要用到的,这里重命名为str1,str2,str3

打开JNI_Onload,按照惯例将vXX + YY这样的变量,选中vXX,按Y键,将值改为Env*,再Force call type,显示如下图
p6

FindClass,RegisterNatives,应该就是动态注册了,进入JNINativeMethod结构,找到第三个地址,即Native函数对应的地址

双击进入F5反编译一下200多行,看来慢慢调吧,第一次这样一行一行看反编译代码,看到其他WP里的神操作真的是服他们是怎么做到的,做不到他们那样就一行一行慢慢看,将算法逆出来然后暴力破解2333;

在这里开始动态调试

动态调试

首先在.datadiv_decodexxxx的首行和eq函数首行加上断点

IDA连上开始操作

首先在.datadiv_decodexxxx中,将三个str导出,双击进入DCB部分,手动输入ipython

v0 = 0;
do
{
    str1[v0] ^= 0xA5u;
    ++v0;
}
while ( v0 != 37 );

v1 = 0;
do
    str2[v1++] ^= 0xA5u;
while ( v1 != 66 );

v2 = 0;
do
    str3[v2++] ^= 0x84u;
while ( v2 != 42 );

第一个依次与0xA5异或,第二个也一样,第三个依次与0x84异或,得到三个字符串
在后面调试eq函数的时候就会用到第一二个字符串

这里放一下结果

str1 = '650f909c-7217-3647-9331-c82df8b98e98'
str2 = "!:#$%&()+-*/`~_[]{}?<>,.@^abcdefghijklmnopqrstuvwxyz0123456789\\';"
str3 = 'android/support/v7/app/AppCompiatActivity'

下面到调试eq函数的阶段了,前面输入的密码有点短,最后直接尝试了一下输入20位,这下挺全的了

这里一下输入20个 0

int __fastcall sub_CBA7B784(JNIEnv *a1, int a2, void *a3)
{
  size_t str1_len; // r10
  unsigned __int8 *str1_buf; // r6
  _BYTE *str1_buf_1; // r8
  _BYTE *str1_buf_2; // r11
  signed int v7; // r0
  size_t counter; // r2
  char *v9; // r1
  int v10; // r3
  int v11; // r1
  unsigned int v12; // r2
  int v13; // r3
  int v14; // r0
  int v15; // r4
  unsigned __int8 v16; // r0
  _BYTE *v17; // r3
  _BYTE *v18; // r5
  char *v19; // r4
  int v20; // r5
  int v21; // r1
  int ix; // r0
  signed int v23; // r1
  int v24; // r2
  size_t input_len; // r0
  unsigned int v26; // r8
  unsigned int str1_buf_2_index3_v; // r5
  _BYTE *xor_final_result; // r0
  int v29; // r3
  int counter_in_256; // r10
  unsigned int v31; // r2
  int v32; // r12
  bool v33; // zf
  _BYTE *v34; // r1
  bool v35; // zf
  int v36; // r3
  int v37; // r1
  unsigned __int8 xor_result; // r11
  unsigned int v39; // lr
  char v40; // r1
  char *v41; // r2
  int v42; // t1
  unsigned int v44; // [sp+4h] [bp-234h]
  unsigned int v45; // [sp+8h] [bp-230h]
  unsigned int v46; // [sp+10h] [bp-228h]
  char *input; // [sp+14h] [bp-224h]
  char loc_buf100[256]; // [sp+18h] [bp-220h]
  char str1_buf_2_cp[256]; // [sp+118h] [bp-120h]
  int v50; // [sp+218h] [bp-20h]


  input = (char *)(*a1)->GetStringUTFChars(a1, a3, 0);           // F8单步执行到这里可以发现我的输入就在这里
  str1_len = strlen(str1);                                       // 取str1的长   度
  str1_buf = (unsigned __int8 *)malloc(str1_len);                // 一下是去一个buf
  str1_buf_1 = malloc(str1_len);
  str1_buf_2 = malloc(str1_len);
  _aeabi_memclr(str1_buf, str1_len);
  _aeabi_memclr(str1_buf_1, str1_len);
  _aeabi_memclr(str1_buf_2, str1_len);
  if ( str1_len )
  {
    v7 = 0;
    counter = str1_len;
    v9 = str1;
    do
    {
      v10 = (unsigned __int8)*v9++;                              
      if ( v10 != 45 )
        str1_buf_1[v7++] = v10;                            // 把str1又放进     str1_buf_1中,这里!=45,因为45对应的是'-',这就是将-去掉然后把其余部分加入str1_buf_1
      --counter;
    }
    while ( counter );
    if ( v7 >= 1 )
    {
      v11 = v7 - 1;
      v12 = -8;
      v13 = 0;
      v14 = 0;
      do
      {
        if ( (v13 | (v12 >> 2)) > 3 )
        {
          v15 = v14;
        }
        else
        {
          v15 = v14 + 1;
          str1_buf[v14] = 45;
        }
        v16 = str1_buf_1[v11--];
        v13 += 0x40000000;
        str1_buf[v15] = v16;
        ++v12;
        v14 = v15 + 1;
      }
      while ( v11 != -1 );
      if ( v15 >= 0 )
      {
        v17 = str1_buf_2;
        while ( 1 )
        {
          v18 = (_BYTE *)*str1_buf;
          if ( (unsigned __int8)((_BYTE)v18 - 97) <= 5u )
            break;
          if ( (unsigned __int8)((_BYTE)v18 - 48) <= 9u )
          {
            v18 = (char *)&unk_CBA7D3DE + (_DWORD)v18 - 48;
            goto LABEL_18;
          }
LABEL_19:
          *v17++ = (_BYTE)v18;
          --v14;
          ++str1_buf;
          if ( !v14 )
            goto LABEL_20;
        }
        v18 = (char *)&unk_CBA7D3D8 + (_DWORD)v18 - 97;
LABEL_18:
        LOBYTE(v18) = *v18;
        goto LABEL_19;
      }
    }
  }
LABEL_20:
  _aeabi_memcpy8(loc_buf100, &unk_CBA7D3E8, 256);
  v19 = str1_buf_2_cp;
  v20 = 0;
  do
  {
    sub_CBA7BD20(v20, str1_len);
    str1_buf_2_cp[v20++] = str1_buf_2[v21];
  }
  while ( v20 != 256 );
  ix = (unsigned __int8)(str1_buf_2_cp[0] - 41);
  loc_buf100[0] = loc_buf100[ix];
  loc_buf100[ix] = -41;
  v23 = 1;
  do
  {
    v24 = (unsigned __int8)loc_buf100[v23];
    ix = (ix + (unsigned __int8)str1_buf_2_cp[v23] + v24) % 256;
    loc_buf100[v23++] = loc_buf100[ix];
    loc_buf100[ix] = v24;
  }
  while ( v23 != 256 );
---------------------------------------------------------------------------------------------------------   这是个分割线以上的部分只是构建了个表,后面才是真正的分析
  input_len = strlen(input);                               // 取我输入的长度,即20
  v26 = input_len;
  str1_buf_2_index3_v = (unsigned __int8)str1_buf_2[3];
  v45 = 8 * (3 - -3 * (input_len / 3));
  v44 = str1_buf_2_index3_v + v45 / 6;
  xor_final_result = malloc(v44 + 1);
  if ( v26 )
  {
    counter_in_256 = 0;
    v31 = 0;
    v32 = 0;
    v46 = str1_buf_2_index3_v;
    do
    {
      counter_in_256 = (counter_in_256 + 1) % 256;
      v37 = (unsigned __int8)loc_buf100[counter_in_256];
      v32 = (v32 + v37) % 256;
      loc_buf100[counter_in_256] = loc_buf100[v32];
      loc_buf100[v32] = v37;
      v19 = (char *)(unsigned __int8)loc_buf100[counter_in_256];


    xor_result = loc_buf100[(unsigned __int8)(v37 + (_BYTE)v19)] ^ input[v31];
      if ( v31 && (v29 = 2863311531u * (unsigned __int64)v31 >> 32, v39 = 3 * (v31 / 3), v39 != v31) )
      {
        v33 = v31 == 1;
        if ( v31 != 1 )
          v33 = v39 + 1 == v31;
        if ( v33 )
        {
          v34 = str2;
          xor_final_result[v46 + v31] = str2[(unsigned __int8)xor_final_result[v46 + v31] | ((unsigned int)xor_result >> 4)];
          v19 = &xor_final_result[v46 + v31];
          v29 = 4 * xor_result & 0x3C;
          v19[1] = v29;
          if ( v31 + 1 >= v26 )
            goto LABEL_53;
        }
        else
        {
          v35 = v31 == 2;
          if ( v31 != 2 )
            v35 = v39 + 2 == v31;
          if ( v35 )
          {
            v19 = (char *)(xor_result & 0xC0);
            v36 = v46++ + v31;
            xor_final_result[v36] = str2[(unsigned __int8)xor_final_result[v36] | ((unsigned int)v19 >> 6)] ^ 0xF;
            v29 = (int)&xor_final_result[v36];
            *(_BYTE *)(v29 + 1) = str2[xor_result & 0x3F];
          }
        }
      }
      else
      {
        xor_final_result[v46 + v31] = str2[(unsigned int)xor_result >> 2] ^ 7;
        v19 = &xor_final_result[v46 + v31];
        v29 = 16 * xor_result & 0x30;
        v19[1] = v29;
        if ( v31 + 1 >= v26 )
        {
          v40 = str2[v29];
          *((_WORD *)v19 + 1) = 15163;
          goto LABEL_43;
        }
      }
      ++v31;
    }
    while ( v31 < v26 );
  }
  while ( 1 )
  {
    if ( v45 )
    {
      v34 = (_BYTE *)1;
      v19 = (char *)v44;
      v41 = &byte_CBA7D4E8;
      do
      {
        v29 = (unsigned __int8)xor_final_result[str1_buf_2_index3_v++];
        v42 = (unsigned __int8)*v41++;
        if ( v42 != v29 )
          v34 = 0;
      }
      while ( str1_buf_2_index3_v < v44 );
    }
    else
    {
      v34 = (_BYTE *)1;
    }
    xor_final_result = (_BYTE *)(_stack_chk_guard - v50);
    if ( _stack_chk_guard == v50 )
      break;
LABEL_53:
    v40 = v34[v29];
    v19[2] = 52;
LABEL_43:
    v19[1] = v40;
  }
  return (unsigned __int8)v34;
}

看别人的WP,有修改变量还以为是什么新的操作,最后问过武师后才知道分析时根据分析出来的变量的语义来进行适当修改,修改结果就如上所示了;

直接写分析的结果吧,真的不能怪那些大佬为啥把wp写的那么简洁,因为这个分析是一个动态的过程,静态的文章没办法表达完全,手动写分析结果吧;

分割线以下的代码将我的输入与构建的表做了异或之后,又通过移位、加盐(特定位异或)等操作将结果放入上面代码的xor_final_result数组变量中

单步执行了几遍之后,发现有些规律,用下面的伪代码表达一下

v1 = table1[0] XOR input[0]
final_result[0] = str2[ v1 >> 2 ] XOR 0x7
final_result[1] = (16 * v1) & 0x30
----------------------------------------
v2 = table1[1] XOR input[1]
final_result[1] = str2[final_result[1] OR (v2 >> 4)]
final_result[2] = (4 * v2) & 0x3C
----------------------------------------
v3 = table1[2] XOR input[2]
v33 = v3 & 0xC0
final_result[2] = str2[final_result[2] OR (v33 >> 6)] XOR 0xF
final_result[3] = str2[v3 & 0x3F]

这样,三个输入对应四个验证的字符,验证的字符串在上面代码,v41处,可直接取出如下:

str4 = " {9*8ga*l!Tn?@#fj'j$\\g;;"

看到是24个,分成6份,每份4个字符,这样每次验证都会输出四个字符。
这里需要注意一个点,就是移位,比如第一个移两位,这样就会出现4个空间的值满足这个条件,移四位就会多2的四次幂个,但是问题不大,每个都试一下就好了,最后发现,有一个满足条件,那满足条件的那一个就是最后的结果;

其实三个对应验证字符串的四个,那么第四个就是验证位,这样就不会出现多个值的情况了

下面按照算法暴力破解跑出结果的脚本就是按照上面的算法从ASCII码 32-127挨个尝试的

关于为什么是32-127:因为这个区间是ASCII可显示字符,之前的和127是ASCII控制字符,像换行制表之类的;

那么那个table1,因为不会用IDAPython,所以只能在

xor_result = loc_buf100[(unsigned __int8)(v37 + (_BYTE)v19)] ^ input[v31];

这段下面加断点,每次都取xor_result,与0对应的ASCII码异或,就可以得到table1了:

str_com = [155, 107, 186, 37, 115, 130, 224, 49, 134, 128, 241, 197, 218, 130, 218, 8, 56, 144,80, 168,49, 99, 7, 162]

然后就可以按照上面的算法暴力破解了

str1 = '650f909c-7217-3647-9331-c82df8b98e98'
str2 = "!:#$%&()+-*/`~_[]{}?<>,.@^abcdefghijklmnopqrstuvwxyz0123456789\\';"
str3 = 'android/support/v7/app/AppCompiatActivity'
str4 = " {9*8ga*l!Tn?@#fj'j$\\g;;"
str_com = [155, 107, 186, 37, 115, 130, 224, 49, 134, 128, 241, 197, 218, 130, 218, 8, 56, 144,80, 168,49, 99, 7, 162]
str_compare = [" {9*", "8ga*", "l!Tn", "?@#f", "j'j$", "\\g;;"]
str_result = ""
final_result = ""

for i in range(6):
    buf_1 = []
    for ii in range(32, 127):
        if ord(str2[(str_com[0+3*i] ^ ii) >> 2]) ^ 7 == ord(str_compare[i][0]):
            buf_1.append(ii)
    print(buf_1)
    
    buf_2 = {}
    for j in buf_1:
        buf_2[str(j)] = []
    for ii in buf_1:
        for iii in range(32, 127):
            if str2[((16*(ii^str_com[0+3*i]))&0x30) | ((str_com[1+3*i] ^ iii) >> 4)] == str_compare[i][7]:
                buf_2[str(ii)].append(iii)
    
    print(buf_2)
    
    for ii in buf_1:
        for iii in buf_2[str(ii)]:
            for iiii in range(32, 127):
                if ord(str2[((4*(iii^str_com[1+3*i])) & 0x3C) | (((str_com[2+3*i] ^ iiii) & 0xC0) >> 6)]) ^ 0xF == ord(str_compare[i][8]):
                    if str2[(str_com[2+3*i] ^ iiii) & 0x3F] == str_compare[i][9]:
                        final_result += chr(ii) + chr(iii) + chr(iiii)
                        print(chr(ii)+chr(iii)+chr(iiii))
    print('-' * 40)
    print()
print(final_result)

可得到如下结果
p7

输入发现不行,自己一看,最后一个根本没有结果,可能是最后的;;的问题,可以看出来54是唯一的一个解,而chr(54)就是字符6,加上这个6就好了。

分析带写代码花了几乎两天时间,真的是强行入门,心如舔狗,还请以后加油,这是这段时间的最后一道题了,开始准备别的事情了,加油吧

真正体验了一把之前说的逆向的感觉,真的是宛如便秘,做完又宛如肠道通畅,这种感觉,应该只有分析算法才有的吧,就不哔哔了,图书馆该闭馆了。

问题

(做题过程中的记录)
好迷啊,主要是这里的知识有一个断层,关于IDA和IDAPython的操作有一点不理解,而WP里直接是跳过这一部分的,就记录一下现在的问题,把周期拉的长一点来做这个题;
p8

p9

我看了好几个wp都是直接把这个byte_xxxxx给换成第一个图的那个a56xxxxx的了,这是IDA什么操作吗还是他们手动换的。
问过武师傅之后解决了,是手动换的,继续看了看代码,得到了那段值

过程中看到一个我在未动态调试之前的知识漏洞吧
v12 = -8

在python测试的时候,结果是
p10

而IDA里
p11

因为截图需要按ctrl + alt + A,而IDA里按Alt的时候,光标放上去的框就会消失,所以这里手动输入

unsigned int v12; // r2
0xFFFFFFF8

++v12;
变成0xFFFFFFF9了???什么操作

所以这里就会首先赋值
p12

记一下这里

do
{

if ( (v13 | (v12 >> 2)) > 3 )
{
    v15 = v14;
}
else
{
    v15 = v14 + 1;
    str1_buf[v14] = 45;
}
v16 = str1_buf_1[v11--];
v13 += 0x40000000;
str1_buf[v15] = v16;
++v12;
v14 = v15 + 1;

}while ( v11 != -1 );

这里的意思是,将存有str1的buf——str1_buf_1,倒序存进str1_buf

只是不太理解这里的 (v13 | (v12 >> 2)) > 3是什么操作
p13

这里是把 str1_buf_2给v49

将这个v49重命名为 str1_buf_2_cp,但是它是256长的数组,是str1_buf_2的循环,但是至今我都还不太清楚这个str1_buf_2是从哪里来的_(:з)∠)_,不过不太重要,得到表了就好

这题必须在我的到目前为止不长的做题生涯里拥有姓名;