首页
社区
课程
招聘
[原创]android算法逆向学习笔记之2016腾讯游戏安全移动赛题Tencent2016A(含注册机)
发表于: 2019-4-12 17:39 7866

[原创]android算法逆向学习笔记之2016腾讯游戏安全移动赛题Tencent2016A(含注册机)

2019-4-12 17:39
7866

听说这个是这个题是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期)

上传的附件:
收藏
免费 3
支持
分享
最新回复 (7)
雪    币: 11108
活跃值: (3040)
能力值: ( LV5,RANK:71 )
在线值:
发帖
回帖
粉丝
2
感谢分享。
2019-4-15 09:12
0
雪    币: 29183
活跃值: (63661)
能力值: (RANK:135 )
在线值:
发帖
回帖
粉丝
3
感谢分享~
2019-4-15 17:53
0
雪    币: 381
活跃值: (165)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
不错不错。
2019-4-17 19:05
0
雪    币: 725
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
感谢分享,正准备看这块呢。
2019-4-18 18:46
0
雪    币: 334
活跃值: (92)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
6
不明觉厉
2019-4-18 21:52
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
牛逼
2019-11-15 17:41
0
雪    币: 1114
活跃值: (1166)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
感谢分享!
2020-1-23 22:21
0
游客
登录 | 注册 方可回帖
返回
//