Android逆向-2016Tencent ctf比赛第一轮第一题详细分析

4 篇文章 1 订阅
2 篇文章 0 订阅

一、题目详细

说明:

Tencent2016A是一个简单的注册程序,见附件,请写一个注册机,点击Check即可显示是否通过, Name和Code可以手动输入,也可以通过扫描二维码输入。

要求:

  1. 注册机是KeyGen,不是内存注册机或文件Patch。
  2. 注册机必须可以运行在Android系统上, 可以是命令行程序,也可以是apk。
  3. 将编译好的注册机和分析文档,源代码一起上传。
  4. 提交方式:将分析文档,注册机工程源代码和编译好的注册机打包后提交。(无说明文档,视为无效方案)。
  5. 不得在论坛或群等场所讨论。
  6. 不得泄露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了。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值