首页
论坛
专栏
课程
17

[原创]看雪.Wifi万能钥匙 CTF 2017 第6题Writeup

poyoten 2017-6-13 17:32 1351


APK直接拖入JEB,发现函数名混淆了,很长的标点什么的,还有就是字串加密了,看起来MainActivity有个文本框和一个按钮,点击按钮后,将文本作为参数调用utils.checkutils加载了一个so库,check为native函数。只不过so库名字加密了,不用管,因为看了下只有一个so库。

ida加载so文件,发现了check函数,发现好多花,JNI_Onloadcheck根本没法看。看了下字串,发现有几个可疑的字串,就研究了下,这几个字串都是通过unhex加异或解码的,大概如下:

5DDF101B20158266    0x2A, 0xBE, 0x79, 0x6F, 0x50, 0x7C, 0xE6, 0x66  waitpid
93581A05791938828C421C         0xC3, 0xC, 0x48, 0x44, 0x3A, 0x5C, 0x67, 0xC1  PTRACE_CONT
5229C2BD6437C15E526793F96430D0595026  0x31, 0x48, 0xB6, 0x9D, 0x4B, 0x47, 0xB3, 0x31   cat  /proc/%d/wchan
13B82D685939526D   0x61, 0xB8, 0x2D, 0x68, 0x59, 0x39, 0x52, 0x6D   r
1410CEB874A13B500B358D  0x67, 0x69, 0xBD, 0xE7, 0x11, 0xD1, 0x54, 0x3C   sys_epoll\0
D79D76C575AC5DAFC8B835 0xA4, 0xE4, 5, 0x9A, 0x10, 0xDC, 0x32, 0xC3   sys_epoll\0
71897FAB1C51CCD475927D964F  1, 0xFD, 0xD, 0xCA, 0x7F, 0x34, 0x93, 0xA7    ptrace_stop\0
304B53EAF184220D345051D7A2   0x40, 0x3F, 0x21, 0x8B, 0x92, 0xE1, 0x7D, 0x7E
4B1D178C542EF29A1042118047 0x64,0x6D,0x65,0xE3,0x37,   1,0x9C,0xFF   /proc/net/tcp
221DD6112A3D27F7 0x50,0x1D,0xD6,0x11,0x2A,0x3D,0x27,0xF7  r
973C7EAA8C8B94DD9D390AA2FD 0xA7, 0xC,0x4E,0x9A,0xBC,0xBB,0xA4,0xED  00000000:5D8A
12FA6FDF29F96CD212E77CC039 0x3D,0x8A,0x1D,0xB0,0x4A,0xD6,0x49,0xB6   /proc/%d/maps
6C600DAFA9FE35C66C7D1EB0B9 0x43,0x10,0x7F,0xC0,0xCA,0xD1,0x10,0xA2  /proc/%d/maps

前面是原始字串,后面为异或表,最后是解码的字串。 查看调用位置,发现几个类似反调试的函数,一个是通过ptrace反附加,一个是通过检查ida的远程调试端口反调试,一个是通过inotify监控maps文件反调试。我尝试进行了更改,前面为偏移,后面为更改情况:

4016   0446 ->  0024
41F2   0120 ->  0020
45AA   50BB ->  50B3

在异或表前后还有很多类似的表,据此发现很多的unhex_xor的操作函数,数量很大。但是找不到调用的地方,反调试函数也没有找到调用的地方。陷入僵局。 无法继续看混淆,发现规律,形式单一,并且中间似乎没有和真实指令有关的东西,所以粗暴替换成NOP,加上前面的unhex_xor一起搞了:

def scan(patt): 
    pattern = patt
    addr = MinEA()
    l = []    for i in range(0,MaxEA()):
        addr = idc.FindBinary(addr, SEARCH_DOWN|SEARCH_NEXT, pattern)        
        if addr != idc.BADADDR:
            l.append(addr)    
            return l
def makefunc(l):
    for addr in l:
        MakeFunction(addr,BADADDR)
        oldname = GetFunctionName(addr)
        MakeName(addr,'unhex_xor_'+hex(addr)[2:-1]) 
        print 'make function at:'+hex(addr)
def junk(l):
    for addr in l:        
        for i in xrange(43):
            PatchWord(addr+i*2,0xbf00)
l = scan('2D E9 F0 47 03 AF 04 46  65 1E')
makefunc(l)
l = scan('13 E0 BD E8 F0 47 05 E0 00 F1 01 00 0A E0 1B 46 0E E0 10 E0 B1 B5 01 E0 12 46 01 E0 82 B0 FB E7 02 B0 F1 E7 A0 F1 01 00 F1 E7 2D E9 F0 47 E8 E7 BD E8 B1 40 ED E7 2D E9 F0 47 BD E8 F0 47 FF E7 B1 B5 82 B0 12 46 02 B0  00 F1 01 00 A0 F1 01 00 1B 46 BD E8 B1 40')
junk(l)

搞完查看伪代码,发现似乎path有问题,看代码:

int __fastcall check(JNIEnv *a1, int a2, int a3)
{
  JNIEnv *v3; // r9@1
  void *v4; // r8@1
  signed int v5; // r4@3
  int v6; // r1@3
  int v7; // r5@4
  int v8; // r6@4
  _WORD *v9; // r4@5
  int v10; // r3@5
  __int16 v11; // r0@6
  int count; // r4@12
  const char *input; // r0@12
  char *v14; // r0@12
  signed int v15; // r0@15
  _WORD v16[32]; // [sp+0h] [bp-54h]@1
  int v17; // [sp+40h] [bp-14h]@2
  int v18; // [sp+44h] [bp-10h]@1
  _BYTE savedregs[20]; // [sp+54h] [bp+0h]@2

  v3 = a1;
  v4 = (void *)a3;
  v18 = _stack_chk_guard;
  *(_DWORD *)v16 = 0x11000F;
  *(_DWORD *)&v16[2] = 0x11FFFF;
  *(_DWORD *)&v16[4] = 0xFFFFFFFF;
  *(_DWORD *)&v16[6] = 0x10010;
  *(_DWORD *)&v16[8] = 0x10FFFF;
  *(_DWORD *)&v16[10] = 0xFFFF0003;
  *(_DWORD *)&v16[12] = 0xF000F;
  *(_DWORD *)&v16[14] = 0;
  *(_DWORD *)&v16[16] = 0xFFFFF;
  *(_DWORD *)&v16[18] = 0xFFFF0011;
  *(_DWORD *)&v16[20] = 0xFFFF0011;
  *(_DWORD *)&v16[22] = 0x10FFFF;
  *(_DWORD *)&v16[24] = 0xFFFF0001;
  *(_DWORD *)&v16[26] = 0x30010;
  *(_DWORD *)&v16[28] = 0x21FFFF;
  *(_DWORD *)&v16[30] = 0xA;  if ( inputcount_20040 >= 6 )
    ((void (__cdecl *)(int, signed int, unsigned int, int *, signed int, signed int, signed int, _BYTE *, int))loop_286C)(
      inputcount_20040,
      0x10FFFF,
      0xFFFF0001,
      &v17,
      0x30010,
      0x21FFFF,
      0xA,
      savedregs,
      a3);
  ++inputcount_20040;
  v16[1] = 0x4A;                                // Jyu3CJlVDSGQ
  v16[2] = 0x79;
  v16[3] = 0x75;
  v16[4] = 0x33;
  v16[5] = 0x43;
  v16[6] = 0x4A;
  v16[7] = 0x6C;
  v16[8] = 0x56;
  v16[9] = 0x44;
  v16[10] = 0x53;
  v16[11] = 0x47;
  v16[12] = 0x51;
  v5 = 0;
  v6 = 0;  while ( 1 )
  {
    v7 = v16[v5];
    v8 = v5 + 3;    
    if ( v7 != 0xFFFFFFFF )
    {
      v9 = &v16[v5];
      v10 = v9[1];      
      if ( (unsigned __int16)v10 == 0xFFFF )
      {
        check_20020[2 * v6++] = v16[v7];
      }      else
      {
        v5 = v9[2];
        v11 = v16[v10] - v16[v7];
        v16[v10] = v11;        
        if ( v11 <= 0 )         
           goto LABEL_11;
      }
    }
    v5 = v8;
LABEL_11:    if ( v5 <= -1 )
    {      
      sub_19FC();
      count = 0;
      input = (*v3)->GetStringUTFChars(v3, v4, 0);      
      sub_19DA8(input);                         // important
      while ( check_20020[count] == (unsigned __int8)v14[count] )
      {        if ( ++count == 24 )
        {
          v15 = 1;          
          goto LABEL_17;
        }
      }      sub_27C8(check_20020, v14);
      v15 = 0;
LABEL_17:      if ( _stack_chk_guard == *(_DWORD *)&v16[18] )
                     JUMPOUT(__CS__, v18);      
        _stack_chk_fail(v15);
    }
  }
}

变量名不知怎么乱了。check_20020[count] == (unsigned __int8)v14[count]似乎就是最终的比较,24位,check_20020的赋值在此处和另一个函数sub_19FC中,分别写check_20020的奇数位和偶数位。似乎sub_19DA8为加密函数,最后来验证。

void __fastcall sub_19DA8(_BYTE *a1)
{  int v1; // r9@1
  int v2; // r0@1
  int v3; // r8@1
  _BYTE *v4; // r0@1
  int v5; // t1@2
  unsigned int v6; // r6@3
  int v7; // r5@3
  _BYTE *v8; // r0@3
  int v9; // t1@4
  int v10; // [sp+0h] [bp-418h]@3
  int v11; // [sp+408h] [bp-10h]@1

  v1 = (int)a1;
  v11 = _stack_chk_guard;  sub_1A31C();                                  // 密钥
  v3 = v2;
  v4 = (_BYTE *)v1; 
  do
      v5 = *v4++;  
  while ( v5 );
  v6 = (unsigned int)&v4[~v1];
  v7 = (int)malloc((size_t)&v4[~v1 + 1]);  
  _aeabi_memclr();  
  sub_55E4((int)&v10, 8, v3);  
  wow_467E(&v10, v6, v1, v7);
  v8 = (_BYTE *)v7;  do
  v9 = *v8++;  while ( v9 );  
  enbase_5AFC(v7, (int)&v8[-v7 - 1]);           // base
  JUMPOUT(_stack_chk_guard, v11, j___stack_chk_fail);
}

经过时间的研究与猜测,sub_1A31C()check_20020取值手法一样,怀疑是密钥,sub_55E4像是密钥扩展什么的,里面开头有这么一段:

  do
  {
    *(_DWORD *)(v3 + 4 * v4) = v4;
    ++v4;
  }  while ( v4 != 256 );

再加上后面及加密部分的难懂的代码,推理加密算法为RC4。 enbase_5AFC为base64编码,64个变换表太显眼了,还有个解码的函数,似乎没有地方调用。 分析加猜测,算法出来了,现在就是验证及校验码的拼接顺序,密钥的取值顺序,其值都是硬编码的明文,目前能得到校验码的可能相关值为Jyu3CJlVDSGQPjpeyjk6mmH=,密钥的相关值为199310124853!,通过以上代码我没有弄出来准确值,代码解析还是有问题的。只好试着动态,在关键点下断找数据。

重新打开so文件,只patch反调试的地方,使用jarjarsigner进行文件替换和签名,push到模拟器进行ida动态调试,意外出现了,在正常指令的地方如MOV R3,R3出现非法指令的异常,又费了很长时间,无法解决了似乎,另外arm虚拟机太慢了,坑死了。带着遗憾过了最后一晚上的攻击机会。最后听说没有反调试,可以直接调试,没时间试,不知道什么情况。

眼看情况快到了,一直在想参数的确定问题,24位base64比较值,两串相关字串就是24位,有可能是直接奇偶拼接,没有更换顺序。而key只有8位,现在是13位,又是怎么取的呢,难道和前面一样,按顺序取? 最后找朋友帮忙动态确定猜想,果然。

check_20020 = 'JPyjup3eCyJjlkV6DmSmGHQ='
key = `19931012`

懒得写py脚本了,用软件解了得到:madebyericky94528




快讯:看雪智能设备漏洞挖掘公开课招生中!

最新回复 (0)
返回