ISCC 练武初赛re+mobile wp
mobileISCC mobile 邦布出击安装apk点击右下角的按钮,进入图鉴界面,百度各种邦布的种类,一个一个试,可以得到三段base64加密的文本邦布图鉴 - 绝区零WIKI_BWIKI_哔哩哔哩然后将三段base64拼接起来,循环解码三次base64得到一串明文尝试打开解压得到的db文件,提示非数据库文件,经查询是经过sqlcipher加密,那么此前得到的明文应该就是解密的keyflag是
mobile
ISCC mobile 邦布出击
安装apk
点击右下角的按钮,进入图鉴界面,百度各种邦布的种类,一个一个试,可以得到三段base64加密的文本
邦布图鉴 - 绝区零WIKI_BWIKI_哔哩哔哩
VVQwOQ==
lZOVlZU
VVZaS1NGS然后将三段base64拼接起来,循环解码三次base64
得到一串明文
尝试打开解压得到的db文件,提示非数据库文件,经查询是经过sqlcipher加密,那么此前得到的明文应该就是解密的key
>sqlcipher enflag.db
SQLCipher version 3.8.0.2 2013-09-03 17:11:13
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> PRAGMA key = 'ARGENTI';
sqlite> ATTACH DATABASE 'plaintext.db' AS plaintext KEY '';
sqlite> SELECT sqlcipher_export('plaintext');
sqlite> DETACH DATABASE plaintext;
sqlite> .q
flag是假的,实际应该留意的是key以及info中的blowfish(一种加密方式)
使用jadx打开apk
将上图中的密文通过blowfish解密之后得到的内容就是DES的明文
根据apk的逻辑,只有当该明文DES加密的结果和输入内容去掉flag格式后的内容相同才正确
已知明文、key、加密方式,那么对于DES加密,还需要具备的就是iv,但是iv是通过native函数生成的
方法一:分析so文件iv的生成逻辑 -- 生成逻辑比较复杂,放弃
方法二:hook native function,在调用getiv时输出iv
这里使用frida hook(要在手机上先运行frida-server)
Java.perform(function () {
try {
var cls = Java.use("com.example.mobile01.MainActivity");
cls.getiv.implementation = function () {
var iv_val = this.getiv();
console.log("[*] MainActivity.getiv() called, returned: " + iv_val);
return iv_val;
};
console.log("[+] Hooked com.example.mobile01.MainActivity.getiv()");
} catch (err) {
console.error("[-] Failed to hook MainActivity.getiv: " + err);
}
});frida -U -f 进程名 -l hook.jsISCC mobile detective
附件是一个apk文件,用jadx打开
可以看到关键是这个stringFromJNI函数,跟进之后发现是native函数,因此用IDA打开so文件
关键是这个xorEncrypt函数
通过分析代码可知,该函数先将字符串转换为十六进制,再将输入与key异或之后转为字符串,然后从每4个字符中提取前2个字符,然后再根据一定规律打乱字符串的位置信息,最后替换特定位置的字符
import re
from functools import reduce
import binascii
class CryptoSolver:
@staticmethod
def extract_alternate_chars(encoded_text):
"""提取每4个字符中的前2个字符"""
if len(encoded_text) % 4 == 2:
encoded_text += '00'
return ''.join([encoded_text[i:i+2] for i in range(0, len(encoded_text), 4)])
@staticmethod
def hex_encode_chars(text):
"""将字符串转为十六进制表示"""
hex_representation = ''.join([f'{ord(c):04x}' for c in text])
return hex_representation[2:] if hex_representation.startswith('00') else hex_representation
@staticmethod
def process_pattern_swaps(text):
"""处理特定模式的字符交换"""
result = []
pattern = re.compile(r'(..)(..)')
i = 0
while i < len(text):
if i + 3 < len(text) and text[i+2:i+4] == '21':
result.append(text[i+1])
result.append(text[i])
i += 4
else:
result.append(text[i:i+2])
i += 2
return ''.join(result)
@staticmethod
def interleave_with_substitution(text):
"""分割、替换和交错合并处理"""
mid_point = len(text) // 2
first_half = list(text[:mid_point])
second_half = list(text[mid_point:])
# 替换特定位置的'3'为'0'
for i in range(len(second_half)):
if second_half[i] == '3' and (i == 0 or i % 3 == 0):
second_half[i] = '0'
for i in range(len(first_half)):
if first_half[i] == '3' and (i == 1 or (i-1) % 3 == 0):
first_half[i] = '0'
# 交错合并
merged = []
for i in range(len(text)):
if i % 2 == 0 and i//2 < len(second_half):
merged.append(second_half[i//2])
elif i % 2 == 1 and i//2 < len(first_half):
merged.append(first_half[i//2])
return ''.join(merged)
@staticmethod
def decode_to_chars(encoded):
"""将十六进制编码转换为字符"""
chars = []
index = 0
while index < len(encoded):
if encoded[index] == '0' and index + 2 < len(encoded):
# 处理三位编码
hex_val = encoded[index+1:index+3]
chars.append(chr(int(hex_val, 16)))
index += 3
else:
# 处理四位编码
end = min(index+4, len(encoded))
hex_val = encoded[index:end]
if end - index == 4: # 确保有足够的字符
chars.append(chr(int(hex_val, 16)))
index += 4
return ''.join(chars)
class XorDecoder:
def __init__(self, key="Sherlock"):
self.key = key.encode('utf-8')
self.solver = CryptoSolver()
def xor_bytes(self, hex_string):
"""XOR解密十六进制字符串"""
# 将十六进制字符串转换为字节列表
bytes_data = [int(hex_string[i:i+2], 16) for i in range(0, len(hex_string), 2)]
# 应用XOR操作
xor_result = []
for i, byte in enumerate(bytes_data):
key_byte = self.key[i % len(self.key)]
xor_result.append(byte ^ key_byte)
# 转换为UTF-8字符串
try:
return bytes(xor_result).decode('utf-8')
except UnicodeDecodeError:
# 处理解码错误
return ''.join([chr(b) for b in xor_result])
def process_layers(self, input_text):
"""应用多层处理"""
layer1 = self.solver.hex_encode_chars(input_text)
layer2 = self.solver.extract_alternate_chars(layer1)
layer3 = self.solver.process_pattern_swaps(layer2)
layer4 = self.solver.interleave_with_substitution(layer3)
return self.solver.decode_to_chars(layer4)
def decrypt(self, encrypted_hex):
"""完整解密流程"""
# 先XOR解密
intermediate = self.xor_bytes(encrypted_hex)
# 然后应用多层解码
return self.process_layers(intermediate)
def main():
# 示例加密数据
encrypted = "xxxxxxxxxxxxxxxxxxx"
# 创建解码器并解密
decoder = XorDecoder()
result = decoder.decrypt(encrypted)
print(f"解密结果: {result}")
# 直接使用XOR解密检查中间结果
xor_result = decoder.xor_bytes(encrypted)
print(f"XOR中间结果: {xor_result}")
if __name__ == "__main__":
main()
HolyGrail
附件为apk安装包
使用jadx打开apk,发现其中有许多checkbox,点击checkbox的响应如下
每点击一个checkbox就会在userSequence末尾添加当前checkbox的资源名称
而根据app的提示,需要按照特定顺序点击checkbox,才能进入验证flag的页面,并且返回在native层加密后的密文
关于顺序,可以自行百度,也可以问ai,最终顺序如下
如何获得密文:通过frida hook,手动传入特定顺序的参数(每个checkbox的参数也需要通过frida hook得到),然后输出返回的密文
var cipher = Java.use("com.example.holygrail.CipherDataHandler");
var args = Java.array("java.lang.String", ["checkBox8","checkBox6","checkBox7","checkBox5","checkBox12","checkBox3","checkBox10","checkBox13","checkBox11","checkBox","checkBox9","checkBox4","checkBox14"]);
console.log(cipher.generateCipherText(args));然后分析验证flag的页面
首先检查flag格式,然后调用a类的validateFlag方法
大概流程
- getEncryptionKey
- vigenereEncrypt
- processWithNative
- b.a
由于processWithNative是JNI函数,因此尝试frida hook该函数,尝试传入不同的值,发现每个字符对应的加密结果和顺序无关,因此可以直接生成所有字符加密的结果,再对目标字符串进行匹配
解密思路
- 转十六进制
- 字符替换
- 字符偏移
exp
from collections import defaultdict
import hashlib
import binascii
from rich.progress import track
CHARACTER_SET = r"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&'()*+,-./:;<=>?@[\]^_`{|}~"
def build_mapping_table():
raw_data = "39213A213B213C21402141214221432144214521464748494A4B4C505152535455565758595A5B5C60616263646550215121522153215421552156215721582159215A215B215C21303132333435363738393A3B3C272129212A212B212C2130213121322133213421352136213721382146214721482149214A214B214C2140414243444566676869"
mapping = []
idx = 0
while idx < len(raw_data):
if idx + 3 < len(raw_data) and raw_data[idx+2:idx+4] == "21":
mapping.append(raw_data[idx:idx+4].lower())
idx += 4
else:
mapping.append(raw_data[idx:idx+2].lower())
idx += 2
return mapping
class VigenereCipher:
def __init__(self, key):
self.key = key.lower()
self.key_length = len(key)
def decrypt(self, text):
result = []
key_position = 0
for character in text:
if not character.isalpha():
result.append(character)
continue
base = ord('a') if character.islower() else ord('A')
key_char = self.key[key_position % self.key_length]
key_shift = ord(key_char) - ord('a')
char_code = ord(character) - base
decrypted_code = (char_code - key_shift) % 26
result.append(chr(decrypted_code + base))
key_position += 1
return ''.join(result)
def compute_hash(content):
"""计算内容的SHA-256哈希值"""
return hashlib.sha256(content.encode('utf-8')).hexdigest()
class CryptSolver:
def __init__(self, cipher_mapping, charset):
self.cipher_mapping = cipher_mapping
self.charset = charset
self.vigenere = VigenereCipher("TheDaVinciCode")
def chunk_hexstring(self, hex_string):
"""将十六进制字符串分割为块"""
chunks = []
position = 0
while position < len(hex_string):
if (position + 3 < len(hex_string) and
hex_string[position+2:position+4] == "21"):
chunks.append(hex_string[position:position+4])
position += 4
else:
chunks.append(hex_string[position:position+2])
position += 2
return chunks
def decrypt_message(self, encrypted_bytes):
"""解密消息的主流程"""
hex_data = binascii.hexlify(encrypted_bytes).decode() if isinstance(encrypted_bytes, bytes) else encrypted_bytes
hex_chunks = self.chunk_hexstring(hex_data)
print(f"解析后的块: {hex_chunks}")
translated = []
for chunk in hex_chunks:
try:
index = self.cipher_mapping.index(chunk.lower())
translated.append(self.charset[index])
except ValueError:
translated.append('?')
intermediate = ''.join(translated)
print(f"中间结果: {intermediate}")
plaintext = self.vigenere.decrypt(intermediate)
print(f"解密结果: {plaintext}")
return plaintext
def main():
cipher_mapping = build_mapping_table()
solver = CryptSolver(cipher_mapping, CHARACTER_SET)
encrypted = b"xxxxxxxxxxxxxxxx"
result = solver.decrypt_message(encrypted.hex())
return result
if __name__ == "__main__":
main()whereisflag
jadx打开apk可以看到具体逻辑
分析之后发现核心函数是native函数
Native 函数基本介绍
- 定义:Native 函数通过
native关键字在 Java 中声明,实际代码编译在.so动态库(ELF 格式)中。 - JNI 桥梁:Java 层通过 JNI(Java Native Interface)调用 Native 函数,函数名和参数需遵循 JNI 规范。
用解压软件直接解压apk文件,然后进入\lib\arm64-v8a目录找到so文件,使用IDA64打开so文件,在其中找到Java_开头的函数便是native导出函数
在加密函数中首先将输入倒序
然后根据字符表查找输入的字符
字符表需要动态调试得到
而根据encrypt、charToIndex、indexToChar函数的逻辑,可以看到在索引转换时有固定偏移,为2
从jadx反编译的结果得到目标密文iB3A7kSISR,解密
exp
s = "WHEReISFLAGBCDJKMNOPQTUVXYZabcdfghijklmnopqrstuvwxyz01234567890"
ss = "iB3A7kSISR"
print("".join([s[(s.index(i)-2)%len(s)] for i in ss][::-1]))RE
打出flag
从可执行程序的图标判断为pyinstaller编译的程序,使用pyinstxtractor反编译
python pyinstxtractor.py asd.exe然后打开反编译的文件夹,打开同名pyc文件,反编译(uncompyle6或者在线)
python反编译 - 在线工具
#!/usr/bin/env python
# visit https://tool.lu/pyc/ for more information
# Version: Python 3.8
import lzma
import base64
exec(lzma.decompress(base64.b64decode('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')))
可以将decompress之后的内容写入文件(以下为部分)
import base64
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵רּ=base64.b64encode
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵ᔳ()
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵ﯘ=680
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𭛱=800
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵מּ=繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𡷛.set_mode((繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵ﯘ,繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𭛱))
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𡷛.set_caption("打出flag")
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵ﳥ=(255,255,255)
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵ر=(0,0,0)
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𐠟=(255,0,0)
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𒋚=(0,255,0)
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𐤫=(128,128,128)
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𦌧=繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𠆁+繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𫪙+繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵揨
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵ᖅ=30
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𞠪='ZpmDBMytVs5Bi0NvBYN4CoA+AXV5AMR0EBp8BYy9'
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𐡦=5
def 繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𬩞(text,shift):
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𐤶=""
for 繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵כֿ in text:
if 'A'<=繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵כֿ<='Z':
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𐤶+=繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵אָ(90-(繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵ⷃ(繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵כֿ)-65))
elif 'a'<=繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵כֿ<='z':
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𐤶+=繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵אָ(122-(繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵ⷃ(繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵כֿ)-97))
else:
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𐤶+=繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵כֿ
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𐿡=繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵רּ(繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𐤶.encode()).decode()
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𞤔=""
for 繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵כֿ in 繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𐿡:
if 'A'<=繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵כֿ<='Z':
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵ﲠ=繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵אָ((繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵ⷃ(繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵כֿ)-65+shift)%26+65)
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𞤔+=繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵ﲠ
elif 'a'<=繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵כֿ<='z':
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵ﲠ=繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵אָ((繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵ⷃ(繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵כֿ)-97+shift)%26+97)
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𞤔+=繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵ﲠ
else:
繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𞤔+=繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵כֿ
return 繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𞤔
class 繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𐬅(繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𗆂.Sprite):
def __init__(繐𩑠𱊎硊𞢱𐫂𮞙𐼶𢢵𗿷):叫AI写个脚本去混淆
import base64
def decrypt(encrypted_text, shift):
# 逆向凯撒移位
decrypted_caesar = []
for c in encrypted_text:
if 'A' <= c <= 'Z':
shifted = (ord(c) - ord('A') - shift) % 26
decrypted_caesar.append(chr(shifted + ord('A')))
elif 'a' <= c <= 'z':
shifted = (ord(c) - ord('a') - shift) % 26
decrypted_caesar.append(chr(shifted + ord('a')))
else:
decrypted_caesar.append(c)
decrypted_caesar_str = ''.join(decrypted_caesar)
# Base64解码
decoded_bytes = base64.b64decode(decrypted_caesar_str)
decoded_str = decoded_bytes.decode('utf-8')
# 字符反转
reversed_str = []
for c in decoded_str:
if 'A' <= c <= 'Z':
reversed_char = chr(ord('Z') - (ord(c) - ord('A')))
elif 'a' <= c <= 'z':
reversed_char = chr(ord('z') - (ord(c) - ord('a')))
else:
reversed_char = c
reversed_str.append(reversed_char)
return ''.join(reversed_str)
target = "ZpmDBMytVs5Bi0NvBYN4CoA+AXV5AMR0EBp8BYy9"
flag = decrypt(target, 5)
print(flag)有趣的小游戏
附件是一个exe和两个txt,其中txt内容为非打印字符
main函数中定义了许多常量
通过查看附近函数,发现其他地方也定义了常数
查看字符串表,可以在其中找到两个txt的文件名,交叉引用查看
其中process是我重命名的结果
可以看到其中比较奇怪的一点是程序将文件的内容作为函数执行,也就是说原本内容不可见的txt其实是函数的二进制数据,要想知道该函数的具体逻辑,需要动态调试,在此处下断点,触发断点之后在汇编步进就可以看到其中逻辑
可以将汇编扔给ai判断函数逻辑
deekseek:“这段汇编代码实现的是 XXTEA(eXtended TEA)算法的解密过程……”
于是知道了加解密逻辑,并且根据xxtea的密钥格式可以判断先前的两处常量中位数较短的是key,而位数较长的是密文
接下来有两种解题方式:
- 手动分析解密逻辑,自己编写代码
- 交给ai
xxtea的加解密逻辑网上有很多就不细说了,直接给出解密脚本
import base64
import struct
from typing import List
def mask_32bit(value):
"""Handle 32-bit unsigned integer overflow"""
return value & 0xFFFFFFFF
def tea_decrypt(data: List[int], key: List[int]) -> List[int]:
"""
Alternative implementation of XXTEA decryption
Args:
data: List of encrypted 32-bit integers
key: Decryption key as 4x 32-bit integers
Returns:
List of decrypted 32-bit integers
"""
data_len = len(data)
rounds = 6 + 52 // data_len
# Convert input data to a list that we can modify
result = data.copy()
# Initialize the sum value
magic_constant = 0x9E3779B9
accumulated_sum = mask_32bit(magic_constant * rounds)
# Main decryption loop
for _ in range(rounds):
# Calculate the feistel key index
mix_index = (accumulated_sum >> 2) & 3
# Process the data from end to beginning (except first element)
for i in range(data_len - 1, 0, -1):
# Get the values for the current operation
current = result[i]
previous = result[i - 1]
next_val = result[0] if i == data_len - 1 else result[i + 1]
# Calculate the mix value
mx1 = mask_32bit((previous >> 5) ^ (next_val << 2))
mx2 = mask_32bit((next_val >> 3) ^ (previous << 4))
mx_sum = mask_32bit(mx1 + mx2)
# Calculate the key part
key_index = (i & 3) ^ mix_index
key_mx = mask_32bit((accumulated_sum ^ next_val) + (key[key_index] ^ previous))
# Apply the decryption transformation
result[i] = mask_32bit(current - (mx_sum ^ key_mx))
# Process the first element separately
current = result[0]
previous = result[data_len - 1]
next_val = result[1]
mx1 = mask_32bit((previous >> 5) ^ (next_val << 2))
mx2 = mask_32bit((next_val >> 3) ^ (previous << 4))
mx_sum = mask_32bit(mx1 + mx2)
key_index = (0 & 3) ^ mix_index
key_mx = mask_32bit((accumulated_sum ^ next_val) + (key[key_index] ^ previous))
result[0] = mask_32bit(current - (mx_sum ^ key_mx))
# Update the accumulated sum for next round
accumulated_sum = mask_32bit(accumulated_sum - magic_constant)
return result
def decrypt_and_check(encrypted_data, encryption_key, max_iterations=10000):
"""
Repeatedly decrypt data and check for readable output
Args:
encrypted_data: List of encrypted 32-bit integers
encryption_key: List of 4 32-bit integers
max_iterations: Maximum number of decryption iterations
"""
current_data = encrypted_data.copy()
for iteration in range(max_iterations):
# Decrypt one round
current_data = tea_decrypt(current_data, encryption_key)
# Try to interpret as text in different ways
raw_bytes = b''.join([struct.pack("<I", val) for val in current_data])
# Method 1: Try to decode the entire output as UTF-8
try:
decoded_text = raw_bytes.decode('utf-8')
print(f"Iteration {iteration + 1}: Found valid UTF-8!")
print(decoded_text)
except UnicodeDecodeError:
pass
# Method 2: Try to extract first byte of each word
try:
first_bytes = bytes([raw_bytes[i] for i in range(0, len(raw_bytes), 4)])
decoded_first = first_bytes.decode('utf-8')
if any(c.isprintable() for c in decoded_first):
print(f"Iteration {iteration + 1}: First bytes as text: {decoded_first}")
except UnicodeDecodeError:
pass
# Main execution
def main():
# Same key and encrypted data as original
encryption_key = [0x12345678, 0x9ABCDEF0, 0xFEDCBA98, 0x76543210]
encrypted_data = [
0x018A550A, 0x840630DB, 0x3EC0C129, 0x175BDB99,
0x7FD5E3DB, 0xF99F6912, 0x199B32C1, 0x836C22BB,
0x440E4880, 0xE4EC8310, 0x2F00227A, 0xAB294A2A,
0x8EDB89F1, 0x28099186, 0xD04F421F, 0x23E7FD1C,
0x6F48B862, 0x61796B6A, 0x857587A7, 0x33254C3A,
0x06AAB088, 0x568A0B78, 0xAC64D9CF, 0xFB40A2C6,
0x9082056A, 0x4FAAB834, 0x5D033C8B, 0x7D570A1C,
0xCC81E29B, 0xCE1DE040
]
decrypt_and_check(encrypted_data, encryption_key)
if __name__ == "__main__":
main()真?复杂
题目附件是一个raw文件,010editor查看发现JFIF文件头,提取图片
然后使用cyberchef解密,解密之前要先把原raw文件中附加的图片信息删除
解密之后得到压缩包一个,解密得exe文件和enc文件各一个
虽然流程图长这个样,但是是可以手动去除的
第一种方法:(直接忽略和输入无关的语句和函数,对于涉及到修改输入的语句统统下断点)
第二种方法:直接分析加密函数的switch逻辑,可以发现是对奇偶索引的字符做不同的变换,核心变量为v4(索引)和v5(控制跳转的case),通过v4&1的操作判断奇偶
通过分析exe文件可知原本逻辑是给定flag.txt,用exe加密得到enc文件,而现在只有enc文件,故需要逆向推解密逻辑
通过分析得到解密脚本
with open('flag.txt.enc', 'rb') as f:
encrypted = f.read()
key = [0x88, 0x83, 0xA3, 0x7E, 0xEA, 0xA1, 0xBA, 0x25, 0x72, 0xCF, 0x1D, 0x6E, 0x79, 0x50, 0x17, 0x50]
decrypted = []
for v4, byte in enumerate(encrypted):
if v4 % 2 == 0: # 偶数索引处理
temp = (~byte) & 0xFF # 取消取反
temp = (temp + v4) % 256 # 逆向减法
temp ^= key[v4 % 16] # 异或密钥
orig = (temp - v4) % 256 # 逆向加法
else: # 奇数索引处理
temp = byte ^ v4 # 取消异或v4
temp = (temp - v4) % 256 # 逆向加法
temp ^= key[v4 % 16] # 异或密钥
orig = (temp + v4) % 256 # 逆向减法
decrypted.append(orig)
# 输出可打印字符(避免解码错误)
print(''.join([chr(b) if 32 <= b <= 126 else '.' for b in decrypted]))faze
题目附件:faze.exe
使用IDA打开附件
一眼C++,通过判断代码可以发现目标字符串在用户输入之前(getline)已经完成了目标字符串的初始化,所以这里有多种解法
- 在sprintf上下断点,直接查看写入目标字符串的内容
- 在比较的时候(
operator==)下断点,查看比较的数据
这里选择前者,在程序暂停时跳转到rcx所在地址
greeting
首先IDA打开可执行文件,会发现有些函数反编译的结果不正确,且提示错误,因此可以查看目标函数附近的汇编代码,找到类似加密逻辑的代码
明显的异或和循环左移操作,大概率是加密逻辑
通过分析可知,代码首先是计算一个偏移,然后将目标数据对应索引的字节在异或i+0x5a之后(esi为索引)循环左移该计算出来的偏移,因此目标可以分为两步:
- 分析该偏移的计算方式
- 反推整个加密逻辑
这里的r15其实是一个固定的值
关于偏移量的计算
- 通过手动分析
mul r15和shr dl, 2的组合实际上执行的是整数除法i / 5lea eax, [rax+rax*4]计算的是(i/5)*5sub ecx, eax计算的是i - (i/5)*5- 以上逻辑等价于
i%5
- 直接动态调试可以发现rol操作中cl的取值是0、1、2、3、4、0……,所以其实偏移的计算方式是索引对5取余
然后就是逆向整个加密逻辑,有了偏移的计算方式,解密的逻辑很好推,就是对每个字节先循环右移再异或(i+0x5a)
对于密文,通过交叉引用和人肉分析等方式最终可以找到位于0x014001B390
因此完整的解密脚本如下
def encrypt(input_bytes):
output = bytearray(len(input_bytes))
for i in range(len(input_bytes)):
div_result = (i // 10) * 5
value = (i + 0x5A) & 0xFF
value ^= input_bytes[i]
rot_amount = (i - div_result) & 0x7
value = ((value << rot_amount) | (value >> (8 - rot_amount))) & 0xFF
output[i] = value
return output
def decrypt(encrypted_bytes):
output = ""
for i in range(len(encrypted_bytes)):
rot_amount = i % 5
value = encrypted_bytes[i] & 0xFF
value = ((value >> rot_amount) | (value << (8 - rot_amount))) & 0xFF
value ^= (i + 0x5A)
output += chr(value & 0xFF)
return output
def main():
encrypted_hex = "xxxxxxxxxxx"
encrypted_bytes = bytes.fromhex(encrypted_hex)
decrypted = decrypt(encrypted_bytes)
print("Decrypted:", decrypted)
if __name__ == "__main__":
main()