收获

  • 安卓逆向如何获取数据资源

  • 更换码表的 Base64 加解密


(2023年3月30日)【楚慧杯 2023】Level_One


思路

下载解压得到一个 chall.apk 文件

2022第七届楚慧杯-Level_One1.png

界面是常规的输入 flag 然后进行校验,没什么特别的

在 jadx-gui 中打开,找到 MainActivity

2022第七届楚慧杯-Level_One2.png

定位到关键判断语句:

2022第七届楚慧杯-Level_One3.png

获取用户输入,并转化为 utf-8 编码,以字节形式调用 Encrypt() 函数进行加密,然后与 C0535R.string.enc 进行比较,如果相同则获得 flag

Encrypt() 函数的内容在下方给出:

2022第七届楚慧杯-Level_One4.png

形式看起来比较复杂,但是可以注意到代码后面一段:

2022第七届楚慧杯-Level_One5.png

这里对 bArr 的长度做了一个余 3 的操作,余数为 length2
并且,还对余数的两种情况分别进行了讨论:
length2 = 1 时,会在最后添加 "%&"
length2 = 2 时,会在最后添加 "*"

同时,java 中 charAt() 方法用于返回字符串中指定索引位置的字符,这里利用 append()sb 插入索引处的值

这样的特点非常类似 Base64 加密算法,但是不同的是:这里根据余数为 12 添加的不是 "==""="

继续观察,可以发现 append() 插入用到的索引表 string 来自 C0535R.string.table

2022第七届楚慧杯-Level_One6.png

在资源中定位这个文件:

2022第七届楚慧杯-Level_One7.png

可以得到表中的内容:n4/Xv90ElB32S6MmyK+iVIgkjU17CDGaLYxeuzsc8TqNOtrJWPfRp=HFhAwdb5oZQ
包含大写字母、小写字母、数字 0-9、'+''/',很符合 Base64 码表的形式,只是原始码表的顺序被打乱了

但是根据这里码表的变动,且多出一个 '=',导致 Encrypt() 函数中最后根据余数个数添加的不是 '==''=',也就基本可以确定 Encrypt() 函数是更换过码表的 Base64 加密了

在资源里找到 if 条件中对比的数据 C0535R.string.enc

2022第七届楚慧杯-Level_One8.png

得到 enc 的内容:UsPYUFORMXnHSXIe6xpFSilR2iKs6RvtjeVFSfpHSX9zM0Sp6XVFjeK5

利用更换码表的 Base64 解密即可


脚本

# coding:utf-8
s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"  # 原始码表


def my_base64_encode(inputs):
    # 将字符串转化为2进制
    bin_str = []
    for i in inputs:
        x = str(bin(ord(i))).replace('0b', '')
        bin_str.append('{:0>8}'.format(x))
    # print(bin_str)
    # 输出的字符串
    outputs = ""
    # 不够三倍数,需补齐的次数
    nums = 0
    while bin_str:
        # 每次取三个字符的二进制
        temp_list = bin_str[:3]
        if len(temp_list) != 3:
            nums = 3 - len(temp_list)
            while len(temp_list) < 3:
                temp_list += ['0' * 8]
        temp_str = "".join(temp_list)
        # print(temp_str)
        # 将三个8字节的二进制转换为4个十进制
        temp_str_list = []
        for i in range(0, 4):
            temp_str_list.append(int(temp_str[i * 6:(i + 1) * 6], 2))
        # print(temp_str_list)
        if nums:
            temp_str_list = temp_str_list[0:4 - nums]

        for i in temp_str_list:
            outputs += s[i]
        bin_str = bin_str[3:]
    outputs += nums * '='
    print("加密完成:\n%s " % outputs)


def my_base64_decode(inputs):
    # 将字符串转化为2进制
    bin_str = []
    for i in inputs:
        if i != '=':
            x = str(bin(s.index(i))).replace('0b', '')
            bin_str.append('{:0>6}'.format(x))
    # print(bin_str)
    # 输出的字符串
    outputs = ""
    nums = inputs.count('=')
    while bin_str:
        temp_list = bin_str[:4]
        temp_str = "".join(temp_list)
        # print(temp_str)
        # 补足8位字节
        if (len(temp_str) % 8 != 0):
            temp_str = temp_str[0:-1 * nums * 2]
        # 将四个6字节的二进制转换为三个字符
        for i in range(0, int(len(temp_str) / 8)):
            outputs += chr(int(temp_str[i * 8:(i + 1) * 8], 2))
        bin_str = bin_str[4:]
    print("解密完成:\n%s " % outputs)


print("     可更换码表的 Base64 加解密系统       ")
print("*************************************")
select = input("是否更换加密的码表? (y or n) 你的选择: ")
if select == "y" or select == "yes":
    s = input("在这里输入码表: ")
    print(".....done, 已更改!")
else:
    print("码表未做更改!")
num = input("选择要进行的操作? (1:加密 or 2:解密) 你的选择: ")
if num == "1" or num == "1:加密":
    input_str = input("输入明文以加密: ")
    my_base64_encode(input_str)
elif num == "2" or num == "2:解密":
    input_str = input("输入密文以解密: ")
    my_base64_decode(input_str)
else:
    print(".....bye")

结果

flag{380605c6-7123-4f71-b573-601e8c4457b4}

2022第七届楚慧杯-Level_One9.png