打开是一道安卓逆向题,珍藏的APKtool不好使,询问大佬,曰:JEB。
打开后发现MainActivity中的oncreate中,注册了onclick事件,并在回掉中进行校验:
看似就是简单的用户名和密码倒序的校验,但是其实这个校验是假的
MainActivity继承的不是系统自带的AppCompatActivity类,而是自己编写的AppCompiatActivity类,,,,一个i的差别。。并且这个类放在了系统包目录里面。。。
AppCompiatActivity类中有onstart,注册了另一个onclick回掉,由于安卓的生命周期,onstart会后于oncreate,从而覆盖掉之前注册的毁掉。。
真正的校验代码:
核心校验位于oo000oo.so在native层导出的eq函数,于是IDA打开so,本题正式开始。
打开发现并没有eq函数导出,百度得到应该是动态导出的。
根据so文件的加载流程应该是先加载init_array,然后是JNI_OnLoad
打开发现so的init_array中有东西,会先执行.datadiv_decode5009363700628197108,异或解密一些数据。
然后JNI_OnLoad中完成了eq函数的导出
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
把这个密码输入,点登陆会提示flag{android4-9},,,然而flag并不是这个!而是之前解密出来的密码。。。。别问我咋知道的。。大佬说的
总之这道题目在大佬指点下做完了。。后续希望整理学习一些密码学的资料。