感觉做完之后能对 Android 逆向有更进一步的理解,特地码一下,也拜读了几位大佬的文章。
1、看一下androidmanifest.xml
就一个活动进去看看
2、Mainactivity
看起来只要把用户名跟密码 作为 颠倒的字符串就行
然而并不是,再仔细观察就能发现 public class MainActivity extends AppCompiatActivity
开始以为是android 原生的AppCompiatActivity ,然而是改了一个相似的名字不仔细看,很容易忽略,这个操作挺骚的
懒得截图,直接用了某大佬的图
3、AppCompaitActivity
真正的校验逻辑在 AppCompaitActivity 这个类的 public native boolean eq(String str); 函数内
这个函数是一个 native 函数,那么具体的校验逻辑必然在 oo000oo 对应的 so 文件中
4、发现so文件中并没有eq该函数,所以判断在JNI_LOAD或者在init_array里面做了手脚。
5、跟踪.init_array
init_array函数执行了datadiv_decode5009363700628197108字符串解密函数
650f909c-7217-3647-9331-c82df8b98e98
!:#$%&()+-*/`~_[]{}?<>,.@^abcdefghijklmnopqrstuvwxyz0123456789\';
android/support/v7/app/AppCompiatActivity
Eq (Ljava/lang/String;)Z
总结:
#解码36长度字符串byte_4020:
byte_4020 =650f909c-7217-3647-9331-c82df8b98e98+0x00(结束符)
#解码Base64为的64+1个编码字符byte_4050=
base64Chars = byte_4050 =!:#$%&()+-*/`~_[]{}?<>,.@^abcdefghijklmnopqrstuvwxyz0123456789\';+0x00(结束符)
#app伪装java类的名称byte_40A0:
className_Sign= android/support/v7/app/AppCompiatActivity +0x00(结束符)
6、JNI_OnLoad
7、动态注册
8、关键函数
app_password = (char *)(*(int (**)(void))(*(_DWORD *)a1 + 676))();
rc4_key_len = strlen(rc4_raw_key); // rc4_raw_key = 650f909c-7217-3647-9331-c82df8b98e98
rc4_key3 = (unsigned __int8 *)malloc(rc4_key_len);// rc4_raw_key 被删除"-"符号,再经过特殊处理,同时有添加回新的“-”符号
// 新的结果:89e89b8f-d28c-1339-7463-7127c909f056
rc4_key2 = malloc(rc4_key_len); // rc4_raw_key的内容删除“-” :650f909c721736479331c82df8b98e98
rc4_key4 = malloc(rc4_key_len);
_aeabi_memclr(rc4_key3, rc4_key_len); // RC4密钥生成
_aeabi_memclr(rc4_key2, rc4_key_len); // aeabi_memclr(memset的地址和长度是常量)
_aeabi_memclr(rc4_key4, rc4_key_len);
if ( rc4_key_len ) // key长度大于0
{
rc4_key2_len = 0;
v6 = rc4_key_len;
v7 = rc4_raw_key;
do
{
v8 = (unsigned __int8)*v7++;
if ( v8 != 45 ) // 45(-)减号/破折号
rc4_key2[rc4_key2_len++] = v8;
--v6;
}
while ( v6 ); // 删除(rc4_raw_key中的"-") =650f909c721736479331c82df8b98e98
if ( rc4_key2_len >= 1 )
{
rc4_key2_pos = rc4_key2_len - 1;
v10 = -8;
v11 = 0;
rc4_key3_len = 0;
do
{
if ( (v11 | (v10 >> 2)) > 3 ) // // v10>>2 = -2?后第一次=0x3FFFFFFE
{
rc4_key3_pos = rc4_key3_len;
}
else
{
rc4_key3_pos = rc4_key3_len + 1;
rc4_key3[rc4_key3_len] = 45;
}
v14 = rc4_key2[rc4_key2_pos--];
v11 += 0x40000000;
rc4_key3[rc4_key3_pos] = v14;
++v10;
rc4_key3_len = rc4_key3_pos + 1;
}
while ( rc4_key2_pos != -1 );
if ( rc4_key3_pos >= 0 )
{
v15 = rc4_key4;
while ( 1 )
{
v16 = (_BYTE *)*rc4_key3;
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;
--rc4_key3_len;
++rc4_key3;
if ( !rc4_key3_len )
goto LABEL_20;
}
v16 = (char *)&unk_23D8 + (_DWORD)v16 - 97;
LABEL_18:
LOBYTE(v16) = *v16;
goto LABEL_19;
}
}
}
LABEL_20: // sbox 生成
_aeabi_memcpy8(sbox, &unk_23E8, 256);
Rc4_T = tbox;
tbox_pos = 0;
do
{
sub_D20(tbox_pos, rc4_key_len); // 需要跟进去看看
tbox[tbox_pos++] = rc4_key4[v19];
}
while ( tbox_pos != 256 );
ix = (unsigned __int8)(tbox[0] - 41);
sbox[0] = sbox[ix];
sbox[ix] = -41; // d7
v21 = 1;
do
{
v22 = (unsigned __int8)sbox[v21];
ix = (ix + (unsigned __int8)tbox[v21] + v22) % 256;
sbox[v21++] = sbox[ix];
sbox[ix] = v22;
}
while ( v21 != 256 ); // sbox初始化
app_password_length = strlen(app_password); // 就是解密数组密码的字符
app_password_length_loc = app_password_length;
new_base64_index = (unsigned __int8)rc4_key4[3];
pwd_b64_bitlen = 8 * (3 - -3 * (app_password_length / 3));
new_base64_length = new_base64_index + pwd_b64_bitlen / 6;
new_base64 = malloc(new_base64_length + 1); // 分配新的base64需要使用的字节。
if ( app_password_length_loc ) // app输入的密码长度
{
txi = 0;
pwd_idx = 0; // 循环体index
txj = 0;
v44 = new_base64_index;
do
{
txi = (txi + 1) % 256; // RC4 算法的产生密钥流循环体开始
tx = (unsigned __int8)sbox[txi];
txj = (txj + tx) % 256;
sbox[txi] = sbox[txj];
sbox[txj] = tx;
Rc4_T = (char *)(unsigned __int8)sbox[txi];
needTodoBase64 = sbox[(unsigned __int8)(tx + (_BYTE)Rc4_T)] ^ app_password[pwd_idx];// Base64内嵌在RC4的内部
// 先线程一个RC4加密后的字符
// 后面用这个字符进行Base64的解码操作
// Base64魔改的算法:
// 1:Base64 字典被替换
// 2:Base最后以为“=”会被替换成“;”
// 3: 对特定字符进行异或操作.每4个字符中第0个与0x6异或,第0个与0xF异或
// switch (i%4){
// case 0:
// base64char = (char) (iAscii ^0x07);
// break;
// case 2:
// base64char = (char) (iAscii ^0xF);
// break;
// default:
// base64char =encodeChars[i];
// }
if ( pwd_idx && (v27 = 2863311531u * (unsigned __int64)pwd_idx >> 32, v37 = 3 * (pwd_idx / 3), v37 != pwd_idx) )// 逗号运算符是指在C语言中,多个表达式可以用逗号分开,
// 其中用逗号分开的表达式的值分别结算,但整个表达式的值是最后一个表达式的值。
//
// 影响结果:
// false:
// 其实就是index/3(取摸)!=0
//
// true:
// index/3 =0
// v37:index可以是3的最大倍数,0,3,6,9,12...
// index=3,则v37=3;index =6,则v37=6;index=7,则v37=6
{
v31 = pwd_idx == 1; // 是否是appPassword[1]字符
if ( pwd_idx != 1 )
v31 = v37 + 1 == pwd_idx;
if ( v31 ) // 判断index%3=1的情况 ,即1,4,7,10
{
retn = base64_table; // v34第一次=0x33(51);
// base64_table:存放的是base64的table字典字符64+1个
// base64_table:(字符长度是64+1):!:#$%&()+-*/`~_[]{}?<>,.@^abcdefghijklmnopqrstuvwxyz0123456789\';
new_base64[v44 + pwd_idx] = base64_table[(unsigned __int8)new_base64[v44 + pwd_idx] | ((unsigned int)needTodoBase64 >> 4)];
Rc4_T = &new_base64[v44 + pwd_idx];
v27 = 4 * needTodoBase64 & 0x3C; // 0x3C =60 ,即111100二进制
Rc4_T[1] = v27;
if ( pwd_idx + 1 >= app_password_length_loc )
goto LABEL_53; // 跳转到程序的倒数第2个Lable
}
else
{
v33 = pwd_idx == 2; // index%3=2的情况 2,5,8,11....
if ( pwd_idx != 2 )
v33 = v37 + 2 == pwd_idx;
if ( v33 )
{
Rc4_T = (char *)(needTodoBase64 & 0xC0);// 0xC0 =192即11000000二进制,保留最高2bit
v34 = v44++ + pwd_idx;
new_base64[v34] = base64_table[(unsigned __int8)new_base64[v34] | ((unsigned int)Rc4_T >> 6)] ^ 0xF;// 0xf=15 ,即二进制1111
// 这里异或好像是对Base64模拟的多余操作
v27 = (int)&new_base64[v34];
*(_BYTE *)(v27 + 1) = base64_table[needTodoBase64 & 0x3F];// 0x3f=63 即111111二进制
}
}
}
else
{
new_base64[v44 + pwd_idx] = base64_table[(unsigned int)needTodoBase64 >> 2] ^ 7;// 7的二进制四111,这里对base64进行了魔改操作
Rc4_T = &new_base64[v44 + pwd_idx];
v27 = 16 * needTodoBase64 & 0x30; // 0x30=48,即二进制110000bit
Rc4_T[1] = v27;
if ( pwd_idx + 1 >= app_password_length_loc )// 最后一位
{
v38 = base64_table[v27];
*((_WORD *)Rc4_T + 1) = 15163;
goto LABEL_43;
}
}
++pwd_idx;
} // end for if ( v26_app_password_length ) 内部的do
while ( pwd_idx < app_password_length_loc );
} // end if ( v26_strText_length ) 输入密码lenght>0
// 这是时RC4-04部分的算法“产生密钥流”
// v28_new_base64:同时结合if部分逐个对RC4生成的结果做魔改的Base64运算
while ( 1 )
{
if ( pwd_b64_bitlen )
{ // v45 是根据输入密码长度计算,比如密码:12345678;v45=0x48u(即78)
retn = (_BYTE *)(&dword_0 + 1); // retn =1
Rc4_T = (char *)new_base64_length;
v39 = &byte_24E8; // v41指向的char* = 0x20(即空格)+"{9*8ga*l!Tn?@#fj'j$\g;;"
do
{
v27 = (unsigned __int8)new_base64[new_base64_index++];
v40 = (unsigned __int8)*v39++;
if ( v40 != v27 )
retn = 0;
}
while ( new_base64_index < new_base64_length );// v44 =0x3Fu
}
else
{
retn = (_BYTE *)(&dword_0 + 1);
}
new_base64 = (_BYTE *)(_stack_chk_guard - v48);
if ( _stack_chk_guard == v48 )
break;
LABEL_53:
v38 = retn[v27];
Rc4_T[2] = 52; // 输入密码:12345678,这里是:wk4.
LABEL_43:
Rc4_T[1] = v38;
}
return (unsigned __int8)retn; // sub_784 方法返回只有这么一个位置。我们侧重分析如何让该方法返回真即可
接下来我分段分析,我把每一段都分别用python写了一个单独的函数
8.1、RC4密钥去掉-
# RC4密钥去掉-
# return 650f909c721736479331c82df8b98e98
def getRc4Key2():
rc4key2 = ""
for i in range(len(rc4key)):
value = rc4key[i]
if ( ord(value) != 0x2d): # 字符串 转 ascii码
rc4key2 += value
return rc4key2
8.2、
# return 89e89b8f-d28c-1339-7463-7127c909f056
def getRc4Key3(key):
keylen = len(key)
rc4key2value1 = 0xFFFFFFF8
rc4key2value2 = 0x0
rc4key3 = ""
while keylen > 0:
temp = rc4key2value1 >> 2
temp2 = rc4key2value2 | temp
if temp2 <= 3:
rc4key3 += chr(0x2d)# ascii码 转 字符串
keylen -= 1
rc4key3 += key[keylen]
rc4key2value2 += 0x40000000
rc4key2value2 = fixedint.UInt32(rc4key2value2)
rc4key2value1 += 1
rc4key2value1 = fixedint.UInt32(rc4key2value1)
return rc4key3
8.3、
# 算法参数01:unk_23DE(10位长度) =2409715836
# 算法参数02:unk_23D8(16位长度)=dbeafc2409715836
# return 36f36b3c-a03e-4996-8759-8408e626c215
def getRc4Key4(key):
keylen = len(key)
keypos = 0
retnkey = ""
unk_23DE = "2409715836"
unk_23D8 = "dbeafc2409715836"
while keylen > 0:
value = key[keypos]
valueascii = ord(value)
value97 = valueascii - 97
value48 = valueascii - 48
if (value97 <= 5) and (value97 >= 0):
value = unk_23D8[value97]
elif (value48 <= 9) and (value48 >= 0):
value = unk_23DE[value48]
retnkey += value
keylen -= 1
keypos += 1
return retnkey
8.4、根据密钥生成临时的数据key
# 根据密钥生成临时的数据key
# 计算RC4 的临时256自己T向量,公式:iK[i]=(byte)aKey.charAt((i % aKey.length()));
def getRc4Key5(key):
tbox = ""
pos = 0
keylen = len(key)
while pos < 256:
tbox += key[pos % keylen]
pos += 1
return tbox
8.5、sbox初始化
def getSboxK(key):
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]
ix = ord(key[0]) - 0x29
Sbox[0] = Sbox[ix]
Sbox[ix] = 0xd7
sboxpos = 1
while sboxpos < 256:
value = Sbox[sboxpos]
ix = (ix + ord(key[sboxpos]) + value)%256
Sbox[sboxpos] = Sbox[ix]
Sbox[ix] = value
sboxpos += 1
return Sbox
8.6、接下来就是 RC4 + base64混合加密的地方了
def getNewBase64(password,tbox,rc4key4):
password_length = len(password)
b64_salt_size = ord(rc4key4[3])
# 空数组
new_base64 = []
if password_length <= 0:
return
txi = 0
pwd_idx = 0
txj = 0
new_base64_tmpValue = 0
while pwd_idx < password_length:
txi = (txi + 1) % 256
tx = tbox[txi]
txj = (txj + tx) % 256
tbox[txi] = tbox[txj]
tbox[txj] = tx
Rc4_T = tbox[txi]
tboxIndex = byteToInteger(tx + Rc4_T)
needTodoBase64 = byteToInteger(tbox[tboxIndex] ^ ord(password[pwd_idx]))
pwdtem = pwd_idx % 3
if pwdtem == 0:
base64table_index = needTodoBase64 >> 2
base64table_value = ord(base64table[base64table_index])
base64table_tmp = base64table_value ^ 7
new_base64.append(base64table_tmp)
needbaser64_tmp = 16 * needTodoBase64
new_base64_tmpValue = needbaser64_tmp & 0x30
elif pwdtem == 1:
base64table_index_temp = needTodoBase64 >> 4
base64table_index = base64table_index_temp + new_base64_tmpValue
new_base64.append(ord(base64table[base64table_index]))
needbaser64_tmp = needTodoBase64 << 2
new_base64_tmpValue = needbaser64_tmp & 0x3C
pwd_idx_tem = pwd_idx + 1
if pwd_idx_tem == password_length:
new_base64.append(ord(base64table[new_base64_tmpValue]))
new_base64.append(0x34)
elif pwdtem == 2:
needbaser64_tmp2 = needTodoBase64 & 0xC0
b64_salt_size = b64_salt_size + 1
base64table_index_temp = needbaser64_tmp2 >> 6
base64table_index = new_base64_tmpValue + base64table_index_temp
base64table_value = ord(base64table[base64table_index])
base64table_tmp = base64table_value ^ 0xf
new_base64.append(base64table_tmp)
needbaser64_tmp2 = needTodoBase64 & 0x3F
new_base64_tmpValue = ord(base64table[needbaser64_tmp2])
new_base64.append(new_base64_tmpValue)
pwd_idx = pwd_idx + 1
return new_base64
9、分析文档及代码地址:https://github.com/dadaoshizhong/rc4-base64-/upload
10、参考
https://bbs.pediy.com/thread-262472.htm
https://bbs.pediy.com/thread-250348-1.htm
http://blog.syang.xyz/2019/04/kanxue-transformer/
https://bbs.pediy.com/thread-250413.htm
https://xz.aliyun.com/t/4614
[课程]Android-CTF解题方法汇总!