一、题目详细
说明:
Tencent2016A是一个简单的注册程序,见附件,请写一个注册机,点击Check即可显示是否通过, Name和Code可以手动输入,也可以通过扫描二维码输入。
要求:
- 注册机是KeyGen,不是内存注册机或文件Patch。
- 注册机必须可以运行在Android系统上, 可以是命令行程序,也可以是apk。
- 将编译好的注册机和分析文档,源代码一起上传。
- 提交方式:将分析文档,注册机工程源代码和编译好的注册机打包后提交。(无说明文档,视为无效方案)。
- 不得在论坛或群等场所讨论。
- 不得泄露KeyGenMe任何有效的姓名/序列号。
通过标准:在注册机中任意输入用户名,生成的Code均能成功通过检查,视为有效注册机。
二、分析过程
将apk程序在手机(模拟器)上运行,观看运行效果:
程序运行效果显示存在两个文本框以及两个按钮,首先进行查壳检测:
未加壳或未知厂商的壳,接下来我们使用android killer或JEB进行JAVA层的静态分析(我是用的JEB),看到如下图所示就可以确定该程序没有加壳:
进入MainActivity,并按下Tab键查看JEB所提供的该程序的java代码(重命名后的):
package com.tencent.tencent2016a;
import android.app.AlertDialog.Builder;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.a.f;
import android.widget.Button;
import android.widget.EditText;
import com.a.a.e.a.a;
import com.a.a.e.a.b;
public class MainActivity extends f {
Button check;
Button ScanQRCode;
EditText name;
EditText code;
AlertDialog.Builder builder;
static {
System.loadLibrary("CheckRegister");
}
public MainActivity() {
this.check = null;
this.ScanQRCode = null;
this.name = null;
this.code = null;
this.builder = null;
}
public native int NativeCheckRegister(String name, String code) {
}
@Override // android.support.a.a.k
protected void onActivityResult(int arg4, int arg5, Intent arg6) {
b v0 = a.a(arg4, arg5, arg6);
if(v0 != null) {
if(v0.a() == null) {
return;
}
String[] v0_1 = v0.a().split(":");
this.name.setText(v0_1[0]);
this.code.setText(v0_1[1]);
return;
}
super.onActivityResult(arg4, arg5, arg6);
}
@Override // android.support.v7.a.f
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
this.setContentView(0x7F040019); // layout:activity_main
this.name = (EditText)this.findViewById(0x7F0D0048); // id:editText
this.code = (EditText)this.findViewById(0x7F0D0049); // id:editText2
this.check = (Button)this.findViewById(0x7F0D004A); // id:button
this.builder = new AlertDialog.Builder(this);
this.ScanQRCode = (Button)this.findViewById(0x7F0D004B); // id:btnScan
this.builder.setMessage("Check Result");
this.builder.setTitle("Check Result");
this.builder.setPositiveButton("OK", new com.tencent.tencent2016a.a(this));
if(this.ScanQRCode != null) {
this.ScanQRCode.setOnClickListener(new com.tencent.tencent2016a.b(this));
}
if(this.check != null) {
this.check.setOnClickListener(new checkCode(this)); // check 按钮的监听触发事件
}
}
}
大致可以看出就是简单的获取name和code加上一些处理,跟进checkCode函数中去查看:
package com.tencent.tencent2016a;
import android.view.View.OnClickListener;
import android.view.View;
class checkCode implements View.OnClickListener {
final MainActivity mainactivity;
checkCode(MainActivity arg1) {
this.mainactivity = arg1;
super();
}
@Override // android.view.View$OnClickListener
public void onClick(View view) {
String name = this.mainactivity.name.getText().toString();
String code = this.mainactivity.code.getText().toString();
if(name.length() < 6 || name.length() > 20) {
this.mainactivity.builder.setMessage("Name too short(<6) or too long(>20)!");
}
else if(1 == this.mainactivity.NativeCheckRegister(name, code)) {
this.mainactivity.builder.setMessage("Check Success!");
}
else {
this.mainactivity.builder.setMessage("Check Fail!");
}
this.mainactivity.builder.create().show();
}
}
看到,程序对输入name进行判断,长度不能小于6或者长度不能大于20,满足条件后调用NativeCheckRegister函数,对name和code进行处理,返回1则返回成功,反之则是失败。其中NativeCheckRegister跟进发现是程序定义的native层函数,其函数在CheckRegister.so文件中。JEB分析到此到一段落。
对.so文件的分析我选用IDA进行分析,可以静态也可以动态。众所周知,apk文件格式相当于zip类似的压缩文件,首先对apk文件进行解压,找到lib文件夹,发现只有armeabi文件使用当中的.so文件---libCheckRegister.so。提取成功后用IDA打开,观看Functions Window发现当中存在相关函数如图所示:
进入发现能够进行F5,没有报错显示为:
学习过Jni编程后就会发现,Java_com_tencent_tencent2016a_MainActivity_NativeCheckRegister函数是Jni自动生成的函数名,其中参数第一个应当是JNIEnv*第二个参数如果函数是静态函数则是jclass如果是动态函数则是jobject,也许你会发现在java层传进来的参数是char类型,为啥这里变为了int(这里没错,原因是java到c之间转换引起的,可以不管)。所以为了将函数格式正确修改,需要引入JNI.h的头文件,该文件包含JNI自带函数的函数声明,引入方法在 :
IDA中 --- File ----- Load file ---- Parse c header file
或者
Ctrl + F9
接下来,找到Structures,进行结构体导入:
按Ins键 + 点击左下角 Add standard structure + 选择你刚才导入的结构体
_JNIEnv struct {const struct JNINativeInterface *functions;} Android ARM
成功后返回F5的界面,对函数进行重命名(快捷键N)和函数格式重定义操作(快捷键Y),操作完成后如下所示:
int __fastcall Java_com_tencent_tencent2016a_MainActivity_NativeCheckRegister(JNIEnv *Env, jobject jobject, int name, int code)
{
const char *vName; // r6
const char *vCode; // [sp+0h] [bp-20h]
int Result; // [sp+4h] [bp-1Ch]
vName = (*Env)->GetStringUTFChars(Env, name, 0);// 将int型的name转换成Char型
vCode = (*Env)->GetStringUTFChars(Env, code, 0);// 将int型的code转换成Char型
Result = NativeCheckRegister(vName, vCode); // NativeCheckRegister函数对name和code进行处理,返回结果为1则成功,反之失败
(*Env)->ReleaseStringUTFChars(Env, (jstring)name, vName);
(*Env)->ReleaseStringUTFChars(Env, (jstring)code, vCode);
return Result;
}
跟进NativeCheckRegister函数观看(对函数重定义加重命名后显示为):
bool __fastcall NativeCheckRegister(const char *name, char *code)
{
signed int nameLength; // r5
_BOOL4 result; // r0
int i; // r4
char *nameArrayAddress; // r7
int nameAlgResult; // r3
int j; // r4
int codeArray; // r1
int nameResultArray[5]; // [sp+18h] [bp-458h] BYREF
_DWORD codeResultArray[5]; // [sp+2Ch] [bp-444h] BYREF
char nameResult[20]; // [sp+40h] [bp-430h] BYREF
char codeResult[1052]; // [sp+54h] [bp-41Ch] BYREF
nameLength = j_strlen(name); // 取name的长度
if ( (unsigned int)(nameLength - 6) > 14 ) // 判断name长度是否大于6小于20
return 0;
j_memset(nameResult, 0, sizeof(nameResult)); // 对保存name第一次处理后的数组进行初始化
for ( i = 0; i != 16; ++i )
{
nameArrayAddress = &nameResult[i]; // 将nameResult的第i个数字地址赋给nameArrayAddress
nameAlgResult = (unsigned __int8)name[i % nameLength] * (i + 20160126) * nameLength;// 对输入的name相对应的字符进行处理计算返回值赋给nameAlgResult
*(_DWORD *)nameArrayAddress += nameAlgResult;// nameResult指向nameArrayAddress +=nameAlgResult
} // 处理后的数据存放在nameResult所指向的地址空间
j_memset(codeResult, 0, 0x400u); // 对codeResult进行初始化
if ( codeEnc1((int)code) > 1024 || codeEnc2(codeResult, (unsigned __int8 *)code) != 20 )// 调用codeEnc1函数判断code返回值小于1024同时函数codeEnc2返回值==20
return 0;
j_memset(nameResultArray, 0, sizeof(nameResultArray));// 初始化nameResultArray
j_memset(codeResultArray, 0, sizeof(codeResultArray));// 初始化codeResultArray
for ( j = 0; j != 5; ++j )
{
codeArray = *(_DWORD *)&codeResult[j * 4];
nameResultArray[j] = *(_DWORD *)&nameResult[j * 4] / 10;
codeResultArray[j] = codeArray; // 对codeResult和nameResult进行第二轮处理
}
result = false;
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 false
{
result = nameResultArray[4] + codeResultArray[1] == 3 * nameResultArray[2];// 关键条件 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(知道) --- nameResult(知道) --- nameResultArray(知道)
code(不知道) --codeResult(不知道) ---- codeResultArray(知道)
所以当前关键是了解上述两个对code处理的函数codeEnc1和codeEnc2,进入codeEnc1函数查看:
int __fastcall codeEnc1(char *code)
{
char *Ecode; // r3
Ecode = code;
do
++Ecode;
while ( (unsigned __int8)a456789[(unsigned __int8)*(Ecode - 1)] <= 63u );// 通过查表判断code是否在63u的范围中没
return 3 * ((Ecode - code + 2) / 4) + 1;
}
进入codeEnc2函数查看:
int __fastcall codeEnc2(char *codeResult, char *code)
{
char *Ecode; // r3
int numberBytes; // r3
int numberBytescode; // r2
int codeEnc1Result; // r5
char *EcodeResult; // r3
int v7; // r3
int v8; // r6
Ecode = code;
do
++Ecode;
while ( (unsigned __int8)a456789[(unsigned __int8)*(Ecode - 1)] <= 0x3Fu );// 和codeEnc1相似,判断输入字符范围
numberBytes = Ecode - code;
numberBytescode = numberBytes - 1;
codeEnc1Result = 3 * ((numberBytes + 2) / 4);
while ( 1 )
{
EcodeResult = codeResult;
if ( numberBytescode <= 4 )
break; // 判断剩余字符数是否小于四个字符
numberBytescode -= 4; // 循环一次字符数减少4
*codeResult = ((unsigned __int8)a456789[(unsigned __int8)code[1]] >> 4) | (4 * a456789[(unsigned __int8)*code]);// 加密操作一
codeResult[1] = ((unsigned __int8)a456789[(unsigned __int8)code[2]] >> 2) | (16 * a456789[(unsigned __int8)code[1]]);// 加密操作二
v7 = (unsigned __int8)code[2];
v8 = (unsigned __int8)code[3];
code += 4; // 每次循环输入4个字符 中的code值
codeResult[2] = (a456789[v7] << 6) | a456789[v8];
codeResult += 3; // 每次循环增加3个字符的codeResult
}
if ( numberBytescode > 1 )
{
*codeResult = ((unsigned __int8)a456789[(unsigned __int8)code[1]] >> 4) | (4 * a456789[(unsigned __int8)*code]);// 加密操作三
if ( numberBytescode == 2 )
{
EcodeResult = codeResult + 1;
}
else
{
codeResult[1] = ((unsigned __int8)a456789[(unsigned __int8)code[2]] >> 2) | (16 * a456789[(unsigned __int8)code[1]]);// 加密操作四
if ( numberBytescode == 4 )
{
EcodeResult = codeResult + 3;
codeResult[2] = (a456789[(unsigned __int8)code[2]] << 6) | a456789[(unsigned __int8)code[3]];// 加密操作五
}
else
{
EcodeResult = codeResult + 2;
}
}
} // 对最后不足4个字符的字符进行加密的方法
//
// 明显可以看出这是一个Base64算法,base64加密表就是a456789中
*EcodeResult = 0;
return codeEnc1Result - (-numberBytescode & 3);
}
通过上述代码,因为Base64加密字符由3到4,解密字符由4到3.所以判断是Base64解密,但是Base64解密存在Base64解密表,而代码中也确实存在查表操作,接下来将a456789对应的内存数据dump出来,进行查看,在IDA中显示为:
可以看出这明显不是我们所需要的Base64解密表,但是不要忘记在codeEnc1和codeEnc2都对a456789进行范围判断,接下来,对dump下的数据进行手动处理:
处理代码:
#include <stdio.h>
#include <Windows.h>
unsigned char data[193] = {
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x3E, 0x40, 0x40, 0x40, 0x3F,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0x40, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x40, 0x40, 0x40, 0x40, 0x40,
0x40, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x40, 0x40, 0x40, 0x40, 0x40,
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0x40
};
int main() {
int j = 0;
for (int i = 0; i < 193; i++)
{
if (data[i] <= 0x3F) {
printf(" 字符:%c,值为:%2x",i,data[i]);
j++;
printf(" ");
}
if (j % 10 == 0) {
printf("\n");
}
}
system("pause");
return 0;
}
得到效果是:
可以看到codeEnc1和codeEnc2都对a456789开头的处理结果就是将加密后的字符表解密成为Base64的密码表。所以注册机可以写成:
2016Tencent001.cpp
#include <stdio.h>
#include <Windows.h>
#include "Jni.h"
#include "base64.h"
signed int nameLength; // r5
BOOL result; // r0
int i; // r4
char *nameArrayAddress; // r7
int nameAlgResult; // r3
int j; // r4
int codeArray; // r1
int nameResultArray[5]; // [sp+18h] [bp-458h] BYREF
DWORD codeResultArray[5]; // [sp+2Ch] [bp-444h] BYREF
char nameResult[20]; // [sp+40h] [bp-430h] BYREF
char codeResult[1052]; // [sp+54h] [bp-41Ch] BYREF
BOOL nativeReguster(char* name);
/*
unsigned char data[193] = {
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x3E, 0x40, 0x40, 0x40, 0x3F,
0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0x40, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x40, 0x40, 0x40, 0x40, 0x40,
0x40, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x40, 0x40, 0x40, 0x40, 0x40,
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0x40
};
int main() {
int j = 0;
for (int i = 0; i < 193; i++)
{
if (data[i] <= 0x3F) {
printf(" 字符:%c,值为:%2x",i,data[i]);
j++;
printf(" ");
}
if (j % 10 == 0) {
printf("\n");
}
}
system("pause");
return 0;
}
*/
int main() {
char* name = (char*)malloc(sizeof(char)*256);
memset(name,0,sizeof(char)*256);
scanf_s("%s",name,256);
nativeReguster(name);
system("pause");
return 0;
}
BOOL nativeReguster(char* name) {
nameLength = strlen(name);
if ((unsigned int)(nameLength - 6) > 0xE)
return 0;
memset(nameResult, 0, sizeof(nameResult));
for (i = 0; i != 16; ++i)
{
nameArrayAddress = &nameResult[i];
nameAlgResult = (unsigned __int8)name[i % nameLength] * (i + 20160126) * nameLength;
*(DWORD *)nameArrayAddress += nameAlgResult;
}
for (j = 0; j != 5; ++j)
{
//codeArray = *(DWORD *)&codeResult[j * 4];
nameResultArray[j] = *(DWORD *)&nameResult[j * 4] / 10;
//codeResultArray[j] = codeArray;
}
codeResultArray[0] = nameResultArray[2] + nameResultArray[2] + nameResultArray[3];
codeResultArray[1] = 3 * nameResultArray[2] - nameResultArray[4];
codeResultArray[2] = 2*nameResultArray[0] + nameResultArray[1];
codeResultArray[3] = nameResultArray[2] + nameResultArray[3];
codeResultArray[4] = nameResultArray[0] + nameResultArray[1];
for (j = 0; j != 5; ++j)
{
codeArray = codeResultArray[j];
*(DWORD *)&codeResult[j * 4] = codeArray;
}
for (j = 0; j != 20; j++) {
//printf("%d\n", codeResult[j]);
}
printf("%s\n",base64_encode((unsigned char*)codeResult));
return TRUE;
}
Base64.h
#pragma once
#ifndef _BASE64_H
#define _BASE64_H
#include <stdlib.h>
#include <string.h>
unsigned char *base64_encode(unsigned char *str);
unsigned char *bae64_decode(unsigned char *code);
#endif
Base64Alg.cpp
/*base64.c*/
#include "base64.h"
unsigned char *base64_encode(unsigned char *str)
{
long len;
long str_len;
unsigned char *res;
int i, j;
//定义base64编码表
unsigned char *base64_table = (unsigned char*)"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
//计算经过base64编码后的字符串长度
str_len = strlen((const char*)str);
if (str_len % 3 == 0)
len = str_len / 3 * 4;
else
len = (str_len / 3 + 1) * 4;
res = (unsigned char*)malloc(sizeof(unsigned char)*len + 1);
res[len] = '\0';
//以3个8位字符为一组进行编码
for (i = 0, j = 0; i < len - 2; j += 3, i += 4)
{
res[i] = base64_table[str[j] >> 2]; //取出第一个字符的前6位并找出对应的结果字符
res[i + 1] = base64_table[(str[j] & 0x3) << 4 | (str[j + 1] >> 4)]; //将第一个字符的后位与第二个字符的前4位进行组合并找到对应的结果字符
res[i + 2] = base64_table[(str[j + 1] & 0xf) << 2 | (str[j + 2] >> 6)]; //将第二个字符的后4位与第三个字符的前2位组合并找出对应的结果字符
res[i + 3] = base64_table[str[j + 2] & 0x3f]; //取出第三个字符的后6位并找出结果字符
}
switch (str_len % 3)
{
case 1:
res[i - 2] = '=';
res[i - 1] = '=';
break;
case 2:
res[i - 1] = '=';
break;
}
return res;
}
unsigned char *base64_decode(unsigned char *code)
{
//根据base64表,以字符找到对应的十进制数据
int table[] = { 0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,62,0,0,0,
63,52,53,54,55,56,57,58,
59,60,61,0,0,0,0,0,0,0,0,
1,2,3,4,5,6,7,8,9,10,11,12,
13,14,15,16,17,18,19,20,21,
22,23,24,25,0,0,0,0,0,0,26,
27,28,29,30,31,32,33,34,35,
36,37,38,39,40,41,42,43,44,
45,46,47,48,49,50,51
};
long len;
long str_len;
unsigned char *res;
int i, j;
//计算解码后的字符串长度
len = strlen((const char*)code);
//判断编码后的字符串后是否有=
if (strstr((char*)code, (const char*)"=="))
str_len = len / 4 * 3 - 2;
else if (strstr((char*)code, "="))
str_len = len / 4 * 3 - 1;
else
str_len = len / 4 * 3;
res = (unsigned char*)malloc(sizeof(unsigned char)*str_len + 1);
res[str_len] = '\0';
//以4个字符为一位进行解码
for (i = 0, j = 0; i < len - 2; j += 3, i += 4)
{
res[j] = ((unsigned char)table[code[i]]) << 2 | (((unsigned char)table[code[i + 1]]) >> 4); //取出第一个字符对应base64表的十进制数的前6位与第二个字符对应base64表的十进制数的后2位进行组合
res[j + 1] = (((unsigned char)table[code[i + 1]]) << 4) | (((unsigned char)table[code[i + 2]]) >> 2); //取出第二个字符对应base64表的十进制数的后4位与第三个字符对应bas464表的十进制数的后4位进行组合
res[j + 2] = (((unsigned char)table[code[i + 2]]) << 6) | ((unsigned char)table[code[i + 3]]); //取出第三个字符对应base64表的十进制数的后2位与第4个字符进行组合
}
return res;
}
大致流程就是如此。
总结,破解思路:
1》程序是对输入的name进行一系列处理操作后,得到nameResultArray
2》通过nameResultArray生成程序对比的codeResultArray
3》分析程序可知codeResultArray是由code经过Base64解密后得到的codeResult数据,在进行了一部分处理后所得到。
4》将codeResultArray进行反向的对应操作在进行Base64加密就得到程序需要的code了。