《XGCTF_符文之语》——对Aztec Rune的一得之见

Aztec Rune

概念

Aztec Rune是Aztec Code的变体,旨在与Aztec Code在图形上兼容。它由Aztec Code的紧凑版本的核心符号以及传达 8 位数据的数字不同数据消息组成。Aztec Rune包含 256 个 11 x 11 模块正方形标记,任何Aztec Code阅读器都可以扫描。

任何一个Aztec码,除了中心的牛眼(或“金字塔)形图案外,在其四个顶点处还会有四个方向标记(即蓝色交叉线标识区域)。对于一个方向正确的Aztec码,这四个标记从左上开始顺时针方向依次是三个点、两个点、一个点、没有点。

image-20240707220510029


相关文献

Aztec Rune

Aztec Code


复现过程

准备工作

根据官方Writeup和题目,我们大致需要做三件事:

1.将mp4按帧提取,并且补全中心图案(代码思路可忽略补全过程)

2.制作Aztec Rune从0到255的标准码表

3.读取mp4文件中的图片,并对照码表得到对应值,转化为16进制存储


提取帧图片

网上有很多现成脚本以及在线网站,这里不做过多赘述。


标准码表的制作

代码实现

from PIL import Image
import os

#=========================文件设置======================================
folder_path = ""         #填写码表文件夹路径
example_path = ""  #示例码表图片路径
block_number=11                                                             #填写块数量
dic = {}
#=========================边角像素点位置=================================

img = Image.open(example_path)
width, height = img.size
print(f'图象尺寸为:{width}×{height}')

#确定左上角像素位置
for y in range(height):
    Flag_lefttop=0
    for x in range(width):
        if img.getpixel((x, y)) == (0, 0, 0, 255):
            x_min=x
            y_min=y
            Flag_lefttop=1
            break
    if Flag_lefttop:
        break

if x_min is not None and y_min is not None:
    print(f"左上角像素位置为: ({x_min}, {y_min})")
else:
    print("左上角未找到指定像素")

#确定右上角像素位置
for y in range(height):
    Flag_righttop=0
    for x in range(width-1, -1, -1):
        if img.getpixel((x, y)) == (0, 0, 0, 255):
            x_max = x
            y_min = y
            Flag_righttop=1
            break
    if Flag_righttop:
        break
if x_max is not None and y_min is not None:
    print(f"右上角像素位置为: ({x_max}, {y_min})")
else:
    print("右上角未找到指定像素")

y_max=x_max

if Flag_lefttop and Flag_righttop:
    block_size=(x_max-x_min+1)//block_number
    print(f'像素块尺寸为:{block_size}×{block_size}')

#=====================顺时针顺序的边界像素坐标================================

points = []

points.extend((x_min + block_size // 2 + x * block_size,block_size // 2) for x in range(block_number))              # 顶部边界(从左到右)
points.extend((x_max - block_size // 2, y_min + block_size // 2 + y*block_size) for y in range(1,block_number))     # 右侧边界(从上到下)
points.extend((x_max - block_size // 2 - x * block_size, y_max-block_size // 2) for x in range(1,block_number))     # 底部边界(从右到左)
points.extend((x_min + block_size // 2, y_max - block_size // 2 - y*block_size) for y in range(1,block_number-1))   # 左侧边界(从下到上)

#===================Aztec码表的生成=========================================
for i in range(256):
    file = os.path.join(folder_path, f'{i}.png')
    img = Image.open(file)
    dic[f'{i:x}'.zfill(2)] = ''.join('1' if img.getpixel((x, y)) == (0, 0, 0, 255) else '0' for x, y in points)  # 生成边界像素颜色序列
print(dic)

问题解决

标准码表图片的制作

最开始是想找到现成的库进行制作,但是并不是Aztec Rune的,这里找到一个可以批量生成Aztec Rune的在线网站,生成从0-255的Aztec Rune

批量条码生成器 | Online-BarCode.com


将标准码表图片转化成标准码表

当我们得到了标准码表的图片后,这时我们遇到一个问题,如何将标准码表图片中的信息存储成码表。初步有几个方案:

1.将周边所有的像素块按照黑白划分为0/1存储

2.将周边所有的除了四角的像素块按照黑白划分为0/1存储

这两种存储思路大致相同,区别在于四角的标志是否存储。在思考后选择了方案1,因为可能会遇到将Aztec旋转的情况,如果将四角标注存储,旋转后也能通过拼接快速得到新的码表,而且不用复原中间部分的标志


图象边缘存在白框

这个问题最开始我是手动将边框给处理掉,但是很多时候会处理错误,所以我决定在代码中解决这个问题

#确定左上角像素位置
for y in range(height):
    Flag_lefttop=0
    for x in range(width):
        if img.getpixel((x, y)) == (0, 0, 0, 255):
            x_min=x
            y_min=y
            Flag_lefttop=1
            break
    if Flag_lefttop:
        break

if x_min is not None and y_min is not None:
    print(f"左上角像素位置为: ({x_min}, {y_min})")
else:
    print("左上角未找到指定像素")

图象左上角的坐标默认为(0,0),由于Aztec码标志特征,可以区分出Aztec码最左上角和最右上角像素的坐标。在确定坐标后,后续的操作将依据此坐标进行


像素块中特征像素的选定

#=====================顺时针顺序的边界像素坐标================================

points = []

points.extend((x_min + block_size // 2 + x * block_size,block_size // 2) for x in range(block_number))              # 顶部边界(从左到右)
points.extend((x_max - block_size // 2, y_min + block_size // 2 + y*block_size) for y in range(1,block_number))     # 右侧边界(从上到下)
points.extend((x_max - block_size // 2 - x * block_size, y_max-block_size // 2) for x in range(1,block_number))     # 底部边界(从右到左)
points.extend((x_min + block_size // 2, y_max - block_size // 2 - y*block_size) for y in range(1,block_number-1))   # 左侧边界(从下到上)

代码的思路是通过识别像素块中的一个像素来代表整个像素块的黑白情况,那么这个选定的像素就极其重要了,最初有三种方案:

1.选取像素块最左上角的像素

2.选取像素块靠外边最中间的像素

3.选取像素块中心像素

在制作码表的时候,这三种其实都可以,因为码表十分清晰,不会存在边缘重影的情况。但是由于题目给的图片存在重影,所以我们选择方案3,可以有效避免重影像素的影响


对题目图片进行读取

代码实现

from PIL import Image
import os

#=========================文件设置======================================
folder_path = ""         #填写码表文件夹路径
example_path = ""  #示例码表图片路径
block_number=11                                                             #填写块数量
hex_data = ''

#========================自定义函数=====================================

def value_to_key(dic, value):
    reversed_dict = {v: k for k, v in dic.items()}
    return reversed_dict.get(value)

#=========================码表==========================================
dic={'00': '1110101010110101010100101010100001010101', '01': '1110101010111000111100101100100000010111',
………………
'1101010100110010111100111111100010001111', 'ff': '1101010100111111010100111001100011001101'}
#=========================边角像素点位置=================================

img = Image.open(example_path)
width, height = img.size
print(f'图象尺寸为:{width}×{height}')

#确定左上角像素位置
for y in range(height):
    Flag_lefttop=0
    for x in range(width):
        if img.getpixel((x, y)) != (255, 255, 255):
            x_min=x
            y_min=y
            Flag_lefttop=1
            break
    if Flag_lefttop:
        break

if x_min is not None and y_min is not None:
    print(f"左上角像素位置为: ({x_min}, {y_min})")
else:
    print("左上角未找到指定像素")

#确定右上角像素位置
for y in range(height):
    Flag_righttop=0
    for x in range(width-1, -1, -1):
        if img.getpixel((x, y)) != (255, 255, 255):
            x_max = x
            y_min = y
            Flag_righttop=1
            break
    if Flag_righttop:
        break
if x_max is not None and y_min is not None:
    print(f"右上角像素位置为: ({x_max}, {y_min})")
else:
    print("右上角未找到指定像素")

y_max=x_max

if Flag_lefttop and Flag_righttop:
    block_size=(x_max-x_min+1)//block_number
    print(f'像素块尺寸为:{block_size}×{block_size}')

#=====================顺时针顺序的边界像素坐标================================

points = []

points.extend((x_min + block_size // 2 + x * block_size,block_size // 2) for x in range(block_number))              # 顶部边界(从左到右)
points.extend((x_max - block_size // 2, y_min + block_size // 2 + y*block_size) for y in range(1,block_number))     # 右侧边界(从上到下)
points.extend((x_max - block_size // 2 - x * block_size, y_max-block_size // 2) for x in range(1,block_number))     # 底部边界(从右到左)
points.extend((x_min + block_size // 2, y_max - block_size // 2 - y*block_size) for y in range(1,block_number-1))   # 左侧边界(从下到上)

#=====================数据提取转化============================================
for i in range(256):
    file = os.path.join(folder_path, f'{i}.png')
    img = Image.open(file)
    point_data = ''.join('1' if img.getpixel((x, y)) != (255, 255, 255) else '0' for x, y in points)
    hex_data = hex_data + value_to_key(dic,point_data)+' '
print(hex_data)

问题解决

像素块的RGB值不统一

point_data = ''.join('1' if img.getpixel((x, y)) != (255, 255, 255) else '0' for x, y in points)

这个问题是在ps中查看到的,我们会发现黑色块的RGB值不一样,有些是12有些是0。所以这里我们在判别的时候修改一下判别条件,将除白色块以外的所有像素均视为黑色,这样问题就得到了有效解决。


后话

实话实说,XGCTF的Misc题目质量以及难度划分很一般,这道题属于是独树一帜。了解到了没见过的Aztec Rune码(其实见过,羞愧)。总的来说,这次比赛收获了很多(不是在线网站嗷),而且也达成了自己的一个小目标,总的来说是不错的。

评论

  1. 日月原
    2 月前
    2024-7-10 11:11:28

    非常好一款阿光,敏感肌也能用|´・ω・)ノ

    来自四川
    • 博主
      日月原
      2 月前
      2024-7-10 22:43:31

      日月原sama可爱捏ヾ(≧∇≦*)ゝ

      来自重庆

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇