听说这个是这个题是2016年腾讯游戏移动安全赛的题
主要是注册机的编写
以前自己学习拿来练习ARM汇编和算法逆向的
自己写的ARM的汇编的就不贴了 这里主要是F5后的
夜神模拟器安装apk 输入name和code
打开androidkiller搜索字符串Check Fail!
找到关键点,发现了一个NativeCheckRegister
找到NativeCheckRegister的声明的地方
我们发现它是一个Native函数,在so层,因此我们只能去so层用ida去分析
libCheckRegister.so 拖入IDA中
找到相关函数,因为函数不多我们可以直接看,如果函数多可以用ctrl+f搜索
双击进去
由于是jni函数,因此我们导入相关结构体并重命名
按insert快捷键,导入JNI相关结构体
参数,变量,函数名都重命名得到如下所示
我们重命名的check里面就是我们想要的算法了
点击进去具体分析
首先也是参数变量函数名重新命名
我们发现这里识别错误 数组大量使用了[j*4] 我们可以将数组类型转化为int
最后我们得到代码是
我们这里梳理下流程逻辑和思路
s[] <--用户名name
v15[]<--密码pwd
v12[]<--s[]
v13[]<--v15[]
v13[]<--v12[]
根据上面的逻辑我们可以得到求解过程
用户名name -> s[] -> v12[] -> v13[]->v15[]->密码pwd
那么v12[]和v13[]的关系怎么得到 ?
v15[]->pwd怎么获得?
那就要我们分析上面那两个函数了
一开始看到头都大了 这个算法的计算量又大了
不过当看到一个数组的时候,立马就点进去看看有什么特征
里面除了一些0x40外 其他的字符特别像base64的表
即使是base64表也不能保证这个表没有变形
还是看代码吧
解码的具体代码 里面注释了详细的解码过程
这里和上个函数的开头差不多,通过我们分析得知这是获得解码后的长度
写注册机前再梳理一遍
v12[]可以得到v13[]
v13[0]=2v12[2]+v12[3]
v13[1] <= 3 v12[2]-v12[4]
v13[2]=2*v12[0]+v12[1]
v13[3]=v12[2] + v12[3]
v13[4]=v12[0]+v12[1]
v13[]得到v15[]
v15[]通过encodebase64得到密码
注册机的编写心得:
练习的时候还是要少用F5 多看ARM汇编,这才是基本功
int __fastcall check(const char *name, char *pwd)
{
char *v2; // r6@1
signed int v3; // r5@1
int result; // r0@2
signed int v5; // r4@3
char *v6; // r7@4
int v7; // r3@4
int v8; // r4@6
int j; // r4@7
int v10; // r1@8
const char *v11; // [sp+Ch] [bp-464h]@1
int v12[5]; // [sp+18h] [bp-458h]@7
int v13[5]; // [sp+2Ch] [bp-444h]@7
int s[5]; // [sp+40h] [bp-430h]@3
int v15[234]; // [sp+54h] [bp-41Ch]@5
int v16; // [sp+454h] [bp-1Ch]@1
v2 = pwd;
v11 = name;
v16 = _stack_chk_guard; // 堆栈检查 用于保护堆栈
v3 = j_j_strlen(name); // 求用户名长度
if ( (unsigned int)(v3 - 6) > 0xE )
goto LABEL_18; // 这里我们得到成立的条件是 userLength-6<=14
j_j_memset(s, 0, 0x14u); // 数组置0
v5 = 0; // 循环计数器 相当于i 这里我们也重命名一下
do
{
v6 = (char *)s + v5; // 对数组s[]赋值
v7 = v11[v5 % v3] * (v5 + 20160126) * v3; // v7=user[i%userLength]*(i+20160126)*userLength
++v5; // i++
*(_DWORD *)v6 += v7; // s[i]+=user[i%userLength]*(i+20160126)*userLength
}
while ( v5 != 16 ); // i<16
j_j_memset(v15, 0, 0x400u); // 数组置0
if ( sub_146C(v2) > 1024 || (v8 = sub_1498((char *)v15, v2), v8 != 20) )
{ // 这个条件等一会分析
LABEL_18: // 现在先分析其他的 并且重命名给出具体的算法
result = 0;
}
else
{
j_j_memset(v12, 0, 0x14u); // 数组置0
j_j_memset(v13, 0, 0x14u); // 数组置0
j = 0; // j=0 计数器 我们还是重命名为j
do
{ // v12[] v13[] 赋值
v10 = v15[j]; // v10=v15[j] 刚才对pwd算出的数组
v12[j] = s[j] / 10; // v12[j]=s[j]/10
v13[j] = v10;
++j; // j++
}
while ( j != 5 ); // j<5
result = 0;
if ( v13[4] + v12[0] == v13[2] // v13[4]+v12[0]==v13[2]
&& v13[4] + v12[0] + v12[1] == 2 * v13[4] // v12[0]+v12[1]==v13[4]
&& v12[2] + v13[3] == v13[0] // v12[2]+v13[3]==v13[0]
&& v12[2] + v13[3] + v12[3] == 2 * v13[3] )// v12[2]+v12[3]==v13[3]
{
result = (unsigned int)(v12[4] + v13[1] - 3 * v12[2]) <= 0;
} // 若要返回true 则这个表达式必须返回true
// 也就是要这个等式成立
}
if ( v16 != _stack_chk_guard ) // 堆栈检查 用于保护堆栈
j_j___stack_chk_fail(result); // 如果堆栈发生错误 返回false
return result;
}
int __fastcall DecodeBase64(char *v15, char *pwd)
{
char *v2; // r3@1
int v3; // r3@3
int v4; // r2@3
int v5; // r5@3
char *v6; // r3@4
int v7; // r3@5
int v8; // r6@5
v2 = pwd; // 设i=0
do // 取出第一个不为0x40的值
++v2; // i++
while ( (unsigned __int8)g_arry[(unsigned __int8)*(v2 - 1)] <= 0x3Fu );// arry[pwd[i]]>0x3f
// pwd[i]在数组内对应的值是不是<=0x3f
v3 = v2 - pwd;
v4 = v3 - 1; // 求个数
v5 = 3 * ((v3 + 2) / 4); // v15的长度
while ( 1 )
{
v6 = v15;
if ( v4 <= 4 ) // 剩下最后4个字节或者不足4个字节的时候跳出循环
break;
v4 -= 4; // 长度 v4循环-4
*v15 = ((unsigned __int8)g_arry[(unsigned __int8)pwd[1]] >> 4) | 4 * g_arry[(unsigned __int8)*pwd];//
// v15[0]=g_arry[pwd[1]>>4|4*g_arry[pwd[0]]]
v15[1] = ((unsigned __int8)g_arry[(unsigned __int8)pwd[2]] >> 2) | 16 * g_arry[(unsigned __int8)pwd[1]];//
// v15[1]=g_arry[pwd[2]>>2|16*g_arry[pwd[1]];
v7 = (unsigned __int8)pwd[2];
v8 = (unsigned __int8)pwd[3];
pwd += 4; // pwd循环到下个四字节
v15[2] = (g_arry[v7] << 6) | g_arry[v8]; // v15[2]=(g_arry[pwd[2]<<6)|g_arry[pwd[3]]
v15 += 3; // v15跳到下三个字节
}
if ( v4 > 1 ) // 如果剩余1-3个字节的时候
{
*v15 = ((unsigned __int8)g_arry[(unsigned __int8)pwd[1]] >> 4) | 4 * g_arry[(unsigned __int8)*pwd];//
// v15[0]=g_arry[pwd[1]]>>4|4*g_arry[pwd[0]]
if ( v4 == 2 ) // 当剩两个字节的时候
{
v6 = v15 + 1; // v15[1]=0
}
else
{
v15[1] = ((unsigned __int8)g_arry[(unsigned __int8)pwd[2]] >> 2) | 16 * g_arry[(unsigned __int8)pwd[1]];//
// v15[1]=g_arry[pwd[2]]>>2|16*g_arry[pwd[1]]
if ( v4 == 4 ) // 当剩余4个字节
{
v6 = v15 + 3; // v15[3]=0
v15[2] = (g_arry[(unsigned __int8)pwd[2]] << 6) | g_arry[(unsigned __int8)pwd[3]];//
// v15[2]=g_arry[pwd[2]<<6|g_arry[pwd[3]]
}
else
{
v6 = v15 + 2; // v15[2]=0
}
}
}
*v6 = 0;
return v5 - (-v4 & 3); // 返回解码后的长度
}
int __fastcall sub_146C(char *pwd)
{
char *v1; // r3@1
v1 = pwd;
do
++v1; // i=0
while ( (unsigned __int8)g_arry[(unsigned __int8)*(v1 - 1)] <= 0x3Fu );// arry[pwd[i]]<=0x3f
return 3 * ((v1 - pwd + 2) / 4) + 1; // return 3*((i+2)/4)+1
}
do
{
v6 = (char *)s + v5;
v7 = v11[v5 % v3] * (v5 + 20160126) * v3;
++v5;
*(_DWORD *)v6 += v7;
}
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)