这道题主要涉及的知识点有base_58,凯撒密码,反反调试,也可以根暴力破解沾上边,我们从头开始分析一遍。
一开始的时候由于很多独立变量,所以首先分析是IDA识别错误,所以手动改v9的大小,再简单遍历全部代码,发现v9是一个最后比较的表。
按照习惯,先对整个代码进行一次名字替换和函数参数的类型重定义,来让代码更加简洁易看。
这个过程中我发现v9里存储了两段字符串,v9在被赋值时是“”式的赋值,所以结尾有一个自动补的0,再结合下文的加密,可以看出有两个加密只对第一段字符串进行了加密,而第一段字符串和第二段字符串又是各自独立作为元素运算,所以可以判断v9的第一段字符串是类似key的存在。我们重新定义类型和大小。
在准备工作完成后我们开始分析每一个加密函数
首先是一开始的一个小加密,对字符的前四位和后四位进行替换,解密就是再进行一次即可。
然后是第一个函数
熟悉的话可以一眼看出这是base58的加密方式,这里有一篇还算可以的讲base58的文章,可以看下
https://www.cnblogs.com/exmyth/p/17336498.html
我们先看下它是否有改动,先查看它通过索引加密的表,即我改后的base_58table数组,
那段绿色的是地址,双击跟进,发现它存储是一段64位的字符串
i5jLW7S0GX6uf1cv3ny4q8es2Q+bdkYgKOIT/tAxUrFlVPzhmow9BHCMDpEaJRZN,这段字符就是根据索引来加密的对照表,而它有64位,所以这是一个魔改版的base58加密,它是以base58的加密方式进行了一次由256进制转10进制再转64进制来通过表查找字符进行加密的base58.
base58的加密可以根据前文的链接了解一下,之后我从网上找了一份base_58的解密脚本,进行修改来使用。
如下
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
unsigned char* en_base58(unsigned char* input) // 编码
{
static char* nb58 = (char*)"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
size_t len = strlen((char*)input);
size_t rlen = (len / 2 + 1) * 3;
unsigned char* ret = (unsigned char*)malloc(rlen + len);
unsigned char* src = ret + rlen;
unsigned char* rptr = ret + rlen;
unsigned char* ptr, * e = src + len - 1;
size_t i;
memcpy(src, input, len);
while (src <= e)
{
if (*src)
{
unsigned char rest = 0;
ptr = src;
while (ptr <= e)
{
unsigned int c = rest * 256;
rest = (c + *ptr) % 58;
*ptr = (c + *ptr) / 58;
ptr++;
}
--rptr;
*rptr = nb58[rest];
}
else
{
src++;
}
}
for (i = 0; i < ret + rlen - rptr; i++)
ret[i] = rptr[i];
ret[i] = 0;
return ret;
}
bool de_base58(unsigned char* src)
{
static char b58n[] =// 解码表,它是根据索引和ASCII值来进行填写的。
{
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,
-1, 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,-1,-1,-1,-1,-1,
-1,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,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
};
size_t len = strlen((char*)src);
size_t rlen = (len / 4 + 1) * 3;
unsigned char* ret = (unsigned char*)malloc(rlen);
unsigned char* rptr = ret + rlen;
size_t i;
unsigned char* ptr;
for (i = 0; i < len; i++)
{
char rest = b58n[src[i]];
if (rest < 0)
{
free(ret);
return NULL;
}
for (ptr = ret + rlen - 1; ptr >= rptr; ptr--)//转换十进制和转化2556进制同时进行
{
unsigned int c = rest + *ptr * 64;//,用的是base58的思路,但是从64进制转换成十进制进制
*ptr = c % 256;//取余得字符
rest = c / 256;//除256,
}
if (rest > 0)
{
rptr--;
if (rptr < ret)
{
free(ret);
return NULL;
}
*rptr = rest;
}
}
for (i = 0; i < ret + rlen - rptr; i++)
{
ret[i] = rptr[i];
ret[i] = (ret[i] >> 4) | (ret[i] << 4 & 0xff);
}
ret[i] = 0;
memcpy(src, ret, strlen((char*)src));
}
int main()
{
char str[] = "CEJne0FzT0FPRlhUaEFCZ0A4RWlualtlan";
de_base58((unsigned char*)str);
printf("%s", str);
getchar();
return 0;
//代码来源于Eutop1a的文章https://blog.csdn.net/yidalipao1/article/details/126002691
}
这里先简要介绍一下几个修改点,首先是解码表的修改,然后是第80行的代码,我将×58改成了×64,原因上文也提到,是因为它的加密方式是256->10->64,所以这里有这一个修改点,然后我还将前面的字符移位的代码写在了里面。
然后是输入位数的问题,这里由于base58的加密方式,根据比较字符串是34位,可以得出输入是25位。34/1.37=24.8,四舍五入为25.
我们再分析这个加密函数的后半段,这里是一个随机数加密,首先它是通过循环和随机数得出一个用来和base58加密后的字符串进行异或的值,所以我们可以通过动调的方式来这个数直接跑出来,但是它这里有一个坑点,就是它设置了反调试,发现这一类反调试的话,有两个办法,一个是对你的关键数据进行快捷键x查看引用,看有没有奇怪的赋值,二就是查看有没有奇怪的函数。
这里造成反调试的是这个函数,我们查看汇编看它的操作,
这里有个关键操作,是对dword_4010进行赋值操作,在loc_1246这句的下面,我们查看引用的话,就会发现它是我们随机数加密里面的值,所以我们将这一句nop掉,在Edit—patch program—Apply patch to进行保存,这里如果在Linux里已经进行过动调,要注意把虚拟机里的该文件删掉,否则它动调还是用的那个文件。动调观察发现最后base58加密字符串要异或值是21。
然后我们base58和随机数的加密就搞定了,脚本就是上面那个。
我们看剩下的加密函数。
这个加密先是简单的加减和跟key数组异或,我们主要看下面的第二个和第三个加密函数,
第二个
经典的凯撒密码,但是用了j也就是外层循环作为移位量。修改函数后更直观
解密代码即如下
for (i =8;i>=0; i--)//en2
{
if (key[i] > 64 && key[i] <= 90)
{
key[i] = (key[i] - 65 - j+52) % 26 + 65;//我将+j改为-j,同时加上52(26*2)防止变成负数。由于取余的关系,所以不会有影响,有点像补码的感觉
}
else if (key[i] >= 97 && key[i] <= 122)
{
key[i] = (key[i] - 97 - j+52) % 26 + 97;//为什么是52呢?因为我试了26发现太小了,有的不行。
}
}
下一个加密代码
简单的首尾交换值,没什么好说的,在加密一次就是解密,所以解密脚本就是它本身,这两步加密都是单纯对key。
最后所有加密完后就是对字符串的比较
这里展示下完整的脚本
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int re(char a)
{//这一段代码是将原本杂乱的对应表进行一次重新对应,使得接下来编写base58解密代码的对应表是方便一点。
char ccc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
char ddd[] = "i5jLW7S0GX6uf1cv3ny4q8es2Q+bdkYgKOIT/tAxUrFlVPzhmow9BHCMDpEaJRZN";
for (int i = 0; i < 65; i++)
{
if (a == ddd[i])
return ccc[i];
}
}
int main()
{
char table[] = { 0x00000078, 0x0000005B, 0x00000056, 0x0000007A, 0x0000005D, 0x00000054, 0x00000025, 0x00000031,
0x00000020, 0x00000068, 0x0000003D, 0x00000064, 0xFFFFFF92, 0x00000076, 0x00000063, 0x0000007B,
0x00000059, 0x00000057, 0x00000021, 0xFFFFFF84, 0x00000057, 0x00000076, 0xFFFFFF87, 0x00000072,
0xFFFFFF84, 0xFFFFFF85, 0x00000070, 0xFFFFFF9E, 0x0000004F, 0x00000070, 0x00000072, 0xFFFFFF84,
0x00000057, 0xFFFFFF88 };
char key[] = "ujlpujlp";//这个是通过动调得出最后的key值
int i,j,n;
n = 0;
int v3;
int result;
int len = 8;
for (j =33; j>=0; --j)
{
for (i = 0; ; ++i)
{
result = (len / 2);
if (i >= result)
break;
v3 = key[i];
key[i] = key[len - i - 1];
key[len - i - 1] = v3;
}
for (i =8;i>=0; i--)//en2
{
if (key[i] > 64 && key[i] <= 90)//这段代码有待考察正确性
{
key[i] = (key[i] - 65 - j+52) % 26 + 65;
}
else if (key[i] >= 97 && key[i] <= 122)
{
key[i] = (key[i] - 97 - j+52) % 26 + 97;
}
}
table[j] ^= key[j %4] % 32 + 1;
table[j] -= j;
}
for (i = 0; i < 34; i++)
{
table[i] ^= 21;
printf("%c", re(table[i]));
// //jWXxYB794B7vntOq+W7jQBiDnetz+tPt+x
}//CEJne0FzT0FPRlhUaEFCZ0A4RWlualtlan
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
unsigned char* en_base58(unsigned char* input) // 编码
{
static char* nb58 = (char*)"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
size_t len = strlen((char*)input);
size_t rlen = (len / 2 + 1) * 3;
unsigned char* ret = (unsigned char*)malloc(rlen + len);
unsigned char* src = ret + rlen;
unsigned char* rptr = ret + rlen;
unsigned char* ptr, * e = src + len - 1;
size_t i;
memcpy(src, input, len);
while (src <= e)
{
if (*src)
{
unsigned char rest = 0;
ptr = src;
while (ptr <= e)
{
unsigned int c = rest * 256;
rest = (c + *ptr) % 58;
*ptr = (c + *ptr) / 58;
ptr++;
}
--rptr;
*rptr = nb58[rest];
}
else
{
src++;
}
}
for (i = 0; i < ret + rlen - rptr; i++)
ret[i] = rptr[i];
ret[i] = 0;
return ret;
}
bool de_base58(unsigned char* src)
{
static char b58n[] =// 解码表,它是根据索引和ASCII值来进行填写的。
{
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,
-1, 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,-1,-1,-1,-1,-1,
-1,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,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
};
size_t len = strlen((char*)src);
size_t rlen = (len / 4 + 1) * 3;
unsigned char* ret = (unsigned char*)malloc(rlen);
unsigned char* rptr = ret + rlen;
size_t i;
unsigned char* ptr;
for (i = 0; i < len; i++)
{
char rest = b58n[src[i]];
if (rest < 0)
{
free(ret);
return NULL;
}
for (ptr = ret + rlen - 1; ptr >= rptr; ptr--)//转换十进制和转化2556进制同时进行
{
unsigned int c = rest + *ptr * 64;//,用的是base58的思路,但是从64进制转换成十进制进制
*ptr = c % 256;//取余得字符
rest = c / 256;//除256,
}
if (rest > 0)
{
rptr--;
if (rptr < ret)
{
free(ret);
return NULL;
}
*rptr = rest;
}
}
for (i = 0; i < ret + rlen - rptr; i++)
{
ret[i] = rptr[i];
ret[i] = (ret[i] >> 4) | (ret[i] << 4 & 0xff);
}
ret[i] = 0;
memcpy(src, ret, strlen((char*)src));
}
int main()
{
char str[] = "CEJne0FzT0FPRlhUaEFCZ0A4RWlualtlan";
de_base58((unsigned char*)str);
printf("%s", str);
getchar();
return 0;
//代码来源于Eutop1a的文章https://blog.csdn.net/yidalipao1/article/details/126002691
}
最后再提一嘴,其实key的加密是单独的,可以抄写加密代码将每一轮用到的key跑出来,对最后两步的加密进行暴力求解,而无需写解密脚本。