[{"data":1,"prerenderedAt":2087},["ShallowReactive",2],{"category-data-misc":3},[4,1062],{"id":5,"title":6,"body":7,"description":1049,"extension":1050,"meta":1051,"navigation":452,"ogImage":1053,"path":1058,"seo":1059,"stem":1060,"__hash__":1061},"content/blogs/13. ctf-misc-overview.md","CTF Misc 入门：文件头、binwalk 与 LSB 隐写",{"type":8,"value":9,"toc":1039},"minimark",[10,15,29,32,98,102,150,154,159,165,171,184,187,195,202,228,232,237,262,264,270,280,284,289,303,327,330,334,337,350,353,359,362,376,383,390,401,405,409,412,418,421,425,430,659,673,677,682,687,818,820,826,830,855,861,865,941,945,951,954,965,971,974,992,995,998,1008,1011,1014,1019,1023,1035],[11,12,14],"h3",{"id":13},"一现实背景扩展名不可信","一、现实背景：扩展名不可信",[16,17,18,19,23,24,28],"p",{},"操作系统按扩展名选择打开程序（",[20,21,22],"code",{},".png"," 用图片查看器），但文件的真实类型由文件头（magic bytes）决定。日常中\"把 zip 改成 png\"会让图片查看器报错；取证中则要反过来：",[25,26,27],"strong",{},"先看文件头，再信扩展名","。",[16,30,31],{},"必须记住的三个文件头：",[33,34,35,51],"table",{},[36,37,38],"thead",{},[39,40,41,45,48],"tr",{},[42,43,44],"th",{},"文件",[42,46,47],{},"文件头（hex）",[42,49,50],{},"明文",[52,53,54,70,83],"tbody",{},[39,55,56,60,65],{},[57,58,59],"td",{},"PNG",[57,61,62],{},[20,63,64],{},"89 50 4E 47 0D 0A 1A 0A",[57,66,67],{},[20,68,69],{},"‰PNG",[39,71,72,75,80],{},[57,73,74],{},"JPEG",[57,76,77],{},[20,78,79],{},"FF D8 FF",[57,81,82],{},"—",[39,84,85,88,93],{},[57,86,87],{},"ZIP",[57,89,90],{},[20,91,92],{},"50 4B 03 04",[57,94,95],{},[20,96,97],{},"PK",[11,99,101],{"id":100},"二环境准备","二、环境准备",[103,104,109],"pre",{"className":105,"code":106,"language":107,"meta":108,"style":108},"language-bash shiki shiki-themes dracula","apt install file binutils binwalk foremost exiftool\npip3 install pillow\n","bash","",[20,110,111,139],{"__ignoreMap":108},[112,113,116,120,124,127,130,133,136],"span",{"class":114,"line":115},"line",1,[112,117,119],{"class":118},"sAOxA","apt",[112,121,123],{"class":122},"s-mGx"," install",[112,125,126],{"class":122}," file",[112,128,129],{"class":122}," binutils",[112,131,132],{"class":122}," binwalk",[112,134,135],{"class":122}," foremost",[112,137,138],{"class":122}," exiftool\n",[112,140,142,145,147],{"class":114,"line":141},2,[112,143,144],{"class":118},"pip3",[112,146,123],{"class":122},[112,148,149],{"class":122}," pillow\n",[11,151,153],{"id":152},"三核心技能-1file-与-xxd","三、核心技能 1：file 与 xxd",[155,156,158],"h4",{"id":157},"_31-file看真实类型","3.1 file：看真实类型",[16,160,161,164],{},[25,162,163],{},"目的","：判断题目文件到底是图片、压缩包还是其他东西，避免被扩展名误导。",[16,166,167,170],{},[25,168,169],{},"验证方式","：看输出的第一段描述（PNG image data / Zip archive data / PDF document ...）。",[103,172,174],{"className":105,"code":173,"language":107,"meta":108,"style":108},"file hidden.png\n",[20,175,176],{"__ignoreMap":108},[112,177,178,181],{"class":114,"line":115},[112,179,180],{"class":118},"file",[112,182,183],{"class":122}," hidden.png\n",[16,185,186],{},"实际输出：",[103,188,193],{"className":189,"code":191,"language":192,"meta":108},[190],"language-text","hidden.png: PNG image data, 8 x 8, 8-bit/color RGB, non-interlaced\n","text",[20,194,191],{"__ignoreMap":108},[16,196,197,198,201],{},"如果输出是 ",[20,199,200],{},"Zip archive data","，说明文件其实是 zip：",[103,203,205],{"className":105,"code":204,"language":107,"meta":108,"style":108},"mv flag.png flag.zip && unzip flag.zip\n",[20,206,207],{"__ignoreMap":108},[112,208,209,212,215,218,222,225],{"class":114,"line":115},[112,210,211],{"class":118},"mv",[112,213,214],{"class":122}," flag.png",[112,216,217],{"class":122}," flag.zip",[112,219,221],{"class":220},"sCdxs"," && ",[112,223,224],{"class":118},"unzip",[112,226,227],{"class":122}," flag.zip\n",[155,229,231],{"id":230},"_32-xxd看文件头","3.2 xxd：看文件头",[16,233,234,236],{},[25,235,163],{},"：用肉眼确认文件开头的魔数，验证 file 的判断，也为手工分析文件结构做准备。",[103,238,240],{"className":105,"code":239,"language":107,"meta":108,"style":108},"xxd -l 16 hidden.png\n# -l 16：只显示前 16 个字节，看文件头足够。\n",[20,241,242,256],{"__ignoreMap":108},[112,243,244,247,251,254],{"class":114,"line":115},[112,245,246],{"class":118},"xxd",[112,248,250],{"class":249},"sIQBb"," -l",[112,252,253],{"class":249}," 16",[112,255,183],{"class":122},[112,257,258],{"class":114,"line":141},[112,259,261],{"class":260},"shSDL","# -l 16：只显示前 16 个字节，看文件头足够。\n",[16,263,186],{},[103,265,268],{"className":266,"code":267,"language":192,"meta":108},[190],"00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR\n",[20,269,267],{"__ignoreMap":108},[16,271,272,273,276,277,28],{},"开头的 ",[20,274,275],{},"89 50 4e 47 0d 0a 1a 0a"," 正是 PNG 魔数，右侧明文是 ",[20,278,279],{},".PNG",[155,281,283],{"id":282},"_33-strings搜字符串","3.3 strings：搜字符串",[16,285,286,288],{},[25,287,163],{},"：快速判断 flag 是不是直接以文本形式藏在文件里。",[16,290,291,294,295,298,299,302],{},[25,292,293],{},"思路","：",[20,296,297],{},"strings"," 把可打印字符串抽出来，",[20,300,301],{},"grep -i flag"," 过滤含 flag 的行。有输出 = 直接提取；无输出 = 可能是隐写，进入下一步。",[103,304,306],{"className":105,"code":305,"language":107,"meta":108,"style":108},"strings flag.png | grep -i flag\n",[20,307,308],{"__ignoreMap":108},[112,309,310,312,314,318,321,324],{"class":114,"line":115},[112,311,297],{"class":118},[112,313,214],{"class":122},[112,315,317],{"class":316},"s0Tla"," |",[112,319,320],{"class":118}," grep",[112,322,323],{"class":249}," -i",[112,325,326],{"class":122}," flag\n",[16,328,329],{},"有输出说明 flag 直接以文本形式藏在文件里；无输出则可能是隐写。",[11,331,333],{"id":332},"四核心技能-2binwalk-提取隐藏文件","四、核心技能 2：binwalk 提取隐藏文件",[16,335,336],{},"文件可以追加在其他文件之后（如图片尾部拼接 zip）。扫描：",[103,338,340],{"className":105,"code":339,"language":107,"meta":108,"style":108},"binwalk flag.png\n",[20,341,342],{"__ignoreMap":108},[112,343,344,347],{"class":114,"line":115},[112,345,346],{"class":118},"binwalk",[112,348,349],{"class":122}," flag.png\n",[16,351,352],{},"预期输出：",[103,354,357],{"className":355,"code":356,"language":192,"meta":108},[190],"DECIMAL  HEXADECIMAL  DESCRIPTION\n0        0x0           PNG image, 500 x 500\n12345    0x3039        Zip archive data\n",[20,358,356],{"__ignoreMap":108},[16,360,361],{},"提取：",[103,363,365],{"className":105,"code":364,"language":107,"meta":108,"style":108},"binwalk -e flag.png\n",[20,366,367],{"__ignoreMap":108},[112,368,369,371,374],{"class":114,"line":115},[112,370,346],{"class":118},[112,372,373],{"class":249}," -e",[112,375,349],{"class":122},[16,377,378,379,382],{},"会在当前目录生成 ",[20,380,381],{},"_flag.png.extracted"," 文件夹，里面是提取出的 zip。",[16,384,385,386,389],{},"备选工具 ",[20,387,388],{},"foremost","（按文件头暴力切割）：",[103,391,393],{"className":105,"code":392,"language":107,"meta":108,"style":108},"foremost flag.png\n",[20,394,395],{"__ignoreMap":108},[112,396,397,399],{"class":114,"line":115},[112,398,388],{"class":118},[112,400,349],{"class":122},[11,402,404],{"id":403},"五核心技能-3lsb-隐写完整实现","五、核心技能 3：LSB 隐写（完整实现）",[155,406,408],{"id":407},"_51-原理","5.1 原理",[16,410,411],{},"RGB 像素每个通道是一个 0-255 的字节。把每个字节的最低位（LSB）替换成秘密比特，像素值最多变化 1，肉眼不可察觉：",[103,413,416],{"className":414,"code":415,"language":192,"meta":108},[190],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101    ← 最低位依次写入 1,0,1\n",[20,417,415],{"__ignoreMap":108},[16,419,420],{},"一张 500×500 的图能藏 500×500×3 比特 ≈ 93 KB。",[155,422,424],{"id":423},"_52-隐藏出题人视角","5.2 隐藏（出题人视角）",[16,426,427,429],{},[25,428,163],{},"：理解\"把秘密写进像素最低位\"的具体实现，便于反向写出提取脚本。",[103,431,435],{"className":432,"code":433,"language":434,"meta":108,"style":108},"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)：新建一张图片。\n# mode='RGB' 表示三通道；size=(8,8)；color=(200,150,100) 是初始颜色。\nmessage = \"flag{lsb_steganography}\"\n\ndata = message.encode() + b'\\x00'\n# message.encode()：把字符串按 UTF-8 转成字节串。\n# 末尾追加 b'\\x00'（字节 0）作为结束符，提取时遇到 0 就停止。\nbits = ''.join(f'{b:08b}' for b in data)\n# 把每个字节转成 8 位二进制字符串并拼接。\n# 例：b'f'=0x66=01100110，8 位拼进 bits。\npixels = list(img.getdata())\n# img.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：把红色通道的最低位清零（0xfe=11111110）。\n        # | 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    # 修改后的像素放入新列表。\n\nimg.putdata(new_pixels)\n# putdata()：把新像素列表写回图片对象。\nimg.save('hidden.png')\n# save()：按扩展名格式保存为 PNG 文件。\nprint(\"已生成 hidden.png\")\n","python",[20,436,437,442,447,454,460,466,472,478,483,489,495,501,507,513,519,525,531,536,542,548,554,560,566,572,578,583,589,595,600,606,612,618,624,629,635,641,647,653],{"__ignoreMap":108},[112,438,439],{"class":114,"line":115},[112,440,441],{},"from PIL import Image\n",[112,443,444],{"class":114,"line":141},[112,445,446],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、修改、保存图片。\n",[112,448,450],{"class":114,"line":449},3,[112,451,453],{"emptyLinePlaceholder":452},true,"\n",[112,455,457],{"class":114,"line":456},4,[112,458,459],{},"img = Image.new('RGB', (8, 8), (200, 150, 100))\n",[112,461,463],{"class":114,"line":462},5,[112,464,465],{},"# Image.new(mode, size, color)：新建一张图片。\n",[112,467,469],{"class":114,"line":468},6,[112,470,471],{},"# mode='RGB' 表示三通道；size=(8,8)；color=(200,150,100) 是初始颜色。\n",[112,473,475],{"class":114,"line":474},7,[112,476,477],{},"message = \"flag{lsb_steganography}\"\n",[112,479,481],{"class":114,"line":480},8,[112,482,453],{"emptyLinePlaceholder":452},[112,484,486],{"class":114,"line":485},9,[112,487,488],{},"data = message.encode() + b'\\x00'\n",[112,490,492],{"class":114,"line":491},10,[112,493,494],{},"# message.encode()：把字符串按 UTF-8 转成字节串。\n",[112,496,498],{"class":114,"line":497},11,[112,499,500],{},"# 末尾追加 b'\\x00'（字节 0）作为结束符，提取时遇到 0 就停止。\n",[112,502,504],{"class":114,"line":503},12,[112,505,506],{},"bits = ''.join(f'{b:08b}' for b in data)\n",[112,508,510],{"class":114,"line":509},13,[112,511,512],{},"# 把每个字节转成 8 位二进制字符串并拼接。\n",[112,514,516],{"class":114,"line":515},14,[112,517,518],{},"# 例：b'f'=0x66=01100110，8 位拼进 bits。\n",[112,520,522],{"class":114,"line":521},15,[112,523,524],{},"pixels = list(img.getdata())\n",[112,526,528],{"class":114,"line":527},16,[112,529,530],{},"# img.getdata()：返回所有像素的迭代器，转成列表后每项是 (r,g,b) 三元组。\n",[112,532,534],{"class":114,"line":533},17,[112,535,453],{"emptyLinePlaceholder":452},[112,537,539],{"class":114,"line":538},18,[112,540,541],{},"new_pixels = []\n",[112,543,545],{"class":114,"line":544},19,[112,546,547],{},"i = 0\n",[112,549,551],{"class":114,"line":550},20,[112,552,553],{},"for r, g, b in pixels:\n",[112,555,557],{"class":114,"line":556},21,[112,558,559],{},"    if i \u003C len(bits):\n",[112,561,563],{"class":114,"line":562},22,[112,564,565],{},"        r = (r & 0xfe) | int(bits[i]); i += 1\n",[112,567,569],{"class":114,"line":568},23,[112,570,571],{},"        # r & 0xfe：把红色通道的最低位清零（0xfe=11111110）。\n",[112,573,575],{"class":114,"line":574},24,[112,576,577],{},"        # | int(bits[i])：把秘密比特写进最低位；i 是当前比特下标。\n",[112,579,581],{"class":114,"line":580},25,[112,582,559],{},[112,584,586],{"class":114,"line":585},26,[112,587,588],{},"        g = (g & 0xfe) | int(bits[i]); i += 1\n",[112,590,592],{"class":114,"line":591},27,[112,593,594],{},"        # 绿色通道同样处理。\n",[112,596,598],{"class":114,"line":597},28,[112,599,559],{},[112,601,603],{"class":114,"line":602},29,[112,604,605],{},"        b = (b & 0xfe) | int(bits[i]); i += 1\n",[112,607,609],{"class":114,"line":608},30,[112,610,611],{},"        # 蓝色通道同样处理。三个通道依次消耗秘密比特。\n",[112,613,615],{"class":114,"line":614},31,[112,616,617],{},"    new_pixels.append((r, g, b))\n",[112,619,621],{"class":114,"line":620},32,[112,622,623],{},"    # 修改后的像素放入新列表。\n",[112,625,627],{"class":114,"line":626},33,[112,628,453],{"emptyLinePlaceholder":452},[112,630,632],{"class":114,"line":631},34,[112,633,634],{},"img.putdata(new_pixels)\n",[112,636,638],{"class":114,"line":637},35,[112,639,640],{},"# putdata()：把新像素列表写回图片对象。\n",[112,642,644],{"class":114,"line":643},36,[112,645,646],{},"img.save('hidden.png')\n",[112,648,650],{"class":114,"line":649},37,[112,651,652],{},"# save()：按扩展名格式保存为 PNG 文件。\n",[112,654,656],{"class":114,"line":655},38,[112,657,658],{},"print(\"已生成 hidden.png\")\n",[16,660,661,664,665,668,669,672],{},[20,662,663],{},"r & 0xfe"," 把最低位清零（",[20,666,667],{},"0xfe = 11111110","），",[20,670,671],{},"| bit"," 写入目标比特。",[155,674,676],{"id":675},"_53-提取解题视角","5.3 提取（解题视角）",[16,678,679,681],{},[25,680,163],{},"：把像素最低位收集起来，按 8 位一组还原成字节，得到隐藏消息。",[16,683,684,686],{},[25,685,293],{},"：读取所有像素 → 依次取 R、G、B 的最低位 → 每 8 个比特拼成一个字节 → 遇到 0 结束。",[103,688,690],{"className":432,"code":689,"language":434,"meta":108,"style":108},"from PIL import Image\n\nimg = Image.open('hidden.png').convert('RGB')\n# Image.open(path)：打开图片文件。\n# .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    # 三个最低位依次追加到 bits，顺序与隐藏时一致。\n\ndata = bytearray()\nfor i in range(0, len(bits) - 7, 8):\n    # range 步长 8：每 8 个比特处理一次，组成 1 字节。\n    byte = 0\n    for bit in bits[i:i+8]:\n        byte = (byte \u003C\u003C 1) | bit\n        # 左移一位再或上新比特，等价于二进制拼接。\n        # 例：1,0,1 → ((1\u003C\u003C1)|0)=2, (2\u003C\u003C1)|1=5，即二进制 101。\n    if byte == 0:\n        # 字节 0 是隐藏时追加的结束符，到此停止。\n        break\n    data.append(byte)\n\nprint(data.decode())\n# decode()：字节串按 UTF-8 解码成字符串显示。\n",[20,691,692,696,700,705,710,715,720,725,730,735,740,744,749,754,759,764,769,774,779,784,789,794,799,804,808,813],{"__ignoreMap":108},[112,693,694],{"class":114,"line":115},[112,695,441],{},[112,697,698],{"class":114,"line":141},[112,699,453],{"emptyLinePlaceholder":452},[112,701,702],{"class":114,"line":449},[112,703,704],{},"img = Image.open('hidden.png').convert('RGB')\n",[112,706,707],{"class":114,"line":456},[112,708,709],{},"# Image.open(path)：打开图片文件。\n",[112,711,712],{"class":114,"line":462},[112,713,714],{},"# .convert('RGB')：统一转成 RGB 模式，避免调色板/透明度干扰。\n",[112,716,717],{"class":114,"line":468},[112,718,719],{},"bits = []\n",[112,721,722],{"class":114,"line":474},[112,723,724],{},"for r, g, b in img.getdata():\n",[112,726,727],{"class":114,"line":480},[112,728,729],{},"    bits += [r & 1, g & 1, b & 1]\n",[112,731,732],{"class":114,"line":485},[112,733,734],{},"    # r & 1：取出红色通道最低位（0 或 1），绿、蓝同理。\n",[112,736,737],{"class":114,"line":491},[112,738,739],{},"    # 三个最低位依次追加到 bits，顺序与隐藏时一致。\n",[112,741,742],{"class":114,"line":497},[112,743,453],{"emptyLinePlaceholder":452},[112,745,746],{"class":114,"line":503},[112,747,748],{},"data = bytearray()\n",[112,750,751],{"class":114,"line":509},[112,752,753],{},"for i in range(0, len(bits) - 7, 8):\n",[112,755,756],{"class":114,"line":515},[112,757,758],{},"    # range 步长 8：每 8 个比特处理一次，组成 1 字节。\n",[112,760,761],{"class":114,"line":521},[112,762,763],{},"    byte = 0\n",[112,765,766],{"class":114,"line":527},[112,767,768],{},"    for bit in bits[i:i+8]:\n",[112,770,771],{"class":114,"line":533},[112,772,773],{},"        byte = (byte \u003C\u003C 1) | bit\n",[112,775,776],{"class":114,"line":538},[112,777,778],{},"        # 左移一位再或上新比特，等价于二进制拼接。\n",[112,780,781],{"class":114,"line":544},[112,782,783],{},"        # 例：1,0,1 → ((1\u003C\u003C1)|0)=2, (2\u003C\u003C1)|1=5，即二进制 101。\n",[112,785,786],{"class":114,"line":550},[112,787,788],{},"    if byte == 0:\n",[112,790,791],{"class":114,"line":556},[112,792,793],{},"        # 字节 0 是隐藏时追加的结束符，到此停止。\n",[112,795,796],{"class":114,"line":562},[112,797,798],{},"        break\n",[112,800,801],{"class":114,"line":568},[112,802,803],{},"    data.append(byte)\n",[112,805,806],{"class":114,"line":574},[112,807,453],{"emptyLinePlaceholder":452},[112,809,810],{"class":114,"line":580},[112,811,812],{},"print(data.decode())\n",[112,814,815],{"class":114,"line":585},[112,816,817],{},"# decode()：字节串按 UTF-8 解码成字符串显示。\n",[16,819,186],{},[103,821,824],{"className":822,"code":823,"language":192,"meta":108},[190],"flag{lsb_steganography}\n",[20,825,823],{"__ignoreMap":108},[155,827,829],{"id":828},"_54-现成工具","5.4 现成工具",[103,831,833],{"className":105,"code":832,"language":107,"meta":108,"style":108},"gem install zsteg\nzsteg -a hidden.png\n",[20,834,835,845],{"__ignoreMap":108},[112,836,837,840,842],{"class":114,"line":115},[112,838,839],{"class":118},"gem",[112,841,123],{"class":122},[112,843,844],{"class":122}," zsteg\n",[112,846,847,850,853],{"class":114,"line":141},[112,848,849],{"class":118},"zsteg",[112,851,852],{"class":249}," -a",[112,854,183],{"class":122},[16,856,857,860],{},[20,858,859],{},"-a"," 全自动检测各种 LSB 变体。",[11,862,864],{"id":863},"六必学工具","六、必学工具",[33,866,867,880],{},[36,868,869],{},[39,870,871,874,877],{},[42,872,873],{},"工具",[42,875,876],{},"核心功能",[42,878,879],{},"命令",[52,881,882,894,907,926],{},[39,883,884,886,889],{},[57,885,180],{},[57,887,888],{},"看类型、看编码、看无后缀文件",[57,890,891],{},[20,892,893],{},"file flag",[39,895,896,899,902],{},[57,897,898],{},"xxd / hexdump",[57,900,901],{},"查看文件头、指定长度",[57,903,904],{},[20,905,906],{},"xxd -l 16 flag.png",[39,908,909,911,914],{},[57,910,297],{},[57,912,913],{},"提取字符串、指定长度、管道过滤",[57,915,916,919,920,919,923],{},[20,917,918],{},"strings flag.png","、",[20,921,922],{},"strings -n 3 a",[20,924,925],{},"strings a | grep flag",[39,927,928,930,933],{},[57,929,346],{},[57,931,932],{},"扫描、提取",[57,934,935,919,938],{},[20,936,937],{},"binwalk flag.png",[20,939,940],{},"binwalk -e flag.png",[11,942,944],{"id":943},"七小-ctf-实战藏了-flag-的-png","七、小 CTF 实战：藏了 flag 的 PNG",[16,946,947,948,28],{},"题目：",[20,949,950],{},"flag.png",[16,952,953],{},"第一步：",[103,955,957],{"className":105,"code":956,"language":107,"meta":108,"style":108},"file flag.png\n",[20,958,959],{"__ignoreMap":108},[112,960,961,963],{"class":114,"line":115},[112,962,180],{"class":118},[112,964,349],{"class":122},[16,966,967,968,28],{},"输出：",[20,969,970],{},"PNG image data",[16,972,973],{},"第二步：",[103,975,976],{"className":105,"code":305,"language":107,"meta":108,"style":108},[20,977,978],{"__ignoreMap":108},[112,979,980,982,984,986,988,990],{"class":114,"line":115},[112,981,297],{"class":118},[112,983,214],{"class":122},[112,985,317],{"class":316},[112,987,320],{"class":118},[112,989,323],{"class":249},[112,991,326],{"class":122},[16,993,994],{},"无输出。",[16,996,997],{},"第三步：",[103,999,1000],{"className":105,"code":339,"language":107,"meta":108,"style":108},[20,1001,1002],{"__ignoreMap":108},[112,1003,1004,1006],{"class":114,"line":115},[112,1005,346],{"class":118},[112,1007,349],{"class":122},[16,1009,1010],{},"无隐藏文件。",[16,1012,1013],{},"第四步：怀疑 LSB，运行提取脚本，输出：",[103,1015,1017],{"className":1016,"code":823,"language":192,"meta":108},[190],[20,1018,823],{"__ignoreMap":108},[11,1020,1022],{"id":1021},"八小结","八、小结",[16,1024,1025,1026,1028,1029,1031,1032,1034],{},"Misc 入门三板斧：",[20,1027,180],{}," 看类型、",[20,1030,297],{},"/",[20,1033,346],{}," 排除简单情况、LSB 脚本提数据。音频隐写（Audacity 频谱图）、ZIP 伪加密、USB 流量分析在《隐写与取证深入实战》中完整展开。",[1036,1037,1038],"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 .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}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":108,"searchDepth":141,"depth":141,"links":1040},[1041,1042,1043,1044,1045,1046,1047,1048],{"id":13,"depth":449,"text":14},{"id":100,"depth":449,"text":101},{"id":152,"depth":449,"text":153},{"id":332,"depth":449,"text":333},{"id":403,"depth":449,"text":404},{"id":863,"depth":449,"text":864},{"id":943,"depth":449,"text":944},{"id":1021,"depth":449,"text":1022},"杂项三板斧：file/xxd 识别文件头、binwalk 提取隐藏文件、Python 实现 LSB 隐写的隐藏与提取。","md",{"date":1052,"image":1053,"alt":1054,"tags":1055,"published":452},"14th Aug 2026","/blogs-img/blog4.png","CTF Misc 入门",[1056,1057],"ctf","misc","/blogs/ctf-misc-overview",{"title":6,"description":1049},"blogs/13. ctf-misc-overview","XI4JQUKrJS4uak8Rm3ioTk2L2WukeCdhI54Ldj4FkRo",{"id":1063,"title":1064,"body":1065,"description":2076,"extension":1050,"meta":2077,"navigation":452,"ogImage":2078,"path":2083,"seo":2084,"stem":2085,"__hash__":2086},"content/blogs/20. ctf-misc-stego-forensics-deep-dive.md","隐写与取证深入实战：LSB 隐写与 ZIP 伪加密",{"type":8,"value":1066,"toc":2066},[1067,1071,1077,1079,1113,1117,1121,1124,1130,1134,1139,1286,1296,1300,1305,1310,1413,1415,1420,1424,1437,1443,1447,1452,1457,1461,1464,1470,1477,1481,1486,1524,1526,1532,1535,1541,1545,1550,1607,1610,1616,1619,1657,1659,1665,1668,1672,1681,1736,1738,1744,1751,1755,1760,1765,1768,1866,1873,1879,1890,1892,1957,1961,1966,1971,1975,1977,2018,2021,2026,2032,2047,2050,2052,2063],[11,1068,1070],{"id":1069},"一现实背景文件头与压缩包","一、现实背景：文件头与压缩包",[16,1072,1073,1074,1076],{},"文件真实类型由文件头决定。ZIP 文件头是 ",[20,1075,92],{},"，其内部结构里有\"加密标志位\"，出题人可以把标志位置 1 制造\"伪加密\"（数据其实没加密）。图片取证中，LSB 隐写把秘密藏在像素最低位。本文完整实现这两种技术。",[11,1078,101],{"id":100},[103,1080,1082],{"className":105,"code":1081,"language":107,"meta":108,"style":108},"apt install file binutils binwalk\npip3 install pillow\ngem install zsteg\n",[20,1083,1084,1097,1105],{"__ignoreMap":108},[112,1085,1086,1088,1090,1092,1094],{"class":114,"line":115},[112,1087,119],{"class":118},[112,1089,123],{"class":122},[112,1091,126],{"class":122},[112,1093,129],{"class":122},[112,1095,1096],{"class":122}," binwalk\n",[112,1098,1099,1101,1103],{"class":114,"line":141},[112,1100,144],{"class":118},[112,1102,123],{"class":122},[112,1104,149],{"class":122},[112,1106,1107,1109,1111],{"class":114,"line":449},[112,1108,839],{"class":118},[112,1110,123],{"class":122},[112,1112,844],{"class":122},[11,1114,1116],{"id":1115},"三核心技能-1lsb-隐写完整实现","三、核心技能 1：LSB 隐写（完整实现）",[155,1118,1120],{"id":1119},"_31-原理","3.1 原理",[16,1122,1123],{},"像素每个通道 1 字节（0-255）。把字节最低位替换成秘密比特，值最多变化 1，肉眼不可察觉：",[103,1125,1128],{"className":1126,"code":1127,"language":192,"meta":108},[190],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101   ← 最低位写入 1,0,1\n",[20,1129,1127],{"__ignoreMap":108},[155,1131,1133],{"id":1132},"_32-隐藏出题人视角","3.2 隐藏（出题人视角）",[16,1135,1136,1138],{},[25,1137,163],{},"：理解\"把秘密写进像素最低位\"的实现细节，才能反过来写出提取脚本。",[103,1140,1142],{"className":432,"code":1141,"language":434,"meta":108,"style":108},"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",[20,1143,1144,1148,1153,1157,1161,1166,1170,1174,1178,1183,1187,1192,1196,1201,1205,1209,1213,1217,1221,1225,1230,1234,1238,1243,1247,1251,1256,1260,1264,1268,1273,1277,1282],{"__ignoreMap":108},[112,1145,1146],{"class":114,"line":115},[112,1147,441],{},[112,1149,1150],{"class":114,"line":141},[112,1151,1152],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、新建、保存图片。\n",[112,1154,1155],{"class":114,"line":449},[112,1156,453],{"emptyLinePlaceholder":452},[112,1158,1159],{"class":114,"line":456},[112,1160,459],{},[112,1162,1163],{"class":114,"line":462},[112,1164,1165],{},"# Image.new(mode, size, color)：新建图片。mode='RGB'，size=(8,8)，color 是初始色。\n",[112,1167,1168],{"class":114,"line":468},[112,1169,477],{},[112,1171,1172],{"class":114,"line":474},[112,1173,453],{"emptyLinePlaceholder":452},[112,1175,1176],{"class":114,"line":480},[112,1177,488],{},[112,1179,1180],{"class":114,"line":485},[112,1181,1182],{},"# encode()：字符串转字节；追加 b'\\x00' 作为结束符（提取时遇到 0 停止）。\n",[112,1184,1185],{"class":114,"line":491},[112,1186,506],{},[112,1188,1189],{"class":114,"line":497},[112,1190,1191],{},"# 每个字节转成 8 位二进制字符串后拼接成一条长比特串。\n",[112,1193,1194],{"class":114,"line":503},[112,1195,524],{},[112,1197,1198],{"class":114,"line":509},[112,1199,1200],{},"# getdata()：取出所有像素，每项是 (r,g,b)。\n",[112,1202,1203],{"class":114,"line":515},[112,1204,453],{"emptyLinePlaceholder":452},[112,1206,1207],{"class":114,"line":521},[112,1208,541],{},[112,1210,1211],{"class":114,"line":527},[112,1212,547],{},[112,1214,1215],{"class":114,"line":533},[112,1216,553],{},[112,1218,1219],{"class":114,"line":538},[112,1220,559],{},[112,1222,1223],{"class":114,"line":544},[112,1224,565],{},[112,1226,1227],{"class":114,"line":550},[112,1228,1229],{},"        # r & 0xfe：最低位清零；| int(bits[i])：写入第 i 个秘密比特。\n",[112,1231,1232],{"class":114,"line":556},[112,1233,559],{},[112,1235,1236],{"class":114,"line":562},[112,1237,588],{},[112,1239,1240],{"class":114,"line":568},[112,1241,1242],{},"        # 绿色通道，同样处理。\n",[112,1244,1245],{"class":114,"line":574},[112,1246,559],{},[112,1248,1249],{"class":114,"line":580},[112,1250,605],{},[112,1252,1253],{"class":114,"line":585},[112,1254,1255],{},"        # 蓝色通道。三个通道依次消耗比特，直到消息写完。\n",[112,1257,1258],{"class":114,"line":591},[112,1259,617],{},[112,1261,1262],{"class":114,"line":597},[112,1263,453],{"emptyLinePlaceholder":452},[112,1265,1266],{"class":114,"line":602},[112,1267,634],{},[112,1269,1270],{"class":114,"line":608},[112,1271,1272],{},"# putdata()：把修改后的像素写回图片。\n",[112,1274,1275],{"class":114,"line":614},[112,1276,646],{},[112,1278,1279],{"class":114,"line":620},[112,1280,1281],{},"# save()：保存为 PNG。\n",[112,1283,1284],{"class":114,"line":626},[112,1285,658],{},[16,1287,1288,1290,1291,668,1293,1295],{},[20,1289,663],{}," 清零最低位（",[20,1292,667],{},[20,1294,671],{}," 写入。",[155,1297,1299],{"id":1298},"_33-提取解题视角","3.3 提取（解题视角）",[16,1301,1302,1304],{},[25,1303,163],{},"：把像素最低位收集起来，每 8 位一组还原成字节，得到隐藏消息。",[16,1306,1307,1309],{},[25,1308,293],{},"：读取像素 → 依次取 R、G、B 最低位 → 8 位拼 1 字节 → 遇到 0 结束。",[103,1311,1313],{"className":432,"code":1312,"language":434,"meta":108,"style":108},"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,1314,1315,1319,1323,1327,1332,1336,1340,1344,1349,1353,1357,1361,1366,1370,1374,1378,1383,1387,1392,1396,1400,1404,1408],{"__ignoreMap":108},[112,1316,1317],{"class":114,"line":115},[112,1318,441],{},[112,1320,1321],{"class":114,"line":141},[112,1322,453],{"emptyLinePlaceholder":452},[112,1324,1325],{"class":114,"line":449},[112,1326,704],{},[112,1328,1329],{"class":114,"line":456},[112,1330,1331],{},"# Image.open：打开图片；convert('RGB')：统一为 RGB 模式。\n",[112,1333,1334],{"class":114,"line":462},[112,1335,719],{},[112,1337,1338],{"class":114,"line":468},[112,1339,724],{},[112,1341,1342],{"class":114,"line":474},[112,1343,729],{},[112,1345,1346],{"class":114,"line":480},[112,1347,1348],{},"    # r & 1：取最低位（0 或 1），顺序与隐藏时一致。\n",[112,1350,1351],{"class":114,"line":485},[112,1352,453],{"emptyLinePlaceholder":452},[112,1354,1355],{"class":114,"line":491},[112,1356,748],{},[112,1358,1359],{"class":114,"line":497},[112,1360,753],{},[112,1362,1363],{"class":114,"line":503},[112,1364,1365],{},"    # 每 8 个比特一组，步长为 8。\n",[112,1367,1368],{"class":114,"line":509},[112,1369,763],{},[112,1371,1372],{"class":114,"line":515},[112,1373,768],{},[112,1375,1376],{"class":114,"line":521},[112,1377,773],{},[112,1379,1380],{"class":114,"line":527},[112,1381,1382],{},"        # 左移 1 位再或上当前比特，等价于二进制拼接。\n",[112,1384,1385],{"class":114,"line":533},[112,1386,788],{},[112,1388,1389],{"class":114,"line":538},[112,1390,1391],{},"        # 字节 0 是隐藏时追加的结束符。\n",[112,1393,1394],{"class":114,"line":544},[112,1395,798],{},[112,1397,1398],{"class":114,"line":550},[112,1399,803],{},[112,1401,1402],{"class":114,"line":556},[112,1403,453],{"emptyLinePlaceholder":452},[112,1405,1406],{"class":114,"line":562},[112,1407,812],{},[112,1409,1410],{"class":114,"line":568},[112,1411,1412],{},"# decode()：字节串解码成字符串。\n",[16,1414,186],{},[103,1416,1418],{"className":1417,"code":823,"language":192,"meta":108},[190],[20,1419,823],{"__ignoreMap":108},[155,1421,1423],{"id":1422},"_34-工具验证","3.4 工具验证",[103,1425,1427],{"className":105,"code":1426,"language":107,"meta":108,"style":108},"zsteg -a hidden.png\n",[20,1428,1429],{"__ignoreMap":108},[112,1430,1431,1433,1435],{"class":114,"line":115},[112,1432,849],{"class":118},[112,1434,852],{"class":249},[112,1436,183],{"class":122},[16,1438,1439,1440,28],{},"预期输出中包含 ",[20,1441,1442],{},"flag{lsb_steganography}",[11,1444,1446],{"id":1445},"四核心技能-2zip-伪加密完整实现","四、核心技能 2：ZIP 伪加密（完整实现）",[16,1448,1449,1451],{},[25,1450,163],{},"：理解 ZIP 的\"加密标志位\"结构，学会修复伪加密。",[16,1453,1454,1456],{},[25,1455,293],{},"：先看正常 zip 的十六进制，找到标志位位置；把加密位置 1 模拟伪加密（此时解压报\"需要密码\"）；再把位置 0 修复，验证能正常读出内容。",[155,1458,1460],{"id":1459},"_41-zip-结构","4.1 ZIP 结构",[16,1462,1463],{},"本地文件头：",[103,1465,1468],{"className":1466,"code":1467,"language":192,"meta":108},[190],"偏移   字段\n0x00  签名 50 4B 03 04\n0x04  版本（2 字节）\n0x06  通用标志位（2 字节，第 0 位 = 加密位）\n",[20,1469,1467],{"__ignoreMap":108},[16,1471,1472,1473,1476],{},"中央目录头（签名 ",[20,1474,1475],{},"50 4B 01 02","）中标志位位于偏移 0x08。",[155,1478,1480],{"id":1479},"_42-生成-zip-并查看结构","4.2 生成 zip 并查看结构",[16,1482,1483,1485],{},[25,1484,163],{},"：建立\"正常 zip\"的基线，确认本地文件头与中央目录头中标志位的位置。",[103,1487,1489],{"className":432,"code":1488,"language":434,"meta":108,"style":108},"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,1490,1491,1496,1500,1505,1510,1514,1519],{"__ignoreMap":108},[112,1492,1493],{"class":114,"line":115},[112,1494,1495],{},"import zipfile\n",[112,1497,1498],{"class":114,"line":141},[112,1499,453],{"emptyLinePlaceholder":452},[112,1501,1502],{"class":114,"line":449},[112,1503,1504],{},"with zipfile.ZipFile('flag.zip', 'w') as z:\n",[112,1506,1507],{"class":114,"line":456},[112,1508,1509],{},"    z.writestr('flag.txt', 'ctf{zip}')\n",[112,1511,1512],{"class":114,"line":462},[112,1513,453],{"emptyLinePlaceholder":452},[112,1515,1516],{"class":114,"line":468},[112,1517,1518],{},"data = open('flag.zip', 'rb').read()\n",[112,1520,1521],{"class":114,"line":474},[112,1522,1523],{},"print(' '.join(f'{b:02x}' for b in data[:60]))\n",[16,1525,186],{},[103,1527,1530],{"className":1528,"code":1529,"language":192,"meta":108},[190],"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,1531,1529],{"__ignoreMap":108},[16,1533,1534],{},"解释：",[103,1536,1539],{"className":1537,"code":1538,"language":192,"meta":108},[190],"50 4b 03 04                    ← 本地文件头签名\n14 00                          ← 版本\n00 00                          ← 通用标志位（00 00 = 未加密）\n...\n50 4b 01 02                    ← 中央目录头签名\n14 03 14 00                    ← 版本信息\n00 00                          ← 中央目录标志位（未加密）\n",[20,1540,1538],{"__ignoreMap":108},[155,1542,1544],{"id":1543},"_43-设置加密位伪加密","4.3 设置加密位（伪加密）",[16,1546,1547,1549],{},[25,1548,293],{},"：只改数据不加密，而是把\"加密标志位\"置 1，让解压程序误以为需要密码。",[103,1551,1553],{"className":432,"code":1552,"language":434,"meta":108,"style":108},"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,1554,1555,1560,1564,1569,1574,1579,1583,1588,1593,1598,1602],{"__ignoreMap":108},[112,1556,1557],{"class":114,"line":115},[112,1558,1559],{},"data = bytearray(open('flag.zip', 'rb').read())\n",[112,1561,1562],{"class":114,"line":141},[112,1563,453],{"emptyLinePlaceholder":452},[112,1565,1566],{"class":114,"line":449},[112,1567,1568],{},"# 本地文件头：偏移 6 处标志位置位\n",[112,1570,1571],{"class":114,"line":456},[112,1572,1573],{},"idx = data.find(b'PK\\x03\\x04')\n",[112,1575,1576],{"class":114,"line":462},[112,1577,1578],{},"data[idx + 6] |= 1\n",[112,1580,1581],{"class":114,"line":468},[112,1582,453],{"emptyLinePlaceholder":452},[112,1584,1585],{"class":114,"line":474},[112,1586,1587],{},"# 中央目录头：偏移 8 处标志位置位\n",[112,1589,1590],{"class":114,"line":480},[112,1591,1592],{},"idx2 = data.find(b'PK\\x01\\x02')\n",[112,1594,1595],{"class":114,"line":485},[112,1596,1597],{},"data[idx2 + 8] |= 1\n",[112,1599,1600],{"class":114,"line":491},[112,1601,453],{"emptyLinePlaceholder":452},[112,1603,1604],{"class":114,"line":497},[112,1605,1606],{},"open('flag_enc.zip', 'wb').write(data)\n",[16,1608,1609],{},"修改后的 hexdump：",[103,1611,1614],{"className":1612,"code":1613,"language":192,"meta":108},[190],"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,1615,1613],{"__ignoreMap":108},[16,1617,1618],{},"尝试读取：",[103,1620,1622],{"className":432,"code":1621,"language":434,"meta":108,"style":108},"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,1623,1624,1628,1632,1637,1642,1647,1652],{"__ignoreMap":108},[112,1625,1626],{"class":114,"line":115},[112,1627,1495],{},[112,1629,1630],{"class":114,"line":141},[112,1631,453],{"emptyLinePlaceholder":452},[112,1633,1634],{"class":114,"line":449},[112,1635,1636],{},"try:\n",[112,1638,1639],{"class":114,"line":456},[112,1640,1641],{},"    with zipfile.ZipFile('flag_enc.zip') as z:\n",[112,1643,1644],{"class":114,"line":462},[112,1645,1646],{},"        print(z.read('flag.txt'))\n",[112,1648,1649],{"class":114,"line":468},[112,1650,1651],{},"except Exception as e:\n",[112,1653,1654],{"class":114,"line":474},[112,1655,1656],{},"    print(type(e).__name__, e)\n",[16,1658,186],{},[103,1660,1663],{"className":1661,"code":1662,"language":192,"meta":108},[190],"RuntimeError File 'flag.txt' is encrypted, password required for extraction\n",[20,1664,1662],{"__ignoreMap":108},[16,1666,1667],{},"程序要求密码——但数据根本没加密，这就是伪加密。",[155,1669,1671],{"id":1670},"_44-修复","4.4 修复",[16,1673,1674,1676,1677,1680],{},[25,1675,293],{},"：把两处标志位最低位清零（",[20,1678,1679],{},"& 0xfe","），恢复成未加密状态。",[103,1682,1684],{"className":432,"code":1683,"language":434,"meta":108,"style":108},"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,1685,1686,1691,1695,1699,1704,1708,1713,1717,1722,1726,1731],{"__ignoreMap":108},[112,1687,1688],{"class":114,"line":115},[112,1689,1690],{},"data = bytearray(open('flag_enc.zip', 'rb').read())\n",[112,1692,1693],{"class":114,"line":141},[112,1694,453],{"emptyLinePlaceholder":452},[112,1696,1697],{"class":114,"line":449},[112,1698,1573],{},[112,1700,1701],{"class":114,"line":456},[112,1702,1703],{},"data[idx + 6] &= 0xfe          # 清除本地头加密位\n",[112,1705,1706],{"class":114,"line":462},[112,1707,1592],{},[112,1709,1710],{"class":114,"line":468},[112,1711,1712],{},"data[idx2 + 8] &= 0xfe         # 清除中央目录加密位\n",[112,1714,1715],{"class":114,"line":474},[112,1716,453],{"emptyLinePlaceholder":452},[112,1718,1719],{"class":114,"line":480},[112,1720,1721],{},"open('flag_fixed.zip', 'wb').write(data)\n",[112,1723,1724],{"class":114,"line":485},[112,1725,453],{"emptyLinePlaceholder":452},[112,1727,1728],{"class":114,"line":491},[112,1729,1730],{},"with zipfile.ZipFile('flag_fixed.zip') as z:\n",[112,1732,1733],{"class":114,"line":497},[112,1734,1735],{},"    print(z.read('flag.txt'))\n",[16,1737,186],{},[103,1739,1742],{"className":1740,"code":1741,"language":192,"meta":108},[190],"b'ctf{zip}'\n",[20,1743,1741],{"__ignoreMap":108},[16,1745,1746,1747,1750],{},"实战提示：很多伪加密题只改本地头或中央目录头之一，",[20,1748,1749],{},"binwalk -e"," 常常直接绕过标志位提取，先试它再手工改。",[11,1752,1754],{"id":1753},"五核心技能-3crc32-爆破","五、核心技能 3：CRC32 爆破",[16,1756,1757,1759],{},[25,1758,163],{},"：当压缩包内文件内容很短、又拿不到明文时，用校验值反推内容。",[16,1761,1762,1764],{},[25,1763,293],{},"：ZIP 记录每个文件的 CRC32（明文校验和）。枚举所有短内容，算 CRC 与记录值比对，相等即明文。",[16,1766,1767],{},"ZIP 中央目录记录每个文件的 CRC32。当文件内容很短（1-4 字符）时，枚举内容匹配校验值：",[103,1769,1771],{"className":432,"code":1770,"language":434,"meta":108,"style":108},"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,1772,1773,1778,1783,1787,1792,1797,1802,1807,1811,1816,1821,1826,1831,1836,1841,1846,1851,1856,1861],{"__ignoreMap":108},[112,1774,1775],{"class":114,"line":115},[112,1776,1777],{},"import zipfile, zlib, itertools\n",[112,1779,1780],{"class":114,"line":141},[112,1781,1782],{},"# zipfile：读写 zip；zlib：提供 crc32 校验函数；itertools：组合枚举。\n",[112,1784,1785],{"class":114,"line":449},[112,1786,453],{"emptyLinePlaceholder":452},[112,1788,1789],{"class":114,"line":456},[112,1790,1791],{},"z = zipfile.ZipFile('flag.zip')\n",[112,1793,1794],{"class":114,"line":462},[112,1795,1796],{},"target = z.getinfo('flag.txt').CRC\n",[112,1798,1799],{"class":114,"line":468},[112,1800,1801],{},"# getinfo('flag.txt')：读取该文件的目录信息；.CRC 属性是它的 CRC32 值。\n",[112,1803,1804],{"class":114,"line":474},[112,1805,1806],{},"print(\"目标 CRC:\", target)\n",[112,1808,1809],{"class":114,"line":480},[112,1810,453],{"emptyLinePlaceholder":452},[112,1812,1813],{"class":114,"line":485},[112,1814,1815],{},"charset = 'ctf123{}_abcdefghijklmnopqrstuvwxyz'\n",[112,1817,1818],{"class":114,"line":491},[112,1819,1820],{},"# 题目语境下的候选字符集，缩小枚举空间。\n",[112,1822,1823],{"class":114,"line":497},[112,1824,1825],{},"for length in range(1, 5):\n",[112,1827,1828],{"class":114,"line":503},[112,1829,1830],{},"    for tup in itertools.product(charset, repeat=length):\n",[112,1832,1833],{"class":114,"line":509},[112,1834,1835],{},"        # product(charset, repeat=length)：生成长度为 length 的所有组合。\n",[112,1837,1838],{"class":114,"line":515},[112,1839,1840],{},"        s = ''.join(tup).encode()\n",[112,1842,1843],{"class":114,"line":521},[112,1844,1845],{},"        # ''.join(tup)：组合元组拼成字符串；encode() 转字节。\n",[112,1847,1848],{"class":114,"line":527},[112,1849,1850],{},"        if zlib.crc32(s) & 0xffffffff == target:\n",[112,1852,1853],{"class":114,"line":533},[112,1854,1855],{},"            # zlib.crc32 返回有符号整数，& 0xffffffff 转成无符号再比对。\n",[112,1857,1858],{"class":114,"line":538},[112,1859,1860],{},"            print(\"爆破结果:\", s)\n",[112,1862,1863],{"class":114,"line":544},[112,1864,1865],{},"            exit()\n",[16,1867,1868,1869,1872],{},"实际输出（目标内容 ",[20,1870,1871],{},"ctf1","）：",[103,1874,1877],{"className":1875,"code":1876,"language":192,"meta":108},[190],"目标 CRC: 961973094\n爆破结果: b'ctf1'\n",[20,1878,1876],{"__ignoreMap":108},[16,1880,1881,1882,1885,1886,1889],{},"注意 ",[20,1883,1884],{},"zlib.crc32"," 返回有符号整数，需要 ",[20,1887,1888],{},"& 0xffffffff"," 归一化。",[11,1891,864],{"id":863},[33,1893,1894,1904],{},[36,1895,1896],{},[39,1897,1898,1900,1902],{},[42,1899,873],{},[42,1901,876],{},[42,1903,879],{},[52,1905,1906,1918,1932,1945],{},[39,1907,1908,1910,1913],{},[57,1909,180],{},[57,1911,1912],{},"看类型",[57,1914,1915],{},[20,1916,1917],{},"file flag.png",[39,1919,1920,1922,1925],{},[57,1921,246],{},[57,1923,1924],{},"看文件头与结构",[57,1926,1927,919,1929],{},[20,1928,906],{},[20,1930,1931],{},"xxd flag.zip | head",[39,1933,1934,1936,1939],{},[57,1935,346],{},[57,1937,1938],{},"扫描/提取隐藏文件",[57,1940,1941,919,1943],{},[20,1942,937],{},[20,1944,940],{},[39,1946,1947,1949,1952],{},[57,1948,849],{},[57,1950,1951],{},"LSB 自动检测",[57,1953,1954],{},[20,1955,1956],{},"zsteg -a flag.png",[11,1958,1960],{"id":1959},"七小-ctf-实战综合题","七、小 CTF 实战：综合题",[16,1962,1963,1965],{},[25,1964,163],{},"：把前三节串成一道完整题目。",[16,1967,1968,1970],{},[25,1969,293],{},"：先排除简单情况（file/strings/binwalk），再怀疑 LSB 提取；若提取结果是 zip 头（PK），保存后检查伪加密。",[16,1972,947,1973,28],{},[20,1974,950],{},[16,1976,953],{},[103,1978,1980],{"className":105,"code":1979,"language":107,"meta":108,"style":108},"file flag.png          # PNG image data\nstrings flag.png | grep -i flag    # 无输出\nbinwalk flag.png                   # 无隐藏文件\n",[20,1981,1982,1991,2009],{"__ignoreMap":108},[112,1983,1984,1986,1988],{"class":114,"line":115},[112,1985,180],{"class":118},[112,1987,214],{"class":122},[112,1989,1990],{"class":260},"          # PNG image data\n",[112,1992,1993,1995,1997,1999,2001,2003,2006],{"class":114,"line":141},[112,1994,297],{"class":118},[112,1996,214],{"class":122},[112,1998,317],{"class":316},[112,2000,320],{"class":118},[112,2002,323],{"class":249},[112,2004,2005],{"class":122}," flag",[112,2007,2008],{"class":260},"    # 无输出\n",[112,2010,2011,2013,2015],{"class":114,"line":449},[112,2012,346],{"class":118},[112,2014,214],{"class":122},[112,2016,2017],{"class":260},"                   # 无隐藏文件\n",[16,2019,2020],{},"第二步：怀疑 LSB，运行提取脚本：",[103,2022,2024],{"className":2023,"code":823,"language":192,"meta":108},[190],[20,2025,823],{"__ignoreMap":108},[16,2027,2028,2029,2031],{},"变体：若提取出的字节不是文本而是 ",[20,2030,97],{}," 开头，把它保存为 zip，再检查是否伪加密：",[103,2033,2035],{"className":432,"code":2034,"language":434,"meta":108,"style":108},"with open('extracted.zip', 'wb') as f:\n    f.write(data)\n",[20,2036,2037,2042],{"__ignoreMap":108},[112,2038,2039],{"class":114,"line":115},[112,2040,2041],{},"with open('extracted.zip', 'wb') as f:\n",[112,2043,2044],{"class":114,"line":141},[112,2045,2046],{},"    f.write(data)\n",[16,2048,2049],{},"然后按第四节流程检查并修复标志位。",[11,2051,1022],{"id":1021},[16,2053,2054,2055,2057,2058,1031,2060,2062],{},"取证主线：",[20,2056,180],{}," 确认类型 → ",[20,2059,297],{},[20,2061,346],{}," 排除简单情况 → LSB 提取 → 压缩包伪加密修复。这三板斧可覆盖大部分入门与中级 Misc 题。",[1036,2064,2065],{},"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":108,"searchDepth":141,"depth":141,"links":2067},[2068,2069,2070,2071,2072,2073,2074,2075],{"id":1069,"depth":449,"text":1070},{"id":100,"depth":449,"text":101},{"id":1115,"depth":449,"text":1116},{"id":1445,"depth":449,"text":1446},{"id":1753,"depth":449,"text":1754},{"id":863,"depth":449,"text":864},{"id":1959,"depth":449,"text":1960},{"id":1021,"depth":449,"text":1022},"完整实现 LSB 隐写的隐藏与提取、ZIP 伪加密的十六进制修改与修复、CRC32 短内容爆破。",{"date":1052,"image":2078,"alt":2079,"tags":2080,"published":452},"/blogs-img/blog2.png","隐写与取证深入实战",[1056,1057,2081,2082],"隐写","example","/blogs/ctf-misc-stego-forensics-deep-dive",{"title":1064,"description":2076},"blogs/20. ctf-misc-stego-forensics-deep-dive","o_6kN9OqAx31pkyKuqmJvPnMsOsMrKsw-PBDwWsaepk",1786796568559]