听说这个是这个题是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。最后我们得到代码是:
int __fastcall check(const char *name, char *pwd)
{
char *v2;
signed int v3;
int result;
signed int v5;
char *v6;
int v7;
int v8;
int j;
int v10;
const char *v11;
int v12[5];
int v13[5];
int s[5];
int v15[234];
int v16;
v2 = pwd;
v11 = name;
v16 = _stack_chk_guard;
v3 = j_j_strlen(name);
if ( (unsigned int)(v3 - 6) > 0xE )
goto LABEL_18;
j_j_memset(s, 0, 0x14u);
v5 = 0;
do
{
v6 = (char *)s + v5;
v7 = v11[v5 % v3] * (v5 + 20160126) * v3;
++v5;
*(_DWORD *)v6 += v7;
}
while ( v5 != 16 );
j_j_memset(v15, 0, 0x400u);
if ( sub_146C(v2) > 1024 || (v8 = sub_1498((char *)v15, v2), v8 != 20) )
{
LABEL_18:
result = 0;
}
else
{
j_j_memset(v12, 0, 0x14u);
j_j_memset(v13, 0, 0x14u);
j = 0;
do
{
v10 = v15[j];
v12[j] = s[j] / 10;
v13[j] = v10;
++j;
}
while ( j != 5 );
result = 0;
if ( v13[4] + v12[0] == v13[2]
&& v13[4] + v12[0] + v12[1] == 2 * v13[4]
&& v12[2] + v13[3] == v13[0]
&& v12[2] + v13[3] + v12[3] == 2 * v13[3] )
{
result = (unsigned int)(v12[4] + v13[1] - 3 * v12[2]) <= 0;
}
}
if ( v16 != _stack_chk_guard )
j_j___stack_chk_fail(result);
return result;
}
我们这里梳理下流程逻辑和思路
1、逻辑流程
s[]
v15[]
v12[]
v13[]
v13[]
2. 思路
① 我们的注册机是已知name求pwd
② 根据上面的逻辑我们可以得到求解过程
用户名name -> s[] -> v12[] -> v13[]->v15[]->密码pwd
③ 那么v12[]和v13[]的关系怎么得到 ?
④v15[]->pwd怎么获得?
那就要我们分析上面那两个函数了
分析 sub_1498发现这是个base64的解码函数
一开始看到头都大了 这个算法的计算量又大了
不过当看到一个数组的时候,立马就点进去看看有什么特征
里面除了一些0x40外,其他的字符特别像base64的表。
即使是base64表也不能保证这个表没有变形。还是看代码吧......
base64解码注释
解码的具体代码,里面注释了详细的解码过程:
int __fastcall DecodeBase64(char *v15, char *pwd)
{
char *v2;
int v3;
int v4;
int v5;
char *v6;
int v7;
int v8;
v2 = pwd;
do
++v2;
while ( (unsigned __int8)g_arry[(unsigned __int8)*(v2 - 1)] <= 0x3Fu );
v3 = v2 - pwd;
v4 = v3 - 1;
v5 = 3 * ((v3 + 2) / 4);
while ( 1 )
{
v6 = v15;
if ( v4 <= 4 )
break;
v4 -= 4;
*v15 = ((unsigned __int8)g_arry[(unsigned __int8)pwd[1]] >> 4) | 4 * g_arry[(unsigned __int8)*pwd];
v15[1] = ((unsigned __int8)g_arry[(unsigned __int8)pwd[2]] >> 2) | 16 * g_arry[(unsigned __int8)pwd[1]];
v7 = (unsigned __int8)pwd[2];
v8 = (unsigned __int8)pwd[3];
pwd += 4;
v15[2] = (g_arry[v7] << 6) | g_arry[v8];
v15 += 3;
}
if ( v4 > 1 )
{
*v15 = ((unsigned __int8)g_arry[(unsigned __int8)pwd[1]] >> 4) | 4 * g_arry[(unsigned __int8)*pwd];
if ( v4 == 2 )
{
v6 = v15 + 1;
}
else
{
v15[1] = ((unsigned __int8)g_arry[(unsigned __int8)pwd[2]] >> 2) | 16 * g_arry[(unsigned __int8)pwd[1]];
if ( v4 == 4 )
{
v6 = v15 + 3;
v15[2] = (g_arry[(unsigned __int8)pwd[2]] << 6) | g_arry[(unsigned __int8)pwd[3]];
}
else
{
v6 = v15 + 2;
}
}
}
*v6 = 0;
return v5 - (-v4 & 3);
}
sub_146C分析
这里和上个函数的开头差不多,通过我们分析得知这是获得解码后的长度:
int __fastcall sub_146C(char *pwd)
{
char *v1;
v1 = pwd;
do
++v1;
while ( (unsigned __int8)g_arry[(unsigned __int8)*(v1 - 1)] <= 0x3Fu );
return 3 * ((v1 - pwd + 2) / 4) + 1;
}
1、写注册机前再梳理一遍,name得到一个数组s[],可以由ida得到
do
{
v6 = (char *)s + v5;
v7 = v11[v5 % v3] * (v5 + 20160126) * v3;
++v5;
*(_DWORD *)v6 += v7;
}
2、由s[]可以得到v12[]
3、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]
4、v13[]得到v15[]
5、v15[]通过encodebase64得到密码
main.cpp
#include
#include
#include "base64.h"
#include
int main()
{
const unsigned char name[20] = {0};
char *v2;
signed int v3;
int result;
signed int v5;
char *v6;
int v7;
int v8;
int j;
int v10;
const unsigned char *v11;
int v12[5] = {0};
int v13[5] = {0};
int s[5] = {0};
int v15[234] = {0};
printf("请输入name:");
scanf_s("%s", name, 20);
v11 = name;
v5 = 0;
v3 = strlen((const char*)name);
do
{
v6 = (char *)s + v5;
v7 = v11[v5 % v3] * (v5 + 20160126) * v3;
++v5;
*(DWORD *)v6 += v7;
} while (v5 != 16);
j = 0;
do
{
v12[j] = s[j] / 10;
++j;
} while (j != 5);
v13[0] = 2 * v12[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];
j = 0;
do
{
v15[j] = v13[j];
++j;
} while (j != 5);
std::string encodeStr = base64_encode((unsigned char*)v15, 20);
const char* pwd = encodeStr.c_str();
printf("密码是:%s",pwd);
system("pause");
return 0;
}
base64.h
#pragma once
#include
std::string base64_encode(unsigned char const*, unsigned int len);
std::string base64_decode(std::string const& s);
base64.cpp
#include "base64.h"
#include
static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
static inline bool is_base64(unsigned char c) {
return (isalnum(c) || (c == '+') || (c == '/'));
}
std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) {
std::string ret;
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];
while (in_len--) {
char_array_3[i++] = *(bytes_to_encode++);
if (i == 3) {
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 (i = 0; (i < 4); i++)
ret += base64_chars[char_array_4[i]];
i = 0;
}
}
if (i)
{
for (j = i; j < 3; j++)
char_array_3[j] = '\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;
}
std::string base64_decode(std::string const& encoded_string) {
int in_len = encoded_string.size();
int i = 0;
int j = 0;
int in_ = 0;
unsigned char char_array_4[4], char_array_3[3];
std::string ret;
while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
char_array_4[i++] = encoded_string[in_]; in_++;
if (i == 4) {
for (i = 0; i < 4; i++)
char_array_4[i] = base64_chars.find(char_array_4[i]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (i = 0; (i < 3); i++)
ret += char_array_3[i];
i = 0;
}
}
if (i) {
for (j = i; j < 4; j++)
char_array_4[j] = 0;
for (j = 0; j < 4; j++)
char_array_4[j] = base64_chars.find(char_array_4[j]);
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
}
return ret;
}
① 算法逆向真的是体力活,坐不住根本分析不下,这里是F5后分析的比较简单,干看ARM汇编更吃体力。
② 常用加解密的算法还是要了解啊,最好熟悉,这样对逆向的帮助很大。
③ 注册机的编写心得:
④ 练习的时候还是要少用F5 多看ARM汇编,这才是基本功
⑤ 书读百遍其义自见,同样逆向百遍其义自见
https://bbs.pediy.com/user-768906.htm
本文由看雪论坛 lcdxsun 原创
转载请注明来自看雪社区
戳 立即购买!
公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com