[{"data":1,"prerenderedAt":1173},["ShallowReactive",2],{"category-data-隐写":3},[4],{"id":5,"title":6,"body":7,"description":1158,"extension":1159,"meta":1160,"navigation":133,"ogImage":1162,"path":1169,"seo":1170,"stem":1171,"__hash__":1172},"content/blogs/20. ctf-misc-stego-forensics-deep-dive.md","隐写与取证深入实战：LSB 隐写与 ZIP 伪加密",{"type":8,"value":9,"toc":1148},"minimark",[10,15,24,28,81,85,90,93,101,105,112,310,324,328,333,339,455,458,464,468,485,492,496,501,506,510,513,519,526,530,535,573,575,581,584,590,594,599,656,659,665,668,706,708,714,717,721,730,785,787,793,800,804,809,814,817,915,922,928,939,943,1024,1028,1033,1038,1044,1047,1095,1098,1103,1110,1125,1128,1132,1144],[11,12,14],"h3",{"id":13},"一现实背景文件头与压缩包","一、现实背景：文件头与压缩包",[16,17,18,19,23],"p",{},"文件真实类型由文件头决定。ZIP 文件头是 ",[20,21,22],"code",{},"50 4B 03 04","，其内部结构里有\"加密标志位\"，出题人可以把标志位置 1 制造\"伪加密\"（数据其实没加密）。图片取证中，LSB 隐写把秘密藏在像素最低位。本文完整实现这两种技术。",[11,25,27],{"id":26},"二环境准备","二、环境准备",[29,30,35],"pre",{"className":31,"code":32,"language":33,"meta":34,"style":34},"language-bash shiki shiki-themes dracula","apt install file binutils binwalk\npip3 install pillow\ngem install zsteg\n","bash","",[20,36,37,59,70],{"__ignoreMap":34},[38,39,42,46,50,53,56],"span",{"class":40,"line":41},"line",1,[38,43,45],{"class":44},"sAOxA","apt",[38,47,49],{"class":48},"s-mGx"," install",[38,51,52],{"class":48}," file",[38,54,55],{"class":48}," binutils",[38,57,58],{"class":48}," binwalk\n",[38,60,62,65,67],{"class":40,"line":61},2,[38,63,64],{"class":44},"pip3",[38,66,49],{"class":48},[38,68,69],{"class":48}," pillow\n",[38,71,73,76,78],{"class":40,"line":72},3,[38,74,75],{"class":44},"gem",[38,77,49],{"class":48},[38,79,80],{"class":48}," zsteg\n",[11,82,84],{"id":83},"三核心技能-1lsb-隐写完整实现","三、核心技能 1：LSB 隐写（完整实现）",[86,87,89],"h4",{"id":88},"_31-原理","3.1 原理",[16,91,92],{},"像素每个通道 1 字节（0-255）。把字节最低位替换成秘密比特，值最多变化 1，肉眼不可察觉：",[29,94,99],{"className":95,"code":97,"language":98,"meta":34},[96],"language-text","原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101   ← 最低位写入 1,0,1\n","text",[20,100,97],{"__ignoreMap":34},[86,102,104],{"id":103},"_32-隐藏出题人视角","3.2 隐藏（出题人视角）",[16,106,107,111],{},[108,109,110],"strong",{},"目的","：理解\"把秘密写进像素最低位\"的实现细节，才能反过来写出提取脚本。",[29,113,117],{"className":114,"code":115,"language":116,"meta":34,"style":34},"language-python shiki shiki-themes dracula","from PIL import Image\n# PIL 是 Python 图像处理库，Image 模块负责打开、新建、保存图片。\n\nimg = Image.new('RGB', (8, 8), (200, 150, 100))\n# Image.new(mode, size, color)：新建图片。mode='RGB'，size=(8,8)，color 是初始色。\nmessage = \"flag{lsb_steganography}\"\n\ndata = message.encode() + b'\\x00'\n# encode()：字符串转字节；追加 b'\\x00' 作为结束符（提取时遇到 0 停止）。\nbits = ''.join(f'{b:08b}' for b in data)\n# 每个字节转成 8 位二进制字符串后拼接成一条长比特串。\npixels = list(img.getdata())\n# getdata()：取出所有像素，每项是 (r,g,b)。\n\nnew_pixels = []\ni = 0\nfor r, g, b in pixels:\n    if i \u003C len(bits):\n        r = (r & 0xfe) | int(bits[i]); i += 1\n        # r & 0xfe：最低位清零；| int(bits[i])：写入第 i 个秘密比特。\n    if i \u003C len(bits):\n        g = (g & 0xfe) | int(bits[i]); i += 1\n        # 绿色通道，同样处理。\n    if i \u003C len(bits):\n        b = (b & 0xfe) | int(bits[i]); i += 1\n        # 蓝色通道。三个通道依次消耗比特，直到消息写完。\n    new_pixels.append((r, g, b))\n\nimg.putdata(new_pixels)\n# putdata()：把修改后的像素写回图片。\nimg.save('hidden.png')\n# save()：保存为 PNG。\nprint(\"已生成 hidden.png\")\n","python",[20,118,119,124,129,135,141,147,153,158,164,170,176,182,188,194,199,205,211,217,223,229,235,240,246,252,257,263,269,275,280,286,292,298,304],{"__ignoreMap":34},[38,120,121],{"class":40,"line":41},[38,122,123],{},"from PIL import Image\n",[38,125,126],{"class":40,"line":61},[38,127,128],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、新建、保存图片。\n",[38,130,131],{"class":40,"line":72},[38,132,134],{"emptyLinePlaceholder":133},true,"\n",[38,136,138],{"class":40,"line":137},4,[38,139,140],{},"img = Image.new('RGB', (8, 8), (200, 150, 100))\n",[38,142,144],{"class":40,"line":143},5,[38,145,146],{},"# Image.new(mode, size, color)：新建图片。mode='RGB'，size=(8,8)，color 是初始色。\n",[38,148,150],{"class":40,"line":149},6,[38,151,152],{},"message = \"flag{lsb_steganography}\"\n",[38,154,156],{"class":40,"line":155},7,[38,157,134],{"emptyLinePlaceholder":133},[38,159,161],{"class":40,"line":160},8,[38,162,163],{},"data = message.encode() + b'\\x00'\n",[38,165,167],{"class":40,"line":166},9,[38,168,169],{},"# encode()：字符串转字节；追加 b'\\x00' 作为结束符（提取时遇到 0 停止）。\n",[38,171,173],{"class":40,"line":172},10,[38,174,175],{},"bits = ''.join(f'{b:08b}' for b in data)\n",[38,177,179],{"class":40,"line":178},11,[38,180,181],{},"# 每个字节转成 8 位二进制字符串后拼接成一条长比特串。\n",[38,183,185],{"class":40,"line":184},12,[38,186,187],{},"pixels = list(img.getdata())\n",[38,189,191],{"class":40,"line":190},13,[38,192,193],{},"# getdata()：取出所有像素，每项是 (r,g,b)。\n",[38,195,197],{"class":40,"line":196},14,[38,198,134],{"emptyLinePlaceholder":133},[38,200,202],{"class":40,"line":201},15,[38,203,204],{},"new_pixels = []\n",[38,206,208],{"class":40,"line":207},16,[38,209,210],{},"i = 0\n",[38,212,214],{"class":40,"line":213},17,[38,215,216],{},"for r, g, b in pixels:\n",[38,218,220],{"class":40,"line":219},18,[38,221,222],{},"    if i \u003C len(bits):\n",[38,224,226],{"class":40,"line":225},19,[38,227,228],{},"        r = (r & 0xfe) | int(bits[i]); i += 1\n",[38,230,232],{"class":40,"line":231},20,[38,233,234],{},"        # r & 0xfe：最低位清零；| int(bits[i])：写入第 i 个秘密比特。\n",[38,236,238],{"class":40,"line":237},21,[38,239,222],{},[38,241,243],{"class":40,"line":242},22,[38,244,245],{},"        g = (g & 0xfe) | int(bits[i]); i += 1\n",[38,247,249],{"class":40,"line":248},23,[38,250,251],{},"        # 绿色通道，同样处理。\n",[38,253,255],{"class":40,"line":254},24,[38,256,222],{},[38,258,260],{"class":40,"line":259},25,[38,261,262],{},"        b = (b & 0xfe) | int(bits[i]); i += 1\n",[38,264,266],{"class":40,"line":265},26,[38,267,268],{},"        # 蓝色通道。三个通道依次消耗比特，直到消息写完。\n",[38,270,272],{"class":40,"line":271},27,[38,273,274],{},"    new_pixels.append((r, g, b))\n",[38,276,278],{"class":40,"line":277},28,[38,279,134],{"emptyLinePlaceholder":133},[38,281,283],{"class":40,"line":282},29,[38,284,285],{},"img.putdata(new_pixels)\n",[38,287,289],{"class":40,"line":288},30,[38,290,291],{},"# putdata()：把修改后的像素写回图片。\n",[38,293,295],{"class":40,"line":294},31,[38,296,297],{},"img.save('hidden.png')\n",[38,299,301],{"class":40,"line":300},32,[38,302,303],{},"# save()：保存为 PNG。\n",[38,305,307],{"class":40,"line":306},33,[38,308,309],{},"print(\"已生成 hidden.png\")\n",[16,311,312,315,316,319,320,323],{},[20,313,314],{},"r & 0xfe"," 清零最低位（",[20,317,318],{},"0xfe = 11111110","），",[20,321,322],{},"| bit"," 写入。",[86,325,327],{"id":326},"_33-提取解题视角","3.3 提取（解题视角）",[16,329,330,332],{},[108,331,110],{},"：把像素最低位收集起来，每 8 位一组还原成字节，得到隐藏消息。",[16,334,335,338],{},[108,336,337],{},"思路","：读取像素 → 依次取 R、G、B 最低位 → 8 位拼 1 字节 → 遇到 0 结束。",[29,340,342],{"className":114,"code":341,"language":116,"meta":34,"style":34},"from PIL import Image\n\nimg = Image.open('hidden.png').convert('RGB')\n# Image.open：打开图片；convert('RGB')：统一为 RGB 模式。\nbits = []\nfor r, g, b in img.getdata():\n    bits += [r & 1, g & 1, b & 1]\n    # r & 1：取最低位（0 或 1），顺序与隐藏时一致。\n\ndata = bytearray()\nfor i in range(0, len(bits) - 7, 8):\n    # 每 8 个比特一组，步长为 8。\n    byte = 0\n    for bit in bits[i:i+8]:\n        byte = (byte \u003C\u003C 1) | bit\n        # 左移 1 位再或上当前比特，等价于二进制拼接。\n    if byte == 0:\n        # 字节 0 是隐藏时追加的结束符。\n        break\n    data.append(byte)\n\nprint(data.decode())\n# decode()：字节串解码成字符串。\n",[20,343,344,348,352,357,362,367,372,377,382,386,391,396,401,406,411,416,421,426,431,436,441,445,450],{"__ignoreMap":34},[38,345,346],{"class":40,"line":41},[38,347,123],{},[38,349,350],{"class":40,"line":61},[38,351,134],{"emptyLinePlaceholder":133},[38,353,354],{"class":40,"line":72},[38,355,356],{},"img = Image.open('hidden.png').convert('RGB')\n",[38,358,359],{"class":40,"line":137},[38,360,361],{},"# Image.open：打开图片；convert('RGB')：统一为 RGB 模式。\n",[38,363,364],{"class":40,"line":143},[38,365,366],{},"bits = []\n",[38,368,369],{"class":40,"line":149},[38,370,371],{},"for r, g, b in img.getdata():\n",[38,373,374],{"class":40,"line":155},[38,375,376],{},"    bits += [r & 1, g & 1, b & 1]\n",[38,378,379],{"class":40,"line":160},[38,380,381],{},"    # r & 1：取最低位（0 或 1），顺序与隐藏时一致。\n",[38,383,384],{"class":40,"line":166},[38,385,134],{"emptyLinePlaceholder":133},[38,387,388],{"class":40,"line":172},[38,389,390],{},"data = bytearray()\n",[38,392,393],{"class":40,"line":178},[38,394,395],{},"for i in range(0, len(bits) - 7, 8):\n",[38,397,398],{"class":40,"line":184},[38,399,400],{},"    # 每 8 个比特一组，步长为 8。\n",[38,402,403],{"class":40,"line":190},[38,404,405],{},"    byte = 0\n",[38,407,408],{"class":40,"line":196},[38,409,410],{},"    for bit in bits[i:i+8]:\n",[38,412,413],{"class":40,"line":201},[38,414,415],{},"        byte = (byte \u003C\u003C 1) | bit\n",[38,417,418],{"class":40,"line":207},[38,419,420],{},"        # 左移 1 位再或上当前比特，等价于二进制拼接。\n",[38,422,423],{"class":40,"line":213},[38,424,425],{},"    if byte == 0:\n",[38,427,428],{"class":40,"line":219},[38,429,430],{},"        # 字节 0 是隐藏时追加的结束符。\n",[38,432,433],{"class":40,"line":225},[38,434,435],{},"        break\n",[38,437,438],{"class":40,"line":231},[38,439,440],{},"    data.append(byte)\n",[38,442,443],{"class":40,"line":237},[38,444,134],{"emptyLinePlaceholder":133},[38,446,447],{"class":40,"line":242},[38,448,449],{},"print(data.decode())\n",[38,451,452],{"class":40,"line":248},[38,453,454],{},"# decode()：字节串解码成字符串。\n",[16,456,457],{},"实际输出：",[29,459,462],{"className":460,"code":461,"language":98,"meta":34},[96],"flag{lsb_steganography}\n",[20,463,461],{"__ignoreMap":34},[86,465,467],{"id":466},"_34-工具验证","3.4 工具验证",[29,469,471],{"className":31,"code":470,"language":33,"meta":34,"style":34},"zsteg -a hidden.png\n",[20,472,473],{"__ignoreMap":34},[38,474,475,478,482],{"class":40,"line":41},[38,476,477],{"class":44},"zsteg",[38,479,481],{"class":480},"sIQBb"," -a",[38,483,484],{"class":48}," hidden.png\n",[16,486,487,488,491],{},"预期输出中包含 ",[20,489,490],{},"flag{lsb_steganography}","。",[11,493,495],{"id":494},"四核心技能-2zip-伪加密完整实现","四、核心技能 2：ZIP 伪加密（完整实现）",[16,497,498,500],{},[108,499,110],{},"：理解 ZIP 的\"加密标志位\"结构，学会修复伪加密。",[16,502,503,505],{},[108,504,337],{},"：先看正常 zip 的十六进制，找到标志位位置；把加密位置 1 模拟伪加密（此时解压报\"需要密码\"）；再把位置 0 修复，验证能正常读出内容。",[86,507,509],{"id":508},"_41-zip-结构","4.1 ZIP 结构",[16,511,512],{},"本地文件头：",[29,514,517],{"className":515,"code":516,"language":98,"meta":34},[96],"偏移   字段\n0x00  签名 50 4B 03 04\n0x04  版本（2 字节）\n0x06  通用标志位（2 字节，第 0 位 = 加密位）\n",[20,518,516],{"__ignoreMap":34},[16,520,521,522,525],{},"中央目录头（签名 ",[20,523,524],{},"50 4B 01 02","）中标志位位于偏移 0x08。",[86,527,529],{"id":528},"_42-生成-zip-并查看结构","4.2 生成 zip 并查看结构",[16,531,532,534],{},[108,533,110],{},"：建立\"正常 zip\"的基线，确认本地文件头与中央目录头中标志位的位置。",[29,536,538],{"className":114,"code":537,"language":116,"meta":34,"style":34},"import zipfile\n\nwith zipfile.ZipFile('flag.zip', 'w') as z:\n    z.writestr('flag.txt', 'ctf{zip}')\n\ndata = open('flag.zip', 'rb').read()\nprint(' '.join(f'{b:02x}' for b in data[:60]))\n",[20,539,540,545,549,554,559,563,568],{"__ignoreMap":34},[38,541,542],{"class":40,"line":41},[38,543,544],{},"import zipfile\n",[38,546,547],{"class":40,"line":61},[38,548,134],{"emptyLinePlaceholder":133},[38,550,551],{"class":40,"line":72},[38,552,553],{},"with zipfile.ZipFile('flag.zip', 'w') as z:\n",[38,555,556],{"class":40,"line":137},[38,557,558],{},"    z.writestr('flag.txt', 'ctf{zip}')\n",[38,560,561],{"class":40,"line":143},[38,562,134],{"emptyLinePlaceholder":133},[38,564,565],{"class":40,"line":149},[38,566,567],{},"data = open('flag.zip', 'rb').read()\n",[38,569,570],{"class":40,"line":155},[38,571,572],{},"print(' '.join(f'{b:02x}' for b in data[:60]))\n",[16,574,457],{},[29,576,579],{"className":577,"code":578,"language":98,"meta":34},[96],"50 4b 03 04 14 00 00 00 00 00 d8 73 0e 5d b0 75 c1 31 08 00 00 00 08 00 00 00 08 00 00 00 66 6c 61 67 2e 74 78 74 63 74 66 7b 7a 69 70 7d 50 4b 01 02 14 03 14 00 00 00 00 00 d8 73\n",[20,580,578],{"__ignoreMap":34},[16,582,583],{},"解释：",[29,585,588],{"className":586,"code":587,"language":98,"meta":34},[96],"50 4b 03 04                    ← 本地文件头签名\n14 00                          ← 版本\n00 00                          ← 通用标志位（00 00 = 未加密）\n...\n50 4b 01 02                    ← 中央目录头签名\n14 03 14 00                    ← 版本信息\n00 00                          ← 中央目录标志位（未加密）\n",[20,589,587],{"__ignoreMap":34},[86,591,593],{"id":592},"_43-设置加密位伪加密","4.3 设置加密位（伪加密）",[16,595,596,598],{},[108,597,337],{},"：只改数据不加密，而是把\"加密标志位\"置 1，让解压程序误以为需要密码。",[29,600,602],{"className":114,"code":601,"language":116,"meta":34,"style":34},"data = bytearray(open('flag.zip', 'rb').read())\n\n# 本地文件头：偏移 6 处标志位置位\nidx = data.find(b'PK\\x03\\x04')\ndata[idx + 6] |= 1\n\n# 中央目录头：偏移 8 处标志位置位\nidx2 = data.find(b'PK\\x01\\x02')\ndata[idx2 + 8] |= 1\n\nopen('flag_enc.zip', 'wb').write(data)\n",[20,603,604,609,613,618,623,628,632,637,642,647,651],{"__ignoreMap":34},[38,605,606],{"class":40,"line":41},[38,607,608],{},"data = bytearray(open('flag.zip', 'rb').read())\n",[38,610,611],{"class":40,"line":61},[38,612,134],{"emptyLinePlaceholder":133},[38,614,615],{"class":40,"line":72},[38,616,617],{},"# 本地文件头：偏移 6 处标志位置位\n",[38,619,620],{"class":40,"line":137},[38,621,622],{},"idx = data.find(b'PK\\x03\\x04')\n",[38,624,625],{"class":40,"line":143},[38,626,627],{},"data[idx + 6] |= 1\n",[38,629,630],{"class":40,"line":149},[38,631,134],{"emptyLinePlaceholder":133},[38,633,634],{"class":40,"line":155},[38,635,636],{},"# 中央目录头：偏移 8 处标志位置位\n",[38,638,639],{"class":40,"line":160},[38,640,641],{},"idx2 = data.find(b'PK\\x01\\x02')\n",[38,643,644],{"class":40,"line":166},[38,645,646],{},"data[idx2 + 8] |= 1\n",[38,648,649],{"class":40,"line":172},[38,650,134],{"emptyLinePlaceholder":133},[38,652,653],{"class":40,"line":178},[38,654,655],{},"open('flag_enc.zip', 'wb').write(data)\n",[16,657,658],{},"修改后的 hexdump：",[29,660,663],{"className":661,"code":662,"language":98,"meta":34},[96],"50 4b 03 04 14 00 01 00 00 00 d8 73 ...   ← 本地标志位 01\n50 4b 01 02 14 03 14 00 01 00 00 00 d8 73 ...  ← 中央标志位 01\n",[20,664,662],{"__ignoreMap":34},[16,666,667],{},"尝试读取：",[29,669,671],{"className":114,"code":670,"language":116,"meta":34,"style":34},"import zipfile\n\ntry:\n    with zipfile.ZipFile('flag_enc.zip') as z:\n        print(z.read('flag.txt'))\nexcept Exception as e:\n    print(type(e).__name__, e)\n",[20,672,673,677,681,686,691,696,701],{"__ignoreMap":34},[38,674,675],{"class":40,"line":41},[38,676,544],{},[38,678,679],{"class":40,"line":61},[38,680,134],{"emptyLinePlaceholder":133},[38,682,683],{"class":40,"line":72},[38,684,685],{},"try:\n",[38,687,688],{"class":40,"line":137},[38,689,690],{},"    with zipfile.ZipFile('flag_enc.zip') as z:\n",[38,692,693],{"class":40,"line":143},[38,694,695],{},"        print(z.read('flag.txt'))\n",[38,697,698],{"class":40,"line":149},[38,699,700],{},"except Exception as e:\n",[38,702,703],{"class":40,"line":155},[38,704,705],{},"    print(type(e).__name__, e)\n",[16,707,457],{},[29,709,712],{"className":710,"code":711,"language":98,"meta":34},[96],"RuntimeError File 'flag.txt' is encrypted, password required for extraction\n",[20,713,711],{"__ignoreMap":34},[16,715,716],{},"程序要求密码——但数据根本没加密，这就是伪加密。",[86,718,720],{"id":719},"_44-修复","4.4 修复",[16,722,723,725,726,729],{},[108,724,337],{},"：把两处标志位最低位清零（",[20,727,728],{},"& 0xfe","），恢复成未加密状态。",[29,731,733],{"className":114,"code":732,"language":116,"meta":34,"style":34},"data = bytearray(open('flag_enc.zip', 'rb').read())\n\nidx = data.find(b'PK\\x03\\x04')\ndata[idx + 6] &= 0xfe          # 清除本地头加密位\nidx2 = data.find(b'PK\\x01\\x02')\ndata[idx2 + 8] &= 0xfe         # 清除中央目录加密位\n\nopen('flag_fixed.zip', 'wb').write(data)\n\nwith zipfile.ZipFile('flag_fixed.zip') as z:\n    print(z.read('flag.txt'))\n",[20,734,735,740,744,748,753,757,762,766,771,775,780],{"__ignoreMap":34},[38,736,737],{"class":40,"line":41},[38,738,739],{},"data = bytearray(open('flag_enc.zip', 'rb').read())\n",[38,741,742],{"class":40,"line":61},[38,743,134],{"emptyLinePlaceholder":133},[38,745,746],{"class":40,"line":72},[38,747,622],{},[38,749,750],{"class":40,"line":137},[38,751,752],{},"data[idx + 6] &= 0xfe          # 清除本地头加密位\n",[38,754,755],{"class":40,"line":143},[38,756,641],{},[38,758,759],{"class":40,"line":149},[38,760,761],{},"data[idx2 + 8] &= 0xfe         # 清除中央目录加密位\n",[38,763,764],{"class":40,"line":155},[38,765,134],{"emptyLinePlaceholder":133},[38,767,768],{"class":40,"line":160},[38,769,770],{},"open('flag_fixed.zip', 'wb').write(data)\n",[38,772,773],{"class":40,"line":166},[38,774,134],{"emptyLinePlaceholder":133},[38,776,777],{"class":40,"line":172},[38,778,779],{},"with zipfile.ZipFile('flag_fixed.zip') as z:\n",[38,781,782],{"class":40,"line":178},[38,783,784],{},"    print(z.read('flag.txt'))\n",[16,786,457],{},[29,788,791],{"className":789,"code":790,"language":98,"meta":34},[96],"b'ctf{zip}'\n",[20,792,790],{"__ignoreMap":34},[16,794,795,796,799],{},"实战提示：很多伪加密题只改本地头或中央目录头之一，",[20,797,798],{},"binwalk -e"," 常常直接绕过标志位提取，先试它再手工改。",[11,801,803],{"id":802},"五核心技能-3crc32-爆破","五、核心技能 3：CRC32 爆破",[16,805,806,808],{},[108,807,110],{},"：当压缩包内文件内容很短、又拿不到明文时，用校验值反推内容。",[16,810,811,813],{},[108,812,337],{},"：ZIP 记录每个文件的 CRC32（明文校验和）。枚举所有短内容，算 CRC 与记录值比对，相等即明文。",[16,815,816],{},"ZIP 中央目录记录每个文件的 CRC32。当文件内容很短（1-4 字符）时，枚举内容匹配校验值：",[29,818,820],{"className":114,"code":819,"language":116,"meta":34,"style":34},"import zipfile, zlib, itertools\n# zipfile：读写 zip；zlib：提供 crc32 校验函数；itertools：组合枚举。\n\nz = zipfile.ZipFile('flag.zip')\ntarget = z.getinfo('flag.txt').CRC\n# getinfo('flag.txt')：读取该文件的目录信息；.CRC 属性是它的 CRC32 值。\nprint(\"目标 CRC:\", target)\n\ncharset = 'ctf123{}_abcdefghijklmnopqrstuvwxyz'\n# 题目语境下的候选字符集，缩小枚举空间。\nfor length in range(1, 5):\n    for tup in itertools.product(charset, repeat=length):\n        # product(charset, repeat=length)：生成长度为 length 的所有组合。\n        s = ''.join(tup).encode()\n        # ''.join(tup)：组合元组拼成字符串；encode() 转字节。\n        if zlib.crc32(s) & 0xffffffff == target:\n            # zlib.crc32 返回有符号整数，& 0xffffffff 转成无符号再比对。\n            print(\"爆破结果:\", s)\n            exit()\n",[20,821,822,827,832,836,841,846,851,856,860,865,870,875,880,885,890,895,900,905,910],{"__ignoreMap":34},[38,823,824],{"class":40,"line":41},[38,825,826],{},"import zipfile, zlib, itertools\n",[38,828,829],{"class":40,"line":61},[38,830,831],{},"# zipfile：读写 zip；zlib：提供 crc32 校验函数；itertools：组合枚举。\n",[38,833,834],{"class":40,"line":72},[38,835,134],{"emptyLinePlaceholder":133},[38,837,838],{"class":40,"line":137},[38,839,840],{},"z = zipfile.ZipFile('flag.zip')\n",[38,842,843],{"class":40,"line":143},[38,844,845],{},"target = z.getinfo('flag.txt').CRC\n",[38,847,848],{"class":40,"line":149},[38,849,850],{},"# getinfo('flag.txt')：读取该文件的目录信息；.CRC 属性是它的 CRC32 值。\n",[38,852,853],{"class":40,"line":155},[38,854,855],{},"print(\"目标 CRC:\", target)\n",[38,857,858],{"class":40,"line":160},[38,859,134],{"emptyLinePlaceholder":133},[38,861,862],{"class":40,"line":166},[38,863,864],{},"charset = 'ctf123{}_abcdefghijklmnopqrstuvwxyz'\n",[38,866,867],{"class":40,"line":172},[38,868,869],{},"# 题目语境下的候选字符集，缩小枚举空间。\n",[38,871,872],{"class":40,"line":178},[38,873,874],{},"for length in range(1, 5):\n",[38,876,877],{"class":40,"line":184},[38,878,879],{},"    for tup in itertools.product(charset, repeat=length):\n",[38,881,882],{"class":40,"line":190},[38,883,884],{},"        # product(charset, repeat=length)：生成长度为 length 的所有组合。\n",[38,886,887],{"class":40,"line":196},[38,888,889],{},"        s = ''.join(tup).encode()\n",[38,891,892],{"class":40,"line":201},[38,893,894],{},"        # ''.join(tup)：组合元组拼成字符串；encode() 转字节。\n",[38,896,897],{"class":40,"line":207},[38,898,899],{},"        if zlib.crc32(s) & 0xffffffff == target:\n",[38,901,902],{"class":40,"line":213},[38,903,904],{},"            # zlib.crc32 返回有符号整数，& 0xffffffff 转成无符号再比对。\n",[38,906,907],{"class":40,"line":219},[38,908,909],{},"            print(\"爆破结果:\", s)\n",[38,911,912],{"class":40,"line":225},[38,913,914],{},"            exit()\n",[16,916,917,918,921],{},"实际输出（目标内容 ",[20,919,920],{},"ctf1","）：",[29,923,926],{"className":924,"code":925,"language":98,"meta":34},[96],"目标 CRC: 961973094\n爆破结果: b'ctf1'\n",[20,927,925],{"__ignoreMap":34},[16,929,930,931,934,935,938],{},"注意 ",[20,932,933],{},"zlib.crc32"," 返回有符号整数，需要 ",[20,936,937],{},"& 0xffffffff"," 归一化。",[11,940,942],{"id":941},"六必学工具","六、必学工具",[944,945,946,962],"table",{},[947,948,949],"thead",{},[950,951,952,956,959],"tr",{},[953,954,955],"th",{},"工具",[953,957,958],{},"核心功能",[953,960,961],{},"命令",[963,964,965,979,996,1012],"tbody",{},[950,966,967,971,974],{},[968,969,970],"td",{},"file",[968,972,973],{},"看类型",[968,975,976],{},[20,977,978],{},"file flag.png",[950,980,981,984,987],{},[968,982,983],{},"xxd",[968,985,986],{},"看文件头与结构",[968,988,989,992,993],{},[20,990,991],{},"xxd -l 16 flag.png","、",[20,994,995],{},"xxd flag.zip | head",[950,997,998,1001,1004],{},[968,999,1000],{},"binwalk",[968,1002,1003],{},"扫描/提取隐藏文件",[968,1005,1006,992,1009],{},[20,1007,1008],{},"binwalk flag.png",[20,1010,1011],{},"binwalk -e flag.png",[950,1013,1014,1016,1019],{},[968,1015,477],{},[968,1017,1018],{},"LSB 自动检测",[968,1020,1021],{},[20,1022,1023],{},"zsteg -a flag.png",[11,1025,1027],{"id":1026},"七小-ctf-实战综合题","七、小 CTF 实战：综合题",[16,1029,1030,1032],{},[108,1031,110],{},"：把前三节串成一道完整题目。",[16,1034,1035,1037],{},[108,1036,337],{},"：先排除简单情况（file/strings/binwalk），再怀疑 LSB 提取；若提取结果是 zip 头（PK），保存后检查伪加密。",[16,1039,1040,1041,491],{},"题目：",[20,1042,1043],{},"flag.png",[16,1045,1046],{},"第一步：",[29,1048,1050],{"className":31,"code":1049,"language":33,"meta":34,"style":34},"file flag.png          # PNG image data\nstrings flag.png | grep -i flag    # 无输出\nbinwalk flag.png                   # 无隐藏文件\n",[20,1051,1052,1063,1086],{"__ignoreMap":34},[38,1053,1054,1056,1059],{"class":40,"line":41},[38,1055,970],{"class":44},[38,1057,1058],{"class":48}," flag.png",[38,1060,1062],{"class":1061},"shSDL","          # PNG image data\n",[38,1064,1065,1068,1070,1074,1077,1080,1083],{"class":40,"line":61},[38,1066,1067],{"class":44},"strings",[38,1069,1058],{"class":48},[38,1071,1073],{"class":1072},"s0Tla"," |",[38,1075,1076],{"class":44}," grep",[38,1078,1079],{"class":480}," -i",[38,1081,1082],{"class":48}," flag",[38,1084,1085],{"class":1061},"    # 无输出\n",[38,1087,1088,1090,1092],{"class":40,"line":72},[38,1089,1000],{"class":44},[38,1091,1058],{"class":48},[38,1093,1094],{"class":1061},"                   # 无隐藏文件\n",[16,1096,1097],{},"第二步：怀疑 LSB，运行提取脚本：",[29,1099,1101],{"className":1100,"code":461,"language":98,"meta":34},[96],[20,1102,461],{"__ignoreMap":34},[16,1104,1105,1106,1109],{},"变体：若提取出的字节不是文本而是 ",[20,1107,1108],{},"PK"," 开头，把它保存为 zip，再检查是否伪加密：",[29,1111,1113],{"className":114,"code":1112,"language":116,"meta":34,"style":34},"with open('extracted.zip', 'wb') as f:\n    f.write(data)\n",[20,1114,1115,1120],{"__ignoreMap":34},[38,1116,1117],{"class":40,"line":41},[38,1118,1119],{},"with open('extracted.zip', 'wb') as f:\n",[38,1121,1122],{"class":40,"line":61},[38,1123,1124],{},"    f.write(data)\n",[16,1126,1127],{},"然后按第四节流程检查并修复标志位。",[11,1129,1131],{"id":1130},"八小结","八、小结",[16,1133,1134,1135,1137,1138,1140,1141,1143],{},"取证主线：",[20,1136,970],{}," 确认类型 → ",[20,1139,1067],{},"/",[20,1142,1000],{}," 排除简单情况 → LSB 提取 → 压缩包伪加密修复。这三板斧可覆盖大部分入门与中级 Misc 题。",[1145,1146,1147],"style",{},"html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}",{"title":34,"searchDepth":61,"depth":61,"links":1149},[1150,1151,1152,1153,1154,1155,1156,1157],{"id":13,"depth":72,"text":14},{"id":26,"depth":72,"text":27},{"id":83,"depth":72,"text":84},{"id":494,"depth":72,"text":495},{"id":802,"depth":72,"text":803},{"id":941,"depth":72,"text":942},{"id":1026,"depth":72,"text":1027},{"id":1130,"depth":72,"text":1131},"完整实现 LSB 隐写的隐藏与提取、ZIP 伪加密的十六进制修改与修复、CRC32 短内容爆破。","md",{"date":1161,"image":1162,"alt":1163,"tags":1164,"published":133},"14th Aug 2026","/blogs-img/blog2.png","隐写与取证深入实战",[1165,1166,1167,1168],"ctf","misc","隐写","example","/blogs/ctf-misc-stego-forensics-deep-dive",{"title":6,"description":1158},"blogs/20. ctf-misc-stego-forensics-deep-dive","o_6kN9OqAx31pkyKuqmJvPnMsOsMrKsw-PBDwWsaepk",1786796568811]