2024年网鼎杯青龙组逆向2题wp
把附件拖入ida
shift+F12查看可疑字符串 这里很明显看到了AES加密,还有一个base64编码表。猜测解密方式和AES加密,base64编码有关
进入主函数查看 F5反编译查看伪c代码
这里我们一点一点搓代码
1.除二解密
- 提示用户输入标志。
- 检查用户输入的长度是否为 40,以及是否以 "wdflag{" 开头且第 32 个字符为 '}'。
- 将
v20
的前 32 个字符复制到dest
。 - 将
dest
中的前 8 个元素乘以 2 并存储到s1
中。
这里的加密过程很简单,将s2数组的前八位数字都乘以2。逆向解密过程只要将s2数组的前八位都除2就得到了flag的前八位了。
解密脚本
# 定义给定的十进制数列表
decimal_numbers = [96, 98, 110, 96, 196, 102, 114, 198]# 除以2并将结果转换为对应的字符
characters = [chr(num // 2) for num in decimal_numbers]# 打印结果
print(characters)
并且得到前八位
2.异或加密
- 定义了一个字符串
v22
,赋值为 "XorrLord"。 - 在
v11
数组的索引位置 2 到 5 分别赋值为 22, 64, 120, 12。 - 通过循环,计算
dest
数组中索引从 8 到 15 的元素与v22
字符串对应位置的元素的异或结果,保存到数组v13
中。
这里有个点,数组的值给的不全,是ida编译时可能出了点问题,这里我们只需要按tab键进入流程窗口,查看数组的值即可
加密的过程就是将V11数组的值与XorrLord进行异或,解密的过程就是逆向一下就好啦。
解密脚本
# 给定的十进制数列表
nums1 = [109, 10, 22, 64, 120, 12, 68, 87]
# 另一个十进制数列表
nums2 = [88, 111, 114, 114, 76, 111, 114, 100]# 进行逐个异或运算
result = [num1 ^ num2 for num1, num2 in zip(nums1, nums2)]# 将异或结果转换为字符
char_result = [chr(num) for num in result]print("异或结果(字符):", ''.join(char_result))
得到flag的第八位到第十五位
3.变种的base64解密
进入base64_encode函数
_BYTE *__fastcall base64_encode(__int64 a1, unsigned __int64 a2, __int64 a3)
{int v3; // eaxint v4; // eaxint v5; // eaxint v6; // eaxint v7; // eaxint v8; // eax_BYTE *result; // raxunsigned __int64 v10; // [rsp+8h] [rbp-30h]unsigned int v11; // [rsp+20h] [rbp-18h]int v12; // [rsp+28h] [rbp-10h]int v13; // [rsp+2Ch] [rbp-Ch]int v14; // [rsp+30h] [rbp-8h]int v15; // [rsp+34h] [rbp-4h]v10 = a2;v15 = 0;v14 = 0;while ( v15 < a2 ){v3 = v15++;v13 = *(unsigned __int8 *)(v3 + a1);if ( v15 >= a2 ){v5 = 0;}else{v4 = v15++;v5 = *(unsigned __int8 *)(v4 + a1);}v12 = v5;if ( v15 >= a2 ){v7 = 0;}else{v6 = v15++;v7 = *(unsigned __int8 *)(v6 + a1);}v11 = (v12 << 8) + (v13 << 16) + v7;*(_BYTE *)(a3 + v14) = base64_table[(v11 >> 18) & 0x3F];*(_BYTE *)(a3 + v14 + 1) = base64_table[(v11 >> 12) & 0x3F];*(_BYTE *)(a3 + v14 + 2) = base64_table[(v11 >> 6) & 0x3F];v8 = v14 + 3;v14 += 4;*(_BYTE *)(a3 + v8) = base64_table[v11 & 0x3F];}while ( v10 % 3 ){*(_BYTE *)(--v14 + a3) = 61;++v10;}result = (_BYTE *)(v14 + a3);*result = 0;return result;
}
解密过程就是对字符串BYOzAjWyAVA进行变种的base64解密,新的base64编码表是DEFGHIJKLMNOPQRSTUVWXYZABabcdefghijklmnopqrstuvwxyz0123456789+/
但是这里有个小坑,新的base64编码表少了个C。我们手动给它加上去就好了,所以真正的编码表是CDEFGHIJKLMNOPQRSTUVWXYZABabcdefghijklmnopqrstuvwxyz0123456789+/
解密脚本
import base64# 自定义Base64编码表
base64_chars = 'CDEFGHIJKLMNOPQRSTUVWXYZABabcdefghijklmnopqrstuvwxyz0123456789+/'# 待解码的Base64编码字符串
encoded_str = 'BYOzAjWyAVA'# 添加正确的填充字符 "=",使长度变为4的倍数
while len(encoded_str) % 4 != 0:encoded_str += '='# 使用自定义编码表解码
decoded_bytes = base64.b64decode(encoded_str.translate(str.maketrans(base64_chars, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/')))# 解码成字符串
decoded_text = decoded_bytes.decode('utf-8')print("Decoded string:", decoded_text)
flag的第三个八位也得到了
4.AES加密
简单介绍一下AES算法
AES(Advanced Encryption Standard)是一种对称加密算法,广泛用于加密和解密数据。AES算法有三种密钥长度:128位、192位和256位。它将明文(plaintext)转换为密文(ciphertext),并将密文转换回明文。
AES的基本步骤包括:
- SubBytes(字节替代):将每个字节替换为S盒中对应的值。
- ShiftRows(行移位):对每一行进行循环移位操作。
- MixColumns(列混淆):对每一列进行固定的矩阵乘法。
- AddRoundKey(轮密钥加):将轮密钥与状态矩阵进行按位异或操作。
这些步骤在加密和解密过程中稍有不同,但遵循类似的原理。
AES加密流程:
- 密钥扩展:生成轮密钥。
- 初始轮密钥加:将明文与初始轮密钥进行异或操作。
- 多轮加密:重复固定数量的轮次,每轮包括SubBytes、ShiftRows、MixColumns和AddRoundKey操作。
- 最终轮:省略MixColumns,仅执行SubBytes、ShiftRows和AddRoundKey操作。
- 输出密文:最后一轮操作后得到的密文即为加密结果。
AES解密流程:
- 密钥扩展:生成轮密钥。
- 初始轮密钥加:将密文与最后一轮轮密钥进行异或操作。
- 多轮解密:逆序执行每轮的逆操作。
- 最终轮:省略MixColumns,仅执行逆SubBytes、逆ShiftRows和AddRoundKey操作。
- 输出明文:最后一轮操作后得到的明文即为解密结果。
这里的AES加密的密钥是AesMasterAesMast,明文是V4数组。
解密过程是将字符串密钥 "AesMasterAesMast"
编码为字节序列,作为AES解密所需的密钥。然后使用 ECB 模式初始化了一个AES解密器。最后使用 Cryptodome.Util.Padding
模块中的 unpad
函数去除填充,将解密后的数据恢复到原始状态。
解密脚本
from Cryptodome.Cipher import AES
from Cryptodome.Util.Padding import unpad# 提供的数据 v4
v4 = bytes([0xF6, 0xAB, 0x47, 0xBE, 0x71, 0xE4, 0x01, 0xDC, 0x03, 0x30, 0x9F, 0xF1, 0x43, 0xF1, 0xD3, 0x66])# 使用"AesMasterAesMast"作为密钥
key = "AesMasterAesMast".encode()# 初始化AES解密器
cipher = AES.new(key, AES.MODE_ECB)# 解密数据
decrypted_data = cipher.decrypt(v4)# 去除填充
unpadded_data = unpad(decrypted_data, AES.block_size)# 输出解密后的数据
print("Decrypted data:", unpadded_data)
得到flag的最后部分
5.总结
将上面的四个部分拼接一下就得到flag了
wdflag{0170b39c5ed24c63ec3b52a6724ba7d5}
由于我写脚本能力的不熟练,所以只能将代码拆开一部分一部分的解密。但凡加密过程复杂点,我写脚本能耗好久。这次比赛的逆向题更是深有体会,只能努力提升自己写脚本的能力。