CTF-Misc "雅"题共赏

用这篇博客来记录一下本人在比赛中遇到的一些疑难题(本人尚未解出的题)

如果师傅们有进一步的想法或者做出来了,可以联系我一起交流一下解题思路

【重要】阅前须知

这篇博客可以算是本人的求助贴,因此本文中的大部分内容并不适合刚接触Misc方向的新同学

尝试本文中提到的相关题目可能会耗费大量时间,请各位读者量力而行【慎行】

[SOLVED] 题目名称 nothing (2024 蓝桥杯全国总决赛)

题目附件: https://pan.baidu.com/s/1eGIfajRXx3uqjlk54CaZ1g?pwd=ax6g 提取码: ax6g

下载附件,得到一个noting.zip,打开发现是DOCX的结构

imgs/image-20241219143340010.png

因此改后缀为.docx并打开,发现有一张白色的图片,还有一段白色的文字:什么都没有

imgs/image-20241219143422926.png

可以把DOCX作为ZIP解压,然后直接从noting\word\media路径中把这张白色图片image1.png提取出来

imgs/image-20241219143710413.png

图片的大小是34x34,我们把图片分通道提取出来,猜测存在LSB隐写

imgs/image-20241219144046769.png

仔细观察各通道的数据,发现RGBA的0通道中都隐写了信息

RGBA里面都有LSB的数据,按道理来说一共就4x3x2x1=24种排列组合,爆破一下组合的顺序应该就能得到flag

但是我尝试后并没有发现flag,下面放的是我尝试的提取LSB数据的脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from PIL import Image
import libnum
import itertools

img = Image.open("image1.png")
width,height = img.size
# print(width,height) 34 34 
r = []
g = []
b = []
a = []

for y in range(height):
    for x in range(width):
        pixel = img.getpixel((x,y))
        r.append(pixel[0]&1)
        g.append(pixel[1]&1)
        b.append(pixel[2]&1)
        a.append(pixel[3]&1)

channels = ['r', 'g', 'b', 'a']
color_arrays = {'r': r, 'g': g, 'b': b, 'a': a}
permutations = itertools.permutations(channels)

for perm in permutations:
    res = ""
    for i in range(len(r)):
        for channel in perm:
            res += str(color_arrays[channel][i])
    print(f"[+] {' '.join(perm)}")
    print(libnum.b2s(res))
    print()

后来在@byxs20师傅的帮助下,获得了新的解题思路,其实这张图片种一共就一下五种像素点

1
2
3
4
5
(255, 255, 255, 255)
(255, 255, 255, 254)
(255, 255, 254, 255)
(255, 254, 255, 255)
(254, 255, 255, 255)

其中(255, 255, 255, 255)像素是没有隐写数据的,然后另外几个像素分别按照254的位置用四进制隐写了数据

这里为啥能想到(255, 255, 255, 255)像素是没有隐写数据的呢?

因为如果师傅们尝试把不同像素的坐标打出来,可以发现这个像素是主要集中在前两列和第三列的前16个像素的

因此比较有经验的Misc师傅就会感觉到,大段连在一起的相同像素是不存在隐写数据的,因此需要把这个像素剔除

并且这里这样的排列方式,也提示了我们后续步骤中提取像素点需要按列提取

具体的对照表如下:

1
2
3
4
5
6
table = {
        (255, 255, 255, 254):0,
        (255, 255, 254, 255):1,
        (255, 254, 255, 255):2,
        (254, 255, 255, 255):3
    }

然后具体隐写的原理就是每轮的值x4,再加上当前的四进制值,最后可以得到一个长整型,具体解密代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def extract_data_from_img(img_name):
    img = Image.open(img_name)
    width,height = img.size
    # 四进制的表
    table = {
        (255, 255, 255, 254):0,
        (255, 255, 254, 255):1,
        (255, 254, 255, 255):2,
        (254, 255, 255, 255):3
    }
    raw_long = 0
    # 按列提取
    for x in range(width):
        for y in range(height):
            pixel = img.getpixel((x,y))
            if pixel != (255, 255, 255, 255):
                # print(pixel)
                raw_long = raw_long*4+table[pixel]
    
    print(raw_long)
    data = long_to_bytes(raw_long)
    print(data)
    
    with open("out.zip",'wb') as f:
        f.write(data)

还得是感谢B神脚本提供的思路,要不然根本想不到这个原理

运行以上脚本后就可以得到一个ZIP压缩包,但是Windows下直接打开是看不到内容的

imgs/image-20241219202304066.png

因为被压缩的文件内容包括文件名都是 \r\n\t空格 这种空白字符

因此我们在Linux下使用脚本解压并提取其中的内容(因为Windows下看不到文件名为空格的文件)

然后里面内容的加密方式其实和上面的原理是一样的,也是四进制,就是具体的对照表是未知的

但是因为一共就四种字符,所以我们可以直接爆破一下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
def blankbytes_decode(raw, blank_character):
    raw_long = 0
    for c in raw:
        for i in range(len(blank_character)):
            if c == ord(blank_character[i]):
                raw_long = raw_long * 4 + i
    return long_to_bytes(raw_long)

def blankbytes_brute(raw):
    blank_character_base = [b"\x09", b"\x0a", b"\x0d", b"\x20"]
    for perm in permutations(blank_character_base):
        print(f"Testing permutation: {perm}")
        try:
            result = blankbytes_decode(raw, perm)
            print(f"Decoded result: {result}")
        except Exception as e:
            print(f"Error with permutation {perm}: {e}")

爆破后即可得到正确的表和最后的flag:flag{46eade75-846b-4d26-98f7-2cb3cb4686ed}

imgs/image-20241219202718201.png

完整的解题脚本如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
from Crypto.Util.number import long_to_bytes
from itertools import permutations
from PIL import Image
import pyzipper


def extract_data_from_img(img_name):
    img = Image.open(img_name)
    width,height = img.size
    # 四进制的表
    table = {
        (255, 255, 255, 254):0,
        (255, 255, 254, 255):1,
        (255, 254, 255, 255):2,
        (254, 255, 255, 255):3
    }
    raw_long = 0
    # 按列提取
    for x in range(width):
        for y in range(height):
            pixel = img.getpixel((x,y))
            if pixel != (255, 255, 255, 255):
                # print(pixel)
                raw_long = raw_long*4+table[pixel]
    
    print(raw_long)
    data = long_to_bytes(raw_long)
    print(data)
    
    with open("out.zip",'wb') as f:
        f.write(data)

def blankbytes_decode(raw, blank_character):
    raw_long = 0
    for c in raw:
        for i in range(len(blank_character)):
            if c == ord(blank_character[i]):
                raw_long = raw_long * 4 + i
    return long_to_bytes(raw_long)

def blankbytes_brute(raw):
    blank_character_base = [b"\x09", b"\x0a", b"\x0d", b"\x20"]
    for perm in permutations(blank_character_base):
        print(f"Testing permutation: {perm}")
        try:
            result = blankbytes_decode(raw, perm)
            print(f"Decoded result: {result}")
        except Exception as e:
            print(f"Error with permutation {perm}: {e}")


if __name__ == "__main__":
    img_name = "image1.png"
    extract_data_from_img(img_name)
    zip_file = "out.zip"
    with pyzipper.ZipFile(zip_file,"r") as zip_ref:
        zip_ref.extract(" ","./")
    with open(" ","rb") as f:
        data = f.read()
    res = blankbytes_brute(data)
    print(res)

B站讲解视频中的脚本如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
from PIL import Image
from itertools import permutations
from Crypto.Util.number import long_to_bytes

def func1():
    img = Image.open("image1.png")
    width, height = img.size # 34 34 
    pixel_dic = {}

    for y in range(height):
        for x in range(width):
            pixel = img.getpixel((x,y))
            if pixel not in pixel_dic:
                pixel_dic[pixel] = 1
            else:
                pixel_dic[pixel] += 1
                
    print(pixel_dic)


def func2():
    img = Image.open("image1.png")
    width, height = img.size
    img2 = Image.new("RGBA",(width,height),(0,0,0,255))
    for y in range(height):
        for x in range(width):
            pixel = img.getpixel((x,y))
            # if pixel == (255, 255, 255, 255):
                # img2.putpixel((x,y),(255,255,255,255))
            # if pixel == (255, 255, 255, 254):
            #     img2.putpixel((x,y),(255,255,255,254))
            # if pixel == (255, 255, 254, 255):
            #     img2.putpixel((x,y),(255,255,254,255))
            # if pixel == (255, 254, 255, 255):
            #     img2.putpixel((x,y),(255,254,255,255))
            # if pixel == (254, 255, 255, 255):
            #     img2.putpixel((x,y),(254,255,255,255))
    img2.show()
    
    
def deocde_img(perm):
    img = Image.open("image1.png")
    width, height = img.size # 34 34 
    res = 0
    # ** 注意需要按列提取 **
    for x in range(width):
        for y in range(height):
            pixel = img.getpixel((x,y))
            if pixel != (255, 255, 255, 255):
                for idx,item in enumerate(perm):
                    if pixel == item:
                        res = res*4 + idx
    print(f"{'='*20} {perm} {'='*20}")
    print(res)
    print(long_to_bytes(res))    
    
    
def func3():
    table_list = [(255, 255, 255, 254),(255, 255, 254, 255),(255, 254, 255, 255),(254, 255, 255, 255)]
    for perm in permutations(table_list):
        # print(perm)
        deocde_img(perm)
    
    
def func4():
    perm = [(255, 255, 255, 254),(255, 255, 254, 255),(255, 254, 255, 255),(254, 255, 255, 255)]
    img = Image.open("image1.png")
    width, height = img.size # 34 34 
    res = 0
    # ** 注意需要按列提取 **
    for x in range(width):
        for y in range(height):
            pixel = img.getpixel((x,y))
            if pixel != (255, 255, 255, 255):
                for idx,item in enumerate(perm):
                    if pixel == item:
                        res = res*4 + idx
    print(res)
    print(long_to_bytes(res))
    
    with open("1.zip",'wb') as f:
        f.write(long_to_bytes(res))        


def decode_cipher(perm):
    res = 0
    with open("flag",'rb') as f:
        data = f.read()
    # print(data)
    for item in data:
        for idx,elem in enumerate(perm):
            if item == elem:
                res = 4 * res + idx

    print(f"{'='*20} {perm} {'='*20}")
    print(res)
    print(long_to_bytes(res))
                
        
def func5():
    with open("flag","rb") as f:
        data = f.read()
    # print(data)
    
    elem_list = [ord('\n'),ord('\t'),ord(' '),ord('\r')]
    for perm in permutations(elem_list):
        # print(perm)
        decode_cipher(perm)


if __name__ == "__main__":
    # func1()
    # func2()
    # func3()
    # func4()
    func5()

[SOLVED] 题目名称 道可道,非常道 (2024 ISCC 博弈对抗赛)

非常感谢 @Aura 师傅最后的奇思妙想,发现了频谱图中的二维码需要旋转,给这道题画上了圆满的句号。

题目附件: https://pan.baidu.com/s/1dyDJ_az_smtX6exFLinavg?pwd=pnet 提取码: pnet

赛后主办方给出了本题考察的知识点:

imgs/image-20250306115413629.jpeg

附件给了两个txt还有三个7z压缩包

imgs/image-20250103191805347.png

皮箱封条.txt的内容如下:

大衍数列,来源于《乾坤谱》中对易传“大衍之数五十”的推论。主要用于解释中国传统文化中的太极衍生原理。

数列中的每一项,都代表太极衍生过程中,曾经经历过的两仪数量总和。是中华传统文化中隐藏着的世界数学史上第一道数列题。

请依据下面的提示总结出大衍数列的通项式

0,2,4,8,12,18,24,32,40,50,60,72,84,98……

最后请求出第22002244位是多少?(好像他比较喜欢十六进制)

解决该问题的脚本如下,最后算出答案是242049370517768,十六进制是dc2482bf7108

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
lst = [0] * 22002245

for i in range(1,22002245):
    if i % 2 == 1:
        lst[i] = (i * i - 1) // 2
    else:
        lst[i] = (i * i) // 2
    
res = lst[22002244]
print(res,hex(res),hex(res)[2:])
# 242049370517768 0xdc2482bf7108 dc2482bf7108

尝试使用得到的答案去解压压缩包,但是发现不是解压密码,不知道哪里出问题了,因此打算直接爆破了

先用下面这个脚本生成所有可能的结果,然后把结果输出到一个字典中

用在线网站或者7z2john生成压缩包的hash,然后使用hashcat进行爆破

1
2
3
4
5
6
7
8
lst = [0] * 22002245

for i in range(1,22002245):
    if i % 2 == 1:
        lst[i] = (i * i - 1) // 2
    else:
        lst[i] = (i * i) // 2
    print(hex(lst[i])[2:])

imgs/image-20250107200433630.png

1
2
python3 1.py > dic.txt
hashcat -a 0 -m 11600 hash.txt dic.txt

imgs/image-20250107195608287.png

爆破即可得到皮箱左边.7z压缩包的解压密码:5a2dd7b80,这个数字转十进制是24207260544,是数列的第220033

当然,这里也可以用 PasswareKit 进行爆破,大概要爆个十分钟左右也能得到压缩包的解压密码

imgs/image-20250305212946653.png

解压后可以得到下面这5张二维码碎片

imgs/image-20250107201342425.png

对二维码比较熟悉的师傅可以看出来,兑不兑呢?这个图片的位置好像有点不对劲

并且我们拿010打开可以在末尾得到一个提示:overturn180

imgs/image-20250305202215525.png

因此我们需要把上面那个兑不兑呢?图片旋转180度(当然,我一开始没看到这个提示,但也凭着感觉把这个图片旋转了180度。。

皮箱封条2.txt的内容如下:

203879 * 203879 = 41566646641,

仔细观察,203879 是个6位数,

它的每个数位上的数字都是不同的,

平方后的所有数位上都不出现组成它自身的数字。

在1000000以内具有这样特点的6位数还有一个,两数相乘是多少?

解决该问题的脚本如下,可以得到另一个数为639172,因此和203879相乘的结果为130313748188

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
for num in range(100000, 1000000):
    num_str = str(num)
    if len(set(num_str)) != len(num_str):
        continue
    tmp = num * num
    tmp_str = str(tmp)
    flag = False
    for digit in tmp_str:
        if digit in num_str:
            flag = True
            break
    if not flag:
        print(num, tmp)
        
# 203879 41566646641
# 639172 408540845584

经过尝试,发现130313748188的十六进制值1e574dfedc就是皮箱右边.7z的解压密码

解压后可以得到以下四个文件夹

imgs/image-20250103215857333.png

第一个文件夹里有加急密信.word,010打开查看文件头发现是PNG图片,改后缀为.png可以得到下图

imgs/image-20250103220008449.png

第二个文件夹里有个wav文件,au打开看频谱图可以得到下图

imgs/image-20250103220051153.png

第三个文件夹里有一张宽高被修改导致CRC报错的PNG图片,还原宽高后可以得到下图

imgs/image-20250103220135167.png

第四个文件夹里有一张food.png,直接stegsolve打开查看,发现红色通道里藏了下图

imgs/image-20250103220222869.png

因此结合文件夹的名称和得到的类似二维码的碎片,大概就能猜到出题人的意图了。。

听说比赛快结束的时候,主办方给出了fuxi.7z的密码:iscc1234

额,虽然确实是弱密码,但是大部分人字典里应该都没有这个吧

直接爆破的话,8位的7zip密码也几乎不可能在赛中爆出来吧

不知道出题人咋想的,有没有测过题?或者出题人就是一个完全不懂Misc的新手?

解压压缩包后,可以得到下面这张bmp图片

imgs/fuxi.bmp

上面这张图片结合之前得到二维码碎片的文件名,很容易联想到是伏羲八卦图

imgs/641.webp

去网上搜一个伏羲八卦图,按照图中的顺序,二维码碎片的分布应该如下

太极

但是经过尝试,发现按照这个顺序拼出来的二维码扫出来是乱码,因此尝试换了一个想法

首先我们可以保证正确的是二维码三块定位块以及中间太极这几张图片的位置

我们尝试先将上面确定的四块用QRazyBox拼好,然后观察下图中红色框框标出的部分,猜测这两块碎片中一定有一行是这样的

imgs/image-20250305202615033.png

因此,经过对比,我们首先可以确定出左侧中部的那一块是

imgs/image-20250305202856559.png

然后经过反复比对,发现中间上面那块找不到对应的碎片,这里需要感谢@Aura师傅,发现了wav频谱图中那块碎片旋转后正好符合

imgs/image-20250305203036759.png

到这一步,我们就可以完全确定出每块碎片的位置了,因为的对面肯定是的对面肯定是

太极

最后我们用QRazyBox按照上面的顺序拼出完整的图片,然后扫码即可得到最后的flag:ISCC{wisH_U_ki7mo5_all_tHe_bEst}

imgs/image-20250305203334845.png

imgs/image-20250305203309887.png

完结撒花 *★,°*:.☆( ̄▽ ̄)/$:*.°★* 。

不知道有没有和我一样一直在等待这道题答案的师傅,但是人海茫茫,还是感谢师傅们能看到这里!

[SOLVED] 题目名称 QRSACode

题目附件: https://pan.baidu.com/s/1Jtgzh2AOcR4J7A-Wa-83LQ?pwd=8zcj 提取码: 8zcj

这道题要感谢 @Aura 师傅的奇思妙想,发现了hint.png中的每个像素其实都是RSA中的e

题面信息如下

描述:p = 13,q = 19,e = ?

解压附件给的压缩包,可以得到如下两张图片,其中task.png中隐约可以看到一张二维码

imgs/image-20250307114638056.png

然后结合题面的信息,我们知道在RSA中e要和phi互质,其中phi=(q-1)*(p-1)

因此我们可以写个脚本得到e所有可能的取值范围

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import gmpy2

def cal_e():
    p = 13
    q = 19
    phi = (p - 1) * (q - 1)
    res = [e for e in range(2, 256) if gmpy2.gcd(e, phi) == 1]
    # print(len(res)) # 84
    # print(res)
    return res

得到e所有可能的取值如下,一共84种可能取值:

1
[5, 7, 11, 13, 17, 19, 23, 25, 29, 31, 35, 37, 41, 43, 47, 49, 53, 55, 59, 61, 65, 67, 71, 73, 77, 79, 83, 85, 89, 91, 95, 97, 101, 103, 107, 109, 113, 115, 119, 121, 125, 127, 131, 133, 137, 139, 143, 145, 149, 151, 155, 157, 161, 163, 167, 169, 173, 175, 179, 181, 185, 187, 191, 193, 197, 199, 203, 205, 209, 211, 215, 217, 221, 223, 227, 229, 233, 235, 239, 241, 245, 247, 251, 253]

然后我们尝试去读取hint.png中的像素点

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def func1():
    dic = {}
    img1 = Image.open("hint.png")
    width,height = img1.size # 50 50
    for y in range(height):
        for x in range(width):
            pixel = img1.getpixel((x,y))
            if pixel not in dic:
                dic[pixel] = 1
            else:
                dic[pixel] += 1
    # print(len(dic)) # 2496
    print(dic)

发现2500个像素点中有2496种像素,并且只有以下两种像素出现了2次,别的像素都是只出现一次

1
2
(133, 167, 215): 2
(31, 163, 119): 2

我们把所有像素打印出来可以发现,每个像素的RGB值都是取自我们之前得到的e的取值范围中

然后我们再去看task.png,发现图像时RGBA格式的,只不过A通道的值都是255

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def solve():
    dic = {}
    img1 = Image.open("task.png")
    width,height = img1.size # 50 50
    for y in range(height):
        for x in range(width):
            pixel = img1.getpixel((x,y))
            if pixel not in dic:
                dic[pixel] = 1
            else:
                dic[pixel] += 1
    # print(len(dic)) # 1112
    # print(dic)

发现一共有1112种不同的像素

并且背景接近白色的像素点的RGBA的值为(246, 246, 246, 255),黑色像素点的RGBA值为(0, 0, 0, 255)

后来在 @Aura 师傅的帮助下,发现了其实图片中的每个像素的每个RGB的值都是RSA加密中的参数

因为我们之前得到了,hint.png中每个像素的每个RGB值都在e的取值范围中

然后hint.pngtask.png的长宽是一样的,也就是说像素的个数以及RGB值的个数也是一样的,所以是一一对应的

因此我们可以联想到,把每个像素的每个RGB值都做一次RSA解密,hint.png中的是etask.png中的是密文c

最后把我们RSA解密得到的m转为RGB值塞回图像中即可复原出二维码,扫码即可得到最后的flag:DASCTF{R54_W1th_Cv_1s_Fun}

imgs/image-20250313101353466.png

imgs/image-20250313101321442.png

最终的解题脚本如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from PIL import Image
import gmpy2
import numpy as np

p = 13
q = 19
n = p * q # 247
phi = (p-1)*(q-1) # 216

def get_e():
    e_list = []
    img1 = Image.open("hint.png")
    width,height = img1.size
    for y in range(height):
        for x in range(width):
            pixel = img1.getpixel((x,y))
            for item in pixel:
                e_list.append(item)
    print(len(e_list))
    return e_list

def func1(e_list):
    c_list = []
    m_list = []
    img1 = Image.open("task.png")
    width,height = img1.size # 50 50
    for y in range(height):
        for x in range(width):
            r,g,b,a = img1.getpixel((x,y))
            c_list.append(r)
            c_list.append(g)
            c_list.append(b)
    print(len(c_list))
    for idx,e in enumerate(e_list):
        c = c_list[idx]
        d = gmpy2.invert(e, phi)
        m = pow(c, d, n)
        m_list.append(m)
    print(len(m_list))
    pixel_array = np.array(m_list, dtype=np.uint8).reshape((height, width, 3))
    img2 = Image.fromarray(pixel_array, mode="RGB")
    img2.save("decrypted.png")
    print("[+] 处理完成,已保存为 decrypted.png")
    
if __name__ == "__main__":
    e_list = get_e()
    func1(e_list)

[SOLVED] 题目名称 Boxing Boxer

题目附件: https://pan.baidu.com/s/195It-h7CBEXJ53-cV9MkoA?pwd=dahr 提取码: dahr

这道题的成功解决要感谢@烛影摇红师傅提供的解题思路

题面信息如下:

A boxing boxer unbox a box in which another box boxes little boxes and boxes and boxes and so on.

翻译:一位拳击手打开一个箱子,里面装着另一个装满小箱子的箱子,而这些小箱子里面又装着更多的小箱子,如此这般,层层叠叠。

解压附件压缩包,可以得到一个flag.gif

imgs/flag.gif

尝试分帧提取,可以得到504张图片

imgs/image-20250307133401161.png

尝试用stegsolve查看每张图片的LSB信息,并没有发现什么特殊的信息

然后尝试提取出GIF每一帧的间隔,可以得到如下内容,发现开头和结尾都是70,一共32+31=63

1
['70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '50', '60', '50', '50', '50', '50', '50', '50', '60', '50', '60', '50', '60', '50', '60', '60', '50', '60', '60', '60', '50', '50', '50', '60', '50', '50', '50', '50', '60', '50', '50', '50', '60', '60', '60', '50', '50', '60', '50', '60', '60', '50', '60', '50', '60', '60', '50', '50', '60', '60', '60', '50', '60', '50', '50', '60', '60', '60', '50', '60', '50', '50', '60', '50', '60', '50', '60', '60', '60', '50', '60', '60', '60', '60', '60', '60', '50', '60', '60', '60', '60', '60', '60', '60', '60', '60', '50', '60', '60', '50', '60', '50', '50', '60', '50', '50', '50', '60', '60', '50', '50', '60', '50', '50', '60', '60', '50', '50', '50', '60', '60', '50', '50', '60', '50', '50', '50', '50', '50', '60', '50', '60', '50', '60', '60', '60', '60', '50', '50', '50', '50', '60', '60', '50', '60', '50', '50', '50', '50', '50', '60', '50', '60', '50', '50', '50', '60', '50', '60', '60', '60', '50', '60', '60', '60', '60', '50', '50', '50', '60', '50', '50', '50', '50', '60', '50', '60', '50', '60', '60', '50', '60', '60', '50', '60', '50', '60', '60', '50', '60', '60', '50', '50', '60', '60', '60', '50', '60', '50', '50', '50', '50', '50', '60', '50', '50', '50', '50', '60', '60', '50', '60', '50', '60', '60', '50', '50', '50', '50', '60', '60', '50', '50', '60', '60', '50', '50', '60', '50', '60', '50', '50', '50', '60', '50', '50', '60', '60', '60', '50', '50', '50', '60', '60', '60', '50', '50', '50', '60', '60', '50', '60', '50', '60', '50', '50', '60', '50', '50', '50', '50', '50', '60', '60', '60', '60', '50', '60', '60', '60', '60', '60', '60', '60', '50', '60', '50', '50', '60', '50', '60', '60', '60', '60', '50', '60', '60', '50', '50', '50', '60', '50', '50', '50', '60', '60', '50', '50', '60', '50', '50', '50', '60', '50', '60', '60', '60', '60', '60', '60', '60', '60', '60', '50', '50', '60', '50', '60', '60', '60', '50', '60', '50', '60', '60', '60', '50', '60', '50', '50', '50', '50', '50', '50', '60', '60', '50', '60', '60', '60', '50', '60', '60', '50', '50', '50', '50', '50', '60', '50', '50', '60', '50', '60', '50', '50', '60', '60', '50', '50', '60', '50', '50', '60', '60', '60', '50', '60', '50', '60', '60', '60', '60', '60', '60', '60', '50', '60', '50', '60', '60', '50', '50', '60', '60', '50', '60', '60', '50', '50', '60', '50', '50', '50', '60', '50', '60', '50', '60', '50', '50', '60', '50', '60', '60', '50', '60', '50', '60', '50', '60', '60', '50', '60', '60', '60', '60', '60', '50', '50', '60', '50', '60', '50', '60', '50', '50', '50', '60', '60', '60', '50', '50', '50', '50', '60', '60', '50', '50', '50', '50', '60', '50', '60', '60', '50', '60', '50', '50', '60', '60', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70']

除去开头和结尾的70,发现中间的部分只有50和60,然后统计了一下一共441个,因为441=21x21

所以我们猜测可能隐写了一张二维码,我们尝试把50和60转为0和1,然后尝试绘制二维码,可以得到下面这些图片

imgs/image-20250307133820417.png

转换出来后发现并不是二维码,并且尝试直接二进制转字符串也得不到什么有效的信息

1
2
3
4
5
6
import libnum

bin_data = "010000001010101101110001000010001110010110101100111010011101001010111011111101111111110110100100011001001100011001000001010111100001101000001010001011101111000100001010110110101101100111010000010000110101100001100110010100010011100011100011010100100000111101111111010010111101100010001100100010111111111001011101011101000000110111011000001001010011001001110101111111010110011011001000101010010110101011011111001010100011100001100001011010011"

print(libnum.b2s(bin_data))
# b'\x81V\xe2\x11\xcbY\xd3\xa5w\xef\xfbH\xc9\x8c\x82\xbc4\x14]\xe2\x15\xb5\xb3\xa0\x86\xb0\xcc\xa2q\xc6\xa4\x1e\xfe\x97\xb1\x19\x17\xfc\xba\xe8\x1b\xb0Jd\xeb\xfa\xcd\x91R\xd5\xbeTp\xc2\xd3'

后来在@烛影摇红师傅的帮助下,知道了这题GIF隐写的关键

这里出题人是把二维码像素的坐标隐写到了GIF每一帧的偏移量以及GIF每一帧图像的实际尺寸中了

imgs/image-20250309152216728.png

imgs/image-20250309152236508.png

因此,我们可以接着我们上面的分析,因为除去开头和结尾的70,中间的50和60一共有441个,又因为441=21x21

所以很明显暗示了我们是一个21x21的二维码,因此我们可以编写以下脚本提取出隐写的坐标并绘制二维码

Tips:以下脚本需要在Linux中运行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import subprocess
from PIL import Image
from datetime import datetime

time_space = ['70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '50', '60', '50', '50', '50', '50', '50', '50', '60', '50', '60', '50', '60', '50', '60', '60', '50', '60', '60', '60', '50', '50', '50', '60', '50', '50', '50', '50', '60', '50', '50', '50', '60', '60', '60', '50', '50', '60', '50', '60', '60', '50', '60', '50', '60', '60', '50', '50', '60', '60', '60', '50', '60', '50', '50', '60', '60', '60', '50', '60', '50', '50', '60', '50', '60', '50', '60', '60', '60', '50', '60', '60', '60', '60', '60', '60', '50', '60', '60', '60', '60', '60', '60', '60', '60', '60', '50', '60', '60', '50', '60', '50', '50', '60', '50', '50', '50', '60', '60', '50', '50', '60', '50', '50', '60', '60', '50', '50', '50', '60', '60', '50', '50', '60', '50', '50', '50', '50', '50', '60', '50', '60', '50', '60', '60', '60', '60', '50', '50', '50', '50', '60', '60', '50', '60', '50', '50', '50', '50', '50', '60', '50', '60', '50', '50', '50', '60', '50', '60', '60', '60', '50', '60', '60', '60', '60', '50', '50', '50', '60', '50', '50', '50', '50', '60', '50', '60', '50', '60', '60', '50', '60', '60', '50', '60', '50', '60', '60', '50', '60', '60', '50', '50', '60', '60', '60', '50', '60', '50', '50', '50', '50', '50', '60', '50', '50', '50', '50', '60', '60', '50', '60', '50', '60', '60', '50', '50', '50', '50', '60', '60', '50', '50', '60', '60', '50', '50', '60', '50', '60', '50', '50', '50', '60', '50', '50', '60', '60', '60', '50', '50', '50', '60', '60', '60', '50', '50', '50', '60', '60', '50', '60', '50', '60', '50', '50', '60', '50', '50', '50', '50', '50', '60', '60', '60', '60', '50', '60', '60', '60', '60', '60', '60', '60', '50', '60', '50', '50', '60', '50', '60', '60', '60', '60', '50', '60', '60', '50', '50', '50', '60', '50', '50', '50', '60', '60', '50', '50', '60', '50', '50', '50', '60', '50', '60', '60', '60', '60', '60', '60', '60', '60', '60', '50', '50', '60', '50', '60', '60', '60', '50', '60', '50', '60', '60', '60', '50', '60', '50', '50', '50', '50', '50', '50', '60', '60', '50', '60', '60', '60', '50', '60', '60', '50', '50', '50', '50', '50', '60', '50', '50', '60', '50', '60', '50', '50', '60', '60', '50', '50', '60', '50', '50', '60', '60', '60', '50', '60', '50', '60', '60', '60', '60', '60', '60', '60', '50', '60', '50', '60', '60', '50', '50', '60', '60', '50', '60', '60', '50', '50', '60', '50', '50', '50', '60', '50', '60', '50', '60', '50', '50', '60', '50', '60', '60', '50', '60', '50', '60', '50', '60', '60', '50', '60', '60', '60', '60', '60', '50', '50', '60', '50', '60', '50', '60', '50', '50', '50', '60', '60', '60', '50', '50', '50', '50', '60', '60', '50', '50', '50', '50', '60', '50', '60', '60', '50', '60', '50', '50', '60', '60', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70', '70']

def get_pos(gif_file):
    offset_x = []
    offset_y = []
    pic_width = []
    pic_height = []
    cmd = f'identify {gif_file}'
    res = subprocess.run(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True)
    output = res.stdout
    lines = output.strip().split('\n')
    
    for line in lines:
        tmp_lst = line.split(' ')
        frame_size,tmp_x,tmp_y = tmp_lst[3].split('+')
        offset_x.append(int(tmp_x))
        offset_y.append(int(tmp_y))
        tmp_x,tmp_y = tmp_lst[2].split('x')
        pic_width.append(int(tmp_x))
        pic_height.append(int(tmp_y))
        
    return offset_x,offset_y,pic_width,pic_height

def draw2pic(offset_x,offset_y,pic_width,pic_height):
    img = Image.new("RGB",(500,500),(255,255,255)) # 新建一张尺寸为500x500的RGB图像
    for idx,item in enumerate(time_space):
        if item == '70':
            continue
        elif item == '50':
            img.putpixel((offset_x[idx],offset_y[idx]),(255,255,255))
            img.putpixel((offset_x[idx]+pic_width[idx],offset_y[idx]+pic_height[idx]),(255,255,255))
        elif item == '60':
            img.putpixel((offset_x[idx],offset_y[idx]),(0,0,0))
            img.putpixel((offset_x[idx]+pic_width[idx],offset_y[idx]+pic_height[idx]),(0,0,0))
            
    timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
    print(timestamp)
    img.save(f"{timestamp}.png")

if __name__ == "__main__":
    gif_file = "flag.gif"
    offset_x,offset_y,pic_width,pic_height = get_pos(gif_file)
    draw2pic(offset_x,offset_y,pic_width,pic_height)

运行以上脚本后,即可得到左上和右下两个二维码,分别对应一半的flag

最后把两段flag组合即可得到最后的flag:DASCTF{Unb0x_a_Fil3_t0_Get_a_Fl4g}

imgs/image-20250309152607609.png

然后至于为啥右下角的二维码像素的坐标等于帧图像的实际尺寸+偏移量,一开始我也没想明白

一开始我也是直接用帧图像的实际尺寸绘图发现出不来

我这里为读者提供了两种理解方法:

第一种理解方法就是,出题人出题的时候,肯定是预先已经确定了两个二维码的位置

第一个二维码没问题,直接用偏移量隐写就行,但是第二个二维码要怎么隐写呢?

还是可以用偏移量,但是由于第一个二维码已经确定了偏移量具体的值,所以这里,只能配合偏移量改变帧图像的实际尺寸

当然这里直接通过帧图像的实际尺寸来隐写也是可以的,直接让帧图像的实际尺寸等于二维码像素的坐标就行

第二种理解方法就是,我们仔细去查看那个identify的输出结果

可以发现从第32个帧(下图中的下标是从0开始,所以下图中31代表第32帧)开始它的帧实际尺寸还是没变的

但是我们之前统计过,时间间隔为70的帧只存在于前32帧,因此这里也可以看出不是直接根据帧的实际尺寸隐写的

这时候可能就会联想到需要结合偏移量生成新的坐标

第三种的理解思路来源于@八神:把每一帧gif的实际位置的左上角和右下角像素当成两个画笔

441帧之后画出来的图形就是这两个QR码(本题思路类似于ctfshow-misc入门题-aka赛博扶乩)

imgs/image-20250309153718186.png

[SOLVED] 题目名称 Cyclone Joker

题目附件: https://pan.baidu.com/s/11DkgyTOBwumhHzIh5TKfNA?pwd=c2rb 提取码: c2rb

本题的成功解出离不开 @Aura @zysgmzb @八神这几位师傅的帮助,师傅们都太强了!【膜拜】

题面信息如下:

W is for double.

解压附件压缩包,可以得到下面这张flag.jpg的图片

imgs/image-20250315154954770.jpeg

用010打开图片,发现末尾藏了个rar,手动给它提取出来,发现rar是加密的,并且里面有个flag.bmp

imgs/image-20250315155147055.png

imgs/image-20250315155102163.png

猜测需要我们去那张flag.jpg中寻找压缩包的解压密码

尝试用PS打开图片,仔细观察可以发现最后两列的像素明显与之前的像素不同,因此猜测有内容隐写在了这里

imgs/image-20250315155342383.png

但是尝试了多种方法提取像素里的信息,都没有发现明显的压缩包密码

不知道这里提取像素中的信息是否需要用到题面中的 W is for double ,具体又该如何使用呢?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from PIL import Image
import libnum

img = Image.open("flag.jpg")
pixel_list = []
res = ""


width, height = img.size # 378 222
for y in range(height):
    for x in range(376,378):
        r,g,b = img.getpixel((x,y))
        res += str(b&1)
        # res += str(r&1) + str(g&1) + str(b&1)
        
print(len(res))
print(res)
print(libnum.b2s(res))

后来在@八神的指点下,知道了JPG图片还存在宽度(width)隐写

JPEG(jpg)图像的最小编码单元(MCU,Minimum Coded Unit)是 8×8 像素的块。换句话说,JPEG 压缩过程中会把图像划分为 8×8 的小方块进行处理。但如果宽度 不是 8 的倍数,JPEG 编码时通常会在 右边补足 至最近的 8 的倍数,以符合 8×8 MCU 处理的要求。

因此,我们尝试把上面那张JPG的宽度加到8的整数倍,即384

imgs/image-20250317144333538.png

然后打开就能很清晰的看见后面隐藏的内容

imgs/image-20250317143543572.png

发现主要是黑白像素块,因此我们写个脚本去提取里面的数据,并尝试转二进制为字符串

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from PIL import Image
import libnum

img = Image.open("flag.jpg")
pixel_list = []
res = ""

width, height = img.size # 378 222
# print(width,height)

for y in range(height):
    for x in range(380,384): # 因为最后4列像素才有黑色块,并且4是8的二分之一
        r,g,b = img.getpixel((x,y))
        # print(r,g,b)
        if b > 200:
            res += "0"
        else:
            res += "1"
            
print(res)
pwd = libnum.b2s(res)
print(pwd)
print(pwd.decode())
# password:ysiUDvXg(20~lPZi#2\*&)<,nb7m)x;ZBeZo+k*=t7npWGQ[w&s;#&yThd+PHnmAOd7bf3~~?)#0bg29+|F;E/u>Evv0oML:R$07

运行以上脚本即可得到rar的解压密码:ysiUDvXg(20~lPZi#2\*&)<,nb7m)x;ZBeZo+k*=t7npWGQ[w&s;#&yThd+PHnmAOd7bf3~~?)#0bg29+|F;E/u>Evv0oML:R$07

解压后可以得到下面这张bmp图片

imgs/flag.bmp

尝试了常见的BMP的隐写后发现无果,然后发现BMP图片的宽度也不是8的整数倍

因此猜测可能和上面一样,存在宽度隐写

所以我们把BMP的图片也增加到8的整数倍,即在010中把宽度改为184

imgs/image-20250317144223304.png

改完后就能清晰的看到隐写的像素

imgs/image-20250317144253026.png

然后尝试用stegsolve去看看有没有LSB隐写的时候,发现了PNG文件的特征,只不过数据是逆置的

imgs/image-20250317144529424.png

imgs/image-20250317144608196.png

imgs/image-20250317144619902.png

发现其实这里就是直接把PNG的十六进制数据转为bytes类型分三段隐写到RGB像素里了

因此我们写个脚本把数据提取出来就行,但是这里要注意每段数据前后有多余的\x00

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from PIL import Image

img = Image.open("flag.bmp")
r_lst = []
g_lst = []
b_lst = []

width, height = img.size # 184 107
# print(width,height)
for y in range(height):
    for x in range(183,184):
        r,g,b = img.getpixel((x,y))
        r_lst.append(r)
        g_lst.append(g)
        b_lst.append(b)

print(bytes(r_lst))
print(bytes(g_lst))
print(bytes(b_lst))
part1 = bytes(r_lst)[::-1][7:-7]
part2 = bytes(g_lst)[::-1][7:-7]
part3 = bytes(b_lst)[::-1][7:-7]
res = part3 + part2 + part1
print(res)

with open("flag.png","wb") as f:
    f.write(res)

运行以上脚本后即可得到下图

imgs/image-20250317144900585.png

经过尝试发现这张图片其实也和上面一样,用width隐写了内容(其实也可以说题面 W (width) is double 提示了这个)

因此我们还是一样,把这张PNG图片的宽度加到8的倍数。即把宽度修改为64

imgs/image-20250317145203105.png

然后就可以看到如下隐写的内容

imgs/image-20250317145301256.png

因此我们写个脚本去提取里面的数据,但是在提取的过程中发现因为宽度被修改了,CRC校验会报错

从而导致PIL和CV都无法识别这张图片,所以我们可以用PS重新导出一下这张PNG

imgs/image-20250317145541736.png

重新导出后的图片就能被PIL正常识别了,然后我们看这道题的出题模板

imgs/image-20250317145721493.png

很容易就能看出是安恒的题,因此猜测flag的前缀是DASCTF{

因此我们转二进制,然后和图片对应起来看,很容易找到对应的规律:黑色-0 白色-1

但是由于后面只有7列,因此我们需要在每一行的二进制数据前补0

imgs/image-20250317145950183.png

最后写个脚本提取数据即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from PIL import Image, ImageFile
import libnum


img = Image.open("1.png")
width, height = img.size # 184 107
# print(width,height)
res = ""
for y in range(height):
    res += '0'
    for x in range(width-7,width):
        r,g,b,a = img.getpixel((x,y))
        if r > 200:
            res += "1"
        else:
            res += "0"
print(res)
print(libnum.b2s(res))
# 000000000100010001000001010100110100001101010100010001100111101101001110011011110111011100101100010111110110001101101111011101010110111001110100010111110111010101110000010111110111100101101111011101010111001001011111011100110110100101101110011100110010000101111101
# b'DASCTF{Now,_count_up_your_sins!}'

运行以上脚本即可得到最后的flag:DASCTF{Now,_count_up_your_sins!}

这个flag翻译成中文就是:现在,数一数你的罪过。

也许出题人也知道自己干了坏事,对那场比赛的选手心存愧疚吧

至此,这道题也是圆满结束了,最后还是再次感谢@Aura @zysgmzb @八神这几位师傅的帮助

[SOLVED] 题目名称 Steganography_challenges0.3(2025 西湖论剑全国总决赛)

题目附件: https://pan.baidu.com/s/1F6ZBdr-Nnhw0UmjF3_9DzA?pwd=f83m 提取码: f83m

附件已更新,原来的附件是损坏的,感谢@1cePeak师傅的提醒,要不然对着坏的附件,这题这辈子都做不出来了

本题的成功解出离不开@1cePeak师傅的帮助

解压附件压缩包,可以得到一个encrypt.py和一张PNG图片,内容如下所示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from PIL import Image
from Crypto.Cipher import ARC4

def rc4_encrypt(data, key):
    cipher = ARC4.new(key.encode())
    return cipher.encrypt(data)

image = Image.open('flag1.png').convert('RGB')
width, height = image.size

new_image = Image.new('RGB', (width, height))

key = ''#ps:Passwords are common passwords (weak passwords) that may be required...

for y in range(height):
    for x in range(width):
        r, g, b = image.getpixel((x, y))
        rgb_bytes = bytes([r, g, b])
        encrypted_rgb = rc4_encrypt(rgb_bytes, key)
        new_image.putpixel((x, y), (encrypted_rgb[0], encrypted_rgb[1], encrypted_rgb[2]))

new_image.save('Steganography_challenges0.3.png')

imgs/image-20250331201515898.png

发现是用弱密码简单加密了一下图片,然后我们用stegsolve打开图片可以发现图片存在LSB隐写

imgs/image-20250331201900614.png

因此我们根据LSB隐写的内容写个脚本爆破一下密钥即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def func1():
    with open("rockyou.txt",'r',errors='ignore') as f:
        keys = f.read().split()
    for key in keys:
        img = Image.open("1.png")
        w,h = img.size
        img1 = Image.new('RGB',(w,h))
        for y in range(h):
            for x in range(w):
                r,g,b = img.getpixel((x,y))
                rgb_bytes = bytes([r,g,b])
                dec_rgb = rc4_decrypt(rgb_bytes,key)
                img1.putpixel((x,y),(dec_rgb[0],dec_rgb[1],dec_rgb[2]))
        # img1.show()
        bin_data = (np.array(img1) & 1).flatten().astype(str)
        res = libnum.b2s(''.join(bin_data))
        print(res[:100])
        if b'flag' in res or b'DASCTF' in res:
            print(f"[+] 爆破成功,密钥是 {key}")
            print(res[:150])
            break
# [+] 爆破成功,密钥是 password
# b"DASCTF{01d0eed8-2e4b,Do you know DWT-QIM? Now I'll give you the key information,
# next_information_length = 102,block_size = 8,delta=8,Green channel

运行以上脚本后即可得到第一段flag:DASCTF{01d0eed8-2e4b,以及下一步的提示

并且可以用爆破出来的密码还原图片,还原后的图片如下

imgs/image-20250331201753939.png

然后去网上搜DWT-QIM相关的内容,可以搜到2024WMCTF-steg_allInOne这道题

发现整体思路和这道题差不多,猜测出题人的出题思路也是来源于这道题

然后我们010打开附件给的那张PNG,发现图片的末尾藏了一张base64编码后的图片

imgs/image-20250401183123671.png

提取出来base64解码即可得到下图

imgs/image-20250401183228651.png

参考上面2024WMCTF那道题的wp,改一下提取水印的脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from PIL import Image
import numpy as np
import libnum
import pywt

def extract_qim(block, delta):
    block_flat = block.flatten()
    avg = np.mean(block_flat)
    mod_value = avg % delta
    if mod_value < delta / 4 or mod_value > 3 * delta / 4:
        return '0'
    else:
        return '1'

def extract_watermark1(G_watermarked, watermark_length, delta):
    watermark_bits = []
    block_size = 8
    k = 0
    for i in range(0, G_watermarked.shape[0], block_size):
        for j in range(0, G_watermarked.shape[1], block_size):
            if k < watermark_length * 8:
                block = G_watermarked[i:i+block_size, j:j+block_size]
                if block.shape != (block_size, block_size):
                    continue
                coeffs = pywt.dwt2(block, 'haar')
                LL, (LH, HL, HH) = coeffs
                bit = extract_qim(LL, delta)
                watermark_bits.append(bit)
                k += 1

    watermark_str = ''.join(watermark_bits)
    return watermark_str

if __name__ == "__main__":
    p = Image.open('download.png').convert('RGB')
    p_data = np.array(p)
    G = p_data[:,:,1].astype(np.float32)
    print(libnum.b2s(extract_watermark1(G,102,8)))
# b"Hey boy, I'm here to help you, now you'ze one step away from successl let me |ell you key:79557c2d8f94"

运行后可以得到如下内容,提示了我们一个key:79557c2d8f94

1
b"Hey boy, I'm here to help you, now you'ze one step away from successl let me |ell you key:79557c2d8f94"

然后我们010打开提取得到的图片,发现有一块异常的IDAT chunk

imgs/image-20250401183350583.png

尝试给他单独提取出来,发现按照2024WMCTF的思路做得不到有用的东西

然后也尝试了异或密钥,也没有得到有效的信息,在@IcePeak师傅的提醒下,查看了这块数据的大小

发现刚刚好是300kb,因此结合题目给了密钥,联想到可能是VeraCrypt加密容器

imgs/image-20250401184416937.png

然后这里提取这块数据的时候要注意,不要把表示数据长度的那四个字节也提取出来

我们要从下图中高亮的位置后面开始提取

imgs/image-20250401184348934.png

提取出来后,用VC挂载即可得到flag2:-4dc6-9152-ffe56b0f70b4}

imgs/image-20250401184736460.png

综上,将得到的两段flag拼起来即可得到最后的flag:DASCTF{01d0eed8-2e4b-4dc6-9152-ffe56b0f70b4}

一些碎碎念:出题人最后套了一个VC加密容器确实会比较难想到,需要选手结合数据的长度和密钥进行联想

但也正是出题人这最后套了一下VC加密容器,让这道题更贴合“数据安全”这个分类吧

[SOLVED] 题目名称 扫一扫 (2025 安徽省赛)

题目附件: https://pan.baidu.com/s/1Xuo8ACEdarB8Z4D989Ytvw?pwd=hbph 提取码: hbph

解压附件压缩包,得到一张flag.png,是张二维码,扫码后可以得到:123

imgs/image-20250420153541878.png

010打开,发现末尾藏了另一张二维码还有一个加密的压缩包,二维码提取出来扫码后得到:qwe

imgs/image-20250420153618119.png

用密码123qwe解压压缩包,可以得到一个flag.pcap.pcapng流量包文件

imgs/image-20250420153744285.png

追踪流发现只有一个HTTP请求,但是没有看到响应

010打开流量包,发现里面有一串AES加密后的密文

imgs/image-20250420153913383.png

用密钥123qwe作为密钥去解密这段密文,可以得到一串Base64

imgs/image-20250420154001050.png

Base64解码后可以得到一串32位的字符串,经过尝试发现是MD5

imgs/image-20250420154018750.png

最后,用somd5反查MD5即可得到最后的flag:flag{you_are_the_best}

imgs/image-20250420154120585.png

这道题本身出的没啥水平,最后一步反查MD5更是典中典(听说赛后才能查得到)

把它挂在这里也就是想记录一下这场比赛的某些主办方丑陋的行径

[SOLVED] 题目名称 discretedpoint

题目附件: https://pan.baidu.com/s/1V9ndTVFoiFWzdndj18N92w?pwd=8ppa 提取码: 8ppa

解压附件压缩包,可以得到下面这张比较杂乱的图片

imgs/image-20250429141528394.png

010打开提示报错,发现是图片高度被篡改了,因此我们爆破一下图片宽高并修复,可以得到下面这张图片

imgs/image-20250429141646786.png

可以看到一串红色的字符串:DL5aRqTz39

然后我们用PS打开分析这张图片,从像素点上看我们可以发现出题人应该是把两张图片合到一起了

合成的方法应该是提取一列像素,然后以一定的间隔合成,我们仔细观察下面的黑色像素,就可以知道间隔是3

imgs/image-20250429141842307.png

因此我们可以尝试以3作为间隔,写个脚本提取一下上图中黑色像素构成的图像

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from PIL import Image
import numpy as np

def fix_png():
    img = Image.open('1.png')
    w,h = img.size
    # print(w,h) # 840 598
    for y in range(h):
        for x in range(0,w,4):
            r,g,b,a = img.getpixel((x,y))
            pixel_list.append([r,g,b,a])

    new_wifth = len(pixel_list) // h
    print(np.array(pixel_list,dtype=np.uint8))
    img_pixel = np.array(pixel_list,dtype=np.uint8).reshape(h,new_wifth,4)
    fix_img = Image.fromarray(img_pixel,'RGBA')
    fix_img.save("fixed.png")

if __name__ == "__main__":
    fix_png()

运行以上脚本后即可得到下图

imgs/image-20250429142156924.png

然后我们继续关注剩下的像素,尝试把像素点的RGBA值打印出来,看看是否有什么规律

imgs/image-20250429142407257.png

首先我们观察图像的最后几列,发现彩色的像素数据到倒数第二列中间就中断了

因此猜测我们需要按列来遍历像素

imgs/image-20250429142338767.png

然后当我们按列遍历像素,并把RGBA值转为十六进制打印出来的时候

发现了明显的压缩包的PK头(504B0304),因此我们写个脚本提取一下数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from PIL import Image
import numpy as np

r_list = []
g_list = []
b_list = []
a_list = []
pixel_list = []

def extract_data():
    hex_data = ""
    img = Image.open('1.png')
    w,h = img.size
    # print(w,h) # 840 598
    for r in range(1,w,4):
        for x in range(r,r+3):
            for y in range(h):
                r,g,b,a = img.getpixel((x,y))
                r_list.append(f"{r:02x}")
                g_list.append(f"{g:02x}")
                b_list.append(f"{b:02x}")
                a_list.append(f"{a:02x}")

    print(r_list[:30])
    print(g_list[:30])
    print(b_list[:30])
    print(a_list[:30])

    for i in range(len(r_list)):
        hex_data += r_list[i]+g_list[i]+b_list[i]+a_list[i]

    with open("flag.zip",'wb') as f:
        f.write(bytes.fromhex(hex_data))

if __name__ == "__main__":
    extract_data()

运行以上脚本即可得到一个加密的压缩包,然后用之前得到的DL5aRqTz39作为解压密码解压

即可得到最后的flag:DASCTF{8ea740d3c5fc1022739fdb6c055eb8cb}

imgs/image-20250429142819946.png

[SOLVED] 题目名称 Ste9ano9raphy 6inary(2022CISCN 华南分区赛)

题目附件: https://pan.baidu.com/s/1-yjWWcdAwGmNDsGxAA8Gow?pwd=93bs 提取码: 93bs

本题的成功解出,离不开@1cePeak师傅的帮助

附件压缩包中有一张PNG图片和一个看起来是加密的wav文件

imgs/image-20250321193247844.png

其中 996.png 那张图片的内容如下:

imgs/image-20250321193335358.png

直接zsteg扫一下,发现LSB隐写了第一段的flag:Part1:dasctf{D0_U_

imgs/image-20250429141301854.png

然后经过尝试,发现了上面压缩包中的那个wav是伪加密的,010打开压缩包改一下加密位即可正常解压

然后在@1cePeak师傅的帮助下,知道了这个WAV音频存在LSB隐写

因此写个脚本提取出其中的LSB数据后可以得到一个密码:password:NO996!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import wave
import libnum

wav = wave.open('996.wav', 'r')

# 读取前1000帧的音频数据并将其转换为十六进制字符串
# readframes()返回的是字节数据,.hex()将其转换为十六进制表示
frames_data = wav.readframes(1000).hex()
res = ''

# 遍历十六进制数据,每次处理4个字符(2字节)
# 因为WAV文件通常使用16位(2字节)采样
for i in range(0, len(frames_data), 4):
    data = frames_data[i:i+4]
    # 将数据从小端序转换为大端序,因为WAV文件使用小端序存储数据
    data_rev = int(data[2:] + data[:2], 16)
    # 使用位与运算(&)获取最低有效位(LSB)
    res += str(data_rev & 1)
    
print(libnum.b2s(res))
# 7avpassword:NO996!=

然后用上面得到的密码去Silenteye中解密即可得到第二段的flag:like_996?}

最后,把两段flag合起来就是本题最后的flag:dasctf{D0_U_like_996?}

imgs/image-20250429203549843.png

[SOLVED] 题目名称 环环相扣

题目附件: https://pan.baidu.com/s/1WRXY2Ki_BCZTlUnrZhnQWA?pwd=8jjq 提取码: 8jjq

本题的成功解出,需要感谢@烛影摇红师傅提供的思路

解压附件压缩包可以得到下面这张001.png,其中有文字 无情哈拉少zbc

imgs/image-20250429214719757.png

010打开图片,发现图片存在明显OurSecret隐写的痕迹,并且末尾藏了一个压缩包

imgs/image-20250429214847922.png

我们把压缩包提取出来后打开,发现里面有如下几个文件

imgs/image-20250429214925406.png

我们从flag.pdf中可以得到flag1.zip的解压密码:3f9ed0gw2jk `` imgs/image-20250429215014389.png

解压后可以得到一个flag.txt

imgs/image-20250429215054764.png

经过尝试,发现存在snow隐写,并且snow隐写的密钥就是之前的 无情哈拉少zbc

imgs/image-20250429215204134.png

snow隐写提取得到的内容如下:

1
11011 10101 10101 10101 11111 01110 11011 10101 10111 10101 00111 00111 11111 11001 11011 10000 00111 00001 10110 00111 00111 00111 00111 00111 00111 10000

然后我们用随波逐流梭一把,发现博多baudot码可以正常解码得到666C开头的十六进制字符串

imgs/image-20250429215315518.png

把解码后得到的十六进制数据转字符串即可得到第一段flag:flag{W0www

imgs/image-20250430104705645.png

然后我们再把注意力集中到压缩包中那几个没有内容txt文件中,猜测是文件创建时间戳隐写

因此我们写个脚本,提取一下时间戳,并尝试通过某种方式转换到Ascii码上

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import pyzipper
import datetime

def get_zip_timestamps(zip_path):
    timestamps = []
    with pyzipper.ZipFile(zip_path) as zip_file:
        for file_info in zip_file.infolist():
            filename = file_info.filename
            modified_datetime = datetime.datetime(*file_info.date_time)
            modified_timestamp = modified_datetime.timestamp()
            timestamps.append({
                'filename': filename,
                'modified_timestamp': modified_timestamp
            })
    return timestamps

def translate_data(timestamps):
    for entry in timestamps:
        # print(f"{entry['filename']} {entry['modified_timestamp']}")
        if '.txt' in entry['filename']:
            print(chr(int(entry['modified_timestamp']) - 1737276000+1),end='')
            # YUKWOU9sUYeWSU5qUUKOaA==

if __name__ == '__main__':
    zip_file_path = '1.zip'
    timestamps = get_zip_timestamps(zip_file_path)
    translate_data(timestamps)

发现提取出来的数据,最后两个字符是相同的,因此猜测可能是Base64编码中的==

按照这个想法,将每个字符的Ascii码后移一位即可得到:YUKWOU9sUYeWSU5qUUKOaA==

这里需要感谢@烛影摇红师傅提供的思路,这里读取时间戳需要把压缩包中的.txt文件先解压到一个目录中

然后再读取目录中所有.txt文件修改时间的时间戳

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import os
import base64
from datetime import datetime

def get_numeric_part(filename):
    try:
        base_name = os.path.splitext(filename)[0]
        return int(base_name)
    except ValueError:
        return float('inf')

def get_file_timestamps(directory):
    file_timestamps = {}
    file_list = []
    
    for root, dirs, files in os.walk(directory):
        for filename in files:
            filepath = os.path.join(root, filename)
            file_list.append((filename, filepath))
    
    file_list.sort(key=lambda x: get_numeric_part(x[0]))
    
    for filename, filepath in file_list:
        try:

            stat_info = os.stat(filepath)
            # 获取修改时间和创建时间
            modified_time = stat_info.st_mtime
            if os.name == 'nt':
                created_time = stat_info.st_ctime
            else:
                created_time = stat_info.st_ctime
            
            # 转换为datetime对象
            modified_dt = datetime.fromtimestamp(modified_time)
            created_dt = datetime.fromtimestamp(created_time)
            
            file_timestamps[filepath] = {
                'created_unix': created_time,
                'modified_unix': modified_time,
                'created_datetime': created_dt.strftime('%Y-%m-%d %H:%M:%S'),
                'modified_datetime': modified_dt.strftime('%Y-%m-%d %H:%M:%S')
            }
        except Exception as e:
            print(f"无法获取文件 {filepath} 的时间戳: {str(e)}")
            continue
    
    # 按排序后的顺序打印
    for filename, filepath in file_list:
        if filepath in file_timestamps:
            time_info = file_timestamps[filepath]
            print(f"{filepath} 创建时间: {time_info['created_unix']} 修改时间: {time_info['modified_unix']}")
    
    return file_timestamps

def extract_data(timestamps):
    res = ""
    # 按文件名中的数字排序,会把字典中的键值对转为元组列表
    sorted_files = sorted(timestamps.items(), key=lambda x: get_numeric_part(os.path.basename(x[0])))
    # print(sorted_files)
    for filepath, time_info in sorted_files:
        res += chr(int(time_info['modified_unix']-1737276000))
    print(res)

if __name__ == "__main__":
    target_directory = "out"
    timestamps = get_file_timestamps(target_directory)
    extract_data(timestamps)
    # YTJWNU9sTXdVRU5qTTJNaA==

读取出来后可以得到一串base64编码,解两次base64后即可得到密钥:key:S0PCc3c!

imgs/image-20250502144812573.png

然后用这个密钥去解密Oursecret即可得到flag2.txt

imgs/image-20250502144841492.png

flag2.txt中的内容如下:

1
11111 01101 11011 01010 11111 01001 11011 00001 10111 00111 00001 10101 00001 10000 11111 01101 11011 01010 11111 01001 11011 01010 10110 00111 00001 00111 01010 00001 00001 00111 10011 00001 00001 00001 00001 00001 00001 10011 10111 10011 10111 10011 10111 00111 11111 01001

猜测是和之前一样的博多码,因此我们把两段密文合起来,然后用随波逐流解密

1
11011 10101 10101 10101 11111 01110 11011 10101 10111 10101 00111 00111 11111 11001 11011 10000 00111 00001 10110 00111 00111 00111 00111 00111 00111 10000 11111 01101 11011 01010 11111 01001 11011 00001 10111 00111 00001 10101 00001 10000 11111 01101 11011 01010 11111 01001 11011 01010 10110 00111 00001 00111 01010 00001 00001 00111 10011 00001 00001 00001 00001 00001 00001 10011 10111 10011 10111 10011 10111 00111 11111 01001

imgs/image-20250502145008947.png

最后把十六进制转字符串即可得到最后的flag:flag{W0www_M1sc_M@st3r333!!!}

imgs/image-20250502145045075.png

题目名称 Just Not Good

题目附件: https://pan.baidu.com/s/12j_kEm7Vw0PETvJ8GSiC8g?pwd=u7q6 提取码: u7q6

解压附件压缩包可以得到一个file,但是打开发现有PNG的文件头,但改后缀为.png后图片无法正常显示

imgs/image-20250411202213509.png

imgs/image-20250411202232465.png

在010中打开图片,发现PNG头还在,但是尾部已经损坏了,并且发现里面由一大串02 1A 00 00

因此猜测是异或了02 1A 00 00,因此我们用CyberChef异或一下

imgs/image-20250411202350469.png

异或完后下载到本地,再用010打开,PNG头坏了也没事

尝试foremost一下,发现可以得到下图

imgs/image-20250411202508050.jpeg

其实我们在010中爆搜也能找到JPG的头,因此也可以手动提取

imgs/image-20250411202601189.png

得到上述图片后,用010改一下图片的高度,即可得到最后的flag:DASCTF{fab6ce57e3131f1635b0c953debe67ac165f}

imgs/image-20250411202746950.png

imgs/image-20250411202652827.png

题目名称 Fingers_play (2024 ISCC 个人挑战赛)

题目附件: https://pan.baidu.com/s/1wSR_G9N-5739BeJgP4zouQ?pwd=6dqv 提取码: 6dqv

题目名称 one (2024 古剑山)

题目附件: https://pan.baidu.com/s/1iSL1P1Z1Oa8WB0tXRWjSmg?pwd=vc66 提取码: vc66

题目附件给了一个cnc.txt,内容是10000行每行114个字符的十六进制数据

部分内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
dd1bbd60cff3095f188b70af36a4ea2644f5241a425469a3b2b7c92fabd639ad55dfc8dd4393e4c572af31dbc4dab5173cc0bcb768331fb51d
a3f4057ce7fee14d7a79a28f9b51fb0e64063e41e09a0102b9024ed8da62c79a7e02155b23e9f2f66d8962260a04a6a92a4336aca932cc7431
be509b0be9e225cf2d639bbfb6e9595eb8111deb74b2b236265359a0bf5f2cdae1825a9072ce751f9fa40ea1bef650b5137506282ee02674c7
e8041edfc9dd08a5e74e4c4abe8c9dbca089572974798180fd5c11a15dad2a10968803c191592084600ce534cd9361194010759e97d30dfa15
9ab9a7966725e87fe0c92b4fdc95e7ae833aa180db6f295340cbcf294c7d08834e91dfdfafa98c7cc03404dbdf502bdc2e7e4c046ebd62fb23
c8725bb8060e13fead44e9a5ffb6f331383a717e9ec8498112a33ca8940a75947a8b191c68ac68e8459f7a6eb5737413a6484ff0b91004ec1a
cc385111badde9d78f2f5aee756eb34a09b86a4284a18902eee1bbbfadf1915bf8d19a03a7dc6b9bad117530dc505a76e6d5fc9f3897e89034
b318450ba8c7c8cd618ea3c4d396a2e99c3d99fe150218f2ff484003ed13e205ebe61b6108970ff530635f8fbc57e61e476618b3566de76ef2
95cda1e94b9d59843069fb8f2c4415404f31be077ebc1f83a61f08cd62d75e3d6379ca0b69e375372fd0f206d1009ebf397683c927da0ab63d
bc3cf1722bf8f617acc85ba7169649ecd70c7e9575c05ef04cde5bd8eb79120a756e1a7755acb17da63fe76286759b68f646178c8a01fac142

发现每行长度都一样,然后结合one联想到可能是一个密钥加密的,一开始猜测是OPT或者MPT但是发现做不出来

题目名称 pingping (2024 蓝桥杯全国总决赛)

题目附件: https://pan.baidu.com/s/1nE4F_kVzgRaDulA0xOjiWQ?pwd=33tm 提取码: 33tm

题面信息如下:

你是一个网络安全管理员,负责监控公司内部网络的安全性。

最近,你收到了关于内部网络存在异常 ICMP 流量的报告。

你决定分析这些流量,以确定是否将公司的机密信息传输到了外部。

题目附件给了一个流量包文件,首先看了HTTP流量,发现没有什么关键信息

结合题目名pingping,猜测关键信息在icmp协议的流量中

因此结合过滤器查看,发现果然有关键信息,如下图中压缩包的PK头(504B0304)

imgs/image-20250502163730487.png

因此我们尝试使用tshark把不同icmp.idnt流量包的数据都提取出来

1
2
3
4
5
6
7
tshark -r 1.pcap -Y '((_ws.col.protocol == "ICMP") && (ip.src == 10.211.55.4)) && (icmp.ident == 44329)' -T fields -e 'data' > ad29.txt
tshark -r 1.pcap -Y '((_ws.col.protocol == "ICMP") && (ip.src == 10.211.55.4)) && (icmp.ident == 5930)' -T fields -e 'data' > 172a.txt
tshark -r 1.pcap -Y '((_ws.col.protocol == "ICMP") && (ip.src == 10.211.55.4)) && (icmp.ident == 16429)' -T fields -e 'data' > 402d.txt
tshark -r 1.pcap -Y '((_ws.col.protocol == "ICMP") && (ip.src == 10.211.55.4)) && (icmp.ident == 41517)' -T fields -e 'data' > a22d.txt
tshark -r 1.pcap -Y '((_ws.col.protocol == "ICMP") && (ip.src == 10.211.55.4)) && (icmp.ident == 21038)' -T fields -e 'data' > 522e.txt
tshark -r 1.pcap -Y '((_ws.col.protocol == "ICMP") && (ip.src == 10.211.55.4)) && (icmp.ident == 29237)' -T fields -e 'data' > 7235.txt
tshark -r 1.pcap -Y '((_ws.col.protocol == "ICMP") && (ip.src == 10.211.55.4)) && (icmp.ident == 55349)' -T fields -e 'data' > d835.txt

数据提取出来,解码Hex可以得到多个zip压缩包,解压后可以得到多张PNG图片

经过比较MD5值去重后,一共可以得到下面这25张大部分像素为蓝色并且大小为20x20的PNG图片

这里其实只要提取 icmp.ident == 0x522e 的数据包就行,直接可以提出来所有的图片

imgs/image-20250502164419287.png

每张图片的文件名长度都是32,因此猜测可能是MD5的哈希值

经过尝试发现,其实就是字符1-25的MD5哈希值,因此我们可以把图片重命名一下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import os
import hashlib

files = [f for f in os.listdir('.') if f.endswith('.png')]

md5_map = {}
for i in range(1, 26):
    md5_hash = hashlib.md5(str(i).encode('utf-8')).hexdigest()
    md5_map[md5_hash + '.png'] = f'{i}.png'

for filename in files:
    if filename in md5_map:
        new_name = md5_map[filename]
        os.rename(filename, new_name)
        print(f'Renamed: {filename} -> {new_name}')

观察到某些图片并不是全是蓝色像素(0,0,255),因此写了个脚本提取像素点并转为Ascii码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from PIL import Image

def func1():
    res = ""
    for i in range(1,26):
        img_name = f"{i}.png"
        img = Image.open(img_name)
        w,h = img.size
        tmp_list = []
        pixel_list = []

        for y in range(h):
            for x in range(w):
                pixel = img.getpixel((x,y))
                pixel_list.append(pixel)
                if pixel != (0,0,255):
                    tmp_list.append((pixel[0],chr(pixel[0]),(x,y)))
                    res += chr(pixel[0])
        print(f"{img_name}: {tmp_list}")
        # print(pixel_list)
    print(res)

if __name__ == "__main__":
    func1()

运行以上脚本后得到如下内容(我打印输出了RGB值、Ascii码转换后的字符、像素的坐标):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1.png: [(45, '-', (13, 3)), (0, '\x00', (4, 7))]
2.png: [(101, 'e', (4, 3))]
3.png: [(98, 'b', (7, 15))]
4.png: [(103, 'g', (0, 7)), (56, '8', (12, 17))]
5.png: [(54, '6', (7, 12))]
6.png: [(53, '5', (10, 0)), (55, '7', (5, 3)), (52, '4', (16, 19))]
7.png: [(125, '}', (13, 7)), (51, '3', (5, 16))]
8.png: [(48, '0', (5, 3))]
9.png: [(45, '-', (6, 0)), (100, 'd', (19, 6)), (51, '3', (2, 11)), (102, 'f', (11, 17)), (123, '{', (19, 17))]
10.png: [(50, '2', (13, 10))]
11.png: []
12.png: [(99, 'c', (6, 0)), (100, 'd', (16, 2)), (97, 'a', (3, 6)), (102, 'f', (16, 7)), (45, '-', (4, 19))]
13.png: [(45, '-', (16, 0)), (100, 'd', (3, 19)), (99, 'c', (10, 19))]
14.png: [(52, '4', (3, 1)), (52, '4', (18, 1)), (98, 'b', (6, 3))]
15.png: [(97, 'a', (0, 0))]
16.png: [(53, '5', (9, 15))]
17.png: []
18.png: []
19.png: [(108, 'l', (12, 6)), (97, 'a', (1, 12))]
20.png: [(57, '9', (16, 12)), (101, 'e', (10, 15)), (101, 'e', (19, 15))]
21.png: [(99, 'c', (5, 10)), (49, '1', (15, 12))]
22.png: [(99, 'c', (1, 3)), (101, 'e', (6, 4))]
23.png: [(52, '4', (3, 3))]
24.png: []
25.png: [(57, '9', (19, 10))]
-ebg86574}30-d3f{2cdaf--dc44ba5la9eec1ce49

结合字符查看得到的字符串,很明显flag就是藏在这里了,但是不知道flag具体的排列规律

结合题面的信息,猜测肯定是有内网的主机向外网通信了,使用过滤器过滤一下

发现ip地址为10.211.55.7的主机嫌疑最大

imgs/image-20250502174412404.png

然后发现10.211.55.7主要和185.125.188.5591.189.91.43这两个主机通信了

并且通信的数据包还都是TLSv1.3加密的,因此猜测需要我们解密这些数据包才能得到flag的排列规律

题目名称 tag (2023 福建省职业院校技能大赛高职组信息安全管理与评估)

题目附件:https://pan.baidu.com/s/1TJrAnVSDirjdl3xk6wJRHA?pwd=ee6p 提取码: ee6p

参考链接:https://blog.csdn.net/m0_45155797/article/details/135027395

题目的内容就是要从下面这张图片中找出evidence2的字样

imgs/image-20241230185454044.jpeg

已经知道答案,但是不知道这个evidence2被出题人藏哪了,感觉是内幕题。。

题目名称 破译行动 (2024 ISCC 博弈对抗赛)

题目附件:https://pan.baidu.com/s/1GAhnDyy_2yplJeWibRsadg?pwd=py7u 提取码: py7u

赛后主办方给出了本题考察的知识点:

imgs/image-20250306115340070.jpeg

附件给了一张图片和一个TXT,其中TXT内容如下

不要忘记我们的接头暗号:58,20,36,40,32,60,48,88,42,46,70,21,42, 6,51,71,40,14,30,4,37,25,28,7,39,46,20,33

imgs/image-20241230190828836.png

图片打开内容如下

imgs/image-20241230190908870.png

用010打开发现图片末尾藏了以下数据,并且提示了后面那串base64是加密后的

IHaveEncryptedTheSignalToPreventLeakage:U2FsdGVkX18SCg3hRbbWKiIXLrevGD0Sv0aCNfGr5YEBzPi8f7oWRq5vQ5QziXjuYrfShzuxlEQe9qAN0SYZUU+cQLB3wREFNCyhjvhHTlt3dmTjDFElG3okDzg3Eu4Xj+2AINbme9zgOjdsJgpVZg==

imgs/image-20241230191013242.png

解密需要密钥,但是找不到密钥,猜测会和图片中的那个时间有关系,但是不知道具体什么关系。。

后来听去了线下颁奖的师傅说,主办方在赛后总结的时候说这道题好像和具体的经纬度有关系

因此去网上搜了天安门广场的具体经纬度如下:

1
2
39° 54′ 26.4″ N, 116° 23′ 27.9″ E
39.907333, 116.391083

题目名称 Sakura

题目附件: https://pan.baidu.com/s/1qQ94akilsReXwJeAgjltjg?pwd=s9dh 提取码: s9dh

题目附件给了一个secret.pcapng的流量包文件,打开发现主要是USBMS流量(U盘流量)

imgs/image-20250321192933723.png

然后翻看一下流量包发现其中分段传输了一张很大的PNG图片,因此尝试写个Python提取出图片的数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import subprocess
import os
import json

tshark_path = r"tshark"
file_path = "secret.pcapng"
output = ""
command = [
	tshark_path,  # 使用完整的 tshark 路径
	'-r', file_path,  # 读取指定的 pcapng 文件
	'-Y', 'frame.len == 65563',  # 过滤出 HTTP 数据包
	'-T', 'jsonraw',  # 输出为 JSON 格式
]
result = subprocess.run(
    command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
json_output = json.loads(result.stdout)
# print(json.dumps(json_output, indent=4))  # 这个输出是用来调试的,可以移除

# 遍历 JSON 数据并提取字段
for packet in json_output:
    layers = packet.get('_source', {}).get('layers', {})
    
    data = layers.get('frame_raw', ['None'])[0]
    # print(data[54:100])
    output += data[54:]

pic_data = bytes.fromhex(output)
with open("output.png", "wb") as f:
    f.write(pic_data)

图片最后一段的数据需要手动提取一下,把所有图片数据都提取出来后可以得到下图

imgs/image-20250321193130917.png

图片的像素看起来很复杂,然后尝试了zstegstegsolve,感觉不存在LSB隐写

也尝试了单图盲水印,没有得到什么有用的信息

0%