题目链接:https://ctf.bugku.com/challenges/detail/id/2971.html
初步使用binwalk查看,无明显的文件隐藏。
加密方式:在其中的5*5的局部黑白格子中,藏有信息。目测两个黑为摩斯密码的长,一个黑为摩斯密码的短。1个白色是摩斯密码的间隔,2个白色是字母的分割。题目提示3个字母,
呃呃呃,本来暴力应该也可以过的,也就17576种可能。
这里按照常规方式解题吧。
编写python,大部分代码是AI写的,我提供思路给AI的。如果觉得某些写法有点反人类,属于正常的。
思路如下:
1.解析二维码的行和列数。并转为2维数组
2.根据黑白的二维数组,进行遍历所有的5*5的局部信息,每个信息包含25个位信息。
3.根据25个位的信息,进行摩斯密码解密。输出内容包含局部区域的左上角位置,解析出来的字母,25个位信息。
代码如下:
python
from PIL import Image
import numpy as np
# 读取图像
image_path = r"file.jpg"
img = Image.open(image_path).convert('L') # 转换为灰度图
data = np.array(img)
# 获取图像尺寸
height, width = data.shape
# 将二维码转换为二值化二维数组
def convert_to_binary_array(gray_data, threshold=128):
"""
将灰度图像数据转换为二值化二维数组
1 表示黑色像素,0 表示白色像素
"""
binary_array = (gray_data < threshold).astype(int)
return binary_array
# 使用更精确的算法计算二维码模块数量
def calculate_qr_module_count(binary_array):
"""
使用边缘检测和网格模式识别计算二维码的模块数量
"""
# 检测图像中的黑白变化,找出模块边界
row_changes = []
for row in range(height):
changes = 0
for col in range(1, width):
if binary_array[row, col] != binary_array[row, col-1]:
changes += 1
row_changes.append(changes)
# 找到变化次数最多的行,这通常对应二维码的有效区域
max_changes = max(row_changes)
# 二维码模块数量通常是变化次数的一半(因为每次黑白变化算两次)
# 加上3是因为二维码边缘有空白区域
module_count = (max_changes // 2) + 3
# 确保模块数量是合理的
if module_count < 21 or module_count > 100:
# 使用备用方法:通过寻找模式重复来确定模块大小
# 在实际二维码中,模块大小通常是一致的
module_sizes = []
# 检查前10行中连续相同颜色的像素长度
for row in range(min(10, height)):
current_color = binary_array[row, 0]
current_length = 1
for col in range(1, width):
if binary_array[row, col] == current_color:
current_length += 1
else:
if current_length > 1:
module_sizes.append(current_length)
current_color = binary_array[row, col]
current_length = 1
if module_sizes:
# 找到最常见的长度作为模块大小
from collections import Counter
most_common_size = Counter(module_sizes).most_common(1)[0][0]
module_count = max(width, height) // most_common_size
return module_count
# 生成二维码的25*25二维数组(使用拟合算法)
def generate_qr_array(binary_array, module_count):
"""
使用拟合算法生成module_count x module_count大小的二维码黑白情况二维数组
1表示黑色,0表示白色
使用占比超过90%的判断标准
"""
# 计算精确的模块大小(考虑到像素可能不是完美整除)
module_size_height = height / module_count
module_size_width = width / module_count
module_array = []
# 遍历每个模块
for module_row in range(module_count):
current_row = []
for module_col in range(module_count):
# 计算这个模块在图像中的精确位置
start_row = int(module_row * module_size_height)
end_row = int((module_row + 1) * module_size_height)
start_col = int(module_col * module_size_width)
end_col = int((module_col + 1) * module_size_width)
# 确保不会越界
end_row = min(end_row, height)
end_col = min(end_col, width)
# 获取这个模块区域内的所有像素
block = binary_array[start_row:end_row, start_col:end_col]
# 计算黑色像素(值为1)的数量
total_pixels = block.size
black_pixels = np.sum(block)
white_pixels = total_pixels - black_pixels
# 计算占比
black_ratio = black_pixels / total_pixels if total_pixels > 0 else 0
white_ratio = white_pixels / total_pixels if total_pixels > 0 else 0
# 判断颜色:占比超过90%才确定为该颜色
if black_ratio >= 0.9:
current_row.append(1) # 黑色
elif white_ratio >= 0.9:
current_row.append(0) # 白色
else:
# 如果两种颜色占比都不超过90%,则使用平均值判断
# 这里可以根据实际情况调整策略
if black_ratio > white_ratio:
current_row.append(1)
else:
current_row.append(0)
module_array.append(current_row)
return module_array
# 遍历并解析所有5*5区域的函数
def parse_5x5_regions(qr_array):
"""
遍历二维码25*25中的所有5*5区域
按行展开为25长度信息
"""
# 存储所有5*5区域的展开结果
region_results = {}
# 遍历所有可能的5*5区域
for start_row in range(len(qr_array) - 4):
for start_col in range(len(qr_array[0]) - 4):
# 提取5*5区域
region = []
for i in range(5):
row = qr_array[start_row + i][start_col:start_col + 5]
region.append(row)
# 按行展开为25长度的一维数组
flattened = [cell for row in region for cell in row]
# 存储结果
region_results[(start_row, start_col)] = {
'region': region,
'flattened': flattened,
'flattened_str': ''.join(map(str, flattened))
}
# 特别处理左上角定位符右下角的5*5区域(定位符通常从(0,0)开始,大小为7*7)
# 所以右下角5*5区域应该是从(2,2)开始
top_left_corner_region = []
for i in range(2, 7):
row = qr_array[i][2:7]
top_left_corner_region.append(row)
# 按行展开为25长度的一维数组
corner_flattened = [cell for row in top_left_corner_region for cell in row]
return {
'all_regions': region_results,
'top_left_corner_region': {
'region': top_left_corner_region,
'flattened': corner_flattened,
'flattened_str': ''.join(map(str, corner_flattened))
}
}
# 解析5*5区域的摩斯密码
def parse_5x5_morse(flattened_array):
"""
解析5x5区域的摩斯码
功能:
1. 将输入的二进制字符串转换为摩斯电码
2. 按连续1的数量解析:1个1='.', 2个1='-',≥3个1视为无效
3. 使用00作为分隔符分割不同的摩斯符号
4. 将解析出的摩斯码转换为对应的字母
"""
# 确保输入是字符串类型
if not isinstance(flattened_array, str):
flattened_array = ''.join(map(str, flattened_array))
# 完整的摩斯电码字典
morse_to_char = {
'.-': 'A', '-...': 'B', '-.-.': 'C', '-..': 'D', '.': 'E',
'..-.': 'F', '--.': 'G', '....': 'H', '..': 'I', '.---': 'J',
'-.-': 'K', '.-..': 'L', '--': 'M', '-.': 'N', '---': 'O',
'.--.': 'P', '--.-': 'Q', '.-.': 'R', '...': 'S', '-': 'T',
'..-': 'U', '...-': 'V', '.--': 'W', '-..-': 'X', '-.--': 'Y',
'--..': 'Z'
}
# 使用00作为分隔符分割字符串
segments = flattened_array.split('00')
# 存储解析结果
result = []
# 解析每个段
for segment in segments:
if len(segment) == 0:
break
# 解析连续的1
morse_code = ''
i = 0
while i < len(segment):
# 处理连续的1
if segment[i] == '1':
start = i
while i < len(segment) and segment[i] == '1':
i += 1
count = i - start
# 根据连续1的数量确定摩斯符号
if count == 1:
morse_code += '.'
elif count == 2:
morse_code += '-'
else: # 连续3个或以上的1,视为无效
morse_code = ''
break
# 处理单个0(不影响解析)
elif segment[i] == '0':
start = i
while i < len(segment) and segment[i] == '0':
i += 1
count = i - start
if count >= 2:
morse_code = ''
break
if len(morse_code) == 0:
morse_code = ''
break
# 如果解析出有效的摩斯码,则查找对应的字母
if morse_code in morse_to_char:
result.append(morse_to_char[morse_code])
else:
break
return result
# 主函数
def main():
# 声明使用全局变量
global data, height, width
# 输出像素的行列数量
print(f"像素:{height}*{width}")
# 将图像转换为二值化二维数组
binary_array = convert_to_binary_array(data)
# 使用改进的算法计算模块数量
module_count = calculate_qr_module_count(binary_array)
# 输出二维码的行列数量
print(f"二维码:{module_count}*{module_count}")
# 生成25*25的二维码黑白情况二维数组
qr_array = generate_qr_array(binary_array, module_count)
# 输出25*25的二维码黑白信息
print("\n二维码25*25黑白信息:")
for row in qr_array:
# 将当前行转换为字符串(1表示黑块,0表示白块)
row_str = ''.join(map(str, row))
print(row_str)
# 解析所有5*5区域
print("\n开始解析5*5区域...")
print()
region_results = parse_5x5_regions(qr_array)
# 处理所有5*5区域
for pos, region in region_results['all_regions'].items():
# 使用新的摩斯密码解析函数解析该区域
region_words = parse_5x5_morse(region['flattened'])
# 获取25个二维码的黑白信息
info_str = region['flattened_str']
# 输出所有字母结果,不再限制长度
if len(region_words) == 3:
print(f"区域: {pos} 字母:{region_words} 信息:{info_str}")
# 特殊处理左上角定位符右下角5*5区域(确保也被处理)
corner_region = region_results['top_left_corner_region']
corner_words = parse_5x5_morse(corner_region['flattened'])
# 获取角落区域的25个二维码的黑白信息
corner_info_str = corner_region['flattened_str']
for word in corner_words:
if word.isalpha():
print(f"区域: (2,2) 字母:{word} 信息:{corner_info_str}")
if __name__ == "__main__":
main()
运行结果如下:
python
像素:500*499
二维码:25*25
二维码25*25黑白信息:
1111111011110011101111111
1000001000011101101000001
1011101001010100001011101
1011101000110010101011101
1011101000111111001011101
1000001001010111001000001
1111111101001010101111111
0000001001001010000000000
1101101100101110101000001
0111101010100011000101111
1000100000001010101111010
0000010100000010101111001
1100001111100110010111000
1011110000101000011101000
1101101100100010010101000
1011000010101111001000010
1010011101011000111111101
0000000011010001100010110
1111111001001110101010100
1000001000010100100010100
1011101011010100111111101
1011101011101100100100011
1011101000010101110001011
1000001011111100010001011
1111111010011100011011001
开始解析5*5区域...
区域: (2, 4) 字母:['I', 'I', 'I'] 信息:1010010100101000010011110
区域: (3, 16) 字母:['S', 'I', 'E'] 信息:1010100101001001011100000
区域: (5, 15) 字母:['E', 'E', 'A'] 信息:1001001011000000101010001
区域: (6, 6) 字母:['D', 'A', 'D'] 信息:1101010010110011010100000
区域: (7, 6) 字母:['E', 'A', 'D'] 信息:1001011001101010000001000
区域: (7, 9) 字母:['E', 'E', 'R'] 信息:1001001011010000001000000
区域: (8, 7) 字母:['E', 'E', 'I'] 信息:1001001010000001000011110
区域: (10, 12) 字母:['S', 'U', 'E'] 信息:1010100101011001000000100
区域: (12, 9) 字母:['T', 'S', 'E'] 信息:1100101010010000101110110
区域: (14, 14) 字母:['E', 'A', 'E'] 信息:1001011001001110110010101
区域: (17, 8) 字母:['N', 'E', 'E'] 信息:1101001001000101101011101
区域: (17, 9) 字母:['I', 'E', 'T'] 信息:1010010011001011010111011
可以看到几个3字母,一个个尝试即可。