腾讯游戏安全移动赛题Tencent2016A

App信息

包名:com.tencent.tencent2016a

Java层分析

MainActivity

校验name和code的函数在c类里

c

NativeCheckRegister是校验name和code的

NativeCheckRegister

发现NativeCheckRegister是一个native函数,进入so进行分析。

SO分析

定位NativeCheckRegister函数位置

NativeCheckRegister

类型修复及函数重命名之后的NativeCheckRegister函数如下

check

类型修复及函数重命名后的check函数

c 复制代码
bool __fastcall check(const char *name, char *code)
{
  signed int namelength; // r5
  _BOOL4 result; // r0
  int i; // r4
  _DWORD *nametempaddr; // r7
  int nametempresult; // r3
  int j; // r4
  int codetemp; // r1
  _DWORD nameresultarray[5]; // [sp+18h] [bp-458h] BYREF
  _DWORD coderesultarray[5]; // [sp+2Ch] [bp-444h] BYREF
  _BYTE namemix[20]; // [sp+40h] [bp-430h] BYREF
  _BYTE codeResult[1052]; // [sp+54h] [bp-41Ch] BYREF

  namelength = j_strlen(name);
  if ( (namelength - 6) > 0xE )                 // 校验name长度范围[6:20]
    return 0;
  j_memset(namemix, 0, sizeof(namemix));
  for ( i = 0; i != 16; ++i )                   // 基于name生成一个混淆数据
  {
    nametempaddr = &namemix[i];
    nametempresult = name[i % namelength] * (i + 20160126) * namelength;// 基于 name 的字符生成一个与 name 相关的混淆数据,存入namemix
    *nametempaddr += nametempresult;
  }
  j_memset(codeResult, 0, 0x400u);
  if ( codeEnc1(code) > 1024 || codeEnc2(codeResult, code) != 20 )
    return 0;
  j_memset(nameresultarray, 0, sizeof(nameresultarray));
  j_memset(coderesultarray, 0, sizeof(coderesultarray));
  for ( j = 0; j != 5; ++j )
  {
    codetemp = *&codeResult[j * 4];
    nameresultarray[j] = *&namemix[j * 4] / 10;
    coderesultarray[j] = codetemp;
  }
  result = 0;
  if ( coderesultarray[4] + nameresultarray[0] == coderesultarray[2]
    && coderesultarray[4] + nameresultarray[0] + nameresultarray[1] == 2 * coderesultarray[4]
    && nameresultarray[2] + coderesultarray[3] == coderesultarray[0]
    && nameresultarray[2] + coderesultarray[3] + nameresultarray[3] == 2 * coderesultarray[3] )
  {
    return nameresultarray[4] + coderesultarray[1] == 3 * nameresultarray[2];  // 返回值为1则验证成功
  }
  return result;
}

根据以上信息,当满足以下条件,程序验证成功

复制代码
coderesultarray[4] + nameresultarray[0] == coderesultarray[2]
coderesultarray[4] + nameresultarray[0] + nameresultarray[1] == 2 * coderesultarray[4]
nameresultarray[2] + coderesultarray[3] == coderesultarray[0]
nameresultarray[2] + coderesultarray[3] + nameresultarray[3] == 2 * coderesultarray[3]
nameresultarray[4] + coderesultarray[1] == 3 * nameresultarray[2]

变换,整理得到如下内容:

复制代码
coderesultarray[4] + nameresultarray[0] == coderesultarray[2]
nameresultarray[0] + nameresultarray[1] == coderesultarray[4]
nameresultarray[2] + coderesultarray[3] == coderesultarray[0]
nameresultarray[2] + nameresultarray[3] == coderesultarray[3]
nameresultarray[4] + coderesultarray[1] == 3 * nameresultarray[2]

由此可知:

  • coderesultarray[0]可由nameresultarray[2]+nameresultarray[2]+nameresultarray[3]推导
  • coderesultarray[1]可由3 * nameresultarray[2]-nameresultarray[4]推导
  • coderesultarray[2]可由nameresultarray[0] + nameresultarray[1]+nameresultarray[0]推导
  • coderesultarray[3]可由nameresultarray[2] + nameresultarray[3]推导
  • coderesultarray[4]可由nameresultarray[0] + nameresultarray[1]推导

分析

由以上信息,我们可以知道,由name->namemix->nameresultarray->coderesultarray

还知道code->codeResult->coderesultarray

猜测可通过coderesultarray还原code

接下来分析code的两个处理函数codeEnc1和codeEnc2

codeEnc1

a456789


观察数据窗口

发现末尾有一串Base64表,盲猜会出现Base64编码

经过分析,codeEnc1函数的功能是对输入的code进行校验,返回一个与字符串长度和规则相关的值,然后与1024进行比较。

codeEnc2

处理过后的codeEnc2函数如下

c 复制代码
int __fastcall codeEnc2(char *codeResult, char *code)
{
  char *Cocode; // r3
  int Cocode_code; // r3
  int v4; // r2
  int v5; // r5
  char *codeResultTemp; // r3
  int v7; // r3
  int v8; // r6

  Cocode = code;
  do
    ++Cocode;
  while ( a456789[*(Cocode - 1)] <= 63u );
  Cocode_code = Cocode - code;                  // Cocode_code是Cocode-code
  v4 = Cocode_code - 1;
  v5 = 3 * ((Cocode_code + 2) / 4);
  while ( 1 )
  {
    codeResultTemp = codeResult;
    if ( v4 <= 4 )                              // 判断剩余字符是否小于4字节
      break;
    v4 -= 4;
    *codeResult = (a456789[code[1]] >> 4) | (4 * a456789[*code]);
    codeResult[1] = (a456789[code[2]] >> 2) | (16 * a456789[code[1]]);
    v7 = code[2];
    v8 = code[3];
    code += 4;
    codeResult[2] = (a456789[v7] << 6) | a456789[v8];
    codeResult += 3;
  }
  if ( v4 > 1 )
  {
    *codeResult = (a456789[code[1]] >> 4) | (4 * a456789[*code]);
    if ( v4 == 2 )
    {
      codeResultTemp = codeResult + 1;
    }
    else
    {
      codeResult[1] = (a456789[code[2]] >> 2) | (16 * a456789[code[1]]);
      if ( v4 == 4 )
      {
        codeResultTemp = codeResult + 3;
        codeResult[2] = (a456789[code[2]] << 6) | a456789[code[3]];
      }
      else
      {
        codeResultTemp = codeResult + 2;
      }
    }
  }                                             // 自实现过Base64编码的人在这里就不难看出来这里是Base64解码算法
  *codeResultTemp = 0;
  return v5 - (-v4 & 3);
}

这是一个Base64解码算法

hook验证

利用frida进行hook

js 复制代码
function hook_native(){
    var soAddr = Module.findBaseAddress("libCheckRegister.so");
    var codeEnc2 = soAddr.add(0x1499)
    console.log(codeEnc2)
    Memory.protect(codeEnc2, 0x1000, 'rwx');
    Interceptor.attach(codeEnc2,{
        onEnter:function(args){
            console.log("arg0:",hexdump(args[0]));
            console.log("arg1:",hexdump(args[1]));
            this.codeResult = args[0];
        },
        onLeave:function(ret){
            console.log("codeResult:",hexdump(this.codeResult));
            console.log("ret:",ret)
            this.ret=1
        }
    })
}
hook_native()

输出

复制代码
arg0:            
0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
ffdc62f4  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
ffdc6304  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
ffdc6314  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
arg1:
0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
deb247a0  31 32 33 34 35 36 37 38 00 00 00 00 00 00 00 00  12345678........
deb247b0  00 00 00 00 28 00 00 00 00 00 00 00 00 00 00 00  ....(...........
codeResult: 
0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
ffdc62f4  d7 6d f8 e7 ae fc 00 00 00 00 00 00 00 00 00 00  .m..............
ffdc6304  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
ffdc6314  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
ret: 0x6

如下证明codeEnc2是标准的Base64解码算法

分析

这里弄明白了,code经过Base64解码得到codeResult,codeResult部分数据经过Base64解码得到coderesultarray。

重新捋一遍流程

首先,我们输入name,name经过混淆得到namemix,namemix的前20字节经过处理得到nameresultarray,然后由nameresultarray推导出coderesultarray,coderesultarray经过Base64编码得到code。

C++还原

c++ 复制代码
#include <iostream>
#include <cstring>
using namespace std;

// 定义 Base64 编码字符表
string base64_chars =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz"
        "0123456789+/";

// Base64 编码函数,将字节数组编码为 Base64 格式的字符串
string base64_encode(unsigned char* bytes_to_encode, unsigned int in_len) {
    std::string ret; // 存储编码后的结果
    int i = 0;
    int j = 0;
    unsigned char char_array_3[3]; // 临时存储3个字节
    unsigned char char_array_4[4]; // 临时存储4个编码后的字符

    // 处理输入数据,3个字节一组进行编码
    while (in_len--) {
        char_array_3[i++] = *(bytes_to_encode++);
        if (i == 3) {
            // 将3个字节转换为4个6位的Base64字符索引
            char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
            char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
            char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
            char_array_4[3] = char_array_3[2] & 0x3f;

            // 根据索引从Base64字符表中获取字符
            for (i = 0; (i < 4); i++)
                ret += base64_chars[char_array_4[i]];
            i = 0;
        }
    }

    // 处理剩余的不足3字节的数据
    if (i) {
        for (j = i; j < 3; j++)
            char_array_3[j] = '\0'; // 用\0填充

        char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
        char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
        char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
        char_array_4[3] = char_array_3[2] & 0x3f;

        for (j = 0; (j < i + 1); j++)
            ret += base64_chars[char_array_4[j]];

        while ((i++ < 3))
            ret += '='; // 用=填充
    }

    return ret;
}

int main() {
    char name[20] = {0}; // 用于存储用户输入的姓名
    printf("Please input name:");
    cin >> name; // 输入姓名

    int namelength = strlen(name); // 计算姓名长度
    cout << "namelength:" << namelength << endl;

    char* temppointer = NULL; // 临时指针
    int namemix[5] = {0}; // 混淆后的结果数组

    // 生成混淆数据
    for (int i = 0; i < 16; i++) {
        temppointer = (char*)namemix + i; // 将指针指向namemix数组中的第i字节

        // 根据姓名字符和索引计算一个混淆数
        int tempnumber = name[i % namelength] * (i + 20160126) * namelength;

        *(int*)temppointer += tempnumber; // 将计算结果写入对应位置
    }

    // 输出混淆数据
    //cout << "namemix:" << endl;
    for (int i = 0; i != 5; i++) {
        //cout << namemix[i] << endl;
    }

    // 对混淆数据进一步处理,生成结果数组
    int nameresultarray[5] = {0};
    for (int i = 0; i < 5; i++) {
        nameresultarray[i] = namemix[i] / 10; // 每个值缩小10倍
    }

    // 输出结果数组
    //cout << "nameresultarrat" << endl;
    for (int i = 0; i < 5; i++) {
        //cout << nameresultarray[i] << endl;
    }

    // 根据结果数组生成编码数组
    int coderesultarray[5] = {0};
    coderesultarray[0] = nameresultarray[2] + nameresultarray[2] + nameresultarray[3];
    coderesultarray[1] = 3 * nameresultarray[2] - nameresultarray[4];
    coderesultarray[2] = nameresultarray[0] + nameresultarray[1] + nameresultarray[0];
    coderesultarray[3] = nameresultarray[2] + nameresultarray[3];
    coderesultarray[4] = nameresultarray[0] + nameresultarray[1];

    // 输出编码数组
    //cout << "coderesultarray:" << endl;
    for (int i = 0; i < 5; i++) {
        // 输出为16进制格式
        //printf("%x\n", coderesultarray[i]);
    }

    // 使用 Base64 对编码数组进行编码
    string encodeStr = base64_encode((unsigned char*)coderesultarray, 20);
    cout << "Code is :" << encodeStr;

    return 0;
}

验证成功

参考文章

https://blog.csdn.net/z_Fuck/article/details/116010636

https://bbs.kanxue.com/thread-250740.htm

相关推荐
凌峰的博客7 分钟前
基于深度学习的图像安全与隐私保护研究方向调研(上)
安全
是喵斯特ya11 分钟前
JNDI注入漏洞分析
java·安全
ZeroNews内网穿透11 分钟前
EasyNode 结合 ZeroNews,实现远程管理服务器
运维·服务器·网络协议·安全·http
UWA18 分钟前
GPM 2.0全新功能发布|GPU精准监测 + 精细化运营,重构游戏性能管控新范式
人工智能·游戏·性能优化·重构·游戏开发·uwa
向宇it1 小时前
【unity游戏开发——网络】使用Unity+PurrNet+Heathens+Steam,在 Unity 中通过 Steam与你的朋友建立联系
网络·游戏·unity·c#·游戏引擎·steam
Black蜡笔小新1 小时前
视频融合平台EasyCVR构筑智慧小区安全防护与智能管理的数字底座
安全·音视频
Hilaku2 小时前
2025快手直播至暗时刻:当黑产自动化洪流击穿P0防线,我们前端能做什么?🤷‍♂️
前端·javascript·安全
思通数据2 小时前
AI智能预警系统:矿山、工厂与油气站安全管理架构浅析
人工智能·深度学习·安全·目标检测·机器学习·计算机视觉·架构
HUST2 小时前
C 语言 第七讲:数组和函数实践:扫雷游戏
c语言·开发语言·数据结构·vscode·算法·游戏·c#
数字护盾(和中)2 小时前
BAS模拟入侵攻击系统:前置防控核心,守护企业网络安全
网络·安全·web安全