2026数字中国创新大赛数字安全赛道|网络和数据安全积分争夺赛Writeup

哥,再给我点token吧,我快不行了,求你了哥,我感觉我身上有蚂蚁在爬,我感觉我浑身都在抖,快不能呼吸了,求求你了哥,就再给我一点token吧,就一点就行,我再也不碰了,求求你了哥,我发誓我以后再也不碰这东西了!

/images/image-20260413225133.png

个人赛

办公安全事件

企业在向内部员工下发保密资产图片时,会预先在图片中嵌入该员工专属的不可见水印,以便日后追溯泄露来源。近期,安全团队在外网监测中发现一张与公司内部资料高度吻合的图片,判断其已被人私自外传,需从图片中提取水印,找出泄露者。

提交从图片中提取出的泄露者员工工号。

例:提取出的工号为 EMP-001,则提交答案为:EMP-001

先说结论:这题古法和AI都输了😭该试的都试了,A老师和G老师看了直摇头😴

本题的不可见水印采用了加法正弦水印(Additive Sinusoidal Watermarking)算法,将数据嵌入在频域(2D-FFT 幅度谱)中。

/images/image-20260413195140.png

从这些图里可以明显观察到图片背景上存在规律性的斜向细条纹,翻一下每个RGB三个通道的样式,很明显水印主要嵌在蓝通道。

/images/image-20260413200222.png

接着从蓝色频谱图里面找,沿着蓝通道频谱中心往左上角那条对角线去扫,提取一系列形如 (cy-k, cx-k) 的点的幅值,扫描一段范围后发现,在 k = 90 ~ 206、步长为 2 的这些点上,峰值分布非常有规律:

/images/image-20260413201425.png

  • 一部分点幅值非常高,大约在 16 万到 22 万之间;
  • 另一部分点幅值明显低很多,通常只有几千到几万。

可以很快想到转换成0、1

  • 有峰 = 1
  • 无峰 = 0

于是设一个比较保守的阈值,比如 150000,把这些频点离散成 bit,得到:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# -*- coding: utf-8 -*-
# @Author  : 1cePeak
from PIL import Image
import numpy as np

img = np.array(Image.open('leaked_photo.png').convert('RGB'))[:, :, 2].astype(float)
F = np.fft.fftshift(np.fft.fft2(img))

h, w = img.shape
cy, cx = h // 2, w // 2

bits = []
for k in range(90, 207, 2):
    mag = abs(F[cy-k, cx-k])
    bits.append('1' if mag >= 150000 else '0')

bs = ''.join(bits)
text = ''.join(chr(int(bs[3+i:3+i+8], 2)) for i in range(0, 56, 8))
print(text)

可以得到01字符串

1
11101000101010011010101000000101101001100000011011100110011

拿到 bit 串后,第一反应肯定是按 8 位切分转 ASCII。但这一步不会直接出结果,因为真实嵌入时很可能并不是从 bit 串的第 0 位刚好对齐,于是继续尝试不同偏移量。

删掉前面的三个1,从第 4 位开始,每 8 位分组时,刚好得到:

1
01000101 01001101 01010000 00101101 00110000 00110111 00110011

/images/image-20260413202235.png

所以,这道题就是要把水印数据流按照合理的方式提取出来,代码核心逻辑虽然不长,但不是很好想到🫪

总结一下:

  1. 读蓝通道;
  2. 做 FFT 并中心化;
  3. 沿频谱对角线取样;
  4. 用阈值判 0/1;
  5. 取正确偏移按字节解码。

团队赛

0%