2023年第十四届极客大挑战re逆向方向babycode复现

这道题主要涉及的知识点有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跑出来,对最后两步的加密进行暴力求解,而无需写解密脚本。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值