看雪2019CTF——变形金刚

打开是一道安卓逆向题,珍藏的APKtool不好使,询问大佬,曰:JEB。
打开后发现MainActivity中的oncreate中,注册了onclick事件,并在回掉中进行校验:


image.png

看似就是简单的用户名和密码倒序的校验,但是其实这个校验是假的
MainActivity继承的不是系统自带的AppCompatActivity类,而是自己编写的AppCompiatActivity类,,,,一个i的差别。。并且这个类放在了系统包目录里面。。。
AppCompiatActivity类中有onstart,注册了另一个onclick回掉,由于安卓的生命周期,onstart会后于oncreate,从而覆盖掉之前注册的毁掉。。


image.png

真正的校验代码:
image.png

核心校验位于oo000oo.so在native层导出的eq函数,于是IDA打开so,本题正式开始。

打开发现并没有eq函数导出,百度得到应该是动态导出的。
根据so文件的加载流程应该是先加载init_array,然后是JNI_OnLoad
打开发现so的init_array中有东西,会先执行.datadiv_decode5009363700628197108,异或解密一些数据。
然后JNI_OnLoad中完成了eq函数的导出


image.png

PS:为了方便阅读代码,可以把v4、v5变量类型改为JNIEnv*。
分析得知eq函数就是sub_784(实际上JNI_OnLoad中注册的地址是0x785,有点奇怪。。经过测试,个人猜想:0x784这个字节代表操作数,0x785这个字节代表指令,也就是说arm是把指令放在操作数的后面的。。arm中汇编语句的地址应该是按指令的地址算的,,而ida是按照汇编语句的第一个字节的地址算的)
int __fastcall sub_784(JNIEnv *a1)
{
  size_t v1; // r10
  unsigned __int8 *v2; // r6
  _BYTE *v3; // r8
  _BYTE *v4; // r11
  int v5; // r0
  size_t v6; // r2
  char *v7; // r1
  int v8; // r3
  int v9; // r1
  unsigned int v10; // r2
  int v11; // r3
  int v12; // r0
  int v13; // r4
  unsigned __int8 v14; // r0
  _BYTE *v15; // r3
  _BYTE *v16; // r5
  char *v17; // r4
  int v18; // r5
  int v19; // r1
  int v20; // r0
  signed int v21; // r1
  int v22; // r2
  size_t v23; // r0
  unsigned int v24; // r8
  unsigned int v25; // r5
  _BYTE *v26; // r0
  int v27; // r3
  int v28; // r10
  unsigned int v29; // r2
  int v30; // r12
  bool v31; // zf
  _BYTE *v32; // r1
  bool v33; // zf
  int v34; // r3
  int v35; // r1
  unsigned __int8 v36; // r11
  unsigned int v37; // lr
  char v38; // r1
  char *v39; // r2
  int v40; // t1
  unsigned int v42; // [sp+4h] [bp-234h]
  unsigned int v43; // [sp+8h] [bp-230h]
  unsigned int v44; // [sp+10h] [bp-228h]
  char *s; // [sp+14h] [bp-224h]
  char v46[256]; // [sp+18h] [bp-220h]
  char v47[256]; // [sp+118h] [bp-120h]
  int v48; // [sp+218h] [bp-20h]

  s = (char *)((int (*)(void))(*a1)->GetStringUTFChars)();
  v1 = strlen(byte_4020);
  v2 = (unsigned __int8 *)malloc(v1);
  v3 = malloc(v1);
  v4 = malloc(v1);
  _aeabi_memclr(v2, v1);
  _aeabi_memclr(v3, v1);
  _aeabi_memclr(v4, v1);
  if ( v1 )
  {
    v5 = 0;
    v6 = v1;
    v7 = byte_4020;//byte_4020手工解密就是650f909c-7217-3647-9331-c82df8b98e98
    do
    {
      v8 = (unsigned __int8)*v7++;
      if ( v8 != 45 )
        v3[v5++] = v8;
      --v6;
    }
    while ( v6 );//去掉‘-’
    if ( v5 >= 1 )
    {
      v9 = v5 - 1;
      v10 = -8;
      v11 = 0;
      v12 = 0;
      do
      {
        if ( (v11 | (v10 >> 2)) > 3 )
        {
          v13 = v12;
        }
        else
        {
          v13 = v12 + 1;
          v2[v12] = 45;
        }
        v14 = v3[v9--];
        v11 += 0x40000000;
        v2[v13] = v14;
        ++v10;
        v12 = v13 + 1;
      }
      while ( v9 != -1 );//倒叙输出,并按照一定的规律加上‘-’
      if ( v13 >= 0 )
      {
        v15 = v4;
        while ( 1 )
        {
          v16 = (_BYTE *)*v2;
          if ( (unsigned __int8)((_BYTE)v16 - 97) <= 5u )
            break;
          if ( (unsigned __int8)((_BYTE)v16 - 48) <= 9u )
          {
            v16 = (char *)&unk_23DE + (_DWORD)v16 - 48;
            goto LABEL_18;
          }
LABEL_19:
          *v15++ = (_BYTE)v16;
          --v12;
          ++v2;
          if ( !v12 )
            goto LABEL_20;
        }
        v16 = (char *)&unk_23D8 + (_DWORD)v16 - 97;
LABEL_18:
        LOBYTE(v16) = *v16;
        goto LABEL_19;
      }
    }
  }//单表替换的加密
LABEL_20:
  _aeabi_memcpy8(v46, &unk_23E8, 256);
  v17 = v47;
  v18 = 0;
  do
  {
    sub_D20(v18, v1);
    v47[v18++] = v4[v19];
  }
  while ( v18 != 256 );
  v20 = (unsigned __int8)(v47[0] - 41);
  v46[0] = v46[v20];
  v46[v20] = -41;
  v21 = 1;
  do
  {
    v22 = (unsigned __int8)v46[v21];
    v20 = (v20 + (unsigned __int8)v47[v21] + v22) % 256;
    v46[v21++] = v46[v20];
    v46[v20] = v22;
  }
  while ( v21 != 256 );
  v23 = strlen(s);
  v24 = v23;
  v25 = (unsigned __int8)v4[3];
  v43 = 8 * (3 - -3 * (v23 / 3));
  v42 = v25 + v43 / 6;
  v26 = malloc(v42 + 1);
  if ( v24 )
  {
    v28 = 0;
    v29 = 0;
    v30 = 0;
    v44 = v25;
    do
    {
      v28 = (v28 + 1) % 256;
      v35 = (unsigned __int8)v46[v28];
      v30 = (v30 + v35) % 256;
      v46[v28] = v46[v30];
      v46[v30] = v35;
      v17 = (char *)(unsigned __int8)v46[v28];
      v36 = v46[(unsigned __int8)(v35 + (_BYTE)v17)] ^ s[v29];//动态调的话只要在这里下端,记录下每次v46[(unsigned __int8)(v35 + (_BYTE)v17)]的值即可
      if ( v29 && (v27 = 0xAAAAAAAB * (unsigned __int64)v29 >> 32, v37 = 3 * (v29 / 3), v37 != v29) )
      {
        v31 = v29 == 1;
        if ( v29 != 1 )
          v31 = v37 + 1 == v29;
        if ( v31 )
        {
          v32 = byte_4050;
          v26[v44 + v29] = byte_4050[(unsigned __int8)v26[v44 + v29] | ((unsigned int)v36 >> 4)];
          v17 = &v26[v44 + v29];
          v27 = 4 * v36 & 0x3C;
          v17[1] = v27;
          if ( v29 + 1 >= v24 )
            goto LABEL_53;
        }
        else
        {
          v33 = v29 == 2;
          if ( v29 != 2 )
            v33 = v37 + 2 == v29;
          if ( v33 )
          {
            v17 = (char *)(v36 & 0xC0);
            v34 = v44++ + v29;
            v26[v34] = byte_4050[(unsigned __int8)v26[v34] | ((unsigned int)v17 >> 6)] ^ 0xF;
            v27 = (int)&v26[v34];
            *(_BYTE *)(v27 + 1) = byte_4050[v36 & 0x3F];
          }
        }
      }
      else
      {
        v26[v44 + v29] = byte_4050[(unsigned int)v36 >> 2] ^ 7;
        v17 = &v26[v44 + v29];
        v27 = 16 * v36 & 0x30;
        v17[1] = v27;
        if ( v29 + 1 >= v24 )
        {
          v38 = byte_4050[v27];
          *((_WORD *)v17 + 1) = 15163;
          goto LABEL_43;
        }
      }
      ++v29;
    }
    while ( v29 < v24 );
  }
  while ( 1 )
  {
    if ( v43 )
    {
      v32 = (_BYTE *)(&dword_0 + 1);
      v17 = (char *)v42;
      v39 = &byte_24E8;
      do
      {
        v27 = (unsigned __int8)v26[v25++];
        v40 = (unsigned __int8)*v39++;
        if ( v40 != v27 )
          v32 = 0;
      }
      while ( v25 < v42 );
    }
    else
    {
      v32 = (_BYTE *)(&dword_0 + 1);
    }
    v26 = (_BYTE *)(_stack_chk_guard - v48);
    if ( _stack_chk_guard == v48 )
      break;
LABEL_53:
    v38 = v32[v27];
    v17[2] = 52;
LABEL_43:
    v17[1] = v38;
  }
  return (unsigned __int8)v32;
}

总的来看,这道题目先是三次自写的加密,然后是RC4,最后是Base64。。但是和输入有关的只在base64那个地方出现。
思路有两个:
1.动态调试到Base64的地方,把之前与输入无关的解密的数据dump下来,在逆向base64之前的数据,抑或运算即可得到输入。(调试需要root的安卓真机)
2.静态分析
前面三次自写的加密,很容易自己解密,第二次复杂些,我是直接把ida的代码复制下来,然后编译运行得到的结果。。。具体啥逻辑。。没看明白。。贴上C代码:

#include 
#include 

char v3[100]="650f909c721736479331c82df8b98e98";
char v2[100];
int main()
{
    int v9=strlen(v3)-1;
    unsigned int v10=-8;
    int v11=0;
    int v12=0;
    int v13=0;
    int v14=0;
    unsigned char v16;
    do
    {
        if ((v11|(v10>>2))>3)
            v13=v12;
        else{
            v13=v12+1;
            v2[v12]=45;
        }
        v14=v3[v9--];
        v11+=0x40000000;
        v2[v13]=v14;
        ++v10;
        v12=v13+1;
    }
    while(v9!=-1);
    printf(v2);
}

接下来是变形的RC4,其实就是替换了Sbox[256]这个表(unk_23E8),密钥是前面解密的数据,加密的数据是用户的输入。加密后在进行Base64加密,然后加密后的数据与一块内存比较,进行check。思路是先解密base64,然后解密rc4即可反推得到正确输入。
首先解密BASE64,这个base64改了编码表,并且补齐的部分不是=而是;表示,并对下标除4余0和2的数据再次抑或加密了,上脚本:

import base64
import string

# base 字符集

#base64_charset = string.ascii_uppercase + string.ascii_lowercase + string.digits + '+/'
base64_charset = "!:#$%&()+-*/`~_[]{}?<>,.@^abcdefghijklmnopqrstuvwxyz0123456789\\'"


def encode(origin_bytes):
    """
    将bytes类型编码为base64
    :param origin_bytes:需要编码的bytes
    :return:base64字符串
    """
    # 将每一位bytes转换为二进制字符串
    base64_bytes = ['{:0>8}'.format(str(bin(ord(b))).replace('0b', '')) for b in origin_bytes]

    resp = ''
    nums = len(base64_bytes) // 3
    remain = len(base64_bytes) % 3
    integral_part = base64_bytes[0:3 * nums]
    while integral_part:
        # 取三个字节,以每6比特,转换为4个整数
        tmp_unit = ''.join(integral_part[0:3])
        tmp_unit = [int(tmp_unit[x: x + 6], 2) for x in [0, 6, 12, 18]]
        # 取对应base64字符
        resp += ''.join([base64_charset[i] for i in tmp_unit])
        integral_part = integral_part[3:]

    if remain:
        # 补齐三个字节,每个字节补充 0000 0000
        remain_part = ''.join(base64_bytes[3 * nums:]) + (3 - remain) * '0' * 8
        # 取三个字节,以每6比特,转换为4个整数
        # 剩余1字节可构造2个base64字符,补充==;剩余2字节可构造3个base64字符,补充=
        tmp_unit = [int(remain_part[x: x + 6], 2) for x in [0, 6, 12, 18]][:remain + 1]
        resp += ''.join([base64_charset[i] for i in tmp_unit]) + (3 - remain) * '='

    return resp


def decode(base64_str):
    """
    解码base64字符串
    :param base64_str:base64字符串
    :return:解码后的bytearray;若入参不是合法base64字符串,返回空bytearray
    """
    if not valid_base64_str(base64_str):
        pass#return bytearray()

    # 对每一个base64字符取下标索引,并转换为6为二进制字符串
    base64_bytes = ['{:0>6}'.format(str(bin(base64_charset.index(s))).replace('0b', '')) for s in base64_str if
                    s != '=']
    resp = bytearray()
    nums = len(base64_bytes) // 4
    remain = len(base64_bytes) % 4
    integral_part = base64_bytes[0:4 * nums]

    while integral_part:
        # 取4个6位base64字符,作为3个字节
        tmp_unit = ''.join(integral_part[0:4])
        tmp_unit = [int(tmp_unit[x: x + 8], 2) for x in [0, 8, 16]]
        for i in tmp_unit:
            resp.append(i)
        integral_part = integral_part[4:]

    if remain:
        remain_part = ''.join(base64_bytes[nums * 4:])
        tmp_unit = [int(remain_part[i * 8:(i + 1) * 8], 2) for i in range(remain - 1)]
        for i in tmp_unit:
            resp.append(i)

    return resp


def valid_base64_str(b_str):
    """
    验证是否为合法base64字符串
    :param b_str: 待验证的base64字符串
    :return:是否合法
    """
    if len(b_str) % 4:
        print (123)
        return False

    for m in b_str:
        if m not in base64_charset:
            print (m)
            return False
    return True

data = [0x20,0x7B,0x39,0x2A,0x38,0x67,0x61,0x2A,0x6C,0x21,0x54,0x6E,0x3F,0x40,0x23,0x66,0x6A,0x27,0x6A,0x24,0x5C,0x67,0x3B,0x3B]
for i in range(len(data)):
    if i % 4 == 0:
        data[i] ^= 0x7
    if i % 4 == 2:
        data[i] ^= 0xF
if __name__ == '__main__':
    data[22]=ord('=')
    data[23]=ord('=')
    data = ''.join([chr(i) for i in data])
    print (data)
    print (decode(data))

这个编码表在提取的时候有个坑,,byte_4050这个地址有66个字节的数据,直接复制到python的话因为有转义字符啥的,长度是64,其实是错的。。记得只复制64个字节,,,
接着是RC4:

# -*- coding: utf-8 -*-

import time

class RC4:
    def __init__(self, k):
        self.Sbox = self.RC4_init(k)

    def RC4_init(self,k):
        #Sbox = range(256)
        Sbox = [0xD7,0xDF,0x02,0xD4,0xFE,0x6F,0x53,0x3C,0x25,0x6C,0x99,0x97,0x06,0x56,0x8F,0xDE,0x40,0x11,0x64,0x07,0x36,0x15,0x70,0xCA,0x18,0x17,0x7D,0x6A,0xDB,0x13,0x30,0x37,0x29,0x60,0xE1,0x23,0x28,0x8A,0x50,0x8C,0xAC,0x2F,0x88,0x20,0x27,0x0F,0x7C,0x52,0xA2,0xAB,0xFC,0xA1,0xCC,0x21,0x14,0x1F,0xC2,0xB2,0x8B,0x2C,0xB0,0x3A,0x66,0x46,0x3D,0xBB,0x42,0xA5,0x0C,0x75,0x22,0xD8,0xC3,0x76,0x1E,0x83,0x74,0xF0,0xF6,0x1C,0x26,0xD1,0x4F,0x0B,0xFF,0x4C,0x4D,0xC1,0x87,0x03,0x5A,0xEE,0xA4,0x5D,0x9E,0xF4,0xC8,0x0D,0x62,0x63,0x3E,0x44,0x7B,0xA3,0x68,0x32,0x1B,0xAA,0x2D,0x05,0xF3,0xF7,0x16,0x61,0x94,0xE0,0xD0,0xD3,0x98,0x69,0x78,0xE9,0x0A,0x65,0x91,0x8E,0x35,0x85,0x7A,0x51,0x86,0x10,0x3F,0x7F,0x82,0xDD,0xB5,0x1A,0x95,0xE7,0x43,0xFD,0x9B,0x24,0x45,0xEF,0x92,0x5C,0xE4,0x96,0xA9,0x9C,0x55,0x89,0x9A,0xEA,0xF9,0x90,0x5F,0xB8,0x04,0x84,0xCF,0x67,0x93,0x00,0xA6,0x39,0xA8,0x4E,0x59,0x31,0x6B,0xAD,0x5E,0x5B,0x77,0xB1,0x54,0xDC,0x38,0x41,0xB6,0x47,0x9F,0x73,0xBA,0xF8,0xAE,0xC4,0xBE,0x34,0x01,0x4B,0x2A,0x8D,0xBD,0xC5,0xC6,0xE8,0xAF,0xC9,0xF5,0xCB,0xFB,0xCD,0x79,0xCE,0x12,0x71,0xD2,0xFA,0x09,0xD5,0xBC,0x58,0x19,0x80,0xDA,0x49,0x1D,0xE6,0x2E,0xE3,0x7E,0xB7,0x3B,0xB3,0xA0,0xB9,0xE5,0x57,0x6E,0xD9,0x08,0xEB,0xC7,0xED,0x81,0xF1,0xF2,0xBF,0xC0,0xA7,0x4A,0xD6,0x2B,0xB4,0x72,0x9D,0x0E,0x6D,0xEC,0x48,0xE2,0x33]
        T = [ord(k[i % len(k)]) for i in range(256)]
        j = 0
        for i in range(256):
            j = (j + Sbox[i] + T[i]) & 0xFF
            Sbox[i], Sbox[j] = Sbox[j], Sbox[i]
        return Sbox

    def Encrypt(self,m):
        c = ""
        S = self.Sbox[:]
        i = j = 0
        for ch in m:
            i = (i + 1) & 0xFF
            j = (j + S[i]) & 0xFF
            S[i], S[j] = S[j], S[i]
            t = (S[i] + S[j]) & 0xFF
            c += chr(ord(ch) ^ S[t])
        return c

    def Decrypt(self,m):
        c = ""
        S = self.Sbox[:]
        i = j = 0
        for ch in m:
            i = (i + 1) & 0xFF
            j = (j + S[i]) & 0xFF
            S[i], S[j] = S[j], S[i]
            t = (S[i] + S[j]) & 0xFF
            c += chr(ord(ch) ^ S[t])
        return c

if __name__ == "__main__":
    rc4 = RC4("36f36b3c-a03e-4996-8759-8408e626c215")
    m = rc4.Decrypt("\xfd\x1e\x8aN\t\xca\x90\x03\xe7\xf1\x85\x9f\x9b\xf7\x83>")
    print m
image.png

把这个密码输入,点登陆会提示flag{android4-9},,,然而flag并不是这个!而是之前解密出来的密码。。。。别问我咋知道的。。大佬说的
总之这道题目在大佬指点下做完了。。后续希望整理学习一些密码学的资料。

你可能感兴趣的:(看雪2019CTF——变形金刚)