看雪2019CTF——變形金剛

打開是一道安卓逆向題,珍藏的APKtool不好使,詢問大佬,曰:JEB。
打開后發(fā)現(xiàn)MainActivity中的oncreate中,注冊(cè)了onclick事件,并在回掉中進(jìn)行校驗(yàn):


image.png

看似就是簡(jiǎn)單的用戶名和密碼倒序的校驗(yàn),但是其實(shí)這個(gè)校驗(yàn)是假的
MainActivity繼承的不是系統(tǒng)自帶的AppCompatActivity類,而是自己編寫的AppCompiatActivity類,,,,一個(gè)i的差別。。并且這個(gè)類放在了系統(tǒng)包目錄里面。。。
AppCompiatActivity類中有onstart,注冊(cè)了另一個(gè)onclick回掉,由于安卓的生命周期,onstart會(huì)后于oncreate,從而覆蓋掉之前注冊(cè)的毀掉。。


image.png

真正的校驗(yàn)代碼:
image.png

核心校驗(yàn)位于oo000oo.so在native層導(dǎo)出的eq函數(shù),于是IDA打開so,本題正式開始。

打開發(fā)現(xiàn)并沒有eq函數(shù)導(dǎo)出,百度得到應(yīng)該是動(dòng)態(tài)導(dǎo)出的。
根據(jù)so文件的加載流程應(yīng)該是先加載init_array,然后是JNI_OnLoad
打開發(fā)現(xiàn)so的init_array中有東西,會(huì)先執(zhí)行.datadiv_decode5009363700628197108,異或解密一些數(shù)據(jù)。
然后JNI_OnLoad中完成了eq函數(shù)的導(dǎo)出


image.png

PS:為了方便閱讀代碼,可以把v4、v5變量類型改為JNIEnv*。
分析得知eq函數(shù)就是sub_784(實(shí)際上JNI_OnLoad中注冊(cè)的地址是0x785,有點(diǎn)奇怪。。經(jīng)過測(cè)試,個(gè)人猜想:0x784這個(gè)字節(jié)代表操作數(shù),0x785這個(gè)字節(jié)代表指令,也就是說arm是把指令放在操作數(shù)的后面的。。arm中匯編語(yǔ)句的地址應(yīng)該是按指令的地址算的,,而ida是按照匯編語(yǔ)句的第一個(gè)字節(jié)的地址算的)
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 );//倒敘輸出,并按照一定的規(guī)律加上‘-’
      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];//動(dòng)態(tài)調(diào)的話只要在這里下端,記錄下每次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。。但是和輸入有關(guān)的只在base64那個(gè)地方出現(xiàn)。
思路有兩個(gè):
1.動(dòng)態(tài)調(diào)試到Base64的地方,把之前與輸入無關(guān)的解密的數(shù)據(jù)dump下來,在逆向base64之前的數(shù)據(jù),抑或運(yùn)算即可得到輸入。(調(diào)試需要root的安卓真機(jī))
2.靜態(tài)分析
前面三次自寫的加密,很容易自己解密,第二次復(fù)雜些,我是直接把ida的代碼復(fù)制下來,然后編譯運(yùn)行得到的結(jié)果。。。具體啥邏輯。。沒看明白。。貼上C代碼:

#include <stdio.h>
#include <string.h>

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,其實(shí)就是替換了Sbox[256]這個(gè)表(unk_23E8),密鑰是前面解密的數(shù)據(jù),加密的數(shù)據(jù)是用戶的輸入。加密后在進(jìn)行Base64加密,然后加密后的數(shù)據(jù)與一塊內(nèi)存比較,進(jìn)行check。思路是先解密base64,然后解密rc4即可反推得到正確輸入。
首先解密BASE64,這個(gè)base64改了編碼表,并且補(bǔ)齊的部分不是=而是;表示,并對(duì)下標(biāo)除4余0和2的數(shù)據(jù)再次抑或加密了,上腳本:

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轉(zhuǎn)換為二進(jìn)制字符串
    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:
        # 取三個(gè)字節(jié),以每6比特,轉(zhuǎn)換為4個(gè)整數(shù)
        tmp_unit = ''.join(integral_part[0:3])
        tmp_unit = [int(tmp_unit[x: x + 6], 2) for x in [0, 6, 12, 18]]
        # 取對(duì)應(yīng)base64字符
        resp += ''.join([base64_charset[i] for i in tmp_unit])
        integral_part = integral_part[3:]

    if remain:
        # 補(bǔ)齊三個(gè)字節(jié),每個(gè)字節(jié)補(bǔ)充 0000 0000
        remain_part = ''.join(base64_bytes[3 * nums:]) + (3 - remain) * '0' * 8
        # 取三個(gè)字節(jié),以每6比特,轉(zhuǎn)換為4個(gè)整數(shù)
        # 剩余1字節(jié)可構(gòu)造2個(gè)base64字符,補(bǔ)充==;剩余2字節(jié)可構(gòu)造3個(gè)base64字符,補(bǔ)充=
        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;若入?yún)⒉皇呛戏╞ase64字符串,返回空bytearray
    """
    if not valid_base64_str(base64_str):
        pass#return bytearray()

    # 對(duì)每一個(gè)base64字符取下標(biāo)索引,并轉(zhuǎn)換為6為二進(jìn)制字符串
    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個(gè)6位base64字符,作為3個(gè)字節(jié)
        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):
    """
    驗(yàn)證是否為合法base64字符串
    :param b_str: 待驗(yàn)證的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))

這個(gè)編碼表在提取的時(shí)候有個(gè)坑,,byte_4050這個(gè)地址有66個(gè)字節(jié)的數(shù)據(jù),直接復(fù)制到python的話因?yàn)橛修D(zhuǎn)義字符啥的,長(zhǎng)度是64,其實(shí)是錯(cuò)的。。記得只復(fù)制64個(gè)字節(jié),,,
接著是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

把這個(gè)密碼輸入,點(diǎn)登陸會(huì)提示flag{android4-9},,,然而flag并不是這個(gè)!而是之前解密出來的密碼。。。。別問我咋知道的。。大佬說的
總之這道題目在大佬指點(diǎn)下做完了。。后續(xù)希望整理學(xué)習(xí)一些密碼學(xué)的資料。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容